diff options
author | 2023-03-01 02:15:50 +0100 | |
---|---|---|
committer | 2023-03-01 11:10:39 +0100 | |
commit | da5d898b3bad6f55efd6619f0abee0ec8aa1de4b (patch) | |
tree | d752bb975189a52aaaa5211e64dec42ea0b152fe /subprojects/store-query-viatra/src/main/java | |
parent | chore(deps): bump dependencies (diff) | |
download | refinery-da5d898b3bad6f55efd6619f0abee0ec8aa1de4b.tar.gz refinery-da5d898b3bad6f55efd6619f0abee0ec8aa1de4b.tar.zst refinery-da5d898b3bad6f55efd6619f0abee0ec8aa1de4b.zip |
refactor: more direct access to VIATRA result set
Accessing VIATRA result sets through IQueryResultProvider requires a lot
of indirection, allocations, and locking.
We use reflection instead to have direct access to the underlying
Indexer instead.
Unfortunately, projection to arbitrary tuple masks (with wildcard
entries in a tuple) is completely broken in VIATRA when RETE update
propagation is delayed. While a new, ad-hoc projection indexer gets
added to the RETE net immediately, it is not populated with the
projection results until updates are flushed in the query engine.
Therefore, when encountering an ad-hoc projection for the first time,
the projection results will always be empty (thus usually out of date).
While declaring the desired projections ahead of time would be a
possible solution, for now, we completely remove ad-hoc projection
support. If projections are needed on the ModelQuery level, we should
create an API for declaring projections for each registered Dnf.
Diffstat (limited to 'subprojects/store-query-viatra/src/main/java')
3 files changed, 139 insertions, 65 deletions
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 e0341598..37700413 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 | |||
@@ -21,32 +21,37 @@ import java.util.Map; | |||
21 | 21 | ||
22 | public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { | 22 | public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { |
23 | private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; | 23 | private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; |
24 | private static final MethodHandle SET_UPDATE_PROPAGATION_DELAYED_HANDLE; | ||
24 | private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; | 25 | private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; |
26 | private static final MethodHandle GET_QUERY_BACKENDS_HANDLE; | ||
25 | 27 | ||
26 | private final Model model; | 28 | private final Model model; |
27 | private final ViatraModelQueryStoreAdapterImpl storeAdapter; | 29 | private final ViatraModelQueryStoreAdapterImpl storeAdapter; |
28 | private final ViatraQueryEngineImpl queryEngine; | 30 | private final ViatraQueryEngineImpl queryEngine; |
29 | private final MethodHandle setUpdatePropagationDelayedHandle; | 31 | |
30 | private final MethodHandle getQueryBackendsHandle; | ||
31 | private final Map<Dnf, ResultSet> resultSets; | 32 | private final Map<Dnf, ResultSet> resultSets; |
32 | private boolean pendingChanges; | 33 | private boolean pendingChanges; |
33 | 34 | ||
34 | ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { | 35 | static { |
35 | this.model = model; | ||
36 | this.storeAdapter = storeAdapter; | ||
37 | var scope = new RelationalScope(this); | ||
38 | queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope); | ||
39 | |||
40 | try { | 36 | try { |
41 | var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); | 37 | var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); |
42 | setUpdatePropagationDelayedHandle = lookup.findSetter(ViatraQueryEngineImpl.class, | 38 | SET_UPDATE_PROPAGATION_DELAYED_HANDLE = lookup.findSetter(ViatraQueryEngineImpl.class, |
43 | DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); | 39 | DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); |
44 | getQueryBackendsHandle = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, | 40 | GET_QUERY_BACKENDS_HANDLE = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, |
45 | Map.class); | 41 | Map.class); |
46 | } catch (IllegalAccessException | NoSuchFieldException e) { | 42 | } catch (IllegalAccessException | NoSuchFieldException e) { |
47 | throw new IllegalStateException("Cannot access private members of %s" | 43 | throw new IllegalStateException("Cannot access private members of %s" |
48 | .formatted(ViatraQueryEngineImpl.class.getName()), e); | 44 | .formatted(ViatraQueryEngineImpl.class.getName()), e); |
49 | } | 45 | } |
46 | } | ||
47 | |||
48 | ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { | ||
49 | this.model = model; | ||
50 | this.storeAdapter = storeAdapter; | ||
51 | var scope = new RelationalScope(this); | ||
52 | queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope); | ||
53 | |||
54 | |||
50 | 55 | ||
51 | var querySpecifications = storeAdapter.getQuerySpecifications(); | 56 | var querySpecifications = storeAdapter.getQuerySpecifications(); |
52 | GenericQueryGroup.of( | 57 | GenericQueryGroup.of( |
@@ -67,7 +72,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { | |||
67 | 72 | ||
68 | private void setUpdatePropagationDelayed(boolean value) { | 73 | private void setUpdatePropagationDelayed(boolean value) { |
69 | try { | 74 | try { |
70 | setUpdatePropagationDelayedHandle.invokeExact(queryEngine, value); | 75 | SET_UPDATE_PROPAGATION_DELAYED_HANDLE.invokeExact(queryEngine, value); |
71 | } catch (Error e) { | 76 | } catch (Error e) { |
72 | // Fatal JVM errors should not be wrapped. | 77 | // Fatal JVM errors should not be wrapped. |
73 | throw e; | 78 | throw e; |
@@ -79,7 +84,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { | |||
79 | private Collection<IQueryBackend> getQueryBackends() { | 84 | private Collection<IQueryBackend> getQueryBackends() { |
80 | try { | 85 | try { |
81 | @SuppressWarnings("unchecked") | 86 | @SuppressWarnings("unchecked") |
82 | var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) getQueryBackendsHandle.invokeExact(queryEngine); | 87 | var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) GET_QUERY_BACKENDS_HANDLE.invokeExact(queryEngine); |
83 | return backendMap.values(); | 88 | return backendMap.values(); |
84 | } catch (Error e) { | 89 | } catch (Error e) { |
85 | // Fatal JVM errors should not be wrapped. | 90 | // Fatal JVM errors should not be wrapped. |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/IndexerUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/IndexerUtils.java new file mode 100644 index 00000000..75588b81 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/IndexerUtils.java | |||
@@ -0,0 +1,48 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.pquery; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
4 | import org.eclipse.viatra.query.runtime.rete.index.Indexer; | ||
5 | import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine; | ||
6 | import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; | ||
7 | import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo; | ||
8 | |||
9 | import java.lang.invoke.MethodHandle; | ||
10 | import java.lang.invoke.MethodHandles; | ||
11 | import java.lang.invoke.MethodType; | ||
12 | |||
13 | final class IndexerUtils { | ||
14 | private static final MethodHandle GET_ENGINE_HANDLE; | ||
15 | private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE; | ||
16 | private static final MethodHandle ACCESS_PROJECTION_HANDLE; | ||
17 | |||
18 | static { | ||
19 | try { | ||
20 | var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup()); | ||
21 | GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class); | ||
22 | GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace", | ||
23 | RecipeTraceInfo.class); | ||
24 | ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection", | ||
25 | MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class)); | ||
26 | } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) { | ||
27 | throw new IllegalStateException("Cannot access private members of %s" | ||
28 | .formatted(RetePatternMatcher.class.getPackageName()), e); | ||
29 | } | ||
30 | } | ||
31 | |||
32 | private IndexerUtils() { | ||
33 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
34 | } | ||
35 | |||
36 | public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) { | ||
37 | try { | ||
38 | var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend); | ||
39 | var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend); | ||
40 | return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask); | ||
41 | } catch (Error e) { | ||
42 | // Fatal JVM errors should not be wrapped. | ||
43 | throw e; | ||
44 | } catch (Throwable e) { | ||
45 | throw new IllegalStateException("Cannot access matcher for mask " + mask, e); | ||
46 | } | ||
47 | } | ||
48 | } | ||
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 index e944e873..8f56586e 100644 --- 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 | |||
@@ -2,71 +2,92 @@ package tools.refinery.store.query.viatra.internal.pquery; | |||
2 | 2 | ||
3 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; | 3 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; |
4 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | 4 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; |
5 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; | ||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
10 | import org.eclipse.viatra.query.runtime.rete.index.Indexer; | ||
11 | import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; | ||
5 | import tools.refinery.store.query.ResultSet; | 12 | import tools.refinery.store.query.ResultSet; |
6 | import tools.refinery.store.query.viatra.ViatraTupleLike; | 13 | import tools.refinery.store.query.viatra.ViatraTupleLike; |
7 | import tools.refinery.store.tuple.Tuple; | 14 | import tools.refinery.store.tuple.Tuple; |
8 | import tools.refinery.store.tuple.TupleLike; | 15 | import tools.refinery.store.tuple.TupleLike; |
9 | 16 | ||
10 | import java.util.Optional; | ||
11 | import java.util.stream.Stream; | 17 | import java.util.stream.Stream; |
12 | 18 | ||
19 | /** | ||
20 | * Directly access the tuples inside a VIATRA pattern matcher.<p> | ||
21 | * This class neglects calling | ||
22 | * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} | ||
23 | * and | ||
24 | * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, | ||
25 | * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial | ||
26 | * implementation for these methods. | ||
27 | * Using this class with any other runtime context may lead to undefined behavior. | ||
28 | */ | ||
13 | public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet { | 29 | public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet { |
14 | protected final Object[] empty; | 30 | private final Object[] empty; |
31 | private final TupleMask identityMask; | ||
32 | private Indexer emptyMaskIndexer; | ||
15 | 33 | ||
16 | public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { | 34 | public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { |
17 | super(specification); | 35 | super(specification); |
18 | empty = new Object[specification.getParameterNames().size()]; | 36 | var arity = specification.getParameterNames().size(); |
19 | } | 37 | empty = new Object[arity]; |
38 | identityMask = TupleMask.identity(arity); | ||
39 | } | ||
20 | 40 | ||
21 | @Override | 41 | @Override |
22 | public boolean hasResult() { | 42 | protected void setBackend(ViatraQueryEngine engine, IQueryResultProvider resultProvider, |
23 | return backend.hasMatch(empty); | 43 | IMatcherCapability capabilities) { |
24 | } | 44 | super.setBackend(engine, resultProvider, capabilities); |
45 | if (resultProvider instanceof RetePatternMatcher reteBackend) { | ||
46 | emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, TupleMask.empty(identityMask.sourceWidth)); | ||
47 | } | ||
48 | } | ||
25 | 49 | ||
26 | @Override | 50 | @Override |
27 | public boolean hasResult(Tuple parameters) { | 51 | public boolean hasResult(TupleLike parameters) { |
28 | return backend.hasMatch(toParametersArray(parameters)); | 52 | org.eclipse.viatra.query.runtime.matchers.tuple.Tuple tuple; |
29 | } | 53 | if (parameters instanceof ViatraTupleLike viatraTupleLike) { |
54 | tuple = viatraTupleLike.wrappedTuple().toImmutable(); | ||
55 | } else { | ||
56 | var parametersArray = toParametersArray(parameters); | ||
57 | tuple = Tuples.flatTupleOf(parametersArray); | ||
58 | } | ||
59 | if (emptyMaskIndexer == null) { | ||
60 | return backend.hasMatch(identityMask, tuple); | ||
61 | } | ||
62 | var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); | ||
63 | return matches != null && matches.contains(tuple); | ||
64 | } | ||
30 | 65 | ||
31 | @Override | 66 | @Override |
32 | public Optional<TupleLike> oneResult() { | 67 | public Stream<TupleLike> allResults() { |
33 | return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new); | 68 | if (emptyMaskIndexer == null) { |
34 | } | 69 | return backend.getAllMatches(empty).map(ViatraTupleLike::new); |
70 | } | ||
71 | var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); | ||
72 | return matches == null ? Stream.of() : matches.stream().map(ViatraTupleLike::new); | ||
73 | } | ||
35 | 74 | ||
36 | @Override | 75 | @Override |
37 | public Optional<TupleLike> oneResult(Tuple parameters) { | 76 | public int countResults() { |
38 | return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new); | 77 | if (emptyMaskIndexer == null) { |
39 | } | 78 | return backend.countMatches(empty); |
79 | } | ||
80 | var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); | ||
81 | return matches == null ? 0 : matches.size(); | ||
82 | } | ||
40 | 83 | ||
41 | @Override | 84 | private Object[] toParametersArray(TupleLike tuple) { |
42 | public Stream<TupleLike> allResults() { | 85 | int size = tuple.getSize(); |
43 | return backend.getAllMatches(empty).map(ViatraTupleLike::new); | 86 | var array = new Object[size]; |
44 | } | 87 | for (int i = 0; i < size; i++) { |
45 | 88 | var value = tuple.get(i); | |
46 | @Override | 89 | array[i] = Tuple.of(value); |
47 | public Stream<TupleLike> allResults(Tuple parameters) { | 90 | } |
48 | return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new); | 91 | return array; |
49 | } | 92 | } |
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 | } | 93 | } |