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 2016/06/06 11:34:51 UTC

jena git commit: JENA-1167: Cope with filter-before-defined variables

Repository: jena
Updated Branches:
  refs/heads/master cd1c21ff6 -> 6f12cdbd3


JENA-1167: Cope with filter-before-defined variables


Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/6f12cdbd
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/6f12cdbd
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/6f12cdbd

Branch: refs/heads/master
Commit: 6f12cdbd3964adfddfdaddd3092970a9ea5460aa
Parents: cd1c21f
Author: Andy Seaborne <an...@apache.org>
Authored: Mon Jun 6 12:14:42 2016 +0100
Committer: Andy Seaborne <an...@apache.org>
Committed: Mon Jun 6 12:14:42 2016 +0100

----------------------------------------------------------------------
 .../jena/sparql/engine/main/JoinClassifier.java | 59 +++++++------
 .../sparql/engine/main/LeftJoinClassifier.java  | 47 ++++++-----
 .../jena/sparql/engine/main/VarFinder.java      | 88 +++++++++++++++++---
 .../jena/sparql/algebra/TestClassify.java       | 23 +++--
 .../jena/sparql/algebra/TestTransformQuads.java |  3 +-
 .../jena/sparql/algebra/TestVarFinder.java      | 49 ++++++-----
 6 files changed, 182 insertions(+), 87 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/6f12cdbd/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
----------------------------------------------------------------------
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 22c3f39..00b06ea 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
@@ -87,40 +87,45 @@ public class JoinClassifier
             System.err.println(rightOp) ;
         }
 
-        // Need only check left/rght.
+        // Need only check left/right.
         VarFinder vfLeft = VarFinder.process(leftOp) ;
         Set<Var> vLeftFixed = vfLeft.getFixed() ;
         Set<Var> vLeftOpt = vfLeft.getOpt() ;
         // Set<Var> vLeftFilter = vfLeft.getFilter() ;
-        if ( print )
-            System.err.println("Left/fixed:    " + vLeftFixed) ;
-        if ( print )
-            System.err.println("Left/opt:      " + vLeftOpt) ;
-        // if (print) System.err.println("Left/filter:   " + vLeftFilter) ;
-
-        VarFinder vfRight       = VarFinder.process(rightOp) ;
-        Set<Var> vRightFixed    = vfRight.getFixed() ;
-        Set<Var> vRightOpt      = vfRight.getOpt() ;
-        Set<Var> vRightFilter   = vfRight.getFilter() ;
-        Set<Var> vRightAssign   = vfRight.getAssign() ;
-
-        if ( print )
-            System.err.println("Right/fixed:   " + vRightFixed) ;
-        if ( print )
-            System.err.println("Right/opt:     " + vRightOpt) ;
-        if ( print )
-            System.err.println("Right/filter:  " + vRightFilter) ;
-        if ( print )
-            System.err.println("Right/assign:  " + vRightAssign) ;
-
-        // Step 1 : remove any variable definitely fixed from the floating sets
+        if ( print ) {
+            System.err.println("Left") ;
+            vfLeft.print(System.err) ;
+        }
+        VarFinder vfRight           = VarFinder.process(rightOp) ;
+        if ( print ) {
+            System.err.println("Right") ;
+            vfRight.print(System.err) ;
+        }
+        
+        Set<Var> vRightFixed        = vfRight.getFixed() ;
+        Set<Var> vRightOpt          = vfRight.getOpt() ;
+        Set<Var> vRightFilter       = vfRight.getFilter() ;
+        Set<Var> vRightFilterOnly   = vfRight.getFilterOnly() ;
+        Set<Var> vRightAssign       = vfRight.getAssign() ;
+
+        // Step 1 : If there are any variables in the LHS that are filter-only or filter-before define,
+        // we can't do anything.
+        if ( ! vRightFilterOnly.isEmpty() ) {
+            // A tigher condition is to see of any of the getFilterOnly are possible from the
+            // left.  If not, then we can still use a sequence. 
+            // But an outer sequence may push arbitrary here so play safe on the argument
+            // this is a relative uncommon case.
+            return false ;
+        }
+        
+        // Step 2 : remove any variable definitely fixed from the floating sets
         // because the nature of the "join" will deal with that.
         vLeftOpt = SetUtils.difference(vLeftOpt, vLeftFixed) ;
         vRightOpt = SetUtils.difference(vRightOpt, vRightFixed) ;
 
         // And also assign/filter variables in the RHS which are always defined
-        // in the
-        // RHS. Leaves any potentially free variables in RHS filter.
+        // in the RHS.
+        // Leaves any potentially free variables in RHS filter.
         vRightFilter = SetUtils.difference(vRightFilter, vRightFixed) ;
         vRightAssign = SetUtils.difference(vRightAssign, vRightFixed) ;
 
@@ -156,7 +161,7 @@ public class JoinClassifier
         if ( print )
             System.err.println("Case 1 = " + bad1) ;
 
-        // Case 2 : a filter in the RHS is uses a variable from the LHS (whether
+        // Case 3 : a filter in the RHS is uses a variable from the LHS (whether
         // fixed or optional)
         // Scoping means we must hide the LHS value form the RHS
         // Could mask (??). For now, we stop linearization of this join.
@@ -167,7 +172,7 @@ public class JoinClassifier
         if ( print )
             System.err.println("Case 2 = " + bad2) ;
 
-        // Case 3 : an assign in the RHS uses a variable not introduced
+        // Case 4 : an assign in the RHS uses a variable not introduced
         // Scoping means we must hide the LHS value from the RHS
 
         // Think this may be slightly relaxed, using variables in an

http://git-wip-us.apache.org/repos/asf/jena/blob/6f12cdbd/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/LeftJoinClassifier.java
----------------------------------------------------------------------
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 9608334..ad68c09 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
@@ -47,12 +47,11 @@ public class LeftJoinClassifier
     // Need also worry about filters in the right (not in the LJ condition)
     // which use vars from the left. 
 
-    static public boolean isLinear(OpLeftJoin op)
-    {
+    static public boolean isLinear(OpLeftJoin op) {
         return isLinear(op.getLeft(), op.getRight()) ;
     }
-    static public boolean isLinear(Op left, Op right)
-    {
+
+    static public boolean isLinear(Op left, Op right) {
         left = effectiveOp(left) ;
         right = effectiveOp(right) ;
         
@@ -62,34 +61,43 @@ public class LeftJoinClassifier
             return false ;
         
         Set<Var> leftVars = OpVars.visibleVars(left) ;
+        if ( print ) {
+            System.err.println("Left") ;
+            System.err.println(leftVars) ;
+        }
         VarFinder vf = VarFinder.process(right) ;
+        if ( print ) {
+            System.err.println("Right") ;
+            vf.print(System.err) ;
+        }
+        
+        // Case 1 : If there are any variables in the LHS that are
+        // filter-only or filter-before define, we can't do anything.
+        if ( ! vf.getFilterOnly().isEmpty() ) {
+            // A tigher condition is to see of any of the getFilterOnly are possible from the
+            // left.  If not, then we can still use a sequence. 
+            // But an outer sequence may push arbitrary here so play safe on the argument
+            // this is a relative uncommon case.
+            return false ;
+        }
         
         Set<Var> optRight = vf.getOpt() ;
         Set<Var> fixedRight = vf.getFixed() ;
         Set<Var> filterVarsRight = vf.getFilter() ; 
         Set<Var> assignVarsRight = vf.getAssign() ;
-        
-        if (print) {
-            System.err.println("Left/visible: " + leftVars) ;
-            System.err.println("Right/fixed:  " + fixedRight) ;
-            System.err.println("Right/opt:    " + optRight) ;
-            System.err.println("Right/filter: " + filterVarsRight) ;
-            System.err.println("Right/assign: " + assignVarsRight) ;
-        }
-        
-        // Case 1
+        // Case 2
         // A variable is nested in an optional on the RHS and on the LHS
         // Cannot linearize as we must preserve scope
         boolean b1 = SetUtils.intersectionP(leftVars, optRight) ;
         if (print) System.err.println("Case 1 - " + b1);
         
-        // Case 2
+        // Case 3
         // A variable mentioned in a filter within the RHS already exists on the LHS
         // Cannot linearize as would change filter evaluation
         boolean b2 = SetUtils.intersectionP(leftVars, filterVarsRight) ;
         if (print) System.err.println("Case 2 - " + b2);
         
-        // Case 3
+        // Case 4
         // A variable mentioned in the assign is not introduced on the RHS
         // Cannot linearize as would change bind evaluation
         Set<Var> unsafeAssign = new HashSet<>(assignVarsRight);
@@ -101,8 +109,7 @@ public class LeftJoinClassifier
         return ! b1 && ! b2 && ! b3 ;
     }
     
-    static public Set<Var> nonLinearVars(OpLeftJoin op)
-    {
+    static public Set<Var> nonLinearVars(OpLeftJoin op) { 
         Op left = effectiveOp(op.getLeft()) ;
         Op right = effectiveOp(op.getRight()) ;
         Set<Var> leftVars = OpVars.visibleVars(left) ;
@@ -111,11 +118,9 @@ public class LeftJoinClassifier
         return SetUtils.intersection(leftVars, optRight) ;
     }
     
-    private static Op effectiveOp(Op op)
-    {
+    private static Op effectiveOp(Op op) {
         if (op instanceof OpExt)
             op = ((OpExt) op).effectiveOp() ;
         return op ;
     }
-
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/6f12cdbd/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
index 258f7ea..be6c8e0 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
@@ -24,6 +24,7 @@ import static org.apache.jena.sparql.util.VarUtils.addVarsFromQuad ;
 import static org.apache.jena.sparql.util.VarUtils.addVarsFromTriple ;
 import static org.apache.jena.sparql.util.VarUtils.addVarsFromTriplePath ;
 
+import java.io.PrintStream ;
 import java.util.HashSet ;
 import java.util.List ;
 import java.util.Set ;
@@ -69,11 +70,31 @@ public class VarFinder
     private VarFinder(Op op)
     { varUsageVisitor = VarUsageVisitor.apply(op) ; }
     
-    public Set<Var> getOpt() { return varUsageVisitor.optDefines ; }
-    public Set<Var> getFilter() { return varUsageVisitor.filterMentions ; }
-    public Set<Var> getAssign() { return varUsageVisitor.assignMentions ; }
-    public Set<Var> getFixed() { return varUsageVisitor.defines ; }
+    public Set<Var> getOpt()        { return varUsageVisitor.optDefines ; }
+    public Set<Var> getFilter()     { return varUsageVisitor.filterMentions ; }
+    public Set<Var> getFilterOnly() { return varUsageVisitor.filterMentionsOnly ; }
+    public Set<Var> getAssign()     { return varUsageVisitor.assignMentions ; }
+    public Set<Var> getFixed()      { return varUsageVisitor.defines ; }
     
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder() ;
+        sb.append("Fixed:").append(getFixed()) ;
+        sb.append(", Filter:").append(getFilter()) ;
+        sb.append(", Filter2:").append(getFilterOnly()) ;
+        sb.append(", Opt:").append(getOpt()) ;
+        sb.append(", Assign:").append(getAssign()) ;
+        return sb.toString() ;
+    }
+    
+    public void print(PrintStream out) {
+        out.printf("  Filter:  %s\n", getFilter()) ;
+        out.printf("  Filter2: %s\n", getFilterOnly()) ;
+        out.printf("  Fixed :  %s\n", getFixed()) ;
+        out.printf("  Opt:     %s\n", getOpt()) ;
+        out.printf("  Assign:  %s\n", getAssign()) ;
+    }
+
     private static class VarUsageVisitor 
         //extends OpVisitorBase
         implements OpVisitor
@@ -84,22 +105,25 @@ public class VarFinder
             return v;
         }
 
-        Set<Var> defines        = null;
-        Set<Var> optDefines     = null;
-        Set<Var> filterMentions = null;
-        Set<Var> assignMentions = null;
+        Set<Var> defines            = null ;
+        Set<Var> optDefines         = null ;
+        Set<Var> filterMentions     = null ;
+        Set<Var> filterMentionsOnly = null ;    // Mentioned in filter before defined. 
+        Set<Var> assignMentions     = null ;
 
         VarUsageVisitor() {
             defines = new HashSet<>();
             optDefines = new HashSet<>();
             filterMentions = new HashSet<>();
+            filterMentionsOnly = new HashSet<>();
             assignMentions = new HashSet<>();
         }
 
-        VarUsageVisitor(Set<Var> _defines, Set<Var> _optDefines, Set<Var> _filterMentions, Set<Var> _assignMentions) {
+        VarUsageVisitor(Set<Var> _defines, Set<Var> _optDefines, Set<Var> _filterMentions, Set<Var> _filterMentions2, Set<Var> _assignMentions) {
             defines = _defines;
             optDefines = _optDefines;
             filterMentions = _filterMentions;
+            filterMentionsOnly = _filterMentions2 ;
             assignMentions = _assignMentions;
         }
 
@@ -156,6 +180,7 @@ public class VarFinder
             defines.addAll(usage.defines);
             optDefines.addAll(usage.optDefines);
             filterMentions.addAll(usage.filterMentions);
+            filterMentionsOnly.addAll(usage.filterMentionsOnly);
             assignMentions.addAll(usage.assignMentions);
         }
 
@@ -177,13 +202,22 @@ public class VarFinder
         private void mergeMinusDiff(Op left, Op right) {
             mergeVars(left) ;
             VarUsageVisitor usage = VarUsageVisitor.apply(right);
-            // Everything in the right side is really a filter.  
+            // Everything in the right side is really a filter.
+            combinefilterMentions(this, usage.filterMentionsOnly) ;
+            
             filterMentions.addAll(usage.defines) ;
             filterMentions.addAll(usage.optDefines) ;
             filterMentions.addAll(usage.filterMentions) ;
             filterMentions.addAll(usage.assignMentions) ;
         }
 
+        private static void combinefilterMentions(VarUsageVisitor usage, Set<Var> mentions) {
+            for ( Var v : mentions ) {
+                if ( ! usage.defines.contains(v) )
+                    usage.filterMentionsOnly.add(v) ;
+            }
+        }
+        
         @Override
         public void visit(OpConditional opLeftJoin) {
             leftJoin(opLeftJoin.getLeft(), opLeftJoin.getRight(), null);
@@ -196,11 +230,13 @@ public class VarFinder
             defines.addAll(leftUsage.defines);
             optDefines.addAll(leftUsage.optDefines);
             filterMentions.addAll(leftUsage.filterMentions);
+            filterMentionsOnly.addAll(leftUsage.filterMentionsOnly);
             assignMentions.addAll(leftUsage.assignMentions);
 
             optDefines.addAll(rightUsage.defines); // Asymmetric.
             optDefines.addAll(rightUsage.optDefines);
             filterMentions.addAll(rightUsage.filterMentions);
+            filterMentionsOnly.addAll(rightUsage.filterMentionsOnly);
             assignMentions.addAll(rightUsage.assignMentions);
 
             // Remove any definites that are in the optionals
@@ -208,8 +244,20 @@ public class VarFinder
             optDefines.removeAll(leftUsage.defines);
 
             // And the associated filter.
-            if ( exprs != null )
+            if ( exprs != null ) {
+                processExpr(exprs, rightUsage.defines) ;
                 exprs.varsMentioned(filterMentions);
+            }
+        }
+
+        // additionalDefines - set of variables which are defined is the filter is executed. 
+        private void processExpr(ExprList exprs, Set<Var> additionalDefines) {
+            Set<Var> vars = exprs.getVarsMentioned() ;
+            filterMentions.addAll(vars) ;
+            for ( Var v : vars ) {
+                if ( ! defines.contains(v) && (additionalDefines == null || ! additionalDefines.contains(v) ) )
+                    filterMentionsOnly.add(v) ;
+            }
         }
 
         @Override
@@ -231,6 +279,9 @@ public class VarFinder
             filterMentions.addAll(usage1.filterMentions);
             filterMentions.addAll(usage2.filterMentions);
             
+            filterMentionsOnly.addAll(usage1.filterMentionsOnly);
+            filterMentionsOnly.addAll(usage2.filterMentionsOnly);
+            
             assignMentions.addAll(usage1.assignMentions);
             assignMentions.addAll(usage2.assignMentions);
         }
@@ -248,8 +299,8 @@ public class VarFinder
 
         @Override
         public void visit(OpFilter opFilter) {
-            opFilter.getExprs().varsMentioned(filterMentions);
             opFilter.getSubOp().visit(this);
+            processExpr(opFilter.getExprs(), null) ;
         }
 
         @Override
@@ -279,10 +330,12 @@ public class VarFinder
             subUsage.defines.retainAll(vars);
             subUsage.optDefines.retainAll(vars);
             subUsage.filterMentions.retainAll(vars) ;
+            subUsage.filterMentionsOnly.retainAll(vars) ;
             subUsage.assignMentions.retainAll(vars) ;
             defines.addAll(subUsage.defines);
             optDefines.addAll(subUsage.optDefines);
             filterMentions.addAll(subUsage.filterMentions);
+            filterMentionsOnly.addAll(subUsage.filterMentionsOnly);
             assignMentions.addAll(subUsage.assignMentions);
         }
 
@@ -353,5 +406,16 @@ public class VarFinder
                 defines.addAll(vars) ;
             }
         }
+        
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder() ;
+            sb.append("Fixed:").append(defines) ;
+            sb.append(", Filter:").append(filterMentions) ;
+            sb.append(", Filter2:").append(filterMentionsOnly) ;
+            sb.append(", Opt:").append(optDefines) ;
+            sb.append(", Assign:").append(assignMentions) ;
+            return sb.toString() ;
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/6f12cdbd/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
index 17e7692..7cea0a2 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
@@ -39,8 +39,12 @@ public class TestClassify extends BaseTest
     @Test public void testClassify_Join_03() 
 	{ classifyJ("{?s :p :o . { ?s :p ?o FILTER(?o) } }", true) ; }
 
+    // JENA-1167
+    // This actually safe in thsi case but as general component.
+    // the JoinClassifier is not clever enough to know that ?o is completely
+    // unbound. It may be boudn bu whatever feeds into the potential sequence.
     @Test public void testClassify_Join_04() 
-	{ classifyJ("{?s :p :o . { ?s :p :o FILTER(?o) } }", true) ; }
+	{ classifyJ("{?s :p :o . { ?s :p :o FILTER(?o) } }", false) ; }
 
     @Test public void testClassify_Join_05() 
 	{ classifyJ("{?s :p :o . { ?x :p :o FILTER(?s) } }", false) ; }
@@ -61,6 +65,9 @@ public class TestClassify extends BaseTest
 	@Test public void testClassify_Join_10() 
 	{ classifyJ("{ { ?x :p :o FILTER(?s) }   ?s :p :o }", true) ; }
 
+	
+    // OPTIONAL nested inside {} so it is a join of the LHS and the {}-RHS. 
+
     // Not safe: ?s
     // Other parts of RHS may restrict ?s to things that can't match the LHS.
 	@Test public void testClassify_Join_11() 
@@ -74,9 +81,15 @@ public class TestClassify extends BaseTest
 	{ classifyJ("{?s :p :o . { ?x :p :o OPTIONAL { :s :p :o FILTER(?x) } } }", true) ; }
 
 	@Test public void testClassify_Join_14() 
-	{ classifyJ("{?s :p :o . { OPTIONAL { :s :p :o FILTER(?o) } } }", true) ; }
+	{ classifyJ("{?s :p :o . { OPTIONAL { :s :p :o FILTER(?o) } } }", false) ; }
+
+    @Test public void testClassify_Join_14a() 
+    { classifyJ("{?s :p :o . { OPTIONAL { :s :p ?o FILTER(?o) } } }", true) ; }
 
-	@Test public void testClassify_Join_15() 
+    @Test public void testClassify_Join_14b() 
+    { classifyJ("{?s :p ?o . { OPTIONAL { :s :p :o FILTER(?o) } } }", false) ; }
+    
+    @Test public void testClassify_Join_15() 
 	{ classifyJ("{?s :p :o . { OPTIONAL { ?x :p :o FILTER(?s) } } }", false) ; }
 
     @Test public void testClassify_Join_20() 
@@ -89,9 +102,9 @@ public class TestClassify extends BaseTest
     @Test public void testClassify_Join_31() 
     { classifyJ("{ ?x ?y ?z {SELECT ?s { ?s ?p ?o} } }", true) ; }
 
-    // Use of a filter variable not in from the LHS
+    // JENA-1167 : Use of a filter variable not in from the LHS
     @Test public void testClassify_Join_32() 
-    { classifyJ("{ GRAPH ?g { ?x ?y ?z } { FILTER (?a) } }", true) ; }
+    { classifyJ("{ GRAPH ?g { ?x ?y ?z } { FILTER (?a) } }", false) ; }
 
     // Use of a filter variable from the LHS
     @Test public void testClassify_Join_33() 

http://git-wip-us.apache.org/repos/asf/jena/blob/6f12cdbd/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTransformQuads.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTransformQuads.java b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTransformQuads.java
index 20d7f3a..24d99e3 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTransformQuads.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTransformQuads.java
@@ -72,9 +72,10 @@ public class TestTransformQuads extends BaseTest
                                         ) ; }
     
     // Nested and filter
+    // ?g is unbound in the filter. 
     @Test public void quads20() { test ("{ GRAPH ?g { ?s ?p ?o GRAPH ?g1 { ?s1 ?p1 ?o1 FILTER (str(?g) = 'graphURI') } } }",
                                         "(assign ((?g ?*g0))" +
-                                        "   (sequence" +
+                                        "   (join" +
                                         "     (quadpattern (quad ?*g0 ?s ?p ?o))" +
                                         "     (filter (= (str ?g) 'graphURI')" +
                                         "       (quadpattern (quad ?g1 ?s1 ?p1 ?o1)))))"

http://git-wip-us.apache.org/repos/asf/jena/blob/6f12cdbd/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder.java b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder.java
index aeb8332..b3eb539 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder.java
@@ -35,57 +35,64 @@ public class TestVarFinder extends BaseTest
     @Test public void varfind_01_1() { varfindFixed("(bgp (?s <p> <o>))", "s") ; }
     @Test public void varfind_01_2() { varfindOpt("(bgp (?s <p> <o>))") ; }
     @Test public void varfind_01_3() { varfindFilter("(bgp (?s <p> <o>))") ; }
+    @Test public void varfind_01_4() { varfindFilter("(bgp (?s <p> <o>))") ; }
     
     @Test public void varfind_02_1() { varfindFixed("(graph ?g (bgp (?s <p> <o>)))", "s", "g") ; }
     @Test public void varfind_02_2() { varfindOpt("(graph ?g (bgp (?s <p> <o>)))") ; }
     @Test public void varfind_02_3() { varfindFilter("(graph ?g (bgp (?s <p> <o>)))") ; }
+    @Test public void varfind_02_4() { varfindFilterOnly("(graph ?g (bgp (?s <p> <o>)))") ; }
 
     @Test public void varfind_03_1() { varfindFixed("(filter (?s) (bgp (?s <p> <o>)))", "s") ; }
     @Test public void varfind_03_2() { varfindOpt("(filter (?s) (bgp (?s <p> <o>)))") ; }
     @Test public void varfind_03_3() { varfindFilter("(filter (?s) (bgp (?s <p> <o>)))", "s") ; }
+    @Test public void varfind_03_4() { varfindFilterOnly("(filter (?s) (bgp (?s <p> <o>)))") ; }
+    @Test public void varfind_03_5() { varfindFilterOnly("(filter (?z) (bgp (?s <p> <o>)))", "z") ; }
 
     @Test public void varfind_04_1() { varfindFixed("(leftjoin (bgp (?x <q> <v>)) (filter (?s) (bgp (?s <p> <o>))))", "x") ; }
     @Test public void varfind_04_2() { varfindOpt("(leftjoin (bgp (?x <q> <v>)) (filter (?s) (bgp (?s <p> <o>))))", "s") ; }
     @Test public void varfind_04_3() { varfindFilter("(leftjoin (bgp (?x <q> <v>)) (filter (?s) (bgp (?s <p> <o>))))", "s") ; }
+    @Test public void varfind_04_4() { varfindFilterOnly("(leftjoin (bgp (?x <q> <v>)) (filter (?Z) (bgp (?s <p> <o>))))", "Z") ; }
+    @Test public void varfind_04_5() { varfindFilterOnly("(leftjoin (bgp (?x <q> <v>)) (bgp (?s <p> <o>)) ?Z)", "Z") ; }
 
-    
-    private static void varfindFixed(String string, String...vars)
-    {
-        varfind(string, vars, null, null) ;
+    private static void varfindFixed(String string, String... vars) {
+        varfind(string, vars, null, null, null) ;
+    }
+
+    private static void varfindOpt(String string, String... vars) {
+        varfind(string, null, vars, null, null) ;
     }
 
-    private static void varfindOpt(String string, String...vars)
-    {
-        varfind(string, null, vars, null) ;
+    private static void varfindFilter(String string, String... vars) {
+        varfind(string, null, null, vars, null) ;
     }
 
-    private static void varfindFilter(String string, String...vars)
-    {
-        varfind(string, null, null, vars) ;
+    private static void varfindFilterOnly(String string, String... vars) {
+        varfind(string, null, null, null, vars) ;
     }
 
-    private static void varfind(String string, String[] varsFixed, String [] varsOpt, String [] varsFilter)
-    {
+    private static void varfind(String string, String[] varsFixed, String[] varsOpt, String[] varsFilter, String[] varsFilterOnly) {
         Op op = SSE.parseOp(string) ;
         VarFinder vf = VarFinder.process(op) ;
-        if ( varsFixed != null ) check(varsFixed, vf.getFixed()) ;
-        if ( varsOpt != null ) check(varsOpt, vf.getOpt()) ;
-        if ( varsFilter != null ) check(varsFilter, vf.getFilter()) ;
+        if ( varsFixed != null )
+            check(varsFixed, vf.getFixed()) ;
+        if ( varsOpt != null )
+            check(varsOpt, vf.getOpt()) ;
+        if ( varsFilter != null )
+            check(varsFilter, vf.getFilter()) ;
+        if ( varsFilterOnly != null )
+            check(varsFilterOnly, vf.getFilterOnly()) ;
     }
 
-    private static void check(String[] varsExpected, Set<Var> varsFound)
-    {
+    private static void check(String[] varsExpected, Set<Var> varsFound) {
         Var[] vars = new Var[varsExpected.length] ;
-        for ( int i = 0 ; i < varsExpected.length ; i++ )
-        {
+        for ( int i = 0 ; i < varsExpected.length ; i++ ) {
             Var v = Var.alloc(varsExpected[i]) ;
             vars[i] = v ;
         }
-        
+
         List<Var> varList = Arrays.asList(vars) ;
         HashSet<Var> varSet = new HashSet<>() ;
         varSet.addAll(varList) ;
         assertEquals(varSet, varsFound) ;
     }
-    
 }