aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query-interpreter/src/main
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/main
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/main')
-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
34 files changed, 2486 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}