You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2026/02/15 21:16:40 UTC

(jena) branch main updated (e4628dacbb -> e87cf2d7a1)

This is an automated email from the ASF dual-hosted git repository.

andy pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


    from e4628dacbb Production-annotated Turtle Grammar
     new c83e440e22 GH-3751: OpExecutorTDB: Test input on entry. Wrap in QueryIterFailed handling
     new e87cf2d7a1 GH-3755: QueryIterPlainWrapper: synchronize access to closeIterator()

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../engine/iterator/QueryIterPlainWrapper.java     |   4 +-
 .../sparql/engine/main/StageGeneratorGeneric.java  |  48 ++--
 .../apache/jena/tdb1/solver/OpExecutorTDB1.java    | 163 ++++++--------
 .../jena/tdb1/solver/StageGeneratorDirectTDB.java  |  39 ++--
 .../apache/jena/tdb1/store/Test_SPARQL_TDB1.java   |  21 +-
 .../apache/jena/tdb2/solver/OpExecutorTDB2.java    | 243 +++++++++------------
 .../jena/tdb2/solver/StageGeneratorDirectTDB.java  |   2 +-
 .../apache/jena/tdb2/store/Test_SPARQL_TDB.java    | 117 +++++-----
 8 files changed, 295 insertions(+), 342 deletions(-)


(jena) 02/02: GH-3755: QueryIterPlainWrapper: synchronize access to closeIterator()

Posted by an...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit e87cf2d7a1a54dcf69a2116d64dc0866adf011f0
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Feb 15 16:12:22 2026 +0000

    GH-3755: QueryIterPlainWrapper: synchronize access to closeIterator()
---
 .../org/apache/jena/sparql/engine/iterator/QueryIterPlainWrapper.java | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterPlainWrapper.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterPlainWrapper.java
index 5edd7943cb..ddaec075ea 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterPlainWrapper.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterPlainWrapper.java
@@ -64,8 +64,10 @@ public class QueryIterPlainWrapper extends QueryIter
     @Override
     protected Binding moveToNextBinding() { return iterator.next(); }
 
+    // Synchronized to prevent race conditions when abort() and close() compete
+    // to close the iterator concurrent by calling closeIterator()
     @Override
-    protected void closeIterator() {
+    protected synchronized void closeIterator() {
         if ( iterator != null ) {
             // In case we wrapped a QueryIterator or a Jena graph ExtendedIterator.
             // Includes the effect of NiceIterator.close(iterator)


(jena) 01/02: GH-3751: OpExecutorTDB: Test input on entry. Wrap in QueryIterFailed handling

Posted by an...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit c83e440e2277d674319a2be4d9dcb559f4b4a0a2
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Feb 15 16:04:09 2026 +0000

    GH-3751: OpExecutorTDB: Test input on entry. Wrap in QueryIterFailed handling
---
 .../sparql/engine/main/StageGeneratorGeneric.java  |  48 ++--
 .../apache/jena/tdb1/solver/OpExecutorTDB1.java    | 163 ++++++--------
 .../jena/tdb1/solver/StageGeneratorDirectTDB.java  |  39 ++--
 .../apache/jena/tdb1/store/Test_SPARQL_TDB1.java   |  21 +-
 .../apache/jena/tdb2/solver/OpExecutorTDB2.java    | 243 +++++++++------------
 .../jena/tdb2/solver/StageGeneratorDirectTDB.java  |   2 +-
 .../apache/jena/tdb2/store/Test_SPARQL_TDB.java    | 117 +++++-----
 7 files changed, 292 insertions(+), 341 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/StageGeneratorGeneric.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/StageGeneratorGeneric.java
index 2ddb212fb4..14092374c6 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/StageGeneratorGeneric.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/StageGeneratorGeneric.java
@@ -63,38 +63,30 @@ public class StageGeneratorGeneric implements StageGenerator {
     protected QueryIterator execute(BasicPattern pattern, ReorderTransformation reorder,
                                     QueryIterator input, ExecutionContext execCxt) {
         Explain.explain(pattern, execCxt.getContext()) ;
+        try {
+            if ( ! input.hasNext() )
+                return input ;
 
-         if ( reorder != null && pattern.size() >= 2 ) {
-            // If pattern size is 0 or 1, nothing to do.
-            BasicPattern bgp2 = pattern ;
+            if ( reorder != null && pattern.size() >= 2 ) {
+                // If pattern size is 0 or 1, nothing to do.
+                BasicPattern bgp2 = pattern ;
 
-            // Try to ground the pattern
-            if ( ! input.isJoinIdentity() ) {
-                QueryIterPeek peek = QueryIterPeek.create(input, execCxt) ;
-                // And now use this one
-                input = peek ;
-                Binding b ;
-                // Eager access may fail e.g. due to timeout.
-                try {
-                    b = peek.peek() ;
-                } catch (Exception e) {
-                    return new QueryIterFailed(input, execCxt, e);
+                // Try to ground the pattern
+                if ( ! input.isJoinIdentity() ) {
+                    QueryIterPeek peek = QueryIterPeek.create(input, execCxt) ;
+                    // And now use this one
+                    input = peek ;
+                    Binding b = peek.peek() ;
+                    bgp2 = Substitute.substitute(pattern, b) ;
                 }
-                bgp2 = Substitute.substitute(pattern, b) ;
+                ReorderProc reorderProc = reorder.reorderIndexes(bgp2) ;
+                pattern = reorderProc.reorder(pattern) ;
             }
-            ReorderProc reorderProc = reorder.reorderIndexes(bgp2) ;
-            pattern = reorderProc.reorder(pattern) ;
-        } else {
-            // Eager access may fail e.g. due to timeout.
-            try {
-                if ( ! input.hasNext() )
-                    return input ;
-            } catch (Exception e) {
-                return new QueryIterFailed(input, execCxt, e);
-            }
-        }
 
-        Explain.explain("Reorder/generic", pattern, execCxt.getContext()) ;
-        return PatternMatchData.execute(execCxt.getActiveGraph(), pattern, input, null, execCxt) ;
+            Explain.explain("Reorder/generic", pattern, execCxt.getContext()) ;
+            return PatternMatchData.execute(execCxt.getActiveGraph(), pattern, input, null, execCxt) ;
+        } catch (Exception e) {
+            return new QueryIterFailed(input, execCxt, e);
+        }
     }
 }
diff --git a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/OpExecutorTDB1.java b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/OpExecutorTDB1.java
index 9b41596b53..a7eff6f9fc 100644
--- a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/OpExecutorTDB1.java
+++ b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/OpExecutorTDB1.java
@@ -27,7 +27,6 @@ import org.apache.jena.atlas.lib.tuple.Tuple;
 import org.apache.jena.atlas.logging.Log;
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
-import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.algebra.Op;
 import org.apache.jena.sparql.algebra.op.*;
 import org.apache.jena.sparql.algebra.optimize.TransformFilterPlacement;
@@ -42,6 +41,7 @@ import org.apache.jena.sparql.engine.iterator.QueryIterPeek;
 import org.apache.jena.sparql.engine.main.OpExecutor;
 import org.apache.jena.sparql.engine.main.OpExecutorFactory;
 import org.apache.jena.sparql.engine.main.QC;
+import org.apache.jena.sparql.engine.main.StageGenerator;
 import org.apache.jena.sparql.engine.main.iterator.QueryIterGraph;
 import org.apache.jena.sparql.engine.optimizer.reorder.ReorderProc;
 import org.apache.jena.sparql.engine.optimizer.reorder.ReorderTransformation;
@@ -53,28 +53,28 @@ import org.apache.jena.tdb1.store.NodeId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** TDB executor for algebra expressions.  It is the standard ARQ executor
- *  except for basic graph patterns and filtered basic graph patterns (currently).
+/**
+ * TDB1 executor for algebra expressions.  It is the standard ARQ executor
+ * except for basic graph patterns and filtered basic graph patterns (currently).
  *
- * See also: StageGeneratorDirectTDB, a non-reordering
+ * See also: {@link StageGeneratorDirectTDB}, a non-reordering {@link StageGenerator}
  */
 public class OpExecutorTDB1 extends OpExecutor
 {
     private static final Logger log = LoggerFactory.getLogger(OpExecutorTDB1.class);
 
-    public final static OpExecutorFactory OpExecFactoryTDB = new OpExecutorFactory()
-    {
+    public final static OpExecutorFactory OpExecFactoryTDB = new OpExecutorFactory() {
         @Override
-        public OpExecutor create(ExecutionContext execCxt)
-        { return new OpExecutorTDB1(execCxt); }
+        public OpExecutor create(ExecutionContext execCxt) {
+            return new OpExecutorTDB1(execCxt);
+        }
     };
 
     private final boolean isForTDB;
 
     // A new compile object is created for each op compilation.
     // So the execCxt is changing as we go through the query-compile-execute process
-    public OpExecutorTDB1(ExecutionContext execCxt)
-    {
+    public OpExecutorTDB1(ExecutionContext execCxt) {
         super(execCxt);
         // NB. The dataset may be a TDB one, or a general one.
         // Any merged union graph magic (for a TDB dataset was handled
@@ -92,8 +92,7 @@ public class OpExecutorTDB1 extends OpExecutor
     }
 
     @Override
-    protected QueryIterator execute(OpFilter opFilter, QueryIterator input)
-    {
+    protected QueryIterator execute(OpFilter opFilter, QueryIterator input) {
         if ( ! isForTDB )
             return super.execute(opFilter, input);
 
@@ -101,8 +100,7 @@ public class OpExecutorTDB1 extends OpExecutor
         // Where does ARQ catch this?
 
         // (filter (bgp ...))
-        if ( OpBGP.isBGP(opFilter.getSubOp()) )
-        {
+        if ( OpBGP.isBGP(opFilter.getSubOp()) ) {
             // Still may be a TDB graph in a non-TDB dataset (e.g. a named model)
             GraphTDB graph = (GraphTDB)execCxt.getActiveGraph();
             OpBGP opBGP = (OpBGP)opFilter.getSubOp();
@@ -110,24 +108,20 @@ public class OpExecutorTDB1 extends OpExecutor
         }
 
         // (filter (quadpattern ...))
-        if ( opFilter.getSubOp() instanceof OpQuadPattern quadPattern )
-        {
+        if ( opFilter.getSubOp() instanceof OpQuadPattern quadPattern ) {
             DatasetGraphTDB ds = (DatasetGraphTDB)execCxt.getDataset();
-            return optimizeExecuteQuads(ds, input,
-                                        quadPattern.getGraphNode(), quadPattern.getBasicPattern(),
-                                        opFilter.getExprs(), execCxt);
+            return optimizeExecuteQuads(ds, input, quadPattern.getGraphNode(), quadPattern.getBasicPattern(), opFilter.getExprs(), execCxt);
         }
 
         // (filter (anything else))
         return super.execute(opFilter, input);
-        }
+    }
 
     // ---- Triple patterns
 
     @Override
-    protected QueryIterator execute(OpBGP opBGP, QueryIterator input)
-    {
-        if ( ! isForTDB )
+    protected QueryIterator execute(OpBGP opBGP, QueryIterator input) {
+        if ( !isForTDB )
             return super.execute(opBGP, input);
 
         GraphTDB graph = (GraphTDB)execCxt.getActiveGraph();
@@ -136,15 +130,9 @@ public class OpExecutorTDB1 extends OpExecutor
     }
 
     @Override
-    protected QueryIterator execute(OpQuadPattern quadPattern, QueryIterator input)
-    {
-        if ( ! isForTDB )
+    protected QueryIterator execute(OpQuadPattern quadPattern, QueryIterator input) {
+        if ( !isForTDB )
             return super.execute(quadPattern, input);
-
-    //        DatasetGraph dg = execCxt.getDataset();
-    //        if ( ! ( dg instanceof DatasetGraphTDB ) )
-    //            throw new InternalErrorException("Not a TDB backed dataset in quad pattern execution");
-
         DatasetGraphTDB ds = (DatasetGraphTDB)execCxt.getDataset();
         BasicPattern bgp = quadPattern.getBasicPattern();
         Node gn = quadPattern.getGraphNode();
@@ -152,16 +140,15 @@ public class OpExecutorTDB1 extends OpExecutor
     }
 
     @Override
-    protected QueryIterator execute(OpGraph opGraph, QueryIterator input)
-    {
-        // Path evaluation or dataset sets which do not go straight to the DatasetGraphTDB
+    protected QueryIterator execute(OpGraph opGraph, QueryIterator input) {
+        // Path evaluation or dataset sets which do not go straight to the
+        // DatasetGraphTDB
         return new QueryIterGraph(input, opGraph, execCxt);
     }
 
     /** Execute a BGP (and filters) on a TDB graph, which may be in default storage or it may be a named graph */
     private static QueryIterator executeBGP(GraphTDB graph, OpBGP opBGP, QueryIterator input, ExprList exprs,
-                                            ExecutionContext execCxt)
-    {
+                                            ExecutionContext execCxt) {
         DatasetGraphTDB dsgtdb = graph.getDatasetGraphTDB();
         // Is it the real default graph (normal route or explicitly named)?
         if ( ! isDefaultGraphStorage(graph.getGraphName()))
@@ -177,10 +164,11 @@ public class OpExecutorTDB1 extends OpExecutor
     /** Execute, with optimization, a basic graph pattern on the default graph storage */
     private static QueryIterator optimizeExecuteTriples(DatasetGraphTDB dsgtdb, QueryIterator input,
                                                         BasicPattern pattern, ExprList exprs,
-                                                        ExecutionContext execCxt)
-    {
-        if ( ! input.hasNext() )
-            return input;
+                                                        ExecutionContext execCxt) {
+        try {
+            if ( ! input.hasNext() )
+                return input;
+        } catch (Exception ex) { return new QueryIterFailed(input, execCxt, ex); }
 
         // -- Input
         // Must pass this iterator into the next stage.
@@ -188,8 +176,7 @@ public class OpExecutorTDB1 extends OpExecutor
         {
             // Must be 2 or triples to reorder.
             ReorderTransformation transform = dsgtdb.getReorderTransform();
-            if ( transform != null )
-            {
+            if ( transform != null ) {
                 QueryIterPeek peek = QueryIterPeek.create(input, execCxt);
                 input = peek; // Must pass on
                 try {
@@ -211,24 +198,23 @@ public class OpExecutorTDB1 extends OpExecutor
     }
 
     /** Execute, with optimization, a quad pattern */
-    private static QueryIterator optimizeExecuteQuads(DatasetGraphTDB dsgtdb,
-                                                      QueryIterator input,
-                                                      Node gn, BasicPattern bgp,
-                                                      ExprList exprs, ExecutionContext execCxt)
-    {
-        // ---- Graph names with special meaning.
+    private static QueryIterator optimizeExecuteQuads(DatasetGraphTDB dsgtdb, QueryIterator input,
+                                                      Node gn, BasicPattern bgp, ExprList exprs, ExecutionContext execCxt) {
+        try {
+            if ( ! input.hasNext() )
+                return input;
+        } catch (Exception ex) { return new QueryIterFailed(input, execCxt, ex); }
 
+        // ---- Graph names with special meaning.
         gn = decideGraphNode(gn, execCxt);
         if ( gn == null )
             return optimizeExecuteTriples(dsgtdb, input, bgp, exprs, execCxt);
 
         // ---- Execute quads+filters
-        if ( bgp.size() >= 2 )
-        {
+        if ( bgp.size() >= 2 ) {
             ReorderTransformation transform = dsgtdb.getReorderTransform();
 
-            if ( transform != null )
-            {
+            if ( transform != null ) {
                 QueryIterPeek peek = QueryIterPeek.create(input, execCxt);
                 input = peek; // Original input now invalid.
                 try {
@@ -237,13 +223,6 @@ public class OpExecutorTDB1 extends OpExecutor
                     return new QueryIterFailed(input, execCxt, e);
                 }
             }
-        } else {
-            try {
-                if ( ! input.hasNext() )
-                    return input;
-            } catch (Exception e) {
-                return new QueryIterFailed(input, execCxt, e);
-            }
         }
 
         if ( exprs == null ) {
@@ -257,9 +236,12 @@ public class OpExecutorTDB1 extends OpExecutor
         Op op = TransformFilterPlacement.transform(exprs, gn, bgp);
         return plainExecute(op, input, execCxt);
     }
-    /** Execute without modification of the op - does <b>not</b> apply special graph name translations */
-    private static QueryIterator plainExecute(Op op, QueryIterator input, ExecutionContext execCxt)
-    {
+
+    /**
+     * Execute without modification of the op - does <b>not</b> apply special graph
+     * name translations
+     */
+    private static QueryIterator plainExecute(Op op, QueryIterator input, ExecutionContext execCxt) {
         // -- Execute
         // Switch to a non-reordering executor
         // The Op may be a sequence due to TransformFilterPlacement
@@ -275,23 +257,19 @@ public class OpExecutorTDB1 extends OpExecutor
         return QC.execute(op, input, ec2);
     }
 
-    private static BasicPattern reorder(BasicPattern pattern, QueryIterPeek peek, ReorderTransformation transform)
-    {
-        if ( transform != null )
-        {
+    private static BasicPattern reorder(BasicPattern pattern, QueryIterPeek peek, ReorderTransformation transform) {
+        // Caller tested that peek.hasNext is true.
+        if ( transform != null ) {
             // This works by getting one result from the peek iterator,
             // and creating the more gounded BGP. The tranform is used to
             // determine the best order and the transformation is returned. This
             // transform is applied to the unsubstituted pattern (which will be
             // substituted as part of evaluation.
 
-            if ( ! peek.hasNext() )
-                throw new ARQInternalErrorException("Peek iterator is already empty");
-
             BasicPattern pattern2 = Substitute.substitute(pattern, peek.peek() );
             // Calculate the reordering based on the substituted pattern.
             ReorderProc proc = transform.reorderIndexes(pattern2);
-            // Then reorder original patten
+            // Then reorder original pattern
             pattern = proc.reorder(pattern);
         }
         return pattern;
@@ -301,17 +279,15 @@ public class OpExecutorTDB1 extends OpExecutor
      * Returns null for default graph in storage (use the triple table).
      * Returns Node.ANY for the union graph
      */
-    public static Node decideGraphNode(Node gn, ExecutionContext execCxt)
-    {
-     // ---- Graph names with special meaning.
+    public static Node decideGraphNode(Node gn, ExecutionContext execCxt) {
+        // ---- Graph names with special meaning.
 
         // Graph names with special meaning:
         //   Quad.defaultGraphIRI -- the IRI used in GRAPH <> to mean the default graph.
         //   Quad.defaultGraphNodeGenerated -- the internal marker node used for the quad form of queries.
         //   Quad.unionGraph -- the IRI used in GRAPH <> to mean the union of named graphs
 
-        if ( isDefaultGraphStorage(gn) )
-        {
+        if ( isDefaultGraphStorage(gn) ) {
             // Storage concrete, default graph.
             // Either outside GRAPH (no implicit union)
             // or using the "name" of the default graph
@@ -327,8 +303,7 @@ public class OpExecutorTDB1 extends OpExecutor
     }
 
     // Is this a query against the real default graph in the storage (in a 3-tuple table).
-    private static boolean isDefaultGraphStorage(Node gn)
-    {
+    private static boolean isDefaultGraphStorage(Node gn) {
         if ( gn == null )
             return true;
 
@@ -342,8 +317,7 @@ public class OpExecutorTDB1 extends OpExecutor
     }
 
     @Override
-    protected QueryIterator execute(OpDatasetNames dsNames, QueryIterator input)
-    {
+    protected QueryIterator execute(OpDatasetNames dsNames, QueryIterator input) {
         DatasetGraphTDB ds = (DatasetGraphTDB)execCxt.getDataset();
         Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext());
         Node gn = dsNames.getGraphNode();
@@ -356,8 +330,7 @@ public class OpExecutorTDB1 extends OpExecutor
     // ---- OpExecute factories and plain executor.
 
     private static OpExecutorFactory plainFactory = new OpExecutorPlainFactoryTDB();
-    private static class OpExecutorPlainFactoryTDB implements OpExecutorFactory
-    {
+    private static class OpExecutorPlainFactoryTDB implements OpExecutorFactory {
         @Override
         public OpExecutor create(ExecutionContext execCxt)
         {
@@ -366,27 +339,24 @@ public class OpExecutorTDB1 extends OpExecutor
     }
 
     /** An op executor that simply executes a BGP or QuadPattern without any reordering */
-    private static class OpExecutorPlainTDB extends OpExecutor
-    {
+    private static class OpExecutorPlainTDB extends OpExecutor {
         Predicate<Tuple<NodeId>> filter = null;
 
-        public OpExecutorPlainTDB(ExecutionContext execCxt)
-        {
+        public OpExecutorPlainTDB(ExecutionContext execCxt) {
             super(execCxt);
             filter = QC2.getFilter(execCxt.getContext());
         }
 
         @Override
-        public QueryIterator execute(OpBGP opBGP, QueryIterator input)
-        {
+        public QueryIterator execute(OpBGP opBGP, QueryIterator input) {
             Graph g = execCxt.getActiveGraph();
 
-            if ( g instanceof GraphTDB gtdb )
-            {
+            if ( g instanceof GraphTDB gtdb ) {
                 BasicPattern bgp = opBGP.getPattern();
                 Explain.explain("Execute", bgp, execCxt.getContext());
                 // Triple-backed (but may be named as explicit default graph).
-                //return SolverLib.execute((GraphTDB)g, bgp, input, filter, execCxt);
+                // return SolverLib.execute((GraphTDB)g, bgp, input, filter,
+                // execCxt);
                 Node gn = decideGraphNode(gtdb.getGraphName(), execCxt);
                 return PatternMatchTDB1.execute(gtdb.getDatasetGraphTDB(), gn, bgp, input, filter, execCxt);
             }
@@ -395,26 +365,23 @@ public class OpExecutorTDB1 extends OpExecutor
         }
 
         @Override
-        public QueryIterator execute(OpQuadPattern opQuadPattern, QueryIterator input)
-        {
+        public QueryIterator execute(OpQuadPattern opQuadPattern, QueryIterator input) {
             Node gn = opQuadPattern.getGraphNode();
             gn = decideGraphNode(gn, execCxt);
 
-            if ( execCxt.getDataset() instanceof DatasetGraphTDB dsgtdb )
-            {
+            if ( execCxt.getDataset() instanceof DatasetGraphTDB dsgtdb ) {
                 Explain.explain("Execute", opQuadPattern.getPattern(), execCxt.getContext());
                 BasicPattern bgp = opQuadPattern.getBasicPattern();
                 return PatternMatchTDB1.execute(dsgtdb, gn, bgp, input, filter, execCxt);
             }
             // Maybe a TDB named graph inside a non-TDB dataset.
             Graph g = execCxt.getActiveGraph();
-            if ( g instanceof GraphTDB gtdb )
-            {
-                // Triples graph from TDB (which is the default graph of the dataset),
+            if ( g instanceof GraphTDB gtdb ) {
+                // Triples graph from TDB, which is the default graph of the dataset,
                 // used a named graph in a composite dataset.
                 BasicPattern bgp = opQuadPattern.getBasicPattern();
                 Explain.explain("Execute", bgp, execCxt.getContext());
-                // Don't pass in G -- gn may be different.
+                // Don't pass in g -- gn may be different.
                 return PatternMatchTDB1.execute(gtdb.getDatasetGraphTDB(), gn, bgp, input, filter, execCxt);
             }
             Log.warn(this, "Non-DatasetGraphTDB passed to OpExecutorPlainTDB");
diff --git a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/StageGeneratorDirectTDB.java b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/StageGeneratorDirectTDB.java
index 10f9cc13f2..96b45966ea 100644
--- a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/StageGeneratorDirectTDB.java
+++ b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/StageGeneratorDirectTDB.java
@@ -23,38 +23,35 @@ package org.apache.jena.tdb1.solver;
 
 import java.util.function.Predicate;
 
-import org.apache.jena.atlas.lib.tuple.Tuple ;
-import org.apache.jena.graph.Graph ;
-import org.apache.jena.sparql.core.BasicPattern ;
-import org.apache.jena.sparql.engine.ExecutionContext ;
-import org.apache.jena.sparql.engine.QueryIterator ;
-import org.apache.jena.sparql.engine.main.StageGenerator ;
+import org.apache.jena.atlas.lib.tuple.Tuple;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.sparql.core.BasicPattern;
+import org.apache.jena.sparql.engine.ExecutionContext;
+import org.apache.jena.sparql.engine.QueryIterator;
+import org.apache.jena.sparql.engine.main.StageGenerator;
 import org.apache.jena.tdb1.store.GraphTDB;
 import org.apache.jena.tdb1.store.NodeId;
 
-/** Execute TDB requests directly -- no reordering
- *  Using OpExecutor is preferred.
+/**
+ * Execute TDB requests directly -- no reordering. Using OpExecutor is preferred.
  */
-public class StageGeneratorDirectTDB implements StageGenerator
-{
+public class StageGeneratorDirectTDB implements StageGenerator {
     // Using OpExecutor is preferred.
-    StageGenerator above = null ;
+    StageGenerator above = null;
 
-    public StageGeneratorDirectTDB(StageGenerator original)
-    {
-        above = original ;
+    public StageGeneratorDirectTDB(StageGenerator original) {
+        above = original;
     }
 
     @Override
-    public QueryIterator execute(BasicPattern pattern, QueryIterator input, ExecutionContext execCxt)
-    {
+    public QueryIterator execute(BasicPattern pattern, QueryIterator input, ExecutionContext execCxt) {
         // --- In case this isn't for TDB
-        Graph g = execCxt.getActiveGraph() ;
+        Graph g = execCxt.getActiveGraph();
 
-        if ( ! ( g instanceof GraphTDB graph) )
+        if ( !(g instanceof GraphTDB graph) )
             // Not us - bounce up the StageGenerator chain
-            return above.execute(pattern, input, execCxt) ;
-        Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext()) ;
-        return PatternMatchTDB1.execute(graph, pattern, input, filter, execCxt) ;
+            return above.execute(pattern, input, execCxt);
+        Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext());
+        return PatternMatchTDB1.execute(graph, pattern, input, filter, execCxt);
     }
 }
diff --git a/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/Test_SPARQL_TDB1.java b/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/Test_SPARQL_TDB1.java
index 2a5fffcc38..4539b39c6f 100644
--- a/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/Test_SPARQL_TDB1.java
+++ b/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/Test_SPARQL_TDB1.java
@@ -23,6 +23,8 @@ package org.apache.jena.tdb1.store;
 
 import static org.junit.Assert.assertEquals;
 
+import org.junit.Test ;
+
 import org.apache.jena.atlas.lib.StrUtils ;
 import org.apache.jena.graph.Graph ;
 import org.apache.jena.graph.NodeFactory ;
@@ -30,6 +32,10 @@ import org.apache.jena.graph.Triple ;
 import org.apache.jena.query.* ;
 import org.apache.jena.rdf.model.Model ;
 import org.apache.jena.rdf.model.ModelFactory ;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.exec.QueryExec;
+import org.apache.jena.sparql.exec.RowSet;
+import org.apache.jena.sparql.exec.RowSetOps;
 import org.apache.jena.sparql.exec.UpdateExec;
 import org.apache.jena.sparql.sse.SSE ;
 import org.apache.jena.system.Txn;
@@ -39,7 +45,6 @@ import org.apache.jena.tdb1.base.file.Location;
 import org.apache.jena.update.UpdateAction;
 import org.apache.jena.update.UpdateFactory;
 import org.apache.jena.update.UpdateRequest;
-import org.junit.Test ;
 
 /**
  * Test SPARQL
@@ -152,6 +157,20 @@ public class Test_SPARQL_TDB1
         assertEquals(false, b);
     }
 
+    @Test
+    public void sparql7() {
+        // https://github.com/apache/jena/issues/3751
+        String qs = """
+                PREFIX : <http://example/>
+                SELECT * { ?x :property+ ?y . ?z :q1 123 . ?z :q2 456 . }
+                """;
+        DatasetGraph dsg = TDB1Factory.createDatasetGraph();
+        dsg.executeRead(()->{
+            RowSet rs = QueryExec.dataset(dsg).query(qs).select();
+            RowSetOps.consume(rs);
+        });
+    }
+
     // Test transactions effective.
 
     @Test
diff --git a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/OpExecutorTDB2.java b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/OpExecutorTDB2.java
index 2273e07bae..23c649d348 100644
--- a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/OpExecutorTDB2.java
+++ b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/OpExecutorTDB2.java
@@ -27,7 +27,6 @@ import org.apache.jena.atlas.lib.tuple.Tuple;
 import org.apache.jena.atlas.logging.Log;
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
-import org.apache.jena.sparql.ARQInternalErrorException;
 import org.apache.jena.sparql.algebra.Op;
 import org.apache.jena.sparql.algebra.op.*;
 import org.apache.jena.sparql.algebra.optimize.TransformFilterPlacement;
@@ -42,6 +41,7 @@ import org.apache.jena.sparql.engine.iterator.QueryIterPeek;
 import org.apache.jena.sparql.engine.main.OpExecutor;
 import org.apache.jena.sparql.engine.main.OpExecutorFactory;
 import org.apache.jena.sparql.engine.main.QC;
+import org.apache.jena.sparql.engine.main.StageGenerator;
 import org.apache.jena.sparql.engine.main.iterator.QueryIterGraph;
 import org.apache.jena.sparql.engine.optimizer.reorder.ReorderProc;
 import org.apache.jena.sparql.engine.optimizer.reorder.ReorderTransformation;
@@ -53,17 +53,17 @@ import org.apache.jena.tdb2.store.NodeId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** TDB executor for algebra expressions.  It is the standard ARQ executor
- *  except for basic graph patterns and filtered basic graph patterns (currently).
+/**
+ * TDB2 executor for algebra expressions.  It is the standard ARQ executor
+ * except for basic graph patterns and filtered basic graph patterns (currently).
  *
- * See also: StageGeneratorDirectTDB, a non-reordering
+ * See also: {@link StageGeneratorDirectTDB}, a non-reordering {@link StageGenerator}
  */
 public class OpExecutorTDB2 extends OpExecutor
 {
     private static final Logger log = LoggerFactory.getLogger(OpExecutorTDB2.class);
 
-    public final static OpExecutorFactory OpExecFactoryTDB = new OpExecutorFactory()
-    {
+    public final static OpExecutorFactory OpExecFactoryTDB = new OpExecutorFactory() {
         @Override
         public OpExecutor create(ExecutionContext execCxt)
         { return new OpExecutorTDB2(execCxt); }
@@ -73,8 +73,7 @@ public class OpExecutorTDB2 extends OpExecutor
 
     // A new compile object is created for each op compilation.
     // So the execCxt is changing as we go through the query-compile-execute process
-    public OpExecutorTDB2(ExecutionContext execCxt)
-    {
+    public OpExecutorTDB2(ExecutionContext execCxt) {
         super(execCxt);
         // NB. The dataset may be a TDB one, or a general one.
         // Any merged union graph magic (for a TDB dataset was handled
@@ -96,29 +95,22 @@ public class OpExecutorTDB2 extends OpExecutor
     // Need to work with SolverLib which wraps the NodeId bindgins with a converter.
 
     @Override
-    protected QueryIterator execute(OpDistinct opDistinct, QueryIterator input)
-    {
+    protected QueryIterator execute(OpDistinct opDistinct, QueryIterator input) {
         return super.execute(opDistinct, input);
     }
 
     @Override
-    protected QueryIterator execute(OpReduced opReduced, QueryIterator input)
-    {
+    protected QueryIterator execute(OpReduced opReduced, QueryIterator input) {
         return super.execute(opReduced, input);
     }
 
     @Override
-    protected QueryIterator execute(OpFilter opFilter, QueryIterator input)
-    {
+    protected QueryIterator execute(OpFilter opFilter, QueryIterator input) {
         if ( ! isForTDB )
             return super.execute(opFilter, input);
 
-        // If the filter does not apply to the input??
-        // Where does ARQ catch this?
-
         // (filter (bgp ...))
-        if ( OpBGP.isBGP(opFilter.getSubOp()) )
-        {
+        if ( OpBGP.isBGP(opFilter.getSubOp()) ) {
             // Still may be a TDB graph in a non-TDB dataset (e.g. a named model)
             GraphTDB graph = (GraphTDB)execCxt.getActiveGraph();
             OpBGP opBGP = (OpBGP)opFilter.getSubOp();
@@ -126,8 +118,7 @@ public class OpExecutorTDB2 extends OpExecutor
         }
 
         // (filter (quadpattern ...))
-        if ( opFilter.getSubOp() instanceof OpQuadPattern quadPattern)
-        {
+        if ( opFilter.getSubOp() instanceof OpQuadPattern quadPattern) {
             DatasetGraphTDB ds = (DatasetGraphTDB)execCxt.getDataset();
             return optimizeExecuteQuads(ds, input,
                                         quadPattern.getGraphNode(), quadPattern.getBasicPattern(),
@@ -136,31 +127,23 @@ public class OpExecutorTDB2 extends OpExecutor
 
         // (filter (anything else))
         return super.execute(opFilter, input);
-        }
+    }
 
     // ---- Triple patterns
 
     @Override
-    protected QueryIterator execute(OpBGP opBGP, QueryIterator input)
-    {
+    protected QueryIterator execute(OpBGP opBGP, QueryIterator input) {
         if ( ! isForTDB )
             return super.execute(opBGP, input);
 
         GraphTDB graph = (GraphTDB)execCxt.getActiveGraph();
         return executeBGP(graph, opBGP, input, null, execCxt);
-
     }
 
     @Override
-    protected QueryIterator execute(OpQuadPattern quadPattern, QueryIterator input)
-    {
+    protected QueryIterator execute(OpQuadPattern quadPattern, QueryIterator input) {
         if ( ! isForTDB )
             return super.execute(quadPattern, input);
-
-    //        DatasetGraph dg = execCxt.getDataset();
-    //        if ( ! ( dg instanceof DatasetGraphTDB ) )
-    //            throw new InternalErrorException("Not a TDB backed dataset in quad pattern execution");
-
         DatasetGraphTDB ds = (DatasetGraphTDB)execCxt.getDataset();
         BasicPattern bgp = quadPattern.getBasicPattern();
         Node gn = quadPattern.getGraphNode();
@@ -168,20 +151,17 @@ public class OpExecutorTDB2 extends OpExecutor
     }
 
     @Override
-    protected QueryIterator execute(OpGraph opGraph, QueryIterator input)
-    {
+    protected QueryIterator execute(OpGraph opGraph, QueryIterator input) {
         // Path evaluation or dataset sets which do not go straight to the DatasetGraphTDB
         return new QueryIterGraph(input, opGraph, execCxt);
     }
 
     /** Execute a BGP (and filters) on a TDB graph, which may be in default storage or it may be a named graph */
-    private static QueryIterator executeBGP(GraphTDB graph, OpBGP opBGP, QueryIterator input, ExprList exprs,
-                                            ExecutionContext execCxt)
-    {
+    private static QueryIterator executeBGP(GraphTDB graph, OpBGP opBGP, QueryIterator input,
+                                            ExprList exprs, ExecutionContext execCxt) {
         DatasetGraphTDB dsgtdb = graph.getDSG();
         // Is it the real default graph (normal route or explicitly named)?
-        if ( ! isDefaultGraphStorage(graph.getGraphName()))
-        {
+        if ( !isDefaultGraphStorage(graph.getGraphName()) ) {
             // Not default storage - it's a named graph in storage.
             return optimizeExecuteQuads(dsgtdb, input, graph.getGraphName(), opBGP.getPattern(), exprs, execCxt);
         }
@@ -192,41 +172,36 @@ public class OpExecutorTDB2 extends OpExecutor
 
     /** Execute, with optimization, a basic graph pattern on the default graph storage */
     private static QueryIterator optimizeExecuteTriples(DatasetGraphTDB dsgtdb, QueryIterator input,
-                                                        BasicPattern pattern, ExprList exprs,
-                                                        ExecutionContext execCxt)
+                                                        BasicPattern pattern, ExprList exprs, ExecutionContext execCxt)
     {
-        // -- Input
-        // Must pass this iterator into the next stage.
-        if ( pattern.size() >= 2 ) {
-            // Must be 2 or triples to reorder.
-            ReorderTransformation transform = dsgtdb.getReorderTransform();
-            if ( transform != null ) {
-                QueryIterPeek peek = QueryIterPeek.create(input, execCxt);
-                input = peek; // Must pass on
-                try {
+        try {
+            if ( ! input.hasNext() )
+                return input;
+
+            // -- Input
+            // Must pass this iterator into the next stage.
+            if ( pattern.size() >= 2 ) {
+                // Must be 2 or triples to reorder.
+                ReorderTransformation transform = dsgtdb.getReorderTransform();
+                if ( transform != null ) {
+                    QueryIterPeek peek = QueryIterPeek.create(input, execCxt);
+                    input = peek; // Must pass on
                     pattern = reorder(pattern, peek, transform);
-                } catch (Exception e) {
-                    return new QueryIterFailed(input, execCxt, e);
                 }
             }
-        } else {
-            try {
-                if ( !input.hasNext() )
-                    return input;
-            } catch (Exception e) {
-                return new QueryIterFailed(input, execCxt, e);
+
+            if ( exprs == null ) {
+                Explain.explain("Execute", pattern, execCxt.getContext());
+                Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext());
+                return PatternMatchTDB2.execute(dsgtdb, Quad.defaultGraphNodeGenerated, pattern, input, filter, execCxt);
             }
-        }
+            // -- Filter placement
 
-        if ( exprs == null ) {
-            Explain.explain("Execute", pattern, execCxt.getContext());
-            Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext());
-            return PatternMatchTDB2.execute(dsgtdb, Quad.defaultGraphNodeGenerated, pattern, input, filter, execCxt);
+            Op op = TransformFilterPlacement.transform(exprs, pattern);
+            return plainExecute(op, input, execCxt);
+        } catch (Exception e) {
+            return new QueryIterFailed(input, execCxt, e);
         }
-        // -- Filter placement
-
-        Op op = TransformFilterPlacement.transform(exprs, pattern);
-        return plainExecute(op, input, execCxt);
     }
 
     /** Execute, with optimization, a quad pattern */
@@ -236,48 +211,44 @@ public class OpExecutorTDB2 extends OpExecutor
                                                       ExprList exprs, ExecutionContext execCxt)
     {
         // ---- Graph names with special meaning.
-
         gn = decideGraphNode(gn, execCxt);
         if ( gn == null )
             return optimizeExecuteTriples(dsgtdb, input, bgp, exprs, execCxt);
+        try {
+            if ( ! input.hasNext() )
+                return input;
 
-        // ---- Execute quads+filters
-        if ( bgp.size() >= 2 ) {
-            ReorderTransformation transform = dsgtdb.getReorderTransform();
+            // ---- Execute quads+filters
+            if ( bgp.size() >= 2 ) {
+                ReorderTransformation transform = dsgtdb.getReorderTransform();
 
-            if ( transform != null ) {
-                QueryIterPeek peek = QueryIterPeek.create(input, execCxt);
-                input = peek; // Original input now invalid.
-                try {
+                if ( transform != null ) {
+                    QueryIterPeek peek = QueryIterPeek.create(input, execCxt);
+                    input = peek; // Original input now invalid.
                     bgp = reorder(bgp, peek, transform);
-                } catch (Exception e) {
-                    return new QueryIterFailed(input, execCxt, e);
                 }
             }
-        } else {
-            try {
-                if ( !input.hasNext() )
-                    return input;
-            } catch (Exception e) {
-                return new QueryIterFailed(input, execCxt, e);
+
+            if ( exprs == null ) {
+                // Triple-backed (but may be named as explicit default graph).
+                Explain.explain("Execute", bgp, execCxt.getContext());
+                Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext());
+                return PatternMatchTDB2.execute(dsgtdb, gn, bgp, input, filter, execCxt);
             }
-        }
 
-        if ( exprs == null ) {
-            // Triple-backed (but may be named as explicit default graph).
-            Explain.explain("Execute", bgp, execCxt.getContext());
-            Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext());
-            return PatternMatchTDB2.execute(dsgtdb, gn, bgp, input, filter, execCxt);
+            // -- Filter placement
+            Op op = TransformFilterPlacement.transform(exprs, gn, bgp);
+            return plainExecute(op, input, execCxt);
+        } catch (Exception e) {
+            return new QueryIterFailed(input, execCxt, e);
         }
-
-        // -- Filter placement
-        Op op = TransformFilterPlacement.transform(exprs, gn, bgp);
-        return plainExecute(op, input, execCxt);
     }
 
-    /** Execute without modification of the op - does <b>not</b> apply special graph name translations */
-    private static QueryIterator plainExecute(Op op, QueryIterator input, ExecutionContext execCxt)
-    {
+    /**
+     * Execute without modification of the op - does <b>not</b> apply special graph
+     * name translations
+     */
+    private static QueryIterator plainExecute(Op op, QueryIterator input, ExecutionContext execCxt) {
         // -- Execute
         // Switch to a non-reordering executor
         // The Op may be a sequence due to TransformFilterPlacement
@@ -293,43 +264,37 @@ public class OpExecutorTDB2 extends OpExecutor
         return QC.execute(op, input, ec2);
     }
 
-    private static BasicPattern reorder(BasicPattern pattern, QueryIterPeek peek, ReorderTransformation transform)
-    {
-        if ( transform != null )
-        {
+    private static BasicPattern reorder(BasicPattern pattern, QueryIterPeek peek, ReorderTransformation transform) {
+        // Caller tested that peek.hasNext is true.
+        if ( transform != null ) {
             // This works by getting one result from the peek iterator,
-            // and creating the more gounded BGP. The tranform is used to
+            // and creating the more grounded BGP. The transform is used to
             // determine the best order and the transformation is returned. This
             // transform is applied to the unsubstituted pattern (which will be
             // substituted as part of evaluation.
 
-            if ( ! peek.hasNext() )
-                throw new ARQInternalErrorException("Peek iterator is already empty");
-
-            BasicPattern pattern2 = Substitute.substitute(pattern, peek.peek() );
+            BasicPattern pattern2 = Substitute.substitute(pattern, peek.peek());
             // Calculate the reordering based on the substituted pattern.
             ReorderProc proc = transform.reorderIndexes(pattern2);
-            // Then reorder original patten
+            // Then reorder original pattern
             pattern = proc.reorder(pattern);
         }
         return pattern;
     }
 
-    /** Handle special graph node names.
-     * Returns null for default graph in storage (use the triple table).
-     * Returns Node.ANY for the union graph
+    /**
+     * Handle special graph node names. Returns null for default graph in storage
+     * (use the triple table). Returns Node.ANY for the union graph
      */
-    public static Node decideGraphNode(Node gn, ExecutionContext execCxt)
-    {
-     // ---- Graph names with special meaning.
+    public static Node decideGraphNode(Node gn, ExecutionContext execCxt) {
+        // ---- Graph names with special meaning.
 
         // Graph names with special meaning:
-        //   Quad.defaultGraphIRI -- the IRI used in GRAPH <> to mean the default graph.
-        //   Quad.defaultGraphNodeGenerated -- the internal marker node used for the quad form of queries.
-        //   Quad.unionGraph -- the IRI used in GRAPH <> to mean the union of named graphs
+        // * Quad.defaultGraphIRI -- the IRI used in GRAPH <> to mean the default graph.
+        // * Quad.defaultGraphNodeGenerated -- the internal marker node used for the quad form of queries.
+        // * Quad.unionGraph -- the IRI used in GRAPH <> to mean the union of named graphs
 
-        if ( isDefaultGraphStorage(gn) )
-        {
+        if ( isDefaultGraphStorage(gn) ) {
             // Storage concrete, default graph.
             // Either outside GRAPH (no implicit union)
             // or using the "name" of the default graph
@@ -347,8 +312,7 @@ public class OpExecutorTDB2 extends OpExecutor
     }
 
     // Is this a query against the real default graph in the storage (in a 3-tuple table).
-    private static boolean isDefaultGraphStorage(Node gn)
-    {
+    private static boolean isDefaultGraphStorage(Node gn) {
         if ( gn == null )
             return true;
 
@@ -362,8 +326,7 @@ public class OpExecutorTDB2 extends OpExecutor
     }
 
     @Override
-    protected QueryIterator execute(OpDatasetNames dsNames, QueryIterator input)
-    {
+    protected QueryIterator execute(OpDatasetNames dsNames, QueryIterator input) {
         DatasetGraphTDB ds = (DatasetGraphTDB)execCxt.getDataset();
         Predicate<Tuple<NodeId>> filter = QC2.getFilter(execCxt.getContext());
         Node gn = dsNames.getGraphNode();
@@ -376,64 +339,58 @@ public class OpExecutorTDB2 extends OpExecutor
     // ---- OpExecute factories and plain executor.
 
     private static OpExecutorFactory plainFactory = new OpExecutorPlainFactoryTDB();
-    private static class OpExecutorPlainFactoryTDB implements OpExecutorFactory
-    {
+    private static class OpExecutorPlainFactoryTDB implements OpExecutorFactory {
         @Override
-        public OpExecutor create(ExecutionContext execCxt)
-        {
+        public OpExecutor create(ExecutionContext execCxt) {
             return new OpExecutorPlainTDB(execCxt);
         }
     }
 
-    /** An op executor that simply executes a BGP or QuadPattern without any reordering */
-    private static class OpExecutorPlainTDB extends OpExecutor
-    {
+    /**
+     * An op executor that simply executes a BGP or QuadPattern without any
+     * reordering
+     */
+    private static class OpExecutorPlainTDB extends OpExecutor {
         Predicate<Tuple<NodeId>> filter = null;
 
-        public OpExecutorPlainTDB(ExecutionContext execCxt)
-        {
+        public OpExecutorPlainTDB(ExecutionContext execCxt) {
             super(execCxt);
             filter = QC2.getFilter(execCxt.getContext());
         }
 
         @Override
-        public QueryIterator execute(OpBGP opBGP, QueryIterator input)
-        {
+        public QueryIterator execute(OpBGP opBGP, QueryIterator input) {
             Graph g = execCxt.getActiveGraph();
 
-            if ( g instanceof GraphTDB gtdb )
-            {
+            if ( g instanceof GraphTDB gtdb ) {
                 BasicPattern bgp = opBGP.getPattern();
                 Explain.explain("Execute", bgp, execCxt.getContext());
                 // Triple-backed (but may be named as explicit default graph).
                 Node gn = decideGraphNode(gtdb.getGraphName(), execCxt);
                 return PatternMatchTDB2.execute(gtdb.getDSG(), gn, bgp, input, filter, execCxt);
             }
-            Log.warn(this, "Non-GraphTDB passed to OpExecutorPlainTDB: "+g.getClass().getSimpleName());
+            Log.warn(this, "Non-GraphTDB passed to OpExecutorPlainTDB: " + g.getClass().getSimpleName());
             return super.execute(opBGP, input);
         }
 
         @Override
-        public QueryIterator execute(OpQuadPattern opQuadPattern, QueryIterator input)
-        {
+        public QueryIterator execute(OpQuadPattern opQuadPattern, QueryIterator input) {
             Node gn = opQuadPattern.getGraphNode();
             gn = decideGraphNode(gn, execCxt);
 
-            if ( execCxt.getDataset() instanceof DatasetGraphTDB dsgtdb )
-            {
+            if ( execCxt.getDataset() instanceof DatasetGraphTDB dsgtdb ) {
                 Explain.explain("Execute", opQuadPattern.getPattern(), execCxt.getContext());
                 BasicPattern bgp = opQuadPattern.getBasicPattern();
                 return PatternMatchTDB2.execute(dsgtdb, gn, bgp, input, filter, execCxt);
             }
             // Maybe a TDB named graph inside a non-TDB dataset.
             Graph g = execCxt.getActiveGraph();
-            if ( g instanceof GraphTDB gtdb )
-            {
-                // Triples graph from TDB (which is the default graph of the dataset),
+            if ( g instanceof GraphTDB gtdb ) {
+                // Triples graph from TDB, which is the default graph of the dataset,
                 // used a named graph in a composite dataset.
                 BasicPattern bgp = opQuadPattern.getBasicPattern();
                 Explain.explain("Execute", bgp, execCxt.getContext());
-                // Don't pass in G -- gn may be different.
+                // Don't pass in g -- gn may be different.
                 return PatternMatchTDB2.execute(gtdb.getDSG(), gn, bgp, input, filter, execCxt);
             }
             Log.warn(this, "Non-DatasetGraphTDB passed to OpExecutorPlainTDB");
diff --git a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/StageGeneratorDirectTDB.java b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/StageGeneratorDirectTDB.java
index 2e5605da03..709148ff35 100644
--- a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/StageGeneratorDirectTDB.java
+++ b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/StageGeneratorDirectTDB.java
@@ -34,7 +34,7 @@ import org.apache.jena.tdb2.store.GraphViewSwitchable;
 import org.apache.jena.tdb2.store.NodeId;
 
 /**
- * Execute TDB requests directly -- no reordering Using OpExecutor is preferred.
+ * Execute TDB requests directly -- no reordering. Using OpExecutor is preferred.
  */
 public class StageGeneratorDirectTDB implements StageGenerator {
     // Using OpExecutor is preferred.
diff --git a/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/Test_SPARQL_TDB.java b/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/Test_SPARQL_TDB.java
index 2374295b22..fd9fc01f3d 100644
--- a/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/Test_SPARQL_TDB.java
+++ b/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/Test_SPARQL_TDB.java
@@ -32,8 +32,13 @@ import org.apache.jena.graph.NodeFactory;
 import org.apache.jena.graph.Triple;
 import org.apache.jena.query.*;
 import org.apache.jena.rdf.model.Model;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.exec.QueryExec;
+import org.apache.jena.sparql.exec.RowSet;
+import org.apache.jena.sparql.exec.RowSetOps;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.system.Txn;
+import org.apache.jena.tdb2.DatabaseMgr;
 import org.apache.jena.tdb2.TDB2;
 import org.apache.jena.tdb2.TDB2Factory;
 import org.apache.jena.update.*;
@@ -41,8 +46,7 @@ import org.apache.jena.update.*;
 /**
  * Test SPARQL
  */
-public class Test_SPARQL_TDB
-{
+public class Test_SPARQL_TDB {
     private static Dataset create() {
         return TDB2Factory.createDataset();
     }
@@ -55,12 +59,13 @@ public class Test_SPARQL_TDB
     private static Triple triple = SSE.parseTriple("(<x> <y> 123)");
 
     // Standalone graph.
-    @Test public void sparql1()
-    {
-        // Test OpExecutor.execute(OpBGP) for a named graph used as a standalone model
+    @Test
+    public void sparql1() {
+        // Test OpExecutor.execute(OpBGP) for a named graph used as a standalone
+        // model
         Dataset ds = create();
         add(ds, graphName, triple);
-        Txn.executeRead(ds, ()->{
+        Txn.executeRead(ds, () -> {
             Model m = ds.getNamedModel(graphName);
             String qs = "SELECT * { ?s ?p ?o . }";
             Query query = QueryFactory.create(qs);
@@ -71,17 +76,18 @@ public class Test_SPARQL_TDB
     }
 
     // Standalone graph.
-    @Test public void sparql2()
-    {
-        // Test OpExecutor.execute(OpFilter)for a named graph used as a standalone model
+    @Test
+    public void sparql2() {
+        // Test OpExecutor.execute(OpFilter)for a named graph used as a standalone
+        // model
         Dataset ds = create();
         add(ds, graphName, triple);
 
-        Txn.executeRead(ds, ()->{
+        Txn.executeRead(ds, () -> {
             Model m = ds.getNamedModel(graphName);
             String qs = "SELECT * { ?s ?p ?o . FILTER ( ?o < 456 ) }";
             Query query = QueryFactory.create(qs);
-            try(QueryExecution qexec = QueryExecutionFactory.create(query, m)) {
+            try (QueryExecution qexec = QueryExecutionFactory.create(query, m)) {
                 ResultSet rs = qexec.execSelect();
                 ResultSetFormatter.consume(rs);
             }
@@ -89,11 +95,11 @@ public class Test_SPARQL_TDB
     }
 
     // Requires OpDatasetNames
-    @Test public void sparql3()
-    {
+    @Test
+    public void sparql3() {
         Dataset dataset = create();
         // No triple added
-        Txn.executeRead(dataset, ()->{
+        Txn.executeRead(dataset, () -> {
             Query query = QueryFactory.create("SELECT ?g { GRAPH ?g {} }");
             QueryExecution qExec = QueryExecutionFactory.create(query, dataset);
             ResultSet rs = qExec.execSelect();
@@ -102,11 +108,11 @@ public class Test_SPARQL_TDB
         });
     }
 
-    @Test public void sparql4()
-    {
+    @Test
+    public void sparql4() {
         Dataset dataset = create();
         add(dataset, graphName, triple);
-        Txn.executeRead(dataset, ()->{
+        Txn.executeRead(dataset, () -> {
             Query query = QueryFactory.create("SELECT ?g { GRAPH ?g {} }");
             QueryExecution qExec = QueryExecutionFactory.create(query, dataset);
             ResultSet rs = qExec.execSelect();
@@ -115,30 +121,44 @@ public class Test_SPARQL_TDB
         });
     }
 
-    @Test public void sparql5()
-    {
+    @Test
+    public void sparql5() {
         Dataset dataset = create();
         add(dataset, graphName, triple);
-        Txn.executeRead(dataset, ()->{
-            Query query = QueryFactory.create("ASK { GRAPH <"+graphName+"> {} }");
+        Txn.executeRead(dataset, () -> {
+            Query query = QueryFactory.create("ASK { GRAPH <" + graphName + "> {} }");
             boolean b = QueryExecutionFactory.create(query, dataset).execAsk();
             assertEquals(true, b);
         });
     }
 
-    @Test public void sparql6()
-    {
+    @Test
+    public void sparql6() {
         Dataset dataset = create();
         add(dataset, graphName, triple);
-        Txn.executeRead(dataset, ()->{
+        Txn.executeRead(dataset, () -> {
             Query query = QueryFactory.create("ASK { GRAPH <http://example/x> {} }");
             boolean b = QueryExecutionFactory.create(query, dataset).execAsk();
             assertEquals(false, b);
         });
     }
 
+    @Test
+    public void sparql7() {
+        // https://github.com/apache/jena/issues/3751
+        String qs = """
+                PREFIX : <http://example/>
+                SELECT * { ?x :property+ ?y . ?z :q1 123 . ?z :q2 456 . }
+                """;
+        DatasetGraph dsg = DatabaseMgr.createDatasetGraph();
+        dsg.executeRead(()->{
+            RowSet rs = QueryExec.dataset(dsg).query(qs).select();
+            RowSetOps.consume(rs);
+        });
+    }
+
     private static void add(Dataset dataset, String graphName, Triple triple) {
-        Txn.executeWrite(dataset, ()->{
+        Txn.executeWrite(dataset, () -> {
             Graph g2 = dataset.asDatasetGraph().getGraph(NodeFactory.createURI(graphName));
             g2.add(triple);
         });
@@ -146,10 +166,10 @@ public class Test_SPARQL_TDB
 
     // Test transactions effective.
 
-    @Test public void sparql_txn_1()
-    {
+    @Test
+    public void sparql_txn_1() {
         Dataset dataset = create();
-        Txn.executeWrite(dataset, ()->{
+        Txn.executeWrite(dataset, () -> {
             update(dataset, "INSERT DATA { <x:s> <x:p> <x:o> }");
         });
         // Explicit trasnaction steps.
@@ -159,56 +179,55 @@ public class Test_SPARQL_TDB
             assertEquals(1, n);
             n = count(dataset, "SELECT * { <x:s> <x:p> <x:o>}");
             assertEquals(1, n);
-        } finally { dataset.end(); }
+        } finally {
+            dataset.end();
+        }
     }
 
-    @Test public void sparql_txn_2()
-    {
+    @Test
+    public void sparql_txn_2() {
         Dataset dataset1 = create(Location.mem("foo"));
         Dataset dataset2 = create(Location.mem("foo"));
 
-        Txn.executeWrite(dataset1, ()->{
+        Txn.executeWrite(dataset1, () -> {
             update(dataset1, "INSERT DATA { <x:s> <x:p> <x:o> }");
         });
 
-        Txn.executeRead(dataset1, ()->{
+        Txn.executeRead(dataset1, () -> {
             assertEquals(1, count(dataset1));
         });
 
         // Same location.
-        Txn.executeRead(dataset2, ()->{
+        Txn.executeRead(dataset2, () -> {
             assertEquals(1, count(dataset2));
         });
     }
 
-    @Test public void sparql_update_unionGraph()
-    {
+    @Test
+    public void sparql_update_unionGraph() {
         Dataset ds = TDB2Factory.createDataset();
         // Update concrete default graph
-        Txn.executeWrite(ds, ()->{
+        Txn.executeWrite(ds, () -> {
             ds.asDatasetGraph().add(SSE.parseQuad("(<g> <s> <p> 123)"));
         });
         ds.getContext().setTrue(TDB2.symUnionDefaultGraph);
 
-        Txn.executeWrite(ds, ()->{
+        Txn.executeWrite(ds, () -> {
             // Update by looking in union graph
-            String us = StrUtils.strjoinNL(
-                "INSERT { GRAPH <http://example/g2> { ?s ?p 'NEW' } }",
-                "WHERE { ",
-                     "?s ?p 123",
-                " }" );
+            String us = StrUtils.strjoinNL("INSERT { GRAPH <http://example/g2> { ?s ?p 'NEW' } }", "WHERE { ", "?s ?p 123", " }");
             UpdateRequest req = UpdateFactory.create(us);
             UpdateAction.execute(req, ds);
         });
 
-        Txn.executeRead(ds, ()->{
+        Txn.executeRead(ds, () -> {
             Model m = ds.getNamedModel("http://example/g2");
-            assertEquals(1, m.size(), ()->"Did not find 1 statement in named graph");
+            assertEquals(1, m.size(), () -> "Did not find 1 statement in named graph");
         });
     }
 
-    private int count(Dataset dataset)
-    { return count(dataset, "SELECT * { ?s ?p ?o }"); }
+    private int count(Dataset dataset) {
+        return count(dataset, "SELECT * { ?s ?p ?o }");
+    }
 
     private int count(Dataset dataset, String queryString)
 
@@ -218,8 +237,8 @@ public class Test_SPARQL_TDB
         ResultSet rs = qExec.execSelect();
         return ResultSetFormatter.consume(rs);
     }
-    private void update(Dataset dataset, String string)
-    {
+
+    private void update(Dataset dataset, String string) {
         UpdateRequest req = UpdateFactory.create(string);
         UpdateExecution proc = UpdateExecutionFactory.create(req, dataset);
         proc.execute();