diff options
author | Kristóf Marussy <kristof@marussy.com> | 2023-09-16 13:19:31 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2023-09-16 16:53:01 +0200 |
commit | 97b0c4c1192fe5580a7957c844acc8092b56c604 (patch) | |
tree | bea3cdf9aaeb5da2864fcf87780d356661af8f63 /subprojects/store-query-interpreter/src/test | |
parent | build: fix Sonar quality gate issues (diff) | |
download | refinery-97b0c4c1192fe5580a7957c844acc8092b56c604.tar.gz refinery-97b0c4c1192fe5580a7957c844acc8092b56c604.tar.zst refinery-97b0c4c1192fe5580a7957c844acc8092b56c604.zip |
chore: remove VIATRA branding
Rename VIATRA subprojects to Refinery Interpreter to avoid interfering with
Eclipse Foundation trademarks.
Uses refering to a specific (historical) version of VIATRA were kept to avoid
ambiguity.
Diffstat (limited to 'subprojects/store-query-interpreter/src/test')
12 files changed, 3008 insertions, 0 deletions
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java new file mode 100644 index 00000000..76de8679 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java | |||
@@ -0,0 +1,391 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 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 tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Dnf; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.interpreter.tests.QueryEngineTest; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
15 | import tools.refinery.store.query.view.FunctionView; | ||
16 | import tools.refinery.store.query.view.KeyOnlyView; | ||
17 | import tools.refinery.store.representation.Symbol; | ||
18 | import tools.refinery.store.tuple.Tuple; | ||
19 | |||
20 | import java.util.List; | ||
21 | import java.util.Map; | ||
22 | import java.util.Optional; | ||
23 | |||
24 | import static tools.refinery.store.query.literal.Literals.not; | ||
25 | import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM; | ||
26 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; | ||
27 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
28 | |||
29 | class DiagonalQueryTest { | ||
30 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
31 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
32 | private static final Symbol<Boolean> symbol = Symbol.of("symbol", 4); | ||
33 | private static final Symbol<Integer> intSymbol = Symbol.of("intSymbol", 4, Integer.class); | ||
34 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
35 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
36 | private static final AnySymbolView symbolView = new KeyOnlyView<>(symbol); | ||
37 | private static final FunctionView<Integer> intSymbolView = new FunctionView<>(intSymbol); | ||
38 | |||
39 | @QueryEngineTest | ||
40 | void inputKeyNegationTest(QueryEvaluationHint hint) { | ||
41 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of( | ||
42 | personView.call(p1), | ||
43 | not(symbolView.call(p1, p1, p2, p2)) | ||
44 | ))); | ||
45 | |||
46 | var store = ModelStore.builder() | ||
47 | .symbols(person, symbol) | ||
48 | .with(QueryInterpreterAdapter.builder() | ||
49 | .defaultHint(hint) | ||
50 | .queries(query)) | ||
51 | .build(); | ||
52 | |||
53 | var model = store.createEmptyModel(); | ||
54 | var personInterpretation = model.getInterpretation(person); | ||
55 | var symbolInterpretation = model.getInterpretation(symbol); | ||
56 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
57 | var queryResultSet = queryEngine.getResultSet(query); | ||
58 | |||
59 | personInterpretation.put(Tuple.of(0), true); | ||
60 | personInterpretation.put(Tuple.of(1), true); | ||
61 | personInterpretation.put(Tuple.of(2), true); | ||
62 | |||
63 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
64 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
65 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
66 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
67 | |||
68 | queryEngine.flushChanges(); | ||
69 | assertResults(Map.of( | ||
70 | Tuple.of(0), false, | ||
71 | Tuple.of(1), true, | ||
72 | Tuple.of(2), true, | ||
73 | Tuple.of(3), false | ||
74 | ), queryResultSet); | ||
75 | } | ||
76 | |||
77 | @QueryEngineTest | ||
78 | void subQueryNegationTest(QueryEvaluationHint hint) { | ||
79 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder | ||
80 | .clause( | ||
81 | personView.call(p1), | ||
82 | symbolView.call(p1, p2, p3, p4) | ||
83 | ) | ||
84 | .clause( | ||
85 | personView.call(p2), | ||
86 | symbolView.call(p1, p2, p3, p4) | ||
87 | )); | ||
88 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of( | ||
89 | personView.call(p1), | ||
90 | not(subQuery.call(p1, p1, p2, p2)) | ||
91 | ))); | ||
92 | |||
93 | var store = ModelStore.builder() | ||
94 | .symbols(person, symbol) | ||
95 | .with(QueryInterpreterAdapter.builder() | ||
96 | .defaultHint(hint) | ||
97 | .queries(query)) | ||
98 | .build(); | ||
99 | |||
100 | var model = store.createEmptyModel(); | ||
101 | |||
102 | var personInterpretation = model.getInterpretation(person); | ||
103 | var symbolInterpretation = model.getInterpretation(symbol); | ||
104 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
105 | var queryResultSet = queryEngine.getResultSet(query); | ||
106 | |||
107 | personInterpretation.put(Tuple.of(0), true); | ||
108 | personInterpretation.put(Tuple.of(1), true); | ||
109 | personInterpretation.put(Tuple.of(2), true); | ||
110 | |||
111 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
112 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
113 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
114 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
115 | |||
116 | queryEngine.flushChanges(); | ||
117 | assertResults(Map.of( | ||
118 | Tuple.of(0), false, | ||
119 | Tuple.of(1), true, | ||
120 | Tuple.of(2), true, | ||
121 | Tuple.of(3), false | ||
122 | ), queryResultSet); | ||
123 | } | ||
124 | |||
125 | @QueryEngineTest | ||
126 | void inputKeyCountTest(QueryEvaluationHint hint) { | ||
127 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of( | ||
128 | personView.call(p1), | ||
129 | output.assign(symbolView.count(p1, p1, p2, p2)) | ||
130 | ))); | ||
131 | |||
132 | var store = ModelStore.builder() | ||
133 | .symbols(person, symbol) | ||
134 | .with(QueryInterpreterAdapter.builder() | ||
135 | .defaultHint(hint) | ||
136 | .queries(query)) | ||
137 | .build(); | ||
138 | |||
139 | var model = store.createEmptyModel(); | ||
140 | var personInterpretation = model.getInterpretation(person); | ||
141 | var symbolInterpretation = model.getInterpretation(symbol); | ||
142 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
143 | var queryResultSet = queryEngine.getResultSet(query); | ||
144 | |||
145 | personInterpretation.put(Tuple.of(0), true); | ||
146 | personInterpretation.put(Tuple.of(1), true); | ||
147 | personInterpretation.put(Tuple.of(2), true); | ||
148 | |||
149 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
150 | symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); | ||
151 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
152 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
153 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
154 | |||
155 | queryEngine.flushChanges(); | ||
156 | assertNullableResults(Map.of( | ||
157 | Tuple.of(0), Optional.of(2), | ||
158 | Tuple.of(1), Optional.of(0), | ||
159 | Tuple.of(2), Optional.of(0), | ||
160 | Tuple.of(3), Optional.empty() | ||
161 | ), queryResultSet); | ||
162 | } | ||
163 | |||
164 | @QueryEngineTest | ||
165 | void subQueryCountTest(QueryEvaluationHint hint) { | ||
166 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder.clause( | ||
167 | personView.call(p1), | ||
168 | symbolView.call(p1, p2, p3, p4) | ||
169 | ) | ||
170 | .clause( | ||
171 | personView.call(p2), | ||
172 | symbolView.call(p1, p2, p3, p4) | ||
173 | )); | ||
174 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of( | ||
175 | personView.call(p1), | ||
176 | output.assign(subQuery.count(p1, p1, p2, p2)) | ||
177 | ))); | ||
178 | |||
179 | var store = ModelStore.builder() | ||
180 | .symbols(person, symbol) | ||
181 | .with(QueryInterpreterAdapter.builder() | ||
182 | .defaultHint(hint) | ||
183 | .queries(query)) | ||
184 | .build(); | ||
185 | |||
186 | var model = store.createEmptyModel(); | ||
187 | var personInterpretation = model.getInterpretation(person); | ||
188 | var symbolInterpretation = model.getInterpretation(symbol); | ||
189 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
190 | var queryResultSet = queryEngine.getResultSet(query); | ||
191 | |||
192 | personInterpretation.put(Tuple.of(0), true); | ||
193 | personInterpretation.put(Tuple.of(1), true); | ||
194 | personInterpretation.put(Tuple.of(2), true); | ||
195 | |||
196 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
197 | symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); | ||
198 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
199 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
200 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
201 | |||
202 | queryEngine.flushChanges(); | ||
203 | assertNullableResults(Map.of( | ||
204 | Tuple.of(0), Optional.of(2), | ||
205 | Tuple.of(1), Optional.of(0), | ||
206 | Tuple.of(2), Optional.of(0), | ||
207 | Tuple.of(3), Optional.empty() | ||
208 | ), queryResultSet); | ||
209 | } | ||
210 | |||
211 | @QueryEngineTest | ||
212 | void inputKeyAggregationTest(QueryEvaluationHint hint) { | ||
213 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder | ||
214 | .clause((p2) -> List.of( | ||
215 | personView.call(p1), | ||
216 | output.assign(intSymbolView.aggregate(INT_SUM, p1, p1, p2, p2)) | ||
217 | ))); | ||
218 | |||
219 | var store = ModelStore.builder() | ||
220 | .symbols(person, intSymbol) | ||
221 | .with(QueryInterpreterAdapter.builder() | ||
222 | .defaultHint(hint) | ||
223 | .queries(query)) | ||
224 | .build(); | ||
225 | |||
226 | var model = store.createEmptyModel(); | ||
227 | var personInterpretation = model.getInterpretation(person); | ||
228 | var intSymbolInterpretation = model.getInterpretation(intSymbol); | ||
229 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
230 | var queryResultSet = queryEngine.getResultSet(query); | ||
231 | |||
232 | personInterpretation.put(Tuple.of(0), true); | ||
233 | personInterpretation.put(Tuple.of(1), true); | ||
234 | personInterpretation.put(Tuple.of(2), true); | ||
235 | |||
236 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); | ||
237 | intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); | ||
238 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); | ||
239 | intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); | ||
240 | intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); | ||
241 | |||
242 | queryEngine.flushChanges(); | ||
243 | assertNullableResults(Map.of( | ||
244 | Tuple.of(0), Optional.of(3), | ||
245 | Tuple.of(1), Optional.of(0), | ||
246 | Tuple.of(2), Optional.of(0), | ||
247 | Tuple.of(3), Optional.empty() | ||
248 | ), queryResultSet); | ||
249 | } | ||
250 | |||
251 | @QueryEngineTest | ||
252 | void subQueryAggregationTest(QueryEvaluationHint hint) { | ||
253 | var subQuery = Dnf.of("SubQuery", builder -> { | ||
254 | var p1 = builder.parameter("p1"); | ||
255 | var p2 = builder.parameter("p2"); | ||
256 | var p3 = builder.parameter("p3"); | ||
257 | var p4 = builder.parameter("p4"); | ||
258 | var x = builder.parameter("x", Integer.class); | ||
259 | var y = builder.parameter("y", Integer.class); | ||
260 | builder.clause( | ||
261 | personView.call(p1), | ||
262 | intSymbolView.call(p1, p2, p3, p4, x), | ||
263 | y.assign(x) | ||
264 | ); | ||
265 | builder.clause( | ||
266 | personView.call(p2), | ||
267 | intSymbolView.call(p1, p2, p3, p4, x), | ||
268 | y.assign(x) | ||
269 | ); | ||
270 | }); | ||
271 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder | ||
272 | .clause(Integer.class, Integer.class, (p2, y, z) -> List.of( | ||
273 | personView.call(p1), | ||
274 | output.assign(subQuery.aggregateBy(y, INT_SUM, p1, p1, p2, p2, y, z)) | ||
275 | ))); | ||
276 | |||
277 | var store = ModelStore.builder() | ||
278 | .symbols(person, intSymbol) | ||
279 | .with(QueryInterpreterAdapter.builder() | ||
280 | .defaultHint(hint) | ||
281 | .queries(query)) | ||
282 | .build(); | ||
283 | |||
284 | var model = store.createEmptyModel(); | ||
285 | var personInterpretation = model.getInterpretation(person); | ||
286 | var intSymbolInterpretation = model.getInterpretation(intSymbol); | ||
287 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
288 | var queryResultSet = queryEngine.getResultSet(query); | ||
289 | |||
290 | personInterpretation.put(Tuple.of(0), true); | ||
291 | personInterpretation.put(Tuple.of(1), true); | ||
292 | personInterpretation.put(Tuple.of(2), true); | ||
293 | |||
294 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); | ||
295 | intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); | ||
296 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); | ||
297 | intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); | ||
298 | intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); | ||
299 | |||
300 | queryEngine.flushChanges(); | ||
301 | assertNullableResults(Map.of( | ||
302 | Tuple.of(0), Optional.of(3), | ||
303 | Tuple.of(1), Optional.of(0), | ||
304 | Tuple.of(2), Optional.of(0), | ||
305 | Tuple.of(3), Optional.empty() | ||
306 | ), queryResultSet); | ||
307 | } | ||
308 | |||
309 | @QueryEngineTest | ||
310 | void inputKeyTransitiveTest(QueryEvaluationHint hint) { | ||
311 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause( | ||
312 | personView.call(p1), | ||
313 | friendView.callTransitive(p1, p1) | ||
314 | )); | ||
315 | |||
316 | var store = ModelStore.builder() | ||
317 | .symbols(person, friend) | ||
318 | .with(QueryInterpreterAdapter.builder() | ||
319 | .defaultHint(hint) | ||
320 | .queries(query)) | ||
321 | .build(); | ||
322 | |||
323 | var model = store.createEmptyModel(); | ||
324 | var personInterpretation = model.getInterpretation(person); | ||
325 | var friendInterpretation = model.getInterpretation(friend); | ||
326 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
327 | var queryResultSet = queryEngine.getResultSet(query); | ||
328 | |||
329 | personInterpretation.put(Tuple.of(0), true); | ||
330 | personInterpretation.put(Tuple.of(1), true); | ||
331 | personInterpretation.put(Tuple.of(2), true); | ||
332 | |||
333 | friendInterpretation.put(Tuple.of(0, 0), true); | ||
334 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
335 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
336 | |||
337 | queryEngine.flushChanges(); | ||
338 | assertResults(Map.of( | ||
339 | Tuple.of(0), true, | ||
340 | Tuple.of(1), false, | ||
341 | Tuple.of(2), false, | ||
342 | Tuple.of(3), false | ||
343 | ), queryResultSet); | ||
344 | } | ||
345 | |||
346 | @QueryEngineTest | ||
347 | void subQueryTransitiveTest(QueryEvaluationHint hint) { | ||
348 | var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder | ||
349 | .clause( | ||
350 | personView.call(p1), | ||
351 | friendView.call(p1, p2) | ||
352 | ) | ||
353 | .clause( | ||
354 | personView.call(p2), | ||
355 | friendView.call(p1, p2) | ||
356 | )); | ||
357 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause( | ||
358 | personView.call(p1), | ||
359 | subQuery.callTransitive(p1, p1) | ||
360 | )); | ||
361 | |||
362 | var store = ModelStore.builder() | ||
363 | .symbols(person, friend) | ||
364 | .with(QueryInterpreterAdapter.builder() | ||
365 | .defaultHint(hint) | ||
366 | .queries(query)) | ||
367 | .build(); | ||
368 | |||
369 | var model = store.createEmptyModel(); | ||
370 | var personInterpretation = model.getInterpretation(person); | ||
371 | var friendInterpretation = model.getInterpretation(friend); | ||
372 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
373 | var queryResultSet = queryEngine.getResultSet(query); | ||
374 | |||
375 | personInterpretation.put(Tuple.of(0), true); | ||
376 | personInterpretation.put(Tuple.of(1), true); | ||
377 | personInterpretation.put(Tuple.of(2), true); | ||
378 | |||
379 | friendInterpretation.put(Tuple.of(0, 0), true); | ||
380 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
381 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
382 | |||
383 | queryEngine.flushChanges(); | ||
384 | assertResults(Map.of( | ||
385 | Tuple.of(0), true, | ||
386 | Tuple.of(1), false, | ||
387 | Tuple.of(2), false, | ||
388 | Tuple.of(3), false | ||
389 | ), queryResultSet); | ||
390 | } | ||
391 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java new file mode 100644 index 00000000..00721a34 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java | |||
@@ -0,0 +1,519 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 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 tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.query.ModelQueryAdapter; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.interpreter.tests.QueryEngineTest; | ||
15 | import tools.refinery.store.query.view.AnySymbolView; | ||
16 | import tools.refinery.store.query.view.FilteredView; | ||
17 | import tools.refinery.store.query.view.FunctionView; | ||
18 | import tools.refinery.store.query.view.KeyOnlyView; | ||
19 | import tools.refinery.store.representation.Symbol; | ||
20 | import tools.refinery.store.representation.TruthValue; | ||
21 | import tools.refinery.store.tuple.Tuple; | ||
22 | |||
23 | import java.util.List; | ||
24 | import java.util.Map; | ||
25 | import java.util.Optional; | ||
26 | |||
27 | import static org.hamcrest.MatcherAssert.assertThat; | ||
28 | import static org.hamcrest.Matchers.is; | ||
29 | import static org.hamcrest.Matchers.nullValue; | ||
30 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
31 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
32 | import static tools.refinery.store.query.literal.Literals.check; | ||
33 | import static tools.refinery.store.query.term.int_.IntTerms.*; | ||
34 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; | ||
35 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
36 | |||
37 | class FunctionalQueryTest { | ||
38 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
39 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
40 | private static final Symbol<TruthValue> friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE); | ||
41 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
42 | private static final FunctionView<Integer> ageView = new FunctionView<>(age); | ||
43 | private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must); | ||
44 | |||
45 | @QueryEngineTest | ||
46 | void inputKeyTest(QueryEvaluationHint hint) { | ||
47 | var query = Query.of("InputKey", Integer.class, (builder, p1, output) -> builder.clause( | ||
48 | personView.call(p1), | ||
49 | ageView.call(p1, output) | ||
50 | )); | ||
51 | |||
52 | var store = ModelStore.builder() | ||
53 | .symbols(person, age) | ||
54 | .with(QueryInterpreterAdapter.builder() | ||
55 | .defaultHint(hint) | ||
56 | .queries(query)) | ||
57 | .build(); | ||
58 | |||
59 | var model = store.createEmptyModel(); | ||
60 | var personInterpretation = model.getInterpretation(person); | ||
61 | var ageInterpretation = model.getInterpretation(age); | ||
62 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
63 | var queryResultSet = queryEngine.getResultSet(query); | ||
64 | |||
65 | personInterpretation.put(Tuple.of(0), true); | ||
66 | personInterpretation.put(Tuple.of(1), true); | ||
67 | |||
68 | ageInterpretation.put(Tuple.of(0), 12); | ||
69 | ageInterpretation.put(Tuple.of(1), 24); | ||
70 | ageInterpretation.put(Tuple.of(2), 36); | ||
71 | |||
72 | queryEngine.flushChanges(); | ||
73 | assertNullableResults(Map.of( | ||
74 | Tuple.of(0), Optional.of(12), | ||
75 | Tuple.of(1), Optional.of(24), | ||
76 | Tuple.of(2), Optional.empty() | ||
77 | ), queryResultSet); | ||
78 | } | ||
79 | |||
80 | @QueryEngineTest | ||
81 | void predicateTest(QueryEvaluationHint hint) { | ||
82 | var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( | ||
83 | personView.call(p1), | ||
84 | ageView.call(p1, x) | ||
85 | )); | ||
86 | var query = Query.of("Predicate", Integer.class, (builder, p1, output) -> builder.clause( | ||
87 | personView.call(p1), | ||
88 | output.assign(subQuery.call(p1)) | ||
89 | )); | ||
90 | |||
91 | var store = ModelStore.builder() | ||
92 | .symbols(person, age) | ||
93 | .with(QueryInterpreterAdapter.builder() | ||
94 | .defaultHint(hint) | ||
95 | .queries(query)) | ||
96 | .build(); | ||
97 | |||
98 | var model = store.createEmptyModel(); | ||
99 | var personInterpretation = model.getInterpretation(person); | ||
100 | var ageInterpretation = model.getInterpretation(age); | ||
101 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
102 | var queryResultSet = queryEngine.getResultSet(query); | ||
103 | |||
104 | personInterpretation.put(Tuple.of(0), true); | ||
105 | personInterpretation.put(Tuple.of(1), true); | ||
106 | |||
107 | ageInterpretation.put(Tuple.of(0), 12); | ||
108 | ageInterpretation.put(Tuple.of(1), 24); | ||
109 | ageInterpretation.put(Tuple.of(2), 36); | ||
110 | |||
111 | queryEngine.flushChanges(); | ||
112 | assertNullableResults(Map.of( | ||
113 | Tuple.of(0), Optional.of(12), | ||
114 | Tuple.of(1), Optional.of(24), | ||
115 | Tuple.of(2), Optional.empty() | ||
116 | ), queryResultSet); | ||
117 | } | ||
118 | |||
119 | @QueryEngineTest | ||
120 | void computationTest(QueryEvaluationHint hint) { | ||
121 | var query = Query.of("Computation", Integer.class, (builder, p1, output) -> builder.clause(() -> { | ||
122 | var x = Variable.of("x", Integer.class); | ||
123 | return List.of( | ||
124 | personView.call(p1), | ||
125 | ageView.call(p1, x), | ||
126 | output.assign(mul(x, constant(7))) | ||
127 | ); | ||
128 | })); | ||
129 | |||
130 | var store = ModelStore.builder() | ||
131 | .symbols(person, age) | ||
132 | .with(QueryInterpreterAdapter.builder() | ||
133 | .defaultHint(hint) | ||
134 | .queries(query)) | ||
135 | .build(); | ||
136 | |||
137 | var model = store.createEmptyModel(); | ||
138 | var personInterpretation = model.getInterpretation(person); | ||
139 | var ageInterpretation = model.getInterpretation(age); | ||
140 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
141 | var queryResultSet = queryEngine.getResultSet(query); | ||
142 | |||
143 | personInterpretation.put(Tuple.of(0), true); | ||
144 | personInterpretation.put(Tuple.of(1), true); | ||
145 | |||
146 | ageInterpretation.put(Tuple.of(0), 12); | ||
147 | ageInterpretation.put(Tuple.of(1), 24); | ||
148 | |||
149 | queryEngine.flushChanges(); | ||
150 | assertNullableResults(Map.of( | ||
151 | Tuple.of(0), Optional.of(84), | ||
152 | Tuple.of(1), Optional.of(168), | ||
153 | Tuple.of(2), Optional.empty() | ||
154 | ), queryResultSet); | ||
155 | } | ||
156 | |||
157 | @QueryEngineTest | ||
158 | void inputKeyCountTest(QueryEvaluationHint hint) { | ||
159 | var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause( | ||
160 | personView.call(p1), | ||
161 | output.assign(friendMustView.count(p1, Variable.of())) | ||
162 | )); | ||
163 | |||
164 | var store = ModelStore.builder() | ||
165 | .symbols(person, friend) | ||
166 | .with(QueryInterpreterAdapter.builder() | ||
167 | .defaultHint(hint) | ||
168 | .queries(query)) | ||
169 | .build(); | ||
170 | |||
171 | var model = store.createEmptyModel(); | ||
172 | var personInterpretation = model.getInterpretation(person); | ||
173 | var friendInterpretation = model.getInterpretation(friend); | ||
174 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
175 | var queryResultSet = queryEngine.getResultSet(query); | ||
176 | |||
177 | personInterpretation.put(Tuple.of(0), true); | ||
178 | personInterpretation.put(Tuple.of(1), true); | ||
179 | personInterpretation.put(Tuple.of(2), true); | ||
180 | |||
181 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
182 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
183 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
184 | |||
185 | queryEngine.flushChanges(); | ||
186 | assertNullableResults(Map.of( | ||
187 | Tuple.of(0), Optional.of(1), | ||
188 | Tuple.of(1), Optional.of(2), | ||
189 | Tuple.of(2), Optional.of(0), | ||
190 | Tuple.of(3), Optional.empty() | ||
191 | ), queryResultSet); | ||
192 | } | ||
193 | |||
194 | @QueryEngineTest | ||
195 | void predicateCountTest(QueryEvaluationHint hint) { | ||
196 | var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder.clause( | ||
197 | personView.call(p1), | ||
198 | personView.call(p2), | ||
199 | friendMustView.call(p1, p2) | ||
200 | )); | ||
201 | var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause( | ||
202 | personView.call(p1), | ||
203 | output.assign(subQuery.count(p1, Variable.of())) | ||
204 | )); | ||
205 | |||
206 | var store = ModelStore.builder() | ||
207 | .symbols(person, friend) | ||
208 | .with(QueryInterpreterAdapter.builder() | ||
209 | .defaultHint(hint) | ||
210 | .queries(query)) | ||
211 | .build(); | ||
212 | |||
213 | var model = store.createEmptyModel(); | ||
214 | var personInterpretation = model.getInterpretation(person); | ||
215 | var friendInterpretation = model.getInterpretation(friend); | ||
216 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
217 | var queryResultSet = queryEngine.getResultSet(query); | ||
218 | |||
219 | personInterpretation.put(Tuple.of(0), true); | ||
220 | personInterpretation.put(Tuple.of(1), true); | ||
221 | personInterpretation.put(Tuple.of(2), true); | ||
222 | |||
223 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
224 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
225 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
226 | |||
227 | queryEngine.flushChanges(); | ||
228 | assertNullableResults(Map.of( | ||
229 | Tuple.of(0), Optional.of(1), | ||
230 | Tuple.of(1), Optional.of(2), | ||
231 | Tuple.of(2), Optional.of(0), | ||
232 | Tuple.of(3), Optional.empty() | ||
233 | ), queryResultSet); | ||
234 | } | ||
235 | |||
236 | @QueryEngineTest | ||
237 | void inputKeyAggregationTest(QueryEvaluationHint hint) { | ||
238 | var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause( | ||
239 | output.assign(ageView.aggregate(INT_SUM, Variable.of())) | ||
240 | )); | ||
241 | |||
242 | var store = ModelStore.builder() | ||
243 | .symbols(age) | ||
244 | .with(QueryInterpreterAdapter.builder() | ||
245 | .defaultHint(hint) | ||
246 | .queries(query)) | ||
247 | .build(); | ||
248 | |||
249 | var model = store.createEmptyModel(); | ||
250 | var ageInterpretation = model.getInterpretation(age); | ||
251 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
252 | var queryResultSet = queryEngine.getResultSet(query); | ||
253 | |||
254 | ageInterpretation.put(Tuple.of(0), 12); | ||
255 | ageInterpretation.put(Tuple.of(1), 24); | ||
256 | |||
257 | queryEngine.flushChanges(); | ||
258 | assertResults(Map.of(Tuple.of(), 36), queryResultSet); | ||
259 | } | ||
260 | |||
261 | @QueryEngineTest | ||
262 | void predicateAggregationTest(QueryEvaluationHint hint) { | ||
263 | var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( | ||
264 | personView.call(p1), | ||
265 | ageView.call(p1, x) | ||
266 | )); | ||
267 | var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause( | ||
268 | output.assign(subQuery.aggregate(INT_SUM, Variable.of())) | ||
269 | )); | ||
270 | |||
271 | var store = ModelStore.builder() | ||
272 | .symbols(person, age) | ||
273 | .with(QueryInterpreterAdapter.builder() | ||
274 | .defaultHint(hint) | ||
275 | .queries(query)) | ||
276 | .build(); | ||
277 | |||
278 | var model = store.createEmptyModel(); | ||
279 | var personInterpretation = model.getInterpretation(person); | ||
280 | var ageInterpretation = model.getInterpretation(age); | ||
281 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
282 | var queryResultSet = queryEngine.getResultSet(query); | ||
283 | |||
284 | personInterpretation.put(Tuple.of(0), true); | ||
285 | personInterpretation.put(Tuple.of(1), true); | ||
286 | |||
287 | ageInterpretation.put(Tuple.of(0), 12); | ||
288 | ageInterpretation.put(Tuple.of(1), 24); | ||
289 | |||
290 | queryEngine.flushChanges(); | ||
291 | assertResults(Map.of(Tuple.of(), 36), queryResultSet); | ||
292 | } | ||
293 | |||
294 | @QueryEngineTest | ||
295 | void extremeValueTest(QueryEvaluationHint hint) { | ||
296 | var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( | ||
297 | personView.call(p1), | ||
298 | x.assign(friendMustView.count(p1, Variable.of())) | ||
299 | )); | ||
300 | var minQuery = Query.of("Min", Integer.class, (builder, output) -> builder.clause( | ||
301 | output.assign(subQuery.aggregate(INT_MIN, Variable.of())) | ||
302 | )); | ||
303 | var maxQuery = Query.of("Max", Integer.class, (builder, output) -> builder.clause( | ||
304 | output.assign(subQuery.aggregate(INT_MAX, Variable.of())) | ||
305 | )); | ||
306 | |||
307 | var store = ModelStore.builder() | ||
308 | .symbols(person, friend) | ||
309 | .with(QueryInterpreterAdapter.builder() | ||
310 | .defaultHint(hint) | ||
311 | .queries(minQuery, maxQuery)) | ||
312 | .build(); | ||
313 | |||
314 | var model = store.createEmptyModel(); | ||
315 | var personInterpretation = model.getInterpretation(person); | ||
316 | var friendInterpretation = model.getInterpretation(friend); | ||
317 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
318 | var minResultSet = queryEngine.getResultSet(minQuery); | ||
319 | var maxResultSet = queryEngine.getResultSet(maxQuery); | ||
320 | |||
321 | assertResults(Map.of(Tuple.of(), Integer.MAX_VALUE), minResultSet); | ||
322 | assertResults(Map.of(Tuple.of(), Integer.MIN_VALUE), maxResultSet); | ||
323 | |||
324 | personInterpretation.put(Tuple.of(0), true); | ||
325 | personInterpretation.put(Tuple.of(1), true); | ||
326 | personInterpretation.put(Tuple.of(2), true); | ||
327 | |||
328 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
329 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
330 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
331 | |||
332 | queryEngine.flushChanges(); | ||
333 | assertResults(Map.of(Tuple.of(), 0), minResultSet); | ||
334 | assertResults(Map.of(Tuple.of(), 2), maxResultSet); | ||
335 | |||
336 | friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); | ||
337 | friendInterpretation.put(Tuple.of(2, 1), TruthValue.TRUE); | ||
338 | |||
339 | queryEngine.flushChanges(); | ||
340 | assertResults(Map.of(Tuple.of(), 1), minResultSet); | ||
341 | assertResults(Map.of(Tuple.of(), 2), maxResultSet); | ||
342 | |||
343 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.FALSE); | ||
344 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.FALSE); | ||
345 | friendInterpretation.put(Tuple.of(2, 0), TruthValue.FALSE); | ||
346 | |||
347 | queryEngine.flushChanges(); | ||
348 | assertResults(Map.of(Tuple.of(), 0), minResultSet); | ||
349 | assertResults(Map.of(Tuple.of(), 1), maxResultSet); | ||
350 | } | ||
351 | |||
352 | @QueryEngineTest | ||
353 | void invalidComputationTest(QueryEvaluationHint hint) { | ||
354 | var query = Query.of("InvalidComputation", Integer.class, | ||
355 | (builder, p1, output) -> builder.clause(Integer.class, (x) -> List.of( | ||
356 | personView.call(p1), | ||
357 | ageView.call(p1, x), | ||
358 | output.assign(div(constant(120), x)) | ||
359 | ))); | ||
360 | |||
361 | var store = ModelStore.builder() | ||
362 | .symbols(person, age) | ||
363 | .with(QueryInterpreterAdapter.builder() | ||
364 | .defaultHint(hint) | ||
365 | .queries(query)) | ||
366 | .build(); | ||
367 | |||
368 | var model = store.createEmptyModel(); | ||
369 | var personInterpretation = model.getInterpretation(person); | ||
370 | var ageInterpretation = model.getInterpretation(age); | ||
371 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
372 | var queryResultSet = queryEngine.getResultSet(query); | ||
373 | |||
374 | personInterpretation.put(Tuple.of(0), true); | ||
375 | personInterpretation.put(Tuple.of(1), true); | ||
376 | |||
377 | ageInterpretation.put(Tuple.of(0), 0); | ||
378 | ageInterpretation.put(Tuple.of(1), 30); | ||
379 | |||
380 | queryEngine.flushChanges(); | ||
381 | assertNullableResults(Map.of( | ||
382 | Tuple.of(0), Optional.empty(), | ||
383 | Tuple.of(1), Optional.of(4), | ||
384 | Tuple.of(2), Optional.empty() | ||
385 | ), queryResultSet); | ||
386 | } | ||
387 | |||
388 | @QueryEngineTest | ||
389 | void invalidAssumeTest(QueryEvaluationHint hint) { | ||
390 | var query = Query.of("InvalidAssume", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( | ||
391 | personView.call(p1), | ||
392 | ageView.call(p1, x), | ||
393 | check(lessEq(div(constant(120), x), constant(5))) | ||
394 | ))); | ||
395 | |||
396 | var store = ModelStore.builder() | ||
397 | .symbols(person, age) | ||
398 | .with(QueryInterpreterAdapter.builder() | ||
399 | .defaultHint(hint) | ||
400 | .queries(query)) | ||
401 | .build(); | ||
402 | |||
403 | var model = store.createEmptyModel(); | ||
404 | var personInterpretation = model.getInterpretation(person); | ||
405 | var ageInterpretation = model.getInterpretation(age); | ||
406 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
407 | var queryResultSet = queryEngine.getResultSet(query); | ||
408 | |||
409 | personInterpretation.put(Tuple.of(0), true); | ||
410 | personInterpretation.put(Tuple.of(1), true); | ||
411 | personInterpretation.put(Tuple.of(2), true); | ||
412 | |||
413 | ageInterpretation.put(Tuple.of(0), 0); | ||
414 | ageInterpretation.put(Tuple.of(1), 30); | ||
415 | ageInterpretation.put(Tuple.of(2), 20); | ||
416 | |||
417 | queryEngine.flushChanges(); | ||
418 | assertResults(Map.of( | ||
419 | Tuple.of(0), false, | ||
420 | Tuple.of(1), true, | ||
421 | Tuple.of(2), false, | ||
422 | Tuple.of(3), false | ||
423 | ), queryResultSet); | ||
424 | } | ||
425 | |||
426 | @QueryEngineTest | ||
427 | void multipleAssignmentTest(QueryEvaluationHint hint) { | ||
428 | var query = Query.of("MultipleAssignment", Integer.class, (builder, p1, p2, output) -> builder | ||
429 | .clause(Integer.class, Integer.class, (x1, x2) -> List.of( | ||
430 | ageView.call(p1, x1), | ||
431 | ageView.call(p2, x2), | ||
432 | output.assign(mul(x1, constant(2))), | ||
433 | output.assign(mul(x2, constant(3))) | ||
434 | ))); | ||
435 | |||
436 | var store = ModelStore.builder() | ||
437 | .symbols(age) | ||
438 | .with(QueryInterpreterAdapter.builder() | ||
439 | .defaultHint(hint) | ||
440 | .queries(query)) | ||
441 | .build(); | ||
442 | |||
443 | var model = store.createEmptyModel(); | ||
444 | var ageInterpretation = model.getInterpretation(age); | ||
445 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
446 | var queryResultSet = queryEngine.getResultSet(query); | ||
447 | |||
448 | ageInterpretation.put(Tuple.of(0), 3); | ||
449 | ageInterpretation.put(Tuple.of(1), 2); | ||
450 | ageInterpretation.put(Tuple.of(2), 15); | ||
451 | ageInterpretation.put(Tuple.of(3), 10); | ||
452 | |||
453 | queryEngine.flushChanges(); | ||
454 | assertNullableResults(Map.of( | ||
455 | Tuple.of(0, 1), Optional.of(6), | ||
456 | Tuple.of(1, 0), Optional.empty(), | ||
457 | Tuple.of(2, 3), Optional.of(30), | ||
458 | Tuple.of(3, 2), Optional.empty() | ||
459 | ), queryResultSet); | ||
460 | } | ||
461 | |||
462 | @QueryEngineTest | ||
463 | void notFunctionalTest(QueryEvaluationHint hint) { | ||
464 | var query = Query.of("NotFunctional", Integer.class, (builder, p1, output) -> builder.clause((p2) -> List.of( | ||
465 | personView.call(p1), | ||
466 | friendMustView.call(p1, p2), | ||
467 | ageView.call(p2, output) | ||
468 | ))); | ||
469 | |||
470 | var store = ModelStore.builder() | ||
471 | .symbols(person, age, friend) | ||
472 | .with(QueryInterpreterAdapter.builder() | ||
473 | .defaultHint(hint) | ||
474 | .queries(query)) | ||
475 | .build(); | ||
476 | |||
477 | var model = store.createEmptyModel(); | ||
478 | var personInterpretation = model.getInterpretation(person); | ||
479 | var ageInterpretation = model.getInterpretation(age); | ||
480 | var friendInterpretation = model.getInterpretation(friend); | ||
481 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
482 | var queryResultSet = queryEngine.getResultSet(query); | ||
483 | |||
484 | personInterpretation.put(Tuple.of(0), true); | ||
485 | personInterpretation.put(Tuple.of(1), true); | ||
486 | personInterpretation.put(Tuple.of(2), true); | ||
487 | |||
488 | ageInterpretation.put(Tuple.of(0), 24); | ||
489 | ageInterpretation.put(Tuple.of(1), 30); | ||
490 | ageInterpretation.put(Tuple.of(2), 36); | ||
491 | |||
492 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
493 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
494 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
495 | |||
496 | queryEngine.flushChanges(); | ||
497 | var invalidTuple = Tuple.of(1); | ||
498 | var cursor = queryResultSet.getAll(); | ||
499 | assertAll( | ||
500 | () -> assertThat("value for key 0", queryResultSet.get(Tuple.of(0)), is(30)), | ||
501 | () -> assertThrows(IllegalStateException.class, () -> queryResultSet.get(invalidTuple), | ||
502 | "multiple values for key 1"), | ||
503 | () -> assertThat("value for key 2", queryResultSet.get(Tuple.of(2)), is(nullValue())), | ||
504 | () -> assertThat("value for key 3", queryResultSet.get(Tuple.of(3)), is(nullValue())) | ||
505 | ); | ||
506 | if (hint.getQueryBackendRequirementType() != QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH) { | ||
507 | // Local search doesn't support throwing an error on multiple function return values. | ||
508 | assertThat("results size", queryResultSet.size(), is(2)); | ||
509 | assertThrows(IllegalStateException.class, () -> enumerateValues(cursor), "move cursor"); | ||
510 | } | ||
511 | } | ||
512 | |||
513 | private static void enumerateValues(Cursor<?, ?> cursor) { | ||
514 | //noinspection StatementWithEmptyBody | ||
515 | while (cursor.move()) { | ||
516 | // Nothing do, just let the cursor move through the result set. | ||
517 | } | ||
518 | } | ||
519 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java new file mode 100644 index 00000000..96d0f302 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java | |||
@@ -0,0 +1,117 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 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.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.resultset.OrderedResultSet; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
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 static org.hamcrest.MatcherAssert.assertThat; | ||
20 | import static org.hamcrest.Matchers.is; | ||
21 | |||
22 | class OrderedResultSetTest { | ||
23 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
24 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
25 | |||
26 | @Test | ||
27 | void relationalFlushTest() { | ||
28 | var query = Query.of("Relation", (builder, p1, p2) -> builder.clause( | ||
29 | friendView.call(p1, p2) | ||
30 | )); | ||
31 | |||
32 | var store = ModelStore.builder() | ||
33 | .symbols(friend) | ||
34 | .with(QueryInterpreterAdapter.builder() | ||
35 | .queries(query)) | ||
36 | .build(); | ||
37 | |||
38 | var model = store.createEmptyModel(); | ||
39 | var friendInterpretation = model.getInterpretation(friend); | ||
40 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
41 | var resultSet = queryEngine.getResultSet(query); | ||
42 | |||
43 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
44 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
45 | friendInterpretation.put(Tuple.of(1, 1), true); | ||
46 | queryEngine.flushChanges(); | ||
47 | |||
48 | try (var orderedResultSet = new OrderedResultSet<>(resultSet)) { | ||
49 | assertThat(orderedResultSet.size(), is(3)); | ||
50 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1))); | ||
51 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(1, 1))); | ||
52 | assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 2))); | ||
53 | |||
54 | friendInterpretation.put(Tuple.of(1, 2), false); | ||
55 | friendInterpretation.put(Tuple.of(0, 2), true); | ||
56 | queryEngine.flushChanges(); | ||
57 | |||
58 | assertThat(orderedResultSet.size(), is(3)); | ||
59 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1))); | ||
60 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(0, 2))); | ||
61 | assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 1))); | ||
62 | } | ||
63 | } | ||
64 | |||
65 | @Test | ||
66 | void functionalFlushTest() { | ||
67 | var query = Query.of("Function", Integer.class, (builder, p1, output) -> builder.clause( | ||
68 | friendView.call(p1, Variable.of()), | ||
69 | output.assign(friendView.count(p1, Variable.of())) | ||
70 | )); | ||
71 | |||
72 | var store = ModelStore.builder() | ||
73 | .symbols(friend) | ||
74 | .with(QueryInterpreterAdapter.builder() | ||
75 | .queries(query)) | ||
76 | .build(); | ||
77 | |||
78 | var model = store.createEmptyModel(); | ||
79 | var friendInterpretation = model.getInterpretation(friend); | ||
80 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
81 | var resultSet = queryEngine.getResultSet(query); | ||
82 | |||
83 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
84 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
85 | friendInterpretation.put(Tuple.of(1, 1), true); | ||
86 | queryEngine.flushChanges(); | ||
87 | |||
88 | try (var orderedResultSet = new OrderedResultSet<>(resultSet)) { | ||
89 | assertThat(orderedResultSet.size(), is(2)); | ||
90 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(0))); | ||
91 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(1))); | ||
92 | |||
93 | friendInterpretation.put(Tuple.of(0, 1), false); | ||
94 | friendInterpretation.put(Tuple.of(2, 1), true); | ||
95 | queryEngine.flushChanges(); | ||
96 | |||
97 | assertThat(orderedResultSet.size(), is(2)); | ||
98 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); | ||
99 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); | ||
100 | |||
101 | friendInterpretation.put(Tuple.of(1, 1), false); | ||
102 | queryEngine.flushChanges(); | ||
103 | |||
104 | assertThat(orderedResultSet.size(), is(2)); | ||
105 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); | ||
106 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); | ||
107 | |||
108 | friendInterpretation.put(Tuple.of(1, 2), false); | ||
109 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
110 | queryEngine.flushChanges(); | ||
111 | |||
112 | assertThat(orderedResultSet.size(), is(2)); | ||
113 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); | ||
114 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); | ||
115 | } | ||
116 | } | ||
117 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java new file mode 100644 index 00000000..72728dcd --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java | |||
@@ -0,0 +1,794 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 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 tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.query.ModelQueryAdapter; | ||
12 | import tools.refinery.store.query.dnf.Dnf; | ||
13 | import tools.refinery.store.query.dnf.Query; | ||
14 | import tools.refinery.store.query.term.ParameterDirection; | ||
15 | import tools.refinery.store.query.term.Variable; | ||
16 | import tools.refinery.store.query.interpreter.tests.QueryEngineTest; | ||
17 | import tools.refinery.store.query.view.AnySymbolView; | ||
18 | import tools.refinery.store.query.view.FilteredView; | ||
19 | import tools.refinery.store.query.view.FunctionView; | ||
20 | import tools.refinery.store.query.view.KeyOnlyView; | ||
21 | import tools.refinery.store.representation.Symbol; | ||
22 | import tools.refinery.store.representation.TruthValue; | ||
23 | import tools.refinery.store.tuple.Tuple; | ||
24 | |||
25 | import java.util.List; | ||
26 | import java.util.Map; | ||
27 | |||
28 | import static tools.refinery.store.query.literal.Literals.check; | ||
29 | import static tools.refinery.store.query.literal.Literals.not; | ||
30 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
31 | import static tools.refinery.store.query.term.int_.IntTerms.greaterEq; | ||
32 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
33 | |||
34 | class QueryTest { | ||
35 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
36 | private static final Symbol<TruthValue> friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE); | ||
37 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
38 | private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must); | ||
39 | |||
40 | @QueryEngineTest | ||
41 | void typeConstraintTest(QueryEvaluationHint hint) { | ||
42 | var asset = Symbol.of("Asset", 1); | ||
43 | |||
44 | var predicate = Query.of("TypeConstraint", (builder, p1) -> builder.clause(personView.call(p1))); | ||
45 | |||
46 | var store = ModelStore.builder() | ||
47 | .symbols(person, asset) | ||
48 | .with(QueryInterpreterAdapter.builder() | ||
49 | .defaultHint(hint) | ||
50 | .queries(predicate)) | ||
51 | .build(); | ||
52 | |||
53 | var model = store.createEmptyModel(); | ||
54 | var personInterpretation = model.getInterpretation(person); | ||
55 | var assetInterpretation = model.getInterpretation(asset); | ||
56 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
57 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
58 | |||
59 | personInterpretation.put(Tuple.of(0), true); | ||
60 | personInterpretation.put(Tuple.of(1), true); | ||
61 | |||
62 | assetInterpretation.put(Tuple.of(1), true); | ||
63 | assetInterpretation.put(Tuple.of(2), true); | ||
64 | |||
65 | queryEngine.flushChanges(); | ||
66 | assertResults(Map.of( | ||
67 | Tuple.of(0), true, | ||
68 | Tuple.of(1), true, | ||
69 | Tuple.of(2), false | ||
70 | ), predicateResultSet); | ||
71 | } | ||
72 | |||
73 | @QueryEngineTest | ||
74 | void relationConstraintTest(QueryEvaluationHint hint) { | ||
75 | var predicate = Query.of("RelationConstraint", (builder, p1, p2) -> builder.clause( | ||
76 | personView.call(p1), | ||
77 | personView.call(p2), | ||
78 | friendMustView.call(p1, p2) | ||
79 | )); | ||
80 | |||
81 | var store = ModelStore.builder() | ||
82 | .symbols(person, friend) | ||
83 | .with(QueryInterpreterAdapter.builder() | ||
84 | .defaultHint(hint) | ||
85 | .queries(predicate)) | ||
86 | .build(); | ||
87 | |||
88 | var model = store.createEmptyModel(); | ||
89 | var personInterpretation = model.getInterpretation(person); | ||
90 | var friendInterpretation = model.getInterpretation(friend); | ||
91 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
92 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
93 | |||
94 | personInterpretation.put(Tuple.of(0), true); | ||
95 | personInterpretation.put(Tuple.of(1), true); | ||
96 | personInterpretation.put(Tuple.of(2), true); | ||
97 | |||
98 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
99 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
100 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
101 | friendInterpretation.put(Tuple.of(1, 3), TruthValue.TRUE); | ||
102 | |||
103 | queryEngine.flushChanges(); | ||
104 | assertResults(Map.of( | ||
105 | Tuple.of(0, 1), true, | ||
106 | Tuple.of(1, 0), true, | ||
107 | Tuple.of(1, 2), true, | ||
108 | Tuple.of(2, 1), false | ||
109 | ), predicateResultSet); | ||
110 | } | ||
111 | |||
112 | @QueryEngineTest | ||
113 | void isConstantTest(QueryEvaluationHint hint) { | ||
114 | var predicate = Query.of("RelationConstraint", (builder, p1, p2) -> builder.clause( | ||
115 | personView.call(p1), | ||
116 | p1.isConstant(1), | ||
117 | friendMustView.call(p1, p2) | ||
118 | )); | ||
119 | |||
120 | var store = ModelStore.builder() | ||
121 | .symbols(person, friend) | ||
122 | .with(QueryInterpreterAdapter.builder() | ||
123 | .defaultHint(hint) | ||
124 | .queries(predicate)) | ||
125 | .build(); | ||
126 | |||
127 | var model = store.createEmptyModel(); | ||
128 | var personInterpretation = model.getInterpretation(person); | ||
129 | var friendInterpretation = model.getInterpretation(friend); | ||
130 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
131 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
132 | |||
133 | personInterpretation.put(Tuple.of(0), true); | ||
134 | personInterpretation.put(Tuple.of(1), true); | ||
135 | personInterpretation.put(Tuple.of(2), true); | ||
136 | |||
137 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
138 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
139 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
140 | |||
141 | queryEngine.flushChanges(); | ||
142 | assertResults(Map.of( | ||
143 | Tuple.of(0, 1), false, | ||
144 | Tuple.of(1, 0), true, | ||
145 | Tuple.of(1, 2), true, | ||
146 | Tuple.of(2, 1), false | ||
147 | ), predicateResultSet); | ||
148 | } | ||
149 | |||
150 | @QueryEngineTest | ||
151 | void existTest(QueryEvaluationHint hint) { | ||
152 | var predicate = Query.of("Exists", (builder, p1) -> builder.clause((p2) -> List.of( | ||
153 | personView.call(p1), | ||
154 | personView.call(p2), | ||
155 | friendMustView.call(p1, p2) | ||
156 | ))); | ||
157 | |||
158 | var store = ModelStore.builder() | ||
159 | .symbols(person, friend) | ||
160 | .with(QueryInterpreterAdapter.builder() | ||
161 | .defaultHint(hint) | ||
162 | .queries(predicate)) | ||
163 | .build(); | ||
164 | |||
165 | var model = store.createEmptyModel(); | ||
166 | var personInterpretation = model.getInterpretation(person); | ||
167 | var friendInterpretation = model.getInterpretation(friend); | ||
168 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
169 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
170 | |||
171 | personInterpretation.put(Tuple.of(0), true); | ||
172 | personInterpretation.put(Tuple.of(1), true); | ||
173 | personInterpretation.put(Tuple.of(2), true); | ||
174 | |||
175 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
176 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
177 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
178 | friendInterpretation.put(Tuple.of(3, 2), TruthValue.TRUE); | ||
179 | |||
180 | queryEngine.flushChanges(); | ||
181 | assertResults(Map.of( | ||
182 | Tuple.of(0), true, | ||
183 | Tuple.of(1), true, | ||
184 | Tuple.of(2), false, | ||
185 | Tuple.of(3), false | ||
186 | ), predicateResultSet); | ||
187 | } | ||
188 | |||
189 | @QueryEngineTest | ||
190 | void orTest(QueryEvaluationHint hint) { | ||
191 | var animal = Symbol.of("Animal", 1); | ||
192 | var animalView = new KeyOnlyView<>(animal); | ||
193 | |||
194 | var predicate = Query.of("Or", (builder, p1, p2) -> builder.clause( | ||
195 | personView.call(p1), | ||
196 | personView.call(p2), | ||
197 | friendMustView.call(p1, p2) | ||
198 | ).clause( | ||
199 | animalView.call(p1), | ||
200 | animalView.call(p2), | ||
201 | friendMustView.call(p1, p2) | ||
202 | )); | ||
203 | |||
204 | var store = ModelStore.builder() | ||
205 | .symbols(person, animal, friend) | ||
206 | .with(QueryInterpreterAdapter.builder() | ||
207 | .defaultHint(hint) | ||
208 | .queries(predicate)) | ||
209 | .build(); | ||
210 | |||
211 | var model = store.createEmptyModel(); | ||
212 | var personInterpretation = model.getInterpretation(person); | ||
213 | var animalInterpretation = model.getInterpretation(animal); | ||
214 | var friendInterpretation = model.getInterpretation(friend); | ||
215 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
216 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
217 | |||
218 | personInterpretation.put(Tuple.of(0), true); | ||
219 | personInterpretation.put(Tuple.of(1), true); | ||
220 | |||
221 | animalInterpretation.put(Tuple.of(2), true); | ||
222 | animalInterpretation.put(Tuple.of(3), true); | ||
223 | |||
224 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
225 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); | ||
226 | friendInterpretation.put(Tuple.of(2, 3), TruthValue.TRUE); | ||
227 | friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); | ||
228 | |||
229 | queryEngine.flushChanges(); | ||
230 | assertResults(Map.of( | ||
231 | Tuple.of(0, 1), true, | ||
232 | Tuple.of(0, 2), false, | ||
233 | Tuple.of(2, 3), true, | ||
234 | Tuple.of(3, 0), false, | ||
235 | Tuple.of(3, 2), false | ||
236 | ), predicateResultSet); | ||
237 | } | ||
238 | |||
239 | @QueryEngineTest | ||
240 | void equalityTest(QueryEvaluationHint hint) { | ||
241 | var predicate = Query.of("Equality", (builder, p1, p2) -> builder.clause( | ||
242 | personView.call(p1), | ||
243 | personView.call(p2), | ||
244 | p1.isEquivalent(p2) | ||
245 | )); | ||
246 | |||
247 | var store = ModelStore.builder() | ||
248 | .symbols(person) | ||
249 | .with(QueryInterpreterAdapter.builder() | ||
250 | .defaultHint(hint) | ||
251 | .queries(predicate)) | ||
252 | .build(); | ||
253 | |||
254 | var model = store.createEmptyModel(); | ||
255 | var personInterpretation = model.getInterpretation(person); | ||
256 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
257 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
258 | |||
259 | personInterpretation.put(Tuple.of(0), true); | ||
260 | personInterpretation.put(Tuple.of(1), true); | ||
261 | personInterpretation.put(Tuple.of(2), true); | ||
262 | |||
263 | queryEngine.flushChanges(); | ||
264 | assertResults(Map.of( | ||
265 | Tuple.of(0, 0), true, | ||
266 | Tuple.of(1, 1), true, | ||
267 | Tuple.of(2, 2), true, | ||
268 | Tuple.of(0, 1), false, | ||
269 | Tuple.of(3, 3), false | ||
270 | ), predicateResultSet); | ||
271 | } | ||
272 | |||
273 | @QueryEngineTest | ||
274 | void inequalityTest(QueryEvaluationHint hint) { | ||
275 | var predicate = Query.of("Inequality", (builder, p1, p2, p3) -> builder.clause( | ||
276 | personView.call(p1), | ||
277 | personView.call(p2), | ||
278 | friendMustView.call(p1, p3), | ||
279 | friendMustView.call(p2, p3), | ||
280 | p1.notEquivalent(p2) | ||
281 | )); | ||
282 | |||
283 | var store = ModelStore.builder() | ||
284 | .symbols(person, friend) | ||
285 | .with(QueryInterpreterAdapter.builder() | ||
286 | .defaultHint(hint) | ||
287 | .queries(predicate)) | ||
288 | .build(); | ||
289 | |||
290 | var model = store.createEmptyModel(); | ||
291 | var personInterpretation = model.getInterpretation(person); | ||
292 | var friendInterpretation = model.getInterpretation(friend); | ||
293 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
294 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
295 | |||
296 | personInterpretation.put(Tuple.of(0), true); | ||
297 | personInterpretation.put(Tuple.of(1), true); | ||
298 | personInterpretation.put(Tuple.of(2), true); | ||
299 | |||
300 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); | ||
301 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
302 | |||
303 | queryEngine.flushChanges(); | ||
304 | assertResults(Map.of( | ||
305 | Tuple.of(0, 1, 2), true, | ||
306 | Tuple.of(1, 0, 2), true, | ||
307 | Tuple.of(0, 0, 2), false | ||
308 | ), predicateResultSet); | ||
309 | } | ||
310 | |||
311 | @QueryEngineTest | ||
312 | void patternCallTest(QueryEvaluationHint hint) { | ||
313 | var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause( | ||
314 | personView.call(p1), | ||
315 | personView.call(p2), | ||
316 | friendMustView.call(p1, p2) | ||
317 | )); | ||
318 | var predicate = Query.of("PositivePatternCall", (builder, p3, p4) -> builder.clause( | ||
319 | personView.call(p3), | ||
320 | personView.call(p4), | ||
321 | friendPredicate.call(p3, p4) | ||
322 | )); | ||
323 | |||
324 | var store = ModelStore.builder() | ||
325 | .symbols(person, friend) | ||
326 | .with(QueryInterpreterAdapter.builder() | ||
327 | .defaultHint(hint) | ||
328 | .queries(predicate)) | ||
329 | .build(); | ||
330 | |||
331 | var model = store.createEmptyModel(); | ||
332 | var personInterpretation = model.getInterpretation(person); | ||
333 | var friendInterpretation = model.getInterpretation(friend); | ||
334 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
335 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
336 | |||
337 | personInterpretation.put(Tuple.of(0), true); | ||
338 | personInterpretation.put(Tuple.of(1), true); | ||
339 | personInterpretation.put(Tuple.of(2), true); | ||
340 | |||
341 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
342 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
343 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
344 | |||
345 | queryEngine.flushChanges(); | ||
346 | assertResults(Map.of( | ||
347 | Tuple.of(0, 1), true, | ||
348 | Tuple.of(1, 0), true, | ||
349 | Tuple.of(1, 2), true, | ||
350 | Tuple.of(2, 1), false | ||
351 | ), predicateResultSet); | ||
352 | } | ||
353 | |||
354 | @QueryEngineTest | ||
355 | void patternCallInputArgumentTest(QueryEvaluationHint hint) { | ||
356 | var friendPredicate = Dnf.of("Friend", builder -> { | ||
357 | var p1 = builder.parameter("p1", ParameterDirection.IN); | ||
358 | var p2 = builder.parameter("p2", ParameterDirection.IN); | ||
359 | builder.clause( | ||
360 | personView.call(p1), | ||
361 | personView.call(p2), | ||
362 | friendMustView.call(p1, p2) | ||
363 | ); | ||
364 | }); | ||
365 | var predicate = Query.of("PositivePatternCall", (builder, p3, p4) -> builder.clause( | ||
366 | personView.call(p3), | ||
367 | personView.call(p4), | ||
368 | friendPredicate.call(p3, p4) | ||
369 | )); | ||
370 | |||
371 | var store = ModelStore.builder() | ||
372 | .symbols(person, friend) | ||
373 | .with(QueryInterpreterAdapter.builder() | ||
374 | .defaultHint(hint) | ||
375 | .queries(predicate)) | ||
376 | .build(); | ||
377 | |||
378 | var model = store.createEmptyModel(); | ||
379 | var personInterpretation = model.getInterpretation(person); | ||
380 | var friendInterpretation = model.getInterpretation(friend); | ||
381 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
382 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
383 | |||
384 | personInterpretation.put(Tuple.of(0), true); | ||
385 | personInterpretation.put(Tuple.of(1), true); | ||
386 | personInterpretation.put(Tuple.of(2), true); | ||
387 | |||
388 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
389 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
390 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
391 | |||
392 | queryEngine.flushChanges(); | ||
393 | assertResults(Map.of( | ||
394 | Tuple.of(0, 1), true, | ||
395 | Tuple.of(1, 0), true, | ||
396 | Tuple.of(1, 2), true, | ||
397 | Tuple.of(2, 1), false | ||
398 | ), predicateResultSet); | ||
399 | } | ||
400 | |||
401 | @QueryEngineTest | ||
402 | void negativeRelationViewTest(QueryEvaluationHint hint) { | ||
403 | var predicate = Query.of("NegativePatternCall", (builder, p1, p2) -> builder.clause( | ||
404 | personView.call(p1), | ||
405 | personView.call(p2), | ||
406 | not(friendMustView.call(p1, p2)) | ||
407 | )); | ||
408 | |||
409 | var store = ModelStore.builder() | ||
410 | .symbols(person, friend) | ||
411 | .with(QueryInterpreterAdapter.builder() | ||
412 | .defaultHint(hint) | ||
413 | .queries(predicate)) | ||
414 | .build(); | ||
415 | |||
416 | var model = store.createEmptyModel(); | ||
417 | var personInterpretation = model.getInterpretation(person); | ||
418 | var friendInterpretation = model.getInterpretation(friend); | ||
419 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
420 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
421 | |||
422 | personInterpretation.put(Tuple.of(0), true); | ||
423 | personInterpretation.put(Tuple.of(1), true); | ||
424 | personInterpretation.put(Tuple.of(2), true); | ||
425 | |||
426 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
427 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
428 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
429 | |||
430 | queryEngine.flushChanges(); | ||
431 | assertResults(Map.of( | ||
432 | Tuple.of(0, 0), true, | ||
433 | Tuple.of(0, 2), true, | ||
434 | Tuple.of(1, 1), true, | ||
435 | Tuple.of(2, 0), true, | ||
436 | Tuple.of(2, 1), true, | ||
437 | Tuple.of(2, 2), true, | ||
438 | Tuple.of(0, 1), false, | ||
439 | Tuple.of(1, 0), false, | ||
440 | Tuple.of(1, 2), false, | ||
441 | Tuple.of(0, 3), false | ||
442 | ), predicateResultSet); | ||
443 | } | ||
444 | |||
445 | @QueryEngineTest | ||
446 | void negativePatternCallTest(QueryEvaluationHint hint) { | ||
447 | var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause( | ||
448 | personView.call(p1), | ||
449 | personView.call(p2), | ||
450 | friendMustView.call(p1, p2) | ||
451 | )); | ||
452 | var predicate = Query.of("NegativePatternCall", (builder, p3, p4) -> builder.clause( | ||
453 | personView.call(p3), | ||
454 | personView.call(p4), | ||
455 | not(friendPredicate.call(p3, p4)) | ||
456 | )); | ||
457 | |||
458 | var store = ModelStore.builder() | ||
459 | .symbols(person, friend) | ||
460 | .with(QueryInterpreterAdapter.builder() | ||
461 | .defaultHint(hint) | ||
462 | .queries(predicate)) | ||
463 | .build(); | ||
464 | |||
465 | var model = store.createEmptyModel(); | ||
466 | var personInterpretation = model.getInterpretation(person); | ||
467 | var friendInterpretation = model.getInterpretation(friend); | ||
468 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
469 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
470 | |||
471 | personInterpretation.put(Tuple.of(0), true); | ||
472 | personInterpretation.put(Tuple.of(1), true); | ||
473 | personInterpretation.put(Tuple.of(2), true); | ||
474 | |||
475 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
476 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
477 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
478 | |||
479 | queryEngine.flushChanges(); | ||
480 | assertResults(Map.of( | ||
481 | Tuple.of(0, 0), true, | ||
482 | Tuple.of(0, 2), true, | ||
483 | Tuple.of(1, 1), true, | ||
484 | Tuple.of(2, 0), true, | ||
485 | Tuple.of(2, 1), true, | ||
486 | Tuple.of(2, 2), true, | ||
487 | Tuple.of(0, 1), false, | ||
488 | Tuple.of(1, 0), false, | ||
489 | Tuple.of(1, 2), false, | ||
490 | Tuple.of(0, 3), false | ||
491 | ), predicateResultSet); | ||
492 | } | ||
493 | |||
494 | @QueryEngineTest | ||
495 | void negativeRelationViewWithQuantificationTest(QueryEvaluationHint hint) { | ||
496 | var predicate = Query.of("Negative", (builder, p1) -> builder.clause( | ||
497 | personView.call(p1), | ||
498 | not(friendMustView.call(p1, Variable.of())) | ||
499 | )); | ||
500 | |||
501 | var store = ModelStore.builder() | ||
502 | .symbols(person, friend) | ||
503 | .with(QueryInterpreterAdapter.builder() | ||
504 | .defaultHint(hint) | ||
505 | .queries(predicate)) | ||
506 | .build(); | ||
507 | |||
508 | var model = store.createEmptyModel(); | ||
509 | var personInterpretation = model.getInterpretation(person); | ||
510 | var friendInterpretation = model.getInterpretation(friend); | ||
511 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
512 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
513 | |||
514 | personInterpretation.put(Tuple.of(0), true); | ||
515 | personInterpretation.put(Tuple.of(1), true); | ||
516 | personInterpretation.put(Tuple.of(2), true); | ||
517 | |||
518 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
519 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); | ||
520 | |||
521 | queryEngine.flushChanges(); | ||
522 | assertResults(Map.of( | ||
523 | Tuple.of(0), false, | ||
524 | Tuple.of(1), true, | ||
525 | Tuple.of(2), true, | ||
526 | Tuple.of(3), false | ||
527 | ), predicateResultSet); | ||
528 | } | ||
529 | |||
530 | @QueryEngineTest | ||
531 | void negativeWithQuantificationTest(QueryEvaluationHint hint) { | ||
532 | var called = Query.of("Called", (builder, p1, p2) -> builder.clause( | ||
533 | personView.call(p1), | ||
534 | personView.call(p2), | ||
535 | friendMustView.call(p1, p2) | ||
536 | )); | ||
537 | var predicate = Query.of("Negative", (builder, p1) -> builder.clause( | ||
538 | personView.call(p1), | ||
539 | not(called.call(p1, Variable.of())) | ||
540 | )); | ||
541 | |||
542 | var store = ModelStore.builder() | ||
543 | .symbols(person, friend) | ||
544 | .with(QueryInterpreterAdapter.builder() | ||
545 | .defaultHint(hint) | ||
546 | .queries(predicate)) | ||
547 | .build(); | ||
548 | |||
549 | var model = store.createEmptyModel(); | ||
550 | var personInterpretation = model.getInterpretation(person); | ||
551 | var friendInterpretation = model.getInterpretation(friend); | ||
552 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
553 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
554 | |||
555 | personInterpretation.put(Tuple.of(0), true); | ||
556 | personInterpretation.put(Tuple.of(1), true); | ||
557 | personInterpretation.put(Tuple.of(2), true); | ||
558 | |||
559 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
560 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); | ||
561 | |||
562 | queryEngine.flushChanges(); | ||
563 | assertResults(Map.of( | ||
564 | Tuple.of(0), false, | ||
565 | Tuple.of(1), true, | ||
566 | Tuple.of(2), true, | ||
567 | Tuple.of(3), false | ||
568 | ), predicateResultSet); | ||
569 | } | ||
570 | |||
571 | @QueryEngineTest | ||
572 | void transitiveRelationViewTest(QueryEvaluationHint hint) { | ||
573 | var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause( | ||
574 | personView.call(p1), | ||
575 | personView.call(p2), | ||
576 | friendMustView.callTransitive(p1, p2) | ||
577 | )); | ||
578 | |||
579 | var store = ModelStore.builder() | ||
580 | .symbols(person, friend) | ||
581 | .with(QueryInterpreterAdapter.builder() | ||
582 | .defaultHint(hint) | ||
583 | .queries(predicate)) | ||
584 | .build(); | ||
585 | |||
586 | var model = store.createEmptyModel(); | ||
587 | var personInterpretation = model.getInterpretation(person); | ||
588 | var friendInterpretation = model.getInterpretation(friend); | ||
589 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
590 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
591 | |||
592 | personInterpretation.put(Tuple.of(0), true); | ||
593 | personInterpretation.put(Tuple.of(1), true); | ||
594 | personInterpretation.put(Tuple.of(2), true); | ||
595 | |||
596 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
597 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
598 | |||
599 | queryEngine.flushChanges(); | ||
600 | assertResults(Map.of( | ||
601 | Tuple.of(0, 0), false, | ||
602 | Tuple.of(0, 1), true, | ||
603 | Tuple.of(0, 2), true, | ||
604 | Tuple.of(1, 0), false, | ||
605 | Tuple.of(1, 1), false, | ||
606 | Tuple.of(1, 2), true, | ||
607 | Tuple.of(2, 0), false, | ||
608 | Tuple.of(2, 1), false, | ||
609 | Tuple.of(2, 2), false, | ||
610 | Tuple.of(2, 3), false | ||
611 | ), predicateResultSet); | ||
612 | } | ||
613 | |||
614 | @QueryEngineTest | ||
615 | void transitivePatternCallTest(QueryEvaluationHint hint) { | ||
616 | var called = Query.of("Called", (builder, p1, p2) -> builder.clause( | ||
617 | personView.call(p1), | ||
618 | personView.call(p2), | ||
619 | friendMustView.call(p1, p2) | ||
620 | )); | ||
621 | var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause( | ||
622 | personView.call(p1), | ||
623 | personView.call(p2), | ||
624 | called.callTransitive(p1, p2) | ||
625 | )); | ||
626 | |||
627 | var store = ModelStore.builder() | ||
628 | .symbols(person, friend) | ||
629 | .with(QueryInterpreterAdapter.builder() | ||
630 | .defaultHint(hint) | ||
631 | .queries(predicate)) | ||
632 | .build(); | ||
633 | |||
634 | var model = store.createEmptyModel(); | ||
635 | var personInterpretation = model.getInterpretation(person); | ||
636 | var friendInterpretation = model.getInterpretation(friend); | ||
637 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
638 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
639 | |||
640 | personInterpretation.put(Tuple.of(0), true); | ||
641 | personInterpretation.put(Tuple.of(1), true); | ||
642 | personInterpretation.put(Tuple.of(2), true); | ||
643 | |||
644 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
645 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
646 | |||
647 | queryEngine.flushChanges(); | ||
648 | assertResults(Map.of( | ||
649 | Tuple.of(0, 0), false, | ||
650 | Tuple.of(0, 1), true, | ||
651 | Tuple.of(0, 2), true, | ||
652 | Tuple.of(1, 0), false, | ||
653 | Tuple.of(1, 1), false, | ||
654 | Tuple.of(1, 2), true, | ||
655 | Tuple.of(2, 0), false, | ||
656 | Tuple.of(2, 1), false, | ||
657 | Tuple.of(2, 2), false, | ||
658 | Tuple.of(2, 3), false | ||
659 | ), predicateResultSet); | ||
660 | } | ||
661 | |||
662 | @Test | ||
663 | void filteredIntegerViewTest() { | ||
664 | var distance = Symbol.of("distance", 2, Integer.class); | ||
665 | var nearView = new FilteredView<>(distance, value -> value < 2); | ||
666 | var farView = new FilteredView<>(distance, value -> value >= 5); | ||
667 | var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of( | ||
668 | a1.notEquivalent(a2), | ||
669 | nearView.call(a1, a3), | ||
670 | nearView.call(a2, a3), | ||
671 | not(farView.call(a1, a2)) | ||
672 | ))); | ||
673 | var store = ModelStore.builder() | ||
674 | .symbols(distance) | ||
675 | .with(QueryInterpreterAdapter.builder() | ||
676 | .queries(dangerQuery)) | ||
677 | .build(); | ||
678 | |||
679 | var model = store.createEmptyModel(); | ||
680 | var distanceInterpretation = model.getInterpretation(distance); | ||
681 | distanceInterpretation.put(Tuple.of(0, 1), 1); | ||
682 | distanceInterpretation.put(Tuple.of(1, 0), 1); | ||
683 | distanceInterpretation.put(Tuple.of(0, 2), 1); | ||
684 | distanceInterpretation.put(Tuple.of(2, 0), 1); | ||
685 | distanceInterpretation.put(Tuple.of(1, 2), 3); | ||
686 | distanceInterpretation.put(Tuple.of(2, 1), 3); | ||
687 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
688 | var dangerResultSet = queryEngine.getResultSet(dangerQuery); | ||
689 | queryEngine.flushChanges(); | ||
690 | assertResults(Map.of( | ||
691 | Tuple.of(0, 1), false, | ||
692 | Tuple.of(0, 2), false, | ||
693 | Tuple.of(1, 2), true, | ||
694 | Tuple.of(2, 1), true | ||
695 | ), dangerResultSet); | ||
696 | } | ||
697 | |||
698 | @Test | ||
699 | void filteredDoubleViewTest() { | ||
700 | var distance = Symbol.of("distance", 2, Double.class); | ||
701 | var nearView = new FilteredView<>(distance, value -> value < 2); | ||
702 | var farView = new FilteredView<>(distance, value -> value >= 5); | ||
703 | var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of( | ||
704 | a1.notEquivalent(a2), | ||
705 | nearView.call(a1, a3), | ||
706 | nearView.call(a2, a3), | ||
707 | not(farView.call(a1, a2)) | ||
708 | ))); | ||
709 | var store = ModelStore.builder() | ||
710 | .symbols(distance) | ||
711 | .with(QueryInterpreterAdapter.builder() | ||
712 | .queries(dangerQuery)) | ||
713 | .build(); | ||
714 | |||
715 | var model = store.createEmptyModel(); | ||
716 | var distanceInterpretation = model.getInterpretation(distance); | ||
717 | distanceInterpretation.put(Tuple.of(0, 1), 1.0); | ||
718 | distanceInterpretation.put(Tuple.of(1, 0), 1.0); | ||
719 | distanceInterpretation.put(Tuple.of(0, 2), 1.0); | ||
720 | distanceInterpretation.put(Tuple.of(2, 0), 1.0); | ||
721 | distanceInterpretation.put(Tuple.of(1, 2), 3.0); | ||
722 | distanceInterpretation.put(Tuple.of(2, 1), 3.0); | ||
723 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
724 | var dangerResultSet = queryEngine.getResultSet(dangerQuery); | ||
725 | queryEngine.flushChanges(); | ||
726 | assertResults(Map.of( | ||
727 | Tuple.of(0, 1), false, | ||
728 | Tuple.of(0, 2), false, | ||
729 | Tuple.of(1, 2), true, | ||
730 | Tuple.of(2, 1), true | ||
731 | ), dangerResultSet); | ||
732 | } | ||
733 | |||
734 | @QueryEngineTest | ||
735 | void assumeTest(QueryEvaluationHint hint) { | ||
736 | var age = Symbol.of("age", 1, Integer.class); | ||
737 | var ageView = new FunctionView<>(age); | ||
738 | |||
739 | var query = Query.of("Constraint", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( | ||
740 | personView.call(p1), | ||
741 | ageView.call(p1, x), | ||
742 | check(greaterEq(x, constant(18))) | ||
743 | ))); | ||
744 | |||
745 | var store = ModelStore.builder() | ||
746 | .symbols(person, age) | ||
747 | .with(QueryInterpreterAdapter.builder() | ||
748 | .defaultHint(hint) | ||
749 | .queries(query)) | ||
750 | .build(); | ||
751 | |||
752 | var model = store.createEmptyModel(); | ||
753 | var personInterpretation = model.getInterpretation(person); | ||
754 | var ageInterpretation = model.getInterpretation(age); | ||
755 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
756 | var queryResultSet = queryEngine.getResultSet(query); | ||
757 | |||
758 | personInterpretation.put(Tuple.of(0), true); | ||
759 | personInterpretation.put(Tuple.of(1), true); | ||
760 | |||
761 | ageInterpretation.put(Tuple.of(0), 12); | ||
762 | ageInterpretation.put(Tuple.of(1), 24); | ||
763 | |||
764 | queryEngine.flushChanges(); | ||
765 | assertResults(Map.of( | ||
766 | Tuple.of(0), false, | ||
767 | Tuple.of(1), true, | ||
768 | Tuple.of(2), false | ||
769 | ), queryResultSet); | ||
770 | } | ||
771 | |||
772 | @Test | ||
773 | void alwaysFalseTest() { | ||
774 | var predicate = Query.of("AlwaysFalse", builder -> builder.parameter("p1")); | ||
775 | |||
776 | var store = ModelStore.builder() | ||
777 | .symbols(person) | ||
778 | .with(QueryInterpreterAdapter.builder() | ||
779 | .queries(predicate)) | ||
780 | .build(); | ||
781 | |||
782 | var model = store.createEmptyModel(); | ||
783 | var personInterpretation = model.getInterpretation(person); | ||
784 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
785 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
786 | |||
787 | personInterpretation.put(Tuple.of(0), true); | ||
788 | personInterpretation.put(Tuple.of(1), true); | ||
789 | personInterpretation.put(Tuple.of(2), true); | ||
790 | |||
791 | queryEngine.flushChanges(); | ||
792 | assertResults(Map.of(), predicateResultSet); | ||
793 | } | ||
794 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java new file mode 100644 index 00000000..1cd05d91 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java | |||
@@ -0,0 +1,370 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 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 tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.query.ModelQueryAdapter; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
15 | import tools.refinery.store.query.view.FilteredView; | ||
16 | import tools.refinery.store.query.view.FunctionView; | ||
17 | import tools.refinery.store.query.view.KeyOnlyView; | ||
18 | import tools.refinery.store.representation.Symbol; | ||
19 | import tools.refinery.store.tuple.Tuple; | ||
20 | |||
21 | import java.util.Map; | ||
22 | import java.util.Optional; | ||
23 | |||
24 | import static org.junit.jupiter.api.Assertions.assertFalse; | ||
25 | import static org.junit.jupiter.api.Assertions.assertTrue; | ||
26 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; | ||
27 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
28 | |||
29 | class QueryTransactionTest { | ||
30 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
31 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
32 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
33 | private static final AnySymbolView ageView = new FunctionView<>(age); | ||
34 | private static final RelationalQuery predicate = Query.of("TypeConstraint", (builder, p1) -> | ||
35 | builder.clause(personView.call(p1))); | ||
36 | |||
37 | @Test | ||
38 | void flushTest() { | ||
39 | var store = ModelStore.builder() | ||
40 | .symbols(person) | ||
41 | .with(QueryInterpreterAdapter.builder() | ||
42 | .queries(predicate)) | ||
43 | .build(); | ||
44 | |||
45 | var model = store.createEmptyModel(); | ||
46 | var personInterpretation = model.getInterpretation(person); | ||
47 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
48 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
49 | |||
50 | assertResults(Map.of( | ||
51 | Tuple.of(0), false, | ||
52 | Tuple.of(1), false, | ||
53 | Tuple.of(2), false, | ||
54 | Tuple.of(3), false | ||
55 | ), predicateResultSet); | ||
56 | assertFalse(queryEngine.hasPendingChanges()); | ||
57 | |||
58 | personInterpretation.put(Tuple.of(0), true); | ||
59 | personInterpretation.put(Tuple.of(1), true); | ||
60 | |||
61 | assertResults(Map.of( | ||
62 | Tuple.of(0), false, | ||
63 | Tuple.of(1), false, | ||
64 | Tuple.of(2), false, | ||
65 | Tuple.of(3), false | ||
66 | ), predicateResultSet); | ||
67 | assertTrue(queryEngine.hasPendingChanges()); | ||
68 | |||
69 | queryEngine.flushChanges(); | ||
70 | assertResults(Map.of( | ||
71 | Tuple.of(0), true, | ||
72 | Tuple.of(1), true, | ||
73 | Tuple.of(2), false, | ||
74 | Tuple.of(3), false | ||
75 | ), predicateResultSet); | ||
76 | assertFalse(queryEngine.hasPendingChanges()); | ||
77 | |||
78 | personInterpretation.put(Tuple.of(1), false); | ||
79 | personInterpretation.put(Tuple.of(2), true); | ||
80 | |||
81 | assertResults(Map.of( | ||
82 | Tuple.of(0), true, | ||
83 | Tuple.of(1), true, | ||
84 | Tuple.of(2), false, | ||
85 | Tuple.of(3), false | ||
86 | ), predicateResultSet); | ||
87 | assertTrue(queryEngine.hasPendingChanges()); | ||
88 | |||
89 | queryEngine.flushChanges(); | ||
90 | assertResults(Map.of( | ||
91 | Tuple.of(0), true, | ||
92 | Tuple.of(1), false, | ||
93 | Tuple.of(2), true, | ||
94 | Tuple.of(3), false | ||
95 | ), predicateResultSet); | ||
96 | assertFalse(queryEngine.hasPendingChanges()); | ||
97 | } | ||
98 | |||
99 | @Test | ||
100 | void localSearchTest() { | ||
101 | var store = ModelStore.builder() | ||
102 | .symbols(person) | ||
103 | .with(QueryInterpreterAdapter.builder() | ||
104 | .defaultHint(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) | ||
105 | .queries(predicate)) | ||
106 | .build(); | ||
107 | |||
108 | var model = store.createEmptyModel(); | ||
109 | var personInterpretation = model.getInterpretation(person); | ||
110 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
111 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
112 | |||
113 | assertResults(Map.of( | ||
114 | Tuple.of(0), false, | ||
115 | Tuple.of(1), false, | ||
116 | Tuple.of(2), false, | ||
117 | Tuple.of(3), false | ||
118 | ), predicateResultSet); | ||
119 | assertFalse(queryEngine.hasPendingChanges()); | ||
120 | |||
121 | personInterpretation.put(Tuple.of(0), true); | ||
122 | personInterpretation.put(Tuple.of(1), true); | ||
123 | |||
124 | assertResults(Map.of( | ||
125 | Tuple.of(0), true, | ||
126 | Tuple.of(1), true, | ||
127 | Tuple.of(2), false, | ||
128 | Tuple.of(3), false | ||
129 | ), predicateResultSet); | ||
130 | assertFalse(queryEngine.hasPendingChanges()); | ||
131 | |||
132 | personInterpretation.put(Tuple.of(1), false); | ||
133 | personInterpretation.put(Tuple.of(2), true); | ||
134 | |||
135 | assertResults(Map.of( | ||
136 | Tuple.of(0), true, | ||
137 | Tuple.of(1), false, | ||
138 | Tuple.of(2), true, | ||
139 | Tuple.of(3), false | ||
140 | ), predicateResultSet); | ||
141 | assertFalse(queryEngine.hasPendingChanges()); | ||
142 | } | ||
143 | |||
144 | @Test | ||
145 | void unrelatedChangesTest() { | ||
146 | var asset = Symbol.of("Asset", 1); | ||
147 | |||
148 | var store = ModelStore.builder() | ||
149 | .symbols(person, asset) | ||
150 | .with(QueryInterpreterAdapter.builder() | ||
151 | .queries(predicate)) | ||
152 | .build(); | ||
153 | |||
154 | var model = store.createEmptyModel(); | ||
155 | var personInterpretation = model.getInterpretation(person); | ||
156 | var assetInterpretation = model.getInterpretation(asset); | ||
157 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
158 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
159 | |||
160 | assertFalse(queryEngine.hasPendingChanges()); | ||
161 | |||
162 | personInterpretation.put(Tuple.of(0), true); | ||
163 | personInterpretation.put(Tuple.of(1), true); | ||
164 | |||
165 | assetInterpretation.put(Tuple.of(1), true); | ||
166 | assetInterpretation.put(Tuple.of(2), true); | ||
167 | |||
168 | assertResults(Map.of( | ||
169 | Tuple.of(0), false, | ||
170 | Tuple.of(1), false, | ||
171 | Tuple.of(2), false, | ||
172 | Tuple.of(3), false, | ||
173 | Tuple.of(4), false | ||
174 | ), predicateResultSet); | ||
175 | assertTrue(queryEngine.hasPendingChanges()); | ||
176 | |||
177 | queryEngine.flushChanges(); | ||
178 | assertResults(Map.of( | ||
179 | Tuple.of(0), true, | ||
180 | Tuple.of(1), true, | ||
181 | Tuple.of(2), false, | ||
182 | Tuple.of(3), false, | ||
183 | Tuple.of(4), false | ||
184 | ), predicateResultSet); | ||
185 | assertFalse(queryEngine.hasPendingChanges()); | ||
186 | |||
187 | assetInterpretation.put(Tuple.of(3), true); | ||
188 | assertFalse(queryEngine.hasPendingChanges()); | ||
189 | |||
190 | assertResults(Map.of( | ||
191 | Tuple.of(0), true, | ||
192 | Tuple.of(1), true, | ||
193 | Tuple.of(2), false, | ||
194 | Tuple.of(3), false, | ||
195 | Tuple.of(4), false | ||
196 | ), predicateResultSet); | ||
197 | |||
198 | queryEngine.flushChanges(); | ||
199 | assertResults(Map.of( | ||
200 | Tuple.of(0), true, | ||
201 | Tuple.of(1), true, | ||
202 | Tuple.of(2), false, | ||
203 | Tuple.of(3), false, | ||
204 | Tuple.of(4), false | ||
205 | ), predicateResultSet); | ||
206 | assertFalse(queryEngine.hasPendingChanges()); | ||
207 | } | ||
208 | |||
209 | @Test | ||
210 | void tupleChangingChangeTest() { | ||
211 | var query = Query.of("TypeConstraint", Integer.class, (builder, p1, output) -> builder.clause( | ||
212 | personView.call(p1), | ||
213 | ageView.call(p1, output) | ||
214 | )); | ||
215 | |||
216 | var store = ModelStore.builder() | ||
217 | .symbols(person, age) | ||
218 | .with(QueryInterpreterAdapter.builder() | ||
219 | .queries(query)) | ||
220 | .build(); | ||
221 | |||
222 | var model = store.createEmptyModel(); | ||
223 | var personInterpretation = model.getInterpretation(person); | ||
224 | var ageInterpretation = model.getInterpretation(age); | ||
225 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
226 | var queryResultSet = queryEngine.getResultSet(query); | ||
227 | |||
228 | personInterpretation.put(Tuple.of(0), true); | ||
229 | |||
230 | ageInterpretation.put(Tuple.of(0), 24); | ||
231 | |||
232 | queryEngine.flushChanges(); | ||
233 | assertResults(Map.of(Tuple.of(0), 24), queryResultSet); | ||
234 | |||
235 | ageInterpretation.put(Tuple.of(0), 25); | ||
236 | |||
237 | queryEngine.flushChanges(); | ||
238 | assertResults(Map.of(Tuple.of(0), 25), queryResultSet); | ||
239 | |||
240 | ageInterpretation.put(Tuple.of(0), null); | ||
241 | |||
242 | queryEngine.flushChanges(); | ||
243 | assertNullableResults(Map.of(Tuple.of(0), Optional.empty()), queryResultSet); | ||
244 | } | ||
245 | |||
246 | @Test | ||
247 | void tuplePreservingUnchangedTest() { | ||
248 | var adultView = new FilteredView<>(age, "adult", n -> n != null && n >= 18); | ||
249 | |||
250 | var query = Query.of("TypeConstraint", (builder, p1) -> builder.clause( | ||
251 | personView.call(p1), | ||
252 | adultView.call(p1) | ||
253 | )); | ||
254 | |||
255 | var store = ModelStore.builder() | ||
256 | .symbols(person, age) | ||
257 | .with(QueryInterpreterAdapter.builder() | ||
258 | .queries(query)) | ||
259 | .build(); | ||
260 | |||
261 | var model = store.createEmptyModel(); | ||
262 | var personInterpretation = model.getInterpretation(person); | ||
263 | var ageInterpretation = model.getInterpretation(age); | ||
264 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
265 | var queryResultSet = queryEngine.getResultSet(query); | ||
266 | |||
267 | personInterpretation.put(Tuple.of(0), true); | ||
268 | |||
269 | ageInterpretation.put(Tuple.of(0), 24); | ||
270 | |||
271 | queryEngine.flushChanges(); | ||
272 | assertResults(Map.of(Tuple.of(0), true), queryResultSet); | ||
273 | |||
274 | ageInterpretation.put(Tuple.of(0), 25); | ||
275 | |||
276 | queryEngine.flushChanges(); | ||
277 | assertResults(Map.of(Tuple.of(0), true), queryResultSet); | ||
278 | |||
279 | ageInterpretation.put(Tuple.of(0), 17); | ||
280 | |||
281 | queryEngine.flushChanges(); | ||
282 | assertResults(Map.of(Tuple.of(0), false), queryResultSet); | ||
283 | } | ||
284 | |||
285 | @Test | ||
286 | void commitAfterFlushTest() { | ||
287 | var store = ModelStore.builder() | ||
288 | .symbols(person) | ||
289 | .with(QueryInterpreterAdapter.builder() | ||
290 | .queries(predicate)) | ||
291 | .build(); | ||
292 | |||
293 | var model = store.createEmptyModel(); | ||
294 | var personInterpretation = model.getInterpretation(person); | ||
295 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
296 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
297 | |||
298 | personInterpretation.put(Tuple.of(0), true); | ||
299 | personInterpretation.put(Tuple.of(1), true); | ||
300 | |||
301 | queryEngine.flushChanges(); | ||
302 | assertResults(Map.of( | ||
303 | Tuple.of(0), true, | ||
304 | Tuple.of(1), true, | ||
305 | Tuple.of(2), false, | ||
306 | Tuple.of(3), false | ||
307 | ), predicateResultSet); | ||
308 | |||
309 | var state1 = model.commit(); | ||
310 | |||
311 | personInterpretation.put(Tuple.of(1), false); | ||
312 | personInterpretation.put(Tuple.of(2), true); | ||
313 | |||
314 | queryEngine.flushChanges(); | ||
315 | assertResults(Map.of( | ||
316 | Tuple.of(0), true, | ||
317 | Tuple.of(1), false, | ||
318 | Tuple.of(2), true, | ||
319 | Tuple.of(3), false | ||
320 | ), predicateResultSet); | ||
321 | |||
322 | model.restore(state1); | ||
323 | |||
324 | assertFalse(queryEngine.hasPendingChanges()); | ||
325 | assertResults(Map.of( | ||
326 | Tuple.of(0), true, | ||
327 | Tuple.of(1), true, | ||
328 | Tuple.of(2), false, | ||
329 | Tuple.of(3), false | ||
330 | ), predicateResultSet); | ||
331 | } | ||
332 | |||
333 | @Test | ||
334 | void commitWithoutFlushTest() { | ||
335 | var store = ModelStore.builder() | ||
336 | .symbols(person) | ||
337 | .with(QueryInterpreterAdapter.builder() | ||
338 | .queries(predicate)) | ||
339 | .build(); | ||
340 | |||
341 | var model = store.createEmptyModel(); | ||
342 | var personInterpretation = model.getInterpretation(person); | ||
343 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
344 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
345 | |||
346 | personInterpretation.put(Tuple.of(0), true); | ||
347 | personInterpretation.put(Tuple.of(1), true); | ||
348 | |||
349 | assertResults(Map.of(), predicateResultSet); | ||
350 | assertTrue(queryEngine.hasPendingChanges()); | ||
351 | |||
352 | var state1 = model.commit(); | ||
353 | |||
354 | personInterpretation.put(Tuple.of(1), false); | ||
355 | personInterpretation.put(Tuple.of(2), true); | ||
356 | |||
357 | assertResults(Map.of(), predicateResultSet); | ||
358 | assertTrue(queryEngine.hasPendingChanges()); | ||
359 | |||
360 | model.restore(state1); | ||
361 | |||
362 | assertResults(Map.of( | ||
363 | Tuple.of(0), true, | ||
364 | Tuple.of(1), true, | ||
365 | Tuple.of(2), false, | ||
366 | Tuple.of(3), false | ||
367 | ), predicateResultSet); | ||
368 | assertFalse(queryEngine.hasPendingChanges()); | ||
369 | } | ||
370 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java new file mode 100644 index 00000000..edbd9aff --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java | |||
@@ -0,0 +1,261 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 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.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.literal.Connectivity; | ||
13 | import tools.refinery.store.query.literal.RepresentativeElectionLiteral; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
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 | |||
22 | import static org.hamcrest.MatcherAssert.assertThat; | ||
23 | import static org.hamcrest.Matchers.is; | ||
24 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
25 | |||
26 | class StronglyConnectedComponentsTest { | ||
27 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
28 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
29 | |||
30 | @Test | ||
31 | void symbolViewTest() { | ||
32 | var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder | ||
33 | .clause(v1 -> List.of( | ||
34 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), | ||
35 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) | ||
36 | ))); | ||
37 | |||
38 | var store = ModelStore.builder() | ||
39 | .symbols(friend) | ||
40 | .with(QueryInterpreterAdapter.builder() | ||
41 | .queries(query)) | ||
42 | .build(); | ||
43 | |||
44 | var model = store.createEmptyModel(); | ||
45 | var friendInterpretation = model.getInterpretation(friend); | ||
46 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
47 | var resultSet = queryEngine.getResultSet(query); | ||
48 | |||
49 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
50 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
51 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
52 | queryEngine.flushChanges(); | ||
53 | |||
54 | assertResults(Map.of( | ||
55 | Tuple.of(0, 0), true, | ||
56 | Tuple.of(0, 1), true, | ||
57 | Tuple.of(1, 0), true, | ||
58 | Tuple.of(1, 1), true, | ||
59 | Tuple.of(2, 2), true | ||
60 | ), resultSet); | ||
61 | } | ||
62 | |||
63 | @Test | ||
64 | void symbolViewInsertTest() { | ||
65 | var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder | ||
66 | .clause(v1 -> List.of( | ||
67 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), | ||
68 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) | ||
69 | ))); | ||
70 | |||
71 | var store = ModelStore.builder() | ||
72 | .symbols(friend) | ||
73 | .with(QueryInterpreterAdapter.builder() | ||
74 | .queries(query)) | ||
75 | .build(); | ||
76 | |||
77 | var model = store.createEmptyModel(); | ||
78 | var friendInterpretation = model.getInterpretation(friend); | ||
79 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
80 | var resultSet = queryEngine.getResultSet(query); | ||
81 | |||
82 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
83 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
84 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
85 | queryEngine.flushChanges(); | ||
86 | |||
87 | friendInterpretation.put(Tuple.of(2, 0), true); | ||
88 | friendInterpretation.put(Tuple.of(0, 3), true); | ||
89 | queryEngine.flushChanges(); | ||
90 | |||
91 | assertResults(Map.of( | ||
92 | Tuple.of(0, 0), true, | ||
93 | Tuple.of(0, 1), true, | ||
94 | Tuple.of(0, 2), true, | ||
95 | Tuple.of(1, 1), true, | ||
96 | Tuple.of(1, 0), true, | ||
97 | Tuple.of(1, 2), true, | ||
98 | Tuple.of(2, 0), true, | ||
99 | Tuple.of(2, 1), true, | ||
100 | Tuple.of(2, 2), true, | ||
101 | Tuple.of(3, 3), true | ||
102 | ), resultSet); | ||
103 | } | ||
104 | |||
105 | @Test | ||
106 | void symbolViewDeleteTest() { | ||
107 | var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder | ||
108 | .clause(v1 -> List.of( | ||
109 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), | ||
110 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) | ||
111 | ))); | ||
112 | |||
113 | var store = ModelStore.builder() | ||
114 | .symbols(friend) | ||
115 | .with(QueryInterpreterAdapter.builder() | ||
116 | .queries(query)) | ||
117 | .build(); | ||
118 | |||
119 | var model = store.createEmptyModel(); | ||
120 | var friendInterpretation = model.getInterpretation(friend); | ||
121 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
122 | var resultSet = queryEngine.getResultSet(query); | ||
123 | |||
124 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
125 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
126 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
127 | queryEngine.flushChanges(); | ||
128 | |||
129 | friendInterpretation.put(Tuple.of(1, 0), false); | ||
130 | friendInterpretation.put(Tuple.of(1, 2), false); | ||
131 | queryEngine.flushChanges(); | ||
132 | |||
133 | assertResults(Map.of( | ||
134 | Tuple.of(0, 0), true, | ||
135 | Tuple.of(1, 1), true | ||
136 | ), resultSet); | ||
137 | } | ||
138 | |||
139 | @Test | ||
140 | void diagonalSymbolViewTest() { | ||
141 | var person = Symbol.of("Person", 1); | ||
142 | var personView = new KeyOnlyView<>(person); | ||
143 | |||
144 | var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder | ||
145 | .clause( | ||
146 | personView.call(p1), | ||
147 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, p1) | ||
148 | )); | ||
149 | |||
150 | var store = ModelStore.builder() | ||
151 | .symbols(person, friend) | ||
152 | .with(QueryInterpreterAdapter.builder() | ||
153 | .queries(query)) | ||
154 | .build(); | ||
155 | |||
156 | var model = store.createEmptyModel(); | ||
157 | var personInterpretation = model.getInterpretation(person); | ||
158 | var friendInterpretation = model.getInterpretation(friend); | ||
159 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
160 | var resultSet = queryEngine.getResultSet(query); | ||
161 | |||
162 | personInterpretation.put(Tuple.of(0), true); | ||
163 | personInterpretation.put(Tuple.of(1), true); | ||
164 | personInterpretation.put(Tuple.of(2), true); | ||
165 | |||
166 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
167 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
168 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
169 | queryEngine.flushChanges(); | ||
170 | |||
171 | assertThat(resultSet.size(), is(2)); | ||
172 | assertThat(resultSet.get(Tuple.of(2)), is(true)); | ||
173 | } | ||
174 | |||
175 | @Test | ||
176 | void diagonalDnfTest() { | ||
177 | var person = Symbol.of("Person", 1); | ||
178 | var personView = new KeyOnlyView<>(person); | ||
179 | |||
180 | var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder | ||
181 | .clause( | ||
182 | personView.call(p1), | ||
183 | personView.call(p2), | ||
184 | friendView.call(p1, p2) | ||
185 | )) | ||
186 | .getDnf(); | ||
187 | var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder | ||
188 | .clause( | ||
189 | personView.call(p1), | ||
190 | new RepresentativeElectionLiteral(Connectivity.STRONG, subQuery, p1, p1) | ||
191 | )); | ||
192 | |||
193 | var store = ModelStore.builder() | ||
194 | .symbols(person, friend) | ||
195 | .with(QueryInterpreterAdapter.builder() | ||
196 | .queries(query)) | ||
197 | .build(); | ||
198 | |||
199 | var model = store.createEmptyModel(); | ||
200 | var personInterpretation = model.getInterpretation(person); | ||
201 | var friendInterpretation = model.getInterpretation(friend); | ||
202 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
203 | var resultSet = queryEngine.getResultSet(query); | ||
204 | |||
205 | personInterpretation.put(Tuple.of(0), true); | ||
206 | personInterpretation.put(Tuple.of(1), true); | ||
207 | personInterpretation.put(Tuple.of(2), true); | ||
208 | |||
209 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
210 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
211 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
212 | queryEngine.flushChanges(); | ||
213 | |||
214 | assertThat(resultSet.size(), is(2)); | ||
215 | assertThat(resultSet.get(Tuple.of(2)), is(true)); | ||
216 | } | ||
217 | |||
218 | @Test | ||
219 | void loopTest() { | ||
220 | var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder | ||
221 | .clause(v1 -> List.of( | ||
222 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), | ||
223 | new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) | ||
224 | ))); | ||
225 | |||
226 | var store = ModelStore.builder() | ||
227 | .symbols(friend) | ||
228 | .with(QueryInterpreterAdapter.builder() | ||
229 | .queries(query)) | ||
230 | .build(); | ||
231 | |||
232 | var model = store.createEmptyModel(); | ||
233 | var friendInterpretation = model.getInterpretation(friend); | ||
234 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
235 | var resultSet = queryEngine.getResultSet(query); | ||
236 | |||
237 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
238 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
239 | friendInterpretation.put(Tuple.of(2, 3), true); | ||
240 | friendInterpretation.put(Tuple.of(3, 0), true); | ||
241 | friendInterpretation.put(Tuple.of(3, 4), true); | ||
242 | queryEngine.flushChanges(); | ||
243 | |||
244 | assertThat(resultSet.get(Tuple.of(0, 1)), is(true)); | ||
245 | assertThat(resultSet.get(Tuple.of(1, 2)), is(true)); | ||
246 | assertThat(resultSet.get(Tuple.of(2, 3)), is(true)); | ||
247 | assertThat(resultSet.get(Tuple.of(3, 0)), is(true)); | ||
248 | assertThat(resultSet.get(Tuple.of(3, 4)), is(false)); | ||
249 | |||
250 | friendInterpretation.put(Tuple.of(2, 3), false); | ||
251 | queryEngine.flushChanges(); | ||
252 | |||
253 | assertThat(resultSet.get(Tuple.of(0, 1)), is(false)); | ||
254 | assertThat(resultSet.get(Tuple.of(0, 2)), is(false)); | ||
255 | assertThat(resultSet.get(Tuple.of(0, 3)), is(false)); | ||
256 | assertThat(resultSet.get(Tuple.of(1, 2)), is(false)); | ||
257 | assertThat(resultSet.get(Tuple.of(2, 3)), is(false)); | ||
258 | assertThat(resultSet.get(Tuple.of(3, 0)), is(false)); | ||
259 | assertThat(resultSet.get(Tuple.of(3, 4)), is(false)); | ||
260 | } | ||
261 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java new file mode 100644 index 00000000..3fc85480 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java | |||
@@ -0,0 +1,188 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 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.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.literal.Connectivity; | ||
13 | import tools.refinery.store.query.literal.RepresentativeElectionLiteral; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
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 | |||
22 | import static org.hamcrest.MatcherAssert.assertThat; | ||
23 | import static org.hamcrest.Matchers.is; | ||
24 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
25 | |||
26 | class WeaklyConnectedComponentsTest { | ||
27 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
28 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
29 | |||
30 | @Test | ||
31 | void symbolViewTest() { | ||
32 | var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder | ||
33 | .clause(v1 -> List.of( | ||
34 | new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, v1), | ||
35 | new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p2, v1) | ||
36 | ))); | ||
37 | |||
38 | var store = ModelStore.builder() | ||
39 | .symbols(friend) | ||
40 | .with(QueryInterpreterAdapter.builder() | ||
41 | .queries(query)) | ||
42 | .build(); | ||
43 | |||
44 | var model = store.createEmptyModel(); | ||
45 | var friendInterpretation = model.getInterpretation(friend); | ||
46 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
47 | var resultSet = queryEngine.getResultSet(query); | ||
48 | |||
49 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
50 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
51 | friendInterpretation.put(Tuple.of(2, 3), true); | ||
52 | queryEngine.flushChanges(); | ||
53 | |||
54 | assertResults(Map.of( | ||
55 | Tuple.of(0, 0), true, | ||
56 | Tuple.of(0, 1), true, | ||
57 | Tuple.of(1, 0), true, | ||
58 | Tuple.of(1, 1), true, | ||
59 | Tuple.of(2, 2), true, | ||
60 | Tuple.of(2, 3), true, | ||
61 | Tuple.of(3, 2), true, | ||
62 | Tuple.of(3, 3), true | ||
63 | ), resultSet); | ||
64 | } | ||
65 | |||
66 | @Test | ||
67 | void symbolViewUpdateTest() { | ||
68 | var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder | ||
69 | .clause(v1 -> List.of( | ||
70 | new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, v1), | ||
71 | new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p2, v1) | ||
72 | ))); | ||
73 | |||
74 | var store = ModelStore.builder() | ||
75 | .symbols(friend) | ||
76 | .with(QueryInterpreterAdapter.builder() | ||
77 | .queries(query)) | ||
78 | .build(); | ||
79 | |||
80 | var model = store.createEmptyModel(); | ||
81 | var friendInterpretation = model.getInterpretation(friend); | ||
82 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
83 | var resultSet = queryEngine.getResultSet(query); | ||
84 | |||
85 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
86 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
87 | friendInterpretation.put(Tuple.of(2, 3), true); | ||
88 | queryEngine.flushChanges(); | ||
89 | |||
90 | friendInterpretation.put(Tuple.of(2, 3), false); | ||
91 | friendInterpretation.put(Tuple.of(1, 0), false); | ||
92 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
93 | queryEngine.flushChanges(); | ||
94 | |||
95 | assertResults(Map.of( | ||
96 | Tuple.of(0, 0), true, | ||
97 | Tuple.of(0, 1), true, | ||
98 | Tuple.of(0, 2), true, | ||
99 | Tuple.of(1, 0), true, | ||
100 | Tuple.of(1, 1), true, | ||
101 | Tuple.of(1, 2), true, | ||
102 | Tuple.of(2, 0), true, | ||
103 | Tuple.of(2, 1), true, | ||
104 | Tuple.of(2, 2), true | ||
105 | ), resultSet); | ||
106 | } | ||
107 | |||
108 | @Test | ||
109 | void diagonalSymbolViewTest() { | ||
110 | var person = Symbol.of("Person", 1); | ||
111 | var personView = new KeyOnlyView<>(person); | ||
112 | |||
113 | var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder | ||
114 | .clause( | ||
115 | personView.call(p1), | ||
116 | new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, p1) | ||
117 | )); | ||
118 | |||
119 | var store = ModelStore.builder() | ||
120 | .symbols(person, friend) | ||
121 | .with(QueryInterpreterAdapter.builder() | ||
122 | .queries(query)) | ||
123 | .build(); | ||
124 | |||
125 | var model = store.createEmptyModel(); | ||
126 | var personInterpretation = model.getInterpretation(person); | ||
127 | var friendInterpretation = model.getInterpretation(friend); | ||
128 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
129 | var resultSet = queryEngine.getResultSet(query); | ||
130 | |||
131 | personInterpretation.put(Tuple.of(0), true); | ||
132 | personInterpretation.put(Tuple.of(1), true); | ||
133 | personInterpretation.put(Tuple.of(2), true); | ||
134 | personInterpretation.put(Tuple.of(3), true); | ||
135 | |||
136 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
137 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
138 | friendInterpretation.put(Tuple.of(2, 3), true); | ||
139 | queryEngine.flushChanges(); | ||
140 | |||
141 | assertThat(resultSet.size(), is(2)); | ||
142 | assertThat(resultSet.get(Tuple.of(2)), is(true)); | ||
143 | } | ||
144 | |||
145 | @Test | ||
146 | void diagonalDnfTest() { | ||
147 | var person = Symbol.of("Person", 1); | ||
148 | var personView = new KeyOnlyView<>(person); | ||
149 | |||
150 | var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder | ||
151 | .clause( | ||
152 | personView.call(p1), | ||
153 | personView.call(p2), | ||
154 | friendView.call(p1, p2) | ||
155 | )) | ||
156 | .getDnf(); | ||
157 | var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder | ||
158 | .clause( | ||
159 | personView.call(p1), | ||
160 | new RepresentativeElectionLiteral(Connectivity.WEAK, subQuery, p1, p1) | ||
161 | )); | ||
162 | |||
163 | var store = ModelStore.builder() | ||
164 | .symbols(person, friend) | ||
165 | .with(QueryInterpreterAdapter.builder() | ||
166 | .queries(query)) | ||
167 | .build(); | ||
168 | |||
169 | var model = store.createEmptyModel(); | ||
170 | var personInterpretation = model.getInterpretation(person); | ||
171 | var friendInterpretation = model.getInterpretation(friend); | ||
172 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
173 | var resultSet = queryEngine.getResultSet(query); | ||
174 | |||
175 | personInterpretation.put(Tuple.of(0), true); | ||
176 | personInterpretation.put(Tuple.of(1), true); | ||
177 | personInterpretation.put(Tuple.of(2), true); | ||
178 | personInterpretation.put(Tuple.of(3), true); | ||
179 | |||
180 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
181 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
182 | friendInterpretation.put(Tuple.of(2, 3), true); | ||
183 | queryEngine.flushChanges(); | ||
184 | |||
185 | assertThat(resultSet.size(), is(2)); | ||
186 | assertThat(resultSet.get(Tuple.of(2)), is(true)); | ||
187 | } | ||
188 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java new file mode 100644 index 00000000..1c8044ea --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java | |||
@@ -0,0 +1,239 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.tuple.*; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | import tools.refinery.store.tuple.*; | ||
12 | |||
13 | import java.util.List; | ||
14 | |||
15 | import static org.hamcrest.MatcherAssert.assertThat; | ||
16 | import static org.hamcrest.Matchers.*; | ||
17 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
18 | |||
19 | class MatcherUtilsTest { | ||
20 | @Test | ||
21 | void toViatra0Test() { | ||
22 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of()); | ||
23 | assertThat(viatraTuple.getSize(), is(0)); | ||
24 | assertThat(viatraTuple, instanceOf(FlatTuple0.class)); | ||
25 | } | ||
26 | |||
27 | @Test | ||
28 | void toViatra1Test() { | ||
29 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2)); | ||
30 | assertThat(viatraTuple.getSize(), is(1)); | ||
31 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
32 | assertThat(viatraTuple, instanceOf(FlatTuple1.class)); | ||
33 | } | ||
34 | |||
35 | @Test | ||
36 | void toViatra2Test() { | ||
37 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3)); | ||
38 | assertThat(viatraTuple.getSize(), is(2)); | ||
39 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
40 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
41 | assertThat(viatraTuple, instanceOf(FlatTuple2.class)); | ||
42 | } | ||
43 | |||
44 | @Test | ||
45 | void toViatra3Test() { | ||
46 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5)); | ||
47 | assertThat(viatraTuple.getSize(), is(3)); | ||
48 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
49 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
50 | assertThat(viatraTuple.get(2), is(Tuple.of(5))); | ||
51 | assertThat(viatraTuple, instanceOf(FlatTuple3.class)); | ||
52 | } | ||
53 | |||
54 | @Test | ||
55 | void toViatra4Test() { | ||
56 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8)); | ||
57 | assertThat(viatraTuple.getSize(), is(4)); | ||
58 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
59 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
60 | assertThat(viatraTuple.get(2), is(Tuple.of(5))); | ||
61 | assertThat(viatraTuple.get(3), is(Tuple.of(8))); | ||
62 | assertThat(viatraTuple, instanceOf(FlatTuple4.class)); | ||
63 | } | ||
64 | |||
65 | @Test | ||
66 | void toViatra5Test() { | ||
67 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8, 13)); | ||
68 | assertThat(viatraTuple.getSize(), is(5)); | ||
69 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
70 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
71 | assertThat(viatraTuple.get(2), is(Tuple.of(5))); | ||
72 | assertThat(viatraTuple.get(3), is(Tuple.of(8))); | ||
73 | assertThat(viatraTuple.get(4), is(Tuple.of(13))); | ||
74 | assertThat(viatraTuple, instanceOf(FlatTuple.class)); | ||
75 | } | ||
76 | |||
77 | @Test | ||
78 | void toRefinery0Test() { | ||
79 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf()); | ||
80 | assertThat(refineryTuple.getSize(), is(0)); | ||
81 | assertThat(refineryTuple, instanceOf(Tuple0.class)); | ||
82 | } | ||
83 | |||
84 | @Test | ||
85 | void toRefinery1Test() { | ||
86 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2))); | ||
87 | assertThat(refineryTuple.getSize(), is(1)); | ||
88 | assertThat(refineryTuple.get(0), is(2)); | ||
89 | assertThat(refineryTuple, instanceOf(Tuple1.class)); | ||
90 | } | ||
91 | |||
92 | @Test | ||
93 | void toRefinery2Test() { | ||
94 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3))); | ||
95 | assertThat(refineryTuple.getSize(), is(2)); | ||
96 | assertThat(refineryTuple.get(0), is(2)); | ||
97 | assertThat(refineryTuple.get(1), is(3)); | ||
98 | assertThat(refineryTuple, instanceOf(Tuple2.class)); | ||
99 | } | ||
100 | |||
101 | @Test | ||
102 | void toRefinery3Test() { | ||
103 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5))); | ||
104 | assertThat(refineryTuple.getSize(), is(3)); | ||
105 | assertThat(refineryTuple.get(0), is(2)); | ||
106 | assertThat(refineryTuple.get(1), is(3)); | ||
107 | assertThat(refineryTuple.get(2), is(5)); | ||
108 | assertThat(refineryTuple, instanceOf(Tuple3.class)); | ||
109 | } | ||
110 | |||
111 | @Test | ||
112 | void toRefinery4Test() { | ||
113 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
114 | Tuple.of(8))); | ||
115 | assertThat(refineryTuple.getSize(), is(4)); | ||
116 | assertThat(refineryTuple.get(0), is(2)); | ||
117 | assertThat(refineryTuple.get(1), is(3)); | ||
118 | assertThat(refineryTuple.get(2), is(5)); | ||
119 | assertThat(refineryTuple.get(3), is(8)); | ||
120 | assertThat(refineryTuple, instanceOf(Tuple4.class)); | ||
121 | } | ||
122 | |||
123 | @Test | ||
124 | void toRefinery5Test() { | ||
125 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
126 | Tuple.of(8), Tuple.of(13))); | ||
127 | assertThat(refineryTuple.getSize(), is(5)); | ||
128 | assertThat(refineryTuple.get(0), is(2)); | ||
129 | assertThat(refineryTuple.get(1), is(3)); | ||
130 | assertThat(refineryTuple.get(2), is(5)); | ||
131 | assertThat(refineryTuple.get(3), is(8)); | ||
132 | assertThat(refineryTuple.get(4), is(13)); | ||
133 | assertThat(refineryTuple, instanceOf(TupleN.class)); | ||
134 | } | ||
135 | |||
136 | @Test | ||
137 | void toRefineryInvalidValueTest() { | ||
138 | var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98); | ||
139 | assertThrows(IllegalArgumentException.class, () -> MatcherUtils.toRefineryTuple(viatraTuple)); | ||
140 | } | ||
141 | |||
142 | @Test | ||
143 | void keyToRefinery0Test() { | ||
144 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(-99)); | ||
145 | assertThat(refineryTuple.getSize(), is(0)); | ||
146 | assertThat(refineryTuple, instanceOf(Tuple0.class)); | ||
147 | } | ||
148 | |||
149 | @Test | ||
150 | void keyToRefinery1Test() { | ||
151 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), -99)); | ||
152 | assertThat(refineryTuple.getSize(), is(1)); | ||
153 | assertThat(refineryTuple.get(0), is(2)); | ||
154 | assertThat(refineryTuple, instanceOf(Tuple1.class)); | ||
155 | } | ||
156 | |||
157 | @Test | ||
158 | void keyToRefinery2Test() { | ||
159 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), -99)); | ||
160 | assertThat(refineryTuple.getSize(), is(2)); | ||
161 | assertThat(refineryTuple.get(0), is(2)); | ||
162 | assertThat(refineryTuple.get(1), is(3)); | ||
163 | assertThat(refineryTuple, instanceOf(Tuple2.class)); | ||
164 | } | ||
165 | |||
166 | @Test | ||
167 | void keyToRefinery3Test() { | ||
168 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
169 | -99)); | ||
170 | assertThat(refineryTuple.getSize(), is(3)); | ||
171 | assertThat(refineryTuple.get(0), is(2)); | ||
172 | assertThat(refineryTuple.get(1), is(3)); | ||
173 | assertThat(refineryTuple.get(2), is(5)); | ||
174 | assertThat(refineryTuple, instanceOf(Tuple3.class)); | ||
175 | } | ||
176 | |||
177 | @Test | ||
178 | void keyToRefinery4Test() { | ||
179 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
180 | Tuple.of(8), -99)); | ||
181 | assertThat(refineryTuple.getSize(), is(4)); | ||
182 | assertThat(refineryTuple.get(0), is(2)); | ||
183 | assertThat(refineryTuple.get(1), is(3)); | ||
184 | assertThat(refineryTuple.get(2), is(5)); | ||
185 | assertThat(refineryTuple.get(3), is(8)); | ||
186 | assertThat(refineryTuple, instanceOf(Tuple4.class)); | ||
187 | } | ||
188 | |||
189 | @Test | ||
190 | void keyToRefinery5Test() { | ||
191 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
192 | Tuple.of(8), Tuple.of(13), -99)); | ||
193 | assertThat(refineryTuple.getSize(), is(5)); | ||
194 | assertThat(refineryTuple.get(0), is(2)); | ||
195 | assertThat(refineryTuple.get(1), is(3)); | ||
196 | assertThat(refineryTuple.get(2), is(5)); | ||
197 | assertThat(refineryTuple.get(3), is(8)); | ||
198 | assertThat(refineryTuple.get(4), is(13)); | ||
199 | assertThat(refineryTuple, instanceOf(TupleN.class)); | ||
200 | } | ||
201 | |||
202 | @Test | ||
203 | void keyToRefineryTooShortTest() { | ||
204 | var viatraTuple = Tuples.flatTupleOf(); | ||
205 | assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple)); | ||
206 | } | ||
207 | |||
208 | @Test | ||
209 | void keyToRefineryInvalidValueTest() { | ||
210 | var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98, -99); | ||
211 | assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple)); | ||
212 | } | ||
213 | |||
214 | @Test | ||
215 | void getSingleValueTest() { | ||
216 | var value = MatcherUtils.getSingleValue(List.of(Tuples.flatTupleOf(Tuple.of(2), -99))); | ||
217 | assertThat(value, is(-99)); | ||
218 | } | ||
219 | |||
220 | // Static analysis accurately determines that the result is always {@code null}, but we check anyways. | ||
221 | @SuppressWarnings("ConstantValue") | ||
222 | @Test | ||
223 | void getSingleValueNullTest() { | ||
224 | var value = MatcherUtils.getSingleValue((Iterable<? extends ITuple>) null); | ||
225 | assertThat(value, nullValue()); | ||
226 | } | ||
227 | |||
228 | @Test | ||
229 | void getSingleValueEmptyTest() { | ||
230 | var value = MatcherUtils.getSingleValue(List.of()); | ||
231 | assertThat(value, nullValue()); | ||
232 | } | ||
233 | |||
234 | @Test | ||
235 | void getSingleValueMultipleTest() { | ||
236 | var viatraTuples = List.of(Tuples.flatTupleOf(Tuple.of(2), -98), Tuples.flatTupleOf(Tuple.of(2), -99)); | ||
237 | assertThrows(IllegalStateException.class, () -> MatcherUtils.getSingleValue(viatraTuples)); | ||
238 | } | ||
239 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java new file mode 100644 index 00000000..c4659a98 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java | |||
@@ -0,0 +1,57 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.interpreter.tests; | ||
7 | |||
8 | import org.junit.jupiter.api.function.Executable; | ||
9 | import tools.refinery.store.query.resultset.ResultSet; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.*; | ||
13 | |||
14 | import static org.hamcrest.MatcherAssert.assertThat; | ||
15 | import static org.hamcrest.Matchers.is; | ||
16 | import static org.hamcrest.Matchers.nullValue; | ||
17 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
18 | |||
19 | public final class QueryAssertions { | ||
20 | private QueryAssertions() { | ||
21 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
22 | } | ||
23 | |||
24 | public static <T> void assertNullableResults(Map<Tuple, Optional<T>> expected, ResultSet<T> resultSet) { | ||
25 | var nullableValuesMap = new LinkedHashMap<Tuple, T>(expected.size()); | ||
26 | for (var entry : expected.entrySet()) { | ||
27 | nullableValuesMap.put(entry.getKey(), entry.getValue().orElse(null)); | ||
28 | } | ||
29 | assertResults(nullableValuesMap, resultSet); | ||
30 | } | ||
31 | |||
32 | public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) { | ||
33 | var defaultValue = resultSet.getCanonicalQuery().defaultValue(); | ||
34 | var filteredExpected = new LinkedHashMap<Tuple, T>(); | ||
35 | var executables = new ArrayList<Executable>(); | ||
36 | for (var entry : expected.entrySet()) { | ||
37 | var key = entry.getKey(); | ||
38 | var value = entry.getValue(); | ||
39 | if (!Objects.equals(value, defaultValue)) { | ||
40 | filteredExpected.put(key, value); | ||
41 | } | ||
42 | executables.add(() -> assertThat("value for key " + key,resultSet.get(key), is(value))); | ||
43 | } | ||
44 | executables.add(() -> assertThat("results size", resultSet.size(), is(filteredExpected.size()))); | ||
45 | |||
46 | var actual = new LinkedHashMap<Tuple, T>(); | ||
47 | var cursor = resultSet.getAll(); | ||
48 | while (cursor.move()) { | ||
49 | var key = cursor.getKey(); | ||
50 | var previous = actual.put(key, cursor.getValue()); | ||
51 | assertThat("duplicate value for key " + key, previous, nullValue()); | ||
52 | } | ||
53 | executables.add(() -> assertThat("results cursor", actual, is(filteredExpected))); | ||
54 | |||
55 | assertAll(executables); | ||
56 | } | ||
57 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java new file mode 100644 index 00000000..f9d5b219 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java | |||
@@ -0,0 +1,27 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.interpreter.tests; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
9 | |||
10 | /** | ||
11 | * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names. | ||
12 | */ | ||
13 | class QueryBackendHint extends QueryEvaluationHint { | ||
14 | public QueryBackendHint(BackendRequirement backendRequirementType) { | ||
15 | super(null, backendRequirementType); | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public String toString() { | ||
20 | return switch (getQueryBackendRequirementType()) { | ||
21 | case UNSPECIFIED -> "default"; | ||
22 | case DEFAULT_CACHING -> "incremental"; | ||
23 | case DEFAULT_SEARCH -> "localSearch"; | ||
24 | default -> throw new IllegalStateException("Unknown BackendRequirement"); | ||
25 | }; | ||
26 | } | ||
27 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java new file mode 100644 index 00000000..a5cc7e9c --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.interpreter.tests; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.ArgumentsSource; | ||
10 | |||
11 | import java.lang.annotation.ElementType; | ||
12 | import java.lang.annotation.Retention; | ||
13 | import java.lang.annotation.RetentionPolicy; | ||
14 | import java.lang.annotation.Target; | ||
15 | |||
16 | @ParameterizedTest(name = "backend = {0}") | ||
17 | @ArgumentsSource(QueryEvaluationHintSource.class) | ||
18 | @Target(ElementType.METHOD) | ||
19 | @Retention(RetentionPolicy.RUNTIME) | ||
20 | public @interface QueryEngineTest { | ||
21 | } | ||
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java new file mode 100644 index 00000000..6503ff2f --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java | |||
@@ -0,0 +1,24 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.interpreter.tests; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
9 | import org.junit.jupiter.api.extension.ExtensionContext; | ||
10 | import org.junit.jupiter.params.provider.Arguments; | ||
11 | import org.junit.jupiter.params.provider.ArgumentsProvider; | ||
12 | |||
13 | import java.util.stream.Stream; | ||
14 | |||
15 | public class QueryEvaluationHintSource implements ArgumentsProvider { | ||
16 | @Override | ||
17 | public Stream<? extends Arguments> provideArguments(ExtensionContext context) { | ||
18 | return Stream.of( | ||
19 | Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.UNSPECIFIED)), | ||
20 | Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_CACHING)), | ||
21 | Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) | ||
22 | ); | ||
23 | } | ||
24 | } | ||