aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query-viatra/src
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/store-query-viatra/src')
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java21
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java10
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java22
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java18
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java86
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java119
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java44
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java97
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java53
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java54
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java76
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java117
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java35
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java137
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java60
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java28
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java70
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java32
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java52
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java88
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java53
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java115
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java20
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java47
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java80
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java55
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java21
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java223
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java266
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java189
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java72
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java65
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java55
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java)16
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java37
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java19
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java44
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java48
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java65
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java37
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java44
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java)18
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java390
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java483
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java117
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java832
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java357
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java51
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java87
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java239
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java57
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java27
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java21
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java24
60 files changed, 4297 insertions, 1227 deletions
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java
deleted file mode 100644
index 677e3c7d..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java
+++ /dev/null
@@ -1,21 +0,0 @@
1package tools.refinery.store.query.viatra;
2
3import tools.refinery.store.adapter.ModelAdapterBuilderFactory;
4import tools.refinery.store.model.ModelStoreBuilder;
5import tools.refinery.store.query.ModelQuery;
6import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl;
7
8public final class ViatraModelQuery extends ModelAdapterBuilderFactory<ViatraModelQueryAdapter,
9 ViatraModelQueryStoreAdapter, ViatraModelQueryBuilder> {
10 public static final ViatraModelQuery ADAPTER = new ViatraModelQuery();
11
12 private ViatraModelQuery() {
13 super(ViatraModelQueryAdapter.class, ViatraModelQueryStoreAdapter.class, ViatraModelQueryBuilder.class);
14 extendsAdapter(ModelQuery.ADAPTER);
15 }
16
17 @Override
18 public ViatraModelQueryBuilder createBuilder(ModelStoreBuilder storeBuilder) {
19 return new ViatraModelQueryBuilderImpl(storeBuilder);
20 }
21}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java
index 7e21476b..12c93f62 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java
@@ -1,8 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
3import tools.refinery.store.query.ModelQueryAdapter; 8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl;
4 10
5public interface ViatraModelQueryAdapter extends ModelQueryAdapter { 11public interface ViatraModelQueryAdapter extends ModelQueryAdapter {
6 @Override 12 @Override
7 ViatraModelQueryStoreAdapter getStoreAdapter(); 13 ViatraModelQueryStoreAdapter getStoreAdapter();
14
15 static ViatraModelQueryBuilder builder() {
16 return new ViatraModelQueryBuilderImpl();
17 }
8} 18}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java
index efc6146c..931a07aa 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java
@@ -1,10 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
3import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 8import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
4import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 9import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
5import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 10import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
6import tools.refinery.store.model.ModelStore; 11import tools.refinery.store.model.ModelStore;
7import tools.refinery.store.query.DNF; 12import tools.refinery.store.query.dnf.AnyQuery;
13import tools.refinery.store.query.dnf.Dnf;
8import tools.refinery.store.query.ModelQueryBuilder; 14import tools.refinery.store.query.ModelQueryBuilder;
9 15
10import java.util.Collection; 16import java.util.Collection;
@@ -23,26 +29,26 @@ public interface ViatraModelQueryBuilder extends ModelQueryBuilder {
23 ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); 29 ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory);
24 30
25 @Override 31 @Override
26 default ViatraModelQueryBuilder queries(DNF... queries) { 32 default ViatraModelQueryBuilder queries(AnyQuery... queries) {
27 ModelQueryBuilder.super.queries(queries); 33 ModelQueryBuilder.super.queries(queries);
28 return this; 34 return this;
29 } 35 }
30 36
31 @Override 37 @Override
32 default ViatraModelQueryBuilder queries(Collection<DNF> queries) { 38 default ViatraModelQueryBuilder queries(Collection<? extends AnyQuery> queries) {
33 ModelQueryBuilder.super.queries(queries); 39 ModelQueryBuilder.super.queries(queries);
34 return this; 40 return this;
35 } 41 }
36 42
37 @Override 43 @Override
38 ViatraModelQueryBuilder query(DNF query); 44 ViatraModelQueryBuilder query(AnyQuery query);
39 45
40 ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint); 46 ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint);
41 47
42 ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint); 48 ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint);
43 49
44 ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint); 50 ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint);
45 51
46 @Override 52 @Override
47 ViatraModelQueryStoreAdapter createStoreAdapter(ModelStore store); 53 ViatraModelQueryStoreAdapter build(ModelStore store);
48} 54}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java
index 1ee02f12..da6d7bd5 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
3import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 8import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java
deleted file mode 100644
index 46c28434..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java
+++ /dev/null
@@ -1,18 +0,0 @@
1package tools.refinery.store.query.viatra;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
4import tools.refinery.store.tuple.Tuple1;
5import tools.refinery.store.tuple.TupleLike;
6
7public record ViatraTupleLike(ITuple wrappedTuple) implements TupleLike {
8 @Override
9 public int getSize() {
10 return wrappedTuple.getSize();
11 }
12
13 @Override
14 public int get(int element) {
15 var wrappedValue = (Tuple1) wrappedTuple.get(element);
16 return wrappedValue.value0();
17 }
18}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java
index 8328e759..d1a65a89 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.apache.log4j.Logger; 8import org.apache.log4j.Logger;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java
index 039f46fa..5f3e86b4 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; 8import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine;
@@ -7,62 +12,92 @@ import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl;
7import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; 12import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 13import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
9import tools.refinery.store.model.Model; 14import tools.refinery.store.model.Model;
10import tools.refinery.store.query.DNF; 15import tools.refinery.store.model.ModelListener;
11import tools.refinery.store.query.ResultSet; 16import tools.refinery.store.query.resultset.AnyResultSet;
17import tools.refinery.store.query.resultset.EmptyResultSet;
18import tools.refinery.store.query.resultset.ResultSet;
19import tools.refinery.store.query.dnf.AnyQuery;
20import tools.refinery.store.query.dnf.FunctionalQuery;
21import tools.refinery.store.query.dnf.Query;
22import tools.refinery.store.query.dnf.RelationalQuery;
12import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 23import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
24import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher;
25import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
26import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher;
13 27
14import java.lang.invoke.MethodHandle; 28import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles; 29import java.lang.invoke.MethodHandles;
16import java.util.Collection; 30import java.util.Collection;
17import java.util.Collections; 31import java.util.Collections;
18import java.util.HashMap; 32import java.util.LinkedHashMap;
19import java.util.Map; 33import java.util.Map;
20 34
21public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { 35public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, ModelListener {
22 private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; 36 private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery";
37 private static final MethodHandle SET_UPDATE_PROPAGATION_DELAYED_HANDLE;
23 private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; 38 private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends";
39 private static final MethodHandle GET_QUERY_BACKENDS_HANDLE;
24 40
25 private final Model model; 41 private final Model model;
26 private final ViatraModelQueryStoreAdapterImpl storeAdapter; 42 private final ViatraModelQueryStoreAdapterImpl storeAdapter;
27 private final ViatraQueryEngineImpl queryEngine; 43 private final ViatraQueryEngineImpl queryEngine;
28 private final MethodHandle setUpdatePropagationDelayedHandle; 44 private final Map<AnyQuery, AnyResultSet> resultSets;
29 private final MethodHandle getQueryBackendsHandle;
30 private final Map<DNF, ResultSet> resultSets;
31 private boolean pendingChanges; 45 private boolean pendingChanges;
32 46
33 ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { 47 static {
34 this.model = model;
35 this.storeAdapter = storeAdapter;
36 var scope = new RelationalScope(this);
37 queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope);
38
39 try { 48 try {
40 var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); 49 var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup());
41 setUpdatePropagationDelayedHandle = lookup.findSetter(ViatraQueryEngineImpl.class, 50 SET_UPDATE_PROPAGATION_DELAYED_HANDLE = lookup.findSetter(ViatraQueryEngineImpl.class,
42 DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); 51 DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE);
43 getQueryBackendsHandle = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, 52 GET_QUERY_BACKENDS_HANDLE = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME,
44 Map.class); 53 Map.class);
45 } catch (IllegalAccessException | NoSuchFieldException e) { 54 } catch (IllegalAccessException | NoSuchFieldException e) {
46 throw new IllegalStateException("Cannot access private members of %s" 55 throw new IllegalStateException("Cannot access private members of %s"
47 .formatted(ViatraQueryEngineImpl.class.getName()), e); 56 .formatted(ViatraQueryEngineImpl.class.getName()), e);
48 } 57 }
58 }
59
60 ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) {
61 this.model = model;
62 this.storeAdapter = storeAdapter;
63 var scope = new RelationalScope(this);
64 queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope,
65 storeAdapter.getEngineOptions());
49 66
50 var querySpecifications = storeAdapter.getQuerySpecifications(); 67 var querySpecifications = storeAdapter.getQuerySpecifications();
51 GenericQueryGroup.of( 68 GenericQueryGroup.of(
52 Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream() 69 Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream()
53 ).prepare(queryEngine); 70 ).prepare(queryEngine);
54 resultSets = new HashMap<>(querySpecifications.size()); 71 var vacuousQueries = storeAdapter.getVacuousQueries();
72 resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size());
55 for (var entry : querySpecifications.entrySet()) { 73 for (var entry : querySpecifications.entrySet()) {
56 var matcher = queryEngine.getMatcher(entry.getValue()); 74 var rawPatternMatcher = queryEngine.getMatcher(entry.getValue());
57 resultSets.put(entry.getKey(), matcher); 75 var query = entry.getKey();
76 resultSets.put(query, createResultSet((Query<?>) query, rawPatternMatcher));
77 }
78 for (var vacuousQuery : vacuousQueries) {
79 resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query<?>) vacuousQuery));
58 } 80 }
59 81
60 setUpdatePropagationDelayed(true); 82 setUpdatePropagationDelayed(true);
83 model.addListener(this);
84 }
85
86 private <T> ResultSet<T> createResultSet(Query<T> query, RawPatternMatcher matcher) {
87 if (query instanceof RelationalQuery relationalQuery) {
88 @SuppressWarnings("unchecked")
89 var resultSet = (ResultSet<T>) new RelationalViatraMatcher(this, relationalQuery, matcher);
90 return resultSet;
91 } else if (query instanceof FunctionalQuery<T> functionalQuery) {
92 return new FunctionalViatraMatcher<>(this, functionalQuery, matcher);
93 } else {
94 throw new IllegalArgumentException("Unknown query: " + query);
95 }
61 } 96 }
62 97
63 private void setUpdatePropagationDelayed(boolean value) { 98 private void setUpdatePropagationDelayed(boolean value) {
64 try { 99 try {
65 setUpdatePropagationDelayedHandle.invokeExact(queryEngine, value); 100 SET_UPDATE_PROPAGATION_DELAYED_HANDLE.invokeExact(queryEngine, value);
66 } catch (Error e) { 101 } catch (Error e) {
67 // Fatal JVM errors should not be wrapped. 102 // Fatal JVM errors should not be wrapped.
68 throw e; 103 throw e;
@@ -74,7 +109,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter {
74 private Collection<IQueryBackend> getQueryBackends() { 109 private Collection<IQueryBackend> getQueryBackends() {
75 try { 110 try {
76 @SuppressWarnings("unchecked") 111 @SuppressWarnings("unchecked")
77 var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) getQueryBackendsHandle.invokeExact(queryEngine); 112 var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) GET_QUERY_BACKENDS_HANDLE.invokeExact(queryEngine);
78 return backendMap.values(); 113 return backendMap.values();
79 } catch (Error e) { 114 } catch (Error e) {
80 // Fatal JVM errors should not be wrapped. 115 // Fatal JVM errors should not be wrapped.
@@ -95,12 +130,14 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter {
95 } 130 }
96 131
97 @Override 132 @Override
98 public ResultSet getResultSet(DNF query) { 133 public <T> ResultSet<T> getResultSet(Query<T> query) {
99 var resultSet = resultSets.get(query); 134 var resultSet = resultSets.get(query);
100 if (resultSet == null) { 135 if (resultSet == null) {
101 throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); 136 throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name()));
102 } 137 }
103 return resultSet; 138 @SuppressWarnings("unchecked")
139 var typedResultSet = (ResultSet<T>) resultSet;
140 return typedResultSet;
104 } 141 }
105 142
106 @Override 143 @Override
@@ -132,4 +169,9 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter {
132 } 169 }
133 pendingChanges = false; 170 pendingChanges = false;
134 } 171 }
172
173 @Override
174 public void afterRestore() {
175 flushChanges();
176 }
135} 177}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java
index 9f1e55b1..ce2467b4 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java
@@ -1,115 +1,162 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.eclipse.viatra.query.runtime.api.IQuerySpecification; 8import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
4import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 9import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
5import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; 10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHintOptions;
6import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 11import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
7import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 12import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
8import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; 13import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory;
9import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 14import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
10import tools.refinery.store.model.ModelStore; 15import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.model.ModelStoreBuilder; 16import tools.refinery.store.model.ModelStoreBuilder;
12import tools.refinery.store.query.DNF; 17import tools.refinery.store.query.dnf.AnyQuery;
18import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; 19import tools.refinery.store.query.viatra.ViatraModelQueryBuilder;
14import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; 20import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction;
15import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; 21import tools.refinery.store.query.viatra.internal.localsearch.RelationalLocalSearchBackendFactory;
22import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
23import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery;
16 24
17import java.util.Collections; 25import java.util.*;
18import java.util.LinkedHashMap;
19import java.util.Map;
20import java.util.function.Function; 26import java.util.function.Function;
21 27
22public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder { 28public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder<ViatraModelQueryStoreAdapterImpl>
29 implements ViatraModelQueryBuilder {
23 private ViatraQueryEngineOptions.Builder engineOptionsBuilder; 30 private ViatraQueryEngineOptions.Builder engineOptionsBuilder;
24 private final DNF2PQuery dnf2PQuery = new DNF2PQuery(); 31 private QueryEvaluationHint defaultHint = new QueryEvaluationHint(Map.of(
25 private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>(); 32 // Use a cost function that ignores the initial (empty) model but allows higher arity input keys.
33 LocalSearchHintOptions.PLANNER_COST_FUNCTION, new FlatCostFunction()
34 ), (IQueryBackendFactory) null);
35 private final Dnf2PQuery dnf2PQuery = new Dnf2PQuery();
36 private final Set<AnyQuery> vacuousQueries = new LinkedHashSet<>();
37 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>();
26 38
27 public ViatraModelQueryBuilderImpl(ModelStoreBuilder storeBuilder) { 39 public ViatraModelQueryBuilderImpl() {
28 super(storeBuilder);
29 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() 40 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder()
30 .withDefaultBackend(ReteBackendFactory.INSTANCE) 41 .withDefaultBackend(ReteBackendFactory.INSTANCE)
31 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) 42 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE)
32 .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE); 43 .withDefaultSearchBackend(RelationalLocalSearchBackendFactory.INSTANCE);
33 } 44 }
34 45
35 @Override 46 @Override
36 public ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions) { 47 public ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions) {
48 checkNotConfigured();
37 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder(engineOptions); 49 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder(engineOptions);
38 return this; 50 return this;
39 } 51 }
40 52
41 @Override 53 @Override
42 public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) { 54 public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) {
43 engineOptionsBuilder.withDefaultHint(queryEvaluationHint); 55 checkNotConfigured();
56 defaultHint = defaultHint.overrideBy(queryEvaluationHint);
44 return this; 57 return this;
45 } 58 }
46 59
47 @Override 60 @Override
48 public ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory) { 61 public ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory) {
62 checkNotConfigured();
49 engineOptionsBuilder.withDefaultBackend(queryBackendFactory); 63 engineOptionsBuilder.withDefaultBackend(queryBackendFactory);
50 return this; 64 return this;
51 } 65 }
52 66
53 @Override 67 @Override
54 public ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory) { 68 public ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory) {
69 checkNotConfigured();
55 engineOptionsBuilder.withDefaultCachingBackend(queryBackendFactory); 70 engineOptionsBuilder.withDefaultCachingBackend(queryBackendFactory);
56 return this; 71 return this;
57 } 72 }
58 73
59 @Override 74 @Override
60 public ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory) { 75 public ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory) {
76 checkNotConfigured();
61 engineOptionsBuilder.withDefaultSearchBackend(queryBackendFactory); 77 engineOptionsBuilder.withDefaultSearchBackend(queryBackendFactory);
62 return this; 78 return this;
63 } 79 }
64 80
65 @Override 81 @Override
66 public ViatraModelQueryBuilder query(DNF query) { 82 public ViatraModelQueryBuilder query(AnyQuery query) {
67 if (querySpecifications.containsKey(query)) { 83 checkNotConfigured();
68 throw new IllegalArgumentException("%s was already added to the query engine".formatted(query.name())); 84 if (querySpecifications.containsKey(query) || vacuousQueries.contains(query)) {
85 // Ignore duplicate queries.
86 return this;
87 }
88 var dnf = query.getDnf();
89 var reduction = dnf.getReduction();
90 switch (reduction) {
91 case NOT_REDUCIBLE -> {
92 var pQuery = dnf2PQuery.translate(dnf);
93 querySpecifications.put(query, pQuery.build());
94 }
95 case ALWAYS_FALSE -> vacuousQueries.add(query);
96 case ALWAYS_TRUE -> throw new IllegalArgumentException(
97 "Query %s is relationally unsafe (it matches every tuple)".formatted(query.name()));
98 default -> throw new IllegalArgumentException("Unknown reduction: " + reduction);
69 } 99 }
70 var pQuery = dnf2PQuery.translate(query);
71 querySpecifications.put(query, pQuery.build());
72 return this; 100 return this;
73 } 101 }
74 102
75 @Override 103 @Override
76 public ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint) { 104 public ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint) {
105 hint(query.getDnf(), queryEvaluationHint);
77 query(query); 106 query(query);
78 hint(query, queryEvaluationHint);
79 return this; 107 return this;
80 } 108 }
81 109
82 @Override 110 @Override
83 public ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint) { 111 public ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint) {
112 checkNotConfigured();
84 dnf2PQuery.setComputeHint(computeHint); 113 dnf2PQuery.setComputeHint(computeHint);
85 return this; 114 return this;
86 } 115 }
87 116
88 @Override 117 @Override
89 public ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint) { 118 public ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint) {
90 var pQuery = dnf2PQuery.getAlreadyTranslated(dnf); 119 checkNotConfigured();
91 if (pQuery == null) { 120 dnf2PQuery.hint(dnf, queryEvaluationHint);
92 throw new IllegalArgumentException(
93 "Cannot specify hint for %s, because it was not added to the query engine".formatted(dnf.name()));
94 }
95 pQuery.setEvaluationHints(pQuery.getEvaluationHints().overrideBy(queryEvaluationHint));
96 return this; 121 return this;
97 } 122 }
98 123
99 @Override 124 @Override
100 public ViatraModelQueryStoreAdapterImpl createStoreAdapter(ModelStore store) { 125 public void doConfigure(ModelStoreBuilder storeBuilder) {
126 dnf2PQuery.assertNoUnusedHints();
127 }
128
129 @Override
130 public ViatraModelQueryStoreAdapterImpl doBuild(ModelStore store) {
101 validateSymbols(store); 131 validateSymbols(store);
102 return new ViatraModelQueryStoreAdapterImpl(store, engineOptionsBuilder.build(), dnf2PQuery.getRelationViews(), 132 return new ViatraModelQueryStoreAdapterImpl(store, buildEngineOptions(), dnf2PQuery.getSymbolViews(),
103 Collections.unmodifiableMap(querySpecifications)); 133 Collections.unmodifiableMap(querySpecifications), Collections.unmodifiableSet(vacuousQueries));
134 }
135
136 private ViatraQueryEngineOptions buildEngineOptions() {
137 // Workaround: manually override the default backend, because {@link ViatraQueryEngineOptions.Builder}
138 // ignores all backend requirements except {@code SPECIFIC}.
139 switch (defaultHint.getQueryBackendRequirementType()) {
140 case SPECIFIC -> engineOptionsBuilder.withDefaultBackend(defaultHint.getQueryBackendFactory());
141 case DEFAULT_CACHING -> engineOptionsBuilder.withDefaultBackend(
142 engineOptionsBuilder.build().getDefaultCachingBackendFactory());
143 case DEFAULT_SEARCH -> engineOptionsBuilder.withDefaultBackend(
144 engineOptionsBuilder.build().getDefaultSearchBackendFactory());
145 case UNSPECIFIED -> {
146 // Nothing to do, leave the default backend unchanged.
147 }
148 }
149 engineOptionsBuilder.withDefaultHint(defaultHint);
150 return engineOptionsBuilder.build();
104 } 151 }
105 152
106 private void validateSymbols(ModelStore store) { 153 private void validateSymbols(ModelStore store) {
107 var symbols = store.getSymbols(); 154 var symbols = store.getSymbols();
108 for (var relationView : dnf2PQuery.getRelationViews().keySet()) { 155 for (var symbolView : dnf2PQuery.getSymbolViews().keySet()) {
109 var symbol = relationView.getSymbol(); 156 var symbol = symbolView.getSymbol();
110 if (!symbols.contains(symbol)) { 157 if (!symbols.contains(symbol)) {
111 throw new IllegalArgumentException("Cannot query relation view %s: symbol %s is not in the model" 158 throw new IllegalArgumentException("Cannot query view %s: symbol %s is not in the model"
112 .formatted(relationView, symbol)); 159 .formatted(symbolView, symbol));
113 } 160 }
114 } 161 }
115 } 162 }
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java
index 394e407e..11a3c7fd 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.eclipse.viatra.query.runtime.api.IQuerySpecification; 8import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
@@ -5,27 +10,34 @@ import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
5import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
6import tools.refinery.store.model.Model; 11import tools.refinery.store.model.Model;
7import tools.refinery.store.model.ModelStore; 12import tools.refinery.store.model.ModelStore;
8import tools.refinery.store.query.DNF; 13import tools.refinery.store.query.dnf.AnyQuery;
9import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; 14import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter;
10import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; 15import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
11import tools.refinery.store.query.view.AnyRelationView; 16import tools.refinery.store.query.view.AnySymbolView;
12 17
13import java.util.Collection; 18import java.util.*;
14import java.util.Map;
15 19
16public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter { 20public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter {
17 private final ModelStore store; 21 private final ModelStore store;
18 private final ViatraQueryEngineOptions engineOptions; 22 private final ViatraQueryEngineOptions engineOptions;
19 private final Map<AnyRelationView, IInputKey> inputKeys; 23 private final Map<AnySymbolView, IInputKey> inputKeys;
20 private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications; 24 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications;
25 private final Set<AnyQuery> vacuousQueries;
26 private final Set<AnyQuery> allQueries;
21 27
22 ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, 28 ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions,
23 Map<AnyRelationView, IInputKey> inputKeys, 29 Map<AnySymbolView, IInputKey> inputKeys,
24 Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications) { 30 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications,
31 Set<AnyQuery> vacuousQueries) {
25 this.store = store; 32 this.store = store;
26 this.engineOptions = engineOptions; 33 this.engineOptions = engineOptions;
27 this.inputKeys = inputKeys; 34 this.inputKeys = inputKeys;
28 this.querySpecifications = querySpecifications; 35 this.querySpecifications = querySpecifications;
36 this.vacuousQueries = vacuousQueries;
37 var mutableAllQueries = new LinkedHashSet<AnyQuery>(querySpecifications.size() + vacuousQueries.size());
38 mutableAllQueries.addAll(querySpecifications.keySet());
39 mutableAllQueries.addAll(vacuousQueries);
40 this.allQueries = Collections.unmodifiableSet(mutableAllQueries);
29 } 41 }
30 42
31 @Override 43 @Override
@@ -33,23 +45,27 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd
33 return store; 45 return store;
34 } 46 }
35 47
36 public Collection<AnyRelationView> getRelationViews() { 48 public Collection<AnySymbolView> getSymbolViews() {
37 return inputKeys.keySet(); 49 return inputKeys.keySet();
38 } 50 }
39 51
40 public Map<AnyRelationView, IInputKey> getInputKeys() { 52 public Map<AnySymbolView, IInputKey> getInputKeys() {
41 return inputKeys; 53 return inputKeys;
42 } 54 }
43 55
44 @Override 56 @Override
45 public Collection<DNF> getQueries() { 57 public Collection<AnyQuery> getQueries() {
46 return querySpecifications.keySet(); 58 return allQueries;
47 } 59 }
48 60
49 Map<DNF, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() { 61 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() {
50 return querySpecifications; 62 return querySpecifications;
51 } 63 }
52 64
65 Set<AnyQuery> getVacuousQueries() {
66 return vacuousQueries;
67 }
68
53 @Override 69 @Override
54 public ViatraQueryEngineOptions getEngineOptions() { 70 public ViatraQueryEngineOptions getEngineOptions() {
55 return engineOptions; 71 return engineOptions;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java
deleted file mode 100644
index e0bca9e0..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java
+++ /dev/null
@@ -1,97 +0,0 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator;
4import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
5import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
6import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality;
7import tools.refinery.store.representation.cardinality.UpperCardinalities;
8import tools.refinery.store.representation.cardinality.UpperCardinality;
9
10import java.util.stream.Stream;
11
12public class UpperCardinalitySumAggregationOperator implements IMultisetAggregationOperator<UpperCardinality,
13 UpperCardinalitySumAggregationOperator.Accumulator, UpperCardinality> {
14 public static final UpperCardinalitySumAggregationOperator INSTANCE = new UpperCardinalitySumAggregationOperator();
15
16 public static final BoundAggregator BOUND_AGGREGATOR = new BoundAggregator(INSTANCE, UpperCardinality.class,
17 UpperCardinality.class);
18
19 private UpperCardinalitySumAggregationOperator() {
20 // Singleton constructor.
21 }
22
23 @Override
24 public String getName() {
25 return "sum<UpperCardinality>";
26 }
27
28 @Override
29 public String getShortDescription() {
30 return "%s computes the sum of finite or unbounded upper cardinalities".formatted(getName());
31 }
32
33 @Override
34 public Accumulator createNeutral() {
35 return new Accumulator();
36 }
37
38 @Override
39 public boolean isNeutral(Accumulator result) {
40 return result.sumFiniteUpperBounds == 0 && result.countUnbounded == 0;
41 }
42
43 @Override
44 public Accumulator update(Accumulator oldResult, UpperCardinality updateValue, boolean isInsertion) {
45 if (updateValue instanceof FiniteUpperCardinality finiteUpperCardinality) {
46 int finiteUpperBound = finiteUpperCardinality.finiteUpperBound();
47 if (isInsertion) {
48 oldResult.sumFiniteUpperBounds += finiteUpperBound;
49 } else {
50 oldResult.sumFiniteUpperBounds -= finiteUpperBound;
51 }
52 } else if (updateValue instanceof UnboundedUpperCardinality) {
53 if (isInsertion) {
54 oldResult.countUnbounded += 1;
55 } else {
56 oldResult.countUnbounded -= 1;
57 }
58 } else {
59 throw new IllegalArgumentException("Unknown UpperCardinality: " + updateValue);
60 }
61 return oldResult;
62 }
63
64 @Override
65 public UpperCardinality getAggregate(Accumulator result) {
66 return result.countUnbounded > 0 ? UpperCardinalities.UNBOUNDED :
67 UpperCardinalities.valueOf(result.sumFiniteUpperBounds);
68 }
69
70 @Override
71 public UpperCardinality aggregateStream(Stream<UpperCardinality> stream) {
72 var result = stream.collect(this::createNeutral, (accumulator, value) -> update(accumulator, value, true),
73 (left, right) -> new Accumulator(left.sumFiniteUpperBounds + right.sumFiniteUpperBounds,
74 left.countUnbounded + right.countUnbounded));
75 return getAggregate(result);
76 }
77
78 @Override
79 public Accumulator clone(Accumulator original) {
80 return new Accumulator(original.sumFiniteUpperBounds, original.countUnbounded);
81 }
82
83 public static class Accumulator {
84 private int sumFiniteUpperBounds;
85
86 private int countUnbounded;
87
88 private Accumulator(int sumFiniteUpperBounds, int countUnbounded) {
89 this.sumFiniteUpperBounds = sumFiniteUpperBounds;
90 this.countUnbounded = countUnbounded;
91 }
92
93 private Accumulator() {
94 this(0, 0);
95 }
96 }
97}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java
index 2a24b67c..8cb199d2 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; 8import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java
index 28bc69d0..7220f8ca 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; 8import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java
index cba3fa08..211eacb4 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java
@@ -1,10 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; 8import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext;
4import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 9import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
5import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; 10import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication;
6import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; 11import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey;
7import tools.refinery.store.query.view.AnyRelationView; 12import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper;
13import tools.refinery.store.query.view.AnySymbolView;
8 14
9import java.util.*; 15import java.util.*;
10 16
@@ -12,9 +18,9 @@ import java.util.*;
12 * The meta context information for String scopes. 18 * The meta context information for String scopes.
13 */ 19 */
14public class RelationalQueryMetaContext extends AbstractQueryMetaContext { 20public class RelationalQueryMetaContext extends AbstractQueryMetaContext {
15 private final Map<AnyRelationView, IInputKey> inputKeys; 21 private final Map<AnySymbolView, IInputKey> inputKeys;
16 22
17 RelationalQueryMetaContext(Map<AnyRelationView, IInputKey> inputKeys) { 23 RelationalQueryMetaContext(Map<AnySymbolView, IInputKey> inputKeys) {
18 this.inputKeys = inputKeys; 24 this.inputKeys = inputKeys;
19 } 25 }
20 26
@@ -37,26 +43,43 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext {
37 43
38 @Override 44 @Override
39 public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) { 45 public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) {
40 var relationView = checkKey(implyingKey); 46 if (implyingKey instanceof JavaTransitiveInstancesKey) {
41 var relationViewImplications = relationView.getImpliedRelationViews(); 47 return List.of();
48 }
49 var symbolView = checkKey(implyingKey);
50 var relationViewImplications = symbolView.getImpliedRelationViews();
42 var inputKeyImplications = new HashSet<InputKeyImplication>(relationViewImplications.size()); 51 var inputKeyImplications = new HashSet<InputKeyImplication>(relationViewImplications.size());
43 for (var relationViewImplication : relationViewImplications) { 52 for (var relationViewImplication : relationViewImplications) {
44 if (!relationView.equals(relationViewImplication.implyingRelationView())) { 53 if (!symbolView.equals(relationViewImplication.implyingView())) {
45 throw new IllegalArgumentException("Relation view %s returned unrelated implication %s".formatted( 54 throw new IllegalArgumentException("Relation view %s returned unrelated implication %s".formatted(
46 relationView, relationViewImplication)); 55 symbolView, relationViewImplication));
47 } 56 }
48 var impliedInputKey = inputKeys.get(relationViewImplication.impliedRelationView()); 57 var impliedInputKey = inputKeys.get(relationViewImplication.impliedView());
49 // Ignore implications not relevant for any queries included in the model. 58 // Ignore implications not relevant for any queries included in the model.
50 if (impliedInputKey != null) { 59 if (impliedInputKey != null) {
51 inputKeyImplications.add(new InputKeyImplication(implyingKey, impliedInputKey, 60 inputKeyImplications.add(new InputKeyImplication(implyingKey, impliedInputKey,
52 relationViewImplication.impliedIndices())); 61 relationViewImplication.impliedIndices()));
53 } 62 }
54 } 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 }
55 return inputKeyImplications; 75 return inputKeyImplications;
56 } 76 }
57 77
58 @Override 78 @Override
59 public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) { 79 public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) {
80 if (key instanceof JavaTransitiveInstancesKey) {
81 return Map.of();
82 }
60 var relationView = checkKey(key); 83 var relationView = checkKey(key);
61 var functionalDependencies = relationView.getFunctionalDependencies(); 84 var functionalDependencies = relationView.getFunctionalDependencies();
62 var flattened = new HashMap<Set<Integer>, Set<Integer>>(functionalDependencies.size()); 85 var flattened = new HashMap<Set<Integer>, Set<Integer>>(functionalDependencies.size());
@@ -75,20 +98,20 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext {
75 return flattened; 98 return flattened;
76 } 99 }
77 100
78 private static void checkValidIndices(AnyRelationView relationView, Collection<Integer> indices) { 101 private static void checkValidIndices(AnySymbolView relationView, Collection<Integer> indices) {
79 indices.stream().filter(relationView::invalidIndex).findAny().ifPresent(i -> { 102 indices.stream().filter(relationView::invalidIndex).findAny().ifPresent(i -> {
80 throw new IllegalArgumentException("Index %d is invalid for %s".formatted(i, relationView)); 103 throw new IllegalArgumentException("Index %d is invalid for %s".formatted(i, relationView));
81 }); 104 });
82 } 105 }
83 106
84 public AnyRelationView checkKey(IInputKey key) { 107 public AnySymbolView checkKey(IInputKey key) {
85 if (!(key instanceof RelationViewWrapper wrapper)) { 108 if (!(key instanceof SymbolViewWrapper wrapper)) {
86 throw new IllegalArgumentException("The input key %s is not a valid input key".formatted(key)); 109 throw new IllegalArgumentException("The input key %s is not a valid input key".formatted(key));
87 } 110 }
88 var relationView = wrapper.getWrappedKey(); 111 var symbolView = wrapper.getWrappedKey();
89 if (!inputKeys.containsKey(relationView)) { 112 if (!inputKeys.containsKey(symbolView)) {
90 throw new IllegalArgumentException("The input key %s is not present in the model".formatted(key)); 113 throw new IllegalArgumentException("The input key %s is not present in the model".formatted(key));
91 } 114 }
92 return relationView; 115 return symbolView;
93 } 116 }
94} 117}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java
index 01d20d3e..0f2daca8 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.*; 8import org.eclipse.viatra.query.runtime.matchers.context.*;
@@ -8,9 +13,9 @@ import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
8import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; 13import org.eclipse.viatra.query.runtime.matchers.util.Accuracy;
9import tools.refinery.store.model.Model; 14import tools.refinery.store.model.Model;
10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 15import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
11import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; 16import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper;
12import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; 17import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener;
13import tools.refinery.store.query.view.AnyRelationView; 18import tools.refinery.store.query.view.AnySymbolView;
14 19
15import java.lang.reflect.InvocationTargetException; 20import java.lang.reflect.InvocationTargetException;
16import java.util.Iterator; 21import java.util.Iterator;
@@ -54,8 +59,9 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
54 59
55 @Override 60 @Override
56 public boolean isIndexed(IInputKey key, IndexingService service) { 61 public boolean isIndexed(IInputKey key, IndexingService service) {
57 if (key instanceof AnyRelationView relationalKey) { 62 if (key instanceof SymbolViewWrapper wrapper) {
58 return this.modelUpdateListener.containsRelationView(relationalKey); 63 var symbolViewKey = wrapper.getWrappedKey();
64 return this.modelUpdateListener.containsSymbolView(symbolViewKey);
59 } else { 65 } else {
60 return false; 66 return false;
61 } 67 }
@@ -68,13 +74,13 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
68 } 74 }
69 } 75 }
70 76
71 AnyRelationView checkKey(IInputKey key) { 77 AnySymbolView checkKey(IInputKey key) {
72 if (key instanceof RelationViewWrapper wrappedKey) { 78 if (key instanceof SymbolViewWrapper wrappedKey) {
73 var relationViewKey = wrappedKey.getWrappedKey(); 79 var symbolViewKey = wrappedKey.getWrappedKey();
74 if (modelUpdateListener.containsRelationView(relationViewKey)) { 80 if (modelUpdateListener.containsSymbolView(symbolViewKey)) {
75 return relationViewKey; 81 return symbolViewKey;
76 } else { 82 } else {
77 throw new IllegalStateException("Query is asking for non-indexed key %s".formatted(relationViewKey)); 83 throw new IllegalStateException("Query is asking for non-indexed key %s".formatted(symbolViewKey));
78 } 84 }
79 } else { 85 } else {
80 throw new IllegalStateException("Query is asking for non-relational key"); 86 throw new IllegalStateException("Query is asking for non-relational key");
@@ -83,10 +89,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
83 89
84 @Override 90 @Override
85 public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { 91 public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) {
86 var relationViewKey = checkKey(key); 92 Iterator<Object[]> iterator = enumerate(key, seedMask, seed).iterator();
87 Iterable<Object[]> allObjects = relationViewKey.getAll(model);
88 Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed));
89 Iterator<Object[]> iterator = filteredBySeed.iterator();
90 int result = 0; 93 int result = 0;
91 while (iterator.hasNext()) { 94 while (iterator.hasNext()) {
92 iterator.next(); 95 iterator.next();
@@ -102,13 +105,25 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
102 105
103 @Override 106 @Override
104 public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { 107 public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) {
108 var filteredBySeed = enumerate(key, seedMask, seed);
109 return map(filteredBySeed, Tuples::flatTupleOf);
110 }
111
112 @Override
113 public Iterable<?> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) {
114 var index = seedMask.getFirstOmittedIndex().orElseThrow(
115 () -> new IllegalArgumentException("Seed mask does not omit a value"));
116 var filteredBySeed = enumerate(key, seedMask, seed);
117 return map(filteredBySeed, array -> array[index]);
118 }
119
120 private Iterable<Object[]> enumerate(IInputKey key, TupleMask seedMask, ITuple seed) {
105 var relationViewKey = checkKey(key); 121 var relationViewKey = checkKey(key);
106 Iterable<Object[]> allObjects = relationViewKey.getAll(model); 122 Iterable<Object[]> allObjects = relationViewKey.getAll(model);
107 Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); 123 return filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed));
108 return map(filteredBySeed, Tuples::flatTupleOf);
109 } 124 }
110 125
111 private boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { 126 private static boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) {
112 for (int i = 0; i < seedMask.indices.length; i++) { 127 for (int i = 0; i < seedMask.indices.length; i++) {
113 final Object seedElement = seed.get(i); 128 final Object seedElement = seed.get(i);
114 final Object tupleElement = tuple[seedMask.indices[i]]; 129 final Object tupleElement = tuple[seedMask.indices[i]];
@@ -120,11 +135,6 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
120 } 135 }
121 136
122 @Override 137 @Override
123 public Iterable<?> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) {
124 return enumerateTuples(key, seedMask, seed);
125 }
126
127 @Override
128 public boolean containsTuple(IInputKey key, ITuple seed) { 138 public boolean containsTuple(IInputKey key, ITuple seed) {
129 var relationViewKey = checkKey(key); 139 var relationViewKey = checkKey(key);
130 return relationViewKey.get(model, seed.getElements()); 140 return relationViewKey.get(model, seed.getElements());
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java
new file mode 100644
index 00000000..37177cbf
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java
@@ -0,0 +1,76 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch;
10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor;
14
15import java.util.Iterator;
16
17/**
18 * An operation that can be used to enumerate all possible values for a single position based on a constraint
19 * @author Zoltan Ujhelyi, Akos Horvath
20 * @since 2.0
21 */
22abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor {
23
24 private Iterator<? extends T> it;
25
26 /**
27 * Returns an iterator with the possible options from the current state
28 * @since 2.0
29 */
30 @SuppressWarnings("squid:S1452")
31 protected abstract Iterator<? extends T> getIterator(MatchingFrame frame, ISearchContext context);
32 /**
33 * Updates the frame with the next element of the iterator. Called during {@link #execute(MatchingFrame, ISearchContext)}.
34 *
35 * @return true if the update is successful or false otherwise; in case of false is returned, the next element should be taken from the iterator.
36 * @since 2.0
37 */
38 protected abstract boolean fillInValue(T newValue, MatchingFrame frame, ISearchContext context);
39
40 /**
41 * Restores the frame to the state before {@link #fillInValue(Object, MatchingFrame, ISearchContext)}. Called during
42 * {@link #onBacktrack(MatchingFrame, ISearchContext)}.
43 *
44 * @since 2.0
45 */
46 protected abstract void cleanup(MatchingFrame frame, ISearchContext context);
47
48 @Override
49 public void onInitialize(MatchingFrame frame, ISearchContext context) {
50 it = getIterator(frame, context);
51 }
52
53 @Override
54 public void onBacktrack(MatchingFrame frame, ISearchContext context) {
55 it = null;
56
57 }
58
59 /**
60 * Fixed version of {@link org.eclipse.viatra.query.runtime.localsearch.operations.ExtendOperationExecutor#execute}
61 * that handles failed unification of variables correctly.
62 * @param frame The matching frame to extend.
63 * @param context The search context.
64 * @return {@code true} if an extension was found, {@code false} otherwise.
65 */
66 @Override
67 public boolean execute(MatchingFrame frame, ISearchContext context) {
68 while (it.hasNext()) {
69 var newValue = it.next();
70 if (fillInValue(newValue, frame, context)) {
71 return true;
72 }
73 }
74 return false;
75 }
76}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java
new file mode 100644
index 00000000..9d48c785
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch;
10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.IPatternMatcherOperation;
14import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation;
15import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
16import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
17import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
18import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
19import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
20
21import java.util.Iterator;
22import java.util.List;
23import java.util.function.Function;
24
25/**
26 * @author Grill Balázs
27 * @since 1.4
28 *
29 */
30public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatcherOperation {
31
32 private class Executor extends ExtendOperationExecutor<Tuple> {
33 private final VolatileModifiableMaskedTuple maskedTuple;
34
35 public Executor() {
36 maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
37 }
38
39 @Override
40 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) {
41 maskedTuple.updateTuple(frame);
42 IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment());
43 return matcher.getAllMatches(information.getParameterMask(), maskedTuple).iterator();
44 }
45
46 /**
47 * @since 2.0
48 */
49 @Override
50 protected boolean fillInValue(Tuple result, MatchingFrame frame, ISearchContext context) {
51 TupleMask mask = information.getFullFrameMask();
52 // The first loop clears out the elements from a possible previous iteration
53 for(int i : information.getFreeParameterIndices()) {
54 mask.set(frame, i, null);
55 }
56 for(int i : information.getFreeParameterIndices()) {
57 Object oldValue = mask.getValue(frame, i);
58 Object valueToFill = result.get(i);
59 if (oldValue != null && !oldValue.equals(valueToFill)){
60 // If the inverse map contains more than one values for the same key, it means that these arguments are unified by the caller.
61 // In this case if the callee assigns different values the frame shall be dropped
62 return false;
63 }
64 mask.set(frame, i, valueToFill);
65 }
66 return true;
67 }
68
69 @Override
70 protected void cleanup(MatchingFrame frame, ISearchContext context) {
71 TupleMask mask = information.getFullFrameMask();
72 for(int i : information.getFreeParameterIndices()){
73 mask.set(frame, i, null);
74 }
75
76 }
77
78 @Override
79 public ISearchOperation getOperation() {
80 return ExtendPositivePatternCall.this;
81 }
82 }
83
84 private final CallInformation information;
85
86 /**
87 * @since 1.7
88 */
89 public ExtendPositivePatternCall(CallInformation information) {
90 this.information = information;
91 }
92
93 @Override
94 public ISearchOperationExecutor createExecutor() {
95 return new Executor();
96 }
97
98 @Override
99 public List<Integer> getVariablePositions() {
100 return information.getVariablePositions();
101 }
102
103 @Override
104 public String toString() {
105 return toString(Object::toString);
106 }
107
108 @Override
109 public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) {
110 return "extend find " + information.toString(variableMapping);
111 }
112
113 @Override
114 public CallInformation getCallInformation() {
115 return information;
116 }
117}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java
new file mode 100644
index 00000000..cc906f22
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
9import org.eclipse.viatra.query.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction;
10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
12import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
13import org.eclipse.viatra.query.runtime.matchers.util.Accuracy;
14
15import java.util.Optional;
16
17public class FlatCostFunction extends StatisticsBasedConstraintCostFunction {
18 public FlatCostFunction() {
19 // No inverse navigation penalty thanks to relational storage.
20 super(0);
21 }
22
23 @Override
24 public Optional<Long> projectionSize(IConstraintEvaluationContext input, IInputKey supplierKey, TupleMask groupMask, Accuracy requiredAccuracy) {
25 // We always start from an empty model, where every projection is of size 0.
26 // Therefore, projection size estimation is meaningless.
27 return Optional.empty();
28 }
29
30 @Override
31 protected double _calculateCost(TypeConstraint constraint, IConstraintEvaluationContext input) {
32 // Assume a flat cost for each relation. Maybe adjust in the future if we perform indexing?
33 return DEFAULT_COST;
34 }
35}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java
new file mode 100644
index 00000000..96ac4a72
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java
@@ -0,0 +1,137 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch;
10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.IIteratingSearchOperation;
14import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation;
15import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
16import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
17import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
18import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileMaskedTuple;
19import org.eclipse.viatra.query.runtime.matchers.util.Preconditions;
20
21import java.util.*;
22import java.util.function.Function;
23import java.util.stream.Collectors;
24
25/**
26 * @author Zoltan Ujhelyi
27 * @since 1.7
28 */
29public class GenericTypeExtend implements IIteratingSearchOperation {
30 private class Executor extends ExtendOperationExecutor<Tuple> {
31 private final VolatileMaskedTuple maskedTuple;
32
33 public Executor() {
34 this.maskedTuple = new VolatileMaskedTuple(callMask);
35 }
36
37 @Override
38 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) {
39 maskedTuple.updateTuple(frame);
40 return context.getRuntimeContext().enumerateTuples(type, indexerMask, maskedTuple).iterator();
41 }
42
43 @Override
44 protected boolean fillInValue(Tuple newTuple, MatchingFrame frame, ISearchContext context) {
45 for (Integer position : unboundVariableIndices) {
46 frame.setValue(position, null);
47 }
48 for (int i = 0; i < positions.length; i++) {
49 Object newValue = newTuple.get(i);
50 Object oldValue = frame.getValue(positions[i]);
51 if (oldValue != null && !Objects.equals(oldValue, newValue)) {
52 // If positions tuple maps more than one values for the same element (e.g. loop), it means that
53 // these arguments are to unified by the caller. In this case if the callee assigns different values
54 // the frame shall be considered a failed match
55 return false;
56 }
57 frame.setValue(positions[i], newValue);
58 }
59 return true;
60 }
61
62 @Override
63 protected void cleanup(MatchingFrame frame, ISearchContext context) {
64 for (Integer position : unboundVariableIndices) {
65 frame.setValue(position, null);
66 }
67 }
68
69 @Override
70 public ISearchOperation getOperation() {
71 return GenericTypeExtend.this;
72 }
73 }
74
75 private final IInputKey type;
76 private final int[] positions;
77 private final List<Integer> positionList;
78 private final Set<Integer> unboundVariableIndices;
79 private final TupleMask indexerMask;
80 private final TupleMask callMask;
81
82 /**
83 *
84 * @param type
85 * the type to execute the extend operation on
86 * @param positions
87 * the parameter positions that represent the variables of the input key
88 * @param unboundVariableIndices
89 * the set of positions that are bound at the start of the operation
90 */
91 public GenericTypeExtend(IInputKey type, int[] positions, TupleMask callMask, TupleMask indexerMask, Set<Integer> unboundVariableIndices) {
92 Preconditions.checkArgument(positions.length == type.getArity(),
93 "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(),
94 type.getArity(), positions.length);
95 List<Integer> modifiablePositionList = new ArrayList<>();
96 for (int position : positions) {
97 modifiablePositionList.add(position);
98 }
99 this.positionList = Collections.unmodifiableList(modifiablePositionList);
100 this.positions = positions;
101 this.type = type;
102
103 this.unboundVariableIndices = unboundVariableIndices;
104 this.indexerMask = indexerMask;
105 this.callMask = callMask;
106 }
107
108 @Override
109 public IInputKey getIteratedInputKey() {
110 return type;
111 }
112
113 @Override
114 public ISearchOperationExecutor createExecutor() {
115 return new Executor();
116 }
117
118 @Override
119 public List<Integer> getVariablePositions() {
120 return positionList;
121 }
122
123 @Override
124 public String toString() {
125 return toString(Object::toString);
126 }
127
128 @Override
129 public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) {
130 return "extend " + type.getPrettyPrintableName() + "("
131 + positionList.stream()
132 .map(input -> String.format("%s%s", unboundVariableIndices.contains(input) ? "-" : "+", variableMapping.apply(input)))
133 .collect(Collectors.joining(", "))
134 + ")";
135 }
136
137}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java
new file mode 100644
index 00000000..0c77f587
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
9import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
11import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
12import org.eclipse.viatra.query.runtime.localsearch.plan.SimplePlanProvider;
13import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability;
14import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
15import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
16import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
17import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
18import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
19
20public class RelationalLocalSearchBackendFactory implements IQueryBackendFactory {
21 public static final RelationalLocalSearchBackendFactory INSTANCE = new RelationalLocalSearchBackendFactory();
22
23 private RelationalLocalSearchBackendFactory() {
24 }
25
26 @Override
27 public IQueryBackend create(IQueryBackendContext context) {
28 return new LocalSearchBackend(context) {
29 // Create a new {@link IPlanProvider}, because the original {@link LocalSearchBackend#planProvider} is not
30 // accessible.
31 private final IPlanProvider planProvider = new SimplePlanProvider(context.getLogger());
32
33 @Override
34 protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query,
35 QueryEvaluationHint hints) {
36 return new RelationalLocalSearchResultProvider(this, context, query, planProvider, hints);
37 }
38
39 @Override
40 public IQueryBackendFactory getFactory() {
41 return RelationalLocalSearchBackendFactory.this;
42 }
43 };
44 }
45
46 @Override
47 public Class<? extends IQueryBackend> getBackendClass() {
48 return LocalSearchBackend.class;
49 }
50
51 @Override
52 public IMatcherCapability calculateRequiredCapability(PQuery pQuery, QueryEvaluationHint queryEvaluationHint) {
53 return LocalSearchHints.parse(queryEvaluationHint);
54 }
55
56 @Override
57 public boolean isCaching() {
58 return false;
59 }
60}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java
new file mode 100644
index 00000000..da37be14
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
9import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
11import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
12import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.IOperationCompiler;
13import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
14import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
16
17class RelationalLocalSearchResultProvider extends AbstractLocalSearchResultProvider {
18 public RelationalLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query,
19 IPlanProvider planProvider, QueryEvaluationHint userHints) {
20 super(backend, context, query, planProvider, userHints);
21 }
22
23 @Override
24 protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext,
25 LocalSearchHints configuration) {
26 return new RelationalOperationCompiler(runtimeContext);
27 }
28}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java
new file mode 100644
index 00000000..f76ef486
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java
@@ -0,0 +1,70 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue;
9import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
10import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.GenericOperationCompiler;
11import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
12import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext;
13import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
14import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
15import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
16import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
17import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
18
19import java.util.*;
20
21public class RelationalOperationCompiler extends GenericOperationCompiler {
22 public RelationalOperationCompiler(IQueryRuntimeContext runtimeContext) {
23 super(runtimeContext);
24 }
25
26 @Override
27 protected void createExtend(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping) {
28 IInputKey inputKey = typeConstraint.getSupplierKey();
29 Tuple tuple = typeConstraint.getVariablesTuple();
30
31 int[] positions = new int[tuple.getSize()];
32 List<Integer> boundVariableIndices = new ArrayList<>();
33 List<Integer> boundVariables = new ArrayList<>();
34 Set<Integer> unboundVariables = new HashSet<>();
35 for (int i = 0; i < tuple.getSize(); i++) {
36 PVariable variable = (PVariable) tuple.get(i);
37 Integer position = variableMapping.get(variable);
38 positions[i] = position;
39 if (variableBindings.get(typeConstraint).contains(position)) {
40 boundVariableIndices.add(i);
41 boundVariables.add(position);
42 } else {
43 unboundVariables.add(position);
44 }
45 }
46 TupleMask indexerMask = TupleMask.fromSelectedIndices(inputKey.getArity(), boundVariableIndices);
47 TupleMask callMask = TupleMask.fromSelectedIndices(variableMapping.size(), boundVariables);
48 // If multiple tuple elements from the indexer should be bound to the same variable, we must use a
49 // {@link GenericTypeExtend} check whether the tuple elements have the same value.
50 if (unboundVariables.size() == 1 && indexerMask.getSize() + 1 == indexerMask.getSourceWidth()) {
51 operations.add(new GenericTypeExtendSingleValue(inputKey, positions, callMask, indexerMask,
52 unboundVariables.iterator().next()));
53 } else {
54 // Use a fixed version of
55 // {@code org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtend} that handles
56 // failed unification of variables correctly.
57 operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables));
58 }
59 }
60
61 @Override
62 protected void createExtend(PositivePatternCall pCall, Map<PVariable, Integer> variableMapping) {
63 CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall));
64 // Use a fixed version of
65 // {@code org.eclipse.viatra.query.runtime.localsearch.operations.extend.ExtendPositivePatternCall} that handles
66 // failed unification of variables correctly.
67 operations.add(new ExtendPositivePatternCall(information));
68 dependencies.add(information.getCallWithAdornment());
69 }
70}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java
new file mode 100644
index 00000000..99b0a3d8
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
9import org.eclipse.viatra.query.runtime.matchers.backend.IUpdateable;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.resultset.AbstractResultSet;
12import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
13
14public abstract class AbstractViatraMatcher<T> extends AbstractResultSet<T> implements IUpdateable {
15 protected final IQueryResultProvider backend;
16
17 protected AbstractViatraMatcher(ViatraModelQueryAdapterImpl 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-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java
new file mode 100644
index 00000000..47efb2aa
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java
@@ -0,0 +1,52 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14class FunctionalCursor<T> implements Cursor<Tuple, T> {
15 private final IterableIndexer indexer;
16 private final Iterator<org.eclipse.viatra.query.runtime.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-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java
new file mode 100644
index 00000000..db4740cd
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java
@@ -0,0 +1,88 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
10import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.query.dnf.FunctionalQuery;
14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
15import tools.refinery.store.tuple.Tuple;
16
17/**
18 * Directly access the tuples inside a VIATRA pattern matcher.<p>
19 * This class neglects calling
20 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}
21 * and
22 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)},
23 * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial
24 * implementation for these methods.
25 * Using this class with any other runtime context may lead to undefined behavior.
26 */
27public class FunctionalViatraMatcher<T> extends AbstractViatraMatcher<T> {
28 private final TupleMask emptyMask;
29 private final TupleMask omitOutputMask;
30 private final IterableIndexer omitOutputIndexer;
31
32 public FunctionalViatraMatcher(ViatraModelQueryAdapterImpl adapter, FunctionalQuery<T> query,
33 RawPatternMatcher rawPatternMatcher) {
34 super(adapter, query, rawPatternMatcher);
35 int arity = query.arity();
36 int arityWithOutput = arity + 1;
37 emptyMask = TupleMask.empty(arityWithOutput);
38 omitOutputMask = TupleMask.omit(arity, arityWithOutput);
39 if (backend instanceof RetePatternMatcher reteBackend) {
40 var maybeIterableOmitOutputIndexer = IndexerUtils.getIndexer(reteBackend, omitOutputMask);
41 if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) {
42 omitOutputIndexer = iterableOmitOutputIndexer;
43 } else {
44 omitOutputIndexer = null;
45 }
46 } else {
47 omitOutputIndexer = null;
48 }
49 }
50
51 @Override
52 public T get(Tuple parameters) {
53 var tuple = MatcherUtils.toViatraTuple(parameters);
54 if (omitOutputIndexer == null) {
55 return MatcherUtils.getSingleValue(backend.getAllMatches(omitOutputMask, tuple).iterator());
56 } else {
57 return MatcherUtils.getSingleValue(omitOutputIndexer.get(tuple));
58 }
59 }
60
61 @Override
62 public Cursor<Tuple, T> getAll() {
63 if (omitOutputIndexer == null) {
64 var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf());
65 return new UnsafeFunctionalCursor<>(allMatches.iterator());
66 }
67 return new FunctionalCursor<>(omitOutputIndexer);
68 }
69
70 @Override
71 public int size() {
72 if (omitOutputIndexer == null) {
73 return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf());
74 }
75 return omitOutputIndexer.getBucketCount();
76 }
77
78 @Override
79 public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) {
80 var key = MatcherUtils.keyToRefineryTuple(updateElement);
81 var value = MatcherUtils.<T>getValue(updateElement);
82 if (isInsertion) {
83 notifyChange(key, null, value);
84 } else {
85 notifyChange(key, value, null);
86 }
87 }
88}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java
new file mode 100644
index 00000000..15f00b2d
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.rete.index.Indexer;
10import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
12import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo;
13
14import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles;
16import java.lang.invoke.MethodType;
17
18final class IndexerUtils {
19 private static final MethodHandle GET_ENGINE_HANDLE;
20 private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE;
21 private static final MethodHandle ACCESS_PROJECTION_HANDLE;
22
23 static {
24 try {
25 var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup());
26 GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class);
27 GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace",
28 RecipeTraceInfo.class);
29 ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection",
30 MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class));
31 } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) {
32 throw new IllegalStateException("Cannot access private members of %s"
33 .formatted(RetePatternMatcher.class.getPackageName()), e);
34 }
35 }
36
37 private IndexerUtils() {
38 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
39 }
40
41 public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) {
42 try {
43 var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend);
44 var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend);
45 return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask);
46 } catch (Error e) {
47 // Fatal JVM errors should not be wrapped.
48 throw e;
49 } catch (Throwable e) {
50 throw new IllegalStateException("Cannot access matcher for mask " + mask, e);
51 }
52 }
53}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java
new file mode 100644
index 00000000..6e24812a
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java
@@ -0,0 +1,115 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
10import org.jetbrains.annotations.Nullable;
11import tools.refinery.store.tuple.*;
12
13import java.util.Iterator;
14
15final class MatcherUtils {
16 private MatcherUtils() {
17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
18 }
19
20 public static org.eclipse.viatra.query.runtime.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-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java
new file mode 100644
index 00000000..5b82c4b7
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher;
9import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
10import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
11
12public class RawPatternMatcher extends GenericPatternMatcher {
13 public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
14 super(specification);
15 }
16
17 IQueryResultProvider getBackend() {
18 return backend;
19 }
20}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java
new file mode 100644
index 00000000..1dc8f5db
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java
@@ -0,0 +1,47 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14class RelationalCursor implements Cursor<Tuple, Boolean> {
15 private final Iterator<? extends ITuple> tuplesIterator;
16 private boolean terminated;
17 private Tuple key;
18
19 public RelationalCursor(Iterator<? extends ITuple> tuplesIterator) {
20 this.tuplesIterator = tuplesIterator;
21 }
22
23 @Override
24 public Tuple getKey() {
25 return key;
26 }
27
28 @Override
29 public Boolean getValue() {
30 return true;
31 }
32
33 @Override
34 public boolean isTerminated() {
35 return terminated;
36 }
37
38 @Override
39 public boolean move() {
40 if (!terminated && tuplesIterator.hasNext()) {
41 key = MatcherUtils.toRefineryTuple(tuplesIterator.next());
42 return true;
43 }
44 terminated = true;
45 return false;
46 }
47}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java
new file mode 100644
index 00000000..ac95dcc0
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java
@@ -0,0 +1,80 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
10import org.eclipse.viatra.query.runtime.rete.index.Indexer;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.map.Cursors;
14import tools.refinery.store.query.dnf.RelationalQuery;
15import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
16import tools.refinery.store.tuple.Tuple;
17
18/**
19 * Directly access the tuples inside a VIATRA pattern matcher.<p>
20 * This class neglects calling
21 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}
22 * and
23 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)},
24 * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial
25 * implementation for these methods.
26 * Using this class with any other runtime context may lead to undefined behavior.
27 */
28public class RelationalViatraMatcher extends AbstractViatraMatcher<Boolean> {
29 private final TupleMask emptyMask;
30 private final TupleMask identityMask;
31 private final Indexer emptyMaskIndexer;
32
33 public RelationalViatraMatcher(ViatraModelQueryAdapterImpl adapter, RelationalQuery query,
34 RawPatternMatcher rawPatternMatcher) {
35 super(adapter, query, rawPatternMatcher);
36 int arity = query.arity();
37 emptyMask = TupleMask.empty(arity);
38 identityMask = TupleMask.identity(arity);
39 if (backend instanceof RetePatternMatcher reteBackend) {
40 emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, emptyMask);
41 } else {
42 emptyMaskIndexer = null;
43 }
44 }
45
46 @Override
47 public Boolean get(Tuple parameters) {
48 var tuple = MatcherUtils.toViatraTuple(parameters);
49 if (emptyMaskIndexer == null) {
50 return backend.hasMatch(identityMask, tuple);
51 }
52 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
53 return matches != null && matches.contains(tuple);
54 }
55
56 @Override
57 public Cursor<Tuple, Boolean> getAll() {
58 if (emptyMaskIndexer == null) {
59 var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf());
60 return new RelationalCursor(allMatches.iterator());
61 }
62 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
63 return matches == null ? Cursors.empty() : new RelationalCursor(matches.stream().iterator());
64 }
65
66 @Override
67 public int size() {
68 if (emptyMaskIndexer == null) {
69 return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf());
70 }
71 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
72 return matches == null ? 0 : matches.size();
73 }
74
75 @Override
76 public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) {
77 var key = MatcherUtils.toRefineryTuple(updateElement);
78 notifyChange(key, !isInsertion, isInsertion);
79 }
80}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java
new file mode 100644
index 00000000..b0b507fe
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java
@@ -0,0 +1,55 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14/**
15 * Cursor for a functional result set that iterates over a stream of raw matches and doesn't check whether the
16 * functional dependency of the output on the inputs is obeyed.
17 * @param <T> The output type.
18 */
19class UnsafeFunctionalCursor<T> implements Cursor<Tuple, T> {
20 private final Iterator<? extends ITuple> tuplesIterator;
21 private boolean terminated;
22 private Tuple key;
23 private T value;
24
25 public UnsafeFunctionalCursor(Iterator<? extends ITuple> tuplesIterator) {
26 this.tuplesIterator = tuplesIterator;
27 }
28
29 @Override
30 public Tuple getKey() {
31 return key;
32 }
33
34 @Override
35 public T getValue() {
36 return value;
37 }
38
39 @Override
40 public boolean isTerminated() {
41 return terminated;
42 }
43
44 @Override
45 public boolean move() {
46 if (!terminated && tuplesIterator.hasNext()) {
47 var match = tuplesIterator.next();
48 key = MatcherUtils.keyToRefineryTuple(match);
49 value = MatcherUtils.getValue(match);
50 return true;
51 }
52 terminated = true;
53 return false;
54 }
55}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java
new file mode 100644
index 00000000..cf127291
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.Term;
10
11class AssumptionEvaluator extends TermEvaluator<Boolean> {
12 public AssumptionEvaluator(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-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java
deleted file mode 100644
index 60f1bcae..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java
+++ /dev/null
@@ -1,223 +0,0 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
5import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
6import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
7import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation;
8import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality;
9import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter;
10import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.NegativePatternCall;
12import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
13import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
14import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
15import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
16import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
17import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
18import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
19import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
20import tools.refinery.store.query.DNF;
21import tools.refinery.store.query.DNFAnd;
22import tools.refinery.store.query.DNFUtils;
23import tools.refinery.store.query.Variable;
24import tools.refinery.store.query.atom.*;
25import tools.refinery.store.query.view.AnyRelationView;
26
27import java.util.*;
28import java.util.function.Function;
29import java.util.stream.Collectors;
30
31public class DNF2PQuery {
32 private static final Object P_CONSTRAINT_LOCK = new Object();
33
34 private final Set<DNF> translating = new LinkedHashSet<>();
35
36 private final Map<DNF, RawPQuery> dnf2PQueryMap = new HashMap<>();
37
38 private final Map<AnyRelationView, RelationViewWrapper> view2WrapperMap = new LinkedHashMap<>();
39
40 private final Map<AnyRelationView, RawPQuery> view2EmbeddedMap = new HashMap<>();
41
42 private Function<DNF, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null,
43 QueryEvaluationHint.BackendRequirement.UNSPECIFIED);
44
45 public void setComputeHint(Function<DNF, QueryEvaluationHint> computeHint) {
46 this.computeHint = computeHint;
47 }
48
49 public RawPQuery translate(DNF dnfQuery) {
50 if (translating.contains(dnfQuery)) {
51 var path = translating.stream().map(DNF::name).collect(Collectors.joining(" -> "));
52 throw new IllegalStateException("Circular reference %s -> %s detected".formatted(path,
53 dnfQuery.name()));
54 }
55 // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant
56 // way, which would cause a ConcurrentModificationException with computeIfAbsent.
57 var pQuery = dnf2PQueryMap.get(dnfQuery);
58 if (pQuery == null) {
59 translating.add(dnfQuery);
60 try {
61 pQuery = doTranslate(dnfQuery);
62 dnf2PQueryMap.put(dnfQuery, pQuery);
63 } finally {
64 translating.remove(dnfQuery);
65 }
66 }
67 return pQuery;
68 }
69
70 public Map<AnyRelationView, IInputKey> getRelationViews() {
71 return Collections.unmodifiableMap(view2WrapperMap);
72 }
73
74 public RawPQuery getAlreadyTranslated(DNF dnfQuery) {
75 return dnf2PQueryMap.get(dnfQuery);
76 }
77
78 private RawPQuery doTranslate(DNF dnfQuery) {
79 var pQuery = new RawPQuery(dnfQuery.getUniqueName());
80 pQuery.setEvaluationHints(computeHint.apply(dnfQuery));
81
82 Map<Variable, PParameter> parameters = new HashMap<>();
83 for (Variable variable : dnfQuery.getParameters()) {
84 parameters.put(variable, new PParameter(variable.getUniqueName()));
85 }
86
87 List<PParameter> parameterList = new ArrayList<>();
88 for (var param : dnfQuery.getParameters()) {
89 parameterList.add(parameters.get(param));
90 }
91 pQuery.setParameters(parameterList);
92
93 for (var functionalDependency : dnfQuery.getFunctionalDependencies()) {
94 var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency");
95 for (var forEachVariable : functionalDependency.forEach()) {
96 functionalDependencyAnnotation.addAttribute("forEach", forEachVariable.getUniqueName());
97 }
98 for (var uniqueVariable : functionalDependency.unique()) {
99 functionalDependencyAnnotation.addAttribute("unique", uniqueVariable.getUniqueName());
100 }
101 pQuery.addAnnotation(functionalDependencyAnnotation);
102 }
103
104 // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates
105 // global static state (<code>nextID</code>) without locking. Therefore, we need to synchronize before creating
106 // any query constraints to avoid a data race.
107 synchronized (P_CONSTRAINT_LOCK) {
108 for (DNFAnd clause : dnfQuery.getClauses()) {
109 PBody body = new PBody(pQuery);
110 List<ExportedParameter> symbolicParameters = new ArrayList<>();
111 for (var param : dnfQuery.getParameters()) {
112 PVariable pVar = body.getOrCreateVariableByName(param.getUniqueName());
113 symbolicParameters.add(new ExportedParameter(body, pVar, parameters.get(param)));
114 }
115 body.setSymbolicParameters(symbolicParameters);
116 pQuery.addBody(body);
117 for (DNFAtom constraint : clause.constraints()) {
118 translateDNFAtom(constraint, body);
119 }
120 }
121 }
122
123 return pQuery;
124 }
125
126 private void translateDNFAtom(DNFAtom constraint, PBody body) {
127 if (constraint instanceof EquivalenceAtom equivalenceAtom) {
128 translateEquivalenceAtom(equivalenceAtom, body);
129 } else if (constraint instanceof RelationViewAtom relationViewAtom) {
130 translateRelationViewAtom(relationViewAtom, body);
131 } else if (constraint instanceof DNFCallAtom callAtom) {
132 translateCallAtom(callAtom, body);
133 } else if (constraint instanceof ConstantAtom constantAtom) {
134 translateConstantAtom(constantAtom, body);
135 } else {
136 throw new IllegalArgumentException("Unknown constraint: " + constraint.toString());
137 }
138 }
139
140 private void translateEquivalenceAtom(EquivalenceAtom equivalence, PBody body) {
141 PVariable varSource = body.getOrCreateVariableByName(equivalence.left().getUniqueName());
142 PVariable varTarget = body.getOrCreateVariableByName(equivalence.right().getUniqueName());
143 if (equivalence.positive()) {
144 new Equality(body, varSource, varTarget);
145 } else {
146 new Inequality(body, varSource, varTarget);
147 }
148 }
149
150 private void translateRelationViewAtom(RelationViewAtom relationViewAtom, PBody body) {
151 var substitution = translateSubstitution(relationViewAtom.getSubstitution(), body);
152 var polarity = relationViewAtom.getPolarity();
153 var relationView = relationViewAtom.getTarget();
154 if (polarity == CallPolarity.POSITIVE) {
155 new TypeConstraint(body, substitution, wrapView(relationView));
156 } else {
157 var embeddedPQuery = translateEmbeddedRelationViewPQuery(relationView);
158 switch (polarity) {
159 case TRANSITIVE -> new BinaryTransitiveClosure(body, substitution, embeddedPQuery);
160 case NEGATIVE -> new NegativePatternCall(body, substitution, embeddedPQuery);
161 default -> throw new IllegalArgumentException("Unknown polarity: " + polarity);
162 }
163 }
164 }
165
166 private static Tuple translateSubstitution(List<Variable> substitution, PBody body) {
167 int arity = substitution.size();
168 Object[] variables = new Object[arity];
169 for (int i = 0; i < arity; i++) {
170 var variable = substitution.get(i);
171 variables[i] = body.getOrCreateVariableByName(variable.getUniqueName());
172 }
173 return Tuples.flatTupleOf(variables);
174 }
175
176 private RawPQuery translateEmbeddedRelationViewPQuery(AnyRelationView relationView) {
177 return view2EmbeddedMap.computeIfAbsent(relationView, this::doTranslateEmbeddedRelationViewPQuery);
178 }
179
180 private RawPQuery doTranslateEmbeddedRelationViewPQuery(AnyRelationView relationView) {
181 var embeddedPQuery = new RawPQuery(DNFUtils.generateUniqueName(relationView.name()), PVisibility.EMBEDDED);
182 var body = new PBody(embeddedPQuery);
183 int arity = relationView.arity();
184 var parameters = new ArrayList<PParameter>(arity);
185 var arguments = new Object[arity];
186 var symbolicParameters = new ArrayList<ExportedParameter>(arity);
187 for (int i = 0; i < arity; i++) {
188 var parameterName = "p" + i;
189 var parameter = new PParameter(parameterName);
190 parameters.add(parameter);
191 var variable = body.getOrCreateVariableByName(parameterName);
192 arguments[i] = variable;
193 symbolicParameters.add(new ExportedParameter(body, variable, parameter));
194 }
195 embeddedPQuery.setParameters(parameters);
196 body.setSymbolicParameters(symbolicParameters);
197 var argumentTuple = Tuples.flatTupleOf(arguments);
198 new TypeConstraint(body, argumentTuple, wrapView(relationView));
199 embeddedPQuery.addBody(body);
200 return embeddedPQuery;
201 }
202
203 private RelationViewWrapper wrapView(AnyRelationView relationView) {
204 return view2WrapperMap.computeIfAbsent(relationView, RelationViewWrapper::new);
205 }
206
207 private void translateCallAtom(DNFCallAtom callAtom, PBody body) {
208 var variablesTuple = translateSubstitution(callAtom.getSubstitution(), body);
209 var translatedReferred = translate(callAtom.getTarget());
210 var polarity = callAtom.getPolarity();
211 switch (polarity) {
212 case POSITIVE -> new PositivePatternCall(body, variablesTuple, translatedReferred);
213 case TRANSITIVE -> new BinaryTransitiveClosure(body, variablesTuple, translatedReferred);
214 case NEGATIVE -> new NegativePatternCall(body, variablesTuple, translatedReferred);
215 default -> throw new IllegalArgumentException("Unknown polarity: " + polarity);
216 }
217 }
218
219 private void translateConstantAtom(ConstantAtom constantAtom, PBody body) {
220 var variable = body.getOrCreateVariableByName(constantAtom.variable().getUniqueName());
221 new ConstantValue(body, variable, constantAtom.nodeId());
222 }
223}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
new file mode 100644
index 00000000..5b0ea61d
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
@@ -0,0 +1,266 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
9import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
11import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
12import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
13import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator;
14import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation;
16import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.*;
17import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
18import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
19import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
20import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
21import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
22import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameterDirection;
23import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
24import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
25import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
26import tools.refinery.store.query.dnf.Dnf;
27import tools.refinery.store.query.dnf.DnfClause;
28import tools.refinery.store.query.dnf.SymbolicParameter;
29import tools.refinery.store.query.literal.*;
30import tools.refinery.store.query.term.ConstantTerm;
31import tools.refinery.store.query.term.StatefulAggregator;
32import tools.refinery.store.query.term.StatelessAggregator;
33import tools.refinery.store.query.term.Variable;
34import tools.refinery.store.query.view.AnySymbolView;
35import tools.refinery.store.util.CycleDetectingMapper;
36
37import java.util.*;
38import java.util.function.Function;
39import java.util.stream.Collectors;
40
41public class Dnf2PQuery {
42 private static final Object P_CONSTRAINT_LOCK = new Object();
43 private final CycleDetectingMapper<Dnf, RawPQuery> mapper = new CycleDetectingMapper<>(Dnf::name,
44 this::doTranslate);
45 private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this);
46 private final Map<Dnf, QueryEvaluationHint> hintOverrides = new LinkedHashMap<>();
47 private Function<Dnf, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null,
48 (IQueryBackendFactory) null);
49
50 public void setComputeHint(Function<Dnf, QueryEvaluationHint> computeHint) {
51 this.computeHint = computeHint;
52 }
53
54 public RawPQuery translate(Dnf dnfQuery) {
55 return mapper.map(dnfQuery);
56 }
57
58 public Map<AnySymbolView, IInputKey> getSymbolViews() {
59 return wrapperFactory.getSymbolViews();
60 }
61
62 public void hint(Dnf dnf, QueryEvaluationHint hint) {
63 hintOverrides.compute(dnf, (ignoredKey, existingHint) ->
64 existingHint == null ? hint : existingHint.overrideBy(hint));
65 }
66
67 private QueryEvaluationHint consumeHint(Dnf dnf) {
68 var defaultHint = computeHint.apply(dnf);
69 var existingHint = hintOverrides.remove(dnf);
70 return defaultHint.overrideBy(existingHint);
71 }
72
73 public void assertNoUnusedHints() {
74 if (hintOverrides.isEmpty()) {
75 return;
76 }
77 var unusedHints = hintOverrides.keySet().stream().map(Dnf::name).collect(Collectors.joining(", "));
78 throw new IllegalStateException(
79 "Unused query evaluation hints for %s. Hints must be set before a query is added to the engine"
80 .formatted(unusedHints));
81 }
82
83 private RawPQuery doTranslate(Dnf dnfQuery) {
84 var pQuery = new RawPQuery(dnfQuery.getUniqueName());
85 pQuery.setEvaluationHints(consumeHint(dnfQuery));
86
87 Map<SymbolicParameter, PParameter> parameters = new HashMap<>();
88 List<PParameter> parameterList = new ArrayList<>();
89 for (var parameter : dnfQuery.getSymbolicParameters()) {
90 var direction = switch (parameter.getDirection()) {
91 case OUT -> parameter.isUnifiable() ? PParameterDirection.INOUT : PParameterDirection.OUT;
92 case IN -> throw new IllegalArgumentException("Query %s with input parameter %s is not supported"
93 .formatted(dnfQuery, parameter.getVariable()));
94 };
95 var pParameter = new PParameter(parameter.getVariable().getUniqueName(), null, null, direction);
96 parameters.put(parameter, pParameter);
97 parameterList.add(pParameter);
98 }
99
100 pQuery.setParameters(parameterList);
101
102 for (var functionalDependency : dnfQuery.getFunctionalDependencies()) {
103 var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency");
104 for (var forEachVariable : functionalDependency.forEach()) {
105 functionalDependencyAnnotation.addAttribute("forEach", forEachVariable.getUniqueName());
106 }
107 for (var uniqueVariable : functionalDependency.unique()) {
108 functionalDependencyAnnotation.addAttribute("unique", uniqueVariable.getUniqueName());
109 }
110 pQuery.addAnnotation(functionalDependencyAnnotation);
111 }
112
113 // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates
114 // global static state (<code>nextID</code>) without locking. Therefore, we need to synchronize before creating
115 // any query literals to avoid a data race.
116 synchronized (P_CONSTRAINT_LOCK) {
117 for (DnfClause clause : dnfQuery.getClauses()) {
118 PBody body = new PBody(pQuery);
119 List<ExportedParameter> parameterExports = new ArrayList<>();
120 for (var parameter : dnfQuery.getSymbolicParameters()) {
121 PVariable pVar = body.getOrCreateVariableByName(parameter.getVariable().getUniqueName());
122 parameterExports.add(new ExportedParameter(body, pVar, parameters.get(parameter)));
123 }
124 body.setSymbolicParameters(parameterExports);
125 pQuery.addBody(body);
126 for (Literal literal : clause.literals()) {
127 translateLiteral(literal, body);
128 }
129 }
130 }
131
132 return pQuery;
133 }
134
135 private void translateLiteral(Literal literal, PBody body) {
136 if (literal instanceof EquivalenceLiteral equivalenceLiteral) {
137 translateEquivalenceLiteral(equivalenceLiteral, body);
138 } else if (literal instanceof CallLiteral callLiteral) {
139 translateCallLiteral(callLiteral, body);
140 } else if (literal instanceof ConstantLiteral constantLiteral) {
141 translateConstantLiteral(constantLiteral, body);
142 } else if (literal instanceof AssignLiteral<?> assignLiteral) {
143 translateAssignLiteral(assignLiteral, body);
144 } else if (literal instanceof AssumeLiteral assumeLiteral) {
145 translateAssumeLiteral(assumeLiteral, body);
146 } else if (literal instanceof CountLiteral countLiteral) {
147 translateCountLiteral(countLiteral, body);
148 } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) {
149 translateAggregationLiteral(aggregationLiteral, body);
150 } else {
151 throw new IllegalArgumentException("Unknown literal: " + literal.toString());
152 }
153 }
154
155 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) {
156 PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.left().getUniqueName());
157 PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.right().getUniqueName());
158 if (equivalenceLiteral.positive()) {
159 new Equality(body, varSource, varTarget);
160 } else {
161 new Inequality(body, varSource, varTarget);
162 }
163 }
164
165 private void translateCallLiteral(CallLiteral callLiteral, PBody body) {
166 var polarity = callLiteral.getPolarity();
167 switch (polarity) {
168 case POSITIVE -> {
169 var substitution = translateSubstitution(callLiteral.getArguments(), body);
170 var constraint = callLiteral.getTarget();
171 if (constraint instanceof Dnf dnf) {
172 var pattern = translate(dnf);
173 new PositivePatternCall(body, substitution, pattern);
174 } else if (constraint instanceof AnySymbolView symbolView) {
175 var inputKey = wrapperFactory.getInputKey(symbolView);
176 new TypeConstraint(body, substitution, inputKey);
177 } else {
178 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
179 }
180 }
181 case TRANSITIVE -> {
182 var substitution = translateSubstitution(callLiteral.getArguments(), body);
183 var constraint = callLiteral.getTarget();
184 PQuery pattern;
185 if (constraint instanceof Dnf dnf) {
186 pattern = translate(dnf);
187 } else if (constraint instanceof AnySymbolView symbolView) {
188 pattern = wrapperFactory.wrapSymbolViewIdentityArguments(symbolView);
189 } else {
190 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
191 }
192 new BinaryTransitiveClosure(body, substitution, pattern);
193 }
194 case NEGATIVE -> {
195 var wrappedCall = wrapperFactory.maybeWrapConstraint(callLiteral);
196 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
197 var pattern = wrappedCall.pattern();
198 new NegativePatternCall(body, substitution, pattern);
199 }
200 default -> throw new IllegalArgumentException("Unknown polarity: " + polarity);
201 }
202 }
203
204 private static Tuple translateSubstitution(List<Variable> substitution, PBody body) {
205 int arity = substitution.size();
206 Object[] variables = new Object[arity];
207 for (int i = 0; i < arity; i++) {
208 var variable = substitution.get(i);
209 variables[i] = body.getOrCreateVariableByName(variable.getUniqueName());
210 }
211 return Tuples.flatTupleOf(variables);
212 }
213
214 private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) {
215 var variable = body.getOrCreateVariableByName(constantLiteral.variable().getUniqueName());
216 new ConstantValue(body, variable, constantLiteral.nodeId());
217 }
218
219 private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) {
220 var variable = body.getOrCreateVariableByName(assignLiteral.variable().getUniqueName());
221 var term = assignLiteral.term();
222 if (term instanceof ConstantTerm<T> constantTerm) {
223 new ConstantValue(body, variable, constantTerm.getValue());
224 } else {
225 var evaluator = new TermEvaluator<>(term);
226 new ExpressionEvaluation(body, evaluator, variable);
227 }
228 }
229
230 private void translateAssumeLiteral(AssumeLiteral assumeLiteral, PBody body) {
231 var evaluator = new AssumptionEvaluator(assumeLiteral.term());
232 new ExpressionEvaluation(body, evaluator, null);
233 }
234
235 private void translateCountLiteral(CountLiteral countLiteral, PBody body) {
236 var wrappedCall = wrapperFactory.maybeWrapConstraint(countLiteral);
237 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
238 var resultVariable = body.getOrCreateVariableByName(countLiteral.getResultVariable().getUniqueName());
239 new PatternMatchCounter(body, substitution, wrappedCall.pattern(), resultVariable);
240 }
241
242 private <R, T> void translateAggregationLiteral(AggregationLiteral<R, T> aggregationLiteral, PBody body) {
243 var aggregator = aggregationLiteral.getAggregator();
244 IMultisetAggregationOperator<T, ?, R> aggregationOperator;
245 if (aggregator instanceof StatelessAggregator<R, T> statelessAggregator) {
246 aggregationOperator = new StatelessMultisetAggregator<>(statelessAggregator);
247 } else if (aggregator instanceof StatefulAggregator<R, T> statefulAggregator) {
248 aggregationOperator = new StatefulMultisetAggregator<>(statefulAggregator);
249 } else {
250 throw new IllegalArgumentException("Unknown aggregator: " + aggregator);
251 }
252 var wrappedCall = wrapperFactory.maybeWrapConstraint(aggregationLiteral);
253 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
254 var inputVariable = body.getOrCreateVariableByName(aggregationLiteral.getInputVariable().getUniqueName());
255 var aggregatedColumn = substitution.invertIndex().get(inputVariable);
256 if (aggregatedColumn == null) {
257 throw new IllegalStateException("Input variable %s not found in substitution %s".formatted(inputVariable,
258 substitution));
259 }
260 var boundAggregator = new BoundAggregator(aggregationOperator, aggregator.getInputType(),
261 aggregator.getResultType());
262 var resultVariable = body.getOrCreateVariableByName(aggregationLiteral.getResultVariable().getUniqueName());
263 new AggregatorConstraint(boundAggregator, body, substitution, wrappedCall.pattern(), resultVariable,
264 aggregatedColumn);
265 }
266}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java
new file mode 100644
index 00000000..2b7280f2
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java
@@ -0,0 +1,189 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
10import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter;
12import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
13import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
14import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
16import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
17import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
18import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
19import tools.refinery.store.query.Constraint;
20import tools.refinery.store.query.dnf.Dnf;
21import tools.refinery.store.query.dnf.DnfUtils;
22import tools.refinery.store.query.literal.AbstractCallLiteral;
23import tools.refinery.store.query.term.ParameterDirection;
24import tools.refinery.store.query.term.Variable;
25import tools.refinery.store.query.view.AnySymbolView;
26import tools.refinery.store.query.view.SymbolView;
27import tools.refinery.store.util.CycleDetectingMapper;
28
29import java.util.*;
30import java.util.function.ToIntFunction;
31
32class QueryWrapperFactory {
33 private final Dnf2PQuery dnf2PQuery;
34 private final Map<AnySymbolView, SymbolViewWrapper> view2WrapperMap = new LinkedHashMap<>();
35 private final CycleDetectingMapper<RemappedConstraint, RawPQuery> wrapConstraint = new CycleDetectingMapper<>(
36 RemappedConstraint::toString, 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 VIATRA 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-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java
index 71b74396..255738c5 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
2 7
3import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; 8import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
@@ -9,6 +14,7 @@ import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery;
9import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; 14import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
10import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; 15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
11import tools.refinery.store.query.viatra.internal.RelationalScope; 16import tools.refinery.store.query.viatra.internal.RelationalScope;
17import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
12 18
13import java.util.LinkedHashSet; 19import java.util.LinkedHashSet;
14import java.util.List; 20import java.util.List;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java
deleted file mode 100644
index e944e873..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java
+++ /dev/null
@@ -1,72 +0,0 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher;
4import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
5import tools.refinery.store.query.ResultSet;
6import tools.refinery.store.query.viatra.ViatraTupleLike;
7import tools.refinery.store.tuple.Tuple;
8import tools.refinery.store.tuple.TupleLike;
9
10import java.util.Optional;
11import java.util.stream.Stream;
12
13public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet {
14 protected final Object[] empty;
15
16 public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
17 super(specification);
18 empty = new Object[specification.getParameterNames().size()];
19 }
20
21 @Override
22 public boolean hasResult() {
23 return backend.hasMatch(empty);
24 }
25
26 @Override
27 public boolean hasResult(Tuple parameters) {
28 return backend.hasMatch(toParametersArray(parameters));
29 }
30
31 @Override
32 public Optional<TupleLike> oneResult() {
33 return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new);
34 }
35
36 @Override
37 public Optional<TupleLike> oneResult(Tuple parameters) {
38 return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new);
39 }
40
41 @Override
42 public Stream<TupleLike> allResults() {
43 return backend.getAllMatches(empty).map(ViatraTupleLike::new);
44 }
45
46 @Override
47 public Stream<TupleLike> allResults(Tuple parameters) {
48 return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new);
49 }
50
51 @Override
52 public int countResults() {
53 return backend.countMatches(empty);
54 }
55
56 @Override
57 public int countResults(Tuple parameters) {
58 return backend.countMatches(toParametersArray(parameters));
59 }
60
61 private Object[] toParametersArray(Tuple tuple) {
62 int size = tuple.getSize();
63 var array = new Object[tuple.getSize()];
64 for (int i = 0; i < size; i++) {
65 var value = tuple.get(i);
66 if (value >= 0) {
67 array[i] = Tuple.of(value);
68 }
69 }
70 return array;
71 }
72}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java
new file mode 100644
index 00000000..461416f7
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java
@@ -0,0 +1,65 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatefulAggregate;
10import tools.refinery.store.query.term.StatefulAggregator;
11
12import java.util.stream.Stream;
13
14record StatefulMultisetAggregator<R, T>(StatefulAggregator<R, T> aggregator)
15 implements IMultisetAggregationOperator<T, StatefulAggregate<R, T>, R> {
16 @Override
17 public String getShortDescription() {
18 return getName();
19 }
20
21 @Override
22 public String getName() {
23 return aggregator.toString();
24 }
25
26 @Override
27 public StatefulAggregate<R, T> createNeutral() {
28 return aggregator.createEmptyAggregate();
29 }
30
31 @Override
32 public boolean isNeutral(StatefulAggregate<R, T> result) {
33 return result.isEmpty();
34 }
35
36 @Override
37 public StatefulAggregate<R, T> update(StatefulAggregate<R, T> oldResult, T updateValue, boolean isInsertion) {
38 if (isInsertion) {
39 oldResult.add(updateValue);
40 } else {
41 oldResult.remove(updateValue);
42 }
43 return oldResult;
44 }
45
46 @Override
47 public R getAggregate(StatefulAggregate<R, T> result) {
48 return result.getResult();
49 }
50
51 @Override
52 public R aggregateStream(Stream<T> stream) {
53 return aggregator.aggregateStream(stream);
54 }
55
56 @Override
57 public StatefulAggregate<R, T> clone(StatefulAggregate<R, T> original) {
58 return original.deepCopy();
59 }
60
61 @Override
62 public boolean contains(T value, StatefulAggregate<R, T> accumulator) {
63 return accumulator.contains(value);
64 }
65}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java
new file mode 100644
index 00000000..49175d75
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java
@@ -0,0 +1,55 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatelessAggregator;
10
11import java.util.stream.Stream;
12
13record StatelessMultisetAggregator<R, T>(StatelessAggregator<R, T> aggregator)
14 implements IMultisetAggregationOperator<T, R, R> {
15 @Override
16 public String getShortDescription() {
17 return getName();
18 }
19
20 @Override
21 public String getName() {
22 return aggregator.toString();
23 }
24
25 @Override
26 public R createNeutral() {
27 return aggregator.getEmptyResult();
28 }
29
30 @Override
31 public boolean isNeutral(R result) {
32 return createNeutral().equals(result);
33 }
34
35 @Override
36 public R update(R oldResult, T updateValue, boolean isInsertion) {
37 return isInsertion ? aggregator.add(oldResult, updateValue) : aggregator.remove(oldResult, updateValue);
38 }
39
40 @Override
41 public R getAggregate(R result) {
42 return result;
43 }
44
45 @Override
46 public R clone(R original) {
47 // Aggregate result is immutable.
48 return original;
49 }
50
51 @Override
52 public R aggregateStream(Stream<T> stream) {
53 return aggregator.aggregateStream(stream);
54 }
55}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java
index c442add8..a777613e 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java
@@ -1,10 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper; 8import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper;
4import tools.refinery.store.query.view.AnyRelationView; 9import tools.refinery.store.query.view.AnySymbolView;
5 10
6public class RelationViewWrapper extends BaseInputKeyWrapper<AnyRelationView> { 11public class SymbolViewWrapper extends BaseInputKeyWrapper<AnySymbolView> {
7 public RelationViewWrapper(AnyRelationView wrappedKey) { 12 public SymbolViewWrapper(AnySymbolView wrappedKey) {
8 super(wrappedKey); 13 super(wrappedKey);
9 } 14 }
10 15
@@ -27,4 +32,9 @@ public class RelationViewWrapper extends BaseInputKeyWrapper<AnyRelationView> {
27 public boolean isEnumerable() { 32 public boolean isEnumerable() {
28 return true; 33 return true;
29 } 34 }
35
36 @Override
37 public String toString() {
38 return "RelationViewWrapper{wrappedKey=%s}".formatted(wrappedKey);
39 }
30} 40}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java
new file mode 100644
index 00000000..1187f57a
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IExpressionEvaluator;
9import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
10import tools.refinery.store.query.term.Term;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.stream.Collectors;
14
15class TermEvaluator<T> implements IExpressionEvaluator {
16 private final Term<T> term;
17
18 public TermEvaluator(Term<T> term) {
19 this.term = term;
20 }
21
22 @Override
23 public String getShortDescription() {
24 return term.toString();
25 }
26
27 @Override
28 public Iterable<String> getInputParameterNames() {
29 return term.getInputVariables().stream().map(Variable::getUniqueName).collect(Collectors.toUnmodifiableSet());
30 }
31
32 @Override
33 public Object evaluateExpression(IValueProvider provider) {
34 var valuation = new ValueProviderBasedValuation(provider);
35 return term.evaluate(valuation);
36 }
37}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java
new file mode 100644
index 00000000..62cb8b3a
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.valuation.Valuation;
11
12public record ValueProviderBasedValuation(IValueProvider valueProvider) implements Valuation {
13 @Override
14 public <T> T getValue(DataVariable<T> variable) {
15 @SuppressWarnings("unchecked")
16 var value = (T) valueProvider.getValue(variable.getUniqueName());
17 return value;
18 }
19}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java
index 8a467066..986bb0b1 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java
@@ -1,47 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
4import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; 9import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener;
5import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 10import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
6import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 11import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
7import tools.refinery.store.query.view.AnyRelationView; 12import tools.refinery.store.query.view.AnySymbolView;
8import tools.refinery.store.query.view.RelationView; 13import tools.refinery.store.query.view.SymbolView;
9 14
10import java.util.HashMap; 15import java.util.HashMap;
11import java.util.Map; 16import java.util.Map;
12 17
13public class ModelUpdateListener { 18public class ModelUpdateListener {
14 private final Map<AnyRelationView, RelationViewUpdateListener<?>> relationViewUpdateListeners; 19 private final Map<AnySymbolView, SymbolViewUpdateListener<?>> symbolViewUpdateListeners;
15 20
16 public ModelUpdateListener(ViatraModelQueryAdapterImpl adapter) { 21 public ModelUpdateListener(ViatraModelQueryAdapterImpl adapter) {
17 var relationViews = adapter.getStoreAdapter().getInputKeys().keySet(); 22 var symbolViews = adapter.getStoreAdapter().getInputKeys().keySet();
18 relationViewUpdateListeners = new HashMap<>(relationViews.size()); 23 symbolViewUpdateListeners = new HashMap<>(symbolViews.size());
19 for (var relationView : relationViews) { 24 for (var symbolView : symbolViews) {
20 registerView(adapter, (RelationView<?>) relationView); 25 registerView(adapter, (SymbolView<?>) symbolView);
21 } 26 }
22 } 27 }
23 28
24 private <T> void registerView(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView) { 29 private <T> void registerView(ViatraModelQueryAdapterImpl adapter, SymbolView<T> view) {
25 var listener = RelationViewUpdateListener.of(adapter, relationView);
26 var model = adapter.getModel(); 30 var model = adapter.getModel();
27 var interpretation = model.getInterpretation(relationView.getSymbol()); 31 var interpretation = model.getInterpretation(view.getSymbol());
28 interpretation.addListener(listener, true); 32 var listener = SymbolViewUpdateListener.of(adapter, view, interpretation);
29 relationViewUpdateListeners.put(relationView, listener); 33 symbolViewUpdateListeners.put(view, listener);
30 } 34 }
31 35
32 public boolean containsRelationView(AnyRelationView relationView) { 36 public boolean containsSymbolView(AnySymbolView relationView) {
33 return relationViewUpdateListeners.containsKey(relationView); 37 return symbolViewUpdateListeners.containsKey(relationView);
34 } 38 }
35 39
36 public void addListener(IInputKey key, AnyRelationView relationView, ITuple seed, 40 public void addListener(IInputKey key, AnySymbolView symbolView, ITuple seed,
37 IQueryRuntimeContextListener listener) { 41 IQueryRuntimeContextListener listener) {
38 var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); 42 var symbolViewUpdateListener = symbolViewUpdateListeners.get(symbolView);
39 relationViewUpdateListener.addFilter(key, seed, listener); 43 symbolViewUpdateListener.addFilter(key, seed, listener);
40 } 44 }
41 45
42 public void removeListener(IInputKey key, AnyRelationView relationView, ITuple seed, 46 public void removeListener(IInputKey key, AnySymbolView symbolView, ITuple seed,
43 IQueryRuntimeContextListener listener) { 47 IQueryRuntimeContextListener listener) {
44 var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); 48 var symbolViewUpdateListener = symbolViewUpdateListeners.get(symbolView);
45 relationViewUpdateListener.removeFilter(key, seed, listener); 49 symbolViewUpdateListener.removeFilter(key, seed, listener);
46 } 50 }
47} 51}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java
index 221f1b4a..efdbfcbe 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java
@@ -1,3 +1,8 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java
deleted file mode 100644
index bf6b4197..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java
+++ /dev/null
@@ -1,48 +0,0 @@
1package tools.refinery.store.query.viatra.internal.update;
2
3import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
4import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener;
5import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
6import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
7import tools.refinery.store.model.InterpretationListener;
8import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
9import tools.refinery.store.query.view.RelationView;
10import tools.refinery.store.query.view.TuplePreservingRelationView;
11
12import java.util.ArrayList;
13import java.util.List;
14
15public abstract class RelationViewUpdateListener<T> implements InterpretationListener<T> {
16 private final ViatraModelQueryAdapterImpl adapter;
17 private final List<RelationViewFilter> filters = new ArrayList<>();
18
19 protected RelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter) {
20 this.adapter = adapter;
21 }
22
23 public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) {
24 filters.add(new RelationViewFilter(inputKey, seed, listener));
25 }
26
27 public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) {
28 filters.remove(new RelationViewFilter(inputKey, seed, listener));
29 }
30
31 protected void processUpdate(Tuple tuple, boolean isInsertion) {
32 adapter.markAsPending();
33 int size = filters.size();
34 // Use a for loop instead of a for-each loop to avoid <code>Iterator</code> allocation overhead.
35 //noinspection ForLoopReplaceableByForEach
36 for (int i = 0; i < size; i++) {
37 filters.get(i).update(tuple, isInsertion);
38 }
39 }
40
41 public static <T> RelationViewUpdateListener<T> of(ViatraModelQueryAdapterImpl adapter,
42 RelationView<T> relationView) {
43 if (relationView instanceof TuplePreservingRelationView<T> tuplePreservingRelationView) {
44 return new TuplePreservingRelationViewUpdateListener<>(adapter, tuplePreservingRelationView);
45 }
46 return new TupleChangingRelationViewUpdateListener<>(adapter, relationView);
47 }
48}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java
new file mode 100644
index 00000000..f1a2ac7c
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java
@@ -0,0 +1,65 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.update;
7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener;
10import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
11import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.model.InterpretationListener;
14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
15import tools.refinery.store.query.view.SymbolView;
16import tools.refinery.store.query.view.TuplePreservingView;
17
18import java.util.ArrayList;
19import java.util.List;
20
21public abstract class SymbolViewUpdateListener<T> implements InterpretationListener<T> {
22 private final ViatraModelQueryAdapterImpl adapter;
23 private final Interpretation<T> interpretation;
24 private final List<RelationViewFilter> filters = new ArrayList<>();
25
26 protected SymbolViewUpdateListener(ViatraModelQueryAdapterImpl 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(ViatraModelQueryAdapterImpl 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-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java
deleted file mode 100644
index 14142884..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java
+++ /dev/null
@@ -1,37 +0,0 @@
1package tools.refinery.store.query.viatra.internal.update;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
4import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
5import tools.refinery.store.query.view.RelationView;
6import tools.refinery.store.tuple.Tuple;
7
8import java.util.Arrays;
9
10public class TupleChangingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> {
11 private final RelationView<T> relationView;
12
13 TupleChangingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView) {
14 super(adapter);
15 this.relationView = relationView;
16 }
17
18 @Override
19 public void put(Tuple key, T fromValue, T toValue, boolean restoring) {
20 boolean fromPresent = relationView.filter(key, fromValue);
21 boolean toPresent = relationView.filter(key, toValue);
22 if (fromPresent) {
23 if (toPresent) { // value change
24 var fromArray = relationView.forwardMap(key, fromValue);
25 var toArray = relationView.forwardMap(key, toValue);
26 if (!Arrays.equals(fromArray, toArray)) {
27 processUpdate(Tuples.flatTupleOf(fromArray), false);
28 processUpdate(Tuples.flatTupleOf(toArray), true);
29 }
30 } else { // fromValue disappears
31 processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, fromValue)), false);
32 }
33 } else if (toPresent) { // toValue appears
34 processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, toValue)), true);
35 }
36 }
37}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java
new file mode 100644
index 00000000..45d35571
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java
@@ -0,0 +1,44 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.update;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
11import tools.refinery.store.query.view.SymbolView;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Arrays;
15
16public class TupleChangingViewUpdateListener<T> extends SymbolViewUpdateListener<T> {
17 private final SymbolView<T> view;
18
19 TupleChangingViewUpdateListener(ViatraModelQueryAdapterImpl 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 if (toPresent) { // value change
31 var fromArray = view.forwardMap(key, fromValue);
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(view.forwardMap(key, fromValue)), false);
39 }
40 } else if (toPresent) { // toValue appears
41 processUpdate(Tuples.flatTupleOf(view.forwardMap(key, toValue)), true);
42 }
43 }
44}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java
index 288e018a..c18dbafb 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java
@@ -1,16 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
2 7
3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 8import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
9import tools.refinery.store.model.Interpretation;
4import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
5import tools.refinery.store.query.view.TuplePreservingRelationView; 11import tools.refinery.store.query.view.TuplePreservingView;
6import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
7 13
8public class TuplePreservingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> { 14public class TuplePreservingViewUpdateListener<T> extends SymbolViewUpdateListener<T> {
9 private final TuplePreservingRelationView<T> view; 15 private final TuplePreservingView<T> view;
10 16
11 TuplePreservingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, 17 TuplePreservingViewUpdateListener(ViatraModelQueryAdapterImpl adapter, TuplePreservingView<T> view,
12 TuplePreservingRelationView<T> view) { 18 Interpretation<T> interpretation) {
13 super(adapter); 19 super(adapter, interpretation);
14 this.view = view; 20 this.view = view;
15 } 21 }
16 22
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java
new file mode 100644
index 00000000..6aae2ebe
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java
@@ -0,0 +1,390 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.viatra.tests.QueryEngineTest;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.FunctionView;
16import tools.refinery.store.query.view.KeyOnlyView;
17import tools.refinery.store.representation.Symbol;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.List;
21import java.util.Map;
22import java.util.Optional;
23
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
26import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
27import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
28
29class DiagonalQueryTest {
30 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
31 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
32 private static final Symbol<Boolean> symbol = Symbol.of("symbol", 4);
33 private static final Symbol<Integer> intSymbol = Symbol.of("intSymbol", 4, Integer.class);
34 private static final AnySymbolView personView = new KeyOnlyView<>(person);
35 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
36 private static final AnySymbolView symbolView = new KeyOnlyView<>(symbol);
37 private static final FunctionView<Integer> intSymbolView = new FunctionView<>(intSymbol);
38
39 @QueryEngineTest
40 void inputKeyNegationTest(QueryEvaluationHint hint) {
41 var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of(
42 personView.call(p1),
43 not(symbolView.call(p1, p1, p2, p2))
44 )));
45
46 var store = ModelStore.builder()
47 .symbols(person, symbol)
48 .with(ViatraModelQueryAdapter.builder()
49 .defaultHint(hint)
50 .queries(query))
51 .build();
52
53 var model = store.createEmptyModel();
54 var personInterpretation = model.getInterpretation(person);
55 var symbolInterpretation = model.getInterpretation(symbol);
56 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
57 var queryResultSet = queryEngine.getResultSet(query);
58
59 personInterpretation.put(Tuple.of(0), true);
60 personInterpretation.put(Tuple.of(1), true);
61 personInterpretation.put(Tuple.of(2), true);
62
63 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
64 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
65 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
66 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
67
68 queryEngine.flushChanges();
69 assertResults(Map.of(
70 Tuple.of(0), false,
71 Tuple.of(1), true,
72 Tuple.of(2), true,
73 Tuple.of(3), false
74 ), queryResultSet);
75 }
76
77 @QueryEngineTest
78 void subQueryNegationTest(QueryEvaluationHint hint) {
79 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder
80 .clause(
81 personView.call(p1),
82 symbolView.call(p1, p2, p3, p4)
83 )
84 .clause(
85 personView.call(p2),
86 symbolView.call(p1, p2, p3, p4)
87 ));
88 var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of(
89 personView.call(p1),
90 not(subQuery.call(p1, p1, p2, p2))
91 )));
92
93 var store = ModelStore.builder()
94 .symbols(person, symbol)
95 .with(ViatraModelQueryAdapter.builder()
96 .defaultHint(hint)
97 .queries(query))
98 .build();
99
100 var model = store.createEmptyModel();
101 var personInterpretation = model.getInterpretation(person);
102 var symbolInterpretation = model.getInterpretation(symbol);
103 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
104 var queryResultSet = queryEngine.getResultSet(query);
105
106 personInterpretation.put(Tuple.of(0), true);
107 personInterpretation.put(Tuple.of(1), true);
108 personInterpretation.put(Tuple.of(2), true);
109
110 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
111 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
112 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
113 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
114
115 queryEngine.flushChanges();
116 assertResults(Map.of(
117 Tuple.of(0), false,
118 Tuple.of(1), true,
119 Tuple.of(2), true,
120 Tuple.of(3), false
121 ), queryResultSet);
122 }
123
124 @QueryEngineTest
125 void inputKeyCountTest(QueryEvaluationHint hint) {
126 var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of(
127 personView.call(p1),
128 output.assign(symbolView.count(p1, p1, p2, p2))
129 )));
130
131 var store = ModelStore.builder()
132 .symbols(person, symbol)
133 .with(ViatraModelQueryAdapter.builder()
134 .defaultHint(hint)
135 .queries(query))
136 .build();
137
138 var model = store.createEmptyModel();
139 var personInterpretation = model.getInterpretation(person);
140 var symbolInterpretation = model.getInterpretation(symbol);
141 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
142 var queryResultSet = queryEngine.getResultSet(query);
143
144 personInterpretation.put(Tuple.of(0), true);
145 personInterpretation.put(Tuple.of(1), true);
146 personInterpretation.put(Tuple.of(2), true);
147
148 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
149 symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true);
150 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
151 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
152 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
153
154 queryEngine.flushChanges();
155 assertNullableResults(Map.of(
156 Tuple.of(0), Optional.of(2),
157 Tuple.of(1), Optional.of(0),
158 Tuple.of(2), Optional.of(0),
159 Tuple.of(3), Optional.empty()
160 ), queryResultSet);
161 }
162
163 @QueryEngineTest
164 void subQueryCountTest(QueryEvaluationHint hint) {
165 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder.clause(
166 personView.call(p1),
167 symbolView.call(p1, p2, p3, p4)
168 )
169 .clause(
170 personView.call(p2),
171 symbolView.call(p1, p2, p3, p4)
172 ));
173 var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of(
174 personView.call(p1),
175 output.assign(subQuery.count(p1, p1, p2, p2))
176 )));
177
178 var store = ModelStore.builder()
179 .symbols(person, symbol)
180 .with(ViatraModelQueryAdapter.builder()
181 .defaultHint(hint)
182 .queries(query))
183 .build();
184
185 var model = store.createEmptyModel();
186 var personInterpretation = model.getInterpretation(person);
187 var symbolInterpretation = model.getInterpretation(symbol);
188 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
189 var queryResultSet = queryEngine.getResultSet(query);
190
191 personInterpretation.put(Tuple.of(0), true);
192 personInterpretation.put(Tuple.of(1), true);
193 personInterpretation.put(Tuple.of(2), true);
194
195 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
196 symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true);
197 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
198 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
199 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
200
201 queryEngine.flushChanges();
202 assertNullableResults(Map.of(
203 Tuple.of(0), Optional.of(2),
204 Tuple.of(1), Optional.of(0),
205 Tuple.of(2), Optional.of(0),
206 Tuple.of(3), Optional.empty()
207 ), queryResultSet);
208 }
209
210 @QueryEngineTest
211 void inputKeyAggregationTest(QueryEvaluationHint hint) {
212 var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder
213 .clause((p2) -> List.of(
214 personView.call(p1),
215 output.assign(intSymbolView.aggregate(INT_SUM, p1, p1, p2, p2))
216 )));
217
218 var store = ModelStore.builder()
219 .symbols(person, intSymbol)
220 .with(ViatraModelQueryAdapter.builder()
221 .defaultHint(hint)
222 .queries(query))
223 .build();
224
225 var model = store.createEmptyModel();
226 var personInterpretation = model.getInterpretation(person);
227 var intSymbolInterpretation = model.getInterpretation(intSymbol);
228 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
229 var queryResultSet = queryEngine.getResultSet(query);
230
231 personInterpretation.put(Tuple.of(0), true);
232 personInterpretation.put(Tuple.of(1), true);
233 personInterpretation.put(Tuple.of(2), true);
234
235 intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1);
236 intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2);
237 intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10);
238 intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11);
239 intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12);
240
241 queryEngine.flushChanges();
242 assertNullableResults(Map.of(
243 Tuple.of(0), Optional.of(3),
244 Tuple.of(1), Optional.of(0),
245 Tuple.of(2), Optional.of(0),
246 Tuple.of(3), Optional.empty()
247 ), queryResultSet);
248 }
249
250 @QueryEngineTest
251 void subQueryAggregationTest(QueryEvaluationHint hint) {
252 var subQuery = Dnf.of("SubQuery", builder -> {
253 var p1 = builder.parameter("p1");
254 var p2 = builder.parameter("p2");
255 var p3 = builder.parameter("p3");
256 var p4 = builder.parameter("p4");
257 var x = builder.parameter("x", Integer.class);
258 var y = builder.parameter("y", Integer.class);
259 builder.clause(
260 personView.call(p1),
261 intSymbolView.call(p1, p2, p3, p4, x),
262 y.assign(x)
263 );
264 builder.clause(
265 personView.call(p2),
266 intSymbolView.call(p1, p2, p3, p4, x),
267 y.assign(x)
268 );
269 });
270 var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder
271 .clause(Integer.class, Integer.class, (p2, y, z) -> List.of(
272 personView.call(p1),
273 output.assign(subQuery.aggregateBy(y, INT_SUM, p1, p1, p2, p2, y, z))
274 )));
275
276 var store = ModelStore.builder()
277 .symbols(person, intSymbol)
278 .with(ViatraModelQueryAdapter.builder()
279 .defaultHint(hint)
280 .queries(query))
281 .build();
282
283 var model = store.createEmptyModel();
284 var personInterpretation = model.getInterpretation(person);
285 var intSymbolInterpretation = model.getInterpretation(intSymbol);
286 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
287 var queryResultSet = queryEngine.getResultSet(query);
288
289 personInterpretation.put(Tuple.of(0), true);
290 personInterpretation.put(Tuple.of(1), true);
291 personInterpretation.put(Tuple.of(2), true);
292
293 intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1);
294 intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2);
295 intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10);
296 intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11);
297 intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12);
298
299 queryEngine.flushChanges();
300 assertNullableResults(Map.of(
301 Tuple.of(0), Optional.of(3),
302 Tuple.of(1), Optional.of(0),
303 Tuple.of(2), Optional.of(0),
304 Tuple.of(3), Optional.empty()
305 ), queryResultSet);
306 }
307
308 @QueryEngineTest
309 void inputKeyTransitiveTest(QueryEvaluationHint hint) {
310 var query = Query.of("Diagonal", (builder, p1) -> builder.clause(
311 personView.call(p1),
312 friendView.callTransitive(p1, p1)
313 ));
314
315 var store = ModelStore.builder()
316 .symbols(person, friend)
317 .with(ViatraModelQueryAdapter.builder()
318 .defaultHint(hint)
319 .queries(query))
320 .build();
321
322 var model = store.createEmptyModel();
323 var personInterpretation = model.getInterpretation(person);
324 var friendInterpretation = model.getInterpretation(friend);
325 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
326 var queryResultSet = queryEngine.getResultSet(query);
327
328 personInterpretation.put(Tuple.of(0), true);
329 personInterpretation.put(Tuple.of(1), true);
330 personInterpretation.put(Tuple.of(2), true);
331
332 friendInterpretation.put(Tuple.of(0, 0), true);
333 friendInterpretation.put(Tuple.of(0, 1), true);
334 friendInterpretation.put(Tuple.of(1, 2), true);
335
336 queryEngine.flushChanges();
337 assertResults(Map.of(
338 Tuple.of(0), true,
339 Tuple.of(1), false,
340 Tuple.of(2), false,
341 Tuple.of(3), false
342 ), queryResultSet);
343 }
344
345 @QueryEngineTest
346 void subQueryTransitiveTest(QueryEvaluationHint hint) {
347 var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder
348 .clause(
349 personView.call(p1),
350 friendView.call(p1, p2)
351 )
352 .clause(
353 personView.call(p2),
354 friendView.call(p1, p2)
355 ));
356 var query = Query.of("Diagonal", (builder, p1) -> builder.clause(
357 personView.call(p1),
358 subQuery.callTransitive(p1, p1)
359 ));
360
361 var store = ModelStore.builder()
362 .symbols(person, friend)
363 .with(ViatraModelQueryAdapter.builder()
364 .defaultHint(hint)
365 .queries(query))
366 .build();
367
368 var model = store.createEmptyModel();
369 var personInterpretation = model.getInterpretation(person);
370 var friendInterpretation = model.getInterpretation(friend);
371 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
372 var queryResultSet = queryEngine.getResultSet(query);
373
374 personInterpretation.put(Tuple.of(0), true);
375 personInterpretation.put(Tuple.of(1), true);
376 personInterpretation.put(Tuple.of(2), true);
377
378 friendInterpretation.put(Tuple.of(0, 0), true);
379 friendInterpretation.put(Tuple.of(0, 1), true);
380 friendInterpretation.put(Tuple.of(1, 2), true);
381
382 queryEngine.flushChanges();
383 assertResults(Map.of(
384 Tuple.of(0), true,
385 Tuple.of(1), false,
386 Tuple.of(2), false,
387 Tuple.of(3), false
388 ), queryResultSet);
389 }
390}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java
new file mode 100644
index 00000000..258127e7
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java
@@ -0,0 +1,483 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.viatra.tests.QueryEngineTest;
15import tools.refinery.store.query.view.AnySymbolView;
16import tools.refinery.store.query.view.FilteredView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
19import tools.refinery.store.representation.Symbol;
20import tools.refinery.store.representation.TruthValue;
21import tools.refinery.store.tuple.Tuple;
22
23import java.util.List;
24import java.util.Map;
25import java.util.Optional;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static org.hamcrest.Matchers.is;
29import static org.hamcrest.Matchers.nullValue;
30import static org.junit.jupiter.api.Assertions.assertAll;
31import static org.junit.jupiter.api.Assertions.assertThrows;
32import static tools.refinery.store.query.literal.Literals.assume;
33import static tools.refinery.store.query.term.int_.IntTerms.*;
34import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
35import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
36
37class FunctionalQueryTest {
38 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
39 private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class);
40 private static final Symbol<TruthValue> friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE);
41 private static final AnySymbolView personView = new KeyOnlyView<>(person);
42 private static final FunctionView<Integer> ageView = new FunctionView<>(age);
43 private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must);
44
45 @QueryEngineTest
46 void inputKeyTest(QueryEvaluationHint hint) {
47 var query = Query.of("InputKey", Integer.class, (builder, p1, output) -> builder.clause(
48 personView.call(p1),
49 ageView.call(p1, output)
50 ));
51
52 var store = ModelStore.builder()
53 .symbols(person, age)
54 .with(ViatraModelQueryAdapter.builder()
55 .defaultHint(hint)
56 .queries(query))
57 .build();
58
59 var model = store.createEmptyModel();
60 var personInterpretation = model.getInterpretation(person);
61 var ageInterpretation = model.getInterpretation(age);
62 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
63 var queryResultSet = queryEngine.getResultSet(query);
64
65 personInterpretation.put(Tuple.of(0), true);
66 personInterpretation.put(Tuple.of(1), true);
67
68 ageInterpretation.put(Tuple.of(0), 12);
69 ageInterpretation.put(Tuple.of(1), 24);
70 ageInterpretation.put(Tuple.of(2), 36);
71
72 queryEngine.flushChanges();
73 assertNullableResults(Map.of(
74 Tuple.of(0), Optional.of(12),
75 Tuple.of(1), Optional.of(24),
76 Tuple.of(2), Optional.empty()
77 ), queryResultSet);
78 }
79
80 @QueryEngineTest
81 void predicateTest(QueryEvaluationHint hint) {
82 var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause(
83 personView.call(p1),
84 ageView.call(p1, x)
85 ));
86 var query = Query.of("Predicate", Integer.class, (builder, p1, output) -> builder.clause(
87 personView.call(p1),
88 output.assign(subQuery.call(p1))
89 ));
90
91 var store = ModelStore.builder()
92 .symbols(person, age)
93 .with(ViatraModelQueryAdapter.builder()
94 .defaultHint(hint)
95 .queries(query))
96 .build();
97
98 var model = store.createEmptyModel();
99 var personInterpretation = model.getInterpretation(person);
100 var ageInterpretation = model.getInterpretation(age);
101 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
102 var queryResultSet = queryEngine.getResultSet(query);
103
104 personInterpretation.put(Tuple.of(0), true);
105 personInterpretation.put(Tuple.of(1), true);
106
107 ageInterpretation.put(Tuple.of(0), 12);
108 ageInterpretation.put(Tuple.of(1), 24);
109 ageInterpretation.put(Tuple.of(2), 36);
110
111 queryEngine.flushChanges();
112 assertNullableResults(Map.of(
113 Tuple.of(0), Optional.of(12),
114 Tuple.of(1), Optional.of(24),
115 Tuple.of(2), Optional.empty()
116 ), queryResultSet);
117 }
118
119 @QueryEngineTest
120 void computationTest(QueryEvaluationHint hint) {
121 var query = Query.of("Computation", Integer.class, (builder, p1, output) -> builder.clause(() -> {
122 var x = Variable.of("x", Integer.class);
123 return List.of(
124 personView.call(p1),
125 ageView.call(p1, x),
126 output.assign(mul(x, constant(7)))
127 );
128 }));
129
130 var store = ModelStore.builder()
131 .symbols(person, age)
132 .with(ViatraModelQueryAdapter.builder()
133 .defaultHint(hint)
134 .queries(query))
135 .build();
136
137 var model = store.createEmptyModel();
138 var personInterpretation = model.getInterpretation(person);
139 var ageInterpretation = model.getInterpretation(age);
140 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
141 var queryResultSet = queryEngine.getResultSet(query);
142
143 personInterpretation.put(Tuple.of(0), true);
144 personInterpretation.put(Tuple.of(1), true);
145
146 ageInterpretation.put(Tuple.of(0), 12);
147 ageInterpretation.put(Tuple.of(1), 24);
148
149 queryEngine.flushChanges();
150 assertNullableResults(Map.of(
151 Tuple.of(0), Optional.of(84),
152 Tuple.of(1), Optional.of(168),
153 Tuple.of(2), Optional.empty()
154 ), queryResultSet);
155 }
156
157 @QueryEngineTest
158 void inputKeyCountTest(QueryEvaluationHint hint) {
159 var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause(
160 personView.call(p1),
161 output.assign(friendMustView.count(p1, Variable.of()))
162 ));
163
164 var store = ModelStore.builder()
165 .symbols(person, friend)
166 .with(ViatraModelQueryAdapter.builder()
167 .defaultHint(hint)
168 .queries(query))
169 .build();
170
171 var model = store.createEmptyModel();
172 var personInterpretation = model.getInterpretation(person);
173 var friendInterpretation = model.getInterpretation(friend);
174 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
175 var queryResultSet = queryEngine.getResultSet(query);
176
177 personInterpretation.put(Tuple.of(0), true);
178 personInterpretation.put(Tuple.of(1), true);
179 personInterpretation.put(Tuple.of(2), true);
180
181 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
182 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
183 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
184
185 queryEngine.flushChanges();
186 assertNullableResults(Map.of(
187 Tuple.of(0), Optional.of(1),
188 Tuple.of(1), Optional.of(2),
189 Tuple.of(2), Optional.of(0),
190 Tuple.of(3), Optional.empty()
191 ), queryResultSet);
192 }
193
194 @QueryEngineTest
195 void predicateCountTest(QueryEvaluationHint hint) {
196 var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder.clause(
197 personView.call(p1),
198 personView.call(p2),
199 friendMustView.call(p1, p2)
200 ));
201 var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause(
202 personView.call(p1),
203 output.assign(subQuery.count(p1, Variable.of()))
204 ));
205
206 var store = ModelStore.builder()
207 .symbols(person, friend)
208 .with(ViatraModelQueryAdapter.builder()
209 .defaultHint(hint)
210 .queries(query))
211 .build();
212
213 var model = store.createEmptyModel();
214 var personInterpretation = model.getInterpretation(person);
215 var friendInterpretation = model.getInterpretation(friend);
216 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
217 var queryResultSet = queryEngine.getResultSet(query);
218
219 personInterpretation.put(Tuple.of(0), true);
220 personInterpretation.put(Tuple.of(1), true);
221 personInterpretation.put(Tuple.of(2), true);
222
223 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
224 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
225 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
226
227 queryEngine.flushChanges();
228 assertNullableResults(Map.of(
229 Tuple.of(0), Optional.of(1),
230 Tuple.of(1), Optional.of(2),
231 Tuple.of(2), Optional.of(0),
232 Tuple.of(3), Optional.empty()
233 ), queryResultSet);
234 }
235
236 @QueryEngineTest
237 void inputKeyAggregationTest(QueryEvaluationHint hint) {
238 var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause(
239 output.assign(ageView.aggregate(INT_SUM, Variable.of()))
240 ));
241
242 var store = ModelStore.builder()
243 .symbols(age)
244 .with(ViatraModelQueryAdapter.builder()
245 .defaultHint(hint)
246 .queries(query))
247 .build();
248
249 var model = store.createEmptyModel();
250 var ageInterpretation = model.getInterpretation(age);
251 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
252 var queryResultSet = queryEngine.getResultSet(query);
253
254 ageInterpretation.put(Tuple.of(0), 12);
255 ageInterpretation.put(Tuple.of(1), 24);
256
257 queryEngine.flushChanges();
258 assertResults(Map.of(Tuple.of(), 36), queryResultSet);
259 }
260
261 @QueryEngineTest
262 void predicateAggregationTest(QueryEvaluationHint hint) {
263 var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause(
264 personView.call(p1),
265 ageView.call(p1, x)
266 ));
267 var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause(
268 output.assign(subQuery.aggregate(INT_SUM, Variable.of()))
269 ));
270
271 var store = ModelStore.builder()
272 .symbols(person, age)
273 .with(ViatraModelQueryAdapter.builder()
274 .defaultHint(hint)
275 .queries(query))
276 .build();
277
278 var model = store.createEmptyModel();
279 var personInterpretation = model.getInterpretation(person);
280 var ageInterpretation = model.getInterpretation(age);
281 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
282 var queryResultSet = queryEngine.getResultSet(query);
283
284 personInterpretation.put(Tuple.of(0), true);
285 personInterpretation.put(Tuple.of(1), true);
286
287 ageInterpretation.put(Tuple.of(0), 12);
288 ageInterpretation.put(Tuple.of(1), 24);
289
290 queryEngine.flushChanges();
291 assertResults(Map.of(Tuple.of(), 36), queryResultSet);
292 }
293
294 @QueryEngineTest
295 void extremeValueTest(QueryEvaluationHint hint) {
296 var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause(
297 personView.call(p1),
298 x.assign(friendMustView.count(p1, Variable.of()))
299 ));
300 var minQuery = Query.of("Min", Integer.class, (builder, output) -> builder.clause(
301 output.assign(subQuery.aggregate(INT_MIN, Variable.of()))
302 ));
303 var maxQuery = Query.of("Max", Integer.class, (builder, output) -> builder.clause(
304 output.assign(subQuery.aggregate(INT_MAX, Variable.of()))
305 ));
306
307 var store = ModelStore.builder()
308 .symbols(person, friend)
309 .with(ViatraModelQueryAdapter.builder()
310 .defaultHint(hint)
311 .queries(minQuery, maxQuery))
312 .build();
313
314 var model = store.createEmptyModel();
315 var personInterpretation = model.getInterpretation(person);
316 var friendInterpretation = model.getInterpretation(friend);
317 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
318 var minResultSet = queryEngine.getResultSet(minQuery);
319 var maxResultSet = queryEngine.getResultSet(maxQuery);
320
321 assertResults(Map.of(Tuple.of(), Integer.MAX_VALUE), minResultSet);
322 assertResults(Map.of(Tuple.of(), Integer.MIN_VALUE), maxResultSet);
323
324 personInterpretation.put(Tuple.of(0), true);
325 personInterpretation.put(Tuple.of(1), true);
326 personInterpretation.put(Tuple.of(2), true);
327
328 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
329 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
330 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
331
332 queryEngine.flushChanges();
333 assertResults(Map.of(Tuple.of(), 0), minResultSet);
334 assertResults(Map.of(Tuple.of(), 2), maxResultSet);
335
336 friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE);
337 friendInterpretation.put(Tuple.of(2, 1), TruthValue.TRUE);
338
339 queryEngine.flushChanges();
340 assertResults(Map.of(Tuple.of(), 1), minResultSet);
341 assertResults(Map.of(Tuple.of(), 2), maxResultSet);
342
343 friendInterpretation.put(Tuple.of(0, 1), TruthValue.FALSE);
344 friendInterpretation.put(Tuple.of(1, 0), TruthValue.FALSE);
345 friendInterpretation.put(Tuple.of(2, 0), TruthValue.FALSE);
346
347 queryEngine.flushChanges();
348 assertResults(Map.of(Tuple.of(), 0), minResultSet);
349 assertResults(Map.of(Tuple.of(), 1), maxResultSet);
350 }
351
352 @QueryEngineTest
353 void invalidComputationTest(QueryEvaluationHint hint) {
354 var query = Query.of("InvalidComputation", Integer.class,
355 (builder, p1, output) -> builder.clause(Integer.class, (x) -> List.of(
356 personView.call(p1),
357 ageView.call(p1, x),
358 output.assign(div(constant(120), x))
359 )));
360
361 var store = ModelStore.builder()
362 .symbols(person, age)
363 .with(ViatraModelQueryAdapter.builder()
364 .defaultHint(hint)
365 .queries(query))
366 .build();
367
368 var model = store.createEmptyModel();
369 var personInterpretation = model.getInterpretation(person);
370 var ageInterpretation = model.getInterpretation(age);
371 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
372 var queryResultSet = queryEngine.getResultSet(query);
373
374 personInterpretation.put(Tuple.of(0), true);
375 personInterpretation.put(Tuple.of(1), true);
376
377 ageInterpretation.put(Tuple.of(0), 0);
378 ageInterpretation.put(Tuple.of(1), 30);
379
380 queryEngine.flushChanges();
381 assertNullableResults(Map.of(
382 Tuple.of(0), Optional.empty(),
383 Tuple.of(1), Optional.of(4),
384 Tuple.of(2), Optional.empty()
385 ), queryResultSet);
386 }
387
388 @QueryEngineTest
389 void invalidAssumeTest(QueryEvaluationHint hint) {
390 var query = Query.of("InvalidAssume", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of(
391 personView.call(p1),
392 ageView.call(p1, x),
393 assume(lessEq(div(constant(120), x), constant(5)))
394 )));
395
396 var store = ModelStore.builder()
397 .symbols(person, age)
398 .with(ViatraModelQueryAdapter.builder()
399 .defaultHint(hint)
400 .queries(query))
401 .build();
402
403 var model = store.createEmptyModel();
404 var personInterpretation = model.getInterpretation(person);
405 var ageInterpretation = model.getInterpretation(age);
406 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
407 var queryResultSet = queryEngine.getResultSet(query);
408
409 personInterpretation.put(Tuple.of(0), true);
410 personInterpretation.put(Tuple.of(1), true);
411 personInterpretation.put(Tuple.of(2), true);
412
413 ageInterpretation.put(Tuple.of(0), 0);
414 ageInterpretation.put(Tuple.of(1), 30);
415 ageInterpretation.put(Tuple.of(2), 20);
416
417 queryEngine.flushChanges();
418 assertResults(Map.of(
419 Tuple.of(0), false,
420 Tuple.of(1), true,
421 Tuple.of(2), false,
422 Tuple.of(3), false
423 ), queryResultSet);
424 }
425
426 @QueryEngineTest
427 void notFunctionalTest(QueryEvaluationHint hint) {
428 var query = Query.of("NotFunctional", Integer.class, (builder, p1, output) -> builder.clause((p2) -> List.of(
429 personView.call(p1),
430 friendMustView.call(p1, p2),
431 ageView.call(p2, output)
432 )));
433
434 var store = ModelStore.builder()
435 .symbols(person, age, friend)
436 .with(ViatraModelQueryAdapter.builder()
437 .defaultHint(hint)
438 .queries(query))
439 .build();
440
441 var model = store.createEmptyModel();
442 var personInterpretation = model.getInterpretation(person);
443 var ageInterpretation = model.getInterpretation(age);
444 var friendInterpretation = model.getInterpretation(friend);
445 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
446 var queryResultSet = queryEngine.getResultSet(query);
447
448 personInterpretation.put(Tuple.of(0), true);
449 personInterpretation.put(Tuple.of(1), true);
450 personInterpretation.put(Tuple.of(2), true);
451
452 ageInterpretation.put(Tuple.of(0), 24);
453 ageInterpretation.put(Tuple.of(1), 30);
454 ageInterpretation.put(Tuple.of(2), 36);
455
456 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
457 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
458 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
459
460 queryEngine.flushChanges();
461 var invalidTuple = Tuple.of(1);
462 var cursor = queryResultSet.getAll();
463 assertAll(
464 () -> assertThat("value for key 0", queryResultSet.get(Tuple.of(0)), is(30)),
465 () -> assertThrows(IllegalStateException.class, () -> queryResultSet.get(invalidTuple),
466 "multiple values for key 1"),
467 () -> assertThat("value for key 2", queryResultSet.get(Tuple.of(2)), is(nullValue())),
468 () -> assertThat("value for key 3", queryResultSet.get(Tuple.of(3)), is(nullValue()))
469 );
470 if (hint.getQueryBackendRequirementType() != QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH) {
471 // Local search doesn't support throwing an error on multiple function return values.
472 assertThat("results size", queryResultSet.size(), is(2));
473 assertThrows(IllegalStateException.class, () -> enumerateValues(cursor), "move cursor");
474 }
475 }
476
477 private static void enumerateValues(Cursor<?, ?> cursor) {
478 //noinspection StatementWithEmptyBody
479 while (cursor.move()) {
480 // Nothing do, just let the cursor move through the result set.
481 }
482 }
483}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java
new file mode 100644
index 00000000..8ede6c80
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java
@@ -0,0 +1,117 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.resultset.OrderedResultSet;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.is;
21
22class OrderedResultSetTest {
23 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
24 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
25
26 @Test
27 void relationalFlushTest() {
28 var query = Query.of("Relation", (builder, p1, p2) -> builder.clause(
29 friendView.call(p1, p2)
30 ));
31
32 var store = ModelStore.builder()
33 .symbols(friend)
34 .with(ViatraModelQueryAdapter.builder()
35 .queries(query))
36 .build();
37
38 var model = store.createEmptyModel();
39 var friendInterpretation = model.getInterpretation(friend);
40 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
41 var resultSet = queryEngine.getResultSet(query);
42
43 friendInterpretation.put(Tuple.of(0, 1), true);
44 friendInterpretation.put(Tuple.of(1, 2), true);
45 friendInterpretation.put(Tuple.of(1, 1), true);
46 queryEngine.flushChanges();
47
48 try (var orderedResultSet = new OrderedResultSet<>(resultSet)) {
49 assertThat(orderedResultSet.size(), is(3));
50 assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1)));
51 assertThat(orderedResultSet.getKey(1), is(Tuple.of(1, 1)));
52 assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 2)));
53
54 friendInterpretation.put(Tuple.of(1, 2), false);
55 friendInterpretation.put(Tuple.of(0, 2), true);
56 queryEngine.flushChanges();
57
58 assertThat(orderedResultSet.size(), is(3));
59 assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1)));
60 assertThat(orderedResultSet.getKey(1), is(Tuple.of(0, 2)));
61 assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 1)));
62 }
63 }
64
65 @Test
66 void functionalFlushTest() {
67 var query = Query.of("Function", Integer.class, (builder, p1, output) -> builder.clause(
68 friendView.call(p1, Variable.of()),
69 output.assign(friendView.count(p1, Variable.of()))
70 ));
71
72 var store = ModelStore.builder()
73 .symbols(friend)
74 .with(ViatraModelQueryAdapter.builder()
75 .queries(query))
76 .build();
77
78 var model = store.createEmptyModel();
79 var friendInterpretation = model.getInterpretation(friend);
80 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
81 var resultSet = queryEngine.getResultSet(query);
82
83 friendInterpretation.put(Tuple.of(0, 1), true);
84 friendInterpretation.put(Tuple.of(1, 2), true);
85 friendInterpretation.put(Tuple.of(1, 1), true);
86 queryEngine.flushChanges();
87
88 try (var orderedResultSet = new OrderedResultSet<>(resultSet)) {
89 assertThat(orderedResultSet.size(), is(2));
90 assertThat(orderedResultSet.getKey(0), is(Tuple.of(0)));
91 assertThat(orderedResultSet.getKey(1), is(Tuple.of(1)));
92
93 friendInterpretation.put(Tuple.of(0, 1), false);
94 friendInterpretation.put(Tuple.of(2, 1), true);
95 queryEngine.flushChanges();
96
97 assertThat(orderedResultSet.size(), is(2));
98 assertThat(orderedResultSet.getKey(0), is(Tuple.of(1)));
99 assertThat(orderedResultSet.getKey(1), is(Tuple.of(2)));
100
101 friendInterpretation.put(Tuple.of(1, 1), false);
102 queryEngine.flushChanges();
103
104 assertThat(orderedResultSet.size(), is(2));
105 assertThat(orderedResultSet.getKey(0), is(Tuple.of(1)));
106 assertThat(orderedResultSet.getKey(1), is(Tuple.of(2)));
107
108 friendInterpretation.put(Tuple.of(1, 2), false);
109 friendInterpretation.put(Tuple.of(1, 0), true);
110 queryEngine.flushChanges();
111
112 assertThat(orderedResultSet.size(), is(2));
113 assertThat(orderedResultSet.getKey(0), is(Tuple.of(1)));
114 assertThat(orderedResultSet.getKey(1), is(Tuple.of(2)));
115 }
116 }
117}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
index 6a37b54a..25bcb0dc 100644
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
@@ -1,47 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
3import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.DNF; 11import tools.refinery.store.query.ModelQueryAdapter;
6import tools.refinery.store.query.ModelQuery; 12import tools.refinery.store.query.dnf.Query;
7import tools.refinery.store.query.Variable; 13import tools.refinery.store.query.term.Variable;
8import tools.refinery.store.query.atom.*; 14import tools.refinery.store.query.viatra.tests.QueryEngineTest;
9import tools.refinery.store.query.view.FilteredRelationView; 15import tools.refinery.store.query.view.AnySymbolView;
10import tools.refinery.store.query.view.KeyOnlyRelationView; 16import tools.refinery.store.query.view.FilteredView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
11import tools.refinery.store.representation.Symbol; 19import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.representation.TruthValue; 20import tools.refinery.store.representation.TruthValue;
13import tools.refinery.store.tuple.Tuple; 21import tools.refinery.store.tuple.Tuple;
14import tools.refinery.store.tuple.TupleLike;
15 22
16import java.util.HashSet; 23import java.util.List;
17import java.util.Set; 24import java.util.Map;
18import java.util.stream.Stream;
19 25
20import static org.junit.jupiter.api.Assertions.assertEquals; 26import static tools.refinery.store.query.literal.Literals.assume;
27import static tools.refinery.store.query.literal.Literals.not;
28import static tools.refinery.store.query.term.int_.IntTerms.constant;
29import static tools.refinery.store.query.term.int_.IntTerms.greaterEq;
30import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
21 31
22class QueryTest { 32class QueryTest {
23 @Test 33 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
24 void typeConstraintTest() { 34 private static final Symbol<TruthValue> friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE);
25 var person = new Symbol<>("Person", 1, Boolean.class, false); 35 private static final AnySymbolView personView = new KeyOnlyView<>(person);
26 var asset = new Symbol<>("Asset", 1, Boolean.class, false); 36 private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must);
27 var personView = new KeyOnlyRelationView<>(person); 37
28 38 @QueryEngineTest
29 var p1 = new Variable("p1"); 39 void typeConstraintTest(QueryEvaluationHint hint) {
30 var predicate = DNF.builder("TypeConstraint") 40 var asset = Symbol.of("Asset", 1);
31 .parameters(p1) 41
32 .clause(new RelationViewAtom(personView, p1)) 42 var predicate = Query.of("TypeConstraint", (builder, p1) -> builder.clause(personView.call(p1)));
33 .build();
34 43
35 var store = ModelStore.builder() 44 var store = ModelStore.builder()
36 .symbols(person, asset) 45 .symbols(person, asset)
37 .with(ViatraModelQuery.ADAPTER) 46 .with(ViatraModelQueryAdapter.builder()
38 .queries(predicate) 47 .defaultHint(hint)
48 .queries(predicate))
39 .build(); 49 .build();
40 50
41 var model = store.createEmptyModel(); 51 var model = store.createEmptyModel();
42 var personInterpretation = model.getInterpretation(person); 52 var personInterpretation = model.getInterpretation(person);
43 var assetInterpretation = model.getInterpretation(asset); 53 var assetInterpretation = model.getInterpretation(asset);
44 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 54 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
45 var predicateResultSet = queryEngine.getResultSet(predicate); 55 var predicateResultSet = queryEngine.getResultSet(predicate);
46 56
47 personInterpretation.put(Tuple.of(0), true); 57 personInterpretation.put(Tuple.of(0), true);
@@ -51,42 +61,34 @@ class QueryTest {
51 assetInterpretation.put(Tuple.of(2), true); 61 assetInterpretation.put(Tuple.of(2), true);
52 62
53 queryEngine.flushChanges(); 63 queryEngine.flushChanges();
54 assertEquals(2, predicateResultSet.countResults()); 64 assertResults(Map.of(
55 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); 65 Tuple.of(0), true,
66 Tuple.of(1), true,
67 Tuple.of(2), false
68 ), predicateResultSet);
56 } 69 }
57 70
58 @Test 71 @QueryEngineTest
59 void relationConstraintTest() { 72 void relationConstraintTest(QueryEvaluationHint hint) {
60 var person = new Symbol<>("Person", 1, Boolean.class, false); 73 var predicate = Query.of("RelationConstraint", (builder, p1, p2) -> builder.clause(
61 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 74 personView.call(p1),
62 var personView = new KeyOnlyRelationView<>(person); 75 personView.call(p2),
63 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 76 friendMustView.call(p1, p2)
64 77 ));
65 var p1 = new Variable("p1");
66 var p2 = new Variable("p2");
67 var predicate = DNF.builder("RelationConstraint")
68 .parameters(p1, p2)
69 .clause(
70 new RelationViewAtom(personView, p1),
71 new RelationViewAtom(personView, p2),
72 new RelationViewAtom(friendMustView, p1, p2)
73 )
74 .build();
75 78
76 var store = ModelStore.builder() 79 var store = ModelStore.builder()
77 .symbols(person, friend) 80 .symbols(person, friend)
78 .with(ViatraModelQuery.ADAPTER) 81 .with(ViatraModelQueryAdapter.builder()
79 .queries(predicate) 82 .defaultHint(hint)
83 .queries(predicate))
80 .build(); 84 .build();
81 85
82 var model = store.createEmptyModel(); 86 var model = store.createEmptyModel();
83 var personInterpretation = model.getInterpretation(person); 87 var personInterpretation = model.getInterpretation(person);
84 var friendInterpretation = model.getInterpretation(friend); 88 var friendInterpretation = model.getInterpretation(friend);
85 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 89 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
86 var predicateResultSet = queryEngine.getResultSet(predicate); 90 var predicateResultSet = queryEngine.getResultSet(predicate);
87 91
88 assertEquals(0, predicateResultSet.countResults());
89
90 personInterpretation.put(Tuple.of(0), true); 92 personInterpretation.put(Tuple.of(0), true);
91 personInterpretation.put(Tuple.of(1), true); 93 personInterpretation.put(Tuple.of(1), true);
92 personInterpretation.put(Tuple.of(2), true); 94 personInterpretation.put(Tuple.of(2), true);
@@ -94,97 +96,36 @@ class QueryTest {
94 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); 96 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
95 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); 97 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
96 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 98 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
97 99 friendInterpretation.put(Tuple.of(1, 3), TruthValue.TRUE);
98 assertEquals(0, predicateResultSet.countResults());
99 100
100 queryEngine.flushChanges(); 101 queryEngine.flushChanges();
101 assertEquals(3, predicateResultSet.countResults()); 102 assertResults(Map.of(
102 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(1, 2))); 103 Tuple.of(0, 1), true,
104 Tuple.of(1, 0), true,
105 Tuple.of(1, 2), true,
106 Tuple.of(2, 1), false
107 ), predicateResultSet);
103 } 108 }
104 109
105 @Test 110 @QueryEngineTest
106 void andTest() { 111 void existTest(QueryEvaluationHint hint) {
107 var person = new Symbol<>("Person", 1, Boolean.class, false); 112 var predicate = Query.of("Exists", (builder, p1) -> builder.clause((p2) -> List.of(
108 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 113 personView.call(p1),
109 var personView = new KeyOnlyRelationView<>(person); 114 personView.call(p2),
110 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 115 friendMustView.call(p1, p2)
111 116 )));
112 var p1 = new Variable("p1");
113 var p2 = new Variable("p2");
114 var predicate = DNF.builder("RelationConstraint")
115 .parameters(p1, p2)
116 .clause(
117 new RelationViewAtom(personView, p1),
118 new RelationViewAtom(personView, p2),
119 new RelationViewAtom(friendMustView, p1, p2),
120 new RelationViewAtom(friendMustView, p2, p1)
121 )
122 .build();
123 117
124 var store = ModelStore.builder() 118 var store = ModelStore.builder()
125 .symbols(person, friend) 119 .symbols(person, friend)
126 .with(ViatraModelQuery.ADAPTER) 120 .with(ViatraModelQueryAdapter.builder()
127 .queries(predicate) 121 .defaultHint(hint)
122 .queries(predicate))
128 .build(); 123 .build();
129 124
130 var model = store.createEmptyModel(); 125 var model = store.createEmptyModel();
131 var personInterpretation = model.getInterpretation(person); 126 var personInterpretation = model.getInterpretation(person);
132 var friendInterpretation = model.getInterpretation(friend); 127 var friendInterpretation = model.getInterpretation(friend);
133 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 128 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
134 var predicateResultSet = queryEngine.getResultSet(predicate);
135
136 assertEquals(0, predicateResultSet.countResults());
137
138 personInterpretation.put(Tuple.of(0), true);
139 personInterpretation.put(Tuple.of(1), true);
140 personInterpretation.put(Tuple.of(2), true);
141
142 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
143 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE);
144
145 queryEngine.flushChanges();
146 assertEquals(0, predicateResultSet.countResults());
147
148 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
149 queryEngine.flushChanges();
150 assertEquals(2, predicateResultSet.countResults());
151 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0)));
152
153 friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE);
154 queryEngine.flushChanges();
155 assertEquals(4, predicateResultSet.countResults());
156 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(0, 2),
157 Tuple.of(2, 0)));
158 }
159
160 @Test
161 void existTest() {
162 var person = new Symbol<>("Person", 1, Boolean.class, false);
163 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
164 var personView = new KeyOnlyRelationView<>(person);
165 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
166
167 var p1 = new Variable("p1");
168 var p2 = new Variable("p2");
169 var predicate = DNF.builder("RelationConstraint")
170 .parameters(p1)
171 .clause(
172 new RelationViewAtom(personView, p1),
173 new RelationViewAtom(personView, p2),
174 new RelationViewAtom(friendMustView, p1, p2)
175 )
176 .build();
177
178 var store = ModelStore.builder()
179 .symbols(person, friend)
180 .with(ViatraModelQuery.ADAPTER)
181 .queries(predicate)
182 .build();
183
184 var model = store.createEmptyModel();
185 var personInterpretation = model.getInterpretation(person);
186 var friendInterpretation = model.getInterpretation(friend);
187 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
188 var predicateResultSet = queryEngine.getResultSet(predicate); 129 var predicateResultSet = queryEngine.getResultSet(predicate);
189 130
190 personInterpretation.put(Tuple.of(0), true); 131 personInterpretation.put(Tuple.of(0), true);
@@ -194,50 +135,44 @@ class QueryTest {
194 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); 135 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
195 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); 136 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
196 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 137 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
197 138 friendInterpretation.put(Tuple.of(3, 2), TruthValue.TRUE);
198 assertEquals(0, predicateResultSet.countResults());
199 139
200 queryEngine.flushChanges(); 140 queryEngine.flushChanges();
201 assertEquals(2, predicateResultSet.countResults()); 141 assertResults(Map.of(
202 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); 142 Tuple.of(0), true,
143 Tuple.of(1), true,
144 Tuple.of(2), false,
145 Tuple.of(3), false
146 ), predicateResultSet);
203 } 147 }
204 148
205 @Test 149 @QueryEngineTest
206 void orTest() { 150 void orTest(QueryEvaluationHint hint) {
207 var person = new Symbol<>("Person", 1, Boolean.class, false); 151 var animal = Symbol.of("Animal", 1);
208 var animal = new Symbol<>("Animal", 1, Boolean.class, false); 152 var animalView = new KeyOnlyView<>(animal);
209 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 153
210 var personView = new KeyOnlyRelationView<>(person); 154 var predicate = Query.of("Or", (builder, p1, p2) -> builder.clause(
211 var animalView = new KeyOnlyRelationView<>(animal); 155 personView.call(p1),
212 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 156 personView.call(p2),
213 157 friendMustView.call(p1, p2)
214 var p1 = new Variable("p1"); 158 ).clause(
215 var p2 = new Variable("p2"); 159 animalView.call(p1),
216 var predicate = DNF.builder("Or") 160 animalView.call(p2),
217 .parameters(p1, p2) 161 friendMustView.call(p1, p2)
218 .clause( 162 ));
219 new RelationViewAtom(personView, p1),
220 new RelationViewAtom(personView, p2),
221 new RelationViewAtom(friendMustView, p1, p2)
222 )
223 .clause(
224 new RelationViewAtom(animalView, p1),
225 new RelationViewAtom(animalView, p2),
226 new RelationViewAtom(friendMustView, p1, p2)
227 )
228 .build();
229 163
230 var store = ModelStore.builder() 164 var store = ModelStore.builder()
231 .symbols(person, animal, friend) 165 .symbols(person, animal, friend)
232 .with(ViatraModelQuery.ADAPTER) 166 .with(ViatraModelQueryAdapter.builder()
233 .queries(predicate) 167 .defaultHint(hint)
168 .queries(predicate))
234 .build(); 169 .build();
235 170
236 var model = store.createEmptyModel(); 171 var model = store.createEmptyModel();
237 var personInterpretation = model.getInterpretation(person); 172 var personInterpretation = model.getInterpretation(person);
238 var animalInterpretation = model.getInterpretation(animal); 173 var animalInterpretation = model.getInterpretation(animal);
239 var friendInterpretation = model.getInterpretation(friend); 174 var friendInterpretation = model.getInterpretation(friend);
240 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 175 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
241 var predicateResultSet = queryEngine.getResultSet(predicate); 176 var predicateResultSet = queryEngine.getResultSet(predicate);
242 177
243 personInterpretation.put(Tuple.of(0), true); 178 personInterpretation.put(Tuple.of(0), true);
@@ -252,35 +187,33 @@ class QueryTest {
252 friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); 187 friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE);
253 188
254 queryEngine.flushChanges(); 189 queryEngine.flushChanges();
255 assertEquals(2, predicateResultSet.countResults()); 190 assertResults(Map.of(
256 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(2, 3))); 191 Tuple.of(0, 1), true,
192 Tuple.of(0, 2), false,
193 Tuple.of(2, 3), true,
194 Tuple.of(3, 0), false,
195 Tuple.of(3, 2), false
196 ), predicateResultSet);
257 } 197 }
258 198
259 @Test 199 @QueryEngineTest
260 void equalityTest() { 200 void equalityTest(QueryEvaluationHint hint) {
261 var person = new Symbol<>("Person", 1, Boolean.class, false); 201 var predicate = Query.of("Equality", (builder, p1, p2) -> builder.clause(
262 var personView = new KeyOnlyRelationView<>(person); 202 personView.call(p1),
263 203 personView.call(p2),
264 var p1 = new Variable("p1"); 204 p1.isEquivalent(p2)
265 var p2 = new Variable("p2"); 205 ));
266 var predicate = DNF.builder("Equality")
267 .parameters(p1, p2)
268 .clause(
269 new RelationViewAtom(personView, p1),
270 new RelationViewAtom(personView, p2),
271 new EquivalenceAtom(p1, p2)
272 )
273 .build();
274 206
275 var store = ModelStore.builder() 207 var store = ModelStore.builder()
276 .symbols(person) 208 .symbols(person)
277 .with(ViatraModelQuery.ADAPTER) 209 .with(ViatraModelQueryAdapter.builder()
278 .queries(predicate) 210 .defaultHint(hint)
211 .queries(predicate))
279 .build(); 212 .build();
280 213
281 var model = store.createEmptyModel(); 214 var model = store.createEmptyModel();
282 var personInterpretation = model.getInterpretation(person); 215 var personInterpretation = model.getInterpretation(person);
283 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 216 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
284 var predicateResultSet = queryEngine.getResultSet(predicate); 217 var predicateResultSet = queryEngine.getResultSet(predicate);
285 218
286 personInterpretation.put(Tuple.of(0), true); 219 personInterpretation.put(Tuple.of(0), true);
@@ -288,41 +221,36 @@ class QueryTest {
288 personInterpretation.put(Tuple.of(2), true); 221 personInterpretation.put(Tuple.of(2), true);
289 222
290 queryEngine.flushChanges(); 223 queryEngine.flushChanges();
291 assertEquals(3, predicateResultSet.countResults()); 224 assertResults(Map.of(
292 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 0), Tuple.of(1, 1), Tuple.of(2, 2))); 225 Tuple.of(0, 0), true,
226 Tuple.of(1, 1), true,
227 Tuple.of(2, 2), true,
228 Tuple.of(0, 1), false,
229 Tuple.of(3, 3), false
230 ), predicateResultSet);
293 } 231 }
294 232
295 @Test 233 @QueryEngineTest
296 void inequalityTest() { 234 void inequalityTest(QueryEvaluationHint hint) {
297 var person = new Symbol<>("Person", 1, Boolean.class, false); 235 var predicate = Query.of("Inequality", (builder, p1, p2, p3) -> builder.clause(
298 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 236 personView.call(p1),
299 var personView = new KeyOnlyRelationView<>(person); 237 personView.call(p2),
300 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 238 friendMustView.call(p1, p3),
301 239 friendMustView.call(p2, p3),
302 var p1 = new Variable("p1"); 240 p1.notEquivalent(p2)
303 var p2 = new Variable("p2"); 241 ));
304 var p3 = new Variable("p3");
305 var predicate = DNF.builder("Inequality")
306 .parameters(p1, p2, p3)
307 .clause(
308 new RelationViewAtom(personView, p1),
309 new RelationViewAtom(personView, p2),
310 new RelationViewAtom(friendMustView, p1, p3),
311 new RelationViewAtom(friendMustView, p2, p3),
312 new EquivalenceAtom(false, p1, p2)
313 )
314 .build();
315 242
316 var store = ModelStore.builder() 243 var store = ModelStore.builder()
317 .symbols(person, friend) 244 .symbols(person, friend)
318 .with(ViatraModelQuery.ADAPTER) 245 .with(ViatraModelQueryAdapter.builder()
319 .queries(predicate) 246 .defaultHint(hint)
247 .queries(predicate))
320 .build(); 248 .build();
321 249
322 var model = store.createEmptyModel(); 250 var model = store.createEmptyModel();
323 var personInterpretation = model.getInterpretation(person); 251 var personInterpretation = model.getInterpretation(person);
324 var friendInterpretation = model.getInterpretation(friend); 252 var friendInterpretation = model.getInterpretation(friend);
325 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 253 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
326 var predicateResultSet = queryEngine.getResultSet(predicate); 254 var predicateResultSet = queryEngine.getResultSet(predicate);
327 255
328 personInterpretation.put(Tuple.of(0), true); 256 personInterpretation.put(Tuple.of(0), true);
@@ -333,49 +261,37 @@ class QueryTest {
333 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 261 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
334 262
335 queryEngine.flushChanges(); 263 queryEngine.flushChanges();
336 assertEquals(2, predicateResultSet.countResults()); 264 assertResults(Map.of(
337 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1, 2), Tuple.of(1, 0, 2))); 265 Tuple.of(0, 1, 2), true,
266 Tuple.of(1, 0, 2), true,
267 Tuple.of(0, 0, 2), false
268 ), predicateResultSet);
338 } 269 }
339 270
340 @Test 271 @QueryEngineTest
341 void patternCallTest() { 272 void patternCallTest(QueryEvaluationHint hint) {
342 var person = new Symbol<>("Person", 1, Boolean.class, false); 273 var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause(
343 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 274 personView.call(p1),
344 var personView = new KeyOnlyRelationView<>(person); 275 personView.call(p2),
345 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 276 friendMustView.call(p1, p2)
346 277 ));
347 var p1 = new Variable("p1"); 278 var predicate = Query.of("PositivePatternCall", (builder, p3, p4) -> builder.clause(
348 var p2 = new Variable("p2"); 279 personView.call(p3),
349 var friendPredicate = DNF.builder("RelationConstraint") 280 personView.call(p4),
350 .parameters(p1, p2) 281 friendPredicate.call(p3, p4)
351 .clause( 282 ));
352 new RelationViewAtom(personView, p1),
353 new RelationViewAtom(personView, p2),
354 new RelationViewAtom(friendMustView, p1, p2)
355 )
356 .build();
357
358 var p3 = new Variable("p3");
359 var p4 = new Variable("p4");
360 var predicate = DNF.builder("PositivePatternCall")
361 .parameters(p3, p4)
362 .clause(
363 new RelationViewAtom(personView, p3),
364 new RelationViewAtom(personView, p4),
365 new DNFCallAtom(friendPredicate, p3, p4)
366 )
367 .build();
368 283
369 var store = ModelStore.builder() 284 var store = ModelStore.builder()
370 .symbols(person, friend) 285 .symbols(person, friend)
371 .with(ViatraModelQuery.ADAPTER) 286 .with(ViatraModelQueryAdapter.builder()
372 .queries(predicate) 287 .defaultHint(hint)
288 .queries(predicate))
373 .build(); 289 .build();
374 290
375 var model = store.createEmptyModel(); 291 var model = store.createEmptyModel();
376 var personInterpretation = model.getInterpretation(person); 292 var personInterpretation = model.getInterpretation(person);
377 var friendInterpretation = model.getInterpretation(friend); 293 var friendInterpretation = model.getInterpretation(friend);
378 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 294 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
379 var predicateResultSet = queryEngine.getResultSet(predicate); 295 var predicateResultSet = queryEngine.getResultSet(predicate);
380 296
381 personInterpretation.put(Tuple.of(0), true); 297 personInterpretation.put(Tuple.of(0), true);
@@ -387,37 +303,33 @@ class QueryTest {
387 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 303 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
388 304
389 queryEngine.flushChanges(); 305 queryEngine.flushChanges();
390 assertEquals(3, predicateResultSet.countResults()); 306 assertResults(Map.of(
307 Tuple.of(0, 1), true,
308 Tuple.of(1, 0), true,
309 Tuple.of(1, 2), true,
310 Tuple.of(2, 1), false
311 ), predicateResultSet);
391 } 312 }
392 313
393 @Test 314 @QueryEngineTest
394 void negativeRelationViewTest() { 315 void negativeRelationViewTest(QueryEvaluationHint hint) {
395 var person = new Symbol<>("Person", 1, Boolean.class, false); 316 var predicate = Query.of("NegativePatternCall", (builder, p1, p2) -> builder.clause(
396 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 317 personView.call(p1),
397 var personView = new KeyOnlyRelationView<>(person); 318 personView.call(p2),
398 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 319 not(friendMustView.call(p1, p2))
399 320 ));
400 var p1 = new Variable("p1");
401 var p2 = new Variable("p2");
402 var predicate = DNF.builder("NegativePatternCall")
403 .parameters(p1, p2)
404 .clause(
405 new RelationViewAtom(personView, p1),
406 new RelationViewAtom(personView, p2),
407 new RelationViewAtom(false, friendMustView, p1, p2)
408 )
409 .build();
410 321
411 var store = ModelStore.builder() 322 var store = ModelStore.builder()
412 .symbols(person, friend) 323 .symbols(person, friend)
413 .with(ViatraModelQuery.ADAPTER) 324 .with(ViatraModelQueryAdapter.builder()
414 .queries(predicate) 325 .defaultHint(hint)
326 .queries(predicate))
415 .build(); 327 .build();
416 328
417 var model = store.createEmptyModel(); 329 var model = store.createEmptyModel();
418 var personInterpretation = model.getInterpretation(person); 330 var personInterpretation = model.getInterpretation(person);
419 var friendInterpretation = model.getInterpretation(friend); 331 var friendInterpretation = model.getInterpretation(friend);
420 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 332 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
421 var predicateResultSet = queryEngine.getResultSet(predicate); 333 var predicateResultSet = queryEngine.getResultSet(predicate);
422 334
423 personInterpretation.put(Tuple.of(0), true); 335 personInterpretation.put(Tuple.of(0), true);
@@ -429,48 +341,44 @@ class QueryTest {
429 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 341 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
430 342
431 queryEngine.flushChanges(); 343 queryEngine.flushChanges();
432 assertEquals(6, predicateResultSet.countResults()); 344 assertResults(Map.of(
345 Tuple.of(0, 0), true,
346 Tuple.of(0, 2), true,
347 Tuple.of(1, 1), true,
348 Tuple.of(2, 0), true,
349 Tuple.of(2, 1), true,
350 Tuple.of(2, 2), true,
351 Tuple.of(0, 1), false,
352 Tuple.of(1, 0), false,
353 Tuple.of(1, 2), false,
354 Tuple.of(0, 3), false
355 ), predicateResultSet);
433 } 356 }
434 357
435 @Test 358 @QueryEngineTest
436 void negativePatternCallTest() { 359 void negativePatternCallTest(QueryEvaluationHint hint) {
437 var person = new Symbol<>("Person", 1, Boolean.class, false); 360 var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause(
438 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 361 personView.call(p1),
439 var personView = new KeyOnlyRelationView<>(person); 362 personView.call(p2),
440 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 363 friendMustView.call(p1, p2)
441 364 ));
442 var p1 = new Variable("p1"); 365 var predicate = Query.of("NegativePatternCall", (builder, p3, p4) -> builder.clause(
443 var p2 = new Variable("p2"); 366 personView.call(p3),
444 var friendPredicate = DNF.builder("RelationConstraint") 367 personView.call(p4),
445 .parameters(p1, p2) 368 not(friendPredicate.call(p3, p4))
446 .clause( 369 ));
447 new RelationViewAtom(personView, p1),
448 new RelationViewAtom(personView, p2),
449 new RelationViewAtom(friendMustView, p1, p2)
450 )
451 .build();
452
453 var p3 = new Variable("p3");
454 var p4 = new Variable("p4");
455 var predicate = DNF.builder("NegativePatternCall")
456 .parameters(p3, p4)
457 .clause(
458 new RelationViewAtom(personView, p3),
459 new RelationViewAtom(personView, p4),
460 new DNFCallAtom(false, friendPredicate, p3, p4)
461 )
462 .build();
463 370
464 var store = ModelStore.builder() 371 var store = ModelStore.builder()
465 .symbols(person, friend) 372 .symbols(person, friend)
466 .with(ViatraModelQuery.ADAPTER) 373 .with(ViatraModelQueryAdapter.builder()
467 .queries(predicate) 374 .defaultHint(hint)
375 .queries(predicate))
468 .build(); 376 .build();
469 377
470 var model = store.createEmptyModel(); 378 var model = store.createEmptyModel();
471 var personInterpretation = model.getInterpretation(person); 379 var personInterpretation = model.getInterpretation(person);
472 var friendInterpretation = model.getInterpretation(friend); 380 var friendInterpretation = model.getInterpretation(friend);
473 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 381 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
474 var predicateResultSet = queryEngine.getResultSet(predicate); 382 var predicateResultSet = queryEngine.getResultSet(predicate);
475 383
476 personInterpretation.put(Tuple.of(0), true); 384 personInterpretation.put(Tuple.of(0), true);
@@ -482,37 +390,38 @@ class QueryTest {
482 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 390 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
483 391
484 queryEngine.flushChanges(); 392 queryEngine.flushChanges();
485 assertEquals(6, predicateResultSet.countResults()); 393 assertResults(Map.of(
394 Tuple.of(0, 0), true,
395 Tuple.of(0, 2), true,
396 Tuple.of(1, 1), true,
397 Tuple.of(2, 0), true,
398 Tuple.of(2, 1), true,
399 Tuple.of(2, 2), true,
400 Tuple.of(0, 1), false,
401 Tuple.of(1, 0), false,
402 Tuple.of(1, 2), false,
403 Tuple.of(0, 3), false
404 ), predicateResultSet);
486 } 405 }
487 406
488 @Test 407 @QueryEngineTest
489 void negativeRelationViewWithQuantificationTest() { 408 void negativeRelationViewWithQuantificationTest(QueryEvaluationHint hint) {
490 var person = new Symbol<>("Person", 1, Boolean.class, false); 409 var predicate = Query.of("Negative", (builder, p1) -> builder.clause(
491 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 410 personView.call(p1),
492 var personView = new KeyOnlyRelationView<>(person); 411 not(friendMustView.call(p1, Variable.of()))
493 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 412 ));
494
495 var p1 = new Variable("p1");
496 var p2 = new Variable("p2");
497
498 var predicate = DNF.builder("Count")
499 .parameters(p1)
500 .clause(
501 new RelationViewAtom(personView, p1),
502 new RelationViewAtom(false, friendMustView, p1, p2)
503 )
504 .build();
505 413
506 var store = ModelStore.builder() 414 var store = ModelStore.builder()
507 .symbols(person, friend) 415 .symbols(person, friend)
508 .with(ViatraModelQuery.ADAPTER) 416 .with(ViatraModelQueryAdapter.builder()
509 .queries(predicate) 417 .defaultHint(hint)
418 .queries(predicate))
510 .build(); 419 .build();
511 420
512 var model = store.createEmptyModel(); 421 var model = store.createEmptyModel();
513 var personInterpretation = model.getInterpretation(person); 422 var personInterpretation = model.getInterpretation(person);
514 var friendInterpretation = model.getInterpretation(friend); 423 var friendInterpretation = model.getInterpretation(friend);
515 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 424 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
516 var predicateResultSet = queryEngine.getResultSet(predicate); 425 var predicateResultSet = queryEngine.getResultSet(predicate);
517 426
518 personInterpretation.put(Tuple.of(0), true); 427 personInterpretation.put(Tuple.of(0), true);
@@ -523,46 +432,37 @@ class QueryTest {
523 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); 432 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE);
524 433
525 queryEngine.flushChanges(); 434 queryEngine.flushChanges();
526 assertEquals(2, predicateResultSet.countResults()); 435 assertResults(Map.of(
436 Tuple.of(0), false,
437 Tuple.of(1), true,
438 Tuple.of(2), true,
439 Tuple.of(3), false
440 ), predicateResultSet);
527 } 441 }
528 442
529 @Test 443 @QueryEngineTest
530 void negativeWithQuantificationTest() { 444 void negativeWithQuantificationTest(QueryEvaluationHint hint) {
531 var person = new Symbol<>("Person", 1, Boolean.class, false); 445 var called = Query.of("Called", (builder, p1, p2) -> builder.clause(
532 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 446 personView.call(p1),
533 var personView = new KeyOnlyRelationView<>(person); 447 personView.call(p2),
534 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 448 friendMustView.call(p1, p2)
535 449 ));
536 var p1 = new Variable("p1"); 450 var predicate = Query.of("Negative", (builder, p1) -> builder.clause(
537 var p2 = new Variable("p2"); 451 personView.call(p1),
538 452 not(called.call(p1, Variable.of()))
539 var called = DNF.builder("Called") 453 ));
540 .parameters(p1, p2)
541 .clause(
542 new RelationViewAtom(personView, p1),
543 new RelationViewAtom(personView, p2),
544 new RelationViewAtom(friendMustView, p1, p2)
545 )
546 .build();
547
548 var predicate = DNF.builder("Count")
549 .parameters(p1)
550 .clause(
551 new RelationViewAtom(personView, p1),
552 new DNFCallAtom(false, called, p1, p2)
553 )
554 .build();
555 454
556 var store = ModelStore.builder() 455 var store = ModelStore.builder()
557 .symbols(person, friend) 456 .symbols(person, friend)
558 .with(ViatraModelQuery.ADAPTER) 457 .with(ViatraModelQueryAdapter.builder()
559 .queries(predicate) 458 .defaultHint(hint)
459 .queries(predicate))
560 .build(); 460 .build();
561 461
562 var model = store.createEmptyModel(); 462 var model = store.createEmptyModel();
563 var personInterpretation = model.getInterpretation(person); 463 var personInterpretation = model.getInterpretation(person);
564 var friendInterpretation = model.getInterpretation(friend); 464 var friendInterpretation = model.getInterpretation(friend);
565 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 465 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
566 var predicateResultSet = queryEngine.getResultSet(predicate); 466 var predicateResultSet = queryEngine.getResultSet(predicate);
567 467
568 personInterpretation.put(Tuple.of(0), true); 468 personInterpretation.put(Tuple.of(0), true);
@@ -573,37 +473,33 @@ class QueryTest {
573 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); 473 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE);
574 474
575 queryEngine.flushChanges(); 475 queryEngine.flushChanges();
576 assertEquals(2, predicateResultSet.countResults()); 476 assertResults(Map.of(
477 Tuple.of(0), false,
478 Tuple.of(1), true,
479 Tuple.of(2), true,
480 Tuple.of(3), false
481 ), predicateResultSet);
577 } 482 }
578 483
579 @Test 484 @QueryEngineTest
580 void transitiveRelationViewTest() { 485 void transitiveRelationViewTest(QueryEvaluationHint hint) {
581 var person = new Symbol<>("Person", 1, Boolean.class, false); 486 var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause(
582 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 487 personView.call(p1),
583 var personView = new KeyOnlyRelationView<>(person); 488 personView.call(p2),
584 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 489 friendMustView.callTransitive(p1, p2)
585 490 ));
586 var p1 = new Variable("p1");
587 var p2 = new Variable("p2");
588 var predicate = DNF.builder("TransitivePatternCall")
589 .parameters(p1, p2)
590 .clause(
591 new RelationViewAtom(personView, p1),
592 new RelationViewAtom(personView, p2),
593 new RelationViewAtom(CallPolarity.TRANSITIVE, friendMustView, p1, p2)
594 )
595 .build();
596 491
597 var store = ModelStore.builder() 492 var store = ModelStore.builder()
598 .symbols(person, friend) 493 .symbols(person, friend)
599 .with(ViatraModelQuery.ADAPTER) 494 .with(ViatraModelQueryAdapter.builder()
600 .queries(predicate) 495 .defaultHint(hint)
496 .queries(predicate))
601 .build(); 497 .build();
602 498
603 var model = store.createEmptyModel(); 499 var model = store.createEmptyModel();
604 var personInterpretation = model.getInterpretation(person); 500 var personInterpretation = model.getInterpretation(person);
605 var friendInterpretation = model.getInterpretation(friend); 501 var friendInterpretation = model.getInterpretation(friend);
606 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 502 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
607 var predicateResultSet = queryEngine.getResultSet(predicate); 503 var predicateResultSet = queryEngine.getResultSet(predicate);
608 504
609 personInterpretation.put(Tuple.of(0), true); 505 personInterpretation.put(Tuple.of(0), true);
@@ -614,48 +510,44 @@ class QueryTest {
614 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 510 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
615 511
616 queryEngine.flushChanges(); 512 queryEngine.flushChanges();
617 assertEquals(3, predicateResultSet.countResults()); 513 assertResults(Map.of(
514 Tuple.of(0, 0), false,
515 Tuple.of(0, 1), true,
516 Tuple.of(0, 2), true,
517 Tuple.of(1, 0), false,
518 Tuple.of(1, 1), false,
519 Tuple.of(1, 2), true,
520 Tuple.of(2, 0), false,
521 Tuple.of(2, 1), false,
522 Tuple.of(2, 2), false,
523 Tuple.of(2, 3), false
524 ), predicateResultSet);
618 } 525 }
619 526
620 @Test 527 @QueryEngineTest
621 void transitivePatternCallTest() { 528 void transitivePatternCallTest(QueryEvaluationHint hint) {
622 var person = new Symbol<>("Person", 1, Boolean.class, false); 529 var called = Query.of("Called", (builder, p1, p2) -> builder.clause(
623 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 530 personView.call(p1),
624 var personView = new KeyOnlyRelationView<>(person); 531 personView.call(p2),
625 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 532 friendMustView.call(p1, p2)
626 533 ));
627 var p1 = new Variable("p1"); 534 var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause(
628 var p2 = new Variable("p2"); 535 personView.call(p1),
629 var friendPredicate = DNF.builder("RelationConstraint") 536 personView.call(p2),
630 .parameters(p1, p2) 537 called.callTransitive(p1, p2)
631 .clause( 538 ));
632 new RelationViewAtom(personView, p1),
633 new RelationViewAtom(personView, p2),
634 new RelationViewAtom(friendMustView, p1, p2)
635 )
636 .build();
637
638 var p3 = new Variable("p3");
639 var p4 = new Variable("p4");
640 var predicate = DNF.builder("TransitivePatternCall")
641 .parameters(p3, p4)
642 .clause(
643 new RelationViewAtom(personView, p3),
644 new RelationViewAtom(personView, p4),
645 new DNFCallAtom(CallPolarity.TRANSITIVE, friendPredicate, p3, p4)
646 )
647 .build();
648 539
649 var store = ModelStore.builder() 540 var store = ModelStore.builder()
650 .symbols(person, friend) 541 .symbols(person, friend)
651 .with(ViatraModelQuery.ADAPTER) 542 .with(ViatraModelQueryAdapter.builder()
652 .queries(predicate) 543 .defaultHint(hint)
544 .queries(predicate))
653 .build(); 545 .build();
654 546
655 var model = store.createEmptyModel(); 547 var model = store.createEmptyModel();
656 var personInterpretation = model.getInterpretation(person); 548 var personInterpretation = model.getInterpretation(person);
657 var friendInterpretation = model.getInterpretation(friend); 549 var friendInterpretation = model.getInterpretation(friend);
658 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 550 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
659 var predicateResultSet = queryEngine.getResultSet(predicate); 551 var predicateResultSet = queryEngine.getResultSet(predicate);
660 552
661 personInterpretation.put(Tuple.of(0), true); 553 personInterpretation.put(Tuple.of(0), true);
@@ -666,16 +558,150 @@ class QueryTest {
666 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 558 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
667 559
668 queryEngine.flushChanges(); 560 queryEngine.flushChanges();
669 assertEquals(3, predicateResultSet.countResults()); 561 assertResults(Map.of(
562 Tuple.of(0, 0), false,
563 Tuple.of(0, 1), true,
564 Tuple.of(0, 2), true,
565 Tuple.of(1, 0), false,
566 Tuple.of(1, 1), false,
567 Tuple.of(1, 2), true,
568 Tuple.of(2, 0), false,
569 Tuple.of(2, 1), false,
570 Tuple.of(2, 2), false,
571 Tuple.of(2, 3), false
572 ), predicateResultSet);
573 }
574
575 @Test
576 void filteredIntegerViewTest() {
577 var distance = Symbol.of("distance", 2, Integer.class);
578 var nearView = new FilteredView<>(distance, value -> value < 2);
579 var farView = new FilteredView<>(distance, value -> value >= 5);
580 var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of(
581 a1.notEquivalent(a2),
582 nearView.call(a1, a3),
583 nearView.call(a2, a3),
584 not(farView.call(a1, a2))
585 )));
586 var store = ModelStore.builder()
587 .symbols(distance)
588 .with(ViatraModelQueryAdapter.builder()
589 .queries(dangerQuery))
590 .build();
591
592 var model = store.createEmptyModel();
593 var distanceInterpretation = model.getInterpretation(distance);
594 distanceInterpretation.put(Tuple.of(0, 1), 1);
595 distanceInterpretation.put(Tuple.of(1, 0), 1);
596 distanceInterpretation.put(Tuple.of(0, 2), 1);
597 distanceInterpretation.put(Tuple.of(2, 0), 1);
598 distanceInterpretation.put(Tuple.of(1, 2), 3);
599 distanceInterpretation.put(Tuple.of(2, 1), 3);
600 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
601 var dangerResultSet = queryEngine.getResultSet(dangerQuery);
602 queryEngine.flushChanges();
603 assertResults(Map.of(
604 Tuple.of(0, 1), false,
605 Tuple.of(0, 2), false,
606 Tuple.of(1, 2), true,
607 Tuple.of(2, 1), true
608 ), dangerResultSet);
609 }
610
611 @Test
612 void filteredDoubleViewTest() {
613 var distance = Symbol.of("distance", 2, Double.class);
614 var nearView = new FilteredView<>(distance, value -> value < 2);
615 var farView = new FilteredView<>(distance, value -> value >= 5);
616 var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of(
617 a1.notEquivalent(a2),
618 nearView.call(a1, a3),
619 nearView.call(a2, a3),
620 not(farView.call(a1, a2))
621 )));
622 var store = ModelStore.builder()
623 .symbols(distance)
624 .with(ViatraModelQueryAdapter.builder()
625 .queries(dangerQuery))
626 .build();
627
628 var model = store.createEmptyModel();
629 var distanceInterpretation = model.getInterpretation(distance);
630 distanceInterpretation.put(Tuple.of(0, 1), 1.0);
631 distanceInterpretation.put(Tuple.of(1, 0), 1.0);
632 distanceInterpretation.put(Tuple.of(0, 2), 1.0);
633 distanceInterpretation.put(Tuple.of(2, 0), 1.0);
634 distanceInterpretation.put(Tuple.of(1, 2), 3.0);
635 distanceInterpretation.put(Tuple.of(2, 1), 3.0);
636 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
637 var dangerResultSet = queryEngine.getResultSet(dangerQuery);
638 queryEngine.flushChanges();
639 assertResults(Map.of(
640 Tuple.of(0, 1), false,
641 Tuple.of(0, 2), false,
642 Tuple.of(1, 2), true,
643 Tuple.of(2, 1), true
644 ), dangerResultSet);
645 }
646
647 @QueryEngineTest
648 void assumeTest(QueryEvaluationHint hint) {
649 var age = Symbol.of("age", 1, Integer.class);
650 var ageView = new FunctionView<>(age);
651
652 var query = Query.of("Constraint", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of(
653 personView.call(p1),
654 ageView.call(p1, x),
655 assume(greaterEq(x, constant(18)))
656 )));
657
658 var store = ModelStore.builder()
659 .symbols(person, age)
660 .with(ViatraModelQueryAdapter.builder()
661 .defaultHint(hint)
662 .queries(query))
663 .build();
664
665 var model = store.createEmptyModel();
666 var personInterpretation = model.getInterpretation(person);
667 var ageInterpretation = model.getInterpretation(age);
668 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
669 var queryResultSet = queryEngine.getResultSet(query);
670
671 personInterpretation.put(Tuple.of(0), true);
672 personInterpretation.put(Tuple.of(1), true);
673
674 ageInterpretation.put(Tuple.of(0), 12);
675 ageInterpretation.put(Tuple.of(1), 24);
676
677 queryEngine.flushChanges();
678 assertResults(Map.of(
679 Tuple.of(0), false,
680 Tuple.of(1), true,
681 Tuple.of(2), false
682 ), queryResultSet);
670 } 683 }
671 684
672 static void compareMatchSets(Stream<TupleLike> matchSet, Set<Tuple> expected) { 685 @Test
673 Set<Tuple> translatedMatchSet = new HashSet<>(); 686 void alwaysFalseTest() {
674 var iterator = matchSet.iterator(); 687 var predicate = Query.of("AlwaysFalse", builder -> builder.parameter("p1"));
675 while (iterator.hasNext()) { 688
676 var element = iterator.next(); 689 var store = ModelStore.builder()
677 translatedMatchSet.add(element.toTuple()); 690 .symbols(person)
678 } 691 .with(ViatraModelQueryAdapter.builder()
679 assertEquals(expected, translatedMatchSet); 692 .queries(predicate))
693 .build();
694
695 var model = store.createEmptyModel();
696 var personInterpretation = model.getInterpretation(person);
697 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
698 var predicateResultSet = queryEngine.getResultSet(predicate);
699
700 personInterpretation.put(Tuple.of(0), true);
701 personInterpretation.put(Tuple.of(1), true);
702 personInterpretation.put(Tuple.of(2), true);
703
704 queryEngine.flushChanges();
705 assertResults(Map.of(), predicateResultSet);
680 } 706 }
681} 707}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
index 98995339..66f043c6 100644
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
@@ -1,43 +1,163 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.Disabled;
3import org.junit.jupiter.api.Test; 10import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.ModelStore; 11import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.DNF; 12import tools.refinery.store.query.ModelQueryAdapter;
6import tools.refinery.store.query.ModelQuery; 13import tools.refinery.store.query.dnf.Query;
7import tools.refinery.store.query.Variable; 14import tools.refinery.store.query.dnf.RelationalQuery;
8import tools.refinery.store.query.atom.RelationViewAtom; 15import tools.refinery.store.query.view.AnySymbolView;
9import tools.refinery.store.query.view.KeyOnlyRelationView; 16import tools.refinery.store.query.view.FilteredView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
10import tools.refinery.store.representation.Symbol; 19import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple; 20import tools.refinery.store.tuple.Tuple;
12 21
13import static org.junit.jupiter.api.Assertions.*; 22import java.util.Map;
23import java.util.Optional;
24
25import static org.junit.jupiter.api.Assertions.assertFalse;
26import static org.junit.jupiter.api.Assertions.assertTrue;
27import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
28import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
14 29
15class QueryTransactionTest { 30class QueryTransactionTest {
31 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
32 private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class);
33 private static final AnySymbolView personView = new KeyOnlyView<>(person);
34 private static final AnySymbolView ageView = new FunctionView<>(age);
35 private static final RelationalQuery predicate = Query.of("TypeConstraint", (builder, p1) ->
36 builder.clause(personView.call(p1)));
37
16 @Test 38 @Test
17 void flushTest() { 39 void flushTest() {
18 var person = new Symbol<>("Person", 1, Boolean.class, false); 40 var store = ModelStore.builder()
19 var asset = new Symbol<>("Asset", 1, Boolean.class, false); 41 .symbols(person)
20 var personView = new KeyOnlyRelationView<>(person); 42 .with(ViatraModelQueryAdapter.builder()
21 43 .queries(predicate))
22 var p1 = new Variable("p1");
23 var predicate = DNF.builder("TypeConstraint")
24 .parameters(p1)
25 .clause(new RelationViewAtom(personView, p1))
26 .build(); 44 .build();
27 45
46 var model = store.createEmptyModel();
47 var personInterpretation = model.getInterpretation(person);
48 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
49 var predicateResultSet = queryEngine.getResultSet(predicate);
50
51 assertResults(Map.of(
52 Tuple.of(0), false,
53 Tuple.of(1), false,
54 Tuple.of(2), false,
55 Tuple.of(3), false
56 ), predicateResultSet);
57 assertFalse(queryEngine.hasPendingChanges());
58
59 personInterpretation.put(Tuple.of(0), true);
60 personInterpretation.put(Tuple.of(1), true);
61
62 assertResults(Map.of(
63 Tuple.of(0), false,
64 Tuple.of(1), false,
65 Tuple.of(2), false,
66 Tuple.of(3), false
67 ), predicateResultSet);
68 assertTrue(queryEngine.hasPendingChanges());
69
70 queryEngine.flushChanges();
71 assertResults(Map.of(
72 Tuple.of(0), true,
73 Tuple.of(1), true,
74 Tuple.of(2), false,
75 Tuple.of(3), false
76 ), predicateResultSet);
77 assertFalse(queryEngine.hasPendingChanges());
78
79 personInterpretation.put(Tuple.of(1), false);
80 personInterpretation.put(Tuple.of(2), true);
81
82 assertResults(Map.of(
83 Tuple.of(0), true,
84 Tuple.of(1), true,
85 Tuple.of(2), false,
86 Tuple.of(3), false
87 ), predicateResultSet);
88 assertTrue(queryEngine.hasPendingChanges());
89
90 queryEngine.flushChanges();
91 assertResults(Map.of(
92 Tuple.of(0), true,
93 Tuple.of(1), false,
94 Tuple.of(2), true,
95 Tuple.of(3), false
96 ), predicateResultSet);
97 assertFalse(queryEngine.hasPendingChanges());
98 }
99
100 @Test
101 void localSearchTest() {
102 var store = ModelStore.builder()
103 .symbols(person)
104 .with(ViatraModelQueryAdapter.builder()
105 .defaultHint(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH))
106 .queries(predicate))
107 .build();
108
109 var model = store.createEmptyModel();
110 var personInterpretation = model.getInterpretation(person);
111 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
112 var predicateResultSet = queryEngine.getResultSet(predicate);
113
114 assertResults(Map.of(
115 Tuple.of(0), false,
116 Tuple.of(1), false,
117 Tuple.of(2), false,
118 Tuple.of(3), false
119 ), predicateResultSet);
120 assertFalse(queryEngine.hasPendingChanges());
121
122 personInterpretation.put(Tuple.of(0), true);
123 personInterpretation.put(Tuple.of(1), true);
124
125 assertResults(Map.of(
126 Tuple.of(0), true,
127 Tuple.of(1), true,
128 Tuple.of(2), false,
129 Tuple.of(3), false
130 ), predicateResultSet);
131 assertFalse(queryEngine.hasPendingChanges());
132
133 personInterpretation.put(Tuple.of(1), false);
134 personInterpretation.put(Tuple.of(2), true);
135
136 assertResults(Map.of(
137 Tuple.of(0), true,
138 Tuple.of(1), false,
139 Tuple.of(2), true,
140 Tuple.of(3), false
141 ), predicateResultSet);
142 assertFalse(queryEngine.hasPendingChanges());
143 }
144
145 @Test
146 void unrelatedChangesTest() {
147 var asset = Symbol.of("Asset", 1);
148
28 var store = ModelStore.builder() 149 var store = ModelStore.builder()
29 .symbols(person, asset) 150 .symbols(person, asset)
30 .with(ViatraModelQuery.ADAPTER) 151 .with(ViatraModelQueryAdapter.builder()
31 .queries(predicate) 152 .queries(predicate))
32 .build(); 153 .build();
33 154
34 var model = store.createEmptyModel(); 155 var model = store.createEmptyModel();
35 var personInterpretation = model.getInterpretation(person); 156 var personInterpretation = model.getInterpretation(person);
36 var assetInterpretation = model.getInterpretation(asset); 157 var assetInterpretation = model.getInterpretation(asset);
37 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 158 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
38 var predicateResultSet = queryEngine.getResultSet(predicate); 159 var predicateResultSet = queryEngine.getResultSet(predicate);
39 160
40 assertEquals(0, predicateResultSet.countResults());
41 assertFalse(queryEngine.hasPendingChanges()); 161 assertFalse(queryEngine.hasPendingChanges());
42 162
43 personInterpretation.put(Tuple.of(0), true); 163 personInterpretation.put(Tuple.of(0), true);
@@ -46,19 +166,208 @@ class QueryTransactionTest {
46 assetInterpretation.put(Tuple.of(1), true); 166 assetInterpretation.put(Tuple.of(1), true);
47 assetInterpretation.put(Tuple.of(2), true); 167 assetInterpretation.put(Tuple.of(2), true);
48 168
49 assertEquals(0, predicateResultSet.countResults()); 169 assertResults(Map.of(
170 Tuple.of(0), false,
171 Tuple.of(1), false,
172 Tuple.of(2), false,
173 Tuple.of(3), false,
174 Tuple.of(4), false
175 ), predicateResultSet);
50 assertTrue(queryEngine.hasPendingChanges()); 176 assertTrue(queryEngine.hasPendingChanges());
51 177
52 queryEngine.flushChanges(); 178 queryEngine.flushChanges();
53 assertEquals(2, predicateResultSet.countResults()); 179 assertResults(Map.of(
180 Tuple.of(0), true,
181 Tuple.of(1), true,
182 Tuple.of(2), false,
183 Tuple.of(3), false,
184 Tuple.of(4), false
185 ), predicateResultSet);
54 assertFalse(queryEngine.hasPendingChanges()); 186 assertFalse(queryEngine.hasPendingChanges());
55 187
56 personInterpretation.put(Tuple.of(4), true); 188 assetInterpretation.put(Tuple.of(3), true);
57 assertEquals(2, predicateResultSet.countResults()); 189 assertFalse(queryEngine.hasPendingChanges());
58 assertTrue(queryEngine.hasPendingChanges()); 190
191 assertResults(Map.of(
192 Tuple.of(0), true,
193 Tuple.of(1), true,
194 Tuple.of(2), false,
195 Tuple.of(3), false,
196 Tuple.of(4), false
197 ), predicateResultSet);
198
199 queryEngine.flushChanges();
200 assertResults(Map.of(
201 Tuple.of(0), true,
202 Tuple.of(1), true,
203 Tuple.of(2), false,
204 Tuple.of(3), false,
205 Tuple.of(4), false
206 ), predicateResultSet);
207 assertFalse(queryEngine.hasPendingChanges());
208 }
209
210 @Test
211 void tupleChangingChangeTest() {
212 var query = Query.of("TypeConstraint", Integer.class, (builder, p1, output) -> builder.clause(
213 personView.call(p1),
214 ageView.call(p1, output)
215 ));
216
217 var store = ModelStore.builder()
218 .symbols(person, age)
219 .with(ViatraModelQueryAdapter.builder()
220 .queries(query))
221 .build();
222
223 var model = store.createEmptyModel();
224 var personInterpretation = model.getInterpretation(person);
225 var ageInterpretation = model.getInterpretation(age);
226 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
227 var queryResultSet = queryEngine.getResultSet(query);
228
229 personInterpretation.put(Tuple.of(0), true);
230
231 ageInterpretation.put(Tuple.of(0), 24);
232
233 queryEngine.flushChanges();
234 assertResults(Map.of(Tuple.of(0), 24), queryResultSet);
235
236 ageInterpretation.put(Tuple.of(0), 25);
59 237
60 queryEngine.flushChanges(); 238 queryEngine.flushChanges();
61 assertEquals(3, predicateResultSet.countResults()); 239 assertResults(Map.of(Tuple.of(0), 25), queryResultSet);
240
241 ageInterpretation.put(Tuple.of(0), null);
242
243 queryEngine.flushChanges();
244 assertNullableResults(Map.of(Tuple.of(0), Optional.empty()), queryResultSet);
245 }
246
247 @Test
248 void tuplePreservingUnchangedTest() {
249 var adultView = new FilteredView<>(age, "adult", n -> n != null && n >= 18);
250
251 var query = Query.of("TypeConstraint", (builder, p1) -> builder.clause(
252 personView.call(p1),
253 adultView.call(p1)
254 ));
255
256 var store = ModelStore.builder()
257 .symbols(person, age)
258 .with(ViatraModelQueryAdapter.builder()
259 .queries(query))
260 .build();
261
262 var model = store.createEmptyModel();
263 var personInterpretation = model.getInterpretation(person);
264 var ageInterpretation = model.getInterpretation(age);
265 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
266 var queryResultSet = queryEngine.getResultSet(query);
267
268 personInterpretation.put(Tuple.of(0), true);
269
270 ageInterpretation.put(Tuple.of(0), 24);
271
272 queryEngine.flushChanges();
273 assertResults(Map.of(Tuple.of(0), true), queryResultSet);
274
275 ageInterpretation.put(Tuple.of(0), 25);
276
277 queryEngine.flushChanges();
278 assertResults(Map.of(Tuple.of(0), true), queryResultSet);
279
280 ageInterpretation.put(Tuple.of(0), 17);
281
282 queryEngine.flushChanges();
283 assertResults(Map.of(Tuple.of(0), false), queryResultSet);
284 }
285
286 @Disabled("TODO Fix DiffCursor")
287 @Test
288 void commitAfterFlushTest() {
289 var store = ModelStore.builder()
290 .symbols(person)
291 .with(ViatraModelQueryAdapter.builder()
292 .queries(predicate))
293 .build();
294
295 var model = store.createEmptyModel();
296 var personInterpretation = model.getInterpretation(person);
297 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
298 var predicateResultSet = queryEngine.getResultSet(predicate);
299
300 personInterpretation.put(Tuple.of(0), true);
301 personInterpretation.put(Tuple.of(1), true);
302
303 queryEngine.flushChanges();
304 assertResults(Map.of(
305 Tuple.of(0), true,
306 Tuple.of(1), true,
307 Tuple.of(2), false,
308 Tuple.of(3), false
309 ), predicateResultSet);
310
311 var state1 = model.commit();
312
313 personInterpretation.put(Tuple.of(1), false);
314 personInterpretation.put(Tuple.of(2), true);
315
316 queryEngine.flushChanges();
317 assertResults(Map.of(
318 Tuple.of(0), true,
319 Tuple.of(1), false,
320 Tuple.of(2), true,
321 Tuple.of(3), false
322 ), predicateResultSet);
323
324 model.restore(state1);
325
326 assertFalse(queryEngine.hasPendingChanges());
327 assertResults(Map.of(
328 Tuple.of(0), true,
329 Tuple.of(1), true,
330 Tuple.of(2), false,
331 Tuple.of(3), false
332 ), predicateResultSet);
333 }
334
335 @Disabled("TODO Fix DiffCursor")
336 @Test
337 void commitWithoutFlushTest() {
338 var store = ModelStore.builder()
339 .symbols(person)
340 .with(ViatraModelQueryAdapter.builder()
341 .queries(predicate))
342 .build();
343
344 var model = store.createEmptyModel();
345 var personInterpretation = model.getInterpretation(person);
346 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
347 var predicateResultSet = queryEngine.getResultSet(predicate);
348
349 personInterpretation.put(Tuple.of(0), true);
350 personInterpretation.put(Tuple.of(1), true);
351
352 assertResults(Map.of(), predicateResultSet);
353 assertTrue(queryEngine.hasPendingChanges());
354
355 var state1 = model.commit();
356
357 personInterpretation.put(Tuple.of(1), false);
358 personInterpretation.put(Tuple.of(2), true);
359
360 assertResults(Map.of(), predicateResultSet);
361 assertTrue(queryEngine.hasPendingChanges());
362
363 model.restore(state1);
364
365 assertResults(Map.of(
366 Tuple.of(0), true,
367 Tuple.of(1), true,
368 Tuple.of(2), false,
369 Tuple.of(3), false
370 ), predicateResultSet);
62 assertFalse(queryEngine.hasPendingChanges()); 371 assertFalse(queryEngine.hasPendingChanges());
63 } 372 }
64} 373}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java
deleted file mode 100644
index c529117e..00000000
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.Arguments;
5import org.junit.jupiter.params.provider.MethodSource;
6import tools.refinery.store.representation.cardinality.UpperCardinalities;
7import tools.refinery.store.representation.cardinality.UpperCardinality;
8
9import java.util.List;
10import java.util.stream.Stream;
11
12import static org.hamcrest.MatcherAssert.assertThat;
13import static org.hamcrest.Matchers.equalTo;
14
15class UpperCardinalitySumAggregationOperatorStreamTest {
16 @ParameterizedTest
17 @MethodSource
18 void testStream(List<UpperCardinality> list, UpperCardinality expected) {
19 var result = UpperCardinalitySumAggregationOperator.INSTANCE.aggregateStream(list.stream());
20 assertThat(result, equalTo(expected));
21 }
22
23 static Stream<Arguments> testStream() {
24 return Stream.of(
25 Arguments.of(List.of(), UpperCardinalities.ZERO),
26 Arguments.of(List.of(UpperCardinality.of(3)), UpperCardinality.of(3)),
27 Arguments.of(
28 List.of(
29 UpperCardinality.of(2),
30 UpperCardinality.of(3)
31 ),
32 UpperCardinality.of(5)
33 ),
34 Arguments.of(List.of(UpperCardinalities.UNBOUNDED), UpperCardinalities.UNBOUNDED),
35 Arguments.of(
36 List.of(
37 UpperCardinalities.UNBOUNDED,
38 UpperCardinalities.UNBOUNDED
39 ),
40 UpperCardinalities.UNBOUNDED
41 ),
42 Arguments.of(
43 List.of(
44 UpperCardinalities.UNBOUNDED,
45 UpperCardinality.of(3)
46 ),
47 UpperCardinalities.UNBOUNDED
48 )
49 );
50 }
51}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java
deleted file mode 100644
index 20dad543..00000000
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.Test;
5import tools.refinery.store.representation.cardinality.UpperCardinalities;
6import tools.refinery.store.representation.cardinality.UpperCardinality;
7
8import static org.hamcrest.MatcherAssert.assertThat;
9import static org.hamcrest.Matchers.equalTo;
10
11class UpperCardinalitySumAggregationOperatorTest {
12 private UpperCardinalitySumAggregationOperator.Accumulator accumulator;
13
14 @BeforeEach
15 void beforeEach() {
16 accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.createNeutral();
17 }
18
19 @Test
20 void emptyAggregationTest() {
21 assertResult(UpperCardinality.of(0));
22 }
23
24 @Test
25 void singleBoundedTest() {
26 insert(UpperCardinality.of(3));
27 assertResult(UpperCardinality.of(3));
28 }
29
30 @Test
31 void multipleBoundedTest() {
32 insert(UpperCardinality.of(2));
33 insert(UpperCardinality.of(3));
34 assertResult(UpperCardinality.of(5));
35 }
36
37 @Test
38 void singleUnboundedTest() {
39 insert(UpperCardinalities.UNBOUNDED);
40 assertResult(UpperCardinalities.UNBOUNDED);
41 }
42
43 @Test
44 void multipleUnboundedTest() {
45 insert(UpperCardinalities.UNBOUNDED);
46 insert(UpperCardinalities.UNBOUNDED);
47 assertResult(UpperCardinalities.UNBOUNDED);
48 }
49
50 @Test
51 void removeBoundedTest() {
52 insert(UpperCardinality.of(2));
53 insert(UpperCardinality.of(3));
54 remove(UpperCardinality.of(2));
55 assertResult(UpperCardinality.of(3));
56 }
57
58 @Test
59 void removeAllUnboundedTest() {
60 insert(UpperCardinalities.UNBOUNDED);
61 insert(UpperCardinality.of(3));
62 remove(UpperCardinalities.UNBOUNDED);
63 assertResult(UpperCardinality.of(3));
64 }
65
66 @Test
67 void removeSomeUnboundedTest() {
68 insert(UpperCardinalities.UNBOUNDED);
69 insert(UpperCardinalities.UNBOUNDED);
70 insert(UpperCardinality.of(3));
71 remove(UpperCardinalities.UNBOUNDED);
72 assertResult(UpperCardinalities.UNBOUNDED);
73 }
74
75 private void insert(UpperCardinality value) {
76 accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.update(accumulator, value, true);
77 }
78
79 private void remove(UpperCardinality value) {
80 accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.update(accumulator, value, false);
81 }
82
83 private void assertResult(UpperCardinality expected) {
84 var result = UpperCardinalitySumAggregationOperator.INSTANCE.getAggregate(accumulator);
85 assertThat(result, equalTo(expected));
86 }
87}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java
new file mode 100644
index 00000000..968c6c5e
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java
@@ -0,0 +1,239 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.*;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.tuple.Tuple;
11import tools.refinery.store.tuple.*;
12
13import java.util.List;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.*;
17import static org.junit.jupiter.api.Assertions.assertThrows;
18
19class MatcherUtilsTest {
20 @Test
21 void toViatra0Test() {
22 var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of());
23 assertThat(viatraTuple.getSize(), is(0));
24 assertThat(viatraTuple, instanceOf(FlatTuple0.class));
25 }
26
27 @Test
28 void toViatra1Test() {
29 var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2));
30 assertThat(viatraTuple.getSize(), is(1));
31 assertThat(viatraTuple.get(0), is(Tuple.of(2)));
32 assertThat(viatraTuple, instanceOf(FlatTuple1.class));
33 }
34
35 @Test
36 void toViatra2Test() {
37 var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3));
38 assertThat(viatraTuple.getSize(), is(2));
39 assertThat(viatraTuple.get(0), is(Tuple.of(2)));
40 assertThat(viatraTuple.get(1), is(Tuple.of(3)));
41 assertThat(viatraTuple, instanceOf(FlatTuple2.class));
42 }
43
44 @Test
45 void toViatra3Test() {
46 var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5));
47 assertThat(viatraTuple.getSize(), is(3));
48 assertThat(viatraTuple.get(0), is(Tuple.of(2)));
49 assertThat(viatraTuple.get(1), is(Tuple.of(3)));
50 assertThat(viatraTuple.get(2), is(Tuple.of(5)));
51 assertThat(viatraTuple, instanceOf(FlatTuple3.class));
52 }
53
54 @Test
55 void toViatra4Test() {
56 var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8));
57 assertThat(viatraTuple.getSize(), is(4));
58 assertThat(viatraTuple.get(0), is(Tuple.of(2)));
59 assertThat(viatraTuple.get(1), is(Tuple.of(3)));
60 assertThat(viatraTuple.get(2), is(Tuple.of(5)));
61 assertThat(viatraTuple.get(3), is(Tuple.of(8)));
62 assertThat(viatraTuple, instanceOf(FlatTuple4.class));
63 }
64
65 @Test
66 void toViatra5Test() {
67 var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8, 13));
68 assertThat(viatraTuple.getSize(), is(5));
69 assertThat(viatraTuple.get(0), is(Tuple.of(2)));
70 assertThat(viatraTuple.get(1), is(Tuple.of(3)));
71 assertThat(viatraTuple.get(2), is(Tuple.of(5)));
72 assertThat(viatraTuple.get(3), is(Tuple.of(8)));
73 assertThat(viatraTuple.get(4), is(Tuple.of(13)));
74 assertThat(viatraTuple, instanceOf(FlatTuple.class));
75 }
76
77 @Test
78 void toRefinery0Test() {
79 var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf());
80 assertThat(refineryTuple.getSize(), is(0));
81 assertThat(refineryTuple, instanceOf(Tuple0.class));
82 }
83
84 @Test
85 void toRefinery1Test() {
86 var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2)));
87 assertThat(refineryTuple.getSize(), is(1));
88 assertThat(refineryTuple.get(0), is(2));
89 assertThat(refineryTuple, instanceOf(Tuple1.class));
90 }
91
92 @Test
93 void toRefinery2Test() {
94 var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3)));
95 assertThat(refineryTuple.getSize(), is(2));
96 assertThat(refineryTuple.get(0), is(2));
97 assertThat(refineryTuple.get(1), is(3));
98 assertThat(refineryTuple, instanceOf(Tuple2.class));
99 }
100
101 @Test
102 void toRefinery3Test() {
103 var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5)));
104 assertThat(refineryTuple.getSize(), is(3));
105 assertThat(refineryTuple.get(0), is(2));
106 assertThat(refineryTuple.get(1), is(3));
107 assertThat(refineryTuple.get(2), is(5));
108 assertThat(refineryTuple, instanceOf(Tuple3.class));
109 }
110
111 @Test
112 void toRefinery4Test() {
113 var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5),
114 Tuple.of(8)));
115 assertThat(refineryTuple.getSize(), is(4));
116 assertThat(refineryTuple.get(0), is(2));
117 assertThat(refineryTuple.get(1), is(3));
118 assertThat(refineryTuple.get(2), is(5));
119 assertThat(refineryTuple.get(3), is(8));
120 assertThat(refineryTuple, instanceOf(Tuple4.class));
121 }
122
123 @Test
124 void toRefinery5Test() {
125 var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5),
126 Tuple.of(8), Tuple.of(13)));
127 assertThat(refineryTuple.getSize(), is(5));
128 assertThat(refineryTuple.get(0), is(2));
129 assertThat(refineryTuple.get(1), is(3));
130 assertThat(refineryTuple.get(2), is(5));
131 assertThat(refineryTuple.get(3), is(8));
132 assertThat(refineryTuple.get(4), is(13));
133 assertThat(refineryTuple, instanceOf(TupleN.class));
134 }
135
136 @Test
137 void toRefineryInvalidValueTest() {
138 var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98);
139 assertThrows(IllegalArgumentException.class, () -> MatcherUtils.toRefineryTuple(viatraTuple));
140 }
141
142 @Test
143 void keyToRefinery0Test() {
144 var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(-99));
145 assertThat(refineryTuple.getSize(), is(0));
146 assertThat(refineryTuple, instanceOf(Tuple0.class));
147 }
148
149 @Test
150 void keyToRefinery1Test() {
151 var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), -99));
152 assertThat(refineryTuple.getSize(), is(1));
153 assertThat(refineryTuple.get(0), is(2));
154 assertThat(refineryTuple, instanceOf(Tuple1.class));
155 }
156
157 @Test
158 void keyToRefinery2Test() {
159 var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), -99));
160 assertThat(refineryTuple.getSize(), is(2));
161 assertThat(refineryTuple.get(0), is(2));
162 assertThat(refineryTuple.get(1), is(3));
163 assertThat(refineryTuple, instanceOf(Tuple2.class));
164 }
165
166 @Test
167 void keyToRefinery3Test() {
168 var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5),
169 -99));
170 assertThat(refineryTuple.getSize(), is(3));
171 assertThat(refineryTuple.get(0), is(2));
172 assertThat(refineryTuple.get(1), is(3));
173 assertThat(refineryTuple.get(2), is(5));
174 assertThat(refineryTuple, instanceOf(Tuple3.class));
175 }
176
177 @Test
178 void keyToRefinery4Test() {
179 var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5),
180 Tuple.of(8), -99));
181 assertThat(refineryTuple.getSize(), is(4));
182 assertThat(refineryTuple.get(0), is(2));
183 assertThat(refineryTuple.get(1), is(3));
184 assertThat(refineryTuple.get(2), is(5));
185 assertThat(refineryTuple.get(3), is(8));
186 assertThat(refineryTuple, instanceOf(Tuple4.class));
187 }
188
189 @Test
190 void keyToRefinery5Test() {
191 var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5),
192 Tuple.of(8), Tuple.of(13), -99));
193 assertThat(refineryTuple.getSize(), is(5));
194 assertThat(refineryTuple.get(0), is(2));
195 assertThat(refineryTuple.get(1), is(3));
196 assertThat(refineryTuple.get(2), is(5));
197 assertThat(refineryTuple.get(3), is(8));
198 assertThat(refineryTuple.get(4), is(13));
199 assertThat(refineryTuple, instanceOf(TupleN.class));
200 }
201
202 @Test
203 void keyToRefineryTooShortTest() {
204 var viatraTuple = Tuples.flatTupleOf();
205 assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple));
206 }
207
208 @Test
209 void keyToRefineryInvalidValueTest() {
210 var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98, -99);
211 assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple));
212 }
213
214 @Test
215 void getSingleValueTest() {
216 var value = MatcherUtils.getSingleValue(List.of(Tuples.flatTupleOf(Tuple.of(2), -99)));
217 assertThat(value, is(-99));
218 }
219
220 // Static analysis accurately determines that the result is always {@code null}, but we check anyways.
221 @SuppressWarnings("ConstantValue")
222 @Test
223 void getSingleValueNullTest() {
224 var value = MatcherUtils.getSingleValue((Iterable<? extends ITuple>) null);
225 assertThat(value, nullValue());
226 }
227
228 @Test
229 void getSingleValueEmptyTest() {
230 var value = MatcherUtils.getSingleValue(List.of());
231 assertThat(value, nullValue());
232 }
233
234 @Test
235 void getSingleValueMultipleTest() {
236 var viatraTuples = List.of(Tuples.flatTupleOf(Tuple.of(2), -98), Tuples.flatTupleOf(Tuple.of(2), -99));
237 assertThrows(IllegalStateException.class, () -> MatcherUtils.getSingleValue(viatraTuples));
238 }
239}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java
new file mode 100644
index 00000000..ca089a9d
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java
@@ -0,0 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.junit.jupiter.api.function.Executable;
9import tools.refinery.store.query.resultset.ResultSet;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.*;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16import static org.hamcrest.Matchers.nullValue;
17import static org.junit.jupiter.api.Assertions.assertAll;
18
19public final class QueryAssertions {
20 private QueryAssertions() {
21 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
22 }
23
24 public static <T> void assertNullableResults(Map<Tuple, Optional<T>> expected, ResultSet<T> resultSet) {
25 var nullableValuesMap = new LinkedHashMap<Tuple, T>(expected.size());
26 for (var entry : expected.entrySet()) {
27 nullableValuesMap.put(entry.getKey(), entry.getValue().orElse(null));
28 }
29 assertResults(nullableValuesMap, resultSet);
30 }
31
32 public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) {
33 var defaultValue = resultSet.getQuery().defaultValue();
34 var filteredExpected = new LinkedHashMap<Tuple, T>();
35 var executables = new ArrayList<Executable>();
36 for (var entry : expected.entrySet()) {
37 var key = entry.getKey();
38 var value = entry.getValue();
39 if (!Objects.equals(value, defaultValue)) {
40 filteredExpected.put(key, value);
41 }
42 executables.add(() -> assertThat("value for key " + key,resultSet.get(key), is(value)));
43 }
44 executables.add(() -> assertThat("results size", resultSet.size(), is(filteredExpected.size())));
45
46 var actual = new LinkedHashMap<Tuple, T>();
47 var cursor = resultSet.getAll();
48 while (cursor.move()) {
49 var key = cursor.getKey();
50 var previous = actual.put(key, cursor.getValue());
51 assertThat("duplicate value for key " + key, previous, nullValue());
52 }
53 executables.add(() -> assertThat("results cursor", actual, is(filteredExpected)));
54
55 assertAll(executables);
56 }
57}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java
new file mode 100644
index 00000000..dc0e92c8
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java
@@ -0,0 +1,27 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9
10/**
11 * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names.
12 */
13class QueryBackendHint extends QueryEvaluationHint {
14 public QueryBackendHint(BackendRequirement backendRequirementType) {
15 super(null, backendRequirementType);
16 }
17
18 @Override
19 public String toString() {
20 return switch (getQueryBackendRequirementType()) {
21 case UNSPECIFIED -> "default";
22 case DEFAULT_CACHING -> "incremental";
23 case DEFAULT_SEARCH -> "localSearch";
24 default -> throw new IllegalStateException("Unknown BackendRequirement");
25 };
26 }
27}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java
new file mode 100644
index 00000000..d4f16da7
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.ArgumentsSource;
10
11import java.lang.annotation.ElementType;
12import java.lang.annotation.Retention;
13import java.lang.annotation.RetentionPolicy;
14import java.lang.annotation.Target;
15
16@ParameterizedTest(name = "backend = {0}")
17@ArgumentsSource(QueryEvaluationHintSource.class)
18@Target(ElementType.METHOD)
19@Retention(RetentionPolicy.RUNTIME)
20public @interface QueryEngineTest {
21}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java
new file mode 100644
index 00000000..9e75d5f3
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.extension.ExtensionContext;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.ArgumentsProvider;
12
13import java.util.stream.Stream;
14
15public class QueryEvaluationHintSource implements ArgumentsProvider {
16 @Override
17 public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
18 return Stream.of(
19 Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.UNSPECIFIED)),
20 Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_CACHING)),
21 Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH))
22 );
23 }
24}