diff options
author | 2023-09-16 13:19:31 +0200 | |
---|---|---|
committer | 2023-09-16 16:53:01 +0200 | |
commit | 97b0c4c1192fe5580a7957c844acc8092b56c604 (patch) | |
tree | bea3cdf9aaeb5da2864fcf87780d356661af8f63 /subprojects/store-query-interpreter/src/main | |
parent | build: fix Sonar quality gate issues (diff) | |
download | refinery-97b0c4c1192fe5580a7957c844acc8092b56c604.tar.gz refinery-97b0c4c1192fe5580a7957c844acc8092b56c604.tar.zst refinery-97b0c4c1192fe5580a7957c844acc8092b56c604.zip |
chore: remove VIATRA branding
Rename VIATRA subprojects to Refinery Interpreter to avoid interfering with
Eclipse Foundation trademarks.
Uses refering to a specific (historical) version of VIATRA were kept to avoid
ambiguity.
Diffstat (limited to 'subprojects/store-query-interpreter/src/main')
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 | */ | ||
6 | package tools.refinery.store.query.interpreter; | ||
7 | |||
8 | import tools.refinery.store.query.ModelQueryAdapter; | ||
9 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterBuilderImpl; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter; | ||
7 | |||
8 | import tools.refinery.store.model.ModelStore; | ||
9 | import tools.refinery.store.query.ModelQueryBuilder; | ||
10 | import tools.refinery.store.query.dnf.AnyQuery; | ||
11 | import tools.refinery.store.query.dnf.Dnf; | ||
12 | import tools.refinery.store.query.rewriter.DnfRewriter; | ||
13 | import tools.refinery.interpreter.api.InterpreterEngineOptions; | ||
14 | import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; | ||
15 | import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
16 | |||
17 | import java.util.Collection; | ||
18 | import java.util.function.Function; | ||
19 | |||
20 | @SuppressWarnings("UnusedReturnValue") | ||
21 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter; | ||
7 | |||
8 | import tools.refinery.interpreter.api.InterpreterEngineOptions; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.query.ModelQueryStoreAdapter; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.model.ModelListener; | ||
10 | import tools.refinery.store.query.dnf.AnyQuery; | ||
11 | import tools.refinery.store.query.dnf.FunctionalQuery; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
14 | import tools.refinery.store.query.resultset.AnyResultSet; | ||
15 | import tools.refinery.store.query.resultset.EmptyResultSet; | ||
16 | import tools.refinery.store.query.resultset.ResultSet; | ||
17 | import tools.refinery.store.query.interpreter.QueryInterpreterAdapter; | ||
18 | import tools.refinery.store.query.interpreter.internal.matcher.InterpretedFunctionalMatcher; | ||
19 | import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher; | ||
20 | import tools.refinery.store.query.interpreter.internal.matcher.InterpretedRelationalMatcher; | ||
21 | import tools.refinery.interpreter.CancellationToken; | ||
22 | import tools.refinery.interpreter.api.AdvancedInterpreterEngine; | ||
23 | import tools.refinery.interpreter.api.GenericQueryGroup; | ||
24 | import tools.refinery.interpreter.api.IQuerySpecification; | ||
25 | |||
26 | import java.util.Collections; | ||
27 | import java.util.LinkedHashMap; | ||
28 | import java.util.Map; | ||
29 | |||
30 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal; | ||
7 | |||
8 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.dnf.AnyQuery; | ||
11 | import tools.refinery.store.query.dnf.Dnf; | ||
12 | import tools.refinery.store.query.rewriter.CompositeRewriter; | ||
13 | import tools.refinery.store.query.rewriter.DnfRewriter; | ||
14 | import tools.refinery.store.query.rewriter.DuplicateDnfRemover; | ||
15 | import tools.refinery.store.query.rewriter.InputParameterResolver; | ||
16 | import tools.refinery.store.query.interpreter.QueryInterpreterBuilder; | ||
17 | import tools.refinery.store.query.interpreter.internal.localsearch.FlatCostFunction; | ||
18 | import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher; | ||
19 | import tools.refinery.store.query.interpreter.internal.pquery.Dnf2PQuery; | ||
20 | import tools.refinery.interpreter.api.IQuerySpecification; | ||
21 | import tools.refinery.interpreter.api.InterpreterEngineOptions; | ||
22 | import tools.refinery.interpreter.localsearch.matcher.integration.LocalSearchGenericBackendFactory; | ||
23 | import tools.refinery.interpreter.localsearch.matcher.integration.LocalSearchHintOptions; | ||
24 | import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; | ||
25 | import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
26 | import tools.refinery.interpreter.rete.matcher.ReteBackendFactory; | ||
27 | |||
28 | import java.util.*; | ||
29 | import java.util.function.Function; | ||
30 | |||
31 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal; | ||
7 | |||
8 | import tools.refinery.interpreter.CancellationToken; | ||
9 | import tools.refinery.interpreter.api.IQuerySpecification; | ||
10 | import tools.refinery.interpreter.api.InterpreterEngineOptions; | ||
11 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
12 | import tools.refinery.store.model.Model; | ||
13 | import tools.refinery.store.model.ModelStore; | ||
14 | import tools.refinery.store.query.dnf.AnyQuery; | ||
15 | import tools.refinery.store.query.dnf.Query; | ||
16 | import tools.refinery.store.query.interpreter.QueryInterpreterStoreAdapter; | ||
17 | import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher; | ||
18 | import tools.refinery.store.query.view.AnySymbolView; | ||
19 | |||
20 | import java.util.*; | ||
21 | |||
22 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal; | ||
7 | |||
8 | import org.apache.log4j.Logger; | ||
9 | import tools.refinery.interpreter.api.InterpreterEngine; | ||
10 | import tools.refinery.interpreter.api.scope.IEngineContext; | ||
11 | import tools.refinery.interpreter.api.scope.IIndexingErrorListener; | ||
12 | import tools.refinery.interpreter.api.scope.QueryScope; | ||
13 | import tools.refinery.store.query.interpreter.internal.context.RelationalEngineContext; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.context; | ||
7 | |||
8 | import tools.refinery.interpreter.api.scope.IBaseIndex; | ||
9 | import tools.refinery.interpreter.api.scope.IIndexingErrorListener; | ||
10 | import tools.refinery.interpreter.api.scope.IInstanceObserver; | ||
11 | import tools.refinery.interpreter.api.scope.InterpreterBaseIndexChangeListener; | ||
12 | |||
13 | import java.lang.reflect.InvocationTargetException; | ||
14 | import java.util.concurrent.Callable; | ||
15 | |||
16 | /** | ||
17 | * Copied from <code>tools.refinery.viatra.runtime.tabular.TabularEngineContext</code> | ||
18 | */ | ||
19 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.context; | ||
7 | |||
8 | import tools.refinery.interpreter.api.scope.IBaseIndex; | ||
9 | import tools.refinery.interpreter.api.scope.IEngineContext; | ||
10 | import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; | ||
11 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.context; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.AbstractQueryMetaContext; | ||
9 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
10 | import tools.refinery.interpreter.matchers.context.InputKeyImplication; | ||
11 | import tools.refinery.interpreter.matchers.context.common.JavaTransitiveInstancesKey; | ||
12 | import tools.refinery.store.query.interpreter.internal.pquery.SymbolViewWrapper; | ||
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
14 | |||
15 | import java.util.*; | ||
16 | |||
17 | /** | ||
18 | * The meta context information for String scopes. | ||
19 | */ | ||
20 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.context; | ||
7 | |||
8 | import tools.refinery.interpreter.CancellationToken; | ||
9 | import tools.refinery.interpreter.matchers.context.*; | ||
10 | import tools.refinery.interpreter.matchers.tuple.ITuple; | ||
11 | import tools.refinery.interpreter.matchers.tuple.Tuple; | ||
12 | import tools.refinery.interpreter.matchers.tuple.TupleMask; | ||
13 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
14 | import tools.refinery.interpreter.matchers.util.Accuracy; | ||
15 | import tools.refinery.store.model.Model; | ||
16 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
17 | import tools.refinery.store.query.interpreter.internal.pquery.SymbolViewWrapper; | ||
18 | import tools.refinery.store.query.interpreter.internal.update.ModelUpdateListener; | ||
19 | import tools.refinery.store.query.view.AnySymbolView; | ||
20 | |||
21 | import java.lang.reflect.InvocationTargetException; | ||
22 | import java.util.Iterator; | ||
23 | import java.util.Optional; | ||
24 | import java.util.concurrent.Callable; | ||
25 | |||
26 | import static tools.refinery.store.util.CollectionsUtil.filter; | ||
27 | import static tools.refinery.store.util.CollectionsUtil.map; | ||
28 | |||
29 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.localsearch; | ||
7 | |||
8 | import tools.refinery.interpreter.localsearch.planner.cost.IConstraintEvaluationContext; | ||
9 | import tools.refinery.interpreter.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction; | ||
10 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
11 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint; | ||
12 | import tools.refinery.interpreter.matchers.tuple.TupleMask; | ||
13 | import tools.refinery.interpreter.matchers.util.Accuracy; | ||
14 | |||
15 | import java.util.Optional; | ||
16 | |||
17 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; | ||
9 | import tools.refinery.interpreter.matchers.backend.IUpdateable; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.resultset.AbstractResultSet; | ||
12 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.rete.index.IterableIndexer; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Iterator; | ||
13 | |||
14 | class 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; | ||
9 | import tools.refinery.interpreter.matchers.tuple.TupleMask; | ||
10 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
11 | import tools.refinery.interpreter.rete.index.IterableIndexer; | ||
12 | import tools.refinery.interpreter.rete.matcher.RetePatternMatcher; | ||
13 | import tools.refinery.store.map.Cursor; | ||
14 | import tools.refinery.store.query.dnf.FunctionalQuery; | ||
15 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
16 | import 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 | */ | ||
28 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; | ||
9 | import tools.refinery.interpreter.matchers.tuple.TupleMask; | ||
10 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
11 | import tools.refinery.interpreter.rete.index.Indexer; | ||
12 | import tools.refinery.interpreter.rete.matcher.RetePatternMatcher; | ||
13 | import tools.refinery.store.map.Cursor; | ||
14 | import tools.refinery.store.map.Cursors; | ||
15 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
16 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
17 | import 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 | */ | ||
29 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.tuple.ITuple; | ||
9 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
10 | import org.jetbrains.annotations.Nullable; | ||
11 | import tools.refinery.store.tuple.*; | ||
12 | |||
13 | import java.util.Iterator; | ||
14 | |||
15 | final 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.api.GenericPatternMatcher; | ||
9 | import tools.refinery.interpreter.api.GenericQuerySpecification; | ||
10 | import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.tuple.ITuple; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Iterator; | ||
13 | |||
14 | class 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.matcher; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.tuple.ITuple; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import 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 | */ | ||
19 | class 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.psystem.IValueProvider; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | class 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.psystem.basicdeferred.*; | ||
9 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.*; | ||
10 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity; | ||
11 | import tools.refinery.store.query.Constraint; | ||
12 | import tools.refinery.store.query.dnf.Dnf; | ||
13 | import tools.refinery.store.query.dnf.DnfClause; | ||
14 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
15 | import tools.refinery.store.query.literal.*; | ||
16 | import tools.refinery.store.query.term.ConstantTerm; | ||
17 | import tools.refinery.store.query.term.StatefulAggregator; | ||
18 | import tools.refinery.store.query.term.StatelessAggregator; | ||
19 | import tools.refinery.store.query.term.Variable; | ||
20 | import tools.refinery.store.query.view.AnySymbolView; | ||
21 | import tools.refinery.store.util.CycleDetectingMapper; | ||
22 | import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; | ||
23 | import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; | ||
24 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
25 | import tools.refinery.interpreter.matchers.psystem.PBody; | ||
26 | import tools.refinery.interpreter.matchers.psystem.PVariable; | ||
27 | import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; | ||
28 | import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
29 | import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation; | ||
30 | import tools.refinery.interpreter.matchers.psystem.queries.PParameter; | ||
31 | import tools.refinery.interpreter.matchers.psystem.queries.PParameterDirection; | ||
32 | import tools.refinery.interpreter.matchers.psystem.queries.PQuery; | ||
33 | import tools.refinery.interpreter.matchers.tuple.Tuple; | ||
34 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
35 | |||
36 | import java.util.ArrayList; | ||
37 | import java.util.HashMap; | ||
38 | import java.util.List; | ||
39 | import java.util.Map; | ||
40 | import java.util.function.Function; | ||
41 | |||
42 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
9 | import tools.refinery.interpreter.matchers.psystem.PBody; | ||
10 | import tools.refinery.interpreter.matchers.psystem.PVariable; | ||
11 | import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExportedParameter; | ||
12 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.PositivePatternCall; | ||
13 | import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint; | ||
14 | import tools.refinery.interpreter.matchers.psystem.queries.PParameter; | ||
15 | import tools.refinery.interpreter.matchers.psystem.queries.PQuery; | ||
16 | import tools.refinery.interpreter.matchers.psystem.queries.PVisibility; | ||
17 | import tools.refinery.interpreter.matchers.tuple.Tuple; | ||
18 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
19 | import tools.refinery.store.query.Constraint; | ||
20 | import tools.refinery.store.query.dnf.Dnf; | ||
21 | import tools.refinery.store.query.dnf.DnfUtils; | ||
22 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
23 | import tools.refinery.store.query.term.ParameterDirection; | ||
24 | import tools.refinery.store.query.term.Variable; | ||
25 | import tools.refinery.store.query.view.AnySymbolView; | ||
26 | import tools.refinery.store.query.view.SymbolView; | ||
27 | import tools.refinery.store.util.CycleDetectingMapper; | ||
28 | |||
29 | import java.util.*; | ||
30 | import java.util.function.ToIntFunction; | ||
31 | |||
32 | class 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.api.GenericQuerySpecification; | ||
9 | import tools.refinery.interpreter.api.InterpreterEngine; | ||
10 | import tools.refinery.interpreter.api.scope.QueryScope; | ||
11 | import tools.refinery.interpreter.matchers.psystem.PBody; | ||
12 | import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation; | ||
13 | import tools.refinery.interpreter.matchers.psystem.queries.BasePQuery; | ||
14 | import tools.refinery.interpreter.matchers.psystem.queries.PParameter; | ||
15 | import tools.refinery.interpreter.matchers.psystem.queries.PVisibility; | ||
16 | import tools.refinery.store.query.interpreter.internal.RelationalScope; | ||
17 | import tools.refinery.store.query.interpreter.internal.matcher.RawPatternMatcher; | ||
18 | |||
19 | import java.util.LinkedHashSet; | ||
20 | import java.util.List; | ||
21 | import java.util.Set; | ||
22 | |||
23 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
9 | import tools.refinery.store.query.term.StatefulAggregate; | ||
10 | import tools.refinery.store.query.term.StatefulAggregator; | ||
11 | |||
12 | import java.util.stream.Stream; | ||
13 | |||
14 | record 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
9 | import tools.refinery.store.query.term.StatelessAggregator; | ||
10 | |||
11 | import java.util.stream.Stream; | ||
12 | |||
13 | record 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.common.BaseInputKeyWrapper; | ||
9 | import tools.refinery.store.query.view.AnySymbolView; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.store.query.term.Term; | ||
9 | import tools.refinery.store.query.term.Variable; | ||
10 | import tools.refinery.interpreter.matchers.psystem.IExpressionEvaluator; | ||
11 | import tools.refinery.interpreter.matchers.psystem.IValueProvider; | ||
12 | |||
13 | import java.util.stream.Collectors; | ||
14 | |||
15 | class 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.pquery; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.psystem.IValueProvider; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.update; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
9 | import tools.refinery.interpreter.matchers.context.IQueryRuntimeContextListener; | ||
10 | import tools.refinery.interpreter.matchers.tuple.ITuple; | ||
11 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
12 | import tools.refinery.store.query.view.AnySymbolView; | ||
13 | import tools.refinery.store.query.view.SymbolView; | ||
14 | |||
15 | import java.util.HashMap; | ||
16 | import java.util.Map; | ||
17 | |||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.update; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
9 | import tools.refinery.interpreter.matchers.context.IQueryRuntimeContextListener; | ||
10 | import tools.refinery.interpreter.matchers.tuple.ITuple; | ||
11 | import tools.refinery.interpreter.matchers.tuple.Tuple; | ||
12 | |||
13 | import java.util.Arrays; | ||
14 | import java.util.Objects; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.update; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.context.IInputKey; | ||
9 | import tools.refinery.interpreter.matchers.context.IQueryRuntimeContextListener; | ||
10 | import tools.refinery.interpreter.matchers.tuple.ITuple; | ||
11 | import tools.refinery.interpreter.matchers.tuple.Tuple; | ||
12 | import tools.refinery.store.model.Interpretation; | ||
13 | import tools.refinery.store.model.InterpretationListener; | ||
14 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
15 | import tools.refinery.store.query.view.SymbolView; | ||
16 | import tools.refinery.store.query.view.TuplePreservingView; | ||
17 | |||
18 | import java.util.ArrayList; | ||
19 | import java.util.List; | ||
20 | |||
21 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.update; | ||
7 | |||
8 | import tools.refinery.store.model.Interpretation; | ||
9 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
10 | import tools.refinery.store.query.view.SymbolView; | ||
11 | import tools.refinery.store.tuple.Tuple; | ||
12 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
13 | |||
14 | import java.util.Arrays; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.interpreter.internal.update; | ||
7 | |||
8 | import tools.refinery.interpreter.matchers.tuple.Tuples; | ||
9 | import tools.refinery.store.model.Interpretation; | ||
10 | import tools.refinery.store.query.interpreter.internal.QueryInterpreterAdapterImpl; | ||
11 | import tools.refinery.store.query.view.TuplePreservingView; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | public 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 | } | ||