diff options
author | Kristóf Marussy <kristof@marussy.com> | 2024-02-22 02:02:21 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2024-04-07 14:55:46 +0200 |
commit | 31465875054c847943e625d9e84bbbd52bc3695e (patch) | |
tree | 961b8831158e848244b841b0905f8dbd07ff1e39 /subprojects/store-query-interpreter/src | |
parent | feat(language): datatype declarations (diff) | |
download | refinery-31465875054c847943e625d9e84bbbd52bc3695e.tar.gz refinery-31465875054c847943e625d9e84bbbd52bc3695e.tar.zst refinery-31465875054c847943e625d9e84bbbd52bc3695e.zip |
feat(query): left join for data variables
Diffstat (limited to 'subprojects/store-query-interpreter/src')
3 files changed, 187 insertions, 44 deletions
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java index 24205cf4..4d30f998 100644 --- a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java +++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java | |||
@@ -1,17 +1,31 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.interpreter.internal.pquery; | 6 | package tools.refinery.store.query.interpreter.internal.pquery; |
7 | 7 | ||
8 | import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; | ||
9 | import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
10 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
11 | import tools.refinery.interpreter.matchers.psystem.PBody; | ||
12 | import tools.refinery.interpreter.matchers.psystem.PVariable; | ||
13 | import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; | ||
14 | import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
15 | import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation; | ||
8 | import tools.refinery.interpreter.matchers.psystem.annotations.ParameterReference; | 16 | import tools.refinery.interpreter.matchers.psystem.annotations.ParameterReference; |
9 | import tools.refinery.interpreter.matchers.psystem.basicdeferred.*; | 17 | import tools.refinery.interpreter.matchers.psystem.basicdeferred.*; |
10 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.*; | ||
11 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity; | 18 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity; |
19 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.*; | ||
20 | import tools.refinery.interpreter.matchers.psystem.queries.PParameter; | ||
21 | import tools.refinery.interpreter.matchers.psystem.queries.PParameterDirection; | ||
22 | import tools.refinery.interpreter.matchers.psystem.queries.PQuery; | ||
23 | import tools.refinery.interpreter.matchers.tuple.Tuple; | ||
24 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
12 | import tools.refinery.store.query.Constraint; | 25 | import tools.refinery.store.query.Constraint; |
13 | import tools.refinery.store.query.dnf.Dnf; | 26 | import tools.refinery.store.query.dnf.Dnf; |
14 | import tools.refinery.store.query.dnf.DnfClause; | 27 | import tools.refinery.store.query.dnf.DnfClause; |
28 | import tools.refinery.store.query.dnf.FunctionalDependency; | ||
15 | import tools.refinery.store.query.dnf.SymbolicParameter; | 29 | import tools.refinery.store.query.dnf.SymbolicParameter; |
16 | import tools.refinery.store.query.literal.*; | 30 | import tools.refinery.store.query.literal.*; |
17 | import tools.refinery.store.query.term.ConstantTerm; | 31 | import tools.refinery.store.query.term.ConstantTerm; |
@@ -20,19 +34,6 @@ import tools.refinery.store.query.term.StatelessAggregator; | |||
20 | import tools.refinery.store.query.term.Variable; | 34 | import tools.refinery.store.query.term.Variable; |
21 | import tools.refinery.store.query.view.AnySymbolView; | 35 | import tools.refinery.store.query.view.AnySymbolView; |
22 | import tools.refinery.store.util.CycleDetectingMapper; | 36 | import tools.refinery.store.util.CycleDetectingMapper; |
23 | import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; | ||
24 | import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
25 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
26 | import tools.refinery.interpreter.matchers.psystem.PBody; | ||
27 | import tools.refinery.interpreter.matchers.psystem.PVariable; | ||
28 | import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; | ||
29 | import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
30 | import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation; | ||
31 | import tools.refinery.interpreter.matchers.psystem.queries.PParameter; | ||
32 | import tools.refinery.interpreter.matchers.psystem.queries.PParameterDirection; | ||
33 | import tools.refinery.interpreter.matchers.psystem.queries.PQuery; | ||
34 | import tools.refinery.interpreter.matchers.tuple.Tuple; | ||
35 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
36 | 37 | ||
37 | import java.util.ArrayList; | 38 | import java.util.ArrayList; |
38 | import java.util.HashMap; | 39 | import java.util.HashMap; |
@@ -79,15 +80,7 @@ public class Dnf2PQuery { | |||
79 | pQuery.setParameters(parameterList); | 80 | pQuery.setParameters(parameterList); |
80 | 81 | ||
81 | for (var functionalDependency : dnfQuery.getFunctionalDependencies()) { | 82 | for (var functionalDependency : dnfQuery.getFunctionalDependencies()) { |
82 | var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency"); | 83 | var functionalDependencyAnnotation = getFunctionalDependencyAnnotation(functionalDependency); |
83 | for (var forEachVariable : functionalDependency.forEach()) { | ||
84 | var reference = new ParameterReference(forEachVariable.getUniqueName()); | ||
85 | functionalDependencyAnnotation.addAttribute("forEach", reference); | ||
86 | } | ||
87 | for (var uniqueVariable : functionalDependency.unique()) { | ||
88 | var reference = new ParameterReference(uniqueVariable.getUniqueName()); | ||
89 | functionalDependencyAnnotation.addAttribute("unique", reference); | ||
90 | } | ||
91 | pQuery.addAnnotation(functionalDependencyAnnotation); | 84 | pQuery.addAnnotation(functionalDependencyAnnotation); |
92 | } | 85 | } |
93 | 86 | ||
@@ -108,26 +101,33 @@ public class Dnf2PQuery { | |||
108 | return pQuery; | 101 | return pQuery; |
109 | } | 102 | } |
110 | 103 | ||
111 | private void translateLiteral(Literal literal, PBody body) { | 104 | private static PAnnotation getFunctionalDependencyAnnotation(FunctionalDependency<Variable> functionalDependency) { |
112 | if (literal instanceof EquivalenceLiteral equivalenceLiteral) { | 105 | var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency"); |
113 | translateEquivalenceLiteral(equivalenceLiteral, body); | 106 | for (var forEachVariable : functionalDependency.forEach()) { |
114 | } else if (literal instanceof CallLiteral callLiteral) { | 107 | var reference = new ParameterReference(forEachVariable.getUniqueName()); |
115 | translateCallLiteral(callLiteral, body); | 108 | functionalDependencyAnnotation.addAttribute("forEach", reference); |
116 | } else if (literal instanceof ConstantLiteral constantLiteral) { | 109 | } |
117 | translateConstantLiteral(constantLiteral, body); | 110 | for (var uniqueVariable : functionalDependency.unique()) { |
118 | } else if (literal instanceof AssignLiteral<?> assignLiteral) { | 111 | var reference = new ParameterReference(uniqueVariable.getUniqueName()); |
119 | translateAssignLiteral(assignLiteral, body); | 112 | functionalDependencyAnnotation.addAttribute("unique", reference); |
120 | } else if (literal instanceof CheckLiteral checkLiteral) { | ||
121 | translateCheckLiteral(checkLiteral, body); | ||
122 | } else if (literal instanceof CountLiteral countLiteral) { | ||
123 | translateCountLiteral(countLiteral, body); | ||
124 | } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) { | ||
125 | translateAggregationLiteral(aggregationLiteral, body); | ||
126 | } else if (literal instanceof RepresentativeElectionLiteral representativeElectionLiteral) { | ||
127 | translateRepresentativeElectionLiteral(representativeElectionLiteral, body); | ||
128 | } else { | ||
129 | throw new IllegalArgumentException("Unknown literal: " + literal.toString()); | ||
130 | } | 113 | } |
114 | return functionalDependencyAnnotation; | ||
115 | } | ||
116 | |||
117 | private void translateLiteral(Literal literal, PBody body) { | ||
118 | switch (literal) { | ||
119 | case EquivalenceLiteral equivalenceLiteral -> translateEquivalenceLiteral(equivalenceLiteral, body); | ||
120 | case CallLiteral callLiteral -> translateCallLiteral(callLiteral, body); | ||
121 | case ConstantLiteral constantLiteral -> translateConstantLiteral(constantLiteral, body); | ||
122 | case AssignLiteral<?> assignLiteral -> translateAssignLiteral(assignLiteral, body); | ||
123 | case CheckLiteral checkLiteral -> translateCheckLiteral(checkLiteral, body); | ||
124 | case CountLiteral countLiteral -> translateCountLiteral(countLiteral, body); | ||
125 | case AggregationLiteral<?, ?> aggregationLiteral -> translateAggregationLiteral(aggregationLiteral, body); | ||
126 | case LeftJoinLiteral<?> leftJoinLiteral -> translateLeftJoinLiteral(leftJoinLiteral, body); | ||
127 | case RepresentativeElectionLiteral representativeElectionLiteral -> | ||
128 | translateRepresentativeElectionLiteral(representativeElectionLiteral, body); | ||
129 | case null, default -> throw new IllegalArgumentException("Unknown literal: " + literal); | ||
130 | } | ||
131 | } | 131 | } |
132 | 132 | ||
133 | private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) { | 133 | private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) { |
@@ -244,6 +244,21 @@ public class Dnf2PQuery { | |||
244 | aggregatedColumn); | 244 | aggregatedColumn); |
245 | } | 245 | } |
246 | 246 | ||
247 | private <T> void translateLeftJoinLiteral(LeftJoinLiteral<T> leftJoinLiteral, PBody body) { | ||
248 | var wrappedCall = wrapperFactory.maybeWrapConstraint(leftJoinLiteral); | ||
249 | var substitution = translateSubstitution(wrappedCall.remappedArguments(), body); | ||
250 | var placeholderVariable = body.getOrCreateVariableByName( | ||
251 | leftJoinLiteral.getPlaceholderVariable().getUniqueName()); | ||
252 | var optionalColumn = substitution.invertIndex().get(placeholderVariable); | ||
253 | if (optionalColumn == null) { | ||
254 | throw new IllegalStateException("Placeholder variable %s not found in substitution %s" | ||
255 | .formatted(placeholderVariable, substitution)); | ||
256 | } | ||
257 | var resultVariable = body.getOrCreateVariableByName(leftJoinLiteral.getResultVariable().getUniqueName()); | ||
258 | new LeftJoinConstraint(body, substitution, wrappedCall.pattern(), resultVariable, optionalColumn, | ||
259 | leftJoinLiteral.getDefaultValue()); | ||
260 | } | ||
261 | |||
247 | private void translateRepresentativeElectionLiteral(RepresentativeElectionLiteral literal, PBody body) { | 262 | private void translateRepresentativeElectionLiteral(RepresentativeElectionLiteral literal, PBody body) { |
248 | var substitution = translateSubstitution(literal.getArguments(), body); | 263 | var substitution = translateSubstitution(literal.getArguments(), body); |
249 | var pattern = wrapConstraintWithIdentityArguments(literal.getTarget()); | 264 | var pattern = wrapConstraintWithIdentityArguments(literal.getTarget()); |
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 index b6c96676..ca1512d8 100644 --- 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 | |||
@@ -554,7 +554,6 @@ class FunctionalQueryTest { | |||
554 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 554 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
555 | 555 | ||
556 | queryEngine.flushChanges(); | 556 | queryEngine.flushChanges(); |
557 | queryEngine.flushChanges(); | ||
558 | assertNullableResults(Map.of( | 557 | assertNullableResults(Map.of( |
559 | Tuple.of(0), Optional.of(25), | 558 | Tuple.of(0), Optional.of(25), |
560 | Tuple.of(1), Optional.of(32), | 559 | Tuple.of(1), Optional.of(32), |
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java new file mode 100644 index 00000000..4c849e9d --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java | |||
@@ -0,0 +1,129 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.interpreter; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.term.int_.IntTerms; | ||
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
14 | import tools.refinery.store.query.view.FunctionView; | ||
15 | import tools.refinery.store.query.view.KeyOnlyView; | ||
16 | import tools.refinery.store.representation.Symbol; | ||
17 | import tools.refinery.store.tuple.Tuple; | ||
18 | |||
19 | import java.util.List; | ||
20 | import java.util.Map; | ||
21 | import java.util.Optional; | ||
22 | |||
23 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; | ||
24 | import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; | ||
25 | |||
26 | class LeftJoinTest { | ||
27 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
28 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
29 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
30 | private static final FunctionView<Integer> ageView = new FunctionView<>(age); | ||
31 | |||
32 | @Test | ||
33 | void unarySymbolTest() { | ||
34 | var query = Query.of("Query", Integer.class, (builder, p1, output) -> builder | ||
35 | .clause( | ||
36 | personView.call(p1), | ||
37 | output.assign(ageView.leftJoin(18, p1)) | ||
38 | )); | ||
39 | |||
40 | var store = ModelStore.builder() | ||
41 | .symbols(person, age) | ||
42 | .with(QueryInterpreterAdapter.builder() | ||
43 | .queries(query)) | ||
44 | .build(); | ||
45 | |||
46 | var model = store.createEmptyModel(); | ||
47 | var personInterpretation = model.getInterpretation(person); | ||
48 | var ageInterpretation = model.getInterpretation(age); | ||
49 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
50 | var queryResultSet = queryEngine.getResultSet(query); | ||
51 | |||
52 | personInterpretation.put(Tuple.of(0), true); | ||
53 | personInterpretation.put(Tuple.of(1), true); | ||
54 | personInterpretation.put(Tuple.of(2), true); | ||
55 | |||
56 | ageInterpretation.put(Tuple.of(2), 24); | ||
57 | |||
58 | queryEngine.flushChanges(); | ||
59 | assertNullableResults(Map.of( | ||
60 | Tuple.of(0), Optional.of(18), | ||
61 | Tuple.of(1), Optional.of(18), | ||
62 | Tuple.of(2), Optional.of(24), | ||
63 | Tuple.of(3), Optional.empty() | ||
64 | ), queryResultSet); | ||
65 | |||
66 | personInterpretation.put(Tuple.of(0), false); | ||
67 | |||
68 | ageInterpretation.put(Tuple.of(1), 20); | ||
69 | ageInterpretation.put(Tuple.of(2), null); | ||
70 | |||
71 | queryEngine.flushChanges(); | ||
72 | assertNullableResults(Map.of( | ||
73 | Tuple.of(0), Optional.empty(), | ||
74 | Tuple.of(1), Optional.of(20), | ||
75 | Tuple.of(2), Optional.of(18), | ||
76 | Tuple.of(3), Optional.empty() | ||
77 | ), queryResultSet); | ||
78 | } | ||
79 | |||
80 | @Test | ||
81 | void unarySymbolWithAssignmentTest() { | ||
82 | // Tests an edge case where the outer joined variable is already bound in the query plan. | ||
83 | var query = Query.of("Query", (builder, p1) -> builder | ||
84 | .clause(Integer.class, v1 -> List.of( | ||
85 | personView.call(p1), | ||
86 | v1.assign(IntTerms.constant(18)), | ||
87 | v1.assign(ageView.leftJoin(18, p1)) | ||
88 | ))); | ||
89 | |||
90 | var store = ModelStore.builder() | ||
91 | .symbols(person, age) | ||
92 | .with(QueryInterpreterAdapter.builder() | ||
93 | .queries(query)) | ||
94 | .build(); | ||
95 | |||
96 | var model = store.createEmptyModel(); | ||
97 | var personInterpretation = model.getInterpretation(person); | ||
98 | var ageInterpretation = model.getInterpretation(age); | ||
99 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
100 | var queryResultSet = queryEngine.getResultSet(query); | ||
101 | |||
102 | personInterpretation.put(Tuple.of(0), true); | ||
103 | personInterpretation.put(Tuple.of(1), true); | ||
104 | personInterpretation.put(Tuple.of(2), true); | ||
105 | |||
106 | ageInterpretation.put(Tuple.of(2), 24); | ||
107 | |||
108 | queryEngine.flushChanges(); | ||
109 | assertResults(Map.of( | ||
110 | Tuple.of(0), true, | ||
111 | Tuple.of(1), true, | ||
112 | Tuple.of(2), false, | ||
113 | Tuple.of(3), false | ||
114 | ), queryResultSet); | ||
115 | |||
116 | personInterpretation.put(Tuple.of(0), false); | ||
117 | |||
118 | ageInterpretation.put(Tuple.of(1), 20); | ||
119 | ageInterpretation.put(Tuple.of(2), null); | ||
120 | |||
121 | queryEngine.flushChanges(); | ||
122 | assertResults(Map.of( | ||
123 | Tuple.of(0), false, | ||
124 | Tuple.of(1), false, | ||
125 | Tuple.of(2), true, | ||
126 | Tuple.of(3), false | ||
127 | ), queryResultSet); | ||
128 | } | ||
129 | } | ||