diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-09-26 00:39:23 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-10-03 20:06:52 +0200 |
commit | aac386f0d8c4e4585026b11bfeca20f378f7f261 (patch) | |
tree | cad56fc2c88a1abd56258d64b5ce3a16baaff011 /subprojects/store-query-viatra/src | |
parent | chore: fix some warnings (diff) | |
download | refinery-aac386f0d8c4e4585026b11bfeca20f378f7f261.tar.gz refinery-aac386f0d8c4e4585026b11bfeca20f378f7f261.tar.zst refinery-aac386f0d8c4e4585026b11bfeca20f378f7f261.zip |
refactor: move viatra into a separate subproject
Diffstat (limited to 'subprojects/store-query-viatra/src')
16 files changed, 1675 insertions, 0 deletions
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java new file mode 100644 index 00000000..5a02ca08 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java | |||
@@ -0,0 +1,132 @@ | |||
1 | package tools.refinery.store.query.viatra; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
4 | import tools.refinery.store.model.ModelDiffCursor; | ||
5 | import tools.refinery.store.model.ModelStore; | ||
6 | import tools.refinery.store.model.ModelStoreImpl; | ||
7 | import tools.refinery.store.model.representation.DataRepresentation; | ||
8 | import tools.refinery.store.query.QueryableModel; | ||
9 | import tools.refinery.store.query.QueryableModelStore; | ||
10 | import tools.refinery.store.query.building.*; | ||
11 | import tools.refinery.store.query.viatra.internal.ViatraQueryableModel; | ||
12 | import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; | ||
13 | import tools.refinery.store.query.viatra.internal.RawPatternMatcher; | ||
14 | import tools.refinery.store.query.viatra.internal.pquery.SimplePQuery; | ||
15 | import tools.refinery.store.query.view.RelationView; | ||
16 | |||
17 | import java.util.Collections; | ||
18 | import java.util.HashMap; | ||
19 | import java.util.Map; | ||
20 | import java.util.Set; | ||
21 | |||
22 | public class ViatraQueryableModelStore implements QueryableModelStore { | ||
23 | protected final ModelStore store; | ||
24 | protected final Set<RelationView<?>> relationViews; | ||
25 | protected final Map<DNFPredicate, GenericQuerySpecification<RawPatternMatcher>> predicates; | ||
26 | |||
27 | public ViatraQueryableModelStore(ModelStore store, Set<RelationView<?>> relationViews, | ||
28 | Set<DNFPredicate> predicates) { | ||
29 | this.store = store; | ||
30 | validateViews(store.getDataRepresentations(), relationViews); | ||
31 | this.relationViews = Collections.unmodifiableSet(relationViews); | ||
32 | validatePredicates(relationViews, predicates); | ||
33 | this.predicates = initPredicates(predicates); | ||
34 | } | ||
35 | |||
36 | public ViatraQueryableModelStore(Set<DataRepresentation<?, ?>> dataRepresentations, | ||
37 | Set<RelationView<?>> relationViews, Set<DNFPredicate> predicates) { | ||
38 | this(new ModelStoreImpl(dataRepresentations), relationViews, predicates); | ||
39 | } | ||
40 | |||
41 | private void validateViews(Set<DataRepresentation<?, ?>> dataRepresentations, Set<RelationView<?>> relationViews) { | ||
42 | for (RelationView<?> relationView : relationViews) { | ||
43 | if (!dataRepresentations.contains(relationView.getRepresentation())) { | ||
44 | throw new IllegalArgumentException( | ||
45 | DataRepresentation.class.getSimpleName() + " " + relationView.getStringID() + " added to " | ||
46 | + QueryableModelStore.class.getSimpleName() + " without a referred representation."); | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | private void validatePredicates(Set<RelationView<?>> relationViews, Set<DNFPredicate> predicates) { | ||
52 | for (DNFPredicate dnfPredicate : predicates) { | ||
53 | for (DNFAnd clause : dnfPredicate.getClauses()) { | ||
54 | for (DNFAtom atom : clause.getConstraints()) { | ||
55 | if (atom instanceof RelationAtom relationAtom) { | ||
56 | validateRelationAtom(relationViews, dnfPredicate, relationAtom); | ||
57 | } else if (atom instanceof PredicateAtom predicateAtom) { | ||
58 | validatePredicateAtom(predicates, dnfPredicate, predicateAtom); | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | } | ||
64 | |||
65 | private void validateRelationAtom(Set<RelationView<?>> relationViews, DNFPredicate dnfPredicate, | ||
66 | RelationAtom relationAtom) { | ||
67 | if (!relationViews.contains(relationAtom.view())) { | ||
68 | throw new IllegalArgumentException(DNFPredicate.class.getSimpleName() + " " | ||
69 | + dnfPredicate.getUniqueName() + " contains reference to a view of " | ||
70 | + relationAtom.view().getRepresentation().getName() | ||
71 | + " that is not in the model."); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | private void validatePredicateAtom(Set<DNFPredicate> predicates, DNFPredicate dnfPredicate, | ||
76 | PredicateAtom predicateAtom) { | ||
77 | if (!predicates.contains(predicateAtom.getReferred())) { | ||
78 | throw new IllegalArgumentException( | ||
79 | DNFPredicate.class.getSimpleName() + " " + dnfPredicate.getUniqueName() | ||
80 | + " contains reference to a predicate " | ||
81 | + predicateAtom.getReferred().getName() | ||
82 | + "that is not in the model."); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | private Map<DNFPredicate, GenericQuerySpecification<RawPatternMatcher>> initPredicates(Set<DNFPredicate> predicates) { | ||
87 | Map<DNFPredicate, GenericQuerySpecification<RawPatternMatcher>> result = new HashMap<>(); | ||
88 | Map<DNFPredicate, SimplePQuery> dnf2PQueryMap = new HashMap<>(); | ||
89 | for (DNFPredicate dnfPredicate : predicates) { | ||
90 | GenericQuerySpecification<RawPatternMatcher> query = | ||
91 | DNF2PQuery.translate(dnfPredicate, dnf2PQueryMap).build(); | ||
92 | result.put(dnfPredicate, query); | ||
93 | } | ||
94 | |||
95 | return result; | ||
96 | } | ||
97 | |||
98 | @Override | ||
99 | public Set<DataRepresentation<?, ?>> getDataRepresentations() { | ||
100 | return store.getDataRepresentations(); | ||
101 | } | ||
102 | |||
103 | @Override | ||
104 | public Set<RelationView<?>> getViews() { | ||
105 | return this.relationViews; | ||
106 | } | ||
107 | |||
108 | @Override | ||
109 | public Set<DNFPredicate> getPredicates() { | ||
110 | return predicates.keySet(); | ||
111 | } | ||
112 | |||
113 | @Override | ||
114 | public QueryableModel createModel() { | ||
115 | return new ViatraQueryableModel(this, this.store.createModel(), predicates); | ||
116 | } | ||
117 | |||
118 | @Override | ||
119 | public QueryableModel createModel(long state) { | ||
120 | return new ViatraQueryableModel(this, this.store.createModel(state), predicates); | ||
121 | } | ||
122 | |||
123 | @Override | ||
124 | public synchronized Set<Long> getStates() { | ||
125 | return this.store.getStates(); | ||
126 | } | ||
127 | |||
128 | @Override | ||
129 | public synchronized ModelDiffCursor getDiffCursor(long from, long to) { | ||
130 | return this.store.getDiffCursor(from, to); | ||
131 | } | ||
132 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java new file mode 100644 index 00000000..be348a63 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java | |||
@@ -0,0 +1,51 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; | ||
4 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.AbstractTuple; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
7 | |||
8 | import java.util.Optional; | ||
9 | import java.util.stream.Stream; | ||
10 | |||
11 | public class RawPatternMatcher extends GenericPatternMatcher { | ||
12 | protected final Object[] empty; | ||
13 | |||
14 | public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { | ||
15 | super(specification); | ||
16 | this.empty = new Object[specification.getParameterNames().size()]; | ||
17 | } | ||
18 | |||
19 | public boolean hasResult() { | ||
20 | return hasResult(empty); | ||
21 | } | ||
22 | |||
23 | public boolean hasResult(Object[] parameters) { | ||
24 | return this.backend.hasMatch(parameters); | ||
25 | } | ||
26 | |||
27 | public Optional<Object[]> oneResult() { | ||
28 | return oneResult(empty); | ||
29 | } | ||
30 | |||
31 | public Optional<Object[]> oneResult(Object[] parameters) { | ||
32 | Optional<Tuple> tuple = this.backend.getOneArbitraryMatch(parameters); | ||
33 | return tuple.map(AbstractTuple::getElements); | ||
34 | } | ||
35 | |||
36 | public Stream<Object[]> allResults() { | ||
37 | return allResults(empty); | ||
38 | } | ||
39 | |||
40 | public Stream<Object[]> allResults(Object[] parameters) { | ||
41 | return this.backend.getAllMatches(parameters).map(AbstractTuple::getElements); | ||
42 | } | ||
43 | |||
44 | public int countResults() { | ||
45 | return countResults(empty); | ||
46 | } | ||
47 | |||
48 | public int countResults(Object[] parameters) { | ||
49 | return backend.countMatches(parameters); | ||
50 | } | ||
51 | } | ||
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 new file mode 100644 index 00000000..8dfa22e0 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java | |||
@@ -0,0 +1,43 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | ||
2 | |||
3 | import org.apache.log4j.Logger; | ||
4 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; | ||
5 | import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; | ||
6 | import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; | ||
7 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; | ||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.model.Tuple; | ||
10 | import tools.refinery.store.model.representation.Relation; | ||
11 | import tools.refinery.store.query.viatra.internal.context.RelationalEngineContext; | ||
12 | import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; | ||
13 | import tools.refinery.store.query.view.RelationView; | ||
14 | |||
15 | import java.util.Set; | ||
16 | |||
17 | public class RelationalScope extends QueryScope { | ||
18 | private final Model model; | ||
19 | private final ModelUpdateListener updateListener; | ||
20 | |||
21 | public RelationalScope(Model model, Set<RelationView<?>> relationViews) { | ||
22 | this.model = model; | ||
23 | this.updateListener = new ModelUpdateListener(relationViews); | ||
24 | } | ||
25 | |||
26 | public <D> void processUpdate(Relation<D> relation, Tuple key, D oldValue, D newValue) { | ||
27 | updateListener.addUpdate(relation, key, oldValue, newValue); | ||
28 | } | ||
29 | |||
30 | public boolean hasChanges() { | ||
31 | return updateListener.hasChanges(); | ||
32 | } | ||
33 | |||
34 | public void flush() { | ||
35 | updateListener.flush(); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | protected IEngineContext createEngineContext(ViatraQueryEngine engine, IIndexingErrorListener errorListener, | ||
40 | Logger logger) { | ||
41 | return new RelationalEngineContext(model, updateListener); | ||
42 | } | ||
43 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java new file mode 100644 index 00000000..3803702d --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java | |||
@@ -0,0 +1,219 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; | ||
4 | import org.eclipse.viatra.query.runtime.api.GenericQueryGroup; | ||
5 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
6 | import org.eclipse.viatra.query.runtime.api.IQueryGroup; | ||
7 | import tools.refinery.store.map.Cursor; | ||
8 | import tools.refinery.store.map.DiffCursor; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.model.ModelDiffCursor; | ||
11 | import tools.refinery.store.model.Tuple; | ||
12 | import tools.refinery.store.model.representation.DataRepresentation; | ||
13 | import tools.refinery.store.model.representation.Relation; | ||
14 | import tools.refinery.store.query.QueryableModel; | ||
15 | import tools.refinery.store.query.QueryableModelStore; | ||
16 | import tools.refinery.store.query.building.DNFPredicate; | ||
17 | |||
18 | import java.util.HashMap; | ||
19 | import java.util.Map; | ||
20 | import java.util.Optional; | ||
21 | import java.util.Set; | ||
22 | import java.util.stream.Stream; | ||
23 | |||
24 | public class ViatraQueryableModel implements QueryableModel { | ||
25 | protected final QueryableModelStore store; | ||
26 | |||
27 | protected final Model model; | ||
28 | |||
29 | protected final Map<DNFPredicate, GenericQuerySpecification<RawPatternMatcher>> predicates2PQuery; | ||
30 | |||
31 | protected RelationalScope scope; | ||
32 | |||
33 | protected AdvancedViatraQueryEngine engine; | ||
34 | |||
35 | protected Map<DNFPredicate, RawPatternMatcher> predicate2Matcher; | ||
36 | |||
37 | public ViatraQueryableModel(QueryableModelStore store, Model model, | ||
38 | Map<DNFPredicate, GenericQuerySpecification<RawPatternMatcher>> predicates2PQuery) { | ||
39 | this.store = store; | ||
40 | this.model = model; | ||
41 | this.predicates2PQuery = predicates2PQuery; | ||
42 | initEngine(); | ||
43 | } | ||
44 | |||
45 | private void initEngine() { | ||
46 | this.scope = new RelationalScope(this.model, this.store.getViews()); | ||
47 | this.engine = AdvancedViatraQueryEngine.createUnmanagedEngine(this.scope); | ||
48 | this.predicate2Matcher = initMatchers(this.engine, this.predicates2PQuery); | ||
49 | } | ||
50 | |||
51 | private Map<DNFPredicate, RawPatternMatcher> initMatchers( | ||
52 | AdvancedViatraQueryEngine engine, | ||
53 | Map<DNFPredicate, GenericQuerySpecification<RawPatternMatcher>> predicates2pQuery) { | ||
54 | // 1. prepare group | ||
55 | IQueryGroup queryGroup = GenericQueryGroup.of(Set.copyOf(predicates2pQuery.values())); | ||
56 | engine.prepareGroup(queryGroup, null); | ||
57 | |||
58 | // 2. then get all matchers | ||
59 | Map<DNFPredicate, RawPatternMatcher> result = new HashMap<>(); | ||
60 | for (var entry : predicates2pQuery.entrySet()) { | ||
61 | var matcher = engine.getMatcher(entry.getValue()); | ||
62 | result.put(entry.getKey(), matcher); | ||
63 | } | ||
64 | return result; | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public Set<DataRepresentation<?, ?>> getDataRepresentations() { | ||
69 | return model.getDataRepresentations(); | ||
70 | } | ||
71 | |||
72 | @Override | ||
73 | public Set<DNFPredicate> getPredicates() { | ||
74 | return store.getPredicates(); | ||
75 | } | ||
76 | |||
77 | @Override | ||
78 | public <K, V> V get(DataRepresentation<K, V> representation, K key) { | ||
79 | return model.get(representation, key); | ||
80 | } | ||
81 | |||
82 | @Override | ||
83 | public <K, V> Cursor<K, V> getAll(DataRepresentation<K, V> representation) { | ||
84 | return model.getAll(representation); | ||
85 | } | ||
86 | |||
87 | @SuppressWarnings("unchecked") | ||
88 | @Override | ||
89 | public <K, V> V put(DataRepresentation<K, V> representation, K key, V value) { | ||
90 | V oldValue = this.model.put(representation, key, value); | ||
91 | if (representation instanceof Relation<?> relation) { | ||
92 | this.scope.processUpdate((Relation<V>) relation, (Tuple) key, oldValue, value); | ||
93 | } | ||
94 | return oldValue; | ||
95 | } | ||
96 | |||
97 | @Override | ||
98 | public <K, V> void putAll(DataRepresentation<K, V> representation, Cursor<K, V> cursor) { | ||
99 | if (representation instanceof Relation<?>) { | ||
100 | //noinspection RedundantSuppression | ||
101 | @SuppressWarnings("unchecked") | ||
102 | Relation<V> relation = (Relation<V>) representation; | ||
103 | while (cursor.move()) { | ||
104 | Tuple key = (Tuple) cursor.getKey(); | ||
105 | V newValue = cursor.getValue(); | ||
106 | V oldValue = this.model.put(relation, key, newValue); | ||
107 | this.scope.processUpdate(relation, key, oldValue, newValue); | ||
108 | } | ||
109 | } else { | ||
110 | this.model.putAll(representation, cursor); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | @Override | ||
115 | public <K, V> long getSize(DataRepresentation<K, V> representation) { | ||
116 | return model.getSize(representation); | ||
117 | } | ||
118 | |||
119 | protected RawPatternMatcher getMatcher(DNFPredicate predicate) { | ||
120 | var result = this.predicate2Matcher.get(predicate); | ||
121 | if (result == null) { | ||
122 | throw new IllegalArgumentException("Model does not contain predicate %s".formatted(predicate.getName())); | ||
123 | } else | ||
124 | return result; | ||
125 | } | ||
126 | |||
127 | protected void validateParameters(DNFPredicate predicate, Object[] parameters) { | ||
128 | int predicateArity = predicate.getVariables().size(); | ||
129 | int parameterArity = parameters.length; | ||
130 | if (parameterArity != predicateArity) { | ||
131 | throw new IllegalArgumentException( | ||
132 | "Predicate %s with %d arity called with different number of parameters (%d)" | ||
133 | .formatted(predicate.getName(), predicateArity, parameterArity)); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | @Override | ||
138 | public boolean hasResult(DNFPredicate predicate) { | ||
139 | return getMatcher(predicate).hasResult(); | ||
140 | } | ||
141 | |||
142 | @Override | ||
143 | public boolean hasResult(DNFPredicate predicate, Object[] parameters) { | ||
144 | validateParameters(predicate, parameters); | ||
145 | return getMatcher(predicate).hasResult(parameters); | ||
146 | } | ||
147 | |||
148 | @Override | ||
149 | public Optional<Object[]> oneResult(DNFPredicate predicate) { | ||
150 | return getMatcher(predicate).oneResult(); | ||
151 | } | ||
152 | |||
153 | @Override | ||
154 | public Optional<Object[]> oneResult(DNFPredicate predicate, Object[] parameters) { | ||
155 | validateParameters(predicate, parameters); | ||
156 | return getMatcher(predicate).oneResult(parameters); | ||
157 | } | ||
158 | |||
159 | @Override | ||
160 | public Stream<Object[]> allResults(DNFPredicate predicate) { | ||
161 | return getMatcher(predicate).allResults(); | ||
162 | } | ||
163 | |||
164 | @Override | ||
165 | public Stream<Object[]> allResults(DNFPredicate predicate, Object[] parameters) { | ||
166 | validateParameters(predicate, parameters); | ||
167 | return getMatcher(predicate).allResults(parameters); | ||
168 | } | ||
169 | |||
170 | @Override | ||
171 | public int countResults(DNFPredicate predicate) { | ||
172 | return getMatcher(predicate).countResults(); | ||
173 | } | ||
174 | |||
175 | @Override | ||
176 | public int countResults(DNFPredicate predicate, Object[] parameters) { | ||
177 | validateParameters(predicate, parameters); | ||
178 | return getMatcher(predicate).countResults(parameters); | ||
179 | |||
180 | } | ||
181 | |||
182 | @Override | ||
183 | public boolean hasChanges() { | ||
184 | return scope.hasChanges(); | ||
185 | } | ||
186 | |||
187 | @Override | ||
188 | public void flushChanges() { | ||
189 | this.scope.flush(); | ||
190 | } | ||
191 | |||
192 | @Override | ||
193 | public ModelDiffCursor getDiffCursor(long to) { | ||
194 | return model.getDiffCursor(to); | ||
195 | } | ||
196 | |||
197 | @Override | ||
198 | public long commit() { | ||
199 | return this.model.commit(); | ||
200 | } | ||
201 | |||
202 | @Override | ||
203 | public void restore(long state) { | ||
204 | restoreWithDiffReplay(state); | ||
205 | } | ||
206 | |||
207 | private void restoreWithDiffReplay(long state) { | ||
208 | var modelDiffCursor = getDiffCursor(state); | ||
209 | for (DataRepresentation<?, ?> dataRepresentation : this.getDataRepresentations()) { | ||
210 | restoreRepresentationWithDiffReplay(modelDiffCursor, dataRepresentation); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | private <K, V> void restoreRepresentationWithDiffReplay(ModelDiffCursor modelDiffCursor, | ||
215 | DataRepresentation<K, V> dataRepresentation) { | ||
216 | DiffCursor<K, V> diffCursor = modelDiffCursor.getCursor(dataRepresentation); | ||
217 | this.putAll(dataRepresentation, diffCursor); | ||
218 | } | ||
219 | } | ||
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 new file mode 100644 index 00000000..4b311a64 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java | |||
@@ -0,0 +1,58 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.context; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; | ||
4 | import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; | ||
5 | import org.eclipse.viatra.query.runtime.api.scope.IInstanceObserver; | ||
6 | import org.eclipse.viatra.query.runtime.api.scope.ViatraBaseIndexChangeListener; | ||
7 | |||
8 | import java.lang.reflect.InvocationTargetException; | ||
9 | import java.util.concurrent.Callable; | ||
10 | |||
11 | /** | ||
12 | * Copied from <code>org.eclipse.viatra.query.runtime.tabular.TabularEngineContext</code> | ||
13 | */ | ||
14 | public class DummyBaseIndexer implements IBaseIndex { | ||
15 | @Override | ||
16 | public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException { | ||
17 | try { | ||
18 | return callable.call(); | ||
19 | } catch (Exception e) { | ||
20 | throw new InvocationTargetException(e); | ||
21 | } | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public void addBaseIndexChangeListener(ViatraBaseIndexChangeListener listener) { | ||
26 | // no notification support | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public void removeBaseIndexChangeListener(ViatraBaseIndexChangeListener listener) { | ||
31 | // no notification support | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public void resampleDerivedFeatures() { | ||
36 | throw new UnsupportedOperationException(); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public boolean addIndexingErrorListener(IIndexingErrorListener listener) { | ||
41 | return true; | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public boolean removeIndexingErrorListener(IIndexingErrorListener listener) { | ||
46 | return true; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public boolean addInstanceObserver(IInstanceObserver observer, Object observedObject) { | ||
51 | return true; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public boolean removeInstanceObserver(IInstanceObserver observer, Object observedObject) { | ||
56 | return true; | ||
57 | } | ||
58 | } | ||
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 new file mode 100644 index 00000000..882734cb --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java | |||
@@ -0,0 +1,33 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.context; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; | ||
4 | import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; | ||
6 | |||
7 | import tools.refinery.store.model.Model; | ||
8 | import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; | ||
9 | |||
10 | public class RelationalEngineContext implements IEngineContext { | ||
11 | private final IBaseIndex baseIndex = new DummyBaseIndexer(); | ||
12 | private final RelationalRuntimeContext runtimeContext; | ||
13 | |||
14 | public RelationalEngineContext(Model model, ModelUpdateListener updateListener) { | ||
15 | runtimeContext = new RelationalRuntimeContext(model, updateListener); | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public IBaseIndex getBaseIndex() { | ||
20 | return this.baseIndex; | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public void dispose() { | ||
25 | // Nothing to dispose, because lifecycle is not controlled by the engine. | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public IQueryRuntimeContext getQueryRuntimeContext() { | ||
30 | return runtimeContext; | ||
31 | } | ||
32 | |||
33 | } | ||
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 new file mode 100644 index 00000000..64c23c61 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java | |||
@@ -0,0 +1,44 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.context; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; | ||
6 | import tools.refinery.store.query.view.RelationView; | ||
7 | |||
8 | import java.util.*; | ||
9 | |||
10 | /** | ||
11 | * The meta context information for String scopes. | ||
12 | */ | ||
13 | public class RelationalQueryMetaContext extends AbstractQueryMetaContext { | ||
14 | @Override | ||
15 | public boolean isEnumerable(IInputKey key) { | ||
16 | ensureValidKey(key); | ||
17 | return key.isEnumerable(); | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public boolean isStateless(IInputKey key) { | ||
22 | ensureValidKey(key); | ||
23 | return true; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) { | ||
28 | ensureValidKey(implyingKey); | ||
29 | return Set.of(); | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) { | ||
34 | ensureValidKey(key); | ||
35 | return Map.of(); | ||
36 | } | ||
37 | |||
38 | public void ensureValidKey(IInputKey key) { | ||
39 | if (key instanceof RelationView<?>) { | ||
40 | return; | ||
41 | } | ||
42 | throw new IllegalArgumentException("The input key %s is not a valid input key.".formatted(key)); | ||
43 | } | ||
44 | } | ||
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 new file mode 100644 index 00000000..0bd1b807 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java | |||
@@ -0,0 +1,172 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.context; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.*; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
8 | import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; | ||
11 | import tools.refinery.store.query.view.RelationView; | ||
12 | |||
13 | import java.lang.reflect.InvocationTargetException; | ||
14 | import java.util.Iterator; | ||
15 | import java.util.Optional; | ||
16 | import java.util.concurrent.Callable; | ||
17 | |||
18 | import static tools.refinery.store.util.CollectionsUtil.filter; | ||
19 | import static tools.refinery.store.util.CollectionsUtil.map; | ||
20 | |||
21 | public class RelationalRuntimeContext implements IQueryRuntimeContext { | ||
22 | private final RelationalQueryMetaContext metaContext = new RelationalQueryMetaContext(); | ||
23 | |||
24 | private final ModelUpdateListener modelUpdateListener; | ||
25 | |||
26 | private final Model model; | ||
27 | |||
28 | public RelationalRuntimeContext(Model model, ModelUpdateListener relationUpdateListener) { | ||
29 | this.model = model; | ||
30 | this.modelUpdateListener = relationUpdateListener; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public IQueryMetaContext getMetaContext() { | ||
35 | return metaContext; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException { | ||
40 | try { | ||
41 | return callable.call(); | ||
42 | } catch (Exception e) { | ||
43 | throw new InvocationTargetException(e); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public boolean isCoalescing() { | ||
49 | return true; | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public boolean isIndexed(IInputKey key, IndexingService service) { | ||
54 | if (key instanceof RelationView<?> relationalKey) { | ||
55 | return this.modelUpdateListener.containsRelationalView(relationalKey); | ||
56 | } else { | ||
57 | return false; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public void ensureIndexed(IInputKey key, IndexingService service) { | ||
63 | if (!isIndexed(key, service)) { | ||
64 | throw new IllegalStateException("Engine tries to index a new key " + key); | ||
65 | } | ||
66 | } | ||
67 | |||
68 | @SuppressWarnings("squid:S1452") | ||
69 | RelationView<?> checkKey(IInputKey key) { | ||
70 | if (key instanceof RelationView<?> relationViewKey) { | ||
71 | if (modelUpdateListener.containsRelationalView(relationViewKey)) { | ||
72 | return relationViewKey; | ||
73 | } else { | ||
74 | throw new IllegalStateException("Query is asking for non-indexed key"); | ||
75 | } | ||
76 | } else { | ||
77 | throw new IllegalStateException("Query is asking for non-relational key"); | ||
78 | } | ||
79 | } | ||
80 | |||
81 | @Override | ||
82 | public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
83 | RelationView<?> relationalViewKey = checkKey(key); | ||
84 | Iterable<Object[]> allObjects = relationalViewKey.getAll(model); | ||
85 | Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); | ||
86 | Iterator<Object[]> iterator = filteredBySeed.iterator(); | ||
87 | int result = 0; | ||
88 | while (iterator.hasNext()) { | ||
89 | iterator.next(); | ||
90 | result++; | ||
91 | } | ||
92 | return result; | ||
93 | } | ||
94 | |||
95 | @Override | ||
96 | public Optional<Long> estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { | ||
97 | return Optional.empty(); | ||
98 | } | ||
99 | |||
100 | @Override | ||
101 | public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
102 | RelationView<?> relationalViewKey = checkKey(key); | ||
103 | Iterable<Object[]> allObjects = relationalViewKey.getAll(model); | ||
104 | Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); | ||
105 | return map(filteredBySeed, Tuples::flatTupleOf); | ||
106 | } | ||
107 | |||
108 | private boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { | ||
109 | for (int i = 0; i < seedMask.indices.length; i++) { | ||
110 | final Object seedElement = seed.get(i); | ||
111 | final Object tupleElement = tuple[seedMask.indices[i]]; | ||
112 | if (!tupleElement.equals(seedElement)) { | ||
113 | return false; | ||
114 | } | ||
115 | } | ||
116 | return true; | ||
117 | } | ||
118 | |||
119 | @Override | ||
120 | public Iterable<? extends Object> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
121 | return enumerateTuples(key, seedMask, seed); | ||
122 | } | ||
123 | |||
124 | @Override | ||
125 | public boolean containsTuple(IInputKey key, ITuple seed) { | ||
126 | RelationView<?> relationalViewKey = checkKey(key); | ||
127 | return relationalViewKey.get(model, seed.getElements()); | ||
128 | } | ||
129 | |||
130 | @Override | ||
131 | public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { | ||
132 | RelationView<?> relationalKey = checkKey(key); | ||
133 | this.modelUpdateListener.addListener(relationalKey, seed, listener); | ||
134 | |||
135 | } | ||
136 | |||
137 | @Override | ||
138 | public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { | ||
139 | RelationView<?> relationalKey = checkKey(key); | ||
140 | this.modelUpdateListener.removeListener(relationalKey, seed, listener); | ||
141 | } | ||
142 | |||
143 | @Override | ||
144 | public Object wrapElement(Object externalElement) { | ||
145 | return externalElement; | ||
146 | } | ||
147 | |||
148 | @Override | ||
149 | public Object unwrapElement(Object internalElement) { | ||
150 | return internalElement; | ||
151 | } | ||
152 | |||
153 | @Override | ||
154 | public Tuple wrapTuple(Tuple externalElements) { | ||
155 | return externalElements; | ||
156 | } | ||
157 | |||
158 | @Override | ||
159 | public Tuple unwrapTuple(Tuple internalElements) { | ||
160 | return internalElements; | ||
161 | } | ||
162 | |||
163 | @Override | ||
164 | public void ensureWildcardIndexing(IndexingService service) { | ||
165 | throw new UnsupportedOperationException(); | ||
166 | } | ||
167 | |||
168 | @Override | ||
169 | public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException { | ||
170 | runnable.run(); | ||
171 | } | ||
172 | } | ||
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..c093be47 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java | |||
@@ -0,0 +1,114 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.pquery; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality; | ||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.NegativePatternCall; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
14 | import tools.refinery.store.query.building.*; | ||
15 | |||
16 | import java.util.*; | ||
17 | |||
18 | public class DNF2PQuery { | ||
19 | private DNF2PQuery() { | ||
20 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
21 | } | ||
22 | |||
23 | public static SimplePQuery translate(DNFPredicate predicate, Map<DNFPredicate, SimplePQuery> dnf2PQueryMap) { | ||
24 | SimplePQuery query = dnf2PQueryMap.get(predicate); | ||
25 | if (query != null) { | ||
26 | return query; | ||
27 | } | ||
28 | query = new SimplePQuery(predicate.getName()); | ||
29 | Map<Variable, PParameter> parameters = new HashMap<>(); | ||
30 | |||
31 | predicate.getVariables().forEach(variable -> parameters.put(variable, new PParameter(variable.getName()))); | ||
32 | List<PParameter> parameterList = new ArrayList<>(); | ||
33 | for (var param : predicate.getVariables()) { | ||
34 | parameterList.add(parameters.get(param)); | ||
35 | } | ||
36 | query.setParameters(parameterList); | ||
37 | for (DNFAnd clause : predicate.getClauses()) { | ||
38 | PBody body = new PBody(query); | ||
39 | List<ExportedParameter> symbolicParameters = new ArrayList<>(); | ||
40 | for (var param : predicate.getVariables()) { | ||
41 | PVariable pVar = body.getOrCreateVariableByName(param.getName()); | ||
42 | symbolicParameters.add(new ExportedParameter(body, pVar, parameters.get(param))); | ||
43 | } | ||
44 | body.setSymbolicParameters(symbolicParameters); | ||
45 | query.addBody(body); | ||
46 | for (DNFAtom constraint : clause.getConstraints()) { | ||
47 | translateDNFAtom(constraint, body, dnf2PQueryMap); | ||
48 | } | ||
49 | } | ||
50 | dnf2PQueryMap.put(predicate, query); | ||
51 | return query; | ||
52 | } | ||
53 | |||
54 | private static void translateDNFAtom(DNFAtom constraint, PBody body, | ||
55 | Map<DNFPredicate, SimplePQuery> dnf2PQueryMap) { | ||
56 | if (constraint instanceof EquivalenceAtom equivalence) { | ||
57 | translateEquivalenceAtom(equivalence, body); | ||
58 | } | ||
59 | if (constraint instanceof RelationAtom relation) { | ||
60 | translateRelationAtom(relation, body); | ||
61 | } | ||
62 | if (constraint instanceof PredicateAtom predicate) { | ||
63 | translatePredicateAtom(predicate, body, dnf2PQueryMap); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | private static void translateEquivalenceAtom(EquivalenceAtom equivalence, PBody body) { | ||
68 | PVariable varSource = body.getOrCreateVariableByName(equivalence.getLeft().getName()); | ||
69 | PVariable varTarget = body.getOrCreateVariableByName(equivalence.getRight().getName()); | ||
70 | if (equivalence.isPositive()) | ||
71 | new Equality(body, varSource, varTarget); | ||
72 | else | ||
73 | new Inequality(body, varSource, varTarget); | ||
74 | } | ||
75 | |||
76 | private static void translateRelationAtom(RelationAtom relation, PBody body) { | ||
77 | if (relation.substitution().size() != relation.view().getArity()) { | ||
78 | throw new IllegalArgumentException("Arity (%d) does not match parameter numbers (%d)".formatted( | ||
79 | relation.view().getArity(), relation.substitution().size())); | ||
80 | } | ||
81 | Object[] variables = new Object[relation.substitution().size()]; | ||
82 | for (int i = 0; i < relation.substitution().size(); i++) { | ||
83 | variables[i] = body.getOrCreateVariableByName(relation.substitution().get(i).getName()); | ||
84 | } | ||
85 | new TypeConstraint(body, Tuples.flatTupleOf(variables), relation.view()); | ||
86 | } | ||
87 | |||
88 | private static void translatePredicateAtom(PredicateAtom predicate, PBody body, | ||
89 | Map<DNFPredicate, SimplePQuery> dnf2PQueryMap) { | ||
90 | Object[] variables = new Object[predicate.getSubstitution().size()]; | ||
91 | for (int i = 0; i < predicate.getSubstitution().size(); i++) { | ||
92 | variables[i] = body.getOrCreateVariableByName(predicate.getSubstitution().get(i).getName()); | ||
93 | } | ||
94 | if (predicate.isPositive()) { | ||
95 | if (predicate.isTransitive()) { | ||
96 | if (predicate.getSubstitution().size() != 2) { | ||
97 | throw new IllegalArgumentException("Transitive Predicate Atoms must be binary."); | ||
98 | } | ||
99 | new BinaryTransitiveClosure(body, Tuples.flatTupleOf(variables), | ||
100 | DNF2PQuery.translate(predicate.getReferred(), dnf2PQueryMap)); | ||
101 | } else { | ||
102 | new PositivePatternCall(body, Tuples.flatTupleOf(variables), | ||
103 | DNF2PQuery.translate(predicate.getReferred(), dnf2PQueryMap)); | ||
104 | } | ||
105 | } else { | ||
106 | if (predicate.isTransitive()) { | ||
107 | throw new InputMismatchException("Transitive Predicate Atoms cannot be negative."); | ||
108 | } else { | ||
109 | new NegativePatternCall(body, Tuples.flatTupleOf(variables), | ||
110 | DNF2PQuery.translate(predicate.getReferred(), dnf2PQueryMap)); | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java new file mode 100644 index 00000000..a367cbf2 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java | |||
@@ -0,0 +1,74 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.pquery; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
4 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; | ||
5 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | ||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; | ||
11 | import tools.refinery.store.query.viatra.internal.RawPatternMatcher; | ||
12 | import tools.refinery.store.query.viatra.internal.RelationalScope; | ||
13 | |||
14 | import java.util.LinkedHashSet; | ||
15 | import java.util.List; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public class SimplePQuery extends BasePQuery { | ||
19 | private final String fullyQualifiedName; | ||
20 | private List<PParameter> parameters; | ||
21 | private final LinkedHashSet<PBody> bodies = new LinkedHashSet<>(); | ||
22 | |||
23 | public SimplePQuery(String name) { | ||
24 | super(PVisibility.PUBLIC); | ||
25 | fullyQualifiedName = name; | ||
26 | setEvaluationHints(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.UNSPECIFIED)); | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public String getFullyQualifiedName() { | ||
31 | return fullyQualifiedName; | ||
32 | } | ||
33 | |||
34 | public void setParameters(List<PParameter> parameters) { | ||
35 | this.parameters = parameters; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public List<PParameter> getParameters() { | ||
40 | return parameters; | ||
41 | } | ||
42 | |||
43 | public void addBody(PBody body) { | ||
44 | bodies.add(body); | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | protected Set<PBody> doGetContainedBodies() { | ||
49 | return bodies; | ||
50 | } | ||
51 | |||
52 | public GenericQuerySpecification<RawPatternMatcher> build() { | ||
53 | return new GenericQuerySpecification<>(this) { | ||
54 | @Override | ||
55 | public Class<? extends QueryScope> getPreferredScopeClass() { | ||
56 | return RelationalScope.class; | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | protected RawPatternMatcher instantiate(ViatraQueryEngine engine) { | ||
61 | RawPatternMatcher matcher = engine.getExistingMatcher(this); | ||
62 | if (matcher == null) { | ||
63 | matcher = engine.getMatcher(this); | ||
64 | } | ||
65 | return matcher; | ||
66 | } | ||
67 | |||
68 | @Override | ||
69 | public RawPatternMatcher instantiate() { | ||
70 | return new RawPatternMatcher(this); | ||
71 | } | ||
72 | }; | ||
73 | } | ||
74 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java new file mode 100644 index 00000000..25919888 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java | |||
@@ -0,0 +1,105 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
5 | import tools.refinery.store.model.Tuple; | ||
6 | import tools.refinery.store.model.representation.Relation; | ||
7 | import tools.refinery.store.query.view.RelationView; | ||
8 | |||
9 | import java.util.HashMap; | ||
10 | import java.util.HashSet; | ||
11 | import java.util.Map; | ||
12 | import java.util.Set; | ||
13 | |||
14 | public class ModelUpdateListener { | ||
15 | /** | ||
16 | * Collections of Relations and their Views. | ||
17 | */ | ||
18 | private final Map<Relation<?>, Set<RelationView<?>>> relation2View; | ||
19 | |||
20 | /** | ||
21 | * Collection of Views and their buffers. | ||
22 | */ | ||
23 | private final Map<RelationView<?>, Set<ViewUpdateBuffer<?>>> view2Buffers; | ||
24 | |||
25 | public ModelUpdateListener(Set<RelationView<?>> relationViews) { | ||
26 | this.relation2View = new HashMap<>(); | ||
27 | this.view2Buffers = new HashMap<>(); | ||
28 | |||
29 | for (RelationView<?> relationView : relationViews) { | ||
30 | registerView(relationView); | ||
31 | } | ||
32 | } | ||
33 | |||
34 | private void registerView(RelationView<?> view) { | ||
35 | Relation<?> relation = view.getRepresentation(); | ||
36 | |||
37 | // 1. register views to relations, if necessary | ||
38 | var views = relation2View.computeIfAbsent(relation, x -> new HashSet<>()); | ||
39 | views.add(view); | ||
40 | |||
41 | // 2. register notifier map to views, if necessary | ||
42 | view2Buffers.computeIfAbsent(view, x -> new HashSet<>()); | ||
43 | } | ||
44 | |||
45 | public boolean containsRelationalView(RelationView<?> relationalKey) { | ||
46 | return view2Buffers.containsKey(relationalKey); | ||
47 | } | ||
48 | |||
49 | public <D> void addListener(RelationView<D> relationView, ITuple seed, IQueryRuntimeContextListener listener) { | ||
50 | if (view2Buffers.containsKey(relationView)) { | ||
51 | ViewUpdateTranslator<D> updateListener = new ViewUpdateTranslator<>(relationView, seed, listener); | ||
52 | ViewUpdateBuffer<D> updateBuffer = new ViewUpdateBuffer<>(updateListener); | ||
53 | view2Buffers.get(relationView).add(updateBuffer); | ||
54 | } else | ||
55 | throw new IllegalArgumentException(); | ||
56 | } | ||
57 | |||
58 | public void removeListener(RelationView<?> relationView, ITuple seed, IQueryRuntimeContextListener listener) { | ||
59 | if (view2Buffers.containsKey(relationView)) { | ||
60 | Set<ViewUpdateBuffer<?>> buffers = this.view2Buffers.get(relationView); | ||
61 | for (var buffer : buffers) { | ||
62 | if (buffer.getUpdateListener().equals(relationView, seed, listener)) { | ||
63 | // remove buffer and terminate immediately, or it will break iterator. | ||
64 | buffers.remove(buffer); | ||
65 | return; | ||
66 | } | ||
67 | } | ||
68 | } else { | ||
69 | throw new IllegalArgumentException("Relation view is not registered for updates"); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | public <D> void addUpdate(Relation<D> relation, Tuple key, D oldValue, D newValue) { | ||
74 | var views = this.relation2View.get(relation); | ||
75 | if (views == null) { | ||
76 | return; | ||
77 | } | ||
78 | for (var view : views) { | ||
79 | var buffers = this.view2Buffers.get(view); | ||
80 | for (var buffer : buffers) { | ||
81 | @SuppressWarnings("unchecked") | ||
82 | var typedBuffer = (ViewUpdateBuffer<D>) buffer; | ||
83 | typedBuffer.addChange(key, oldValue, newValue); | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | |||
88 | public boolean hasChanges() { | ||
89 | for (var bufferCollection : this.view2Buffers.values()) { | ||
90 | for (ViewUpdateBuffer<?> buffer : bufferCollection) { | ||
91 | if (buffer.hasChanges()) | ||
92 | return true; | ||
93 | } | ||
94 | } | ||
95 | return false; | ||
96 | } | ||
97 | |||
98 | public void flush() { | ||
99 | for (var bufferCollection : this.view2Buffers.values()) { | ||
100 | for (ViewUpdateBuffer<?> buffer : bufferCollection) { | ||
101 | buffer.flush(); | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java new file mode 100644 index 00000000..c727f046 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java | |||
@@ -0,0 +1,34 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.Objects; | ||
5 | |||
6 | record ViewUpdate (Object[] tuple, boolean isInsertion) { | ||
7 | |||
8 | @Override | ||
9 | public int hashCode() { | ||
10 | final int prime = 31; | ||
11 | int result = 1; | ||
12 | result = prime * result + Arrays.deepHashCode(tuple); | ||
13 | result = prime * result + Objects.hash(isInsertion); | ||
14 | return result; | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | public boolean equals(Object obj) { | ||
19 | if (this == obj) | ||
20 | return true; | ||
21 | if (obj == null) | ||
22 | return false; | ||
23 | if (getClass() != obj.getClass()) | ||
24 | return false; | ||
25 | ViewUpdate other = (ViewUpdate) obj; | ||
26 | return isInsertion == other.isInsertion && Arrays.deepEquals(tuple, other.tuple); | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public String toString() { | ||
31 | return "ViewUpdate [" + Arrays.toString(tuple) + "insertion= "+this.isInsertion+"]"; | ||
32 | } | ||
33 | |||
34 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java new file mode 100644 index 00000000..5a4243f2 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java | |||
@@ -0,0 +1,46 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import tools.refinery.store.model.Tuple; | ||
4 | |||
5 | import java.util.ArrayList; | ||
6 | import java.util.Arrays; | ||
7 | import java.util.List; | ||
8 | |||
9 | public class ViewUpdateBuffer<D> { | ||
10 | protected final ViewUpdateTranslator<D> updateListener; | ||
11 | protected final List<ViewUpdate> buffer = new ArrayList<>(); | ||
12 | |||
13 | public ViewUpdateBuffer(ViewUpdateTranslator<D> updateListener) { | ||
14 | this.updateListener = updateListener; | ||
15 | } | ||
16 | |||
17 | public ViewUpdateTranslator<D> getUpdateListener() { | ||
18 | return updateListener; | ||
19 | } | ||
20 | |||
21 | public boolean hasChanges() { | ||
22 | return !buffer.isEmpty(); | ||
23 | } | ||
24 | |||
25 | public void addChange(Tuple tuple, D oldValue, D newValue) { | ||
26 | if (oldValue != newValue) { | ||
27 | Object[] oldTuple = updateListener.isMatching(tuple, oldValue); | ||
28 | Object[] newTuple = updateListener.isMatching(tuple, newValue); | ||
29 | if (!Arrays.equals(oldTuple, newTuple)) { | ||
30 | if (oldTuple != null) { | ||
31 | buffer.add(new ViewUpdate(oldTuple, false)); | ||
32 | } | ||
33 | if (newTuple != null) { | ||
34 | buffer.add(new ViewUpdate(newTuple, true)); | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | |||
40 | public void flush() { | ||
41 | for (ViewUpdate viewChange : buffer) { | ||
42 | updateListener.processChange(viewChange); | ||
43 | } | ||
44 | buffer.clear(); | ||
45 | } | ||
46 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java new file mode 100644 index 00000000..2f7f9a9c --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java | |||
@@ -0,0 +1,62 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
6 | import tools.refinery.store.model.Tuple; | ||
7 | import tools.refinery.store.query.view.RelationView; | ||
8 | |||
9 | import java.util.Objects; | ||
10 | |||
11 | public class ViewUpdateTranslator<D> { | ||
12 | private final RelationView<D> key; | ||
13 | |||
14 | private final ITuple filter; | ||
15 | |||
16 | private final IQueryRuntimeContextListener listener; | ||
17 | |||
18 | public ViewUpdateTranslator(RelationView<D> key, ITuple filter, IQueryRuntimeContextListener listener) { | ||
19 | super(); | ||
20 | this.key = key; | ||
21 | this.filter = filter; | ||
22 | this.listener = listener; | ||
23 | } | ||
24 | |||
25 | public boolean equals(RelationView<?> relationView, ITuple seed, IQueryRuntimeContextListener listener) { | ||
26 | return key == relationView && filter.equals(seed) && this.listener == listener; | ||
27 | } | ||
28 | |||
29 | public void processChange(ViewUpdate change) { | ||
30 | listener.update(key, Tuples.flatTupleOf(change.tuple()), change.isInsertion()); | ||
31 | } | ||
32 | |||
33 | public Object[] isMatching(Tuple tuple, D value) { | ||
34 | return isMatching(key.getWrappedKey().transform(tuple, value), filter); | ||
35 | } | ||
36 | |||
37 | @SuppressWarnings("squid:S1168") | ||
38 | private Object[] isMatching(Object[] tuple, ITuple filter) { | ||
39 | for (int i = 0; i < filter.getSize(); i++) { | ||
40 | final Object filterObject = filter.get(i); | ||
41 | if (filterObject != null && !filterObject.equals(tuple[i])) { | ||
42 | return null; | ||
43 | } | ||
44 | } | ||
45 | return tuple; | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public int hashCode() { | ||
50 | return Objects.hash(filter, key, listener); | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public boolean equals(Object obj) { | ||
55 | if (this == obj) | ||
56 | return true; | ||
57 | if (!(obj instanceof ViewUpdateTranslator<?> other)) | ||
58 | return false; | ||
59 | return Objects.equals(filter, other.filter) && Objects.equals(key, other.key) | ||
60 | && Objects.equals(listener, other.listener); | ||
61 | } | ||
62 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTest.java new file mode 100644 index 00000000..4307ab6b --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTest.java | |||
@@ -0,0 +1,432 @@ | |||
1 | package tools.refinery.store.query.viatra.tests; | ||
2 | |||
3 | import org.junit.jupiter.api.Test; | ||
4 | import tools.refinery.store.model.Tuple; | ||
5 | import tools.refinery.store.model.representation.Relation; | ||
6 | import tools.refinery.store.model.representation.TruthValue; | ||
7 | import tools.refinery.store.query.QueryableModel; | ||
8 | import tools.refinery.store.query.QueryableModelStore; | ||
9 | import tools.refinery.store.query.building.*; | ||
10 | import tools.refinery.store.query.viatra.ViatraQueryableModelStore; | ||
11 | import tools.refinery.store.query.view.FilteredRelationView; | ||
12 | import tools.refinery.store.query.view.KeyOnlyRelationView; | ||
13 | import tools.refinery.store.query.view.RelationView; | ||
14 | |||
15 | import java.util.*; | ||
16 | import java.util.stream.Stream; | ||
17 | |||
18 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
19 | |||
20 | class QueryTest { | ||
21 | @Test | ||
22 | void typeConstraintTest() { | ||
23 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
24 | Relation<Boolean> asset = new Relation<>("Asset", 1, false); | ||
25 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
26 | |||
27 | List<Variable> parameters = List.of(new Variable("p1")); | ||
28 | RelationAtom personRelationAtom = new RelationAtom(personView, parameters); | ||
29 | DNFAnd clause = new DNFAnd(Collections.emptySet(), List.of(personRelationAtom)); | ||
30 | DNFPredicate predicate = new DNFPredicate("TypeConstraint", parameters, List.of(clause)); | ||
31 | |||
32 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, asset), Set.of(personView), | ||
33 | Set.of(predicate)); | ||
34 | QueryableModel model = store.createModel(); | ||
35 | |||
36 | model.put(person, Tuple.of(0), true); | ||
37 | model.put(person, Tuple.of(1), true); | ||
38 | model.put(asset, Tuple.of(1), true); | ||
39 | model.put(asset, Tuple.of(2), true); | ||
40 | |||
41 | model.flushChanges(); | ||
42 | assertEquals(2, model.countResults(predicate)); | ||
43 | compareMatchSets(model.allResults(predicate), Set.of(List.of(Tuple.of(0)), List.of(Tuple.of(1)))); | ||
44 | } | ||
45 | |||
46 | @Test | ||
47 | void relationConstraintTest() { | ||
48 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
49 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
50 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
51 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
52 | |||
53 | Variable p1 = new Variable("p1"); | ||
54 | Variable p2 = new Variable("p2"); | ||
55 | List<Variable> parameters = Arrays.asList(p1, p2); | ||
56 | |||
57 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
58 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
59 | RelationAtom friendRelationAtom = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
60 | DNFAnd clause = new DNFAnd(Collections.emptySet(), | ||
61 | Arrays.asList(personRelationAtom1, personRelationAtom2, friendRelationAtom)); | ||
62 | DNFPredicate predicate = new DNFPredicate("RelationConstraint", parameters, List.of(clause)); | ||
63 | |||
64 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), | ||
65 | Set.of(personView, friendMustView), Set.of(predicate)); | ||
66 | QueryableModel model = store.createModel(); | ||
67 | |||
68 | assertEquals(0, model.countResults(predicate)); | ||
69 | |||
70 | model.put(person, Tuple.of(0), true); | ||
71 | model.put(person, Tuple.of(1), true); | ||
72 | model.put(person, Tuple.of(2), true); | ||
73 | model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); | ||
74 | model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); | ||
75 | model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); | ||
76 | |||
77 | assertEquals(0, model.countResults(predicate)); | ||
78 | |||
79 | model.flushChanges(); | ||
80 | assertEquals(3, model.countResults(predicate)); | ||
81 | compareMatchSets(model.allResults(predicate), Set.of(List.of(Tuple.of(0), Tuple.of(1)), | ||
82 | List.of(Tuple.of(1), Tuple.of(0)), List.of(Tuple.of(1), Tuple.of(2)))); | ||
83 | } | ||
84 | |||
85 | @Test | ||
86 | void andTest() { | ||
87 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
88 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
89 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
90 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
91 | |||
92 | Variable p1 = new Variable("p1"); | ||
93 | Variable p2 = new Variable("p2"); | ||
94 | List<Variable> parameters = Arrays.asList(p1, p2); | ||
95 | |||
96 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
97 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
98 | RelationAtom friendRelationAtom1 = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
99 | RelationAtom friendRelationAtom2 = new RelationAtom(friendMustView, Arrays.asList(p2, p1)); | ||
100 | DNFAnd clause = new DNFAnd(Collections.emptySet(), | ||
101 | Arrays.asList(personRelationAtom1, personRelationAtom2, friendRelationAtom1, friendRelationAtom2)); | ||
102 | DNFPredicate predicate = new DNFPredicate("RelationConstraint", parameters, List.of(clause)); | ||
103 | |||
104 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), | ||
105 | Set.of(personView, friendMustView), Set.of(predicate)); | ||
106 | QueryableModel model = store.createModel(); | ||
107 | |||
108 | assertEquals(0, model.countResults(predicate)); | ||
109 | |||
110 | model.put(person, Tuple.of(0), true); | ||
111 | model.put(person, Tuple.of(1), true); | ||
112 | model.put(person, Tuple.of(2), true); | ||
113 | |||
114 | model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); | ||
115 | model.put(friend, Tuple.of(0, 2), TruthValue.TRUE); | ||
116 | |||
117 | model.flushChanges(); | ||
118 | assertEquals(0, model.countResults(predicate)); | ||
119 | |||
120 | model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); | ||
121 | model.flushChanges(); | ||
122 | assertEquals(2, model.countResults(predicate)); | ||
123 | compareMatchSets(model.allResults(predicate), | ||
124 | Set.of(List.of(Tuple.of(0), Tuple.of(1)), List.of(Tuple.of(1), Tuple.of(0)))); | ||
125 | |||
126 | model.put(friend, Tuple.of(2, 0), TruthValue.TRUE); | ||
127 | model.flushChanges(); | ||
128 | assertEquals(4, model.countResults(predicate)); | ||
129 | compareMatchSets(model.allResults(predicate), | ||
130 | Set.of(List.of(Tuple.of(0), Tuple.of(1)), List.of(Tuple.of(1), Tuple.of(0)), | ||
131 | List.of(Tuple.of(0), Tuple.of(2)), List.of(Tuple.of(2), Tuple.of(0)))); | ||
132 | } | ||
133 | |||
134 | @Test | ||
135 | void existTest() { | ||
136 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
137 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
138 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
139 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
140 | |||
141 | Variable p1 = new Variable("p1"); | ||
142 | Variable p2 = new Variable("p2"); | ||
143 | List<Variable> parameters = List.of(p1); | ||
144 | |||
145 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
146 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
147 | RelationAtom friendRelationAtom = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
148 | DNFAnd clause = new DNFAnd(Set.of(p2), | ||
149 | Arrays.asList(personRelationAtom1, personRelationAtom2, friendRelationAtom)); | ||
150 | DNFPredicate predicate = new DNFPredicate("RelationConstraint", parameters, List.of(clause)); | ||
151 | |||
152 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), | ||
153 | Set.of(personView, friendMustView), Set.of(predicate)); | ||
154 | QueryableModel model = store.createModel(); | ||
155 | |||
156 | assertEquals(0, model.countResults(predicate)); | ||
157 | |||
158 | model.put(person, Tuple.of(0), true); | ||
159 | model.put(person, Tuple.of(1), true); | ||
160 | model.put(person, Tuple.of(2), true); | ||
161 | model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); | ||
162 | model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); | ||
163 | model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); | ||
164 | |||
165 | assertEquals(0, model.countResults(predicate)); | ||
166 | |||
167 | model.flushChanges(); | ||
168 | assertEquals(2, model.countResults(predicate)); | ||
169 | compareMatchSets(model.allResults(predicate), Set.of(List.of(Tuple.of(0)), List.of(Tuple.of(1)))); | ||
170 | } | ||
171 | |||
172 | @Test | ||
173 | void orTest() { | ||
174 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
175 | Relation<Boolean> animal = new Relation<>("Animal", 1, false); | ||
176 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
177 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
178 | RelationView<Boolean> animalView = new KeyOnlyRelationView(animal); | ||
179 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
180 | |||
181 | Variable p1 = new Variable("p1"); | ||
182 | Variable p2 = new Variable("p2"); | ||
183 | List<Variable> parameters = Arrays.asList(p1, p2); | ||
184 | |||
185 | // Person-Person friendship | ||
186 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
187 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
188 | RelationAtom friendRelationAtom1 = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
189 | DNFAnd clause1 = new DNFAnd(Collections.emptySet(), | ||
190 | Arrays.asList(personRelationAtom1, personRelationAtom2, friendRelationAtom1)); | ||
191 | |||
192 | // Animal-Animal friendship | ||
193 | RelationAtom animalRelationAtom1 = new RelationAtom(animalView, List.of(p1)); | ||
194 | RelationAtom animalRelationAtom2 = new RelationAtom(animalView, List.of(p2)); | ||
195 | RelationAtom friendRelationAtom2 = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
196 | DNFAnd clause2 = new DNFAnd(Collections.emptySet(), | ||
197 | Arrays.asList(animalRelationAtom1, animalRelationAtom2, friendRelationAtom2)); | ||
198 | |||
199 | // No inter-species friendship | ||
200 | |||
201 | DNFPredicate predicate = new DNFPredicate("Or", parameters, Arrays.asList(clause1, clause2)); | ||
202 | |||
203 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, animal, friend), | ||
204 | Set.of(personView, animalView, friendMustView), Set.of(predicate)); | ||
205 | QueryableModel model = store.createModel(); | ||
206 | |||
207 | model.put(person, Tuple.of(0), true); | ||
208 | model.put(person, Tuple.of(1), true); | ||
209 | model.put(animal, Tuple.of(2), true); | ||
210 | model.put(animal, Tuple.of(3), true); | ||
211 | model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); | ||
212 | model.put(friend, Tuple.of(0, 2), TruthValue.TRUE); | ||
213 | model.put(friend, Tuple.of(2, 3), TruthValue.TRUE); | ||
214 | model.put(friend, Tuple.of(3, 0), TruthValue.TRUE); | ||
215 | |||
216 | model.flushChanges(); | ||
217 | assertEquals(2, model.countResults(predicate)); | ||
218 | compareMatchSets(model.allResults(predicate), | ||
219 | Set.of(List.of(Tuple.of(0), Tuple.of(1)), List.of(Tuple.of(2), Tuple.of(3)))); | ||
220 | } | ||
221 | |||
222 | @Test | ||
223 | void equalityTest() { | ||
224 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
225 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
226 | |||
227 | Variable p1 = new Variable("p1"); | ||
228 | Variable p2 = new Variable("p2"); | ||
229 | List<Variable> parameters = Arrays.asList(p1, p2); | ||
230 | |||
231 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
232 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
233 | EquivalenceAtom equivalenceAtom = new EquivalenceAtom(true, p1, p2); | ||
234 | DNFAnd clause = new DNFAnd(Collections.emptySet(), | ||
235 | Arrays.asList(personRelationAtom1, personRelationAtom2, equivalenceAtom)); | ||
236 | DNFPredicate predicate = new DNFPredicate("Equality", parameters, List.of(clause)); | ||
237 | |||
238 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person), Set.of(personView), Set.of(predicate)); | ||
239 | QueryableModel model = store.createModel(); | ||
240 | |||
241 | model.put(person, Tuple.of(0), true); | ||
242 | model.put(person, Tuple.of(1), true); | ||
243 | model.put(person, Tuple.of(2), true); | ||
244 | |||
245 | model.flushChanges(); | ||
246 | assertEquals(3, model.countResults(predicate)); | ||
247 | compareMatchSets(model.allResults(predicate), Set.of(List.of(Tuple.of(0), Tuple.of(0)), | ||
248 | List.of(Tuple.of(1), Tuple.of(1)), List.of(Tuple.of(2), Tuple.of(2)))); | ||
249 | } | ||
250 | |||
251 | @Test | ||
252 | void inequalityTest() { | ||
253 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
254 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
255 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
256 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
257 | |||
258 | Variable p1 = new Variable("p1"); | ||
259 | Variable p2 = new Variable("p2"); | ||
260 | Variable p3 = new Variable("p3"); | ||
261 | List<Variable> parameters = Arrays.asList(p1, p2, p3); | ||
262 | |||
263 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
264 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
265 | RelationAtom friendRelationAtom1 = new RelationAtom(friendMustView, Arrays.asList(p1, p3)); | ||
266 | RelationAtom friendRelationAtom2 = new RelationAtom(friendMustView, Arrays.asList(p2, p3)); | ||
267 | EquivalenceAtom inequivalenceAtom = new EquivalenceAtom(false, p1, p2); | ||
268 | DNFAnd clause = new DNFAnd(Collections.emptySet(), Arrays.asList(personRelationAtom1, personRelationAtom2, | ||
269 | friendRelationAtom1, friendRelationAtom2, inequivalenceAtom)); | ||
270 | DNFPredicate predicate = new DNFPredicate("Inequality", parameters, List.of(clause)); | ||
271 | |||
272 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), | ||
273 | Set.of(personView, friendMustView), Set.of(predicate)); | ||
274 | QueryableModel model = store.createModel(); | ||
275 | |||
276 | model.put(person, Tuple.of(0), true); | ||
277 | model.put(person, Tuple.of(1), true); | ||
278 | model.put(person, Tuple.of(2), true); | ||
279 | model.put(friend, Tuple.of(0, 2), TruthValue.TRUE); | ||
280 | model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); | ||
281 | |||
282 | model.flushChanges(); | ||
283 | assertEquals(2, model.countResults(predicate)); | ||
284 | compareMatchSets(model.allResults(predicate), | ||
285 | Set.of(List.of(Tuple.of(0), Tuple.of(1), Tuple.of(2)), List.of(Tuple.of(1), Tuple.of(0), Tuple.of(2)))); | ||
286 | } | ||
287 | |||
288 | @Test | ||
289 | void patternCallTest() { | ||
290 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
291 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
292 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
293 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
294 | |||
295 | Variable p1 = new Variable("p1"); | ||
296 | Variable p2 = new Variable("p2"); | ||
297 | List<Variable> parameters = Arrays.asList(p1, p2); | ||
298 | |||
299 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
300 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
301 | RelationAtom friendRelationAtom = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
302 | DNFAnd clause = new DNFAnd(Collections.emptySet(), | ||
303 | Arrays.asList(personRelationAtom1, personRelationAtom2, friendRelationAtom)); | ||
304 | DNFPredicate friendPredicate = new DNFPredicate("RelationConstraint", parameters, List.of(clause)); | ||
305 | |||
306 | Variable p3 = new Variable("p3"); | ||
307 | Variable p4 = new Variable("p4"); | ||
308 | List<Variable> substitution = Arrays.asList(p3, p4); | ||
309 | RelationAtom personRelationAtom3 = new RelationAtom(personView, List.of(p3)); | ||
310 | RelationAtom personRelationAtom4 = new RelationAtom(personView, List.of(p4)); | ||
311 | PredicateAtom friendPredicateAtom = new PredicateAtom(true, false, friendPredicate, substitution); | ||
312 | DNFAnd patternCallClause = new DNFAnd(Collections.emptySet(), | ||
313 | Arrays.asList(personRelationAtom3, personRelationAtom4, friendPredicateAtom)); | ||
314 | DNFPredicate predicate = new DNFPredicate("PatternCall", substitution, List.of(patternCallClause)); | ||
315 | |||
316 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), | ||
317 | Set.of(personView, friendMustView), Set.of(friendPredicate, predicate)); | ||
318 | QueryableModel model = store.createModel(); | ||
319 | |||
320 | model.put(person, Tuple.of(0), true); | ||
321 | model.put(person, Tuple.of(1), true); | ||
322 | model.put(person, Tuple.of(2), true); | ||
323 | model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); | ||
324 | model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); | ||
325 | model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); | ||
326 | |||
327 | model.flushChanges(); | ||
328 | |||
329 | assertEquals(3, model.countResults(friendPredicate)); | ||
330 | } | ||
331 | |||
332 | @Test | ||
333 | void negativePatternCallTest() { | ||
334 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
335 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
336 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
337 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
338 | |||
339 | Variable p1 = new Variable("p1"); | ||
340 | Variable p2 = new Variable("p2"); | ||
341 | List<Variable> parameters = Arrays.asList(p1, p2); | ||
342 | |||
343 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
344 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
345 | RelationAtom friendRelationAtom = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
346 | DNFAnd clause = new DNFAnd(Collections.emptySet(), | ||
347 | Arrays.asList(personRelationAtom1, personRelationAtom2, friendRelationAtom)); | ||
348 | DNFPredicate friendPredicate = new DNFPredicate("RelationConstraint", parameters, List.of(clause)); | ||
349 | |||
350 | Variable p3 = new Variable("p3"); | ||
351 | Variable p4 = new Variable("p4"); | ||
352 | List<Variable> substitution = Arrays.asList(p3, p4); | ||
353 | RelationAtom personRelationAtom3 = new RelationAtom(personView, List.of(p3)); | ||
354 | RelationAtom personRelationAtom4 = new RelationAtom(personView, List.of(p4)); | ||
355 | PredicateAtom friendPredicateAtom = new PredicateAtom(false, false, friendPredicate, substitution); | ||
356 | DNFAnd negativePatternCallClause = new DNFAnd(Collections.emptySet(), | ||
357 | Arrays.asList(personRelationAtom3, personRelationAtom4, friendPredicateAtom)); | ||
358 | DNFPredicate predicate = new DNFPredicate("NegativePatternCall", substitution, | ||
359 | List.of(negativePatternCallClause)); | ||
360 | |||
361 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), | ||
362 | Set.of(personView, friendMustView), Set.of(friendPredicate, predicate)); | ||
363 | QueryableModel model = store.createModel(); | ||
364 | |||
365 | model.put(person, Tuple.of(0), true); | ||
366 | model.put(person, Tuple.of(1), true); | ||
367 | model.put(person, Tuple.of(2), true); | ||
368 | model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); | ||
369 | model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); | ||
370 | model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); | ||
371 | |||
372 | model.flushChanges(); | ||
373 | assertEquals(6, model.countResults(predicate)); | ||
374 | } | ||
375 | |||
376 | @Test | ||
377 | void transitivePatternCallTest() { | ||
378 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
379 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.FALSE); | ||
380 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
381 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
382 | |||
383 | Variable p1 = new Variable("p1"); | ||
384 | Variable p2 = new Variable("p2"); | ||
385 | List<Variable> parameters = Arrays.asList(p1, p2); | ||
386 | |||
387 | RelationAtom personRelationAtom1 = new RelationAtom(personView, List.of(p1)); | ||
388 | RelationAtom personRelationAtom2 = new RelationAtom(personView, List.of(p2)); | ||
389 | RelationAtom friendRelationAtom = new RelationAtom(friendMustView, Arrays.asList(p1, p2)); | ||
390 | DNFAnd clause = new DNFAnd(Collections.emptySet(), | ||
391 | Arrays.asList(personRelationAtom1, personRelationAtom2, friendRelationAtom)); | ||
392 | DNFPredicate friendPredicate = new DNFPredicate("RelationConstraint", parameters, List.of(clause)); | ||
393 | |||
394 | Variable p3 = new Variable("p3"); | ||
395 | Variable p4 = new Variable("p4"); | ||
396 | List<Variable> substitution = Arrays.asList(p3, p4); | ||
397 | RelationAtom personRelationAtom3 = new RelationAtom(personView, List.of(p3)); | ||
398 | RelationAtom personRelationAtom4 = new RelationAtom(personView, List.of(p4)); | ||
399 | PredicateAtom friendPredicateAtom = new PredicateAtom(true, true, friendPredicate, substitution); | ||
400 | DNFAnd patternCallClause = new DNFAnd(Collections.emptySet(), | ||
401 | Arrays.asList(personRelationAtom3, personRelationAtom4, friendPredicateAtom)); | ||
402 | DNFPredicate predicate = new DNFPredicate("TransitivePatternCall", substitution, | ||
403 | List.of(patternCallClause)); | ||
404 | |||
405 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), | ||
406 | Set.of(personView, friendMustView), Set.of(friendPredicate, predicate)); | ||
407 | QueryableModel model = store.createModel(); | ||
408 | |||
409 | model.put(person, Tuple.of(0), true); | ||
410 | model.put(person, Tuple.of(1), true); | ||
411 | model.put(person, Tuple.of(2), true); | ||
412 | model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); | ||
413 | model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); | ||
414 | |||
415 | model.flushChanges(); | ||
416 | assertEquals(3, model.countResults(predicate)); | ||
417 | } | ||
418 | static void compareMatchSets(Stream<Object[]> matchSet, Set<List<Tuple>> expected) { | ||
419 | Set<List<Tuple>> translatedMatchSet = new HashSet<>(); | ||
420 | var iterator = matchSet.iterator(); | ||
421 | while (iterator.hasNext()) { | ||
422 | var element = iterator.next(); | ||
423 | List<Tuple> elementToTranslatedMatchSet = new ArrayList<>(); | ||
424 | for (Object o : element) { | ||
425 | elementToTranslatedMatchSet.add((Tuple) o); | ||
426 | } | ||
427 | translatedMatchSet.add(elementToTranslatedMatchSet); | ||
428 | } | ||
429 | |||
430 | assertEquals(expected, translatedMatchSet); | ||
431 | } | ||
432 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTransactionTest.java new file mode 100644 index 00000000..613d0074 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTransactionTest.java | |||
@@ -0,0 +1,56 @@ | |||
1 | package tools.refinery.store.query.viatra.tests; | ||
2 | |||
3 | import org.junit.jupiter.api.Test; | ||
4 | import tools.refinery.store.model.Tuple; | ||
5 | import tools.refinery.store.model.representation.Relation; | ||
6 | import tools.refinery.store.query.QueryableModel; | ||
7 | import tools.refinery.store.query.QueryableModelStore; | ||
8 | import tools.refinery.store.query.building.DNFAnd; | ||
9 | import tools.refinery.store.query.building.DNFPredicate; | ||
10 | import tools.refinery.store.query.building.RelationAtom; | ||
11 | import tools.refinery.store.query.building.Variable; | ||
12 | import tools.refinery.store.query.viatra.ViatraQueryableModelStore; | ||
13 | import tools.refinery.store.query.view.KeyOnlyRelationView; | ||
14 | import tools.refinery.store.query.view.RelationView; | ||
15 | |||
16 | import java.util.Collections; | ||
17 | import java.util.List; | ||
18 | import java.util.Set; | ||
19 | |||
20 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
21 | |||
22 | class QueryTransactionTest { | ||
23 | @Test | ||
24 | void flushTest() { | ||
25 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
26 | Relation<Boolean> asset = new Relation<>("Asset", 1, false); | ||
27 | RelationView<Boolean> personView = new KeyOnlyRelationView(person); | ||
28 | |||
29 | List<Variable> parameters = List.of(new Variable("p1")); | ||
30 | RelationAtom personRelationAtom = new RelationAtom(personView, parameters); | ||
31 | DNFAnd clause = new DNFAnd(Collections.emptySet(), List.of(personRelationAtom)); | ||
32 | DNFPredicate predicate = new DNFPredicate("TypeConstraint", parameters, List.of(clause)); | ||
33 | |||
34 | QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, asset), Set.of(personView), | ||
35 | Set.of(predicate)); | ||
36 | QueryableModel model = store.createModel(); | ||
37 | |||
38 | assertEquals(0, model.countResults(predicate)); | ||
39 | |||
40 | model.put(person, Tuple.of(0), true); | ||
41 | model.put(person, Tuple.of(1), true); | ||
42 | model.put(asset, Tuple.of(1), true); | ||
43 | model.put(asset, Tuple.of(2), true); | ||
44 | |||
45 | assertEquals(0, model.countResults(predicate)); | ||
46 | |||
47 | model.flushChanges(); | ||
48 | assertEquals(2, model.countResults(predicate)); | ||
49 | |||
50 | model.put(person, Tuple.of(4), true); | ||
51 | assertEquals(2, model.countResults(predicate)); | ||
52 | |||
53 | model.flushChanges(); | ||
54 | assertEquals(3, model.countResults(predicate)); | ||
55 | } | ||
56 | } | ||