diff options
Diffstat (limited to 'subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java')
-rw-r--r-- | subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java new file mode 100644 index 00000000..6633b3b1 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java | |||
@@ -0,0 +1,129 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.interpreter; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.logic.dnf.Query; | ||
12 | import tools.refinery.logic.term.int_.IntTerms; | ||
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
14 | import tools.refinery.store.query.view.FunctionView; | ||
15 | import tools.refinery.store.query.view.KeyOnlyView; | ||
16 | import tools.refinery.store.representation.Symbol; | ||
17 | import tools.refinery.store.tuple.Tuple; | ||
18 | |||
19 | import java.util.List; | ||
20 | import java.util.Map; | ||
21 | import java.util.Optional; | ||
22 | |||
23 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; | ||
24 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
25 | |||
26 | class LeftJoinTest { | ||
27 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
28 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
29 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
30 | private static final FunctionView<Integer> ageView = new FunctionView<>(age); | ||
31 | |||
32 | @Test | ||
33 | void unarySymbolTest() { | ||
34 | var query = Query.of("Query", Integer.class, (builder, p1, output) -> builder | ||
35 | .clause( | ||
36 | personView.call(p1), | ||
37 | output.assign(ageView.leftJoin(18, p1)) | ||
38 | )); | ||
39 | |||
40 | var store = ModelStore.builder() | ||
41 | .symbols(person, age) | ||
42 | .with(QueryInterpreterAdapter.builder() | ||
43 | .queries(query)) | ||
44 | .build(); | ||
45 | |||
46 | var model = store.createEmptyModel(); | ||
47 | var personInterpretation = model.getInterpretation(person); | ||
48 | var ageInterpretation = model.getInterpretation(age); | ||
49 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
50 | var queryResultSet = queryEngine.getResultSet(query); | ||
51 | |||
52 | personInterpretation.put(Tuple.of(0), true); | ||
53 | personInterpretation.put(Tuple.of(1), true); | ||
54 | personInterpretation.put(Tuple.of(2), true); | ||
55 | |||
56 | ageInterpretation.put(Tuple.of(2), 24); | ||
57 | |||
58 | queryEngine.flushChanges(); | ||
59 | assertNullableResults(Map.of( | ||
60 | Tuple.of(0), Optional.of(18), | ||
61 | Tuple.of(1), Optional.of(18), | ||
62 | Tuple.of(2), Optional.of(24), | ||
63 | Tuple.of(3), Optional.empty() | ||
64 | ), queryResultSet); | ||
65 | |||
66 | personInterpretation.put(Tuple.of(0), false); | ||
67 | |||
68 | ageInterpretation.put(Tuple.of(1), 20); | ||
69 | ageInterpretation.put(Tuple.of(2), null); | ||
70 | |||
71 | queryEngine.flushChanges(); | ||
72 | assertNullableResults(Map.of( | ||
73 | Tuple.of(0), Optional.empty(), | ||
74 | Tuple.of(1), Optional.of(20), | ||
75 | Tuple.of(2), Optional.of(18), | ||
76 | Tuple.of(3), Optional.empty() | ||
77 | ), queryResultSet); | ||
78 | } | ||
79 | |||
80 | @Test | ||
81 | void unarySymbolWithAssignmentTest() { | ||
82 | // Tests an edge case where the outer joined variable is already bound in the query plan. | ||
83 | var query = Query.of("Query", (builder, p1) -> builder | ||
84 | .clause(Integer.class, v1 -> List.of( | ||
85 | personView.call(p1), | ||
86 | v1.assign(IntTerms.constant(18)), | ||
87 | v1.assign(ageView.leftJoin(18, p1)) | ||
88 | ))); | ||
89 | |||
90 | var store = ModelStore.builder() | ||
91 | .symbols(person, age) | ||
92 | .with(QueryInterpreterAdapter.builder() | ||
93 | .queries(query)) | ||
94 | .build(); | ||
95 | |||
96 | var model = store.createEmptyModel(); | ||
97 | var personInterpretation = model.getInterpretation(person); | ||
98 | var ageInterpretation = model.getInterpretation(age); | ||
99 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
100 | var queryResultSet = queryEngine.getResultSet(query); | ||
101 | |||
102 | personInterpretation.put(Tuple.of(0), true); | ||
103 | personInterpretation.put(Tuple.of(1), true); | ||
104 | personInterpretation.put(Tuple.of(2), true); | ||
105 | |||
106 | ageInterpretation.put(Tuple.of(2), 24); | ||
107 | |||
108 | queryEngine.flushChanges(); | ||
109 | assertResults(Map.of( | ||
110 | Tuple.of(0), true, | ||
111 | Tuple.of(1), true, | ||
112 | Tuple.of(2), false, | ||
113 | Tuple.of(3), false | ||
114 | ), queryResultSet); | ||
115 | |||
116 | personInterpretation.put(Tuple.of(0), false); | ||
117 | |||
118 | ageInterpretation.put(Tuple.of(1), 20); | ||
119 | ageInterpretation.put(Tuple.of(2), null); | ||
120 | |||
121 | queryEngine.flushChanges(); | ||
122 | assertResults(Map.of( | ||
123 | Tuple.of(0), false, | ||
124 | Tuple.of(1), false, | ||
125 | Tuple.of(2), true, | ||
126 | Tuple.of(3), false | ||
127 | ), queryResultSet); | ||
128 | } | ||
129 | } | ||