You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by rv...@apache.org on 2013/06/25 21:06:45 UTC

svn commit: r1496593 - in /jena/trunk/jena-arq/src: main/java/com/hp/hpl/jena/sparql/algebra/optimize/ main/java/com/hp/hpl/jena/sparql/expr/ test/java/com/hp/hpl/jena/sparql/algebra/optimize/

Author: rvesse
Date: Tue Jun 25 19:06:44 2013
New Revision: 1496593

URL: http://svn.apache.org/r1496593
Log:
More test cases for implicit join optimizations along with some bug fixes (JENA-473)

Modified:
    jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformFilterImplicitJoin.java
    jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformImplicitLeftJoin.java
    jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/expr/ExprVar.java
    jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestSemanticEquivalence.java
    jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestTransformFilters.java

Modified: jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformFilterImplicitJoin.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformFilterImplicitJoin.java?rev=1496593&r1=1496592&r2=1496593&view=diff
==============================================================================
--- jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformFilterImplicitJoin.java (original)
+++ jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformFilterImplicitJoin.java Tue Jun 25 19:06:44 2013
@@ -420,7 +420,7 @@ public class TransformFilterImplicitJoin
     }
 
     private static Op subst(Op subOp, Var find, Var replace) {
-        Op op = Substitute.substitute(subOp, find, replace.asNode());
+        Op op = Substitute.substitute(subOp, find, replace);
         return OpAssign.assign(op, find, new ExprVar(replace));
     }
 }

Modified: jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformImplicitLeftJoin.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformImplicitLeftJoin.java?rev=1496593&r1=1496592&r2=1496593&view=diff
==============================================================================
--- jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformImplicitLeftJoin.java (original)
+++ jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/algebra/optimize/TransformImplicitLeftJoin.java Tue Jun 25 19:06:44 2013
@@ -150,9 +150,13 @@ public class TransformImplicitLeftJoin e
                 } else if (rhsVars.contains(lVar)) {
                     // Substitute left variable for right variable
                     op = processFilterWorker(op, lVar, rVar);
-                } else {
+                } else if (rhsVars.contains(rVar)) {
                     // Substitute right variable for left variable
                     op = processFilterWorker(op, rVar, lVar);
+                } else {
+                    // May be hit if trying to apply a sequence of
+                    // substitutions
+                    return null;
                 }
             } else if (lhsVars.contains(lVar)) {
                 // Only left variable on RHS
@@ -175,8 +179,16 @@ public class TransformImplicitLeftJoin e
                     return null;
                 }
             } else {
-                // May be hit if trying to apply a sequence of substitutions
-                return null;
+                // Neither variable is on LHS
+
+                if (rhsVars.contains(lVar) && rhsVars.contains(rVar)) {
+                    // Both variables are on RHS so can substitute one for the
+                    // other
+                    op = processFilterWorker(op, lVar, rVar);
+                } else {
+                    // May be hit if trying to apply a sequence of substitutions
+                    return null;
+                }
             }
 
             // Re-compute visible RHS vars after each substitution as it may
@@ -184,7 +196,11 @@ public class TransformImplicitLeftJoin e
             rhsVars = OpVars.visibleVars(op);
         }
 
-        return OpLeftJoin.create(left, op, remaining);
+        if (remaining.size() > 0) {
+            return OpLeftJoin.create(left, op, remaining);
+        } else {
+            return OpLeftJoin.create(left, op, (ExprList) null);
+        }
     }
 
     private static Pair<List<Pair<Var, Var>>, ExprList> preprocessFilterImplicitJoin(Op left, Op right, ExprList exprs) {
@@ -199,6 +215,8 @@ public class TransformImplicitLeftJoin e
                 // Special case for where we've split an && in case the
                 // remaining condition is also an implicit join that we can
                 // process
+                // TODO See notes in other preprocess() method for a suggested
+                // improved process for && expressions
                 if (others != exprsOther.size()) {
                     int possRemove = others;
                     others = exprsOther.size();
@@ -206,8 +224,12 @@ public class TransformImplicitLeftJoin e
                     if (p != null) {
                         exprsJoins.add(p);
                         // Two possibilities here:
-                        // 1 - The other condition was a plain implicit join in which case the size of the expr list won't have changed
-                        // 2 - The other condition was a nested && which contained an implicit join in which case the size of the expr list will have changed
+                        // 1 - The other condition was a plain implicit join in
+                        // which case the size of the expr list won't have
+                        // changed
+                        // 2 - The other condition was a nested && which
+                        // contained an implicit join in which case the size of
+                        // the expr list will have changed
                         if (others == exprsOther.size()) {
                             // Trim the additional condition off the others list
                             exprsOther = possRemove > 0 ? exprsOther.subList(0, possRemove - 1) : new ExprList();
@@ -216,8 +238,9 @@ public class TransformImplicitLeftJoin e
                             Expr extra = exprsOther.get(exprsOther.size() - 1);
                             exprsOther = possRemove > 0 ? exprsOther.subList(0, possRemove - 1) : new ExprList();
                             exprsOther.add(extra);
-                            
-                            // NB - We don't try and handle further nesting which in principle may exist
+
+                            // NB - We don't try and handle further nesting
+                            // which in principle may exist
                         }
                     }
                 }
@@ -236,9 +259,14 @@ public class TransformImplicitLeftJoin e
 
         ExprFunction2 eq = (ExprFunction2) e;
         if (e instanceof E_LogicalAnd) {
+            // TODO A better approach would be to define a general recursive
+            // traversal for && which pulls out all implicit joins and produces
+            // an && tree with all other non-eligible expressions
+
             // NB - We only handle a single level of nesting here
-            // There is some special case code in the calling function that can attempt to recurse one level further
-            
+            // There is some special case code in the calling function that can
+            // attempt to recurse one level further
+
             // Is LHS of the && an implicit join?
             if (eq.getArg1() instanceof E_Equals || eq.getArg1() instanceof E_SameTerm) {
                 Pair<Var, Var> p = preprocess(opLeft, opRight, eq.getArg1(), exprsOther);
@@ -441,7 +469,7 @@ public class TransformImplicitLeftJoin e
     }
 
     private static Op subst(Op subOp, Var find, Var replace) {
-        Op op = Substitute.substitute(subOp, find, replace.asNode());
+        Op op = Substitute.substitute(subOp, find, replace);
         return OpAssign.assign(op, find, new ExprVar(replace));
     }
 }

Modified: jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/expr/ExprVar.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/expr/ExprVar.java?rev=1496593&r1=1496592&r2=1496593&view=diff
==============================================================================
--- jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/expr/ExprVar.java (original)
+++ jena/trunk/jena-arq/src/main/java/com/hp/hpl/jena/sparql/expr/ExprVar.java Tue Jun 25 19:06:44 2013
@@ -69,14 +69,10 @@ public class ExprVar extends ExprNode
     public Expr copySubstitute(Binding binding, boolean foldConstants)
     {
         Var v = varNode ;  
-        if ( binding == null || !binding.contains(varNode) )
+        if ( binding == null || !binding.contains(v) )
             return new ExprVar(v) ;
-        return eval(binding, null) ;
-//        catch (VariableNotBoundException ex)
-//        {
-//            Log.warn(this, "Failed to eval bound variable (was bound earlier!)");
-//            throw ex ;
-//        }
+        Node v2 = binding.get(v);
+        return v2.isVariable() ? new ExprVar(v2) : eval(binding, null) ;
     }
     
     @Override

Modified: jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestSemanticEquivalence.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestSemanticEquivalence.java?rev=1496593&r1=1496592&r2=1496593&view=diff
==============================================================================
--- jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestSemanticEquivalence.java (original)
+++ jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestSemanticEquivalence.java Tue Jun 25 19:06:44 2013
@@ -22,8 +22,10 @@ import com.hp.hpl.jena.query.QueryExecut
 import com.hp.hpl.jena.query.QueryExecutionFactory;
 import com.hp.hpl.jena.query.QueryFactory;
 import com.hp.hpl.jena.query.ResultSetFactory;
+import com.hp.hpl.jena.query.ResultSetFormatter;
 import com.hp.hpl.jena.query.ResultSetRewindable;
 import com.hp.hpl.jena.rdf.model.ModelFactory;
+import com.hp.hpl.jena.sparql.algebra.Algebra;
 import com.hp.hpl.jena.sparql.algebra.Op;
 import com.hp.hpl.jena.sparql.algebra.OpVars;
 import com.hp.hpl.jena.sparql.core.DatasetGraph;
@@ -34,6 +36,9 @@ import com.hp.hpl.jena.sparql.engine.Res
 import com.hp.hpl.jena.sparql.engine.binding.BindingFactory;
 import com.hp.hpl.jena.sparql.engine.main.QueryEngineMain;
 import com.hp.hpl.jena.sparql.resultset.ResultSetCompare;
+import com.hp.hpl.jena.sparql.resultset.ResultsFormat;
+import com.hp.hpl.jena.sparql.resultset.TextOutput;
+import com.hp.hpl.jena.sparql.serializer.SerializationContext;
 import com.hp.hpl.jena.sparql.sse.SSE;
 import com.hp.hpl.jena.sparql.util.Symbol;
 
@@ -56,6 +61,7 @@ public class TestSemanticEquivalence {
         Node c = NodeFactory.createURI("http://c");
         Node p1 = NodeFactory.createURI("http://p1");
         Node p2 = NodeFactory.createURI("http://p2");
+        Node pSelf = NodeFactory.createURI("http://self");
         Node o = NodeFactory.createLiteral("object");
 
         DatasetGraph dsg = implJoin.asDatasetGraph();
@@ -64,6 +70,7 @@ public class TestSemanticEquivalence {
         dsg.add(Quad.defaultGraphNodeGenerated, b, p1, o);
         dsg.add(Quad.defaultGraphNodeGenerated, b, p2, o);
         dsg.add(Quad.defaultGraphNodeGenerated, c, p1, o);
+        //dsg.add(Quad.defaultGraphNodeGenerated, a, pSelf, a);
         
         // Currently these optimizations are off by default
         Assert.assertFalse(ARQ.isFalse(ARQ.optFilterImplicitJoin));
@@ -121,25 +128,34 @@ public class TestSemanticEquivalence {
     @Test
     public void implicitLeftJoinEvaluation1() {
         String query = "SELECT * WHERE { ?x <http://p1> ?o1 . OPTIONAL { ?y <http://p2> ?o2 . FILTER(?x = ?y) } }";
-        test(query, implJoin, ARQ.optFilterImplicitJoin, 3);
+        test(query, implJoin, ARQ.optImplicitLeftJoin, 3);
 
         String alg1 = "(leftjoin (bgp (?x <http://p1> ?o1)) (bgp (?y <http://p2> ?o2)) (= ?x ?y))";
-        testAsAlgebra(alg1, implJoin, ARQ.optFilterImplicitJoin, 3);
+        testAsAlgebra(alg1, implJoin, ARQ.optImplicitLeftJoin, 3);
 
         String alg2 = "(leftjoin (bgp (?x <http://p1> ?o1)) (bgp (?y <http://p2> ?o2)) (= ?y ?x))";
-        testAsAlgebra(alg2, implJoin, ARQ.optFilterImplicitJoin, 3);
+        testAsAlgebra(alg2, implJoin, ARQ.optImplicitLeftJoin, 3);
     }
 
     @Test
     public void implicitLeftJoinEvaluation2() {
         String query = "SELECT * WHERE { ?x <http://p1> ?o1 . OPTIONAL { ?y <http://p2> ?o2 . FILTER(?x = ?y && ?o1 >= ?o2) } }";
-        test(query, implJoin, ARQ.optFilterImplicitJoin, 3);
+        test(query, implJoin, ARQ.optImplicitLeftJoin, 3);
 
         String alg1 = "(leftjoin (bgp (?x <http://p1> ?o1)) (bgp (?y <http://p2> ?o2)) (&& (= ?x ?y)(> ?o1 ?o2)))";
-        testAsAlgebra(alg1, implJoin, ARQ.optFilterImplicitJoin, 3);
+        testAsAlgebra(alg1, implJoin, ARQ.optImplicitLeftJoin, 3);
 
         String alg2 = "(leftjoin (bgp (?x <http://p1> ?o1)) (bgp (?y <http://p2> ?o2)) (&& (= ?y ?x)(> ?o1 ?o2)))";
-        testAsAlgebra(alg2, implJoin, ARQ.optFilterImplicitJoin, 3);
+        testAsAlgebra(alg2, implJoin, ARQ.optImplicitLeftJoin, 3);
+    }
+    
+    @Test
+    public void implicitLeftJoinEvaluation3() {
+        String query = "SELECT * WHERE { ?x ?p ?o . OPTIONAL { ?y ?p1 ?o1 . ?y ?p2 ?z . FILTER(?x = ?y) FILTER(?x = ?z) FILTER(?y = ?z) } }";
+        test(query, implJoin, ARQ.optImplicitLeftJoin, 5);
+        
+        String alg1 = "(leftjoin (bgp (?x ?p ?o)) (bgp (?y ?p1 ?o1) (?y ?p2 ?z)) ((= ?x ?y) (= ?x ?z) (= ?y ?z)))";
+        testAsAlgebra(alg1, implJoin, ARQ.optImplicitLeftJoin, 5);
     }
     
     /**
@@ -160,6 +176,11 @@ public class TestSemanticEquivalence {
 
         if (!q.isSelectType())
             Assert.fail("Only SELECT queries are testable with this method");
+        
+        System.out.println(q.toString());
+        Op op = Algebra.compile(q);
+        System.out.println(op.toString());
+        System.out.println(Algebra.optimize(op));
 
         // Track current state
         boolean isEnabled = ARQ.isTrue(opt);
@@ -170,6 +191,12 @@ public class TestSemanticEquivalence {
             ARQ.set(opt, false);
             QueryExecution qe = QueryExecutionFactory.create(q, ds);
             ResultSetRewindable rs = ResultSetFactory.makeRewindable(qe.execSelect());
+            if (expected != rs.size()) {
+                System.err.println("Non-optimized results not as expected");
+                TextOutput output = new TextOutput((SerializationContext)null);
+                output.format(System.out, rs);
+                rs.reset();
+            }
             Assert.assertEquals(expected, rs.size());
             qe.close();
 
@@ -177,6 +204,12 @@ public class TestSemanticEquivalence {
             ARQ.set(opt, true);
             QueryExecution qeOpt = QueryExecutionFactory.create(q, ds);
             ResultSetRewindable rsOpt = ResultSetFactory.makeRewindable(qeOpt.execSelect());
+            if (expected != rsOpt.size()) {
+                System.err.println("Optimized results not as expected");
+                TextOutput output = new TextOutput((SerializationContext)null);
+                output.format(System.out, rsOpt);
+                rsOpt.reset();
+            }
             Assert.assertEquals(expected, rsOpt.size());
             qeOpt.close();
 
@@ -224,6 +257,12 @@ public class TestSemanticEquivalence {
             QueryIterator iter = engine.eval(op, ds.asDatasetGraph(), BindingFactory.binding(), ARQ.getContext());
             ResultSetRewindable rs = ResultSetFactory.makeRewindable(new ResultSetStream(vars, ModelFactory.createDefaultModel(),
                     iter));
+            if (expected != rs.size()) {
+                System.err.println("Non-optimized results not as expected");
+                TextOutput output = new TextOutput((SerializationContext)null);
+                output.format(System.out, rs);
+                rs.reset();
+            }
             Assert.assertEquals(expected, rs.size());
             iter.close();
 
@@ -233,6 +272,12 @@ public class TestSemanticEquivalence {
             QueryIterator iterOpt = engine.eval(op, ds.asDatasetGraph(), BindingFactory.binding(), ARQ.getContext());
             ResultSetRewindable rsOpt = ResultSetFactory.makeRewindable(new ResultSetStream(vars, ModelFactory
                     .createDefaultModel(), iterOpt));
+            if (expected != rsOpt.size()) {
+                System.err.println("Optimized results not as expected");
+                TextOutput output = new TextOutput((SerializationContext)null);
+                output.format(System.out, rsOpt);
+                rsOpt.reset();
+            }
             Assert.assertEquals(expected, rsOpt.size());
             iterOpt.close();
 

Modified: jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestTransformFilters.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestTransformFilters.java?rev=1496593&r1=1496592&r2=1496593&view=diff
==============================================================================
--- jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestTransformFilters.java (original)
+++ jena/trunk/jena-arq/src/test/java/com/hp/hpl/jena/sparql/algebra/optimize/TestTransformFilters.java Tue Jun 25 19:06:44 2013
@@ -18,20 +18,34 @@
 
 package com.hp.hpl.jena.sparql.algebra.optimize;
 
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.plaf.basic.BasicPanelUI;
+
 import org.junit.Assert ;
 import junit.framework.JUnit4TestAdapter ;
 import org.apache.jena.atlas.lib.StrUtils ;
 import org.junit.Test ;
 
+import com.hp.hpl.jena.graph.NodeFactory;
+import com.hp.hpl.jena.graph.Triple;
 import com.hp.hpl.jena.sparql.algebra.Op ;
 import com.hp.hpl.jena.sparql.algebra.Transform ;
 import com.hp.hpl.jena.sparql.algebra.Transformer ;
+import com.hp.hpl.jena.sparql.algebra.op.OpAssign;
 import com.hp.hpl.jena.sparql.algebra.op.OpBGP ;
 import com.hp.hpl.jena.sparql.algebra.op.OpFilter ;
+import com.hp.hpl.jena.sparql.algebra.op.OpTable;
 import com.hp.hpl.jena.sparql.algebra.optimize.TransformExpandOneOf ;
 import com.hp.hpl.jena.sparql.algebra.optimize.TransformFilterDisjunction ;
 import com.hp.hpl.jena.sparql.algebra.optimize.TransformFilterEquality ;
 import com.hp.hpl.jena.sparql.algebra.optimize.TransformFilterPlacement ;
+import com.hp.hpl.jena.sparql.core.BasicPattern;
+import com.hp.hpl.jena.sparql.core.Var;
+import com.hp.hpl.jena.sparql.core.VarExprList;
+import com.hp.hpl.jena.sparql.expr.Expr;
+import com.hp.hpl.jena.sparql.expr.ExprVar;
 import com.hp.hpl.jena.sparql.sse.SSE ;
 
 /** Tests of transforms related to filters */
@@ -820,21 +834,22 @@ public class TestTransformFilters
     
     @Test public void implicitLeftJoin13()
     {
-        // There are some overlapping conditions that cannot be safely optimized
+        // There are some overlapping conditions that are safe to optimize however they may end up introducing
+        // additional unnecessary assign operators
         test(
                 "(leftjoin (bgp (?x ?p ?o)) (bgp (?y ?p1 ?o1) (?y ?p2 ?z)) ((= ?x ?y) (= ?x ?z) (= ?y ?z)))",
                 t_implicitLeftJoin,
-                (String[])null);
-
+                "(leftjoin (bgp (?x ?p ?o)) (assign ((?y ?z)) (assign ((?y ?x) (?z ?x)) (bgp (?x ?p1 ?o1) (?x ?p2 ?x)))))");
+        
         test(
                 "(leftjoin (bgp (?x ?p ?o)) (bgp (?y ?p1 ?o1) (?y ?p2 ?z)) ((= ?z ?y) (= ?x ?z) (= ?x ?y)))",
                 t_implicitLeftJoin,
-                (String[])null);
+                "(leftjoin (bgp (?x ?p ?o)) (assign ((?z ?x) (?y ?x)) (assign ((?z ?x)) (bgp (?x ?p1 ?o1) (?x ?p2 ?x)))))");
         
         test(
                 "(leftjoin (bgp (?x ?p ?o)) (bgp (?y ?p1 ?o1) (?y ?p2 ?z)) ((= ?z ?y) (= ?z ?x) (= ?y ?x)))",
                 t_implicitLeftJoin,
-                (String[])null);
+                "(leftjoin (bgp (?x ?p ?o)) (assign ((?z ?x) (?y ?x)) (assign ((?z ?x)) (bgp (?x ?p1 ?o1) (?x ?p2 ?x)))))");
     }
     
     @Test public void implicitLeftJoin14()
@@ -878,6 +893,90 @@ public class TestTransformFilters
              (String[])null);
     }
     
+    @Test public void implicitLeftJoin18()
+    {
+        // The optimizer is capable of going one level into a nested && to find conditions to apply
+        test(
+             "(leftjoin (bgp (?x ?p ?o)) (bgp (?y ?p1 ?o1) (?y ?p2 ?z)) ((&& (&& (= ?x ?y) (= ?y ?z)) (= ?x ?z))))",
+             t_implicitLeftJoin,
+             "(leftjoin (bgp (?x ?p ?o)) (assign ((?z ?x) (?y ?x)) (bgp (?x ?p1 ?o1) (?x ?p2 ?x))) (= ?y ?z))");
+    }
+    
+    @Test public void implicitLeftJoin19()
+    {
+        // Covers the case of both variables on left and neither on right
+        test(
+             "(leftjoin (bgp (?x ?p ?o)(?x <http://pred> ?y)) (table unit) ((= ?x ?y)))",
+             t_implicitLeftJoin,
+             (String[])null);
+        
+        // Swapping the order of the equality expression should make no difference
+        test(
+             "(leftjoin (bgp (?x ?p ?o)(?x <http://pred> ?y)) (table unit) ((= ?y ?x)))",
+             t_implicitLeftJoin,
+             (String[])null);
+    }
+    
+    @Test public void implicitLeftJoin20()
+    {
+        // Covers the case of one variable on left and neither on right
+        test(
+             "(leftjoin (bgp (?x ?p ?o)) (table unit) ((= ?x ?y)))",
+             t_implicitLeftJoin,
+             (String[])null);
+        
+        // Swapping the order of the equality expression should make no difference
+        test(
+             "(leftjoin (bgp (?x ?p ?o)) (table unit) ((= ?y ?x)))",
+             t_implicitLeftJoin,
+             (String[])null);
+    }
+    
+    @Test public void implicitLeftJoin21()
+    {
+        // Covers the case of one variable on left and neither on right
+        test(
+             "(leftjoin (bgp (?y ?p ?o)) (table unit) ((= ?x ?y)))",
+             t_implicitLeftJoin,
+             (String[])null);
+        
+        // Swapping the order of the equality expression should make no difference
+        test(
+             "(leftjoin (bgp (?y ?p ?o)) (table unit) ((= ?y ?x)))",
+             t_implicitLeftJoin,
+             (String[])null);
+    }
+    
+    @Test public void implicitLeftJoin22()
+    {
+        // Covers the case of no variables actually being relevant
+        test(
+             "(leftjoin (bgp (?s ?p ?o)) (table unit) ((= ?x ?y)))",
+             t_implicitLeftJoin,
+             (String[])null);
+        
+        // Swapping the order of the equality expression should make no difference
+        test(
+             "(leftjoin (bgp (?s ?p ?o)) (table unit) ((= ?y ?x)))",
+             t_implicitLeftJoin,
+             (String[])null);
+    }
+    
+    @Test public void implicitLeftJoin23()
+    {
+        // Covers the case of neither variable on left and both on right
+        test(
+             "(leftjoin (table unit) (bgp (?x ?p ?o)(?x <http://pred> ?y)) ((= ?x ?y)))",
+             t_implicitLeftJoin,
+             "(leftjoin (table unit) (assign ((?x ?y)) (bgp (?y ?p ?o)(?y <http://pred> ?y))))");
+        
+        // Swapping the order of the equality expression should make no difference
+        test(
+             "(leftjoin (table unit) (bgp (?x ?p ?o)(?x <http://pred> ?y)) ((= ?y ?x)))",
+             t_implicitLeftJoin,
+             "(leftjoin (table unit) (assign ((?y ?x)) (bgp (?x ?p ?o)(?x <http://pred> ?x))))");
+    }
+    
     @Test public void implicitLeftJoinConditional1()
     {
         // Can be optimized because not all assigns block linearization