aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query-interpreter/src
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-16 13:19:31 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-16 16:53:01 +0200
commit97b0c4c1192fe5580a7957c844acc8092b56c604 (patch)
treebea3cdf9aaeb5da2864fcf87780d356661af8f63 /subprojects/store-query-interpreter/src
parentbuild: fix Sonar quality gate issues (diff)
downloadrefinery-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')
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterAdapter.java18
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterBuilder.java54
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterStoreAdapter.java17
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterAdapterImpl.java122
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java169
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterStoreAdapterImpl.java100
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/RelationalScope.java27
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/DummyBaseIndexer.java66
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalEngineContext.java35
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalQueryMetaContext.java117
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalRuntimeContext.java204
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/localsearch/FlatCostFunction.java35
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/AbstractInterpretedMatcher.java32
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/FunctionalCursor.java52
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedFunctionalMatcher.java89
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedRelationalMatcher.java81
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtils.java115
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RawPatternMatcher.java20
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RelationalCursor.java47
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/UnsafeFunctionalCursor.java55
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/CheckEvaluator.java21
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java253
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/QueryWrapperFactory.java189
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/RawPQuery.java87
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatefulMultisetAggregator.java65
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatelessMultisetAggregator.java55
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/SymbolViewWrapper.java40
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/TermEvaluator.java37
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/ValueProviderBasedValuation.java19
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/ModelUpdateListener.java51
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/RelationViewFilter.java71
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/SymbolViewUpdateListener.java65
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TupleChangingViewUpdateListener.java45
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TuplePreservingViewUpdateListener.java33
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java391
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java519
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java117
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java794
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java370
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java261
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java188
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java239
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java57
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java27
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java21
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java24
46 files changed, 5494 insertions, 0 deletions
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterAdapter.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterAdapter.java
new file mode 100644
index 00000000..83e69ae8
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterAdapter.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter;
7
8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.interpreter.internal.QueryInterpreterBuilderImpl;
10
11public interface QueryInterpreterAdapter extends ModelQueryAdapter {
12 @Override
13 QueryInterpreterStoreAdapter getStoreAdapter();
14
15 static QueryInterpreterBuilder builder() {
16 return new QueryInterpreterBuilderImpl();
17 }
18}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterBuilder.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterBuilder.java
new file mode 100644
index 00000000..6e167d0d
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterBuilder.java
@@ -0,0 +1,54 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter;
7
8import tools.refinery.store.model.ModelStore;
9import tools.refinery.store.query.ModelQueryBuilder;
10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.rewriter.DnfRewriter;
13import tools.refinery.interpreter.api.InterpreterEngineOptions;
14import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory;
15import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
16
17import java.util.Collection;
18import java.util.function.Function;
19
20@SuppressWarnings("UnusedReturnValue")
21public interface QueryInterpreterBuilder extends ModelQueryBuilder {
22 QueryInterpreterBuilder engineOptions(InterpreterEngineOptions engineOptions);
23
24 QueryInterpreterBuilder defaultHint(QueryEvaluationHint queryEvaluationHint);
25
26 QueryInterpreterBuilder backend(IQueryBackendFactory queryBackendFactory);
27
28 QueryInterpreterBuilder cachingBackend(IQueryBackendFactory queryBackendFactory);
29
30 QueryInterpreterBuilder searchBackend(IQueryBackendFactory queryBackendFactory);
31
32 @Override
33 default QueryInterpreterBuilder queries(AnyQuery... queries) {
34 ModelQueryBuilder.super.queries(queries);
35 return this;
36 }
37
38 @Override
39 default QueryInterpreterBuilder queries(Collection<? extends AnyQuery> queries) {
40 ModelQueryBuilder.super.queries(queries);
41 return this;
42 }
43
44 @Override
45 QueryInterpreterBuilder query(AnyQuery query);
46
47 @Override
48 QueryInterpreterBuilder rewriter(DnfRewriter rewriter);
49
50 QueryInterpreterBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint);
51
52 @Override
53 QueryInterpreterStoreAdapter build(ModelStore store);
54}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterStoreAdapter.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterStoreAdapter.java
new file mode 100644
index 00000000..9c55ecc2
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/QueryInterpreterStoreAdapter.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter;
7
8import tools.refinery.interpreter.api.InterpreterEngineOptions;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.ModelQueryStoreAdapter;
11
12public interface QueryInterpreterStoreAdapter extends ModelQueryStoreAdapter {
13 InterpreterEngineOptions getEngineOptions();
14
15 @Override
16 QueryInterpreterAdapter createModelAdapter(Model model);
17}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterAdapterImpl.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterAdapterImpl.java
new file mode 100644
index 00000000..ee527fd3
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterAdapterImpl.java
@@ -0,0 +1,122 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelListener;
10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.dnf.FunctionalQuery;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.dnf.RelationalQuery;
14import tools.refinery.store.query.resultset.AnyResultSet;
15import tools.refinery.store.query.resultset.EmptyResultSet;
16import tools.refinery.store.query.resultset.ResultSet;
17import tools.refinery.store.query.interpreter.QueryInterpreterAdapter;
18import tools.refinery.store.query.interpreter.internal.matcher.InterpretedFunctionalMatcher;
19import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher;
20import tools.refinery.store.query.interpreter.internal.matcher.InterpretedRelationalMatcher;
21import tools.refinery.interpreter.CancellationToken;
22import tools.refinery.interpreter.api.AdvancedInterpreterEngine;
23import tools.refinery.interpreter.api.GenericQueryGroup;
24import tools.refinery.interpreter.api.IQuerySpecification;
25
26import java.util.Collections;
27import java.util.LinkedHashMap;
28import java.util.Map;
29
30public class QueryInterpreterAdapterImpl implements QueryInterpreterAdapter, ModelListener {
31 private final Model model;
32 private final QueryInterpreterStoreAdapterImpl storeAdapter;
33 private final AdvancedInterpreterEngine queryEngine;
34 private final Map<AnyQuery, AnyResultSet> resultSets;
35 private boolean pendingChanges;
36
37 QueryInterpreterAdapterImpl(Model model, QueryInterpreterStoreAdapterImpl storeAdapter) {
38 this.model = model;
39 this.storeAdapter = storeAdapter;
40 var scope = new RelationalScope(this);
41 queryEngine = AdvancedInterpreterEngine.createUnmanagedEngine(scope,
42 storeAdapter.getEngineOptions());
43
44 var querySpecifications = storeAdapter.getQuerySpecifications();
45 GenericQueryGroup.of(
46 Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream()
47 ).prepare(queryEngine);
48 queryEngine.flushChanges();
49 var vacuousQueries = storeAdapter.getVacuousQueries();
50 resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size());
51 for (var entry : querySpecifications.entrySet()) {
52 var rawPatternMatcher = queryEngine.getMatcher(entry.getValue());
53 var query = entry.getKey();
54 resultSets.put(query, createResultSet((Query<?>) query, rawPatternMatcher));
55 }
56 for (var vacuousQuery : vacuousQueries) {
57 resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query<?>) vacuousQuery));
58 }
59
60 model.addListener(this);
61 }
62
63 private <T> ResultSet<T> createResultSet(Query<T> query, RawPatternMatcher matcher) {
64 if (query instanceof RelationalQuery relationalQuery) {
65 @SuppressWarnings("unchecked")
66 var resultSet = (ResultSet<T>) new InterpretedRelationalMatcher(this, relationalQuery, matcher);
67 return resultSet;
68 } else if (query instanceof FunctionalQuery<T> functionalQuery) {
69 return new InterpretedFunctionalMatcher<>(this, functionalQuery, matcher);
70 } else {
71 throw new IllegalArgumentException("Unknown query: " + query);
72 }
73 }
74
75 @Override
76 public Model getModel() {
77 return model;
78 }
79
80 @Override
81 public QueryInterpreterStoreAdapterImpl getStoreAdapter() {
82 return storeAdapter;
83 }
84
85 public CancellationToken getCancellationToken() {
86 return storeAdapter.getCancellationToken();
87 }
88
89 @Override
90 public <T> ResultSet<T> getResultSet(Query<T> query) {
91 var canonicalQuery = storeAdapter.getCanonicalQuery(query);
92 var resultSet = resultSets.get(canonicalQuery);
93 if (resultSet == null) {
94 throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name()));
95 }
96 @SuppressWarnings("unchecked")
97 var typedResultSet = (ResultSet<T>) resultSet;
98 return typedResultSet;
99 }
100
101 @Override
102 public boolean hasPendingChanges() {
103 return pendingChanges;
104 }
105
106 public void markAsPending() {
107 if (!pendingChanges) {
108 pendingChanges = true;
109 }
110 }
111
112 @Override
113 public void flushChanges() {
114 queryEngine.flushChanges();
115 pendingChanges = false;
116 }
117
118 @Override
119 public void afterRestore() {
120 flushChanges();
121 }
122}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java
new file mode 100644
index 00000000..c0d802da
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java
@@ -0,0 +1,169 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal;
7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.rewriter.CompositeRewriter;
13import tools.refinery.store.query.rewriter.DnfRewriter;
14import tools.refinery.store.query.rewriter.DuplicateDnfRemover;
15import tools.refinery.store.query.rewriter.InputParameterResolver;
16import tools.refinery.store.query.interpreter.QueryInterpreterBuilder;
17import tools.refinery.store.query.interpreter.internal.localsearch.FlatCostFunction;
18import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher;
19import tools.refinery.store.query.interpreter.internal.pquery.Dnf2PQuery;
20import tools.refinery.interpreter.api.IQuerySpecification;
21import tools.refinery.interpreter.api.InterpreterEngineOptions;
22import tools.refinery.interpreter.localsearch.matcher.integration.LocalSearchGenericBackendFactory;
23import tools.refinery.interpreter.localsearch.matcher.integration.LocalSearchHintOptions;
24import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory;
25import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
26import tools.refinery.interpreter.rete.matcher.ReteBackendFactory;
27
28import java.util.*;
29import java.util.function.Function;
30
31public class QueryInterpreterBuilderImpl extends AbstractModelAdapterBuilder<QueryInterpreterStoreAdapterImpl>
32 implements QueryInterpreterBuilder {
33 private InterpreterEngineOptions.Builder engineOptionsBuilder;
34 private QueryEvaluationHint defaultHint = new QueryEvaluationHint(Map.of(
35 // Use a cost function that ignores the initial (empty) model but allows higher arity input keys.
36 LocalSearchHintOptions.PLANNER_COST_FUNCTION, new FlatCostFunction()
37 ), (IQueryBackendFactory) null);
38 private final CompositeRewriter rewriter;
39 private final Dnf2PQuery dnf2PQuery = new Dnf2PQuery();
40 private final Set<AnyQuery> queries = new LinkedHashSet<>();
41
42 public QueryInterpreterBuilderImpl() {
43 engineOptionsBuilder = new InterpreterEngineOptions.Builder()
44 .withDefaultBackend(ReteBackendFactory.INSTANCE)
45 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE)
46 .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE);
47 rewriter = new CompositeRewriter();
48 rewriter.addFirst(new DuplicateDnfRemover());
49 rewriter.addFirst(new InputParameterResolver());
50 }
51
52 @Override
53 public QueryInterpreterBuilder engineOptions(InterpreterEngineOptions engineOptions) {
54 checkNotConfigured();
55 engineOptionsBuilder = new InterpreterEngineOptions.Builder(engineOptions);
56 return this;
57 }
58
59 @Override
60 public QueryInterpreterBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) {
61 checkNotConfigured();
62 defaultHint = defaultHint.overrideBy(queryEvaluationHint);
63 return this;
64 }
65
66 @Override
67 public QueryInterpreterBuilder backend(IQueryBackendFactory queryBackendFactory) {
68 checkNotConfigured();
69 engineOptionsBuilder.withDefaultBackend(queryBackendFactory);
70 return this;
71 }
72
73 @Override
74 public QueryInterpreterBuilder cachingBackend(IQueryBackendFactory queryBackendFactory) {
75 checkNotConfigured();
76 engineOptionsBuilder.withDefaultCachingBackend(queryBackendFactory);
77 return this;
78 }
79
80 @Override
81 public QueryInterpreterBuilder searchBackend(IQueryBackendFactory queryBackendFactory) {
82 checkNotConfigured();
83 engineOptionsBuilder.withDefaultSearchBackend(queryBackendFactory);
84 return this;
85 }
86
87 @Override
88 public QueryInterpreterBuilder queries(Collection<? extends AnyQuery> queries) {
89 checkNotConfigured();
90 this.queries.addAll(queries);
91 return this;
92 }
93
94 @Override
95 public QueryInterpreterBuilder query(AnyQuery query) {
96 checkNotConfigured();
97 queries.add(query);
98 return this;
99 }
100
101 @Override
102 public QueryInterpreterBuilder rewriter(DnfRewriter rewriter) {
103 this.rewriter.addFirst(rewriter);
104 return this;
105 }
106
107 @Override
108 public QueryInterpreterBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint) {
109 checkNotConfigured();
110 dnf2PQuery.setComputeHint(computeHint);
111 return this;
112 }
113
114 @Override
115 public QueryInterpreterStoreAdapterImpl doBuild(ModelStore store) {
116 var canonicalQueryMap = new HashMap<AnyQuery, AnyQuery>();
117 var querySpecifications = new LinkedHashMap<AnyQuery, IQuerySpecification<RawPatternMatcher>>();
118 var vacuousQueries = new LinkedHashSet<AnyQuery>();
119 for (var query : queries) {
120 var canonicalQuery = rewriter.rewrite(query);
121 canonicalQueryMap.put(query, canonicalQuery);
122 var dnf = canonicalQuery.getDnf();
123 var reduction = dnf.getReduction();
124 switch (reduction) {
125 case NOT_REDUCIBLE -> {
126 var pQuery = dnf2PQuery.translate(dnf);
127 querySpecifications.put(canonicalQuery, pQuery.build());
128 }
129 case ALWAYS_FALSE -> vacuousQueries.add(canonicalQuery);
130 case ALWAYS_TRUE -> throw new IllegalArgumentException(
131 "Query %s is relationally unsafe (it matches every tuple)".formatted(query.name()));
132 default -> throw new IllegalArgumentException("Unknown reduction: " + reduction);
133 }
134 }
135
136 validateSymbols(store);
137 return new QueryInterpreterStoreAdapterImpl(store, buildEngineOptions(), dnf2PQuery.getSymbolViews(),
138 Collections.unmodifiableMap(canonicalQueryMap), Collections.unmodifiableMap(querySpecifications),
139 Collections.unmodifiableSet(vacuousQueries), store::checkCancelled);
140 }
141
142 private InterpreterEngineOptions buildEngineOptions() {
143 // Workaround: manually override the default backend, because {@link ViatraQueryEngineOptions.Builder}
144 // ignores all backend requirements except {@code SPECIFIC}.
145 switch (defaultHint.getQueryBackendRequirementType()) {
146 case SPECIFIC -> engineOptionsBuilder.withDefaultBackend(defaultHint.getQueryBackendFactory());
147 case DEFAULT_CACHING -> engineOptionsBuilder.withDefaultBackend(
148 engineOptionsBuilder.build().getDefaultCachingBackendFactory());
149 case DEFAULT_SEARCH -> engineOptionsBuilder.withDefaultBackend(
150 engineOptionsBuilder.build().getDefaultSearchBackendFactory());
151 case UNSPECIFIED -> {
152 // Nothing to do, leave the default backend unchanged.
153 }
154 }
155 engineOptionsBuilder.withDefaultHint(defaultHint);
156 return engineOptionsBuilder.build();
157 }
158
159 private void validateSymbols(ModelStore store) {
160 var symbols = store.getSymbols();
161 for (var symbolView : dnf2PQuery.getSymbolViews().keySet()) {
162 var symbol = symbolView.getSymbol();
163 if (!symbols.contains(symbol)) {
164 throw new IllegalArgumentException("Cannot query view %s: symbol %s is not in the model"
165 .formatted(symbolView, symbol));
166 }
167 }
168 }
169}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterStoreAdapterImpl.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterStoreAdapterImpl.java
new file mode 100644
index 00000000..10e7a402
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterStoreAdapterImpl.java
@@ -0,0 +1,100 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal;
7
8import tools.refinery.interpreter.CancellationToken;
9import tools.refinery.interpreter.api.IQuerySpecification;
10import tools.refinery.interpreter.api.InterpreterEngineOptions;
11import tools.refinery.interpreter.matchers.context.IInputKey;
12import tools.refinery.store.model.Model;
13import tools.refinery.store.model.ModelStore;
14import tools.refinery.store.query.dnf.AnyQuery;
15import tools.refinery.store.query.dnf.Query;
16import tools.refinery.store.query.interpreter.QueryInterpreterStoreAdapter;
17import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher;
18import tools.refinery.store.query.view.AnySymbolView;
19
20import java.util.*;
21
22public class QueryInterpreterStoreAdapterImpl implements QueryInterpreterStoreAdapter {
23 private final ModelStore store;
24 private final InterpreterEngineOptions engineOptions;
25 private final Map<AnySymbolView, IInputKey> inputKeys;
26 private final Map<AnyQuery, AnyQuery> canonicalQueryMap;
27 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications;
28 private final Set<AnyQuery> vacuousQueries;
29 private final Set<AnyQuery> allQueries;
30 private final CancellationToken cancellationToken;
31
32 QueryInterpreterStoreAdapterImpl(ModelStore store, InterpreterEngineOptions engineOptions,
33 Map<AnySymbolView, IInputKey> inputKeys,
34 Map<AnyQuery, AnyQuery> canonicalQueryMap,
35 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications,
36 Set<AnyQuery> vacuousQueries, CancellationToken cancellationToken) {
37 this.store = store;
38 this.engineOptions = engineOptions;
39 this.inputKeys = inputKeys;
40 this.canonicalQueryMap = canonicalQueryMap;
41 this.querySpecifications = querySpecifications;
42 this.vacuousQueries = vacuousQueries;
43 this.cancellationToken = cancellationToken;
44 var mutableAllQueries = new LinkedHashSet<AnyQuery>(querySpecifications.size() + vacuousQueries.size());
45 mutableAllQueries.addAll(querySpecifications.keySet());
46 mutableAllQueries.addAll(vacuousQueries);
47 this.allQueries = Collections.unmodifiableSet(mutableAllQueries);
48 }
49
50 @Override
51 public ModelStore getStore() {
52 return store;
53 }
54
55 public Collection<AnySymbolView> getSymbolViews() {
56 return inputKeys.keySet();
57 }
58
59 public Map<AnySymbolView, IInputKey> getInputKeys() {
60 return inputKeys;
61 }
62
63 @Override
64 public Collection<AnyQuery> getQueries() {
65 return allQueries;
66 }
67
68 public CancellationToken getCancellationToken() {
69 return cancellationToken;
70 }
71
72 @Override
73 public <T> Query<T> getCanonicalQuery(Query<T> query) {
74 // We know that canonical forms of queries do not change output types.
75 @SuppressWarnings("unchecked")
76 var canonicalQuery = (Query<T>) canonicalQueryMap.get(query);
77 if (canonicalQuery == null) {
78 throw new IllegalArgumentException("Unknown query: " + query);
79 }
80 return canonicalQuery;
81 }
82
83 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() {
84 return querySpecifications;
85 }
86
87 Set<AnyQuery> getVacuousQueries() {
88 return vacuousQueries;
89 }
90
91 @Override
92 public InterpreterEngineOptions getEngineOptions() {
93 return engineOptions;
94 }
95
96 @Override
97 public QueryInterpreterAdapterImpl createModelAdapter(Model model) {
98 return new QueryInterpreterAdapterImpl(model, this);
99 }
100}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/RelationalScope.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/RelationalScope.java
new file mode 100644
index 00000000..7eef5b85
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/RelationalScope.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 */
6package tools.refinery.store.query.interpreter.internal;
7
8import org.apache.log4j.Logger;
9import tools.refinery.interpreter.api.InterpreterEngine;
10import tools.refinery.interpreter.api.scope.IEngineContext;
11import tools.refinery.interpreter.api.scope.IIndexingErrorListener;
12import tools.refinery.interpreter.api.scope.QueryScope;
13import tools.refinery.store.query.interpreter.internal.context.RelationalEngineContext;
14
15public class RelationalScope extends QueryScope {
16 private final QueryInterpreterAdapterImpl adapter;
17
18 public RelationalScope(QueryInterpreterAdapterImpl adapter) {
19 this.adapter = adapter;
20 }
21
22 @Override
23 protected IEngineContext createEngineContext(InterpreterEngine engine, IIndexingErrorListener errorListener,
24 Logger logger) {
25 return new RelationalEngineContext(adapter);
26 }
27}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/DummyBaseIndexer.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/DummyBaseIndexer.java
new file mode 100644
index 00000000..e9a05fe9
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/DummyBaseIndexer.java
@@ -0,0 +1,66 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.context;
7
8import tools.refinery.interpreter.api.scope.IBaseIndex;
9import tools.refinery.interpreter.api.scope.IIndexingErrorListener;
10import tools.refinery.interpreter.api.scope.IInstanceObserver;
11import tools.refinery.interpreter.api.scope.InterpreterBaseIndexChangeListener;
12
13import java.lang.reflect.InvocationTargetException;
14import java.util.concurrent.Callable;
15
16/**
17 * Copied from <code>tools.refinery.viatra.runtime.tabular.TabularEngineContext</code>
18 */
19public class DummyBaseIndexer implements IBaseIndex {
20 DummyBaseIndexer() {
21 }
22
23 @Override
24 public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException {
25 try {
26 return callable.call();
27 } catch (Exception e) {
28 throw new InvocationTargetException(e);
29 }
30 }
31
32 @Override
33 public void addBaseIndexChangeListener(InterpreterBaseIndexChangeListener listener) {
34 // no notification support
35 }
36
37 @Override
38 public void removeBaseIndexChangeListener(InterpreterBaseIndexChangeListener listener) {
39 // no notification support
40 }
41
42 @Override
43 public void resampleDerivedFeatures() {
44 throw new UnsupportedOperationException();
45 }
46
47 @Override
48 public boolean addIndexingErrorListener(IIndexingErrorListener listener) {
49 return false;
50 }
51
52 @Override
53 public boolean removeIndexingErrorListener(IIndexingErrorListener listener) {
54 return false;
55 }
56
57 @Override
58 public boolean addInstanceObserver(IInstanceObserver observer, Object observedObject) {
59 return false;
60 }
61
62 @Override
63 public boolean removeInstanceObserver(IInstanceObserver observer, Object observedObject) {
64 return false;
65 }
66}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalEngineContext.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalEngineContext.java
new file mode 100644
index 00000000..f6e8b605
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalEngineContext.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.context;
7
8import tools.refinery.interpreter.api.scope.IBaseIndex;
9import tools.refinery.interpreter.api.scope.IEngineContext;
10import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext;
11import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
12
13public class RelationalEngineContext implements IEngineContext {
14 private final IBaseIndex baseIndex = new DummyBaseIndexer();
15 private final RelationalRuntimeContext runtimeContext;
16
17 public RelationalEngineContext(QueryInterpreterAdapterImpl adapter) {
18 runtimeContext = new RelationalRuntimeContext(adapter);
19 }
20
21 @Override
22 public IBaseIndex getBaseIndex() {
23 return this.baseIndex;
24 }
25
26 @Override
27 public void dispose() {
28 // Nothing to dispose, because lifecycle is not controlled by the engine.
29 }
30
31 @Override
32 public IQueryRuntimeContext getQueryRuntimeContext() {
33 return runtimeContext;
34 }
35}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalQueryMetaContext.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalQueryMetaContext.java
new file mode 100644
index 00000000..2b1ff2b4
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalQueryMetaContext.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 */
6package tools.refinery.store.query.interpreter.internal.context;
7
8import tools.refinery.interpreter.matchers.context.AbstractQueryMetaContext;
9import tools.refinery.interpreter.matchers.context.IInputKey;
10import tools.refinery.interpreter.matchers.context.InputKeyImplication;
11import tools.refinery.interpreter.matchers.context.common.JavaTransitiveInstancesKey;
12import tools.refinery.store.query.interpreter.internal.pquery.SymbolViewWrapper;
13import tools.refinery.store.query.view.AnySymbolView;
14
15import java.util.*;
16
17/**
18 * The meta context information for String scopes.
19 */
20public class RelationalQueryMetaContext extends AbstractQueryMetaContext {
21 private final Map<AnySymbolView, IInputKey> inputKeys;
22
23 RelationalQueryMetaContext(Map<AnySymbolView, IInputKey> inputKeys) {
24 this.inputKeys = inputKeys;
25 }
26
27 @Override
28 public boolean isEnumerable(IInputKey key) {
29 checkKey(key);
30 return key.isEnumerable();
31 }
32
33 @Override
34 public boolean isStateless(IInputKey key) {
35 checkKey(key);
36 return true;
37 }
38
39 @Override
40 public boolean canLeadOutOfScope(IInputKey key) {
41 return false;
42 }
43
44 @Override
45 public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) {
46 if (implyingKey instanceof JavaTransitiveInstancesKey) {
47 return List.of();
48 }
49 var symbolView = checkKey(implyingKey);
50 var relationViewImplications = symbolView.getImpliedRelationViews();
51 var inputKeyImplications = new HashSet<InputKeyImplication>(relationViewImplications.size());
52 for (var relationViewImplication : relationViewImplications) {
53 if (!symbolView.equals(relationViewImplication.implyingView())) {
54 throw new IllegalArgumentException("Relation view %s returned unrelated implication %s".formatted(
55 symbolView, relationViewImplication));
56 }
57 var impliedInputKey = inputKeys.get(relationViewImplication.impliedView());
58 // Ignore implications not relevant for any queries included in the model.
59 if (impliedInputKey != null) {
60 inputKeyImplications.add(new InputKeyImplication(implyingKey, impliedInputKey,
61 relationViewImplication.impliedIndices()));
62 }
63 }
64 var parameters = symbolView.getParameters();
65 int arity = symbolView.arity();
66 for (int i = 0; i < arity; i++) {
67 var parameter = parameters.get(i);
68 var parameterType = parameter.tryGetType();
69 if (parameterType.isPresent()) {
70 var javaTransitiveInstancesKey = new JavaTransitiveInstancesKey(parameterType.get());
71 var javaImplication = new InputKeyImplication(implyingKey, javaTransitiveInstancesKey, List.of(i));
72 inputKeyImplications.add(javaImplication);
73 }
74 }
75 return inputKeyImplications;
76 }
77
78 @Override
79 public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) {
80 if (key instanceof JavaTransitiveInstancesKey) {
81 return Map.of();
82 }
83 var relationView = checkKey(key);
84 var functionalDependencies = relationView.getFunctionalDependencies();
85 var flattened = new HashMap<Set<Integer>, Set<Integer>>(functionalDependencies.size());
86 for (var functionalDependency : functionalDependencies) {
87 var forEach = functionalDependency.forEach();
88 checkValidIndices(relationView, forEach);
89 var unique = functionalDependency.unique();
90 checkValidIndices(relationView, unique);
91 var existing = flattened.get(forEach);
92 if (existing == null) {
93 flattened.put(forEach, new HashSet<>(unique));
94 } else {
95 existing.addAll(unique);
96 }
97 }
98 return flattened;
99 }
100
101 private static void checkValidIndices(AnySymbolView relationView, Collection<Integer> indices) {
102 indices.stream().filter(relationView::invalidIndex).findAny().ifPresent(i -> {
103 throw new IllegalArgumentException("Index %d is invalid for %s".formatted(i, relationView));
104 });
105 }
106
107 public AnySymbolView checkKey(IInputKey key) {
108 if (!(key instanceof SymbolViewWrapper wrapper)) {
109 throw new IllegalArgumentException("The input key %s is not a valid input key".formatted(key));
110 }
111 var symbolView = wrapper.getWrappedKey();
112 if (!inputKeys.containsKey(symbolView)) {
113 throw new IllegalArgumentException("The input key %s is not present in the model".formatted(key));
114 }
115 return symbolView;
116 }
117}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalRuntimeContext.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalRuntimeContext.java
new file mode 100644
index 00000000..5870b0c2
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/context/RelationalRuntimeContext.java
@@ -0,0 +1,204 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.context;
7
8import tools.refinery.interpreter.CancellationToken;
9import tools.refinery.interpreter.matchers.context.*;
10import tools.refinery.interpreter.matchers.tuple.ITuple;
11import tools.refinery.interpreter.matchers.tuple.Tuple;
12import tools.refinery.interpreter.matchers.tuple.TupleMask;
13import tools.refinery.interpreter.matchers.tuple.Tuples;
14import tools.refinery.interpreter.matchers.util.Accuracy;
15import tools.refinery.store.model.Model;
16import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
17import tools.refinery.store.query.interpreter.internal.pquery.SymbolViewWrapper;
18import tools.refinery.store.query.interpreter.internal.update.ModelUpdateListener;
19import tools.refinery.store.query.view.AnySymbolView;
20
21import java.lang.reflect.InvocationTargetException;
22import java.util.Iterator;
23import java.util.Optional;
24import java.util.concurrent.Callable;
25
26import static tools.refinery.store.util.CollectionsUtil.filter;
27import static tools.refinery.store.util.CollectionsUtil.map;
28
29public class RelationalRuntimeContext implements IQueryRuntimeContext {
30 private final RelationalQueryMetaContext metaContext;
31
32 private final ModelUpdateListener modelUpdateListener;
33
34 private final Model model;
35
36 private final CancellationToken cancellationToken;
37
38 RelationalRuntimeContext(QueryInterpreterAdapterImpl adapter) {
39 model = adapter.getModel();
40 metaContext = new RelationalQueryMetaContext(adapter.getStoreAdapter().getInputKeys());
41 modelUpdateListener = new ModelUpdateListener(adapter);
42 cancellationToken = adapter.getCancellationToken();
43 }
44
45 @Override
46 public IQueryMetaContext getMetaContext() {
47 return metaContext;
48 }
49
50 @Override
51 public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException {
52 try {
53 return callable.call();
54 } catch (Exception e) {
55 throw new InvocationTargetException(e);
56 }
57 }
58
59 @Override
60 public boolean isCoalescing() {
61 return false;
62 }
63
64 @Override
65 public boolean isIndexed(IInputKey key, IndexingService service) {
66 if (key instanceof SymbolViewWrapper wrapper) {
67 var symbolViewKey = wrapper.getWrappedKey();
68 return this.modelUpdateListener.containsSymbolView(symbolViewKey);
69 } else {
70 return false;
71 }
72 }
73
74 @Override
75 public void ensureIndexed(IInputKey key, IndexingService service) {
76 if (!isIndexed(key, service)) {
77 throw new IllegalStateException("Engine tries to index a new key %s".formatted(key));
78 }
79 }
80
81 AnySymbolView checkKey(IInputKey key) {
82 if (key instanceof SymbolViewWrapper wrappedKey) {
83 var symbolViewKey = wrappedKey.getWrappedKey();
84 if (modelUpdateListener.containsSymbolView(symbolViewKey)) {
85 return symbolViewKey;
86 } else {
87 throw new IllegalStateException("Query is asking for non-indexed key %s".formatted(symbolViewKey));
88 }
89 } else {
90 throw new IllegalStateException("Query is asking for non-relational key");
91 }
92 }
93
94 @Override
95 public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) {
96 Iterator<Object[]> iterator = enumerate(key, seedMask, seed).iterator();
97 int result = 0;
98 while (iterator.hasNext()) {
99 iterator.next();
100 result++;
101 }
102 return result;
103 }
104
105 @Override
106 public Optional<Long> estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) {
107 return Optional.empty();
108 }
109
110 @Override
111 public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) {
112 var filteredBySeed = enumerate(key, seedMask, seed);
113 return map(filteredBySeed, Tuples::flatTupleOf);
114 }
115
116 @Override
117 public Iterable<?> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) {
118 var index = seedMask.getFirstOmittedIndex().orElseThrow(
119 () -> new IllegalArgumentException("Seed mask does not omit a value"));
120 var filteredBySeed = enumerate(key, seedMask, seed);
121 return map(filteredBySeed, array -> array[index]);
122 }
123
124 private Iterable<Object[]> enumerate(IInputKey key, TupleMask seedMask, ITuple seed) {
125 var relationViewKey = checkKey(key);
126 Iterable<Object[]> allObjects = getAllObjects(relationViewKey, seedMask, seed);
127 return filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed));
128 }
129
130 private Iterable<Object[]> getAllObjects(AnySymbolView key, TupleMask seedMask, ITuple seed) {
131 for (int i = 0; i < seedMask.indices.length; i++) {
132 int slot = seedMask.indices[i];
133 if (key.canIndexSlot(slot)) {
134 return key.getAdjacent(model, slot, seed.get(i));
135 }
136 }
137 return key.getAll(model);
138 }
139
140 private static boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) {
141 for (int i = 0; i < seedMask.indices.length; i++) {
142 final Object seedElement = seed.get(i);
143 final Object tupleElement = tuple[seedMask.indices[i]];
144 if (!tupleElement.equals(seedElement)) {
145 return false;
146 }
147 }
148 return true;
149 }
150
151 @Override
152 public boolean containsTuple(IInputKey key, ITuple seed) {
153 var relationViewKey = checkKey(key);
154 return relationViewKey.get(model, seed.getElements());
155 }
156
157 @Override
158 public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) {
159 var relationViewKey = checkKey(key);
160 this.modelUpdateListener.addListener(key, relationViewKey, seed, listener);
161
162 }
163
164 @Override
165 public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) {
166 var relationViewKey = checkKey(key);
167 this.modelUpdateListener.removeListener(key, relationViewKey, seed, listener);
168 }
169
170 @Override
171 public Object wrapElement(Object externalElement) {
172 return externalElement;
173 }
174
175 @Override
176 public Object unwrapElement(Object internalElement) {
177 return internalElement;
178 }
179
180 @Override
181 public Tuple wrapTuple(Tuple externalElements) {
182 return externalElements;
183 }
184
185 @Override
186 public Tuple unwrapTuple(Tuple internalElements) {
187 return internalElements;
188 }
189
190 @Override
191 public void ensureWildcardIndexing(IndexingService service) {
192 throw new UnsupportedOperationException();
193 }
194
195 @Override
196 public void executeAfterTraversal(Runnable runnable) {
197 runnable.run();
198 }
199
200 @Override
201 public CancellationToken getCancellationToken() {
202 return cancellationToken;
203 }
204}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/localsearch/FlatCostFunction.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/localsearch/FlatCostFunction.java
new file mode 100644
index 00000000..45fd9fbd
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/localsearch/FlatCostFunction.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.localsearch;
7
8import tools.refinery.interpreter.localsearch.planner.cost.IConstraintEvaluationContext;
9import tools.refinery.interpreter.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction;
10import tools.refinery.interpreter.matchers.context.IInputKey;
11import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint;
12import tools.refinery.interpreter.matchers.tuple.TupleMask;
13import tools.refinery.interpreter.matchers.util.Accuracy;
14
15import java.util.Optional;
16
17public class FlatCostFunction extends StatisticsBasedConstraintCostFunction {
18 public FlatCostFunction() {
19 // No inverse navigation penalty thanks to relational storage.
20 super(0);
21 }
22
23 @Override
24 public Optional<Long> projectionSize(IConstraintEvaluationContext input, IInputKey supplierKey, TupleMask groupMask, Accuracy requiredAccuracy) {
25 // We always start from an empty model, where every projection is of size 0.
26 // Therefore, projection size estimation is meaningless.
27 return Optional.empty();
28 }
29
30 @Override
31 protected double _calculateCost(TypeConstraint constraint, IConstraintEvaluationContext input) {
32 // Assume a flat cost for each relation. Maybe adjust in the future if we perform indexing?
33 return DEFAULT_COST;
34 }
35}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/AbstractInterpretedMatcher.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/AbstractInterpretedMatcher.java
new file mode 100644
index 00000000..8cec0bf6
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/AbstractInterpretedMatcher.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.matchers.backend.IQueryResultProvider;
9import tools.refinery.interpreter.matchers.backend.IUpdateable;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.resultset.AbstractResultSet;
12import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
13
14public abstract class AbstractInterpretedMatcher<T> extends AbstractResultSet<T> implements IUpdateable {
15 protected final IQueryResultProvider backend;
16
17 protected AbstractInterpretedMatcher(QueryInterpreterAdapterImpl adapter, Query<T> query,
18 RawPatternMatcher rawPatternMatcher) {
19 super(adapter, query);
20 backend = rawPatternMatcher.getBackend();
21 }
22
23 @Override
24 protected void startListeningForChanges() {
25 backend.addUpdateListener(this, this, false);
26 }
27
28 @Override
29 protected void stopListeningForChanges() {
30 backend.removeUpdateListener(this);
31 }
32}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/FunctionalCursor.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/FunctionalCursor.java
new file mode 100644
index 00000000..e3b53f6b
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/FunctionalCursor.java
@@ -0,0 +1,52 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.rete.index.IterableIndexer;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14class FunctionalCursor<T> implements Cursor<Tuple, T> {
15 private final IterableIndexer indexer;
16 private final Iterator<tools.refinery.interpreter.matchers.tuple.Tuple> iterator;
17 private boolean terminated;
18 private Tuple key;
19 private T value;
20
21 public FunctionalCursor(IterableIndexer indexer) {
22 this.indexer = indexer;
23 iterator = indexer.getSignatures().iterator();
24 }
25
26 @Override
27 public Tuple getKey() {
28 return key;
29 }
30
31 @Override
32 public T getValue() {
33 return value;
34 }
35
36 @Override
37 public boolean isTerminated() {
38 return terminated;
39 }
40
41 @Override
42 public boolean move() {
43 if (!terminated && iterator.hasNext()) {
44 var match = iterator.next();
45 key = MatcherUtils.toRefineryTuple(match);
46 value = MatcherUtils.getSingleValue(indexer.get(match));
47 return true;
48 }
49 terminated = true;
50 return false;
51 }
52}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedFunctionalMatcher.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedFunctionalMatcher.java
new file mode 100644
index 00000000..249664a4
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedFunctionalMatcher.java
@@ -0,0 +1,89 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext;
9import tools.refinery.interpreter.matchers.tuple.TupleMask;
10import tools.refinery.interpreter.matchers.tuple.Tuples;
11import tools.refinery.interpreter.rete.index.IterableIndexer;
12import tools.refinery.interpreter.rete.matcher.RetePatternMatcher;
13import tools.refinery.store.map.Cursor;
14import tools.refinery.store.query.dnf.FunctionalQuery;
15import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
16import tools.refinery.store.tuple.Tuple;
17
18/**
19 * Directly access the tuples inside a Refinery Interpreter pattern matcher.<p>
20 * This class neglects calling
21 * {@link IQueryRuntimeContext#wrapTuple(tools.refinery.interpreter.matchers.tuple.Tuple)}
22 * and
23 * {@link IQueryRuntimeContext#unwrapTuple(tools.refinery.interpreter.matchers.tuple.Tuple)},
24 * because {@link tools.refinery.store.query.interpreter.internal.context.RelationalRuntimeContext} provides a trivial
25 * implementation for these methods.
26 * Using this class with any other runtime context may lead to undefined behavior.
27 */
28public class InterpretedFunctionalMatcher<T> extends AbstractInterpretedMatcher<T> {
29 private final TupleMask emptyMask;
30 private final TupleMask omitOutputMask;
31 private final IterableIndexer omitOutputIndexer;
32
33 public InterpretedFunctionalMatcher(QueryInterpreterAdapterImpl adapter, FunctionalQuery<T> query,
34 RawPatternMatcher rawPatternMatcher) {
35 super(adapter, query, rawPatternMatcher);
36 int arity = query.arity();
37 int arityWithOutput = arity + 1;
38 emptyMask = TupleMask.empty(arityWithOutput);
39 omitOutputMask = TupleMask.omit(arity, arityWithOutput);
40 if (backend instanceof RetePatternMatcher reteBackend) {
41 var maybeIterableOmitOutputIndexer = reteBackend.getInternalIndexer(omitOutputMask);
42 if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) {
43 omitOutputIndexer = iterableOmitOutputIndexer;
44 } else {
45 omitOutputIndexer = null;
46 }
47 } else {
48 omitOutputIndexer = null;
49 }
50 }
51
52 @Override
53 public T get(Tuple parameters) {
54 var tuple = MatcherUtils.toViatraTuple(parameters);
55 if (omitOutputIndexer == null) {
56 return MatcherUtils.getSingleValue(backend.getAllMatches(omitOutputMask, tuple).iterator());
57 } else {
58 return MatcherUtils.getSingleValue(omitOutputIndexer.get(tuple));
59 }
60 }
61
62 @Override
63 public Cursor<Tuple, T> getAll() {
64 if (omitOutputIndexer == null) {
65 var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf());
66 return new UnsafeFunctionalCursor<>(allMatches.iterator());
67 }
68 return new FunctionalCursor<>(omitOutputIndexer);
69 }
70
71 @Override
72 public int size() {
73 if (omitOutputIndexer == null) {
74 return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf());
75 }
76 return omitOutputIndexer.getBucketCount();
77 }
78
79 @Override
80 public void update(tools.refinery.interpreter.matchers.tuple.Tuple updateElement, boolean isInsertion) {
81 var key = MatcherUtils.keyToRefineryTuple(updateElement);
82 var value = MatcherUtils.<T>getValue(updateElement);
83 if (isInsertion) {
84 notifyChange(key, null, value);
85 } else {
86 notifyChange(key, value, null);
87 }
88 }
89}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedRelationalMatcher.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedRelationalMatcher.java
new file mode 100644
index 00000000..9278b46d
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/InterpretedRelationalMatcher.java
@@ -0,0 +1,81 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext;
9import tools.refinery.interpreter.matchers.tuple.TupleMask;
10import tools.refinery.interpreter.matchers.tuple.Tuples;
11import tools.refinery.interpreter.rete.index.Indexer;
12import tools.refinery.interpreter.rete.matcher.RetePatternMatcher;
13import tools.refinery.store.map.Cursor;
14import tools.refinery.store.map.Cursors;
15import tools.refinery.store.query.dnf.RelationalQuery;
16import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
17import tools.refinery.store.tuple.Tuple;
18
19/**
20 * Directly access the tuples inside a Refinery Interpreter pattern matcher.<p>
21 * This class neglects calling
22 * {@link IQueryRuntimeContext#wrapTuple(tools.refinery.interpreter.matchers.tuple.Tuple)}
23 * and
24 * {@link IQueryRuntimeContext#unwrapTuple(tools.refinery.interpreter.matchers.tuple.Tuple)},
25 * because {@link tools.refinery.store.query.interpreter.internal.context.RelationalRuntimeContext} provides a trivial
26 * implementation for these methods.
27 * Using this class with any other runtime context may lead to undefined behavior.
28 */
29public class InterpretedRelationalMatcher extends AbstractInterpretedMatcher<Boolean> {
30 private final TupleMask emptyMask;
31 private final TupleMask identityMask;
32 private final Indexer emptyMaskIndexer;
33
34 public InterpretedRelationalMatcher(QueryInterpreterAdapterImpl adapter, RelationalQuery query,
35 RawPatternMatcher rawPatternMatcher) {
36 super(adapter, query, rawPatternMatcher);
37 int arity = query.arity();
38 emptyMask = TupleMask.empty(arity);
39 identityMask = TupleMask.identity(arity);
40 if (backend instanceof RetePatternMatcher reteBackend) {
41 emptyMaskIndexer = reteBackend.getInternalIndexer(emptyMask);
42 } else {
43 emptyMaskIndexer = null;
44 }
45 }
46
47 @Override
48 public Boolean get(Tuple parameters) {
49 var tuple = MatcherUtils.toViatraTuple(parameters);
50 if (emptyMaskIndexer == null) {
51 return backend.hasMatch(identityMask, tuple);
52 }
53 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
54 return matches != null && matches.contains(tuple);
55 }
56
57 @Override
58 public Cursor<Tuple, Boolean> getAll() {
59 if (emptyMaskIndexer == null) {
60 var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf());
61 return new RelationalCursor(allMatches.iterator());
62 }
63 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
64 return matches == null ? Cursors.empty() : new RelationalCursor(matches.stream().iterator());
65 }
66
67 @Override
68 public int size() {
69 if (emptyMaskIndexer == null) {
70 return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf());
71 }
72 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
73 return matches == null ? 0 : matches.size();
74 }
75
76 @Override
77 public void update(tools.refinery.interpreter.matchers.tuple.Tuple updateElement, boolean isInsertion) {
78 var key = MatcherUtils.toRefineryTuple(updateElement);
79 notifyChange(key, !isInsertion, isInsertion);
80 }
81}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtils.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtils.java
new file mode 100644
index 00000000..b30b83b5
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtils.java
@@ -0,0 +1,115 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.matchers.tuple.ITuple;
9import tools.refinery.interpreter.matchers.tuple.Tuples;
10import org.jetbrains.annotations.Nullable;
11import tools.refinery.store.tuple.*;
12
13import java.util.Iterator;
14
15final class MatcherUtils {
16 private MatcherUtils() {
17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
18 }
19
20 public static tools.refinery.interpreter.matchers.tuple.Tuple toViatraTuple(Tuple refineryTuple) {
21 if (refineryTuple instanceof Tuple0) {
22 return Tuples.staticArityFlatTupleOf();
23 } else if (refineryTuple instanceof Tuple1) {
24 return Tuples.staticArityFlatTupleOf(refineryTuple);
25 } else if (refineryTuple instanceof Tuple2 tuple2) {
26 return Tuples.staticArityFlatTupleOf(Tuple.of(tuple2.value0()), Tuple.of(tuple2.value1()));
27 } else if (refineryTuple instanceof Tuple3 tuple3) {
28 return Tuples.staticArityFlatTupleOf(Tuple.of(tuple3.value0()), Tuple.of(tuple3.value1()),
29 Tuple.of(tuple3.value2()));
30 } else if (refineryTuple instanceof Tuple4 tuple4) {
31 return Tuples.staticArityFlatTupleOf(Tuple.of(tuple4.value0()), Tuple.of(tuple4.value1()),
32 Tuple.of(tuple4.value2()), Tuple.of(tuple4.value3()));
33 } else {
34 int arity = refineryTuple.getSize();
35 var values = new Object[arity];
36 for (int i = 0; i < arity; i++) {
37 values[i] = Tuple.of(refineryTuple.get(i));
38 }
39 return Tuples.flatTupleOf(values);
40 }
41 }
42
43 public static Tuple toRefineryTuple(ITuple viatraTuple) {
44 int arity = viatraTuple.getSize();
45 if (arity == 1) {
46 return getWrapper(viatraTuple, 0);
47 }
48 return prefixToRefineryTuple(viatraTuple, viatraTuple.getSize());
49 }
50
51 public static Tuple keyToRefineryTuple(ITuple viatraTuple) {
52 return prefixToRefineryTuple(viatraTuple, viatraTuple.getSize() - 1);
53 }
54
55 private static Tuple prefixToRefineryTuple(ITuple viatraTuple, int targetArity) {
56 if (targetArity < 0) {
57 throw new IllegalArgumentException("Requested negative prefix %d of %s"
58 .formatted(targetArity, viatraTuple));
59 }
60 return switch (targetArity) {
61 case 0 -> Tuple.of();
62 case 1 -> Tuple.of(unwrap(viatraTuple, 0));
63 case 2 -> Tuple.of(unwrap(viatraTuple, 0), unwrap(viatraTuple, 1));
64 case 3 -> Tuple.of(unwrap(viatraTuple, 0), unwrap(viatraTuple, 1), unwrap(viatraTuple, 2));
65 case 4 -> Tuple.of(unwrap(viatraTuple, 0), unwrap(viatraTuple, 1), unwrap(viatraTuple, 2),
66 unwrap(viatraTuple, 3));
67 default -> {
68 var entries = new int[targetArity];
69 for (int i = 0; i < targetArity; i++) {
70 entries[i] = unwrap(viatraTuple, i);
71 }
72 yield Tuple.of(entries);
73 }
74 };
75 }
76
77 private static Tuple1 getWrapper(ITuple viatraTuple, int index) {
78 if (!((viatraTuple.get(index)) instanceof Tuple1 wrappedObjectId)) {
79 throw new IllegalArgumentException("Element %d of tuple %s is not an object id"
80 .formatted(index, viatraTuple));
81 }
82 return wrappedObjectId;
83 }
84
85 private static int unwrap(ITuple viatraTuple, int index) {
86 return getWrapper(viatraTuple, index).value0();
87 }
88
89 public static <T> T getValue(ITuple match) {
90 // This is only safe if we know for sure that match came from a functional query of type {@code T}.
91 @SuppressWarnings("unchecked")
92 var result = (T) match.get(match.getSize() - 1);
93 return result;
94 }
95
96 public static <T> T getSingleValue(@Nullable Iterable<? extends ITuple> viatraTuples) {
97 if (viatraTuples == null) {
98 return null;
99 }
100 return getSingleValue(viatraTuples.iterator());
101 }
102
103 public static <T> T getSingleValue(Iterator<? extends ITuple> iterator) {
104 if (!iterator.hasNext()) {
105 return null;
106 }
107 var match = iterator.next();
108 var result = MatcherUtils.<T>getValue(match);
109 if (iterator.hasNext()) {
110 var input = keyToRefineryTuple(match);
111 throw new IllegalStateException("Query is not functional for input tuple: " + input);
112 }
113 return result;
114 }
115}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RawPatternMatcher.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RawPatternMatcher.java
new file mode 100644
index 00000000..fcd5a236
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RawPatternMatcher.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.api.GenericPatternMatcher;
9import tools.refinery.interpreter.api.GenericQuerySpecification;
10import tools.refinery.interpreter.matchers.backend.IQueryResultProvider;
11
12public class RawPatternMatcher extends GenericPatternMatcher {
13 public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
14 super(specification);
15 }
16
17 IQueryResultProvider getBackend() {
18 return backend;
19 }
20}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RelationalCursor.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RelationalCursor.java
new file mode 100644
index 00000000..45eb9fff
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/RelationalCursor.java
@@ -0,0 +1,47 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14class RelationalCursor implements Cursor<Tuple, Boolean> {
15 private final Iterator<? extends ITuple> tuplesIterator;
16 private boolean terminated;
17 private Tuple key;
18
19 public RelationalCursor(Iterator<? extends ITuple> tuplesIterator) {
20 this.tuplesIterator = tuplesIterator;
21 }
22
23 @Override
24 public Tuple getKey() {
25 return key;
26 }
27
28 @Override
29 public Boolean getValue() {
30 return true;
31 }
32
33 @Override
34 public boolean isTerminated() {
35 return terminated;
36 }
37
38 @Override
39 public boolean move() {
40 if (!terminated && tuplesIterator.hasNext()) {
41 key = MatcherUtils.toRefineryTuple(tuplesIterator.next());
42 return true;
43 }
44 terminated = true;
45 return false;
46 }
47}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/UnsafeFunctionalCursor.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/UnsafeFunctionalCursor.java
new file mode 100644
index 00000000..d3a7c743
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/matcher/UnsafeFunctionalCursor.java
@@ -0,0 +1,55 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14/**
15 * Cursor for a functional result set that iterates over a stream of raw matches and doesn't check whether the
16 * functional dependency of the output on the inputs is obeyed.
17 * @param <T> The output type.
18 */
19class UnsafeFunctionalCursor<T> implements Cursor<Tuple, T> {
20 private final Iterator<? extends ITuple> tuplesIterator;
21 private boolean terminated;
22 private Tuple key;
23 private T value;
24
25 public UnsafeFunctionalCursor(Iterator<? extends ITuple> tuplesIterator) {
26 this.tuplesIterator = tuplesIterator;
27 }
28
29 @Override
30 public Tuple getKey() {
31 return key;
32 }
33
34 @Override
35 public T getValue() {
36 return value;
37 }
38
39 @Override
40 public boolean isTerminated() {
41 return terminated;
42 }
43
44 @Override
45 public boolean move() {
46 if (!terminated && tuplesIterator.hasNext()) {
47 var match = tuplesIterator.next();
48 key = MatcherUtils.keyToRefineryTuple(match);
49 value = MatcherUtils.getValue(match);
50 return true;
51 }
52 terminated = true;
53 return false;
54 }
55}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/CheckEvaluator.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/CheckEvaluator.java
new file mode 100644
index 00000000..4a71e879
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/CheckEvaluator.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 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.Term;
10
11class CheckEvaluator extends TermEvaluator<Boolean> {
12 public CheckEvaluator(Term<Boolean> term) {
13 super(term);
14 }
15
16 @Override
17 public Object evaluateExpression(IValueProvider provider) {
18 var result = super.evaluateExpression(provider);
19 return result == null ? Boolean.FALSE : result;
20 }
21}
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
new file mode 100644
index 00000000..73ce4043
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java
@@ -0,0 +1,253 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.matchers.psystem.basicdeferred.*;
9import tools.refinery.interpreter.matchers.psystem.basicenumerables.*;
10import tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity;
11import tools.refinery.store.query.Constraint;
12import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.dnf.DnfClause;
14import tools.refinery.store.query.dnf.SymbolicParameter;
15import tools.refinery.store.query.literal.*;
16import tools.refinery.store.query.term.ConstantTerm;
17import tools.refinery.store.query.term.StatefulAggregator;
18import tools.refinery.store.query.term.StatelessAggregator;
19import tools.refinery.store.query.term.Variable;
20import tools.refinery.store.query.view.AnySymbolView;
21import tools.refinery.store.util.CycleDetectingMapper;
22import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory;
23import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
24import tools.refinery.interpreter.matchers.context.IInputKey;
25import tools.refinery.interpreter.matchers.psystem.PBody;
26import tools.refinery.interpreter.matchers.psystem.PVariable;
27import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator;
28import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator;
29import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation;
30import tools.refinery.interpreter.matchers.psystem.queries.PParameter;
31import tools.refinery.interpreter.matchers.psystem.queries.PParameterDirection;
32import tools.refinery.interpreter.matchers.psystem.queries.PQuery;
33import tools.refinery.interpreter.matchers.tuple.Tuple;
34import tools.refinery.interpreter.matchers.tuple.Tuples;
35
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.function.Function;
41
42public class Dnf2PQuery {
43 private final CycleDetectingMapper<Dnf, RawPQuery> mapper = new CycleDetectingMapper<>(Dnf::name,
44 this::doTranslate);
45 private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this);
46 private Function<Dnf, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null,
47 (IQueryBackendFactory) null);
48
49 public void setComputeHint(Function<Dnf, QueryEvaluationHint> computeHint) {
50 this.computeHint = computeHint;
51 }
52
53 public RawPQuery translate(Dnf dnfQuery) {
54 return mapper.map(dnfQuery);
55 }
56
57 public Map<AnySymbolView, IInputKey> getSymbolViews() {
58 return wrapperFactory.getSymbolViews();
59 }
60
61 private RawPQuery doTranslate(Dnf dnfQuery) {
62 var pQuery = new RawPQuery(dnfQuery.getUniqueName());
63 pQuery.setEvaluationHints(computeHint.apply(dnfQuery));
64
65 Map<SymbolicParameter, PParameter> parameters = new HashMap<>();
66 List<PParameter> parameterList = new ArrayList<>();
67 for (var parameter : dnfQuery.getSymbolicParameters()) {
68 var direction = switch (parameter.getDirection()) {
69 case OUT -> PParameterDirection.INOUT;
70 case IN -> throw new IllegalArgumentException("Query %s with input parameter %s is not supported"
71 .formatted(dnfQuery, parameter.getVariable()));
72 };
73 var pParameter = new PParameter(parameter.getVariable().getUniqueName(), null, null, direction);
74 parameters.put(parameter, pParameter);
75 parameterList.add(pParameter);
76 }
77
78 pQuery.setParameters(parameterList);
79
80 for (var functionalDependency : dnfQuery.getFunctionalDependencies()) {
81 var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency");
82 for (var forEachVariable : functionalDependency.forEach()) {
83 functionalDependencyAnnotation.addAttribute("forEach", forEachVariable.getUniqueName());
84 }
85 for (var uniqueVariable : functionalDependency.unique()) {
86 functionalDependencyAnnotation.addAttribute("unique", uniqueVariable.getUniqueName());
87 }
88 pQuery.addAnnotation(functionalDependencyAnnotation);
89 }
90
91 for (DnfClause clause : dnfQuery.getClauses()) {
92 PBody body = new PBody(pQuery);
93 List<ExportedParameter> parameterExports = new ArrayList<>();
94 for (var parameter : dnfQuery.getSymbolicParameters()) {
95 PVariable pVar = body.getOrCreateVariableByName(parameter.getVariable().getUniqueName());
96 parameterExports.add(new ExportedParameter(body, pVar, parameters.get(parameter)));
97 }
98 body.setSymbolicParameters(parameterExports);
99 pQuery.addBody(body);
100 for (Literal literal : clause.literals()) {
101 translateLiteral(literal, body);
102 }
103 }
104
105 return pQuery;
106 }
107
108 private void translateLiteral(Literal literal, PBody body) {
109 if (literal instanceof EquivalenceLiteral equivalenceLiteral) {
110 translateEquivalenceLiteral(equivalenceLiteral, body);
111 } else if (literal instanceof CallLiteral callLiteral) {
112 translateCallLiteral(callLiteral, body);
113 } else if (literal instanceof ConstantLiteral constantLiteral) {
114 translateConstantLiteral(constantLiteral, body);
115 } else if (literal instanceof AssignLiteral<?> assignLiteral) {
116 translateAssignLiteral(assignLiteral, body);
117 } else if (literal instanceof CheckLiteral checkLiteral) {
118 translateCheckLiteral(checkLiteral, body);
119 } else if (literal instanceof CountLiteral countLiteral) {
120 translateCountLiteral(countLiteral, body);
121 } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) {
122 translateAggregationLiteral(aggregationLiteral, body);
123 } else if (literal instanceof RepresentativeElectionLiteral representativeElectionLiteral) {
124 translateRepresentativeElectionLiteral(representativeElectionLiteral, body);
125 } else {
126 throw new IllegalArgumentException("Unknown literal: " + literal.toString());
127 }
128 }
129
130 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) {
131 PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.getLeft().getUniqueName());
132 PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.getRight().getUniqueName());
133 if (equivalenceLiteral.isPositive()) {
134 new Equality(body, varSource, varTarget);
135 } else {
136 new Inequality(body, varSource, varTarget);
137 }
138 }
139
140 private void translateCallLiteral(CallLiteral callLiteral, PBody body) {
141 var polarity = callLiteral.getPolarity();
142 switch (polarity) {
143 case POSITIVE -> {
144 var substitution = translateSubstitution(callLiteral.getArguments(), body);
145 var constraint = callLiteral.getTarget();
146 if (constraint instanceof Dnf dnf) {
147 var pattern = translate(dnf);
148 new PositivePatternCall(body, substitution, pattern);
149 } else if (constraint instanceof AnySymbolView symbolView) {
150 var inputKey = wrapperFactory.getInputKey(symbolView);
151 new TypeConstraint(body, substitution, inputKey);
152 } else {
153 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
154 }
155 }
156 case TRANSITIVE -> {
157 var substitution = translateSubstitution(callLiteral.getArguments(), body);
158 var pattern = wrapConstraintWithIdentityArguments(callLiteral.getTarget());
159 new BinaryTransitiveClosure(body, substitution, pattern);
160 }
161 case NEGATIVE -> {
162 var wrappedCall = wrapperFactory.maybeWrapConstraint(callLiteral);
163 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
164 var pattern = wrappedCall.pattern();
165 new NegativePatternCall(body, substitution, pattern);
166 }
167 default -> throw new IllegalArgumentException("Unknown polarity: " + polarity);
168 }
169 }
170
171 private PQuery wrapConstraintWithIdentityArguments(Constraint constraint) {
172 if (constraint instanceof Dnf dnf) {
173 return translate(dnf);
174 } else if (constraint instanceof AnySymbolView symbolView) {
175 return wrapperFactory.wrapSymbolViewIdentityArguments(symbolView);
176 } else {
177 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
178 }
179 }
180
181 private static Tuple translateSubstitution(List<Variable> substitution, PBody body) {
182 int arity = substitution.size();
183 Object[] variables = new Object[arity];
184 for (int i = 0; i < arity; i++) {
185 var variable = substitution.get(i);
186 variables[i] = body.getOrCreateVariableByName(variable.getUniqueName());
187 }
188 return Tuples.flatTupleOf(variables);
189 }
190
191 private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) {
192 var variable = body.getOrCreateVariableByName(constantLiteral.getVariable().getUniqueName());
193 new ConstantValue(body, variable, tools.refinery.store.tuple.Tuple.of(constantLiteral.getNodeId()));
194 }
195
196 private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) {
197 var variable = body.getOrCreateVariableByName(assignLiteral.getVariable().getUniqueName());
198 var term = assignLiteral.getTerm();
199 if (term instanceof ConstantTerm<T> constantTerm) {
200 new ConstantValue(body, variable, constantTerm.getValue());
201 } else {
202 var evaluator = new TermEvaluator<>(term);
203 new ExpressionEvaluation(body, evaluator, variable);
204 }
205 }
206
207 private void translateCheckLiteral(CheckLiteral checkLiteral, PBody body) {
208 var evaluator = new CheckEvaluator(checkLiteral.getTerm());
209 new ExpressionEvaluation(body, evaluator, null);
210 }
211
212 private void translateCountLiteral(CountLiteral countLiteral, PBody body) {
213 var wrappedCall = wrapperFactory.maybeWrapConstraint(countLiteral);
214 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
215 var resultVariable = body.getOrCreateVariableByName(countLiteral.getResultVariable().getUniqueName());
216 new PatternMatchCounter(body, substitution, wrappedCall.pattern(), resultVariable);
217 }
218
219 private <R, T> void translateAggregationLiteral(AggregationLiteral<R, T> aggregationLiteral, PBody body) {
220 var aggregator = aggregationLiteral.getAggregator();
221 IMultisetAggregationOperator<T, ?, R> aggregationOperator;
222 if (aggregator instanceof StatelessAggregator<R, T> statelessAggregator) {
223 aggregationOperator = new StatelessMultisetAggregator<>(statelessAggregator);
224 } else if (aggregator instanceof StatefulAggregator<R, T> statefulAggregator) {
225 aggregationOperator = new StatefulMultisetAggregator<>(statefulAggregator);
226 } else {
227 throw new IllegalArgumentException("Unknown aggregator: " + aggregator);
228 }
229 var wrappedCall = wrapperFactory.maybeWrapConstraint(aggregationLiteral);
230 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
231 var inputVariable = body.getOrCreateVariableByName(aggregationLiteral.getInputVariable().getUniqueName());
232 var aggregatedColumn = substitution.invertIndex().get(inputVariable);
233 if (aggregatedColumn == null) {
234 throw new IllegalStateException("Input variable %s not found in substitution %s".formatted(inputVariable,
235 substitution));
236 }
237 var boundAggregator = new BoundAggregator(aggregationOperator, aggregator.getInputType(),
238 aggregator.getResultType());
239 var resultVariable = body.getOrCreateVariableByName(aggregationLiteral.getResultVariable().getUniqueName());
240 new AggregatorConstraint(boundAggregator, body, substitution, wrappedCall.pattern(), resultVariable,
241 aggregatedColumn);
242 }
243
244 private void translateRepresentativeElectionLiteral(RepresentativeElectionLiteral literal, PBody body) {
245 var substitution = translateSubstitution(literal.getArguments(), body);
246 var pattern = wrapConstraintWithIdentityArguments(literal.getTarget());
247 var connectivity = switch (literal.getConnectivity()) {
248 case WEAK -> Connectivity.WEAK;
249 case STRONG -> Connectivity.STRONG;
250 };
251 new RepresentativeElectionConstraint(body, substitution, pattern, connectivity);
252 }
253}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/QueryWrapperFactory.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/QueryWrapperFactory.java
new file mode 100644
index 00000000..a710dab3
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/QueryWrapperFactory.java
@@ -0,0 +1,189 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.matchers.context.IInputKey;
9import tools.refinery.interpreter.matchers.psystem.PBody;
10import tools.refinery.interpreter.matchers.psystem.PVariable;
11import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExportedParameter;
12import tools.refinery.interpreter.matchers.psystem.basicenumerables.PositivePatternCall;
13import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint;
14import tools.refinery.interpreter.matchers.psystem.queries.PParameter;
15import tools.refinery.interpreter.matchers.psystem.queries.PQuery;
16import tools.refinery.interpreter.matchers.psystem.queries.PVisibility;
17import tools.refinery.interpreter.matchers.tuple.Tuple;
18import tools.refinery.interpreter.matchers.tuple.Tuples;
19import tools.refinery.store.query.Constraint;
20import tools.refinery.store.query.dnf.Dnf;
21import tools.refinery.store.query.dnf.DnfUtils;
22import tools.refinery.store.query.literal.AbstractCallLiteral;
23import tools.refinery.store.query.term.ParameterDirection;
24import tools.refinery.store.query.term.Variable;
25import tools.refinery.store.query.view.AnySymbolView;
26import tools.refinery.store.query.view.SymbolView;
27import tools.refinery.store.util.CycleDetectingMapper;
28
29import java.util.*;
30import java.util.function.ToIntFunction;
31
32class QueryWrapperFactory {
33 private final Dnf2PQuery dnf2PQuery;
34 private final Map<AnySymbolView, SymbolViewWrapper> view2WrapperMap = new LinkedHashMap<>();
35 private final CycleDetectingMapper<RemappedConstraint, RawPQuery> wrapConstraint = new CycleDetectingMapper<>(
36 this::doWrapConstraint);
37
38 QueryWrapperFactory(Dnf2PQuery dnf2PQuery) {
39 this.dnf2PQuery = dnf2PQuery;
40 }
41
42 public PQuery wrapSymbolViewIdentityArguments(AnySymbolView symbolView) {
43 var identity = new int[symbolView.arity()];
44 for (int i = 0; i < identity.length; i++) {
45 identity[i] = i;
46 }
47 return maybeWrapConstraint(symbolView, identity);
48 }
49
50 public WrappedCall maybeWrapConstraint(AbstractCallLiteral callLiteral) {
51 var arguments = callLiteral.getArguments();
52 int arity = arguments.size();
53 var remappedParameters = new int[arity];
54 var unboundVariableIndices = new HashMap<Variable, Integer>();
55 var appendVariable = new VariableAppender();
56 for (int i = 0; i < arity; i++) {
57 var variable = arguments.get(i);
58 // Unify all variables to avoid Refinery Interpreter bugs, even if they're bound in the containing clause.
59 remappedParameters[i] = unboundVariableIndices.computeIfAbsent(variable, appendVariable::applyAsInt);
60 }
61 var pattern = maybeWrapConstraint(callLiteral.getTarget(), remappedParameters);
62 return new WrappedCall(pattern, appendVariable.getRemappedArguments());
63 }
64
65 private PQuery maybeWrapConstraint(Constraint constraint, int[] remappedParameters) {
66 if (remappedParameters.length != constraint.arity()) {
67 throw new IllegalArgumentException("Constraint %s expected %d parameters, but got %d parameters".formatted(
68 constraint, constraint.arity(), remappedParameters.length));
69 }
70 if (constraint instanceof Dnf dnf && isIdentity(remappedParameters)) {
71 return dnf2PQuery.translate(dnf);
72 }
73 return wrapConstraint.map(new RemappedConstraint(constraint, remappedParameters));
74 }
75
76 private static boolean isIdentity(int[] remappedParameters) {
77 for (int i = 0; i < remappedParameters.length; i++) {
78 if (remappedParameters[i] != i) {
79 return false;
80 }
81 }
82 return true;
83 }
84
85 private RawPQuery doWrapConstraint(RemappedConstraint remappedConstraint) {
86 var constraint = remappedConstraint.constraint();
87 var remappedParameters = remappedConstraint.remappedParameters();
88
89 checkNoInputParameters(constraint);
90
91 var embeddedPQuery = new RawPQuery(DnfUtils.generateUniqueName(constraint.name()), PVisibility.EMBEDDED);
92 var body = new PBody(embeddedPQuery);
93 int arity = Arrays.stream(remappedParameters).max().orElse(-1) + 1;
94 var parameters = new ArrayList<PParameter>(arity);
95 var parameterVariables = new PVariable[arity];
96 var symbolicParameters = new ArrayList<ExportedParameter>(arity);
97 for (int i = 0; i < arity; i++) {
98 var parameterName = "p" + i;
99 var parameter = new PParameter(parameterName);
100 parameters.add(parameter);
101 var variable = body.getOrCreateVariableByName(parameterName);
102 parameterVariables[i] = variable;
103 symbolicParameters.add(new ExportedParameter(body, variable, parameter));
104 }
105 embeddedPQuery.setParameters(parameters);
106 body.setSymbolicParameters(symbolicParameters);
107
108 var arguments = new Object[remappedParameters.length];
109 for (int i = 0; i < remappedParameters.length; i++) {
110 arguments[i] = parameterVariables[remappedParameters[i]];
111 }
112 var argumentTuple = Tuples.flatTupleOf(arguments);
113
114 addPositiveConstraint(constraint, body, argumentTuple);
115 embeddedPQuery.addBody(body);
116 return embeddedPQuery;
117 }
118
119 private static void checkNoInputParameters(Constraint constraint) {
120 for (var constraintParameter : constraint.getParameters()) {
121 if (constraintParameter.getDirection() == ParameterDirection.IN) {
122 throw new IllegalArgumentException("Input parameter %s of %s is not supported"
123 .formatted(constraintParameter, constraint));
124 }
125 }
126 }
127
128 private void addPositiveConstraint(Constraint constraint, PBody body, Tuple argumentTuple) {
129 if (constraint instanceof SymbolView<?> view) {
130 new TypeConstraint(body, argumentTuple, getInputKey(view));
131 } else if (constraint instanceof Dnf dnf) {
132 var calledPQuery = dnf2PQuery.translate(dnf);
133 new PositivePatternCall(body, argumentTuple, calledPQuery);
134 } else {
135 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
136 }
137 }
138
139 public IInputKey getInputKey(AnySymbolView symbolView) {
140 return view2WrapperMap.computeIfAbsent(symbolView, SymbolViewWrapper::new);
141 }
142
143 public Map<AnySymbolView, IInputKey> getSymbolViews() {
144 return Collections.unmodifiableMap(view2WrapperMap);
145 }
146
147 public record WrappedCall(PQuery pattern, List<Variable> remappedArguments) {
148 }
149
150 private static class VariableAppender implements ToIntFunction<Variable> {
151 private final List<Variable> remappedArguments = new ArrayList<>();
152 private int nextIndex = 0;
153
154 @Override
155 public int applyAsInt(Variable variable) {
156 remappedArguments.add(variable);
157 int index = nextIndex;
158 nextIndex++;
159 return index;
160 }
161
162 public List<Variable> getRemappedArguments() {
163 return remappedArguments;
164 }
165 }
166
167 private record RemappedConstraint(Constraint constraint, int[] remappedParameters) {
168 @Override
169 public boolean equals(Object o) {
170 if (this == o) return true;
171 if (o == null || getClass() != o.getClass()) return false;
172 RemappedConstraint that = (RemappedConstraint) o;
173 return constraint.equals(that.constraint) && Arrays.equals(remappedParameters, that.remappedParameters);
174 }
175
176 @Override
177 public int hashCode() {
178 int result = Objects.hash(constraint);
179 result = 31 * result + Arrays.hashCode(remappedParameters);
180 return result;
181 }
182
183 @Override
184 public String toString() {
185 return "RemappedConstraint{constraint=%s, remappedParameters=%s}".formatted(constraint,
186 Arrays.toString(remappedParameters));
187 }
188 }
189}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/RawPQuery.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/RawPQuery.java
new file mode 100644
index 00000000..bbb35f91
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/RawPQuery.java
@@ -0,0 +1,87 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.api.GenericQuerySpecification;
9import tools.refinery.interpreter.api.InterpreterEngine;
10import tools.refinery.interpreter.api.scope.QueryScope;
11import tools.refinery.interpreter.matchers.psystem.PBody;
12import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation;
13import tools.refinery.interpreter.matchers.psystem.queries.BasePQuery;
14import tools.refinery.interpreter.matchers.psystem.queries.PParameter;
15import tools.refinery.interpreter.matchers.psystem.queries.PVisibility;
16import tools.refinery.store.query.interpreter.internal.RelationalScope;
17import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher;
18
19import java.util.LinkedHashSet;
20import java.util.List;
21import java.util.Set;
22
23public class RawPQuery extends BasePQuery {
24 private final String fullyQualifiedName;
25 private List<PParameter> parameters;
26 private final LinkedHashSet<PBody> bodies = new LinkedHashSet<>();
27
28 public RawPQuery(String name, PVisibility visibility) {
29 super(visibility);
30 fullyQualifiedName = name;
31 }
32
33 public RawPQuery(String name) {
34 this(name, PVisibility.PUBLIC);
35 }
36
37 @Override
38 public String getFullyQualifiedName() {
39 return fullyQualifiedName;
40 }
41
42 public void setParameters(List<PParameter> parameters) {
43 this.parameters = parameters;
44 }
45
46 @Override
47 public void addAnnotation(PAnnotation annotation) {
48 super.addAnnotation(annotation);
49 }
50
51 @Override
52 public List<PParameter> getParameters() {
53 return parameters;
54 }
55
56 public void addBody(PBody body) {
57 bodies.add(body);
58 }
59
60 @Override
61 protected Set<PBody> doGetContainedBodies() {
62 return bodies;
63 }
64
65 public GenericQuerySpecification<RawPatternMatcher> build() {
66 return new GenericQuerySpecification<>(this) {
67 @Override
68 public Class<? extends QueryScope> getPreferredScopeClass() {
69 return RelationalScope.class;
70 }
71
72 @Override
73 protected RawPatternMatcher instantiate(InterpreterEngine engine) {
74 RawPatternMatcher matcher = engine.getExistingMatcher(this);
75 if (matcher == null) {
76 matcher = engine.getMatcher(this);
77 }
78 return matcher;
79 }
80
81 @Override
82 public RawPatternMatcher instantiate() {
83 return new RawPatternMatcher(this);
84 }
85 };
86 }
87}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatefulMultisetAggregator.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatefulMultisetAggregator.java
new file mode 100644
index 00000000..7552117b
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatefulMultisetAggregator.java
@@ -0,0 +1,65 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatefulAggregate;
10import tools.refinery.store.query.term.StatefulAggregator;
11
12import java.util.stream.Stream;
13
14record StatefulMultisetAggregator<R, T>(StatefulAggregator<R, T> aggregator)
15 implements IMultisetAggregationOperator<T, StatefulAggregate<R, T>, R> {
16 @Override
17 public String getShortDescription() {
18 return getName();
19 }
20
21 @Override
22 public String getName() {
23 return aggregator.toString();
24 }
25
26 @Override
27 public StatefulAggregate<R, T> createNeutral() {
28 return aggregator.createEmptyAggregate();
29 }
30
31 @Override
32 public boolean isNeutral(StatefulAggregate<R, T> result) {
33 return result.isEmpty();
34 }
35
36 @Override
37 public StatefulAggregate<R, T> update(StatefulAggregate<R, T> oldResult, T updateValue, boolean isInsertion) {
38 if (isInsertion) {
39 oldResult.add(updateValue);
40 } else {
41 oldResult.remove(updateValue);
42 }
43 return oldResult;
44 }
45
46 @Override
47 public R getAggregate(StatefulAggregate<R, T> result) {
48 return result.getResult();
49 }
50
51 @Override
52 public R aggregateStream(Stream<T> stream) {
53 return aggregator.aggregateStream(stream);
54 }
55
56 @Override
57 public StatefulAggregate<R, T> clone(StatefulAggregate<R, T> original) {
58 return original.deepCopy();
59 }
60
61 @Override
62 public boolean contains(T value, StatefulAggregate<R, T> accumulator) {
63 return accumulator.contains(value);
64 }
65}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatelessMultisetAggregator.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatelessMultisetAggregator.java
new file mode 100644
index 00000000..2da7ba87
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/StatelessMultisetAggregator.java
@@ -0,0 +1,55 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatelessAggregator;
10
11import java.util.stream.Stream;
12
13record StatelessMultisetAggregator<R, T>(StatelessAggregator<R, T> aggregator)
14 implements IMultisetAggregationOperator<T, R, R> {
15 @Override
16 public String getShortDescription() {
17 return getName();
18 }
19
20 @Override
21 public String getName() {
22 return aggregator.toString();
23 }
24
25 @Override
26 public R createNeutral() {
27 return aggregator.getEmptyResult();
28 }
29
30 @Override
31 public boolean isNeutral(R result) {
32 return createNeutral().equals(result);
33 }
34
35 @Override
36 public R update(R oldResult, T updateValue, boolean isInsertion) {
37 return isInsertion ? aggregator.add(oldResult, updateValue) : aggregator.remove(oldResult, updateValue);
38 }
39
40 @Override
41 public R getAggregate(R result) {
42 return result;
43 }
44
45 @Override
46 public R clone(R original) {
47 // Aggregate result is immutable.
48 return original;
49 }
50
51 @Override
52 public R aggregateStream(Stream<T> stream) {
53 return aggregator.aggregateStream(stream);
54 }
55}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/SymbolViewWrapper.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/SymbolViewWrapper.java
new file mode 100644
index 00000000..51795f4a
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/SymbolViewWrapper.java
@@ -0,0 +1,40 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.matchers.context.common.BaseInputKeyWrapper;
9import tools.refinery.store.query.view.AnySymbolView;
10
11public class SymbolViewWrapper extends BaseInputKeyWrapper<AnySymbolView> {
12 public SymbolViewWrapper(AnySymbolView wrappedKey) {
13 super(wrappedKey);
14 }
15
16 @Override
17 public String getPrettyPrintableName() {
18 return wrappedKey.name();
19 }
20
21 @Override
22 public String getStringID() {
23 return getPrettyPrintableName();
24 }
25
26 @Override
27 public int getArity() {
28 return wrappedKey.arity();
29 }
30
31 @Override
32 public boolean isEnumerable() {
33 return true;
34 }
35
36 @Override
37 public String toString() {
38 return "RelationViewWrapper{wrappedKey=%s}".formatted(wrappedKey);
39 }
40}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/TermEvaluator.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/TermEvaluator.java
new file mode 100644
index 00000000..ed991091
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/TermEvaluator.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.store.query.term.Term;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.interpreter.matchers.psystem.IExpressionEvaluator;
11import tools.refinery.interpreter.matchers.psystem.IValueProvider;
12
13import java.util.stream.Collectors;
14
15class TermEvaluator<T> implements IExpressionEvaluator {
16 private final Term<T> term;
17
18 public TermEvaluator(Term<T> term) {
19 this.term = term;
20 }
21
22 @Override
23 public String getShortDescription() {
24 return term.toString();
25 }
26
27 @Override
28 public Iterable<String> getInputParameterNames() {
29 return term.getInputVariables().stream().map(Variable::getUniqueName).collect(Collectors.toUnmodifiableSet());
30 }
31
32 @Override
33 public Object evaluateExpression(IValueProvider provider) {
34 var valuation = new ValueProviderBasedValuation(provider);
35 return term.evaluate(valuation);
36 }
37}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/ValueProviderBasedValuation.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/ValueProviderBasedValuation.java
new file mode 100644
index 00000000..4124c9bb
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/ValueProviderBasedValuation.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.pquery;
7
8import tools.refinery.interpreter.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.valuation.Valuation;
11
12public record ValueProviderBasedValuation(IValueProvider valueProvider) implements Valuation {
13 @Override
14 public <T> T getValue(DataVariable<T> variable) {
15 @SuppressWarnings("unchecked")
16 var value = (T) valueProvider.getValue(variable.getUniqueName());
17 return value;
18 }
19}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/ModelUpdateListener.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/ModelUpdateListener.java
new file mode 100644
index 00000000..fad53675
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/ModelUpdateListener.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.update;
7
8import tools.refinery.interpreter.matchers.context.IInputKey;
9import tools.refinery.interpreter.matchers.context.IQueryRuntimeContextListener;
10import tools.refinery.interpreter.matchers.tuple.ITuple;
11import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
12import tools.refinery.store.query.view.AnySymbolView;
13import tools.refinery.store.query.view.SymbolView;
14
15import java.util.HashMap;
16import java.util.Map;
17
18public class ModelUpdateListener {
19 private final Map<AnySymbolView, SymbolViewUpdateListener<?>> symbolViewUpdateListeners;
20
21 public ModelUpdateListener(QueryInterpreterAdapterImpl adapter) {
22 var symbolViews = adapter.getStoreAdapter().getInputKeys().keySet();
23 symbolViewUpdateListeners = new HashMap<>(symbolViews.size());
24 for (var symbolView : symbolViews) {
25 registerView(adapter, (SymbolView<?>) symbolView);
26 }
27 }
28
29 private <T> void registerView(QueryInterpreterAdapterImpl adapter, SymbolView<T> view) {
30 var model = adapter.getModel();
31 var interpretation = model.getInterpretation(view.getSymbol());
32 var listener = SymbolViewUpdateListener.of(adapter, view, interpretation);
33 symbolViewUpdateListeners.put(view, listener);
34 }
35
36 public boolean containsSymbolView(AnySymbolView relationView) {
37 return symbolViewUpdateListeners.containsKey(relationView);
38 }
39
40 public void addListener(IInputKey key, AnySymbolView symbolView, ITuple seed,
41 IQueryRuntimeContextListener listener) {
42 var symbolViewUpdateListener = symbolViewUpdateListeners.get(symbolView);
43 symbolViewUpdateListener.addFilter(key, seed, listener);
44 }
45
46 public void removeListener(IInputKey key, AnySymbolView symbolView, ITuple seed,
47 IQueryRuntimeContextListener listener) {
48 var symbolViewUpdateListener = symbolViewUpdateListeners.get(symbolView);
49 symbolViewUpdateListener.removeFilter(key, seed, listener);
50 }
51}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/RelationViewFilter.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/RelationViewFilter.java
new file mode 100644
index 00000000..4b4c73eb
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/RelationViewFilter.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.update;
7
8import tools.refinery.interpreter.matchers.context.IInputKey;
9import tools.refinery.interpreter.matchers.context.IQueryRuntimeContextListener;
10import tools.refinery.interpreter.matchers.tuple.ITuple;
11import tools.refinery.interpreter.matchers.tuple.Tuple;
12
13import java.util.Arrays;
14import java.util.Objects;
15
16public final class RelationViewFilter {
17 private final IInputKey inputKey;
18 private final Object[] seed;
19 private final IQueryRuntimeContextListener listener;
20
21 public RelationViewFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) {
22 this.inputKey = inputKey;
23 this.seed = seedToArray(seed);
24 this.listener = listener;
25 }
26
27 public void update(Tuple updateTuple, boolean isInsertion) {
28 if (isMatching(updateTuple)) {
29 listener.update(inputKey, updateTuple, isInsertion);
30 }
31 }
32
33 private boolean isMatching(ITuple tuple) {
34 if (seed == null) {
35 return true;
36 }
37 int size = seed.length;
38 for (int i = 0; i < size; i++) {
39 var filterElement = seed[i];
40 if (filterElement != null && !filterElement.equals(tuple.get(i))) {
41 return false;
42 }
43 }
44 return true;
45 }
46
47 // Use <code>null</code> instead of an empty array to speed up comparisons.
48 @SuppressWarnings("squid:S1168")
49 private static Object[] seedToArray(ITuple seed) {
50 for (var element : seed.getElements()) {
51 if (element != null) {
52 return seed.getElements();
53 }
54 }
55 return null;
56 }
57
58 @Override
59 public boolean equals(Object obj) {
60 if (obj == this) return true;
61 if (obj == null || obj.getClass() != this.getClass()) return false;
62 var that = (RelationViewFilter) obj;
63 return Objects.equals(this.inputKey, that.inputKey) && Arrays.equals(this.seed, that.seed) &&
64 Objects.equals(this.listener, that.listener);
65 }
66
67 @Override
68 public int hashCode() {
69 return Objects.hash(inputKey, Arrays.hashCode(seed), listener);
70 }
71}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/SymbolViewUpdateListener.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/SymbolViewUpdateListener.java
new file mode 100644
index 00000000..68020b11
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/SymbolViewUpdateListener.java
@@ -0,0 +1,65 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.update;
7
8import tools.refinery.interpreter.matchers.context.IInputKey;
9import tools.refinery.interpreter.matchers.context.IQueryRuntimeContextListener;
10import tools.refinery.interpreter.matchers.tuple.ITuple;
11import tools.refinery.interpreter.matchers.tuple.Tuple;
12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.model.InterpretationListener;
14import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
15import tools.refinery.store.query.view.SymbolView;
16import tools.refinery.store.query.view.TuplePreservingView;
17
18import java.util.ArrayList;
19import java.util.List;
20
21public abstract class SymbolViewUpdateListener<T> implements InterpretationListener<T> {
22 private final QueryInterpreterAdapterImpl adapter;
23 private final Interpretation<T> interpretation;
24 private final List<RelationViewFilter> filters = new ArrayList<>();
25
26 protected SymbolViewUpdateListener(QueryInterpreterAdapterImpl adapter, Interpretation<T> interpretation) {
27 this.adapter = adapter;
28 this.interpretation = interpretation;
29 }
30
31 public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) {
32 if (filters.isEmpty()) {
33 // First filter to be added, from now on we have to subscribe to model updates.
34 interpretation.addListener(this, true);
35 }
36 filters.add(new RelationViewFilter(inputKey, seed, listener));
37 }
38
39 public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) {
40 if (filters.remove(new RelationViewFilter(inputKey, seed, listener)) && filters.isEmpty()) {
41 // Last listener to be added, we don't have be subscribed to model updates anymore.
42 interpretation.removeListener(this);
43 }
44 }
45
46 protected void processUpdate(Tuple tuple, boolean isInsertion) {
47 adapter.markAsPending();
48 int size = filters.size();
49 // Use a for loop instead of a for-each loop to avoid <code>Iterator</code> allocation overhead.
50 //noinspection ForLoopReplaceableByForEach
51 for (int i = 0; i < size; i++) {
52 filters.get(i).update(tuple, isInsertion);
53 }
54 }
55
56 public static <T> SymbolViewUpdateListener<T> of(QueryInterpreterAdapterImpl adapter,
57 SymbolView<T> view,
58 Interpretation<T> interpretation) {
59 if (view instanceof TuplePreservingView<T> tuplePreservingRelationView) {
60 return new TuplePreservingViewUpdateListener<>(adapter, tuplePreservingRelationView,
61 interpretation);
62 }
63 return new TupleChangingViewUpdateListener<>(adapter, view, interpretation);
64 }
65}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TupleChangingViewUpdateListener.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TupleChangingViewUpdateListener.java
new file mode 100644
index 00000000..13b4af80
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TupleChangingViewUpdateListener.java
@@ -0,0 +1,45 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.update;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
10import tools.refinery.store.query.view.SymbolView;
11import tools.refinery.store.tuple.Tuple;
12import tools.refinery.interpreter.matchers.tuple.Tuples;
13
14import java.util.Arrays;
15
16public class TupleChangingViewUpdateListener<T> extends SymbolViewUpdateListener<T> {
17 private final SymbolView<T> view;
18
19 TupleChangingViewUpdateListener(QueryInterpreterAdapterImpl adapter, SymbolView<T> view,
20 Interpretation<T> interpretation) {
21 super(adapter, interpretation);
22 this.view = view;
23 }
24
25 @Override
26 public void put(Tuple key, T fromValue, T toValue, boolean restoring) {
27 boolean fromPresent = view.filter(key, fromValue);
28 boolean toPresent = view.filter(key, toValue);
29 if (fromPresent) {
30 var fromArray = view.forwardMap(key, fromValue);
31 if (toPresent) { // value change
32 var toArray = view.forwardMap(key, toValue);
33 if (!Arrays.equals(fromArray, toArray)) {
34 processUpdate(Tuples.flatTupleOf(fromArray), false);
35 processUpdate(Tuples.flatTupleOf(toArray), true);
36 }
37 } else { // fromValue disappears
38 processUpdate(Tuples.flatTupleOf(fromArray), false);
39 }
40 } else if (toPresent) { // toValue appears
41 var toArray = view.forwardMap(key, toValue);
42 processUpdate(Tuples.flatTupleOf(toArray), true);
43 }
44 }
45}
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TuplePreservingViewUpdateListener.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TuplePreservingViewUpdateListener.java
new file mode 100644
index 00000000..c9f69145
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/update/TuplePreservingViewUpdateListener.java
@@ -0,0 +1,33 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter.internal.update;
7
8import tools.refinery.interpreter.matchers.tuple.Tuples;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl;
11import tools.refinery.store.query.view.TuplePreservingView;
12import tools.refinery.store.tuple.Tuple;
13
14public class TuplePreservingViewUpdateListener<T> extends SymbolViewUpdateListener<T> {
15 private final TuplePreservingView<T> view;
16
17 TuplePreservingViewUpdateListener(QueryInterpreterAdapterImpl adapter, TuplePreservingView<T> view,
18 Interpretation<T> interpretation) {
19 super(adapter, interpretation);
20 this.view = view;
21 }
22
23 @Override
24 public void put(Tuple key, T fromValue, T toValue, boolean restoring) {
25 boolean fromPresent = view.filter(key, fromValue);
26 boolean toPresent = view.filter(key, toValue);
27 if (fromPresent == toPresent) {
28 return;
29 }
30 var translated = Tuples.flatTupleOf(view.forwardMap(key));
31 processUpdate(translated, toPresent);
32 }
33}
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 */
6package tools.refinery.store.query.interpreter;
7
8import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.interpreter.tests.QueryEngineTest;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.FunctionView;
16import tools.refinery.store.query.view.KeyOnlyView;
17import tools.refinery.store.representation.Symbol;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.List;
21import java.util.Map;
22import java.util.Optional;
23
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
26import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults;
27import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults;
28
29class 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 */
6package tools.refinery.store.query.interpreter;
7
8import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.interpreter.tests.QueryEngineTest;
15import tools.refinery.store.query.view.AnySymbolView;
16import tools.refinery.store.query.view.FilteredView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
19import tools.refinery.store.representation.Symbol;
20import tools.refinery.store.representation.TruthValue;
21import tools.refinery.store.tuple.Tuple;
22
23import java.util.List;
24import java.util.Map;
25import java.util.Optional;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static org.hamcrest.Matchers.is;
29import static org.hamcrest.Matchers.nullValue;
30import static org.junit.jupiter.api.Assertions.assertAll;
31import static org.junit.jupiter.api.Assertions.assertThrows;
32import static tools.refinery.store.query.literal.Literals.check;
33import static tools.refinery.store.query.term.int_.IntTerms.*;
34import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults;
35import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults;
36
37class 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 */
6package tools.refinery.store.query.interpreter;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.resultset.OrderedResultSet;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.is;
21
22class 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 */
6package tools.refinery.store.query.interpreter;
7
8import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.dnf.Query;
14import tools.refinery.store.query.term.ParameterDirection;
15import tools.refinery.store.query.term.Variable;
16import tools.refinery.store.query.interpreter.tests.QueryEngineTest;
17import tools.refinery.store.query.view.AnySymbolView;
18import tools.refinery.store.query.view.FilteredView;
19import tools.refinery.store.query.view.FunctionView;
20import tools.refinery.store.query.view.KeyOnlyView;
21import tools.refinery.store.representation.Symbol;
22import tools.refinery.store.representation.TruthValue;
23import tools.refinery.store.tuple.Tuple;
24
25import java.util.List;
26import java.util.Map;
27
28import static tools.refinery.store.query.literal.Literals.check;
29import static tools.refinery.store.query.literal.Literals.not;
30import static tools.refinery.store.query.term.int_.IntTerms.constant;
31import static tools.refinery.store.query.term.int_.IntTerms.greaterEq;
32import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults;
33
34class 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 */
6package tools.refinery.store.query.interpreter;
7
8import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.dnf.RelationalQuery;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.FilteredView;
16import tools.refinery.store.query.view.FunctionView;
17import tools.refinery.store.query.view.KeyOnlyView;
18import tools.refinery.store.representation.Symbol;
19import tools.refinery.store.tuple.Tuple;
20
21import java.util.Map;
22import java.util.Optional;
23
24import static org.junit.jupiter.api.Assertions.assertFalse;
25import static org.junit.jupiter.api.Assertions.assertTrue;
26import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults;
27import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults;
28
29class 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 */
6package tools.refinery.store.query.interpreter;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.literal.Connectivity;
13import tools.refinery.store.query.literal.RepresentativeElectionLiteral;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.List;
20import java.util.Map;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static org.hamcrest.Matchers.is;
24import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults;
25
26class 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 */
6package tools.refinery.store.query.interpreter;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.literal.Connectivity;
13import tools.refinery.store.query.literal.RepresentativeElectionLiteral;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.List;
20import java.util.Map;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static org.hamcrest.Matchers.is;
24import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults;
25
26class 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 */
6package tools.refinery.store.query.interpreter.internal.matcher;
7
8import tools.refinery.interpreter.matchers.tuple.*;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.tuple.Tuple;
11import tools.refinery.store.tuple.*;
12
13import java.util.List;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.*;
17import static org.junit.jupiter.api.Assertions.assertThrows;
18
19class 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 */
6package tools.refinery.store.query.interpreter.tests;
7
8import org.junit.jupiter.api.function.Executable;
9import tools.refinery.store.query.resultset.ResultSet;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.*;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16import static org.hamcrest.Matchers.nullValue;
17import static org.junit.jupiter.api.Assertions.assertAll;
18
19public 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 */
6package tools.refinery.store.query.interpreter.tests;
7
8import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
9
10/**
11 * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names.
12 */
13class 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 */
6package tools.refinery.store.query.interpreter.tests;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.ArgumentsSource;
10
11import java.lang.annotation.ElementType;
12import java.lang.annotation.Retention;
13import java.lang.annotation.RetentionPolicy;
14import java.lang.annotation.Target;
15
16@ParameterizedTest(name = "backend = {0}")
17@ArgumentsSource(QueryEvaluationHintSource.class)
18@Target(ElementType.METHOD)
19@Retention(RetentionPolicy.RUNTIME)
20public @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 */
6package tools.refinery.store.query.interpreter.tests;
7
8import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.extension.ExtensionContext;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.ArgumentsProvider;
12
13import java.util.stream.Stream;
14
15public 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}