Skip to content

Commit 4f1a470

Browse files
jasperiqJervenBolleman
authored andcommitted
GH-5286 Pushing tests to individual LeftJoin...FilterEvaluationSteps
1 parent 6962ee7 commit 4f1a470

5 files changed

Lines changed: 233 additions & 109 deletions

File tree

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/evaluationsteps/LeftJoinQueryEvaluationStep.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ public CloseableIteration<BindingSet> evaluate(BindingSet bindings) {
103103
if (containsNone) {
104104
// left join is "well designed"
105105
leftJoin.setAlgorithm(LeftJoinIterator.class.getSimpleName());
106-
return LeftJoinIterator.getInstance(left, right, condition, bindings, leftJoin.getBindingNames(), leftJoin);
106+
var rightEvaluationStep = LeftJoinIterator.determineRightEvaluationStep(leftJoin, right, condition, leftJoin.getBindingNames());
107+
return LeftJoinIterator.getInstance(left, bindings, rightEvaluationStep);
107108
} else {
108109
Set<String> problemVars = new HashSet<>(optionalVars);
109110
problemVars.retainAll(bindings.getBindingNames());

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,10 @@ public LeftJoinIterator(CloseableIteration<BindingSet> leftIter, QueryEvaluation
8080

8181
public static CloseableIteration<BindingSet> getInstance(
8282
QueryEvaluationStep left,
83-
QueryEvaluationStep prepareRightArg,
84-
QueryValueEvaluationStep joinCondition,
8583
BindingSet bindings,
86-
Set<String> scopeBindingNames,
87-
LeftJoin leftJoin) {
84+
QueryEvaluationStep rightEvaluationStep) {
8885

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

9688
if (leftIter == QueryEvaluationStep.EMPTY_ITERATION) {
9789
return leftIter;
@@ -162,7 +154,7 @@ protected void handleClose() throws QueryEvaluationException {
162154
}
163155
}
164156

165-
static QueryEvaluationStep determineRightEvaluationStep(
157+
public static QueryEvaluationStep determineRightEvaluationStep(
166158
LeftJoin join,
167159
QueryEvaluationStep prepareRightArg,
168160
QueryValueEvaluationStep joinCondition,

core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/LeftJoinIteratorTest.java

Lines changed: 4 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@
1313
import static org.junit.jupiter.api.Assertions.*;
1414

1515
import java.util.List;
16-
import java.util.Set;
17-
import java.util.concurrent.atomic.AtomicInteger;
1816

1917
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
2018
import org.eclipse.rdf4j.model.*;
21-
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
2219
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
2320
import org.eclipse.rdf4j.query.BindingSet;
2421
import org.eclipse.rdf4j.query.QueryEvaluationException;
@@ -27,7 +24,6 @@
2724
import org.eclipse.rdf4j.query.algebra.evaluation.impl.DefaultEvaluationStrategy;
2825
import org.junit.jupiter.api.BeforeAll;
2926
import org.junit.jupiter.api.BeforeEach;
30-
import org.junit.jupiter.api.DisplayName;
3127
import org.junit.jupiter.api.Test;
3228

3329
class LeftJoinIteratorTest {
@@ -71,104 +67,14 @@ void setUp() {
7167
}
7268

7369
@Test
74-
@DisplayName("when condition can be evaluated only with left hand side and condition evaluates to false, then don't evaluate right hand side")
75-
void skipsRightHandSideEvaluation() {
76-
QueryValueEvaluationStep condition = bindings -> BooleanLiteral.valueOf(false);
77-
Compare compare = new Compare(new Var("left"), new ValueConstant(VALUE_FACTORY.createLiteral(1337)));
78-
79-
runLeftOnce(condition, leftJoin(compare));
80-
81-
assertFalse(rightHandSide.isEvaluated);
82-
}
83-
84-
@Test
85-
@DisplayName("when condition can be evaluated only with left hand side and condition evaluates to false, then return left bindings")
86-
void onlyReturnLeftHandSideBindings() {
87-
QueryValueEvaluationStep condition = bindings -> BooleanLiteral.valueOf(false);
88-
Compare compare = new Compare(new Var("left"), new ValueConstant(VALUE_FACTORY.createLiteral(1337)));
89-
90-
var result = runLeftOnce(condition, leftJoin(compare));
91-
92-
assertIterableEquals(left.getBindingSets(), Set.of(result));
93-
}
94-
95-
@Test
96-
@DisplayName("when condition can be evaluated only with left hand side and condition evaluates to true, then evaluate right hand side")
97-
void evaluatesRightHandSideWithTrueCondition() {
98-
QueryValueEvaluationStep condition = bindings -> BooleanLiteral.valueOf(true);
99-
Compare compare = new Compare(new Var("left"), new ValueConstant(VALUE_FACTORY.createLiteral(42)));
100-
101-
runLeftOnce(condition, leftJoin(compare));
102-
103-
assertTrue(rightHandSide.isEvaluated);
104-
}
105-
106-
@Test
107-
@DisplayName("when condition can be evaluated only with left hand side and condition evaluates to true, then return joined bindings")
108-
void returnsRightBindings() {
109-
QueryValueEvaluationStep condition = bindings -> BooleanLiteral.valueOf(true);
110-
Compare compare = new Compare(new Var("left"), new ValueConstant(VALUE_FACTORY.createLiteral(42)));
111-
112-
var result = runLeftOnce(condition, leftJoin(compare));
113-
114-
var expected = new QueryBindingSet(2);
115-
bindingSet.forEach(expected::addBinding);
116-
RIGHT_BINDINGS.forEach(expected::addBinding);
117-
assertIterableEquals(expected, result);
118-
}
119-
120-
@Test
121-
@DisplayName("when condition can be evaluated only with left hand side and condition evaluates to true, then only evaluate condition once")
122-
void onlyEvaluatesConditionOnce() {
123-
var evaluations = new AtomicInteger(0);
124-
QueryValueEvaluationStep condition = bindings -> {
125-
evaluations.incrementAndGet();
126-
return BooleanLiteral.valueOf(true);
127-
};
128-
Compare compare = new Compare(new Var("left"), new ValueConstant(VALUE_FACTORY.createLiteral(42)));
129-
130-
runLeftOnce(condition, leftJoin(compare));
131-
132-
assertEquals(1, evaluations.get());
133-
}
134-
135-
@Test
136-
@DisplayName("when condition cannot be evaluated only with left hand side, then evaluate right hand side")
137-
void evaluatesRightHandSideEvaluation() {
138-
QueryValueEvaluationStep condition = bindings -> BooleanLiteral.valueOf(true);
139-
Compare compare = new Compare(new Var("right"), new ValueConstant(VALUE_FACTORY.createLiteral(42)));
140-
141-
runLeftOnce(condition, leftJoin(compare));
142-
143-
assertTrue(rightHandSide.isEvaluated);
144-
}
145-
146-
@Test
147-
@DisplayName("when no condition, then evaluate right hand side")
148-
void noCondition() {
149-
Compare compare = new Compare(new Var("right"), new ValueConstant(VALUE_FACTORY.createLiteral(42)));
150-
151-
runLeftOnce(null, leftJoin(compare));
70+
void evaluatesRightHandSideQueryEvaluationStep() {
71+
try (LeftJoinIterator iterator = new LeftJoinIterator(leftHandSide.evaluate(bindingSet), rightHandSide)) {
72+
iterator.getNextElement();
73+
}
15274

15375
assertTrue(rightHandSide.isEvaluated);
15476
}
15577

156-
private LeftJoin leftJoin(Compare compare) {
157-
return new LeftJoin(left, new EmptySet(), compare);
158-
}
159-
160-
private BindingSet runLeftOnce(QueryValueEvaluationStep condition, LeftJoin leftJoin) {
161-
try (LeftJoinIterator iterator = new LeftJoinIterator(
162-
leftHandSide,
163-
rightHandSide,
164-
condition,
165-
bindingSet,
166-
Set.of("left", "right"),
167-
leftJoin)) {
168-
return iterator.getNextElement();
169-
}
170-
}
171-
17278
private static class RightHandSideQueryEvaluationStep implements QueryEvaluationStep {
17379

17480
private boolean isEvaluated = false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package org.eclipse.rdf4j.query.algebra.evaluation.iterator;
2+
3+
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
4+
import org.eclipse.rdf4j.model.ValueFactory;
5+
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
6+
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
7+
import org.eclipse.rdf4j.query.BindingSet;
8+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
9+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
10+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryValueEvaluationStep;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.DisplayName;
13+
import org.junit.jupiter.api.Test;
14+
15+
import java.util.LinkedList;
16+
import java.util.Queue;
17+
import java.util.Set;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
class LeftJoinPostFilterQueryEvaluationStepTest {
22+
23+
private static final ValueFactory VALUE_FACTORY = SimpleValueFactory.getInstance();
24+
25+
private QueryBindingSet bindingSet;
26+
27+
@BeforeEach
28+
void setUp() {
29+
bindingSet = new QueryBindingSet(1);
30+
bindingSet.addBinding("a", VALUE_FACTORY.createLiteral(42));
31+
}
32+
33+
@Test
34+
@DisplayName("when wrapped returns empty iteration, then LeftJoinPostFilterQueryEvaluationStep returns empty iteration")
35+
void emptyIteration() {
36+
QueryEvaluationStep wrapped = (bindings) -> QueryEvaluationStep.EMPTY_ITERATION;
37+
QueryEvaluationStep postFilter = createPostFilter(wrapped, bindings -> BooleanLiteral.valueOf(true));
38+
39+
var result = postFilter.evaluate(bindingSet);
40+
41+
assertThat(result).isEqualTo(QueryEvaluationStep.EMPTY_ITERATION);
42+
}
43+
44+
@Test
45+
@DisplayName("when wrapped returns non-empty iteration, then LeftJoinPostFilterQueryEvaluationStep returns filtered iteration")
46+
void filteredIteration() {
47+
QueryEvaluationStep wrapped = new TestQueryEvaluationStep();
48+
QueryValueEvaluationStep condition = bindings -> {
49+
var shouldAccept = bindings.getValue("b")
50+
.stringValue()
51+
.equals("abc");
52+
return BooleanLiteral.valueOf(shouldAccept);
53+
};
54+
QueryEvaluationStep postFilter = createPostFilter(wrapped, condition);
55+
56+
var result = postFilter.evaluate(bindingSet);
57+
58+
assertThat(result)
59+
.toIterable()
60+
.map(bindings -> bindings.getValue("b"))
61+
.containsExactly(VALUE_FACTORY.createLiteral("abc"));
62+
}
63+
64+
private QueryEvaluationStep createPostFilter(QueryEvaluationStep wrapped, QueryValueEvaluationStep condition) {
65+
var evaluator = new ScopeBindingsJoinConditionEvaluator(Set.of("a", "b"), condition);
66+
return new LeftJoinPostFilterQueryEvaluationStep(wrapped, evaluator);
67+
}
68+
69+
private static class TestQueryEvaluationStep implements QueryEvaluationStep {
70+
71+
private final Queue<String> values = new LinkedList<>();
72+
73+
private TestQueryEvaluationStep() {
74+
values.add("abc");
75+
values.add("xyz");
76+
}
77+
78+
@Override
79+
public CloseableIteration<BindingSet> evaluate(BindingSet bindings) {
80+
return new CloseableIteration<>() {
81+
@Override
82+
public void close() {
83+
// Nothing to close
84+
}
85+
86+
@Override
87+
public boolean hasNext() {
88+
return !values.isEmpty();
89+
}
90+
91+
@Override
92+
public BindingSet next() {
93+
var output = new QueryBindingSet(2);
94+
bindings.forEach(output::addBinding);
95+
output.addBinding("b", VALUE_FACTORY.createLiteral(values.poll()));
96+
return output;
97+
}
98+
};
99+
}
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package org.eclipse.rdf4j.query.algebra.evaluation.iterator;
2+
3+
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
4+
import org.eclipse.rdf4j.model.ValueFactory;
5+
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
6+
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
7+
import org.eclipse.rdf4j.query.BindingSet;
8+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
9+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
10+
import org.eclipse.rdf4j.query.algebra.evaluation.QueryValueEvaluationStep;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.DisplayName;
13+
import org.junit.jupiter.api.Test;
14+
15+
import java.util.LinkedList;
16+
import java.util.Queue;
17+
import java.util.Set;
18+
import java.util.concurrent.atomic.AtomicInteger;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
class LeftJoinPreFilterQueryEvaluationStepTest {
23+
24+
private static final ValueFactory VALUE_FACTORY = SimpleValueFactory.getInstance();
25+
26+
private QueryBindingSet bindingSet;
27+
28+
@BeforeEach
29+
void setUp() {
30+
bindingSet = new QueryBindingSet(1);
31+
bindingSet.addBinding("a", VALUE_FACTORY.createLiteral(42));
32+
}
33+
34+
@Test
35+
@DisplayName("when condition evaluates to false, then don't evaluate wrapped")
36+
void skipWrappedEvaluation() {
37+
var wrapped = new TestQueryEvaluationStep();
38+
QueryEvaluationStep preFilter = createPreFilter(wrapped, bindings -> BooleanLiteral.valueOf(false));
39+
40+
try (var ignored = preFilter.evaluate(bindingSet)) {
41+
assertThat(wrapped.isEvaluated).isFalse();
42+
}
43+
}
44+
45+
@Test
46+
@DisplayName("when condition evaluates to false, then return empty iteration")
47+
void returnOnlyInput() {
48+
var wrapped = new TestQueryEvaluationStep();
49+
QueryEvaluationStep preFilter = createPreFilter(wrapped, bindings -> BooleanLiteral.valueOf(false));
50+
51+
var result = preFilter.evaluate(bindingSet);
52+
53+
assertThat(result).isEqualTo(QueryEvaluationStep.EMPTY_ITERATION);
54+
}
55+
56+
@Test
57+
@DisplayName("when condition evaluates to true, then evaluate and return wrapped output")
58+
void evaluateWrapped() {
59+
var wrapped = new TestQueryEvaluationStep();
60+
QueryEvaluationStep preFilter = createPreFilter(wrapped, bindings -> BooleanLiteral.valueOf(true));
61+
62+
var result = preFilter.evaluate(bindingSet);
63+
64+
assertThat(result)
65+
.toIterable()
66+
.map(bindings -> bindings.getValue("b"))
67+
.containsExactly(VALUE_FACTORY.createLiteral("abc"), VALUE_FACTORY.createLiteral("xyz"));
68+
}
69+
70+
@Test
71+
@DisplayName("when condition evaluates to true, then only evaluate condition once")
72+
void onlyEvaluatesConditionOnce() {
73+
var evaluations = new AtomicInteger(0);
74+
QueryValueEvaluationStep condition = bindings -> {
75+
evaluations.incrementAndGet();
76+
return BooleanLiteral.valueOf(true);
77+
};
78+
QueryEvaluationStep preFilter = createPreFilter(new TestQueryEvaluationStep(), condition);
79+
80+
try (var ignored = preFilter.evaluate(bindingSet)) {
81+
assertThat(evaluations).hasValue(1);
82+
}
83+
}
84+
85+
private QueryEvaluationStep createPreFilter(TestQueryEvaluationStep wrapped, QueryValueEvaluationStep condition) {
86+
var evaluator = new ScopeBindingsJoinConditionEvaluator(Set.of("a", "b"), condition);
87+
return new LeftJoinPreFilterQueryEvaluationStep(wrapped, evaluator);
88+
}
89+
90+
private static class TestQueryEvaluationStep implements QueryEvaluationStep {
91+
92+
private final Queue<String> values = new LinkedList<>();
93+
private boolean isEvaluated = false;
94+
95+
private TestQueryEvaluationStep() {
96+
values.add("abc");
97+
values.add("xyz");
98+
}
99+
100+
@Override
101+
public CloseableIteration<BindingSet> evaluate(BindingSet bindings) {
102+
isEvaluated = true;
103+
return new CloseableIteration<>() {
104+
@Override
105+
public void close() {
106+
// Nothing to close
107+
}
108+
109+
@Override
110+
public boolean hasNext() {
111+
return !values.isEmpty();
112+
}
113+
114+
@Override
115+
public BindingSet next() {
116+
var output = new QueryBindingSet(2);
117+
bindings.forEach(output::addBinding);
118+
output.addBinding("b", VALUE_FACTORY.createLiteral(values.poll()));
119+
return output;
120+
}
121+
};
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)