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 2024/02/10 10:50:04 UTC

(jena) branch main updated (cb53d3d104 -> 69e0ae2248)

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 cb53d3d104 GH-2094: Remove duplicate query expressions
     new 1017746a76 Javadoc tweaks for QuotedURI
     new 7dce9ffa52 Add null tests. Reformat.
     new a9052183be Use lambdas in lang/ParserSPARQL_N
     new 69e0ae2248 GH-2218: Implement LATERAL as per SEP-0006/SEP-0007

The 4 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:
 .../org/apache/jena/riot/out/quoted/QuotedURI.java |  12 +-
 .../org/apache/jena/sparql/core/Substitute.java    |  35 ++-
 .../main/java/org/apache/jena/sparql/core/Var.java |  85 +++---
 .../org/apache/jena/sparql/core/VarExprList.java   |   8 +-
 .../apache/jena/sparql/engine/binding/Binding.java |   4 +
 .../jena/sparql/engine/binding/BindingBase.java    |  12 +-
 .../sparql/engine/iterator/QueryIterLateral.java   | 303 ++++++++++++++++++++-
 .../jena/sparql/engine/main/JoinClassifier.java    |   7 +-
 .../sparql/engine/main/LeftJoinClassifier.java     |   3 +
 .../jena/sparql/engine/ref/EvaluatorDispatch.java  | 154 +++++------
 .../org/apache/jena/sparql/engine/ref/RefEval.java |   2 +-
 .../apache/jena/sparql/lang/ParserSPARQL10.java    |  27 +-
 .../apache/jena/sparql/lang/ParserSPARQL11.java    |  26 +-
 .../apache/jena/sparql/lang/ParserSPARQL12.java    |  27 +-
 .../apache/jena/sparql/lang/SyntaxVarScope.java    |  53 ++--
 jena-arq/testing/ARQ/Lateral/data2.ttl             |   9 +
 .../testing/ARQ/Lateral/lateral-in-optional.arq    |  15 +
 .../testing/ARQ/Lateral/lateral-in-optional.srj    |  22 ++
 jena-arq/testing/ARQ/Lateral/manifest.ttl          |  20 ++
 .../testing/ARQ/Lateral/optional-in-lateral.arq    |  16 ++
 .../testing/ARQ/Lateral/optional-in-lateral.srj    |  27 ++
 21 files changed, 640 insertions(+), 227 deletions(-)
 create mode 100644 jena-arq/testing/ARQ/Lateral/data2.ttl
 create mode 100644 jena-arq/testing/ARQ/Lateral/lateral-in-optional.arq
 create mode 100644 jena-arq/testing/ARQ/Lateral/lateral-in-optional.srj
 create mode 100644 jena-arq/testing/ARQ/Lateral/optional-in-lateral.arq
 create mode 100644 jena-arq/testing/ARQ/Lateral/optional-in-lateral.srj


(jena) 04/04: GH-2218: Implement LATERAL as per SEP-0006/SEP-0007

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 69e0ae2248b29970198cc5366ec7b5c96549f889
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Jan 28 14:42:15 2024 +0000

    GH-2218: Implement LATERAL as per SEP-0006/SEP-0007
---
 .../org/apache/jena/sparql/core/Substitute.java    |  35 ++-
 .../org/apache/jena/sparql/core/VarExprList.java   |   8 +-
 .../apache/jena/sparql/engine/binding/Binding.java |   4 +
 .../jena/sparql/engine/binding/BindingBase.java    |  12 +-
 .../sparql/engine/iterator/QueryIterLateral.java   | 303 ++++++++++++++++++++-
 .../jena/sparql/engine/main/JoinClassifier.java    |   7 +-
 .../sparql/engine/main/LeftJoinClassifier.java     |   3 +
 .../jena/sparql/engine/ref/EvaluatorDispatch.java  | 154 +++++------
 .../org/apache/jena/sparql/engine/ref/RefEval.java |   2 +-
 .../apache/jena/sparql/lang/SyntaxVarScope.java    |  53 ++--
 jena-arq/testing/ARQ/Lateral/data2.ttl             |   9 +
 .../testing/ARQ/Lateral/lateral-in-optional.arq    |  15 +
 .../testing/ARQ/Lateral/lateral-in-optional.srj    |  22 ++
 jena-arq/testing/ARQ/Lateral/manifest.ttl          |  20 ++
 .../testing/ARQ/Lateral/optional-in-lateral.arq    |  16 ++
 .../testing/ARQ/Lateral/optional-in-lateral.srj    |  27 ++
 16 files changed, 565 insertions(+), 125 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java
index 1f7970560f..19cbadc296 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java
@@ -20,16 +20,19 @@ package org.apache.jena.sparql.core;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.NodeFactory;
 import org.apache.jena.graph.Triple;
 import org.apache.jena.sparql.algebra.Op;
+import org.apache.jena.sparql.algebra.Transform;
 import org.apache.jena.sparql.algebra.TransformCopy;
 import org.apache.jena.sparql.algebra.Transformer;
 import org.apache.jena.sparql.algebra.op.*;
 import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.engine.binding.BindingFactory;
+import org.apache.jena.sparql.engine.iterator.QueryIterLateral;
 import org.apache.jena.sparql.expr.Expr;
 import org.apache.jena.sparql.expr.ExprList;
 import org.apache.jena.sparql.pfunction.PropFuncArg;
@@ -40,12 +43,42 @@ import org.apache.jena.sparql.syntax.syntaxtransform.UpdateTransformOps;
  * Substitution in SPARQL algebra.
  * <p>
  * See also {@link QueryTransformOps} and {@link UpdateTransformOps} which operate on SPARQL syntax.
+ * <p>
+ * {@link #inject} provides the substitution, while leaving a variable present, used by LATERAL.
  */
 public class Substitute {
+    /**
+     * Inject takes an {@link Op} to transform using a {Binding binding}. The
+     * transformation assumes the Ope structure is legal for the operation. The
+     * transformation is to wrap each place a variable is used (BGP, GRAPH, Path and
+     * some equivalent operations) with a {@code BIND} to restrict the vartibale to a specific value
+     * while still retaining the variable (e.g for FILETERs).
+     * <p>
+     * <pre>
+     *   (bgp
+     *     (?s :p 123)
+     *     (?s :q ?a)
+     *   )
+     * </pre>
+     * with binding {@code ?s = :x } becomes
+     * <pre>
+     * (assign (?s :x)
+     *    (bgp
+     *      (:x :p 123)
+     *      (:x :q ?a)
+     *    ))
+     * </pre>
+     */
+    public static Op inject(Op opInput, Binding binding) {
+        Set<Var> injectVars = binding.varsMentioned();
+        Transform transform = new QueryIterLateral.TransformInject(injectVars, binding::get);
+        Op opOutput = Transformer.transform(transform, opInput);
+        return opOutput;
+    }
+
     public static Op substitute(Op op, Binding binding) {
         // Want to avoid cost if the binding is empty
         // but the empty test is not zero-cost on non-empty things.
-
         if ( isNotNeeded(binding) )
             return op;
         return Transformer.transform(new OpSubstituteWorker(binding), op);
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java
index 26756d9027..f2b4336d83 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/VarExprList.java
@@ -35,10 +35,10 @@ public class VarExprList {
     private List<Var> vars;
     private LinkedHashMap<Var, Expr> exprs;   // Preserve order.
 
-    public VarExprList(List<Var> vars) {
-        this.vars = new ArrayList<>(vars);
-        this.exprs = new LinkedHashMap<>();
-    }
+//    public VarExprList(List<Var> vars) {
+//        this.vars = new ArrayList<>(vars);
+//        this.exprs = new LinkedHashMap<>();
+//    }
 
     public VarExprList(VarExprList other) {
         this.vars = new ArrayList<>(other.vars);
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java
index c3fc2b044d..9bb7fcf4b7 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java
@@ -19,6 +19,7 @@
 package org.apache.jena.sparql.engine.binding;
 
 import java.util.Iterator;
+import java.util.Set;
 import java.util.function.BiConsumer;
 
 import org.apache.jena.graph.Node;
@@ -56,6 +57,9 @@ public interface Binding
     /** Iterate over all variables of this binding. */
     public Iterator<Var> vars();
 
+    /** Iterate over all variables of this binding. */
+    public Set<Var> varsMentioned();
+
     /** Operate on each entry. */
     public void forEach(BiConsumer<Var, Node> action);
 
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java
index 4716290bae..0e7fe54610 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java
@@ -19,6 +19,8 @@
 package org.apache.jena.sparql.engine.binding;
 
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
 import java.util.function.BiConsumer;
 
 import org.apache.jena.atlas.iterator.IteratorConcat;
@@ -45,14 +47,20 @@ abstract public class BindingBase implements Binding
 //    public Binding getParent() { return parent; }
 
     @Override
-    final public Iterator<Var> vars()
-    {
+    final public Iterator<Var> vars() {
         Iterator<Var> iter = vars1();
         if ( parent != null )
             iter = IteratorConcat.concat(parent.vars(), iter);
         return iter;
     }
 
+    @Override
+    final public Set<Var> varsMentioned() {
+        Set<Var> result = new LinkedHashSet<>();
+        vars().forEachRemaining(result::add);
+        return result;
+    }
+
     /** Operate on each entry. */
     @Override
     public void forEach(BiConsumer<Var, Node> action) {
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java
index 734c9278a5..1162c6cb80 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java
@@ -18,23 +18,36 @@
 
 package org.apache.jena.sparql.engine.iterator;
 
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.jena.atlas.lib.SetUtils;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
 import org.apache.jena.sparql.algebra.Op;
-import org.apache.jena.sparql.algebra.op.OpTable;
-import org.apache.jena.sparql.core.Substitute;
+import org.apache.jena.sparql.algebra.TransformCopy;
+import org.apache.jena.sparql.algebra.op.*;
+import org.apache.jena.sparql.core.*;
 import org.apache.jena.sparql.engine.ExecutionContext;
 import org.apache.jena.sparql.engine.QueryIterator;
 import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.engine.binding.BindingBuilder;
+import org.apache.jena.sparql.engine.binding.BindingFactory;
 import org.apache.jena.sparql.engine.main.QC;
+import org.apache.jena.sparql.expr.NodeValue;
+import org.apache.jena.sparql.util.VarUtils;
 
 public class QueryIterLateral extends QueryIterRepeatApply {
 
-    private final Op lateralOp;
+    private final Op subOp;
     private final boolean isUnit;
 
-    public QueryIterLateral(QueryIterator input, Op lateralOp, ExecutionContext execCxt) {
+    public QueryIterLateral(QueryIterator input, Op subOp, ExecutionContext execCxt) {
         super(input, execCxt);
-        this.lateralOp = lateralOp;
-        this.isUnit = isJoinIdentity(lateralOp);
+        this.subOp = subOp;
+        this.isUnit = isJoinIdentity(subOp);
     }
 
     private boolean isJoinIdentity(Op op) {
@@ -47,7 +60,283 @@ public class QueryIterLateral extends QueryIterRepeatApply {
     protected QueryIterator nextStage(Binding binding) {
         if ( isUnit )
             return QueryIterSingleton.create(binding, super.getExecContext());
-        Op op = Substitute.substitute(lateralOp, binding);
+        Op op = Substitute.inject(subOp, binding);
         return QC.execute(op, binding, super.getExecContext());
     }
+
+    /*
+     * This transform applies variable substitution by injecting a binding for the variable and
+     * also substituting the variable in the op.
+     * The second step - substitution - is strictly unnecessary it happens anyway.
+     */
+    public static class TransformInject extends TransformCopy {
+
+        private final Set<Var> injectVars;
+        private final Set<Node> varsAsNodes;
+        private final Function<Var, Node> replacement;
+        private static final boolean substitute = true;
+
+        // Replacement becomes binding.??
+        // Or "op call injection"!!
+        public TransformInject(Set<Var> injectVars, Function<Var, Node> replacement) {
+            this.injectVars = injectVars;
+            this.varsAsNodes = Set.copyOf(injectVars);
+            this.replacement = replacement;
+        }
+
+        @Override
+        public Op transform(OpBGP opBGP) {
+            if ( injectVars.isEmpty())
+                return opBGP;
+            BasicPattern bp = opBGP.getPattern();
+            List<Triple> triples = bp.getList();
+            // Alt - build up triple by triple.
+                //generateAssignmentTriple(triple, assigns, builder);
+            Set<Var> bgpVars = new LinkedHashSet<>();
+            VarUtils.addVarsTriples(bgpVars, triples);
+            Set<Var> x = SetUtils.intersection(bgpVars, injectVars);
+            if ( x.isEmpty())
+                return opBGP;
+            VarExprList assigns = new VarExprList();
+            BindingBuilder builder = BindingFactory.builder();
+            for ( Var var : x )
+                generateAssignmentVar(var, assigns, builder);
+
+            if ( assigns.isEmpty() )
+                return super.transform(opBGP);
+
+            // If no substitutions, do less work.
+            Binding substitutions = builder.build();
+            Op opExec = substitute
+                    ? Substitute.substitute(opBGP, substitutions)
+                    : opBGP;
+            opExec = OpAssign.create(opExec, assigns);
+            return opExec;
+        }
+
+//        @Override
+//        public Op transform(OpQuadBlock opQuadBlock) {
+//            opQuadBlock.getPattern();
+//        }
+
+        @Override
+        public Op transform(OpQuadPattern opQuadPattern) {
+            // "One node : Commonality with OpGraph, OpService (OpDatasetNames)
+            // Function for maker.
+            BasicPattern bgp = opQuadPattern.getBasicPattern();
+            VarExprList assigns = new VarExprList();
+            BindingBuilder builder = BindingFactory.builder();
+            Node gn = opQuadPattern.getGraphNode();
+
+            generateAssignmentNode(gn, assigns, builder);
+
+            for ( Triple t : bgp ) {
+                generateAssignmentTriple(t, assigns, builder);
+            }
+            if ( assigns.isEmpty() )
+                return super.transform(opQuadPattern);
+            // If no substitutions, do less work.
+            Op opExec = opQuadPattern;
+            if ( substitute ) {
+                Binding substitutions = builder.build();
+                opExec = Substitute.substitute(opQuadPattern, substitutions);
+            }
+            opExec = OpAssign.create(opExec, assigns);
+            return opExec;
+        }
+
+        // XXX Extract - do one node
+
+        @Override
+        public Op transform(OpService opService, Op subOp) {
+            Node g = opService.getService();
+            if ( ! workToDo(g) )
+                return super.transform(opService, subOp);
+            // Fast small lists?
+            VarExprList assigns = new VarExprList();
+            BindingBuilder builder = BindingFactory.builder();
+            Var var = Var.alloc(g);
+            generateAssignmentVar(var, assigns, builder);
+            if ( assigns.isEmpty() )
+                return super.transform(opService, subOp);
+            // subOp has already been processed.
+
+            Node g2 = g;
+            Op op2;
+            if ( substitute ) {
+                Binding substitutions = builder.build();
+                g2 = substitutions.get(var);
+                op2 = new OpService(g2, subOp, opService.getSilent());
+            } else
+                op2 = new OpService(g, subOp, opService.getSilent());
+            Op opExec = OpAssign.create(op2, assigns);
+            return opExec;
+        }
+
+        @Override
+        public Op transform(OpGraph opGraph, Op subOp) {
+            Node g = opGraph.getNode();
+            if ( ! workToDo(g) )
+                return super.transform(opGraph, subOp);
+            // Fast small lists?
+            VarExprList assigns = new VarExprList();
+            BindingBuilder builder = BindingFactory.builder();
+            Var var = Var.alloc(g);
+            generateAssignmentVar(var, assigns, builder);
+            if ( assigns.isEmpty() )
+                return super.transform(opGraph, subOp);
+            // subOp has already been processed.
+
+            Node g2 = g;
+            Op op2;
+            if ( substitute ) {
+                Binding substitutions = builder.build();
+                g2 = substitutions.get(var);
+                op2 = new OpGraph(g2, subOp);
+            } else
+                op2 = new OpGraph(g, subOp);
+            Op opExec = OpAssign.create(op2, assigns);
+            return opExec;
+        }
+
+        @Override
+        public Op transform(OpDatasetNames opDatasetNames) {
+            Node g = opDatasetNames.getGraphNode();
+            if ( ! workToDo(g) )
+                return super.transform(opDatasetNames);
+            Var var = Var.alloc(g);
+            Node g2 = replacement.apply(var);
+            Op op2 = new OpGraph(g2, OpTable.unit());
+            return op2;
+        }
+
+//        Binding for variables occurs in several places in SPARQL:
+    //
+//            Basic Graph Pattern Matching
+//            Property Path Patterns
+//            evaluation of algebra form Graph(var,P) involving a variable (from the syntax GRAPH ?variable {&hellip;})
+
+        @Override
+        public Op transform(OpPath opPath) {
+            // Exactly one of predicate and path is defined.
+            TriplePath path = opPath.getTriplePath();
+            VarExprList assigns = new VarExprList();
+            BindingBuilder builder = BindingFactory.builder();
+
+            if ( path.isTriple() ) {
+                Triple t1 = path.asTriple();
+                generateAssignmentTriple(t1, assigns, builder);
+                // XXX Triple t2 = Substitute.substitute(triple, binding);
+                Triple t2 = applyReplacement(t1, replacement);
+                if ( t1.equals(t2) )
+                    return opPath;
+                TriplePath path2 = new TriplePath(t2);
+                return new OpPath(path2);
+            }
+
+            // Path, not predicate.
+            Node s = path.getSubject();
+            Node o = path.getObject();
+            if ( ! workToDo(s) && ! workToDo(o) )
+                return super.transform(opPath);
+
+            generateAssignmentNode(s, assigns, builder);
+            generateAssignmentNode(o, assigns, builder);
+
+            if ( assigns.isEmpty() )
+                return super.transform(opPath);
+
+            Node s2 = applyReplacement(s, replacement);
+            Node o2 = applyReplacement(o, replacement);
+            TriplePath path2 = new TriplePath(s2, path.getPath(), o2);
+            Op op2 = new OpPath(path2);
+            Op opExec = OpAssign.create(op2, assigns);
+            return opExec;
+        }
+
+        @Override
+        public Op transform(OpTriple opTriple) {
+            Triple triple = opTriple.getTriple();
+            VarExprList assigns = new VarExprList();
+            BindingBuilder builder = BindingFactory.builder();
+            generateAssignmentTriple(triple, assigns, builder);
+            if ( assigns.isEmpty() )
+                return super.transform(opTriple);
+            Triple t2 = triple;
+            if ( substitute )
+                t2 = applyReplacement(triple, replacement);
+            Op op2 = new OpTriple(t2);
+            Op opExec = OpAssign.create(op2, assigns);
+            return opExec;
+        }
+
+        private Triple applyReplacement(Triple triple, Function<Var, Node> replacement) {
+            Node s2 = applyReplacement(triple.getSubject(), replacement);
+            Node p2 = applyReplacement(triple.getPredicate(), replacement);
+            Node o2 = applyReplacement(triple.getObject(), replacement);
+            Triple t2 = Triple.create(s2, p2, o2);
+            return t2;
+        }
+
+        private void generateAssignmentTriple(Triple triple, VarExprList assigns, BindingBuilder builder) {
+            Node s = triple.getSubject();
+            Node p = triple.getPredicate();
+            Node o = triple.getObject();
+            if ( ! workToDo(s) && ! workToDo(p) && ! workToDo(o) )
+                return;
+            generateAssignmentNode(s, assigns, builder);
+            generateAssignmentNode(p, assigns, builder);
+            generateAssignmentNode(o, assigns, builder);
+        }
+
+        private static Node applyReplacement(Node n, Function<Var, Node> replacement) {
+            if ( n instanceof Var x ) {
+                Node x2 = replacement.apply(x);
+                return ( x2 == null ) ? n : x2;
+            }
+            return n;
+    }
+
+//        // Allow for nulls.
+//        private Triple transform(Triple triple) {
+//
+//            if ( assigns.isEmpty() )
+//                return triple;
+//
+//            // XXX assignments()
+//
+//            Triple triple2 = Triple.create(s, p, o);
+//            return triple2;
+//        }
+
+        // XXX Other, non-std, ops.
+
+        private void generateAssignmentNode(Node n, VarExprList assigns, BindingBuilder builder) {
+            if ( n == null )
+                return;
+            if ( ! Var.isVar(n) )
+                return;
+            generateAssignmentVar(Var.alloc(n), assigns, builder);
+        }
+
+        private void generateAssignmentVar(Var var, VarExprList assigns, BindingBuilder builder) {
+            Node value = replacement.apply(var);
+            if ( value != null ) {
+                if ( ! builder.contains(var) ) {
+                    builder.add(var, value);
+                    assigns.add(var, NodeValue.makeNode(value));
+                }
+            }
+        }
+
+        // XXX Needed? To avoid object allocation.
+        private boolean workToDo(Node n) {
+            if ( n == null )
+                return false;
+            if ( ! Var.isVar(n) )
+                return false;
+            Var v = Var.alloc(n);
+            return null != replacement.apply(v);
+        }
+    }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
index 194654085a..2855857b43 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
@@ -60,6 +60,9 @@ public class JoinClassifier
         if ( right instanceof OpTopN )      return false ;
         if ( right instanceof OpOrder )     return false ;
 
+        // Lateral is different.
+        if ( right instanceof OpLateral )   return false ;
+
         // Assume something will not commute these later on.
         return check(left, right) ;
     }
@@ -67,7 +70,9 @@ public class JoinClassifier
     // -- pre check for ops we can't handle in a linear fashion.
     // These are the negation patterns (minus and diff)
     // FILTER NOT EXISTS is safe - it's defined by iteration like the linear execution algorithm.
-    private static class UnsafeLineraOpException extends RuntimeException {}
+    private static class UnsafeLineraOpException extends RuntimeException {
+        @Override public Throwable fillInStackTrace() { return this; }
+    }
     private static OpVisitor checkForUnsafeVisitor = new OpVisitorBase() {
         @Override public void visit(OpMinus opMinus) { throw new UnsafeLineraOpException(); }
         @Override public void visit(OpDiff opDiff)   { throw new UnsafeLineraOpException(); }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/LeftJoinClassifier.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/LeftJoinClassifier.java
index b94a601315..85c59069e2 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/LeftJoinClassifier.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/LeftJoinClassifier.java
@@ -25,6 +25,7 @@ import org.apache.jena.atlas.lib.SetUtils ;
 import org.apache.jena.sparql.algebra.Op ;
 import org.apache.jena.sparql.algebra.OpVars ;
 import org.apache.jena.sparql.algebra.op.OpExt ;
+import org.apache.jena.sparql.algebra.op.OpLateral;
 import org.apache.jena.sparql.algebra.op.OpLeftJoin ;
 import org.apache.jena.sparql.algebra.op.OpModifier ;
 import org.apache.jena.sparql.core.Var ;
@@ -59,6 +60,8 @@ public class LeftJoinClassifier
         // With SELECT *, it's as if the subquery were just the pattern.
         if ( right instanceof OpModifier )
             return false ;
+        if ( right instanceof OpLateral )
+            return false ;
 
         Set<Var> leftVars = OpVars.visibleVars(left) ;
         if ( print ) {
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/EvaluatorDispatch.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/EvaluatorDispatch.java
index 1df12f683b..f1b1de2790 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/EvaluatorDispatch.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/EvaluatorDispatch.java
@@ -35,88 +35,76 @@ import org.apache.jena.sparql.exec.http.Service;
 
 /**  Class to provide type-safe eval() dispatch using the visitor support of Op */
 
-public class EvaluatorDispatch implements OpVisitor
-{
+public class EvaluatorDispatch implements OpVisitor {
     private Deque<Table> stack = new ArrayDeque<>();
     protected Evaluator evaluator;
 
-    public EvaluatorDispatch(Evaluator evaluator)
-    {
+    public EvaluatorDispatch(Evaluator evaluator) {
         this.evaluator = evaluator;
     }
 
-    protected Table eval(Op op)
-    {
+    protected Table eval(Op op) {
         op.visit(this);
         return pop();
     }
 
-    Table getResult()
-    {
+    Table getResult() {
         if ( stack.size() != 1 )
-            Log.warn(this, "Warning: getResult: stack size = "+stack.size());
+            Log.warn(this, "Warning: getResult: stack size = " + stack.size());
 
         Table table = pop();
         return table;
     }
 
     @Override
-    public void visit(OpBGP opBGP)
-    {
+    public void visit(OpBGP opBGP) {
         Table table = evaluator.basicPattern(opBGP.getPattern());
         push(table);
     }
 
     @Override
-    public void visit(OpQuadPattern quadPattern)
-    {
+    public void visit(OpQuadPattern quadPattern) {
         push(RefEval.evalQuadPattern(quadPattern, evaluator));
     }
 
     @Override
-    public void visit(OpQuadBlock quadBlock)
-    {
+    public void visit(OpQuadBlock quadBlock) {
         push(eval(quadBlock.convertOp()));
-        //push(Eval.evalQuadPattern(quadBlock, evaluator));
+        // push(Eval.evalQuadPattern(quadBlock, evaluator));
     }
 
     @Override
-    public void visit(OpTriple opTriple)
-    {
+    public void visit(OpTriple opTriple) {
         visit(opTriple.asBGP());
     }
 
     @Override
-    public void visit(OpQuad opQuad)
-    {
+    public void visit(OpQuad opQuad) {
         visit(opQuad.asQuadPattern());
     }
+
     @Override
-    public void visit(OpPath opPath)
-    {
+    public void visit(OpPath opPath) {
         Table table = evaluator.pathPattern(opPath.getTriplePath());
         push(table);
     }
 
     @Override
-    public void visit(OpProcedure opProc)
-    {
+    public void visit(OpProcedure opProc) {
         Table table = eval(opProc.getSubOp());
         table = evaluator.procedure(table, opProc.getProcId(), opProc.getArgs());
         push(table);
     }
 
     @Override
-    public void visit(OpPropFunc opPropFunc)
-    {
+    public void visit(OpPropFunc opPropFunc) {
         Table table = eval(opPropFunc.getSubOp());
         table = evaluator.propertyFunction(table, opPropFunc.getProperty(), opPropFunc.getSubjectArgs(), opPropFunc.getObjectArgs());
         push(table);
     }
 
     @Override
-    public void visit(OpJoin opJoin)
-    {
+    public void visit(OpJoin opJoin) {
         Table left = eval(opJoin.getLeft());
         Table right = eval(opJoin.getRight());
         Table table = evaluator.join(left, right);
@@ -124,13 +112,11 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpSequence opSequence)
-    {
+    public void visit(OpSequence opSequence) {
         // Evaluation is as a sequence of joins.
         Table table = TableFactory.createUnit();
 
-        for ( Iterator<Op> iter = opSequence.iterator(); iter.hasNext(); )
-        {
+        for ( Iterator<Op> iter = opSequence.iterator() ; iter.hasNext() ; ) {
             Op op = iter.next();
             Table eltTable = eval(op);
             table = evaluator.join(table, eltTable);
@@ -139,13 +125,11 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpDisjunction opDisjunction)
-    {
+    public void visit(OpDisjunction opDisjunction) {
         // Evaluation is as a concatentation of alternatives
         Table table = TableFactory.createEmpty();
 
-        for ( Iterator<Op> iter = opDisjunction.iterator(); iter.hasNext(); )
-        {
+        for ( Iterator<Op> iter = opDisjunction.iterator() ; iter.hasNext() ; ) {
             Op op = iter.next();
             Table eltTable = eval(op);
             table = evaluator.union(table, eltTable);
@@ -154,17 +138,26 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpLeftJoin opLeftJoin)
-    {
+    public void visit(OpLeftJoin opLeftJoin) {
         Table left = eval(opLeftJoin.getLeft());
         Table right = eval(opLeftJoin.getRight());
+
+        try {
+            left.rows();
+        } catch (Throwable th) {
+            Op x = opLeftJoin.getLeft();
+            eval(x);
+
+            System.out.println("NULL");
+        }
+
+
         Table table = evaluator.leftJoin(left, right, opLeftJoin.getExprs());
         push(table);
     }
 
     @Override
-    public void visit(OpDiff opDiff)
-    {
+    public void visit(OpDiff opDiff) {
         Table left = eval(opDiff.getLeft());
         Table right = eval(opDiff.getRight());
         Table table = evaluator.diff(left, right);
@@ -172,8 +165,7 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpMinus opMinus)
-    {
+    public void visit(OpMinus opMinus) {
         Table left = eval(opMinus.getLeft());
         Table right = eval(opMinus.getRight());
         Table table = evaluator.minus(left, right);
@@ -181,8 +173,7 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpUnion opUnion)
-    {
+    public void visit(OpUnion opUnion) {
         Table left = eval(opUnion.getLeft());
         Table right = eval(opUnion.getRight());
         Table table = evaluator.union(left, right);
@@ -190,8 +181,7 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpConditional opCond)
-    {
+    public void visit(OpConditional opCond) {
         Table left = eval(opCond.getLeft());
         // Ref engine - don't care about efficiency
         Table right = eval(opCond.getRight());
@@ -200,60 +190,53 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpLateral opLateral)
-    {
+    public void visit(OpLateral opLateral) {
         Table left = eval(opLateral.getLeft());
         Table table = evaluator.lateral(left, opLateral.getRight());
         push(table);
     }
 
     @Override
-    public void visit(OpFilter opFilter)
-    {
+    public void visit(OpFilter opFilter) {
         Table table = eval(opFilter.getSubOp());
         table = evaluator.filter(opFilter.getExprs(), table);
         push(table);
     }
 
     @Override
-    public void visit(OpGraph opGraph)
-    {
+    public void visit(OpGraph opGraph) {
         push(RefEval.evalGraph(opGraph, evaluator));
     }
 
     @Override
-    public void visit(OpService opService)
-    {
+    public void visit(OpService opService) {
         QueryIterator qIter = Service.exec(opService, ARQ.getContext());
         Table table = TableFactory.create(qIter);
         push(table);
     }
 
     @Override
-    public void visit(OpDatasetNames dsNames)
-    {
+    public void visit(OpDatasetNames dsNames) {
         push(RefEval.evalDS(dsNames, evaluator));
     }
 
     @Override
-    public void visit(OpTable opTable)
-    {
+    public void visit(OpTable opTable) {
         push(opTable.getTable());
     }
 
     @Override
-    public void visit(OpExt opExt)
-    { throw new QueryExecException("Encountered OpExt during execution of reference engine"); }
+    public void visit(OpExt opExt) {
+        throw new QueryExecException("Encountered OpExt during execution of reference engine");
+    }
 
     @Override
-    public void visit(OpNull opNull)
-    {
+    public void visit(OpNull opNull) {
         push(TableFactory.createEmpty());
     }
 
     @Override
-    public void visit(OpLabel opLabel)
-    {
+    public void visit(OpLabel opLabel) {
         if ( opLabel.hasSubOp() )
             push(eval(opLabel.getSubOp()));
         else
@@ -262,93 +245,86 @@ public class EvaluatorDispatch implements OpVisitor
     }
 
     @Override
-    public void visit(OpList opList)
-    {
+    public void visit(OpList opList) {
         Table table = eval(opList.getSubOp());
         table = evaluator.list(table);
         push(table);
     }
 
     @Override
-    public void visit(OpOrder opOrder)
-    {
+    public void visit(OpOrder opOrder) {
         Table table = eval(opOrder.getSubOp());
         table = evaluator.order(table, opOrder.getConditions());
         push(table);
     }
 
     @Override
-    public void visit(OpTopN opTop)
-    {
+    public void visit(OpTopN opTop) {
         Table table = eval(opTop.getSubOp());
-        //table = evaluator.topN(table, opTop.getLimti(), opTop.getConditions());
+        // table = evaluator.topN(table, opTop.getLimti(), opTop.getConditions());
         table = evaluator.order(table, opTop.getConditions());
         table = evaluator.slice(table, 0, opTop.getLimit());
         push(table);
     }
 
     @Override
-    public void visit(OpProject opProject)
-    {
+    public void visit(OpProject opProject) {
         Table table = eval(opProject.getSubOp());
         table = evaluator.project(table, opProject.getVars());
         push(table);
     }
 
     @Override
-    public void visit(OpDistinct opDistinct)
-    {
+    public void visit(OpDistinct opDistinct) {
         Table table = eval(opDistinct.getSubOp());
         table = evaluator.distinct(table);
         push(table);
     }
 
     @Override
-    public void visit(OpReduced opReduced)
-    {
+    public void visit(OpReduced opReduced) {
         Table table = eval(opReduced.getSubOp());
         table = evaluator.reduced(table);
         push(table);
     }
 
     @Override
-    public void visit(OpSlice opSlice)
-    {
+    public void visit(OpSlice opSlice) {
         Table table = eval(opSlice.getSubOp());
         table = evaluator.slice(table, opSlice.getStart(), opSlice.getLength());
         push(table);
     }
 
     @Override
-    public void visit(OpAssign opAssign)
-    {
+    public void visit(OpAssign opAssign) {
         Table table = eval(opAssign.getSubOp());
         table = evaluator.assign(table, opAssign.getVarExprList());
         push(table);
     }
 
     @Override
-    public void visit(OpExtend opExtend)
-    {
+    public void visit(OpExtend opExtend) {
         Table table = eval(opExtend.getSubOp());
         table = evaluator.extend(table, opExtend.getVarExprList());
         push(table);
     }
 
     @Override
-    public void visit(OpGroup opGroup)
-    {
+    public void visit(OpGroup opGroup) {
         Table table = eval(opGroup.getSubOp());
         table = evaluator.groupBy(table, opGroup.getGroupVars(), opGroup.getAggregators());
         push(table);
     }
 
-    protected void push(Table table)  { stack.push(table); }
-    protected Table pop()
-    {
+    protected void push(Table table) {
+        stack.push(table);
+    }
+
+    protected Table pop() {
         if ( stack.size() == 0 )
             Log.warn(this, "Warning: pop: empty stack");
-        return stack.pop();
+        Table table = stack.pop();
+        return table;
     }
 
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/RefEval.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/RefEval.java
index ba212ff3f8..83ddfd4256 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/RefEval.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/ref/RefEval.java
@@ -49,7 +49,6 @@ import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper;
 import org.apache.jena.sparql.engine.iterator.QueryIterRoot;
 import org.apache.jena.sparql.engine.main.QC;
 
-// Spit out a few of the longer ops.
 public class RefEval {
     public static Table eval(Evaluator evaluator, Op op) {
         EvaluatorDispatch ev = new EvaluatorDispatch(evaluator);
@@ -58,6 +57,7 @@ public class RefEval {
         return table;
     }
 
+    // Spit out a few of the longer ops.
     static Table evalDS(OpDatasetNames opDSN, Evaluator evaluator) {
         Node graphNode = opDSN.getGraphNode();
         if ( graphNode.isURI() ) {
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java b/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java
index cb79fd01ce..5e0690ad15 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java
@@ -31,21 +31,29 @@ import org.apache.jena.sparql.syntax.*;
 
 /** Calculate in-scope variables from the AST */
 public class SyntaxVarScope {
+    // @formatter:off
     /* SPARQL 1.1 "in scope" rules These define the variables from a pattern that are
      * in-scope These are not the usage rules.
      *
-     * Syntax Form In-scope variables
+     * <a href="https://www.w3.org/TR/sparql11-query/#variableScope">18.2.1 Variable Scope</a>
      *
-     * Basic Graph Pattern (BGP) v occurs in the BGP Path v occurs in the path Group
-     * { P1 P2 ... } v is in-scope if in-scope in one or more of P1, P2, ... GRAPH
-     * term { P } v is term or v is in-scope in P { P1 } UNION { P2 } v is in-scope
-     * in P1 or in-scope in P2 OPTIONAL {P} v is in-scope in P SERVICE term {P} v is
-     * term or v is in-scope in P (expr AS v) for BIND, SELECT and GROUP BY v is
-     * in-scope SELECT ..v .. { P } v is in-scope if v is mentioned as a project
-     * variable SELECT * { P } v is in-scope in P VALUES var (values) v is in-scope
-     * if v is in varlist VALUES varlist (values) v is in-scope if v is in varlist */
-
-    // Weakness : EXISTS inside FILTERs?
+     * Syntax Form In-scope variables
+     * <pre>
+     * Basic Graph Pattern (BGP) v occurs in the BGP
+     * Path                      v occurs in the path
+     * Group { P1 P2 ... }       v is in-scope if in-scope in one or more of P1, P2, ...
+     * GRAPH term { P }          v is term or v is in-scope in P
+     * { P1 } UNION { P2 }       v is in-scope in P1 or in-scope in P2
+     * OPTIONAL {P}              v is in-scope in P
+     * SERVICE term {P}          v is term or v is in-scope in P
+     * (expr AS v) for BIND, SELECT and GROUP BY   v is in-scope
+     * SELECT ..v .. { P }       v is in-scope if v is mentioned as a project variable
+     * SELECT * { P }            v is in-scope in P
+     * VALUES var (values)       v is in-scope
+     * VALUES varlist (values)   v is in-scope if v is in varlist
+     * </pre>
+     */
+    //@formatter:on
 
     public static void check(Query query) {
         if ( query.getQueryPattern() == null )
@@ -220,17 +228,17 @@ public class SyntaxVarScope {
             for ( int i = 0 ; i < el.size() ; i++ ) {
                 Element e = el.get(i);
                 // Tests.
-                if ( e instanceof ElementBind ) {
+                if ( e instanceof ElementBind eltBind) {
                     Collection<Var> accScope = calcScopeAll(el.getElements(), i);
-                    checkBIND(accScope, (ElementBind)e);
+                    checkBIND(accScope, eltBind);
                 }
-                if ( e instanceof ElementService ) {
+                if ( e instanceof ElementService eltSvc ) {
                     Collection<Var> accScope = calcScopeAll(el.getElements(), i);
-                    checkSERVICE(accScope, (ElementService)e);
+                    checkSERVICE(accScope, eltSvc);
                 }
-                if ( e instanceof ElementLateral ) {
+                if ( e instanceof ElementLateral eltLat) {
                     Collection<Var> accScope = calcScopeAll(el.getElements(), i);
-                    checkLATERAL(accScope, e);
+                    checkLATERAL(accScope, eltLat);
                 }
             }
         }
@@ -285,6 +293,7 @@ public class SyntaxVarScope {
 
                 @Override
                 public void visit(ElementSubQuery eltSubQuery) {
+
                     // Only called when there is an expression.
                     eltSubQuery.getQuery().getProject().forEachExpr((var, expr) -> {
                         if ( accScope.contains(var) )
@@ -293,19 +302,23 @@ public class SyntaxVarScope {
                     // Check inside query pattern
                     Query subQuery = eltSubQuery.getQuery();
                     Collection<Var> accScope2 = accScope;
-                    //eltSubQuery.getQuery().setResultVars();
                     if ( ! subQuery.isQueryResultStar() ) {
                         List<Var> projectVars = eltSubQuery.getQuery().getProject().getVars();
-                        // Calculate variables passed down : scope which are in the project vars.
+                        // Copy
                         accScope2 = new ArrayList<>(accScope);
+                        // Calculate variables passed down : remove any that are not in the project vars
+                        // Any reused name will be renamed apart later.
                         accScope2.removeIf(v->!projectVars.contains(v));
                     }
+                    if ( accScope2.isEmpty() )
+                        // No work to do.
+                        return;
                     Element el2 = eltSubQuery.getQuery().getQueryPattern();
                     checkLATERAL(accScope2, el2);
                 }
             };
 
-            // Does not walk into subqueries but we need to change the scoep for sub-queries.
+            // Does not walk into subqueries but we need to change the scope for sub-queries.
             ElementWalker.walk(el, checker);
         }
     }
diff --git a/jena-arq/testing/ARQ/Lateral/data2.ttl b/jena-arq/testing/ARQ/Lateral/data2.ttl
new file mode 100644
index 0000000000..35745fbbfa
--- /dev/null
+++ b/jena-arq/testing/ARQ/Lateral/data2.ttl
@@ -0,0 +1,9 @@
+@prefix ex: <http://example.org/> .
+
+ex:s1 a ex:T ;
+   ex:p "11" , "12" , "13" .
+
+ex:s2 a ex:T ;
+   ex:p "21" , "22" , "23" .
+
+ex:s3 a ex:T .
diff --git a/jena-arq/testing/ARQ/Lateral/lateral-in-optional.arq b/jena-arq/testing/ARQ/Lateral/lateral-in-optional.arq
new file mode 100644
index 0000000000..a746707a5d
--- /dev/null
+++ b/jena-arq/testing/ARQ/Lateral/lateral-in-optional.arq
@@ -0,0 +1,15 @@
+  PREFIX  ex:   <http://example.org/>
+
+  SELECT  ?s ?o
+  WHERE
+    { ?s  a  ex:T
+      OPTIONAL
+        { LATERAL
+            { SELECT  ?s ?o
+              WHERE
+                { ?s  ex:p  ?o }
+              ORDER BY ?o
+              LIMIT   2
+            }
+        }
+    }
\ No newline at end of file
diff --git a/jena-arq/testing/ARQ/Lateral/lateral-in-optional.srj b/jena-arq/testing/ARQ/Lateral/lateral-in-optional.srj
new file mode 100644
index 0000000000..1a521b0769
--- /dev/null
+++ b/jena-arq/testing/ARQ/Lateral/lateral-in-optional.srj
@@ -0,0 +1,22 @@
+{ "head": {
+    "vars": [ "s" , "o" ]
+  } ,
+  "results": {
+    "bindings": [
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s3" }
+      } ,
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s1" } ,
+        "o": { "type": "literal" , "value": "11" }
+      } ,
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s1" } ,
+        "o": { "type": "literal" , "value": "12" }
+      } ,
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s2" }
+      }
+    ]
+  }
+}
diff --git a/jena-arq/testing/ARQ/Lateral/manifest.ttl b/jena-arq/testing/ARQ/Lateral/manifest.ttl
index 08276f22a1..ea2b7ee3a0 100644
--- a/jena-arq/testing/ARQ/Lateral/manifest.ttl
+++ b/jena-arq/testing/ARQ/Lateral/manifest.ttl
@@ -37,6 +37,8 @@ PREFIX skos:    <http://www.w3.org/2004/02/skos/core#>
       <#lateral-3>
       <#lateral-4>
       <#lateral-5>
+      <#lateral-6>
+      <#lateral-7>
     ).
 
 <#lateral-1> rdf:type mf:QueryEvaluationTest ;
@@ -84,6 +86,24 @@ PREFIX skos:    <http://www.w3.org/2004/02/skos/core#>
     mf:result  <lateral-5.srj>
     .
 
+<#lateral-6> rdf:type mf:QueryEvaluationTest ;
+    mf:name      "LATERAL - LATERAL inside OPTIONAL" ;
+    mf:action [
+       qt:query  <lateral-in-optional.arq> ;
+       qt:data   <data2.ttl>
+    ] ;
+    mf:result  <lateral-in-optional.srj>
+    .
+
+<#lateral-7> rdf:type mf:QueryEvaluationTest ;
+    mf:name      "LATERAL - OPTIONAL inside LATERAL" ;
+    mf:action [
+       qt:query  <optional-in-lateral.arq> ;
+       qt:data   <data2.ttl>
+    ] ;
+    mf:result  <optional-in-lateral.srj>
+    .
+
 
 ## ?x :q ?z . LATERAL { ?s :q ?o FILTER (?z = ?o) }
 
diff --git a/jena-arq/testing/ARQ/Lateral/optional-in-lateral.arq b/jena-arq/testing/ARQ/Lateral/optional-in-lateral.arq
new file mode 100644
index 0000000000..19834d9ee7
--- /dev/null
+++ b/jena-arq/testing/ARQ/Lateral/optional-in-lateral.arq
@@ -0,0 +1,16 @@
+  PREFIX  ex:   <http://example.org/>
+
+  SELECT  ?s ?o
+  WHERE
+    { ?s  a  ex:T
+      LATERAL
+        { OPTIONAL
+            { SELECT  ?s ?o
+              WHERE
+                { ?s  ex:p  ?o }
+              ORDER BY ?o
+              LIMIT   2
+            }
+        }
+    }
+    
\ No newline at end of file
diff --git a/jena-arq/testing/ARQ/Lateral/optional-in-lateral.srj b/jena-arq/testing/ARQ/Lateral/optional-in-lateral.srj
new file mode 100644
index 0000000000..97d1bd12c8
--- /dev/null
+++ b/jena-arq/testing/ARQ/Lateral/optional-in-lateral.srj
@@ -0,0 +1,27 @@
+{ "head": {
+    "vars": [ "s" , "o" ]
+  } ,
+  "results": {
+    "bindings": [
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s3" }
+      } ,
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s1" } ,
+        "o": { "type": "literal" , "value": "11" }
+      } ,
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s1" } ,
+        "o": { "type": "literal" , "value": "12" }
+      } ,
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s2" } ,
+        "o": { "type": "literal" , "value": "21" }
+      } ,
+      { 
+        "s": { "type": "uri" , "value": "http://example.org/s2" } ,
+        "o": { "type": "literal" , "value": "22" }
+      }
+    ]
+  }
+}


(jena) 03/04: Use lambdas in lang/ParserSPARQL_N

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 a9052183be7aca4f3465333f60b274069b19850f
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sat Feb 3 15:44:55 2024 +0000

    Use lambdas in lang/ParserSPARQL_N
---
 .../apache/jena/sparql/lang/ParserSPARQL10.java    | 27 +++++++---------------
 .../apache/jena/sparql/lang/ParserSPARQL11.java    | 26 ++++++---------------
 .../apache/jena/sparql/lang/ParserSPARQL12.java    | 27 ++++++----------------
 3 files changed, 22 insertions(+), 58 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL10.java b/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL10.java
index 3c74b52687..1474ca4777 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL10.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL10.java
@@ -39,25 +39,17 @@ public class ParserSPARQL10 extends SPARQLParser
     protected Query parse$(final Query query, String queryString) {
         query.setSyntax(Syntax.syntaxSPARQL_10);
 
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser10 parser) throws Exception {
-                parser.QueryUnit();
-            }
-        };
-
+        Action action = (SPARQLParser10 parser) ->
+            parser.QueryUnit();
         perform(query, queryString, action);
         return query;
     }
 
     public static Element parseElement(String string) {
         final Query query = new Query();
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser10 parser) throws Exception {
-                Element el = parser.GroupGraphPattern();
-                query.setQueryPattern(el);
-            }
+        Action action = (SPARQLParser10 parser) ->{
+            Element el = parser.GroupGraphPattern();
+            query.setQueryPattern(el);
         };
         perform(query, string, action);
         return query.getQueryPattern();
@@ -65,12 +57,9 @@ public class ParserSPARQL10 extends SPARQLParser
 
     public static Template parseTemplate(String string) {
         final Query query = new Query();
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser10 parser) throws Exception {
-                Template t = parser.ConstructTemplate();
-                query.setConstructTemplate(t);
-            }
+        Action action = (SPARQLParser10 parser) ->{
+            Template t = parser.ConstructTemplate();
+            query.setConstructTemplate(t);
         };
         perform(query, string, action);
         return query.getConstructTemplate();
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL11.java b/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL11.java
index 732b5ef272..f40b04f009 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL11.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL11.java
@@ -39,25 +39,16 @@ public class ParserSPARQL11 extends SPARQLParser
     protected Query parse$(final Query query, String queryString) {
         query.setSyntax(Syntax.syntaxSPARQL_11);
 
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser11 parser) throws Exception {
-                parser.QueryUnit();
-            }
-        };
-
+        Action action = (SPARQLParser11 parser) -> parser.QueryUnit();
         perform(query, queryString, action);
         return query;
     }
 
     public static Element parseElement(String string) {
         final Query query = new Query();
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser11 parser) throws Exception {
-                Element el = parser.GroupGraphPattern();
-                query.setQueryPattern(el);
-            }
+        Action action = (SPARQLParser11 parser) -> {
+            Element el = parser.GroupGraphPattern();
+            query.setQueryPattern(el);
         };
         perform(query, string, action);
         return query.getQueryPattern();
@@ -65,12 +56,9 @@ public class ParserSPARQL11 extends SPARQLParser
 
     public static Template parseTemplate(String string) {
         final Query query = new Query();
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser11 parser) throws Exception {
-                Template t = parser.ConstructTemplate();
-                query.setConstructTemplate(t);
-            }
+        Action action = (SPARQLParser11 parser) -> {
+            Template t = parser.ConstructTemplate();
+            query.setConstructTemplate(t);
         };
         perform(query, string, action);
         return query.getConstructTemplate();
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL12.java b/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL12.java
index e097c20312..a2baddb724 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL12.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/lang/ParserSPARQL12.java
@@ -38,26 +38,16 @@ public class ParserSPARQL12 extends SPARQLParser
     @Override
     protected Query parse$(final Query query, String queryString) {
         query.setSyntax(Syntax.syntaxSPARQL_12);
-
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser12 parser) throws Exception {
-                parser.QueryUnit();
-            }
-        };
-
+        Action action = (SPARQLParser12 parser) -> parser.QueryUnit();
         perform(query, queryString, action);
         return query;
     }
 
     public static Element parseElement(String string) {
         final Query query = new Query();
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser12 parser) throws Exception {
-                Element el = parser.GroupGraphPattern();
-                query.setQueryPattern(el);
-            }
+        Action action = (SPARQLParser12 parser) -> {
+            Element el = parser.GroupGraphPattern();
+            query.setQueryPattern(el);
         };
         perform(query, string, action);
         return query.getQueryPattern();
@@ -65,12 +55,9 @@ public class ParserSPARQL12 extends SPARQLParser
 
     public static Template parseTemplate(String string) {
         final Query query = new Query();
-        Action action = new Action() {
-            @Override
-            public void exec(SPARQLParser12 parser) throws Exception {
-                Template t = parser.ConstructTemplate();
-                query.setConstructTemplate(t);
-            }
+        Action action = (SPARQLParser12 parser) -> {
+            Template t = parser.ConstructTemplate();
+            query.setConstructTemplate(t);
         };
         perform(query, string, action);
         return query.getConstructTemplate();


(jena) 02/04: Add null tests. Reformat.

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 7dce9ffa527a14a2bdb451b37150edca9001adbe
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Jan 28 14:41:12 2024 +0000

    Add null tests. Reformat.
---
 .../main/java/org/apache/jena/sparql/core/Var.java | 85 ++++++++++++----------
 1 file changed, 45 insertions(+), 40 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/Var.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/Var.java
index f1130238d8..e2e85bb37e 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/Var.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/Var.java
@@ -16,19 +16,20 @@
  * limitations under the License.
  */
 
-package org.apache.jena.sparql.core ;
+package org.apache.jena.sparql.core;
 
-import java.util.ArrayList ;
-import java.util.Collection ;
-import java.util.List ;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
 import java.util.function.Function;
 
-import org.apache.jena.graph.Node ;
-import org.apache.jena.graph.Node_Variable ;
-import org.apache.jena.sparql.ARQConstants ;
-import org.apache.jena.sparql.ARQInternalErrorException ;
-import org.apache.jena.sparql.engine.binding.Binding ;
-import org.apache.jena.sparql.expr.ExprVar ;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Node_Variable;
+import org.apache.jena.sparql.ARQConstants;
+import org.apache.jena.sparql.ARQInternalErrorException;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.expr.ExprVar;
 import org.apache.jena.sys.JenaSystem;
 
 /** A SPARQL variable */
@@ -44,22 +45,23 @@ public class Var extends Node_Variable
 
     // Legal SPARQL variable name but note it must be exactly this
     // object, not just the same name, to be anonymous.
-    public static Var ANON = new Var("?_") ;
+    public static Var ANON = new Var("?_");
 
     public static Var alloc(String varName) {
+        Objects.requireNonNull(varName);
 //        if ( varName.equals("_") )
-//            return ANON ;
-        return new Var(varName) ;
+//            return ANON;
+        return new Var(varName);
     }
 
-    public static Var alloc(Node_Variable v) // asVar?
-    {
+    public static Var alloc(Node_Variable v) {
         if ( v instanceof Var )
             return (Var)v;
         return new Var(v);
     }
 
     public static Var alloc(Node v) {
+        Objects.requireNonNull(v);
         if ( v instanceof Var )
             return (Var)v;
         if ( v instanceof Node_Variable )
@@ -68,13 +70,15 @@ public class Var extends Node_Variable
     }
 
     public static Var alloc(Var v) {
-        return v;
+        return Objects.requireNonNull(v);
     }
 
-    public static Var alloc(ExprVar nv)         { return new Var(nv) ; }
+    public static Var alloc(ExprVar nv)         { return new Var(nv); }
 
     /** Return the value in the binding (if node is a Var) or the node itself. */
     public static Node lookup(Binding binding, Node node) {
+        Objects.requireNonNull(binding);
+        Objects.requireNonNull(node);
         if ( !Var.isVar(node) )
             return node;
         Var var = Var.alloc(node);
@@ -83,6 +87,7 @@ public class Var extends Node_Variable
 
     /** Return the value in the binding or the variable itself. */
     public static Node lookup(Binding binding, Var var) {
+        Objects.requireNonNull(var);
         Node n = binding.get(var);
         if ( n != null )
             return n;
@@ -106,41 +111,41 @@ public class Var extends Node_Variable
     }
 
     // Precalulated the hash code because hashCode() is used so heavily with Var's
-    private final int hashCodeValue ;
+    private final int hashCodeValue;
 
-    private Var(String varName)      { super(varName) ; hashCodeValue = super.hashCode() ; }
+    private Var(String varName)      { super(varName); hashCodeValue = super.hashCode(); }
 
-    private Var(Node_Variable v)     { this(v.getName()) ; }
+    private Var(Node_Variable v)     { this(v.getName()); }
 
-    private Var(ExprVar v)           { this(v.getVarName()) ; }
+    private Var(ExprVar v)           { this(v.getVarName()); }
 
-    public String getVarName()       { return getName() ; }
+    public String getVarName()       { return getName(); }
 
     static class NotAVariableException extends ARQInternalErrorException {
-        NotAVariableException(String msg) { super(msg) ; }
+        NotAVariableException(String msg) { super(msg); }
     }
 
     @Override
-    public final int hashCode() { return hashCodeValue ; }
+    public final int hashCode() { return hashCodeValue; }
 
     @Override
     public final boolean equals(Object other) {
-        if ( this == other ) return true ;
+        if ( this == other ) return true;
         if ( ! ( other instanceof Var ) )
-            return false ;
-        return super.equals(other) ;
+            return false;
+        return super.equals(other);
     }
 
 //    @Override
-//    public String toString() { return node.toString() ; }
+//    public String toString() { return node.toString(); }
 
-    public boolean isNamedVar() { return isNamedVarName(getName()) ; }
+    public boolean isNamedVar() { return isNamedVarName(getName()); }
 
-    public boolean isBlankNodeVar() { return isBlankNodeVarName(getName()) ; }
+    public boolean isBlankNodeVar() { return isBlankNodeVarName(getName()); }
 
-    public boolean isAllocVar() { return isAllocVarName(getName()) ; }
+    public boolean isAllocVar() { return isAllocVarName(getName()); }
 
-    public boolean isAnonVar() { return isAnonVar(this) ; }
+    public boolean isAnonVar() { return isAnonVar(this); }
 
     // -------
 
@@ -161,28 +166,28 @@ public class Var extends Node_Variable
     }
 
     public static boolean isRenamedVar(Node node)
-    { return node.isVariable() && isRenamedVar(node.getName()) ; }
+    { return node.isVariable() && isRenamedVar(node.getName()); }
 
     public static boolean isRenamedVar(String x)
-    { return x.startsWith(ARQConstants.allocVarScopeHiding) ; }
+    { return x.startsWith(ARQConstants.allocVarScopeHiding); }
 
     public static boolean isNamedVar(Node node)
-    { return node.isVariable() && isNamedVarName(node.getName()) ; }
+    { return node.isVariable() && isNamedVarName(node.getName()); }
 
     public static boolean isNamedVarName(String x)
-    { return ! isBlankNodeVarName(x) && ! isAllocVarName(x) ; }
+    { return ! isBlankNodeVarName(x) && ! isAllocVarName(x); }
 
     public static boolean isBlankNodeVar(Node node)
-    { return node.isVariable() && isBlankNodeVarName(node.getName()) ; }
+    { return node.isVariable() && isBlankNodeVarName(node.getName()); }
 
     public static boolean isBlankNodeVarName(String x)
-    { return x.startsWith(ARQConstants.allocVarAnonMarker) ; }
+    { return x.startsWith(ARQConstants.allocVarAnonMarker); }
 
     public static boolean isAllocVar(Node node)
-    { return node.isVariable() && isAllocVarName(node.getName()) ; }
+    { return node.isVariable() && isAllocVarName(node.getName()); }
 
     public static boolean isAllocVarName(String x)
-    { return x.startsWith(ARQConstants.allocVarMarker) ; }
+    { return x.startsWith(ARQConstants.allocVarMarker); }
 
     /** Convert a collection of variable names to variables */
     public static List<Var> varList(Collection<String> varNames) {


(jena) 01/04: Javadoc tweaks for QuotedURI

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 1017746a7679daf75b1199b7ef6073e397d73950
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Jan 28 14:39:11 2024 +0000

    Javadoc tweaks for QuotedURI
---
 .../main/java/org/apache/jena/riot/out/quoted/QuotedURI.java | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java b/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java
index 89eef18626..5f1d417b42 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java
@@ -31,12 +31,14 @@ import org.apache.jena.riot.SysRIOT;
 /**
  * Writing URI strings for Turtle etc.
  * <p>If the URI string contains bad characters (control characters
- * x00 to x20 and characters <>"{}|^`\) various ways to handle this are provided.
+ * x00 to x20 and characters {@literal <>}"{}|^`\) various ways to handle this are provided.
  * They cause either a different URI to written or an illegal one.
  * <p>
- * There is no way to
- * write illegal characters. Percent-encoding is an encoding, not an escape
- * mechanism. It put actual 3 characters %-X-X.into the URI.
+ * There is no way to write these illegal characters.
+ * Percent-encoding is an encoding, not an escape mechanism.
+ * It put actual 3 characters %-X-X.into the URI.
+ * Even if the character is put in with a Unicode \-u escape, it is not a legal URI
+ * and will fail URI parsing.
  */
 public class QuotedURI {
 
@@ -46,9 +48,11 @@ public class QuotedURI {
         this(CharSpace.UTF8) ;
     }
 
+    /** Write a replacement for a bad character. */
     @FunctionalInterface
     private interface BadCharWriter { void writeChar(AWriter out, char ch); }
 
+    /** Signal a bad character. */
     @FunctionalInterface
     private interface BadCharHandler { void badChar(int idx, String str, char ch); }