diff options
Diffstat (limited to 'subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java')
-rw-r--r-- | subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java | 949 |
1 files changed, 949 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java new file mode 100644 index 00000000..5df3a971 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java | |||
@@ -0,0 +1,949 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-v20.html. | ||
7 | * | ||
8 | * SPDX-License-Identifier: EPL-2.0 | ||
9 | *******************************************************************************/ | ||
10 | package tools.refinery.viatra.runtime.rete.construction.plancompiler; | ||
11 | |||
12 | import org.apache.log4j.Logger; | ||
13 | import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; | ||
14 | import tools.refinery.viatra.runtime.matchers.backend.CommonQueryHintOptions; | ||
15 | import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; | ||
16 | import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; | ||
17 | import tools.refinery.viatra.runtime.matchers.context.IInputKey; | ||
18 | import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; | ||
19 | import tools.refinery.viatra.runtime.matchers.context.IQueryCacheContext; | ||
20 | import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; | ||
21 | import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; | ||
22 | import tools.refinery.viatra.runtime.matchers.planning.SubPlan; | ||
23 | import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper; | ||
24 | import tools.refinery.viatra.runtime.matchers.planning.operations.*; | ||
25 | import tools.refinery.viatra.runtime.matchers.psystem.*; | ||
26 | import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
27 | import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; | ||
28 | import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*; | ||
29 | import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*; | ||
30 | import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; | ||
31 | import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; | ||
32 | import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; | ||
33 | import tools.refinery.viatra.runtime.matchers.psystem.rewriters.*; | ||
34 | import tools.refinery.viatra.runtime.matchers.tuple.Tuple; | ||
35 | import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; | ||
36 | import tools.refinery.viatra.runtime.matchers.tuple.Tuples; | ||
37 | import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; | ||
38 | import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; | ||
39 | import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; | ||
40 | import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.JoinHelper; | ||
41 | import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.PosetTriplet; | ||
42 | import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; | ||
43 | import tools.refinery.viatra.runtime.rete.recipes.*; | ||
44 | import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper; | ||
45 | import tools.refinery.viatra.runtime.rete.traceability.*; | ||
46 | import tools.refinery.viatra.runtime.rete.util.ReteHintOptions; | ||
47 | |||
48 | import java.util.*; | ||
49 | import java.util.Map.Entry; | ||
50 | import java.util.function.BiFunction; | ||
51 | import java.util.function.Function; | ||
52 | |||
53 | /** | ||
54 | * Compiles queries and query plans into Rete recipes, traced by respectively a {@link CompiledQuery} or | ||
55 | * {@link CompiledSubPlan}. | ||
56 | * | ||
57 | * @author Bergmann Gabor | ||
58 | * | ||
59 | */ | ||
60 | public class ReteRecipeCompiler { | ||
61 | |||
62 | private final IQueryPlannerStrategy plannerStrategy; | ||
63 | private final IQueryMetaContext metaContext; | ||
64 | private final IQueryBackendHintProvider hintProvider; | ||
65 | private final PDisjunctionRewriter normalizer; | ||
66 | private final QueryAnalyzer queryAnalyzer; | ||
67 | private final Logger logger; | ||
68 | |||
69 | /** | ||
70 | * @since 2.2 | ||
71 | */ | ||
72 | protected final boolean deleteAndRederiveEvaluation; | ||
73 | /** | ||
74 | * @since 2.4 | ||
75 | */ | ||
76 | protected final TimelyConfiguration timelyEvaluation; | ||
77 | |||
78 | /** | ||
79 | * @since 1.5 | ||
80 | */ | ||
81 | public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, | ||
82 | IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer) { | ||
83 | this(plannerStrategy, logger, metaContext, queryCacheContext, hintProvider, queryAnalyzer, false, null); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * @since 2.4 | ||
88 | */ | ||
89 | public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, | ||
90 | IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer, | ||
91 | boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { | ||
92 | super(); | ||
93 | this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation; | ||
94 | this.timelyEvaluation = timelyEvaluation; | ||
95 | this.plannerStrategy = plannerStrategy; | ||
96 | this.logger = logger; | ||
97 | this.metaContext = metaContext; | ||
98 | this.queryAnalyzer = queryAnalyzer; | ||
99 | this.normalizer = new PDisjunctionRewriterCacher(new SurrogateQueryRewriter(), | ||
100 | new PBodyNormalizer(metaContext) { | ||
101 | |||
102 | @Override | ||
103 | protected boolean shouldExpandWeakenedAlternatives(PQuery query) { | ||
104 | QueryEvaluationHint hint = ReteRecipeCompiler.this.hintProvider.getQueryEvaluationHint(query); | ||
105 | Boolean expandWeakenedAlternativeConstraints = ReteHintOptions.expandWeakenedAlternativeConstraints | ||
106 | .getValueOrDefault(hint); | ||
107 | return expandWeakenedAlternativeConstraints; | ||
108 | } | ||
109 | |||
110 | }); | ||
111 | this.hintProvider = hintProvider; | ||
112 | } | ||
113 | |||
114 | static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE; | ||
115 | |||
116 | // INTERNALLY CACHED | ||
117 | private Map<PBody, SubPlan> plannerCache = new HashMap<PBody, SubPlan>(); | ||
118 | private Set<PBody> planningInProgress = new HashSet<PBody>(); | ||
119 | |||
120 | private Map<PQuery, CompiledQuery> queryCompilerCache = new HashMap<PQuery, CompiledQuery>(); | ||
121 | private Set<PQuery> compilationInProgress = new HashSet<PQuery>(); | ||
122 | private IMultiLookup<PQuery, RecursionCutoffPoint> recursionCutoffPoints = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); | ||
123 | private Map<SubPlan, CompiledSubPlan> subPlanCompilerCache = new HashMap<SubPlan, CompiledSubPlan>(); | ||
124 | private Map<ReteNodeRecipe, SubPlan> compilerBackTrace = new HashMap<ReteNodeRecipe, SubPlan>(); | ||
125 | |||
126 | /** | ||
127 | * Clears internal state | ||
128 | */ | ||
129 | public void reset() { | ||
130 | plannerCache.clear(); | ||
131 | planningInProgress.clear(); | ||
132 | queryCompilerCache.clear(); | ||
133 | subPlanCompilerCache.clear(); | ||
134 | compilerBackTrace.clear(); | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * Returns a {@link CompiledQuery} compiled from a query | ||
139 | * @throws ViatraQueryRuntimeException | ||
140 | */ | ||
141 | public CompiledQuery getCompiledForm(PQuery query) { | ||
142 | CompiledQuery compiled = queryCompilerCache.get(query); | ||
143 | if (compiled == null) { | ||
144 | |||
145 | IRewriterTraceCollector traceCollector = CommonQueryHintOptions.normalizationTraceCollector | ||
146 | .getValueOrDefault(hintProvider.getQueryEvaluationHint(query)); | ||
147 | if (traceCollector != null) { | ||
148 | traceCollector.addTrace(query, query); | ||
149 | } | ||
150 | |||
151 | boolean reentrant = !compilationInProgress.add(query); | ||
152 | if (reentrant) { // oops, recursion into body in progress | ||
153 | RecursionCutoffPoint cutoffPoint = new RecursionCutoffPoint(query, getHints(query), metaContext, | ||
154 | deleteAndRederiveEvaluation, timelyEvaluation); | ||
155 | recursionCutoffPoints.addPair(query, cutoffPoint); | ||
156 | return cutoffPoint.getCompiledQuery(); | ||
157 | } else { // not reentrant, therefore no recursion, do the compilation | ||
158 | try { | ||
159 | compiled = compileProduction(query); | ||
160 | queryCompilerCache.put(query, compiled); | ||
161 | // backTrace.put(compiled.getRecipe(), plan); | ||
162 | |||
163 | // if this was a recursive query, mend all points where recursion was cut off | ||
164 | for (RecursionCutoffPoint cutoffPoint : recursionCutoffPoints.lookupOrEmpty(query)) { | ||
165 | cutoffPoint.mend(compiled); | ||
166 | } | ||
167 | } finally { | ||
168 | compilationInProgress.remove(query); | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | return compiled; | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * Returns a {@link CompiledSubPlan} compiled from a query plan | ||
177 | * @throws ViatraQueryRuntimeException | ||
178 | */ | ||
179 | public CompiledSubPlan getCompiledForm(SubPlan plan) { | ||
180 | CompiledSubPlan compiled = subPlanCompilerCache.get(plan); | ||
181 | if (compiled == null) { | ||
182 | compiled = doCompileDispatch(plan); | ||
183 | subPlanCompilerCache.put(plan, compiled); | ||
184 | compilerBackTrace.put(compiled.getRecipe(), plan); | ||
185 | } | ||
186 | return compiled; | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * @throws ViatraQueryRuntimeException | ||
191 | */ | ||
192 | public SubPlan getPlan(PBody pBody) { | ||
193 | // if the query is not marked as being compiled, initiate compilation | ||
194 | // (this is useful in case of recursion if getPlan() is the entry point) | ||
195 | PQuery pQuery = pBody.getPattern(); | ||
196 | if (!compilationInProgress.contains(pQuery)) | ||
197 | getCompiledForm(pQuery); | ||
198 | |||
199 | // Is the plan already cached? | ||
200 | SubPlan plan = plannerCache.get(pBody); | ||
201 | if (plan == null) { | ||
202 | boolean reentrant = !planningInProgress.add(pBody); | ||
203 | if (reentrant) { // oops, recursion into body in progress | ||
204 | throw new IllegalArgumentException( | ||
205 | "Planning-level recursion unsupported: " + pBody.getPattern().getFullyQualifiedName()); | ||
206 | } else { // not reentrant, therefore no recursion, do the planning | ||
207 | try { | ||
208 | plan = plannerStrategy.plan(pBody, logger, metaContext); | ||
209 | plannerCache.put(pBody, plan); | ||
210 | } finally { | ||
211 | planningInProgress.remove(pBody); | ||
212 | } | ||
213 | } | ||
214 | } | ||
215 | return plan; | ||
216 | } | ||
217 | |||
218 | private CompiledQuery compileProduction(PQuery query) { | ||
219 | Collection<SubPlan> bodyPlans = new ArrayList<SubPlan>(); | ||
220 | normalizer.setTraceCollector(CommonQueryHintOptions.normalizationTraceCollector | ||
221 | .getValueOrDefault(hintProvider.getQueryEvaluationHint(query))); | ||
222 | for (PBody pBody : normalizer.rewrite(query).getBodies()) { | ||
223 | SubPlan bodyPlan = getPlan(pBody); | ||
224 | bodyPlans.add(bodyPlan); | ||
225 | } | ||
226 | return doCompileProduction(query, bodyPlans); | ||
227 | } | ||
228 | |||
229 | private CompiledQuery doCompileProduction(PQuery query, Collection<SubPlan> bodies) { | ||
230 | // TODO skip production node if there is just one body and no projection needed? | ||
231 | Map<PBody, RecipeTraceInfo> bodyFinalTraces = new HashMap<PBody, RecipeTraceInfo>(); | ||
232 | Collection<ReteNodeRecipe> bodyFinalRecipes = new HashSet<ReteNodeRecipe>(); | ||
233 | |||
234 | for (SubPlan bodyFinalPlan : bodies) { | ||
235 | // skip over any projections at the end | ||
236 | bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); | ||
237 | |||
238 | // TODO checkAndTrimEqualVariables may introduce superfluous trim, | ||
239 | // but whatever (no uniqueness enforcer needed) | ||
240 | |||
241 | // compile body | ||
242 | final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); | ||
243 | |||
244 | // project to parameter list | ||
245 | RecipeTraceInfo finalTrace = projectBodyFinalToParameters(compiledBody, false); | ||
246 | |||
247 | bodyFinalTraces.put(bodyFinalPlan.getBody(), finalTrace); | ||
248 | bodyFinalRecipes.add(finalTrace.getRecipe()); | ||
249 | } | ||
250 | |||
251 | CompiledQuery compiled = CompilerHelper.makeQueryTrace(query, bodyFinalTraces, bodyFinalRecipes, | ||
252 | getHints(query), metaContext, deleteAndRederiveEvaluation, timelyEvaluation); | ||
253 | |||
254 | return compiled; | ||
255 | } | ||
256 | |||
257 | private CompiledSubPlan doCompileDispatch(SubPlan plan) { | ||
258 | final POperation operation = plan.getOperation(); | ||
259 | if (operation instanceof PEnumerate) { | ||
260 | return doCompileEnumerate(((PEnumerate) operation).getEnumerablePConstraint(), plan); | ||
261 | } else if (operation instanceof PApply) { | ||
262 | final PConstraint pConstraint = ((PApply) operation).getPConstraint(); | ||
263 | if (pConstraint instanceof EnumerablePConstraint) { | ||
264 | CompiledSubPlan primaryParent = getCompiledForm(plan.getParentPlans().get(0)); | ||
265 | PlanningTrace secondaryParent = doEnumerateDispatch(plan, (EnumerablePConstraint) pConstraint); | ||
266 | return compileToNaturalJoin(plan, primaryParent, secondaryParent); | ||
267 | } else if (pConstraint instanceof DeferredPConstraint) { | ||
268 | return doDeferredDispatch((DeferredPConstraint) pConstraint, plan); | ||
269 | } else { | ||
270 | throw new IllegalArgumentException("Unsupported PConstraint in query plan: " + plan.toShortString()); | ||
271 | } | ||
272 | } else if (operation instanceof PJoin) { | ||
273 | return doCompileJoin((PJoin) operation, plan); | ||
274 | } else if (operation instanceof PProject) { | ||
275 | return doCompileProject((PProject) operation, plan); | ||
276 | } else if (operation instanceof PStart) { | ||
277 | return doCompileStart((PStart) operation, plan); | ||
278 | } else { | ||
279 | throw new IllegalArgumentException("Unsupported POperation in query plan: " + plan.toShortString()); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | private CompiledSubPlan doDeferredDispatch(DeferredPConstraint constraint, SubPlan plan) { | ||
284 | final SubPlan parentPlan = plan.getParentPlans().get(0); | ||
285 | final CompiledSubPlan parentCompiled = getCompiledForm(parentPlan); | ||
286 | if (constraint instanceof Equality) { | ||
287 | return compileDeferred((Equality) constraint, plan, parentPlan, parentCompiled); | ||
288 | } else if (constraint instanceof ExportedParameter) { | ||
289 | return compileDeferred((ExportedParameter) constraint, plan, parentPlan, parentCompiled); | ||
290 | } else if (constraint instanceof Inequality) { | ||
291 | return compileDeferred((Inequality) constraint, plan, parentPlan, parentCompiled); | ||
292 | } else if (constraint instanceof NegativePatternCall) { | ||
293 | return compileDeferred((NegativePatternCall) constraint, plan, parentPlan, parentCompiled); | ||
294 | } else if (constraint instanceof PatternMatchCounter) { | ||
295 | return compileDeferred((PatternMatchCounter) constraint, plan, parentPlan, parentCompiled); | ||
296 | } else if (constraint instanceof AggregatorConstraint) { | ||
297 | return compileDeferred((AggregatorConstraint) constraint, plan, parentPlan, parentCompiled); | ||
298 | } else if (constraint instanceof ExpressionEvaluation) { | ||
299 | return compileDeferred((ExpressionEvaluation) constraint, plan, parentPlan, parentCompiled); | ||
300 | } else if (constraint instanceof TypeFilterConstraint) { | ||
301 | return compileDeferred((TypeFilterConstraint) constraint, plan, parentPlan, parentCompiled); | ||
302 | } | ||
303 | throw new UnsupportedOperationException("Unknown deferred constraint " + constraint); | ||
304 | } | ||
305 | |||
306 | private CompiledSubPlan compileDeferred(Equality constraint, SubPlan plan, SubPlan parentPlan, | ||
307 | CompiledSubPlan parentCompiled) { | ||
308 | if (constraint.isMoot()) | ||
309 | return parentCompiled.cloneFor(plan); | ||
310 | |||
311 | Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); | ||
312 | Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); | ||
313 | |||
314 | if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { | ||
315 | Integer indexLower = Math.min(index1, index2); | ||
316 | Integer indexHigher = Math.max(index1, index2); | ||
317 | |||
318 | EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe(); | ||
319 | equalityFilterRecipe.setParent(parentCompiled.getRecipe()); | ||
320 | equalityFilterRecipe.getIndices().add(indexLower); | ||
321 | equalityFilterRecipe.getIndices().add(indexHigher); | ||
322 | |||
323 | return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), equalityFilterRecipe, parentCompiled); | ||
324 | } else { | ||
325 | throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", | ||
326 | plan.toShortString(), parentCompiled.toString())); | ||
327 | } | ||
328 | } | ||
329 | |||
330 | /** | ||
331 | * Precondition: constantTrace must map to a ConstantRecipe, and all of its variables must be contained in | ||
332 | * toFilterTrace. | ||
333 | */ | ||
334 | private CompiledSubPlan compileConstantFiltering(SubPlan plan, PlanningTrace toFilterTrace, | ||
335 | ConstantRecipe constantRecipe, List<PVariable> filteredVariables) { | ||
336 | PlanningTrace resultTrace = toFilterTrace; | ||
337 | |||
338 | int constantVariablesSize = filteredVariables.size(); | ||
339 | for (int i = 0; i < constantVariablesSize; ++i) { | ||
340 | Object constantValue = constantRecipe.getConstantValues().get(i); | ||
341 | PVariable filteredVariable = filteredVariables.get(i); | ||
342 | int filteredColumn = resultTrace.getVariablesTuple().indexOf(filteredVariable); | ||
343 | |||
344 | DiscriminatorDispatcherRecipe dispatcherRecipe = FACTORY.createDiscriminatorDispatcherRecipe(); | ||
345 | dispatcherRecipe.setDiscriminationColumnIndex(filteredColumn); | ||
346 | dispatcherRecipe.setParent(resultTrace.getRecipe()); | ||
347 | |||
348 | PlanningTrace dispatcherTrace = new PlanningTrace(plan, resultTrace.getVariablesTuple(), dispatcherRecipe, | ||
349 | resultTrace); | ||
350 | |||
351 | DiscriminatorBucketRecipe bucketRecipe = FACTORY.createDiscriminatorBucketRecipe(); | ||
352 | bucketRecipe.setBucketKey(constantValue); | ||
353 | bucketRecipe.setParent(dispatcherRecipe); | ||
354 | |||
355 | PlanningTrace bucketTrace = new PlanningTrace(plan, dispatcherTrace.getVariablesTuple(), bucketRecipe, | ||
356 | dispatcherTrace); | ||
357 | |||
358 | resultTrace = bucketTrace; | ||
359 | } | ||
360 | |||
361 | return resultTrace.cloneFor(plan); | ||
362 | } | ||
363 | |||
364 | private CompiledSubPlan compileDeferred(ExportedParameter constraint, SubPlan plan, SubPlan parentPlan, | ||
365 | CompiledSubPlan parentCompiled) { | ||
366 | return parentCompiled.cloneFor(plan); | ||
367 | } | ||
368 | |||
369 | private CompiledSubPlan compileDeferred(Inequality constraint, SubPlan plan, SubPlan parentPlan, | ||
370 | CompiledSubPlan parentCompiled) { | ||
371 | if (constraint.isEliminable()) | ||
372 | return parentCompiled.cloneFor(plan); | ||
373 | |||
374 | Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); | ||
375 | Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); | ||
376 | |||
377 | if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { | ||
378 | Integer indexLower = Math.min(index1, index2); | ||
379 | Integer indexHigher = Math.max(index1, index2); | ||
380 | |||
381 | InequalityFilterRecipe inequalityFilterRecipe = FACTORY.createInequalityFilterRecipe(); | ||
382 | inequalityFilterRecipe.setParent(parentCompiled.getRecipe()); | ||
383 | inequalityFilterRecipe.setSubject(indexLower); | ||
384 | inequalityFilterRecipe.getInequals().add(indexHigher); | ||
385 | |||
386 | return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), inequalityFilterRecipe, | ||
387 | parentCompiled); | ||
388 | } else { | ||
389 | throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", | ||
390 | plan.toShortString(), parentCompiled.toString())); | ||
391 | } | ||
392 | } | ||
393 | |||
394 | private CompiledSubPlan compileDeferred(TypeFilterConstraint constraint, SubPlan plan, SubPlan parentPlan, | ||
395 | CompiledSubPlan parentCompiled) { | ||
396 | final IInputKey inputKey = constraint.getInputKey(); | ||
397 | if (!metaContext.isStateless(inputKey)) | ||
398 | throw new UnsupportedOperationException( | ||
399 | "Non-enumerable input keys are currently supported in Rete only if they are stateless, unlike " | ||
400 | + inputKey); | ||
401 | |||
402 | final Tuple constraintVariables = constraint.getVariablesTuple(); | ||
403 | final List<PVariable> parentVariables = parentCompiled.getVariablesTuple(); | ||
404 | |||
405 | Mask mask; // select elements of the tuple to check against extensional relation | ||
406 | if (Tuples.flatTupleOf(parentVariables.toArray()).equals(constraintVariables)) { | ||
407 | mask = null; // lucky case, parent signature equals that of input key | ||
408 | } else { | ||
409 | List<PVariable> variables = new ArrayList<PVariable>(); | ||
410 | for (Object variable : constraintVariables.getElements()) { | ||
411 | variables.add((PVariable) variable); | ||
412 | } | ||
413 | mask = CompilerHelper.makeProjectionMask(parentCompiled, variables); | ||
414 | } | ||
415 | InputFilterRecipe inputFilterRecipe = RecipesHelper.inputFilterRecipe(parentCompiled.getRecipe(), inputKey, | ||
416 | inputKey.getStringID(), mask); | ||
417 | return new CompiledSubPlan(plan, parentVariables, inputFilterRecipe, parentCompiled); | ||
418 | } | ||
419 | |||
420 | private CompiledSubPlan compileDeferred(NegativePatternCall constraint, SubPlan plan, SubPlan parentPlan, | ||
421 | CompiledSubPlan parentCompiled) { | ||
422 | final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, | ||
423 | constraint.getActualParametersTuple()); | ||
424 | |||
425 | JoinHelper joinHelper = new JoinHelper(plan, parentCompiled, callTrace); | ||
426 | final RecipeTraceInfo primaryIndexer = joinHelper.getPrimaryIndexer(); | ||
427 | final RecipeTraceInfo secondaryIndexer = joinHelper.getSecondaryIndexer(); | ||
428 | |||
429 | AntiJoinRecipe antiJoinRecipe = FACTORY.createAntiJoinRecipe(); | ||
430 | antiJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); | ||
431 | antiJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe()); | ||
432 | |||
433 | return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), antiJoinRecipe, primaryIndexer, | ||
434 | secondaryIndexer); | ||
435 | } | ||
436 | |||
437 | private CompiledSubPlan compileDeferred(PatternMatchCounter constraint, SubPlan plan, SubPlan parentPlan, | ||
438 | CompiledSubPlan parentCompiled) { | ||
439 | final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, | ||
440 | constraint.getActualParametersTuple()); | ||
441 | |||
442 | // hack: use some mask computations (+ the indexers) from a fake natural join against the called query | ||
443 | JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace); | ||
444 | final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); | ||
445 | final RecipeTraceInfo callProjectionIndexer = fakeJoinHelper.getSecondaryIndexer(); | ||
446 | |||
447 | final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>( | ||
448 | fakeJoinHelper.getSecondaryMask().transform(callTrace.getVariablesTuple())); | ||
449 | /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); | ||
450 | |||
451 | CountAggregatorRecipe aggregatorRecipe = FACTORY.createCountAggregatorRecipe(); | ||
452 | aggregatorRecipe.setParent((ProjectionIndexerRecipe) callProjectionIndexer.getRecipe()); | ||
453 | PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, | ||
454 | callProjectionIndexer); | ||
455 | |||
456 | IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); | ||
457 | aggregatorIndexerRecipe.setParent(aggregatorRecipe); | ||
458 | // aggregatorIndexerRecipe.setMask(RecipesHelper.mask( | ||
459 | // sideVariablesTuple.size(), | ||
460 | // //use same indices as in the projection indexer | ||
461 | // // EVEN if result variable already visible in left parent | ||
462 | // fakeJoinHelper.getSecondaryMask().indices | ||
463 | // )); | ||
464 | |||
465 | int aggregatorWidth = sideVariablesTuple.size(); | ||
466 | int aggregateResultIndex = aggregatorWidth - 1; | ||
467 | |||
468 | aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( | ||
469 | // aggregate according all but the last index | ||
470 | aggregateResultIndex, aggregatorWidth))); | ||
471 | PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, | ||
472 | aggregatorTrace); | ||
473 | |||
474 | JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); | ||
475 | naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); | ||
476 | naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); | ||
477 | naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, | ||
478 | // extend with last element only - the computation value | ||
479 | aggregateResultIndex)); | ||
480 | |||
481 | // what if the new variable already has a value? | ||
482 | // even if already known, we add the new result variable, so that it can be filtered at the end | ||
483 | // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); | ||
484 | |||
485 | final List<PVariable> aggregatedVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple()); | ||
486 | aggregatedVariablesTuple.add(constraint.getResultVariable()); | ||
487 | |||
488 | PlanningTrace joinTrace = new PlanningTrace(plan, aggregatedVariablesTuple, naturalJoinRecipe, primaryIndexer, | ||
489 | aggregatorIndexerTrace); | ||
490 | |||
491 | return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); | ||
492 | // if (!alreadyKnown) { | ||
493 | // return joinTrace.cloneFor(plan); | ||
494 | // } else { | ||
495 | // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); | ||
496 | // } | ||
497 | } | ||
498 | |||
499 | private CompiledSubPlan compileDeferred(AggregatorConstraint constraint, SubPlan plan, SubPlan parentPlan, | ||
500 | CompiledSubPlan parentCompiled) { | ||
501 | final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, | ||
502 | constraint.getActualParametersTuple()); | ||
503 | |||
504 | // hack: use some mask computations (+ the indexers) from a fake natural join against the called query | ||
505 | JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace); | ||
506 | final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); | ||
507 | TupleMask callGroupMask = fakeJoinHelper.getSecondaryMask(); | ||
508 | |||
509 | final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>( | ||
510 | callGroupMask.transform(callTrace.getVariablesTuple())); | ||
511 | /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); | ||
512 | |||
513 | IMultisetAggregationOperator<?, ?, ?> operator = constraint.getAggregator().getOperator(); | ||
514 | |||
515 | SingleColumnAggregatorRecipe columnAggregatorRecipe = FACTORY.createSingleColumnAggregatorRecipe(); | ||
516 | columnAggregatorRecipe.setParent(callTrace.getRecipe()); | ||
517 | columnAggregatorRecipe.setMultisetAggregationOperator(operator); | ||
518 | |||
519 | int columnIndex = constraint.getAggregatedColumn(); | ||
520 | IPosetComparator posetComparator = null; | ||
521 | Mask groupMask = CompilerHelper.toRecipeMask(callGroupMask); | ||
522 | |||
523 | // temporary solution to support the deprecated option for now | ||
524 | final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(plan)); | ||
525 | |||
526 | columnAggregatorRecipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); | ||
527 | if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { | ||
528 | List<PParameter> parameters = constraint.getReferredQuery().getParameters(); | ||
529 | IInputKey key = parameters.get(columnIndex).getDeclaredUnaryType(); | ||
530 | if (key != null && metaContext.isPosetKey(key)) { | ||
531 | posetComparator = metaContext.getPosetComparator(Collections.singleton(key)); | ||
532 | } | ||
533 | } | ||
534 | |||
535 | if (posetComparator == null) { | ||
536 | columnAggregatorRecipe.setGroupByMask(groupMask); | ||
537 | columnAggregatorRecipe.setAggregableIndex(columnIndex); | ||
538 | } else { | ||
539 | MonotonicityInfo monotonicityInfo = FACTORY.createMonotonicityInfo(); | ||
540 | monotonicityInfo.setCoreMask(groupMask); | ||
541 | monotonicityInfo.setPosetMask(CompilerHelper.toRecipeMask( | ||
542 | TupleMask.selectSingle(columnIndex, constraint.getActualParametersTuple().getSize()))); | ||
543 | monotonicityInfo.setPosetComparator(posetComparator); | ||
544 | columnAggregatorRecipe.setOptionalMonotonicityInfo(monotonicityInfo); | ||
545 | } | ||
546 | |||
547 | ReteNodeRecipe aggregatorRecipe = columnAggregatorRecipe; | ||
548 | PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, callTrace); | ||
549 | |||
550 | IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); | ||
551 | aggregatorIndexerRecipe.setParent(aggregatorRecipe); | ||
552 | |||
553 | int aggregatorWidth = sideVariablesTuple.size(); | ||
554 | int aggregateResultIndex = aggregatorWidth - 1; | ||
555 | |||
556 | aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( | ||
557 | // aggregate according all but the last index | ||
558 | aggregateResultIndex, aggregatorWidth))); | ||
559 | PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, | ||
560 | aggregatorTrace); | ||
561 | |||
562 | JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); | ||
563 | naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); | ||
564 | naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); | ||
565 | naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, | ||
566 | // extend with last element only - the computation value | ||
567 | aggregateResultIndex)); | ||
568 | |||
569 | // what if the new variable already has a value? | ||
570 | // even if already known, we add the new result variable, so that it can be filtered at the end | ||
571 | // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); | ||
572 | |||
573 | final List<PVariable> finalVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple()); | ||
574 | finalVariablesTuple.add(constraint.getResultVariable()); | ||
575 | |||
576 | PlanningTrace joinTrace = new PlanningTrace(plan, finalVariablesTuple, naturalJoinRecipe, primaryIndexer, | ||
577 | aggregatorIndexerTrace); | ||
578 | |||
579 | return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); | ||
580 | // if (!alreadyKnown) { | ||
581 | // return joinTrace.cloneFor(plan); | ||
582 | // } else { | ||
583 | // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); | ||
584 | // } | ||
585 | } | ||
586 | |||
587 | private CompiledSubPlan compileDeferred(ExpressionEvaluation constraint, SubPlan plan, SubPlan parentPlan, | ||
588 | CompiledSubPlan parentCompiled) { | ||
589 | Map<String, Integer> tupleNameMap = new HashMap<String, Integer>(); | ||
590 | for (String name : constraint.getEvaluator().getInputParameterNames()) { | ||
591 | Map<? extends Object, Integer> index = parentCompiled.getPosMapping(); | ||
592 | PVariable variable = constraint.getPSystem().getVariableByNameChecked(name); | ||
593 | Integer position = index.get(variable); | ||
594 | tupleNameMap.put(name, position); | ||
595 | } | ||
596 | |||
597 | final PVariable outputVariable = constraint.getOutputVariable(); | ||
598 | final boolean booleanCheck = outputVariable == null; | ||
599 | |||
600 | // TODO determine whether expression is costly | ||
601 | boolean cacheOutput = ReteHintOptions.cacheOutputOfEvaluatorsByDefault.getValueOrDefault(getHints(plan)); | ||
602 | // for (PAnnotation pAnnotation : | ||
603 | // plan.getBody().getPattern().getAnnotationsByName(EXPRESSION_EVALUATION_ANNOTATION"")) { | ||
604 | // for (Object value : pAnnotation.getAllValues("expensive")) { | ||
605 | // if (value instanceof Boolean) | ||
606 | // cacheOutput = (boolean) value; | ||
607 | // } | ||
608 | // } | ||
609 | |||
610 | ExpressionEnforcerRecipe enforcerRecipe = booleanCheck ? FACTORY.createCheckRecipe() | ||
611 | : FACTORY.createEvalRecipe(); | ||
612 | enforcerRecipe.setParent(parentCompiled.getRecipe()); | ||
613 | enforcerRecipe.setExpression(RecipesHelper.expressionDefinition(constraint.getEvaluator())); | ||
614 | enforcerRecipe.setCacheOutput(cacheOutput); | ||
615 | if (enforcerRecipe instanceof EvalRecipe) { | ||
616 | ((EvalRecipe) enforcerRecipe).setUnwinding(constraint.isUnwinding()); | ||
617 | } | ||
618 | for (Entry<String, Integer> entry : tupleNameMap.entrySet()) { | ||
619 | enforcerRecipe.getMappedIndices().put(entry.getKey(), entry.getValue()); | ||
620 | } | ||
621 | |||
622 | final List<PVariable> enforcerVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple()); | ||
623 | if (!booleanCheck) | ||
624 | enforcerVariablesTuple.add(outputVariable); | ||
625 | PlanningTrace enforcerTrace = new PlanningTrace(plan, enforcerVariablesTuple, enforcerRecipe, parentCompiled); | ||
626 | |||
627 | return CompilerHelper.checkAndTrimEqualVariables(plan, enforcerTrace).cloneFor(plan); | ||
628 | } | ||
629 | |||
630 | private CompiledSubPlan doCompileJoin(PJoin operation, SubPlan plan) { | ||
631 | final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan); | ||
632 | final CompiledSubPlan leftCompiled = compiledParents.get(0); | ||
633 | final CompiledSubPlan rightCompiled = compiledParents.get(1); | ||
634 | |||
635 | return compileToNaturalJoin(plan, leftCompiled, rightCompiled); | ||
636 | } | ||
637 | |||
638 | private CompiledSubPlan compileToNaturalJoin(SubPlan plan, final PlanningTrace leftCompiled, | ||
639 | final PlanningTrace rightCompiled) { | ||
640 | // CHECK IF SPECIAL CASE | ||
641 | |||
642 | // Is constant filtering applicable? | ||
643 | if (ReteHintOptions.useDiscriminatorDispatchersForConstantFiltering.getValueOrDefault(getHints(plan))) { | ||
644 | if (leftCompiled.getRecipe() instanceof ConstantRecipe | ||
645 | && rightCompiled.getVariablesTuple().containsAll(leftCompiled.getVariablesTuple())) { | ||
646 | return compileConstantFiltering(plan, rightCompiled, (ConstantRecipe) leftCompiled.getRecipe(), | ||
647 | leftCompiled.getVariablesTuple()); | ||
648 | } | ||
649 | if (rightCompiled.getRecipe() instanceof ConstantRecipe | ||
650 | && leftCompiled.getVariablesTuple().containsAll(rightCompiled.getVariablesTuple())) { | ||
651 | return compileConstantFiltering(plan, leftCompiled, (ConstantRecipe) rightCompiled.getRecipe(), | ||
652 | rightCompiled.getVariablesTuple()); | ||
653 | } | ||
654 | } | ||
655 | |||
656 | // ELSE: ACTUAL JOIN | ||
657 | JoinHelper joinHelper = new JoinHelper(plan, leftCompiled, rightCompiled); | ||
658 | return new CompiledSubPlan(plan, joinHelper.getNaturalJoinVariablesTuple(), joinHelper.getNaturalJoinRecipe(), | ||
659 | joinHelper.getPrimaryIndexer(), joinHelper.getSecondaryIndexer()); | ||
660 | } | ||
661 | |||
662 | private CompiledSubPlan doCompileProject(PProject operation, SubPlan plan) { | ||
663 | final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan); | ||
664 | final CompiledSubPlan compiledParent = compiledParents.get(0); | ||
665 | |||
666 | List<PVariable> projectedVariables = new ArrayList<PVariable>(operation.getToVariables()); | ||
667 | // Determinizing projection: try to keep original order (hopefully facilitates node reuse) | ||
668 | Map<PVariable, Integer> parentPosMapping = compiledParent.getPosMapping(); | ||
669 | Collections.sort(projectedVariables, Comparator.comparing(parentPosMapping::get)); | ||
670 | |||
671 | return doProjectPlan(compiledParent, projectedVariables, true, | ||
672 | parentTrace -> parentTrace.cloneFor(plan), | ||
673 | (recipe, parentTrace) -> new PlanningTrace(plan, projectedVariables, recipe, parentTrace), | ||
674 | (recipe, parentTrace) -> new CompiledSubPlan(plan, projectedVariables, recipe, parentTrace) | ||
675 | ); | ||
676 | } | ||
677 | |||
678 | /** | ||
679 | * Projects a subplan onto the specified variable tuple | ||
680 | * @param compiledParentPlan the compiled form of the subplan | ||
681 | * @param targetVariables list of variables to project to | ||
682 | * @param enforceUniqueness whether distinctness shall be enforced after the projection. | ||
683 | * Specify false only if directly connecting to a production node. | ||
684 | * @param reinterpretTraceFactory constructs a reinterpreted trace that simply relabels the compiled parent plan, in case it is sufficient | ||
685 | * @param intermediateTraceFactory constructs a recipe trace for an intermediate node, given the recipe of the node and its parent trace | ||
686 | * @param finalTraceFactory constructs a recipe trace for the final resulting node, given the recipe of the node and its parent trace | ||
687 | * @since 2.1 | ||
688 | */ | ||
689 | <ResultTrace extends RecipeTraceInfo> ResultTrace doProjectPlan( | ||
690 | final CompiledSubPlan compiledParentPlan, | ||
691 | final List<PVariable> targetVariables, | ||
692 | boolean enforceUniqueness, | ||
693 | Function<CompiledSubPlan, ResultTrace> reinterpretTraceFactory, | ||
694 | BiFunction<ReteNodeRecipe, RecipeTraceInfo, RecipeTraceInfo> intermediateTraceFactory, | ||
695 | BiFunction<ReteNodeRecipe, RecipeTraceInfo, ResultTrace> finalTraceFactory) | ||
696 | { | ||
697 | if (targetVariables.equals(compiledParentPlan.getVariablesTuple())) // no projection needed | ||
698 | return reinterpretTraceFactory.apply(compiledParentPlan); | ||
699 | |||
700 | // otherwise, we need at least a trimmer | ||
701 | TrimmerRecipe trimmerRecipe = CompilerHelper.makeTrimmerRecipe(compiledParentPlan, targetVariables); | ||
702 | |||
703 | // do we need to eliminate duplicates? | ||
704 | SubPlan parentPlan = compiledParentPlan.getSubPlan(); | ||
705 | if (!enforceUniqueness || BuildHelper.areAllVariablesDetermined( | ||
706 | parentPlan, | ||
707 | targetVariables, | ||
708 | queryAnalyzer, | ||
709 | true)) | ||
710 | { | ||
711 | // if uniqueness enforcess is unwanted or unneeeded, skip it | ||
712 | return finalTraceFactory.apply(trimmerRecipe, compiledParentPlan); | ||
713 | } else { | ||
714 | // add a uniqueness enforcer | ||
715 | UniquenessEnforcerRecipe recipe = FACTORY.createUniquenessEnforcerRecipe(); | ||
716 | recipe.getParents().add(trimmerRecipe); | ||
717 | |||
718 | // temporary solution to support the deprecated option for now | ||
719 | final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(parentPlan)); | ||
720 | |||
721 | recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); | ||
722 | if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { | ||
723 | PosetTriplet triplet = CompilerHelper.computePosetInfo(targetVariables, parentPlan.getBody(), metaContext); | ||
724 | |||
725 | if (triplet.comparator != null) { | ||
726 | MonotonicityInfo info = FACTORY.createMonotonicityInfo(); | ||
727 | info.setCoreMask(triplet.coreMask); | ||
728 | info.setPosetMask(triplet.posetMask); | ||
729 | info.setPosetComparator(triplet.comparator); | ||
730 | recipe.setOptionalMonotonicityInfo(info); | ||
731 | } | ||
732 | } | ||
733 | |||
734 | RecipeTraceInfo trimmerTrace = intermediateTraceFactory.apply(trimmerRecipe, compiledParentPlan); | ||
735 | return finalTraceFactory.apply(recipe, trimmerTrace); | ||
736 | } | ||
737 | } | ||
738 | |||
739 | /** | ||
740 | * Projects the final compiled form of a PBody onto the parameter tuple | ||
741 | * @param compiledBody the compiled form of the body, with all constraints enforced, not yet projected to query parameters | ||
742 | * @param enforceUniqueness whether distinctness shall be enforced after the projection. | ||
743 | * Specify false only if directly connecting to a production node. | ||
744 | * @since 2.1 | ||
745 | */ | ||
746 | RecipeTraceInfo projectBodyFinalToParameters( | ||
747 | final CompiledSubPlan compiledBody, | ||
748 | boolean enforceUniqueness) | ||
749 | { | ||
750 | final PBody body = compiledBody.getSubPlan().getBody(); | ||
751 | final List<PVariable> parameterList = body.getSymbolicParameterVariables(); | ||
752 | |||
753 | return doProjectPlan(compiledBody, parameterList, enforceUniqueness, | ||
754 | parentTrace -> parentTrace, | ||
755 | (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace), | ||
756 | (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace) | ||
757 | ); | ||
758 | } | ||
759 | |||
760 | private CompiledSubPlan doCompileStart(PStart operation, SubPlan plan) { | ||
761 | if (!operation.getAPrioriVariables().isEmpty()) { | ||
762 | throw new IllegalArgumentException("Input variables unsupported by Rete: " + plan.toShortString()); | ||
763 | } | ||
764 | final ConstantRecipe recipe = FACTORY.createConstantRecipe(); | ||
765 | recipe.getConstantValues().clear(); | ||
766 | |||
767 | return new CompiledSubPlan(plan, new ArrayList<PVariable>(), recipe); | ||
768 | } | ||
769 | |||
770 | private CompiledSubPlan doCompileEnumerate(EnumerablePConstraint constraint, SubPlan plan) { | ||
771 | final PlanningTrace trimmedTrace = doEnumerateAndDeduplicate(constraint, plan); | ||
772 | |||
773 | return trimmedTrace.cloneFor(plan); | ||
774 | } | ||
775 | |||
776 | private PlanningTrace doEnumerateAndDeduplicate(EnumerablePConstraint constraint, SubPlan plan) { | ||
777 | final PlanningTrace coreTrace = doEnumerateDispatch(plan, constraint); | ||
778 | final PlanningTrace trimmedTrace = CompilerHelper.checkAndTrimEqualVariables(plan, coreTrace); | ||
779 | return trimmedTrace; | ||
780 | } | ||
781 | |||
782 | private PlanningTrace doEnumerateDispatch(SubPlan plan, EnumerablePConstraint constraint) { | ||
783 | if (constraint instanceof RelationEvaluation) { | ||
784 | return compileEnumerable(plan, (RelationEvaluation) constraint); | ||
785 | } else if (constraint instanceof BinaryTransitiveClosure) { | ||
786 | return compileEnumerable(plan, (BinaryTransitiveClosure) constraint); | ||
787 | } else if (constraint instanceof BinaryReflexiveTransitiveClosure) { | ||
788 | return compileEnumerable(plan, (BinaryReflexiveTransitiveClosure) constraint); | ||
789 | } else if (constraint instanceof RepresentativeElectionConstraint) { | ||
790 | return compileEnumerable(plan, (RepresentativeElectionConstraint) constraint); | ||
791 | } else if (constraint instanceof ConstantValue) { | ||
792 | return compileEnumerable(plan, (ConstantValue) constraint); | ||
793 | } else if (constraint instanceof PositivePatternCall) { | ||
794 | return compileEnumerable(plan, (PositivePatternCall) constraint); | ||
795 | } else if (constraint instanceof TypeConstraint) { | ||
796 | return compileEnumerable(plan, (TypeConstraint) constraint); | ||
797 | } | ||
798 | throw new UnsupportedOperationException("Unknown enumerable constraint " + constraint); | ||
799 | } | ||
800 | |||
801 | private PlanningTrace compileEnumerable(SubPlan plan, BinaryReflexiveTransitiveClosure constraint) { | ||
802 | // TODO the implementation would perform better if an inequality check would be used after tcRecipe and | ||
803 | // uniqueness enforcer be replaced by a transparent node with multiple parents, but such a node is not available | ||
804 | // in recipe metamodel in VIATRA 2.0 | ||
805 | |||
806 | // Find called query | ||
807 | final PQuery referredQuery = constraint.getSupplierKey(); | ||
808 | final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); | ||
809 | |||
810 | // Calculate irreflexive transitive closure | ||
811 | final TransitiveClosureRecipe tcRecipe = FACTORY.createTransitiveClosureRecipe(); | ||
812 | tcRecipe.setParent(callTrace.getRecipe()); | ||
813 | final PlanningTrace tcTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), tcRecipe, callTrace); | ||
814 | |||
815 | // Enumerate universe type | ||
816 | final IInputKey inputKey = constraint.getUniverseType(); | ||
817 | final InputRecipe universeTypeRecipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); | ||
818 | final PlanningTrace universeTypeTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple( | ||
819 | Tuples.staticArityFlatTupleOf(constraint.getVariablesTuple().get(0))), universeTypeRecipe); | ||
820 | |||
821 | // Calculate reflexive access by duplicating universe type column | ||
822 | final TrimmerRecipe reflexiveRecipe = FACTORY.createTrimmerRecipe(); | ||
823 | reflexiveRecipe.setMask(RecipesHelper.mask(1, 0, 0)); | ||
824 | reflexiveRecipe.setParent(universeTypeRecipe); | ||
825 | final PlanningTrace reflexiveTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), reflexiveRecipe, universeTypeTrace); | ||
826 | |||
827 | // Finally, reduce duplicates after a join | ||
828 | final UniquenessEnforcerRecipe brtcRecipe = FACTORY.createUniquenessEnforcerRecipe(); | ||
829 | brtcRecipe.getParents().add(tcRecipe); | ||
830 | brtcRecipe.getParents().add(reflexiveRecipe); | ||
831 | |||
832 | return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), brtcRecipe, reflexiveTrace, tcTrace); | ||
833 | } | ||
834 | |||
835 | private PlanningTrace compileEnumerable(SubPlan plan, RepresentativeElectionConstraint constraint) { | ||
836 | var referredQuery = constraint.getSupplierKey(); | ||
837 | var callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); | ||
838 | var recipe = FACTORY.createRepresentativeElectionRecipe(); | ||
839 | recipe.setParent(callTrace.getRecipe()); | ||
840 | recipe.setConnectivity(constraint.getConnectivity()); | ||
841 | return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); | ||
842 | } | ||
843 | |||
844 | private PlanningTrace compileEnumerable(SubPlan plan, BinaryTransitiveClosure constraint) { | ||
845 | final PQuery referredQuery = constraint.getSupplierKey(); | ||
846 | final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); | ||
847 | |||
848 | final TransitiveClosureRecipe recipe = FACTORY.createTransitiveClosureRecipe(); | ||
849 | recipe.setParent(callTrace.getRecipe()); | ||
850 | |||
851 | return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); | ||
852 | } | ||
853 | |||
854 | private PlanningTrace compileEnumerable(SubPlan plan, RelationEvaluation constraint) { | ||
855 | final List<ReteNodeRecipe> parentRecipes = new ArrayList<ReteNodeRecipe>(); | ||
856 | final List<RecipeTraceInfo> parentTraceInfos = new ArrayList<RecipeTraceInfo>(); | ||
857 | for (final PQuery inputQuery : constraint.getReferredQueries()) { | ||
858 | final CompiledQuery compiledQuery = getCompiledForm(inputQuery); | ||
859 | parentRecipes.add(compiledQuery.getRecipe()); | ||
860 | parentTraceInfos.add(compiledQuery); | ||
861 | } | ||
862 | final RelationEvaluationRecipe recipe = FACTORY.createRelationEvaluationRecipe(); | ||
863 | recipe.getParents().addAll(parentRecipes); | ||
864 | recipe.setEvaluator(RecipesHelper.expressionDefinition(constraint.getEvaluator())); | ||
865 | return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, parentTraceInfos); | ||
866 | } | ||
867 | |||
868 | private PlanningTrace compileEnumerable(SubPlan plan, PositivePatternCall constraint) { | ||
869 | final PQuery referredQuery = constraint.getReferredQuery(); | ||
870 | return referQuery(referredQuery, plan, constraint.getVariablesTuple()); | ||
871 | } | ||
872 | |||
873 | private PlanningTrace compileEnumerable(SubPlan plan, TypeConstraint constraint) { | ||
874 | final IInputKey inputKey = constraint.getSupplierKey(); | ||
875 | final InputRecipe recipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); | ||
876 | return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); | ||
877 | } | ||
878 | |||
879 | private PlanningTrace compileEnumerable(SubPlan plan, ConstantValue constraint) { | ||
880 | final ConstantRecipe recipe = FACTORY.createConstantRecipe(); | ||
881 | recipe.getConstantValues().add(constraint.getSupplierKey()); | ||
882 | return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); | ||
883 | } | ||
884 | |||
885 | // TODO handle recursion | ||
886 | private PlanningTrace referQuery(PQuery query, SubPlan plan, Tuple actualParametersTuple) { | ||
887 | RecipeTraceInfo referredQueryTrace = originalTraceOfReferredQuery(query); | ||
888 | return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(actualParametersTuple), | ||
889 | referredQueryTrace.getRecipe(), referredQueryTrace.getParentRecipeTracesForCloning()); | ||
890 | } | ||
891 | |||
892 | private RecipeTraceInfo originalTraceOfReferredQuery(PQuery query) { | ||
893 | // eliminate superfluous production node? | ||
894 | if (PVisibility.EMBEDDED == query.getVisibility()) { // currently inline patterns only | ||
895 | Set<PBody> rewrittenBodies = normalizer.rewrite(query).getBodies(); | ||
896 | if (1 == rewrittenBodies.size()) { // non-disjunctive | ||
897 | // TODO in the future, check if non-recursive - (not currently permitted) | ||
898 | |||
899 | PBody pBody = rewrittenBodies.iterator().next(); | ||
900 | SubPlan bodyFinalPlan = getPlan(pBody); | ||
901 | |||
902 | // skip over any projections at the end | ||
903 | bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); | ||
904 | |||
905 | // TODO checkAndTrimEqualVariables may introduce superfluous trim, | ||
906 | // but whatever (no uniqueness enforcer needed) | ||
907 | |||
908 | // compile body | ||
909 | final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); | ||
910 | |||
911 | // project to parameter list, add uniqueness enforcer if necessary | ||
912 | return projectBodyFinalToParameters(compiledBody, true /* ensure uniqueness, as no production node is used */); | ||
913 | } | ||
914 | } | ||
915 | |||
916 | // otherwise, regular reference to recipe realizing the query | ||
917 | return getCompiledForm(query); | ||
918 | } | ||
919 | |||
920 | protected List<CompiledSubPlan> getCompiledFormOfParents(SubPlan plan) { | ||
921 | List<CompiledSubPlan> results = new ArrayList<CompiledSubPlan>(); | ||
922 | for (SubPlan parentPlan : plan.getParentPlans()) { | ||
923 | results.add(getCompiledForm(parentPlan)); | ||
924 | } | ||
925 | return results; | ||
926 | } | ||
927 | |||
928 | /** | ||
929 | * Returns an unmodifiable view of currently cached compiled queries. | ||
930 | */ | ||
931 | public Map<PQuery, CompiledQuery> getCachedCompiledQueries() { | ||
932 | return Collections.unmodifiableMap(queryCompilerCache); | ||
933 | } | ||
934 | |||
935 | /** | ||
936 | * Returns an unmodifiable view of currently cached query plans. | ||
937 | */ | ||
938 | public Map<PBody, SubPlan> getCachedQueryPlans() { | ||
939 | return Collections.unmodifiableMap(plannerCache); | ||
940 | } | ||
941 | |||
942 | private QueryEvaluationHint getHints(SubPlan plan) { | ||
943 | return getHints(plan.getBody().getPattern()); | ||
944 | } | ||
945 | |||
946 | private QueryEvaluationHint getHints(PQuery pattern) { | ||
947 | return hintProvider.getQueryEvaluationHint(pattern); | ||
948 | } | ||
949 | } | ||