1111
1212package org .eclipse .rdf4j .query .algebra .evaluation .optimizer ;
1313
14+ import java .util .Objects ;
1415import java .util .Set ;
1516
1617import org .eclipse .rdf4j .query .BindingSet ;
3132import org .eclipse .rdf4j .query .algebra .StatementPattern ;
3233import org .eclipse .rdf4j .query .algebra .TupleExpr ;
3334import org .eclipse .rdf4j .query .algebra .Union ;
35+ import org .eclipse .rdf4j .query .algebra .VariableScopeChange ;
3436import org .eclipse .rdf4j .query .algebra .evaluation .QueryOptimizer ;
3537import org .eclipse .rdf4j .query .algebra .helpers .AbstractQueryModelVisitor ;
3638import org .eclipse .rdf4j .query .algebra .helpers .AbstractSimpleQueryModelVisitor ;
@@ -78,9 +80,49 @@ public class FilterOptimizer implements QueryOptimizer {
7880
7981 @ Override
8082 public void optimize (TupleExpr tupleExpr , Dataset dataset , BindingSet bindings ) {
81- tupleExpr .visit (new FilterUnMerger ());
82- tupleExpr .visit (new FilterOrganizer ());
83- tupleExpr .visit (new FilterMerger ());
83+ Objects .requireNonNull (tupleExpr , "tupleExpr must not be null" );
84+ optimizeScope (tupleExpr );
85+ }
86+
87+ /**
88+ * Bottom‑up optimisation: optimise each child subtree that starts a new variable scope, then run the three filter
89+ * passes on the current subtree.
90+ */
91+ private void optimizeScope (TupleExpr expr ) {
92+ // 1) recurse into nested scopes first
93+ expr .visit (new AbstractQueryModelVisitor <>() {
94+ final TupleExpr current = expr ;
95+
96+ @ Override
97+ protected void meetNode (QueryModelNode node ) {
98+ if (node != current && node instanceof TupleExpr && node instanceof VariableScopeChange
99+ && ((VariableScopeChange ) node ).isVariableScopeChange ()) {
100+
101+ optimizeScope (((TupleExpr ) node ));
102+ // do NOT traverse further into that subtree with this visitor
103+ } else {
104+ super .meetNode (node );
105+ }
106+ }
107+
108+ });
109+
110+ // 2) run the three filter passes for *this* scope
111+ expr .visit (new FilterUnMerger ());
112+ expr .visit (new FilterOrganizer ());
113+ expr .visit (new FilterMerger ());
114+ }
115+
116+ /**
117+ * Copies the {@code isVariableScopeChange} flag from one node to another if both implement
118+ * {@link VariableScopeChange}.
119+ */
120+ private static void transferScopeChange (QueryModelNode source , QueryModelNode target ) {
121+ if (source instanceof VariableScopeChange && target instanceof VariableScopeChange ) {
122+ VariableScopeChange src = (VariableScopeChange ) source ;
123+ VariableScopeChange tgt = (VariableScopeChange ) target ;
124+ tgt .setVariableScopeChange (src .isVariableScopeChange ());
125+ }
84126 }
85127
86128 private static class FilterUnMerger extends AbstractSimpleQueryModelVisitor <RuntimeException > {
@@ -95,6 +137,7 @@ public void meet(Filter filter) {
95137 And and = (And ) filter .getCondition ();
96138 filter .setCondition (and .getLeftArg ().clone ());
97139 Filter newFilter = new Filter (filter .getArg ().clone (), and .getRightArg ().clone ());
140+ transferScopeChange (filter , newFilter ); // preserve scope flag
98141 filter .replaceChildNode (filter .getArg (), newFilter );
99142 }
100143 super .meet (filter );
@@ -117,6 +160,7 @@ public void meet(Filter filter) {
117160 And merge = new And (childFilter .getCondition ().clone (), filter .getCondition ().clone ());
118161
119162 Filter newFilter = new Filter (childFilter .getArg ().clone (), merge );
163+ transferScopeChange (filter , newFilter ); // both have same scope flag
120164 parent .replaceChildNode (filter , newFilter );
121165 }
122166 }
@@ -190,6 +234,7 @@ public void meet(LeftJoin leftJoin) {
190234 public void meet (Union union ) {
191235 Filter clone = new Filter ();
192236 clone .setCondition (filter .getCondition ().clone ());
237+ transferScopeChange (filter , clone );
193238
194239 relocate (filter , union .getLeftArg ());
195240 relocate (clone , union .getRightArg ());
@@ -202,6 +247,7 @@ public void meet(Union union) {
202247 public void meet (Difference node ) {
203248 Filter clone = new Filter ();
204249 clone .setCondition (filter .getCondition ().clone ());
250+ transferScopeChange (filter , clone );
205251
206252 relocate (filter , node .getLeftArg ());
207253 relocate (clone , node .getRightArg ());
@@ -214,6 +260,7 @@ public void meet(Difference node) {
214260 public void meet (Intersection node ) {
215261 Filter clone = new Filter ();
216262 clone .setCondition (filter .getCondition ().clone ());
263+ transferScopeChange (filter , clone );
217264
218265 relocate (filter , node .getLeftArg ());
219266 relocate (clone , node .getRightArg ());
@@ -278,5 +325,4 @@ private void relocate(Filter filter, TupleExpr newFilterArg) {
278325 }
279326 }
280327 }
281-
282328}
0 commit comments