Skip to content

Commit 6962ee7

Browse files
jasperiqJervenBolleman
authored andcommitted
GH-5286 Moving filter logic to separate concepts
1 parent 72040f8 commit 6962ee7

4 files changed

Lines changed: 203 additions & 139 deletions

File tree

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/LeftJoinIterator.java

Lines changed: 77 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -10,118 +10,94 @@
1010
*******************************************************************************/
1111
package org.eclipse.rdf4j.query.algebra.evaluation.iterator;
1212

13-
import java.util.NoSuchElementException;
14-
import java.util.Set;
15-
1613
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
1714
import org.eclipse.rdf4j.common.iteration.LookAheadIteration;
18-
import org.eclipse.rdf4j.model.Value;
19-
import org.eclipse.rdf4j.query.Binding;
2015
import org.eclipse.rdf4j.query.BindingSet;
2116
import org.eclipse.rdf4j.query.QueryEvaluationException;
2217
import org.eclipse.rdf4j.query.algebra.LeftJoin;
23-
import org.eclipse.rdf4j.query.algebra.ValueExpr;
24-
import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy;
25-
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
26-
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
27-
import org.eclipse.rdf4j.query.algebra.evaluation.QueryValueEvaluationStep;
28-
import org.eclipse.rdf4j.query.algebra.evaluation.ValueExprEvaluationException;
18+
import org.eclipse.rdf4j.query.algebra.evaluation.*;
2919
import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext;
30-
import org.eclipse.rdf4j.query.algebra.evaluation.util.QueryEvaluationUtility;
3120
import org.eclipse.rdf4j.query.algebra.helpers.collectors.VarNameCollector;
3221

22+
import java.util.NoSuchElementException;
23+
import java.util.Optional;
24+
import java.util.Set;
25+
3326
public class LeftJoinIterator extends LookAheadIteration<BindingSet> {
3427
/*-----------*
3528
* Variables *
3629
*-----------*/
3730

38-
/**
39-
* The set of binding names that are "in scope" for the filter. The filter must not include bindings that are (only)
40-
* included because of the depth-first evaluation strategy in the evaluation of the constraint.
41-
*/
42-
private final Set<String> scopeBindingNames;
43-
4431
private final CloseableIteration<BindingSet> leftIter;
45-
private final LeftJoin leftJoin;
46-
private final boolean canEvaluateConditionBasedOnLeftHandSide;
32+
private final QueryEvaluationStep rightEvaluationStep;
4733

4834
private CloseableIteration<BindingSet> rightIter;
4935

50-
private final QueryEvaluationStep prepareRightArg;
51-
52-
private final QueryValueEvaluationStep joinCondition;
53-
5436
/*--------------*
5537
* Constructors *
5638
*--------------*/
5739

58-
public LeftJoinIterator(EvaluationStrategy strategy, LeftJoin join, BindingSet bindings,
59-
QueryEvaluationContext context)
60-
throws QueryEvaluationException {
61-
this.scopeBindingNames = join.getBindingNames();
62-
this.leftJoin = join;
40+
public LeftJoinIterator(
41+
EvaluationStrategy strategy,
42+
LeftJoin join,
43+
BindingSet bindings,
44+
QueryEvaluationContext context) throws QueryEvaluationException {
45+
Set<String> scopeBindingNames = join.getBindingNames();
6346

6447
leftIter = strategy.evaluate(join.getLeftArg(), bindings);
6548

6649
rightIter = null;
6750

68-
prepareRightArg = strategy.precompile(join.getRightArg(), context);
51+
QueryEvaluationStep prepareRightArg = strategy.precompile(join.getRightArg(), context);
6952
join.setAlgorithm(this);
70-
final ValueExpr condition = join.getCondition();
71-
if (condition == null) {
72-
joinCondition = null;
73-
} else {
74-
joinCondition = strategy.precompile(condition, context);
75-
}
76-
this.canEvaluateConditionBasedOnLeftHandSide = canEvaluateConditionBasedOnLeftHandSide(leftJoin);
53+
var joinCondition = Optional.ofNullable(join.getCondition())
54+
.map(condition -> strategy.precompile(condition, context));
55+
56+
rightEvaluationStep = determineRightEvaluationStep(
57+
join,
58+
prepareRightArg,
59+
joinCondition.orElse(null),
60+
scopeBindingNames);
7761
}
7862

79-
public LeftJoinIterator(QueryEvaluationStep left, QueryEvaluationStep right, QueryValueEvaluationStep joinCondition,
80-
BindingSet bindings, Set<String> scopeBindingNames, LeftJoin leftJoin)
81-
throws QueryEvaluationException {
82-
this.scopeBindingNames = scopeBindingNames;
83-
this.leftJoin = leftJoin;
84-
85-
leftIter = left.evaluate(bindings);
86-
87-
// Initialize with empty iteration so that var is never null
88-
rightIter = null;
89-
90-
prepareRightArg = right;
91-
this.joinCondition = joinCondition;
92-
this.canEvaluateConditionBasedOnLeftHandSide = canEvaluateConditionBasedOnLeftHandSide(leftJoin);
93-
63+
public LeftJoinIterator(
64+
QueryEvaluationStep left,
65+
QueryEvaluationStep right,
66+
QueryValueEvaluationStep joinCondition,
67+
BindingSet bindings,
68+
Set<String> scopeBindingNames,
69+
LeftJoin leftJoin) throws QueryEvaluationException {
70+
this(
71+
left.evaluate(bindings),
72+
determineRightEvaluationStep(leftJoin, right, joinCondition, scopeBindingNames));
9473
}
9574

96-
public LeftJoinIterator(CloseableIteration<BindingSet> leftIter,
97-
QueryEvaluationStep prepareRightArg,
98-
QueryValueEvaluationStep joinCondition,
99-
Set<String> scopeBindingNames,
100-
LeftJoin leftJoin) {
101-
this.scopeBindingNames = scopeBindingNames;
75+
public LeftJoinIterator(CloseableIteration<BindingSet> leftIter, QueryEvaluationStep rightEvaluationStep) {
10276
this.leftIter = leftIter;
103-
this.leftJoin = leftJoin;
10477
this.rightIter = null;
105-
this.prepareRightArg = prepareRightArg;
106-
this.joinCondition = joinCondition;
107-
this.canEvaluateConditionBasedOnLeftHandSide = canEvaluateConditionBasedOnLeftHandSide(leftJoin);
78+
this.rightEvaluationStep = rightEvaluationStep;
10879
}
10980

110-
public static CloseableIteration<BindingSet> getInstance(QueryEvaluationStep left,
111-
QueryEvaluationStep prepareRightArg,
112-
QueryValueEvaluationStep joinCondition,
113-
BindingSet bindings,
114-
Set<String> scopeBindingNames,
115-
LeftJoin leftJoin) {
81+
public static CloseableIteration<BindingSet> getInstance(
82+
QueryEvaluationStep left,
83+
QueryEvaluationStep prepareRightArg,
84+
QueryValueEvaluationStep joinCondition,
85+
BindingSet bindings,
86+
Set<String> scopeBindingNames,
87+
LeftJoin leftJoin) {
11688

11789
CloseableIteration<BindingSet> leftIter = left.evaluate(bindings);
90+
var rightEvaluationStep = determineRightEvaluationStep(
91+
leftJoin,
92+
prepareRightArg,
93+
joinCondition,
94+
scopeBindingNames);
11895

11996
if (leftIter == QueryEvaluationStep.EMPTY_ITERATION) {
12097
return leftIter;
12198
} else {
122-
return new LeftJoinIterator(leftIter, prepareRightArg, joinCondition, scopeBindingNames, leftJoin);
99+
return new LeftJoinIterator(leftIter, rightEvaluationStep);
123100
}
124-
125101
}
126102

127103
/*---------*
@@ -140,57 +116,25 @@ protected BindingSet getNextElement() throws QueryEvaluationException {
140116
if (leftIter.hasNext()) {
141117
// Use left arg's bindings in case join fails
142118
leftBindings = leftIter.next();
143-
if (shouldEvaluateRightHandSide(leftBindings)) {
144-
nextRightIter = rightIter = prepareRightArg.evaluate(leftBindings);
145-
} else {
146-
return leftBindings;
147-
}
119+
nextRightIter = rightIter = rightEvaluationStep.evaluate(leftBindings);
148120
} else {
149121
return null;
150122
}
151-
152123
} else if (!nextRightIter.hasNext()) {
153124
// Use left arg's bindings in case join fails
154125
leftBindings = leftIter.next();
155126

156127
nextRightIter.close();
157-
if (shouldEvaluateRightHandSide(leftBindings)) {
158-
nextRightIter = rightIter = prepareRightArg.evaluate(leftBindings);
159-
} else {
160-
return leftBindings;
161-
}
128+
nextRightIter = rightIter = rightEvaluationStep.evaluate(leftBindings);
162129
}
163130

164131
if (nextRightIter == QueryEvaluationStep.EMPTY_ITERATION) {
165132
rightIter = null;
166133
return leftBindings;
167134
}
168135

169-
while (nextRightIter.hasNext()) {
170-
BindingSet rightBindings = nextRightIter.next();
171-
172-
try {
173-
if (joinCondition == null || canEvaluateConditionBasedOnLeftHandSide) {
174-
return rightBindings;
175-
} else {
176-
// Limit the bindings to the ones that are in scope for
177-
// this filter
178-
179-
QueryBindingSet scopeBindings = new QueryBindingSet(scopeBindingNames.size());
180-
for (String scopeBindingName : scopeBindingNames) {
181-
Binding binding = rightBindings.getBinding(scopeBindingName);
182-
if (binding != null) {
183-
scopeBindings.addBinding(binding);
184-
}
185-
}
186-
187-
if (isTrue(joinCondition, scopeBindings)) {
188-
return rightBindings;
189-
}
190-
}
191-
} catch (ValueExprEvaluationException e) {
192-
// Ignore, condition not evaluated successfully
193-
}
136+
if (nextRightIter.hasNext()) {
137+
return nextRightIter.next();
194138
}
195139

196140
if (leftBindings != null) {
@@ -207,39 +151,6 @@ protected BindingSet getNextElement() throws QueryEvaluationException {
207151
return null;
208152
}
209153

210-
private static boolean canEvaluateConditionBasedOnLeftHandSide(LeftJoin leftJoin) {
211-
if (leftJoin.hasCondition()) {
212-
var collector = new VarNameCollector();
213-
leftJoin.getCondition().visit(collector);
214-
215-
Set<String> assuredBindingNames = leftJoin.getAssuredBindingNames();
216-
return assuredBindingNames.containsAll(collector.getVarNames());
217-
}
218-
219-
return false;
220-
}
221-
222-
private boolean shouldEvaluateRightHandSide(BindingSet leftBindings) {
223-
if (!canEvaluateConditionBasedOnLeftHandSide) {
224-
return true;
225-
}
226-
227-
QueryBindingSet scopeBindings = new QueryBindingSet(leftJoin.getAssuredBindingNames().size());
228-
for (String scopeBindingName : leftJoin.getAssuredBindingNames()) {
229-
Binding binding = leftBindings.getBinding(scopeBindingName);
230-
if (binding != null) {
231-
scopeBindings.addBinding(binding);
232-
}
233-
}
234-
235-
return isTrue(joinCondition, scopeBindings);
236-
}
237-
238-
private boolean isTrue(QueryValueEvaluationStep expr, QueryBindingSet bindings) {
239-
Value value = expr.evaluate(bindings);
240-
return QueryEvaluationUtility.getEffectiveBooleanValue(value).orElse(false);
241-
}
242-
243154
@Override
244155
protected void handleClose() throws QueryEvaluationException {
245156
try {
@@ -250,4 +161,31 @@ protected void handleClose() throws QueryEvaluationException {
250161
}
251162
}
252163
}
164+
165+
static QueryEvaluationStep determineRightEvaluationStep(
166+
LeftJoin join,
167+
QueryEvaluationStep prepareRightArg,
168+
QueryValueEvaluationStep joinCondition,
169+
Set<String> scopeBindingNames) {
170+
if (joinCondition == null) {
171+
return prepareRightArg;
172+
} else if (canEvaluateConditionBasedOnLeftHandSide(join)) {
173+
return new LeftJoinPreFilterQueryEvaluationStep(
174+
prepareRightArg,
175+
new ScopeBindingsJoinConditionEvaluator(join.getAssuredBindingNames(), joinCondition));
176+
} else {
177+
return new LeftJoinPostFilterQueryEvaluationStep(
178+
prepareRightArg,
179+
new ScopeBindingsJoinConditionEvaluator(scopeBindingNames, joinCondition));
180+
}
181+
}
182+
183+
private static boolean canEvaluateConditionBasedOnLeftHandSide(LeftJoin leftJoin) {
184+
if (!leftJoin.hasCondition()) {
185+
return false;
186+
}
187+
188+
var varNames = VarNameCollector.process(leftJoin.getCondition());
189+
return leftJoin.getAssuredBindingNames().containsAll(varNames);
190+
}
253191
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.eclipse.rdf4j.query.algebra.evaluation.iterator;
2+
3+
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
4+
import org.eclipse.rdf4j.common.iteration.FilterIteration;
5+
import org.eclipse.rdf4j.query.BindingSet;
6+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
7+
8+
class LeftJoinPostFilterQueryEvaluationStep implements QueryEvaluationStep {
9+
10+
private final QueryEvaluationStep wrapped;
11+
private final ScopeBindingsJoinConditionEvaluator joinConditionEvaluator;
12+
13+
LeftJoinPostFilterQueryEvaluationStep(
14+
QueryEvaluationStep wrapped,
15+
ScopeBindingsJoinConditionEvaluator joinConditionEvaluator) {
16+
this.wrapped = wrapped;
17+
this.joinConditionEvaluator = joinConditionEvaluator;
18+
}
19+
20+
@Override
21+
public CloseableIteration<BindingSet> evaluate(BindingSet leftBindings) {
22+
var rightIteration = wrapped.evaluate(leftBindings);
23+
24+
if (rightIteration == QueryEvaluationStep.EMPTY_ITERATION) {
25+
return rightIteration;
26+
}
27+
28+
return new FilterIteration<>(rightIteration) {
29+
30+
@Override
31+
protected boolean accept(BindingSet bindings) {
32+
return joinConditionEvaluator.evaluate(bindings);
33+
}
34+
35+
@Override
36+
protected void handleClose() {
37+
rightIteration.close();
38+
}
39+
};
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.eclipse.rdf4j.query.algebra.evaluation.iterator;
2+
3+
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
4+
import org.eclipse.rdf4j.query.BindingSet;
5+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
6+
7+
class LeftJoinPreFilterQueryEvaluationStep implements QueryEvaluationStep {
8+
9+
private final QueryEvaluationStep wrapped;
10+
private final ScopeBindingsJoinConditionEvaluator joinConditionEvaluator;
11+
12+
LeftJoinPreFilterQueryEvaluationStep(
13+
QueryEvaluationStep wrapped,
14+
ScopeBindingsJoinConditionEvaluator joinConditionEvaluator) {
15+
this.wrapped = wrapped;
16+
this.joinConditionEvaluator = joinConditionEvaluator;
17+
}
18+
19+
@Override
20+
public CloseableIteration<BindingSet> evaluate(BindingSet leftBindings) {
21+
if (shouldEvaluate(leftBindings)) {
22+
return wrapped.evaluate(leftBindings);
23+
}
24+
25+
return QueryEvaluationStep.EMPTY_ITERATION;
26+
}
27+
28+
private boolean shouldEvaluate(BindingSet leftBindings) {
29+
return joinConditionEvaluator.evaluate(leftBindings);
30+
}
31+
}

0 commit comments

Comments
 (0)