aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-22 02:02:21 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2024-04-07 14:55:46 +0200
commit31465875054c847943e625d9e84bbbd52bc3695e (patch)
tree961b8831158e848244b841b0905f8dbd07ff1e39
parentfeat(language): datatype declarations (diff)
downloadrefinery-31465875054c847943e625d9e84bbbd52bc3695e.tar.gz
refinery-31465875054c847943e625d9e84bbbd52bc3695e.tar.zst
refinery-31465875054c847943e625d9e84bbbd52bc3695e.zip
feat(query): left join for data variables
-rw-r--r--subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore11
-rw-r--r--subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore.license2
-rw-r--r--subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel9
-rw-r--r--subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license2
-rw-r--r--subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/LeftJoinNode.java167
-rw-r--r--subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java1815
-rw-r--r--subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java10
-rw-r--r--subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java38
-rw-r--r--subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/LeftJoinConstraint.java82
-rw-r--r--subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java13
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java1
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java101
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java1
-rw-r--r--subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java129
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java12
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LeftJoinLiteral.java140
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java16
21 files changed, 1636 insertions, 950 deletions
diff --git a/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore b/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore
index 6b8f10ea..23ee5a77 100644
--- a/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore
+++ b/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore
@@ -399,4 +399,15 @@
399 <eStructuralFeatures xsi:type="ecore:EAttribute" name="connectivity" eType="#//Connectivity"/> 399 <eStructuralFeatures xsi:type="ecore:EAttribute" name="connectivity" eType="#//Connectivity"/>
400 </eClassifiers> 400 </eClassifiers>
401 <eClassifiers xsi:type="ecore:EDataType" name="Connectivity" instanceClassName="tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity"/> 401 <eClassifiers xsi:type="ecore:EDataType" name="Connectivity" instanceClassName="tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity"/>
402 <eClassifiers xsi:type="ecore:EClass" name="OuterJoinNodeRecipe" eSuperTypes="#//ReteNodeRecipe">
403 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
404 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
405 <details key="body" value="&lt;%tools.refinery.interpreter.rete.recipes.ProjectionIndexerRecipe%> _parent = this.getParent();&#xA;&lt;%tools.refinery.interpreter.rete.recipes.Mask%> _mask = _parent.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;return _sourceIndices.size();"/>
406 </eAnnotations>
407 </eOperations>
408 <eStructuralFeatures xsi:type="ecore:EReference" name="parent" eType="#//ProjectionIndexerRecipe"
409 containment="true" resolveProxies="false"/>
410 <eStructuralFeatures xsi:type="ecore:EAttribute" name="defaultValue" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject"/>
411 </eClassifiers>
412 <eClassifiers xsi:type="ecore:EClass" name="OuterJoinIndexerRecipe" eSuperTypes="#//IndexerRecipe"/>
402</ecore:EPackage> 413</ecore:EPackage>
diff --git a/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore.license b/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore.license
index 03d1d42b..22023c2c 100644
--- a/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore.license
+++ b/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore.license
@@ -1,4 +1,4 @@
1Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro 1Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
2Copyright (c) 2023 The Refinery Authors <https://refinery.tools> 2Copyright (c) 2023-2024 The Refinery Authors <https://refinery.tools>
3 3
4SPDX-License-Identifier: EPL-2.0 4SPDX-License-Identifier: EPL-2.0
diff --git a/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel b/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel
index f7dc7a4e..f156b1a2 100644
--- a/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel
+++ b/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2<genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" 2<genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
3 xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" copyrightText="Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro&#xA;Copyright (c) 2023 The Refinery Authors &lt;https://refinery.tools>&#xA;This program and the accompanying materials are made available under the&#xA;terms of the Eclipse Public License v. 2.0 which is available at&#xA;http://www.eclipse.org/legal/epl-v20.html.&#xA;&#xA;SPDX-License-Identifier: EPL-2.0" 3 xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" copyrightText="Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro&#xA;Copyright (c) 2023-2024 The Refinery Authors &lt;https://refinery.tools>&#xA;This program and the accompanying materials are made available under the&#xA;terms of the Eclipse Public License v. 2.0 which is available at&#xA;http://www.eclipse.org/legal/epl-v20.html.&#xA;&#xA;SPDX-License-Identifier: EPL-2.0"
4 modelDirectory="/tools.refinery.refinery-interpreter-rete-recipes/src/main/emf-gen" 4 modelDirectory="/tools.refinery.refinery-interpreter-rete-recipes/src/main/emf-gen"
5 modelPluginID="tools.refinery.refinery-interpreter-rete-recipes" runtimeJar="true" 5 modelPluginID="tools.refinery.refinery-interpreter-rete-recipes" runtimeJar="true"
6 forceOverwrite="true" modelName="Rete-recipes" updateClasspath="false" nonNLSMarkers="true" 6 forceOverwrite="true" modelName="Rete-recipes" updateClasspath="false" nonNLSMarkers="true"
@@ -170,5 +170,12 @@
170 <genOperations ecoreOperation="recipes.ecore#//RepresentativeElectionRecipe/getArity" 170 <genOperations ecoreOperation="recipes.ecore#//RepresentativeElectionRecipe/getArity"
171 body="return 2;"/> 171 body="return 2;"/>
172 </genClasses> 172 </genClasses>
173 <genClasses ecoreClass="recipes.ecore#//OuterJoinNodeRecipe">
174 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//OuterJoinNodeRecipe/parent"/>
175 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//OuterJoinNodeRecipe/defaultValue"/>
176 <genOperations ecoreOperation="recipes.ecore#//OuterJoinNodeRecipe/getArity"
177 body="&lt;%tools.refinery.interpreter.rete.recipes.ProjectionIndexerRecipe%> _parent = this.getParent();&#xA;&lt;%tools.refinery.interpreter.rete.recipes.Mask%> _mask = _parent.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;return _sourceIndices.size();"/>
178 </genClasses>
179 <genClasses ecoreClass="recipes.ecore#//OuterJoinIndexerRecipe"/>
173 </genPackages> 180 </genPackages>
174</genmodel:GenModel> 181</genmodel:GenModel>
diff --git a/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license b/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license
index 03d1d42b..22023c2c 100644
--- a/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license
+++ b/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license
@@ -1,4 +1,4 @@
1Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro 1Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
2Copyright (c) 2023 The Refinery Authors <https://refinery.tools> 2Copyright (c) 2023-2024 The Refinery Authors <https://refinery.tools>
3 3
4SPDX-License-Identifier: EPL-2.0 4SPDX-License-Identifier: EPL-2.0
diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/LeftJoinNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/LeftJoinNode.java
new file mode 100644
index 00000000..9871e3bc
--- /dev/null
+++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/LeftJoinNode.java
@@ -0,0 +1,167 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro
3 * Copyright (c) 2024 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.interpreter.rete.aggregation;
10
11import tools.refinery.interpreter.matchers.tuple.ITuple;
12import tools.refinery.interpreter.matchers.tuple.Tuple;
13import tools.refinery.interpreter.matchers.tuple.TupleMask;
14import tools.refinery.interpreter.matchers.tuple.Tuples;
15import tools.refinery.interpreter.matchers.util.Direction;
16import tools.refinery.interpreter.matchers.util.timeline.Timeline;
17import tools.refinery.interpreter.rete.index.DefaultIndexerListener;
18import tools.refinery.interpreter.rete.index.Indexer;
19import tools.refinery.interpreter.rete.index.ProjectionIndexer;
20import tools.refinery.interpreter.rete.index.StandardIndexer;
21import tools.refinery.interpreter.rete.network.Node;
22import tools.refinery.interpreter.rete.network.ReteContainer;
23import tools.refinery.interpreter.rete.network.StandardNode;
24import tools.refinery.interpreter.rete.network.communication.Timestamp;
25
26import java.util.Collection;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30
31public class LeftJoinNode extends StandardNode {
32 private final Object defaultValue;
33 private ProjectionIndexer projectionIndexer;
34 private TupleMask projectionMask;
35 private boolean leftInheritanceOutputMask;
36 private OuterIndexer outerIndexer = null;
37
38 public LeftJoinNode(ReteContainer reteContainer, Object defaultValue) {
39 super(reteContainer);
40 this.defaultValue = defaultValue;
41 }
42
43 @Override
44 public void networkStructureChanged() {
45 if (reteContainer.isTimelyEvaluation() && reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
46 throw new IllegalStateException(this + " cannot be used in recursive differential dataflow evaluation!");
47 }
48 super.networkStructureChanged();
49 }
50
51 public void initializeWith(ProjectionIndexer projectionIndexer) {
52 this.projectionIndexer = projectionIndexer;
53 projectionMask = projectionIndexer.getMask();
54 leftInheritanceOutputMask = isLeftInheritanceOutputMask(projectionMask);
55 projectionIndexer.attachListener(new DefaultIndexerListener(this) {
56 @Override
57 public void notifyIndexerUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change,
58 Timestamp timestamp) {
59 update(direction, updateElement, signature, change, timestamp);
60 }
61 });
62 }
63
64 private static boolean isLeftInheritanceOutputMask(TupleMask mask) {
65 int size = mask.getSize();
66 int sourceWidth = mask.getSourceWidth();
67 if (size != sourceWidth - 1) {
68 throw new IllegalArgumentException("projectionMask should omit a single index, got " + mask);
69 }
70 int[] repetitions = new int[sourceWidth];
71 for (int i = 0; i < size; i++) {
72 int index = mask.indices[i];
73 int repetition = repetitions[index] + 1;
74 if (repetition >= 2) {
75 throw new IllegalArgumentException("Repeated index %d in projectionMask %s".formatted(index, mask));
76 }
77 repetitions[index] = repetition;
78 }
79 for (int i = 0; i < size; i++) {
80 int index = mask.indices[i];
81 if (index != i) {
82 return false;
83 }
84 }
85 return true;
86 }
87
88 protected void update(Direction direction, Tuple updateElement, Tuple signature, boolean change,
89 Timestamp timestamp) {
90 propagateUpdate(direction, updateElement, timestamp);
91 if (outerIndexer != null) {
92 outerIndexer.update(direction, updateElement, signature, change, timestamp);
93 }
94 }
95
96 protected Tuple getDefaultTuple(ITuple key) {
97 if (leftInheritanceOutputMask) {
98 return Tuples.staticArityFlatTupleOf(key, defaultValue);
99 }
100 var objects = new Object[projectionMask.sourceWidth];
101 int targetLength = projectionMask.indices.length;
102 for (int i = 0; i < targetLength; i++) {
103 int j = projectionMask.indices[i];
104 objects[j] = key.get(j);
105 }
106 return Tuples.flatTupleOf(objects);
107 }
108
109 @Override
110 public void pullInto(Collection<Tuple> collector, boolean flush) {
111 projectionIndexer.getParent().pullInto(collector, flush);
112 }
113
114 @Override
115 public void pullIntoWithTimeline(Map<Tuple, Timeline<Timestamp>> collector, boolean flush) {
116 projectionIndexer.getParent().pullIntoWithTimeline(collector, flush);
117 }
118
119 @Override
120 public Set<Tuple> getPulledContents(boolean flush) {
121 return projectionIndexer.getParent().getPulledContents(flush);
122 }
123
124 public Indexer getOuterIndexer() {
125 if (outerIndexer == null) {
126 outerIndexer = new OuterIndexer();
127 getCommunicationTracker().registerDependency(this, outerIndexer);
128 }
129 return outerIndexer;
130 }
131
132 /**
133 * A special non-iterable index that retrieves the aggregated, packed result (signature+aggregate) for the original
134 * signature.
135 *
136 * @author Gabor Bergmann
137 */
138 class OuterIndexer extends StandardIndexer {
139 public OuterIndexer() {
140 super(LeftJoinNode.this.reteContainer, LeftJoinNode.this.projectionMask);
141 this.parent = LeftJoinNode.this;
142 }
143
144 @Override
145 public Collection<Tuple> get(Tuple signature) {
146 var collection = projectionIndexer.get(signature);
147 if (collection == null || collection.isEmpty()) {
148 return List.of(getDefaultTuple(signature));
149 }
150 return collection;
151 }
152
153 public void update(Direction direction, Tuple updateElement, Tuple signature, boolean change,
154 Timestamp timestamp) {
155 propagate(direction, updateElement, signature, false, timestamp);
156 if (change) {
157 var defaultTuple = getDefaultTuple(signature);
158 propagate(direction.opposite(), defaultTuple, signature, false, timestamp);
159 }
160 }
161
162 @Override
163 public Node getActiveNode() {
164 return projectionIndexer.getActiveNode();
165 }
166 }
167}
diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java
index f84eae0a..52a4de41 100644
--- a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java
+++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java
@@ -10,6 +10,7 @@
10package tools.refinery.interpreter.rete.construction.plancompiler; 10package tools.refinery.interpreter.rete.construction.plancompiler;
11 11
12import org.apache.log4j.Logger; 12import org.apache.log4j.Logger;
13import org.eclipse.emf.ecore.util.EcoreUtil;
13import tools.refinery.interpreter.matchers.InterpreterRuntimeException; 14import tools.refinery.interpreter.matchers.InterpreterRuntimeException;
14import tools.refinery.interpreter.matchers.backend.CommonQueryHintOptions; 15import tools.refinery.interpreter.matchers.backend.CommonQueryHintOptions;
15import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; 16import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider;
@@ -53,782 +54,843 @@ import java.util.function.Function;
53 * {@link CompiledSubPlan}. 54 * {@link CompiledSubPlan}.
54 * 55 *
55 * @author Bergmann Gabor 56 * @author Bergmann Gabor
56 *
57 */ 57 */
58public class ReteRecipeCompiler { 58public class ReteRecipeCompiler {
59 59
60 private final IQueryPlannerStrategy plannerStrategy; 60 private final IQueryPlannerStrategy plannerStrategy;
61 private final IQueryMetaContext metaContext; 61 private final IQueryMetaContext metaContext;
62 private final IQueryBackendHintProvider hintProvider; 62 private final IQueryBackendHintProvider hintProvider;
63 private final PDisjunctionRewriter normalizer; 63 private final PDisjunctionRewriter normalizer;
64 private final QueryAnalyzer queryAnalyzer; 64 private final QueryAnalyzer queryAnalyzer;
65 private final Logger logger; 65 private final Logger logger;
66 66
67 /** 67 /**
68 * @since 2.2 68 * @since 2.2
69 */ 69 */
70 protected final boolean deleteAndRederiveEvaluation; 70 protected final boolean deleteAndRederiveEvaluation;
71 /** 71 /**
72 * @since 2.4 72 * @since 2.4
73 */ 73 */
74 protected final TimelyConfiguration timelyEvaluation; 74 protected final TimelyConfiguration timelyEvaluation;
75 75
76 /** 76 /**
77 * @since 1.5 77 * @since 1.5
78 */ 78 */
79 public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, 79 public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext,
80 IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer) { 80 IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider,
81 this(plannerStrategy, logger, metaContext, queryCacheContext, hintProvider, queryAnalyzer, false, null); 81 QueryAnalyzer queryAnalyzer) {
82 } 82 this(plannerStrategy, logger, metaContext, queryCacheContext, hintProvider, queryAnalyzer, false, null);
83 83 }
84 /** 84
85 * @since 2.4 85 /**
86 */ 86 * @since 2.4
87 public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, 87 */
88 IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer, 88 public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext,
89 boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { 89 IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider,
90 super(); 90 QueryAnalyzer queryAnalyzer,
91 this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation; 91 boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) {
92 this.timelyEvaluation = timelyEvaluation; 92 super();
93 this.plannerStrategy = plannerStrategy; 93 this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation;
94 this.logger = logger; 94 this.timelyEvaluation = timelyEvaluation;
95 this.metaContext = metaContext; 95 this.plannerStrategy = plannerStrategy;
96 this.queryAnalyzer = queryAnalyzer; 96 this.logger = logger;
97 this.normalizer = new PDisjunctionRewriterCacher(new SurrogateQueryRewriter(), 97 this.metaContext = metaContext;
98 new PBodyNormalizer(metaContext) { 98 this.queryAnalyzer = queryAnalyzer;
99 99 this.normalizer = new PDisjunctionRewriterCacher(new SurrogateQueryRewriter(),
100 @Override 100 new PBodyNormalizer(metaContext) {
101 protected boolean shouldExpandWeakenedAlternatives(PQuery query) { 101
102 QueryEvaluationHint hint = ReteRecipeCompiler.this.hintProvider.getQueryEvaluationHint(query); 102 @Override
103 Boolean expandWeakenedAlternativeConstraints = ReteHintOptions.expandWeakenedAlternativeConstraints 103 protected boolean shouldExpandWeakenedAlternatives(PQuery query) {
104 .getValueOrDefault(hint); 104 QueryEvaluationHint hint = ReteRecipeCompiler.this.hintProvider.getQueryEvaluationHint(query);
105 return expandWeakenedAlternativeConstraints; 105 Boolean expandWeakenedAlternativeConstraints =
106 } 106 ReteHintOptions.expandWeakenedAlternativeConstraints
107 107 .getValueOrDefault(hint);
108 }); 108 return expandWeakenedAlternativeConstraints;
109 this.hintProvider = hintProvider; 109 }
110 } 110
111 111 });
112 static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE; 112 this.hintProvider = hintProvider;
113 113 }
114 // INTERNALLY CACHED 114
115 private Map<PBody, SubPlan> plannerCache = new HashMap<PBody, SubPlan>(); 115 static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE;
116 private Set<PBody> planningInProgress = new HashSet<PBody>(); 116
117 117 // INTERNALLY CACHED
118 private Map<PQuery, CompiledQuery> queryCompilerCache = new HashMap<PQuery, CompiledQuery>(); 118 private Map<PBody, SubPlan> plannerCache = new HashMap<PBody, SubPlan>();
119 private Set<PQuery> compilationInProgress = new HashSet<PQuery>(); 119 private Set<PBody> planningInProgress = new HashSet<PBody>();
120 private IMultiLookup<PQuery, RecursionCutoffPoint> recursionCutoffPoints = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); 120
121 private Map<SubPlan, CompiledSubPlan> subPlanCompilerCache = new HashMap<SubPlan, CompiledSubPlan>(); 121 private Map<PQuery, CompiledQuery> queryCompilerCache = new HashMap<PQuery, CompiledQuery>();
122 private Map<ReteNodeRecipe, SubPlan> compilerBackTrace = new HashMap<ReteNodeRecipe, SubPlan>(); 122 private Set<PQuery> compilationInProgress = new HashSet<PQuery>();
123 123 private IMultiLookup<PQuery, RecursionCutoffPoint> recursionCutoffPoints =
124 /** 124 CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
125 * Clears internal state 125 private Map<SubPlan, CompiledSubPlan> subPlanCompilerCache = new HashMap<SubPlan, CompiledSubPlan>();
126 */ 126 private Map<ReteNodeRecipe, SubPlan> compilerBackTrace = new HashMap<ReteNodeRecipe, SubPlan>();
127 public void reset() { 127
128 plannerCache.clear(); 128 /**
129 planningInProgress.clear(); 129 * Clears internal state
130 queryCompilerCache.clear(); 130 */
131 subPlanCompilerCache.clear(); 131 public void reset() {
132 compilerBackTrace.clear(); 132 plannerCache.clear();
133 } 133 planningInProgress.clear();
134 134 queryCompilerCache.clear();
135 /** 135 subPlanCompilerCache.clear();
136 * Returns a {@link CompiledQuery} compiled from a query 136 compilerBackTrace.clear();
137 * @throws InterpreterRuntimeException 137 }
138 */ 138
139 public CompiledQuery getCompiledForm(PQuery query) { 139 /**
140 CompiledQuery compiled = queryCompilerCache.get(query); 140 * Returns a {@link CompiledQuery} compiled from a query
141 if (compiled == null) { 141 *
142 142 * @throws InterpreterRuntimeException
143 IRewriterTraceCollector traceCollector = CommonQueryHintOptions.normalizationTraceCollector 143 */
144 .getValueOrDefault(hintProvider.getQueryEvaluationHint(query)); 144 public CompiledQuery getCompiledForm(PQuery query) {
145 if (traceCollector != null) { 145 CompiledQuery compiled = queryCompilerCache.get(query);
146 traceCollector.addTrace(query, query); 146 if (compiled == null) {
147 } 147
148 148 IRewriterTraceCollector traceCollector = CommonQueryHintOptions.normalizationTraceCollector
149 boolean reentrant = !compilationInProgress.add(query); 149 .getValueOrDefault(hintProvider.getQueryEvaluationHint(query));
150 if (reentrant) { // oops, recursion into body in progress 150 if (traceCollector != null) {
151 RecursionCutoffPoint cutoffPoint = new RecursionCutoffPoint(query, getHints(query), metaContext, 151 traceCollector.addTrace(query, query);
152 deleteAndRederiveEvaluation, timelyEvaluation); 152 }
153 recursionCutoffPoints.addPair(query, cutoffPoint); 153
154 return cutoffPoint.getCompiledQuery(); 154 boolean reentrant = !compilationInProgress.add(query);
155 } else { // not reentrant, therefore no recursion, do the compilation 155 if (reentrant) { // oops, recursion into body in progress
156 try { 156 RecursionCutoffPoint cutoffPoint = new RecursionCutoffPoint(query, getHints(query), metaContext,
157 compiled = compileProduction(query); 157 deleteAndRederiveEvaluation, timelyEvaluation);
158 queryCompilerCache.put(query, compiled); 158 recursionCutoffPoints.addPair(query, cutoffPoint);
159 // backTrace.put(compiled.getRecipe(), plan); 159 return cutoffPoint.getCompiledQuery();
160 160 } else { // not reentrant, therefore no recursion, do the compilation
161 // if this was a recursive query, mend all points where recursion was cut off 161 try {
162 for (RecursionCutoffPoint cutoffPoint : recursionCutoffPoints.lookupOrEmpty(query)) { 162 compiled = compileProduction(query);
163 cutoffPoint.mend(compiled); 163 queryCompilerCache.put(query, compiled);
164 } 164 // backTrace.put(compiled.getRecipe(), plan);
165 } finally { 165
166 compilationInProgress.remove(query); 166 // if this was a recursive query, mend all points where recursion was cut off
167 } 167 for (RecursionCutoffPoint cutoffPoint : recursionCutoffPoints.lookupOrEmpty(query)) {
168 } 168 cutoffPoint.mend(compiled);
169 } 169 }
170 return compiled; 170 } finally {
171 } 171 compilationInProgress.remove(query);
172 172 }
173 /** 173 }
174 * Returns a {@link CompiledSubPlan} compiled from a query plan 174 }
175 * @throws InterpreterRuntimeException 175 return compiled;
176 */ 176 }
177 public CompiledSubPlan getCompiledForm(SubPlan plan) { 177
178 CompiledSubPlan compiled = subPlanCompilerCache.get(plan); 178 /**
179 if (compiled == null) { 179 * Returns a {@link CompiledSubPlan} compiled from a query plan
180 compiled = doCompileDispatch(plan); 180 *
181 subPlanCompilerCache.put(plan, compiled); 181 * @throws InterpreterRuntimeException
182 compilerBackTrace.put(compiled.getRecipe(), plan); 182 */
183 } 183 public CompiledSubPlan getCompiledForm(SubPlan plan) {
184 return compiled; 184 CompiledSubPlan compiled = subPlanCompilerCache.get(plan);
185 } 185 if (compiled == null) {
186 186 compiled = doCompileDispatch(plan);
187 /** 187 subPlanCompilerCache.put(plan, compiled);
188 * @throws InterpreterRuntimeException 188 compilerBackTrace.put(compiled.getRecipe(), plan);
189 */ 189 }
190 public SubPlan getPlan(PBody pBody) { 190 return compiled;
191 // if the query is not marked as being compiled, initiate compilation 191 }
192 // (this is useful in case of recursion if getPlan() is the entry point) 192
193 PQuery pQuery = pBody.getPattern(); 193 /**
194 if (!compilationInProgress.contains(pQuery)) 194 * @throws InterpreterRuntimeException
195 getCompiledForm(pQuery); 195 */
196 196 public SubPlan getPlan(PBody pBody) {
197 // Is the plan already cached? 197 // if the query is not marked as being compiled, initiate compilation
198 SubPlan plan = plannerCache.get(pBody); 198 // (this is useful in case of recursion if getPlan() is the entry point)
199 if (plan == null) { 199 PQuery pQuery = pBody.getPattern();
200 boolean reentrant = !planningInProgress.add(pBody); 200 if (!compilationInProgress.contains(pQuery))
201 if (reentrant) { // oops, recursion into body in progress 201 getCompiledForm(pQuery);
202 throw new IllegalArgumentException( 202
203 "Planning-level recursion unsupported: " + pBody.getPattern().getFullyQualifiedName()); 203 // Is the plan already cached?
204 } else { // not reentrant, therefore no recursion, do the planning 204 SubPlan plan = plannerCache.get(pBody);
205 try { 205 if (plan == null) {
206 plan = plannerStrategy.plan(pBody, logger, metaContext); 206 boolean reentrant = !planningInProgress.add(pBody);
207 plannerCache.put(pBody, plan); 207 if (reentrant) { // oops, recursion into body in progress
208 } finally { 208 throw new IllegalArgumentException(
209 planningInProgress.remove(pBody); 209 "Planning-level recursion unsupported: " + pBody.getPattern().getFullyQualifiedName());
210 } 210 } else { // not reentrant, therefore no recursion, do the planning
211 } 211 try {
212 } 212 plan = plannerStrategy.plan(pBody, logger, metaContext);
213 return plan; 213 plannerCache.put(pBody, plan);
214 } 214 } finally {
215 215 planningInProgress.remove(pBody);
216 private CompiledQuery compileProduction(PQuery query) { 216 }
217 Collection<SubPlan> bodyPlans = new ArrayList<SubPlan>(); 217 }
218 normalizer.setTraceCollector(CommonQueryHintOptions.normalizationTraceCollector 218 }
219 .getValueOrDefault(hintProvider.getQueryEvaluationHint(query))); 219 return plan;
220 for (PBody pBody : normalizer.rewrite(query).getBodies()) { 220 }
221 SubPlan bodyPlan = getPlan(pBody); 221
222 bodyPlans.add(bodyPlan); 222 private CompiledQuery compileProduction(PQuery query) {
223 } 223 Collection<SubPlan> bodyPlans = new ArrayList<SubPlan>();
224 return doCompileProduction(query, bodyPlans); 224 normalizer.setTraceCollector(CommonQueryHintOptions.normalizationTraceCollector
225 } 225 .getValueOrDefault(hintProvider.getQueryEvaluationHint(query)));
226 226 for (PBody pBody : normalizer.rewrite(query).getBodies()) {
227 private CompiledQuery doCompileProduction(PQuery query, Collection<SubPlan> bodies) { 227 SubPlan bodyPlan = getPlan(pBody);
228 // TODO skip production node if there is just one body and no projection needed? 228 bodyPlans.add(bodyPlan);
229 Map<PBody, RecipeTraceInfo> bodyFinalTraces = new HashMap<PBody, RecipeTraceInfo>(); 229 }
230 Collection<ReteNodeRecipe> bodyFinalRecipes = new HashSet<ReteNodeRecipe>(); 230 return doCompileProduction(query, bodyPlans);
231 231 }
232 for (SubPlan bodyFinalPlan : bodies) { 232
233 // skip over any projections at the end 233 private CompiledQuery doCompileProduction(PQuery query, Collection<SubPlan> bodies) {
234 bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); 234 // TODO skip production node if there is just one body and no projection needed?
235 235 Map<PBody, RecipeTraceInfo> bodyFinalTraces = new HashMap<PBody, RecipeTraceInfo>();
236 // TODO checkAndTrimEqualVariables may introduce superfluous trim, 236 Collection<ReteNodeRecipe> bodyFinalRecipes = new HashSet<ReteNodeRecipe>();
237 // but whatever (no uniqueness enforcer needed) 237
238 238 for (SubPlan bodyFinalPlan : bodies) {
239 // compile body 239 // skip over any projections at the end
240 final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); 240 bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan);
241 241
242 // project to parameter list 242 // TODO checkAndTrimEqualVariables may introduce superfluous trim,
243 RecipeTraceInfo finalTrace = projectBodyFinalToParameters(compiledBody, false); 243 // but whatever (no uniqueness enforcer needed)
244 244
245 bodyFinalTraces.put(bodyFinalPlan.getBody(), finalTrace); 245 // compile body
246 bodyFinalRecipes.add(finalTrace.getRecipe()); 246 final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan);
247 } 247
248 248 // project to parameter list
249 CompiledQuery compiled = CompilerHelper.makeQueryTrace(query, bodyFinalTraces, bodyFinalRecipes, 249 RecipeTraceInfo finalTrace = projectBodyFinalToParameters(compiledBody, false);
250 getHints(query), metaContext, deleteAndRederiveEvaluation, timelyEvaluation); 250
251 251 bodyFinalTraces.put(bodyFinalPlan.getBody(), finalTrace);
252 return compiled; 252 bodyFinalRecipes.add(finalTrace.getRecipe());
253 } 253 }
254 254
255 private CompiledSubPlan doCompileDispatch(SubPlan plan) { 255 CompiledQuery compiled = CompilerHelper.makeQueryTrace(query, bodyFinalTraces, bodyFinalRecipes,
256 final POperation operation = plan.getOperation(); 256 getHints(query), metaContext, deleteAndRederiveEvaluation, timelyEvaluation);
257 if (operation instanceof PEnumerate) { 257
258 return doCompileEnumerate(((PEnumerate) operation).getEnumerablePConstraint(), plan); 258 return compiled;
259 } else if (operation instanceof PApply) { 259 }
260 final PConstraint pConstraint = ((PApply) operation).getPConstraint(); 260
261 if (pConstraint instanceof EnumerablePConstraint) { 261 private CompiledSubPlan doCompileDispatch(SubPlan plan) {
262 CompiledSubPlan primaryParent = getCompiledForm(plan.getParentPlans().get(0)); 262 final POperation operation = plan.getOperation();
263 PlanningTrace secondaryParent = doEnumerateDispatch(plan, (EnumerablePConstraint) pConstraint); 263 if (operation instanceof PEnumerate) {
264 return compileToNaturalJoin(plan, primaryParent, secondaryParent); 264 return doCompileEnumerate(((PEnumerate) operation).getEnumerablePConstraint(), plan);
265 } else if (pConstraint instanceof DeferredPConstraint) { 265 } else if (operation instanceof PApply) {
266 return doDeferredDispatch((DeferredPConstraint) pConstraint, plan); 266 final PConstraint pConstraint = ((PApply) operation).getPConstraint();
267 } else { 267 if (pConstraint instanceof EnumerablePConstraint) {
268 throw new IllegalArgumentException("Unsupported PConstraint in query plan: " + plan.toShortString()); 268 CompiledSubPlan primaryParent = getCompiledForm(plan.getParentPlans().get(0));
269 } 269 PlanningTrace secondaryParent = doEnumerateDispatch(plan, (EnumerablePConstraint) pConstraint);
270 } else if (operation instanceof PJoin) { 270 return compileToNaturalJoin(plan, primaryParent, secondaryParent);
271 return doCompileJoin((PJoin) operation, plan); 271 } else if (pConstraint instanceof DeferredPConstraint) {
272 } else if (operation instanceof PProject) { 272 return doDeferredDispatch((DeferredPConstraint) pConstraint, plan);
273 return doCompileProject((PProject) operation, plan); 273 } else {
274 } else if (operation instanceof PStart) { 274 throw new IllegalArgumentException("Unsupported PConstraint in query plan: " + plan.toShortString());
275 return doCompileStart((PStart) operation, plan); 275 }
276 } else { 276 } else if (operation instanceof PJoin) {
277 throw new IllegalArgumentException("Unsupported POperation in query plan: " + plan.toShortString()); 277 return doCompileJoin((PJoin) operation, plan);
278 } 278 } else if (operation instanceof PProject) {
279 } 279 return doCompileProject((PProject) operation, plan);
280 280 } else if (operation instanceof PStart) {
281 private CompiledSubPlan doDeferredDispatch(DeferredPConstraint constraint, SubPlan plan) { 281 return doCompileStart((PStart) operation, plan);
282 final SubPlan parentPlan = plan.getParentPlans().get(0); 282 } else {
283 final CompiledSubPlan parentCompiled = getCompiledForm(parentPlan); 283 throw new IllegalArgumentException("Unsupported POperation in query plan: " + plan.toShortString());
284 if (constraint instanceof Equality) { 284 }
285 return compileDeferred((Equality) constraint, plan, parentPlan, parentCompiled); 285 }
286 } else if (constraint instanceof ExportedParameter) { 286
287 return compileDeferred((ExportedParameter) constraint, plan, parentPlan, parentCompiled); 287 private CompiledSubPlan doDeferredDispatch(DeferredPConstraint constraint, SubPlan plan) {
288 } else if (constraint instanceof Inequality) { 288 final SubPlan parentPlan = plan.getParentPlans().get(0);
289 return compileDeferred((Inequality) constraint, plan, parentPlan, parentCompiled); 289 final CompiledSubPlan parentCompiled = getCompiledForm(parentPlan);
290 } else if (constraint instanceof NegativePatternCall) { 290 if (constraint instanceof Equality) {
291 return compileDeferred((NegativePatternCall) constraint, plan, parentPlan, parentCompiled); 291 return compileDeferred((Equality) constraint, plan, parentPlan, parentCompiled);
292 } else if (constraint instanceof PatternMatchCounter) { 292 } else if (constraint instanceof ExportedParameter) {
293 return compileDeferred((PatternMatchCounter) constraint, plan, parentPlan, parentCompiled); 293 return compileDeferred((ExportedParameter) constraint, plan, parentPlan, parentCompiled);
294 } else if (constraint instanceof AggregatorConstraint) { 294 } else if (constraint instanceof Inequality) {
295 return compileDeferred((AggregatorConstraint) constraint, plan, parentPlan, parentCompiled); 295 return compileDeferred((Inequality) constraint, plan, parentPlan, parentCompiled);
296 } else if (constraint instanceof ExpressionEvaluation) { 296 } else if (constraint instanceof NegativePatternCall) {
297 return compileDeferred((ExpressionEvaluation) constraint, plan, parentPlan, parentCompiled); 297 return compileDeferred((NegativePatternCall) constraint, plan, parentPlan, parentCompiled);
298 } else if (constraint instanceof TypeFilterConstraint) { 298 } else if (constraint instanceof PatternMatchCounter) {
299 return compileDeferred((TypeFilterConstraint) constraint, plan, parentPlan, parentCompiled); 299 return compileDeferred((PatternMatchCounter) constraint, plan, parentPlan, parentCompiled);
300 } 300 } else if (constraint instanceof AggregatorConstraint) {
301 throw new UnsupportedOperationException("Unknown deferred constraint " + constraint); 301 return compileDeferred((AggregatorConstraint) constraint, plan, parentPlan, parentCompiled);
302 } 302 } else if (constraint instanceof LeftJoinConstraint leftJoinConstraint) {
303 303 return compileDeferred(leftJoinConstraint, plan, parentCompiled);
304 private CompiledSubPlan compileDeferred(Equality constraint, SubPlan plan, SubPlan parentPlan, 304 } else if (constraint instanceof ExpressionEvaluation) {
305 CompiledSubPlan parentCompiled) { 305 return compileDeferred((ExpressionEvaluation) constraint, plan, parentPlan, parentCompiled);
306 if (constraint.isMoot()) 306 } else if (constraint instanceof TypeFilterConstraint) {
307 return parentCompiled.cloneFor(plan); 307 return compileDeferred((TypeFilterConstraint) constraint, plan, parentPlan, parentCompiled);
308 308 }
309 Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); 309 throw new UnsupportedOperationException("Unknown deferred constraint " + constraint);
310 Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); 310 }
311 311
312 if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { 312 private CompiledSubPlan compileDeferred(Equality constraint, SubPlan plan, SubPlan parentPlan,
313 Integer indexLower = Math.min(index1, index2); 313 CompiledSubPlan parentCompiled) {
314 Integer indexHigher = Math.max(index1, index2); 314 if (constraint.isMoot())
315 315 return parentCompiled.cloneFor(plan);
316 EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe(); 316
317 equalityFilterRecipe.setParent(parentCompiled.getRecipe()); 317 Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho());
318 equalityFilterRecipe.getIndices().add(indexLower); 318 Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom());
319 equalityFilterRecipe.getIndices().add(indexHigher); 319
320 320 if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) {
321 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), equalityFilterRecipe, parentCompiled); 321 Integer indexLower = Math.min(index1, index2);
322 } else { 322 Integer indexHigher = Math.max(index1, index2);
323 throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", 323
324 plan.toShortString(), parentCompiled.toString())); 324 EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe();
325 } 325 equalityFilterRecipe.setParent(parentCompiled.getRecipe());
326 } 326 equalityFilterRecipe.getIndices().add(indexLower);
327 327 equalityFilterRecipe.getIndices().add(indexHigher);
328 /** 328
329 * Precondition: constantTrace must map to a ConstantRecipe, and all of its variables must be contained in 329 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), equalityFilterRecipe, parentCompiled);
330 * toFilterTrace. 330 } else {
331 */ 331 throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s",
332 private CompiledSubPlan compileConstantFiltering(SubPlan plan, PlanningTrace toFilterTrace, 332 plan.toShortString(), parentCompiled.toString()));
333 ConstantRecipe constantRecipe, List<PVariable> filteredVariables) { 333 }
334 PlanningTrace resultTrace = toFilterTrace; 334 }
335 335
336 int constantVariablesSize = filteredVariables.size(); 336 /**
337 for (int i = 0; i < constantVariablesSize; ++i) { 337 * Precondition: constantTrace must map to a ConstantRecipe, and all of its variables must be contained in
338 Object constantValue = constantRecipe.getConstantValues().get(i); 338 * toFilterTrace.
339 PVariable filteredVariable = filteredVariables.get(i); 339 */
340 int filteredColumn = resultTrace.getVariablesTuple().indexOf(filteredVariable); 340 private CompiledSubPlan compileConstantFiltering(SubPlan plan, PlanningTrace toFilterTrace,
341 341 ConstantRecipe constantRecipe, List<PVariable> filteredVariables) {
342 DiscriminatorDispatcherRecipe dispatcherRecipe = FACTORY.createDiscriminatorDispatcherRecipe(); 342 PlanningTrace resultTrace = toFilterTrace;
343 dispatcherRecipe.setDiscriminationColumnIndex(filteredColumn); 343
344 dispatcherRecipe.setParent(resultTrace.getRecipe()); 344 int constantVariablesSize = filteredVariables.size();
345 345 for (int i = 0; i < constantVariablesSize; ++i) {
346 PlanningTrace dispatcherTrace = new PlanningTrace(plan, resultTrace.getVariablesTuple(), dispatcherRecipe, 346 Object constantValue = constantRecipe.getConstantValues().get(i);
347 resultTrace); 347 PVariable filteredVariable = filteredVariables.get(i);
348 348 int filteredColumn = resultTrace.getVariablesTuple().indexOf(filteredVariable);
349 DiscriminatorBucketRecipe bucketRecipe = FACTORY.createDiscriminatorBucketRecipe(); 349
350 bucketRecipe.setBucketKey(constantValue); 350 DiscriminatorDispatcherRecipe dispatcherRecipe = FACTORY.createDiscriminatorDispatcherRecipe();
351 bucketRecipe.setParent(dispatcherRecipe); 351 dispatcherRecipe.setDiscriminationColumnIndex(filteredColumn);
352 352 dispatcherRecipe.setParent(resultTrace.getRecipe());
353 PlanningTrace bucketTrace = new PlanningTrace(plan, dispatcherTrace.getVariablesTuple(), bucketRecipe, 353
354 dispatcherTrace); 354 PlanningTrace dispatcherTrace = new PlanningTrace(plan, resultTrace.getVariablesTuple(), dispatcherRecipe,
355 355 resultTrace);
356 resultTrace = bucketTrace; 356
357 } 357 DiscriminatorBucketRecipe bucketRecipe = FACTORY.createDiscriminatorBucketRecipe();
358 358 bucketRecipe.setBucketKey(constantValue);
359 return resultTrace.cloneFor(plan); 359 bucketRecipe.setParent(dispatcherRecipe);
360 } 360
361 361 PlanningTrace bucketTrace = new PlanningTrace(plan, dispatcherTrace.getVariablesTuple(), bucketRecipe,
362 private CompiledSubPlan compileDeferred(ExportedParameter constraint, SubPlan plan, SubPlan parentPlan, 362 dispatcherTrace);
363 CompiledSubPlan parentCompiled) { 363
364 return parentCompiled.cloneFor(plan); 364 resultTrace = bucketTrace;
365 } 365 }
366 366
367 private CompiledSubPlan compileDeferred(Inequality constraint, SubPlan plan, SubPlan parentPlan, 367 return resultTrace.cloneFor(plan);
368 CompiledSubPlan parentCompiled) { 368 }
369 if (constraint.isEliminable()) 369
370 return parentCompiled.cloneFor(plan); 370 private CompiledSubPlan compileDeferred(ExportedParameter constraint, SubPlan plan, SubPlan parentPlan,
371 371 CompiledSubPlan parentCompiled) {
372 Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); 372 return parentCompiled.cloneFor(plan);
373 Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); 373 }
374 374
375 if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { 375 private CompiledSubPlan compileDeferred(Inequality constraint, SubPlan plan, SubPlan parentPlan,
376 Integer indexLower = Math.min(index1, index2); 376 CompiledSubPlan parentCompiled) {
377 Integer indexHigher = Math.max(index1, index2); 377 if (constraint.isEliminable())
378 378 return parentCompiled.cloneFor(plan);
379 InequalityFilterRecipe inequalityFilterRecipe = FACTORY.createInequalityFilterRecipe(); 379
380 inequalityFilterRecipe.setParent(parentCompiled.getRecipe()); 380 Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho());
381 inequalityFilterRecipe.setSubject(indexLower); 381 Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom());
382 inequalityFilterRecipe.getInequals().add(indexHigher); 382
383 383 if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) {
384 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), inequalityFilterRecipe, 384 Integer indexLower = Math.min(index1, index2);
385 parentCompiled); 385 Integer indexHigher = Math.max(index1, index2);
386 } else { 386
387 throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", 387 InequalityFilterRecipe inequalityFilterRecipe = FACTORY.createInequalityFilterRecipe();
388 plan.toShortString(), parentCompiled.toString())); 388 inequalityFilterRecipe.setParent(parentCompiled.getRecipe());
389 } 389 inequalityFilterRecipe.setSubject(indexLower);
390 } 390 inequalityFilterRecipe.getInequals().add(indexHigher);
391 391
392 private CompiledSubPlan compileDeferred(TypeFilterConstraint constraint, SubPlan plan, SubPlan parentPlan, 392 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), inequalityFilterRecipe,
393 CompiledSubPlan parentCompiled) { 393 parentCompiled);
394 final IInputKey inputKey = constraint.getInputKey(); 394 } else {
395 if (!metaContext.isStateless(inputKey)) 395 throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s",
396 throw new UnsupportedOperationException( 396 plan.toShortString(), parentCompiled.toString()));
397 "Non-enumerable input keys are currently supported in Rete only if they are stateless, unlike " 397 }
398 + inputKey); 398 }
399 399
400 final Tuple constraintVariables = constraint.getVariablesTuple(); 400 private CompiledSubPlan compileDeferred(TypeFilterConstraint constraint, SubPlan plan, SubPlan parentPlan,
401 final List<PVariable> parentVariables = parentCompiled.getVariablesTuple(); 401 CompiledSubPlan parentCompiled) {
402 402 final IInputKey inputKey = constraint.getInputKey();
403 Mask mask; // select elements of the tuple to check against extensional relation 403 if (!metaContext.isStateless(inputKey))
404 if (Tuples.flatTupleOf(parentVariables.toArray()).equals(constraintVariables)) { 404 throw new UnsupportedOperationException(
405 mask = null; // lucky case, parent signature equals that of input key 405 "Non-enumerable input keys are currently supported in Rete only if they are stateless, unlike "
406 } else { 406 + inputKey);
407 List<PVariable> variables = new ArrayList<PVariable>(); 407
408 for (Object variable : constraintVariables.getElements()) { 408 final Tuple constraintVariables = constraint.getVariablesTuple();
409 variables.add((PVariable) variable); 409 final List<PVariable> parentVariables = parentCompiled.getVariablesTuple();
410 } 410
411 mask = CompilerHelper.makeProjectionMask(parentCompiled, variables); 411 Mask mask; // select elements of the tuple to check against extensional relation
412 } 412 if (Tuples.flatTupleOf(parentVariables.toArray()).equals(constraintVariables)) {
413 InputFilterRecipe inputFilterRecipe = RecipesHelper.inputFilterRecipe(parentCompiled.getRecipe(), inputKey, 413 mask = null; // lucky case, parent signature equals that of input key
414 inputKey.getStringID(), mask); 414 } else {
415 return new CompiledSubPlan(plan, parentVariables, inputFilterRecipe, parentCompiled); 415 List<PVariable> variables = new ArrayList<PVariable>();
416 } 416 for (Object variable : constraintVariables.getElements()) {
417 417 variables.add((PVariable) variable);
418 private CompiledSubPlan compileDeferred(NegativePatternCall constraint, SubPlan plan, SubPlan parentPlan, 418 }
419 CompiledSubPlan parentCompiled) { 419 mask = CompilerHelper.makeProjectionMask(parentCompiled, variables);
420 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, 420 }
421 constraint.getActualParametersTuple()); 421 InputFilterRecipe inputFilterRecipe = RecipesHelper.inputFilterRecipe(parentCompiled.getRecipe(), inputKey,
422 422 inputKey.getStringID(), mask);
423 CompilerHelper.JoinHelper joinHelper = new CompilerHelper.JoinHelper(plan, parentCompiled, callTrace); 423 return new CompiledSubPlan(plan, parentVariables, inputFilterRecipe, parentCompiled);
424 final RecipeTraceInfo primaryIndexer = joinHelper.getPrimaryIndexer(); 424 }
425 final RecipeTraceInfo secondaryIndexer = joinHelper.getSecondaryIndexer(); 425
426 426 private CompiledSubPlan compileDeferred(NegativePatternCall constraint, SubPlan plan, SubPlan parentPlan,
427 AntiJoinRecipe antiJoinRecipe = FACTORY.createAntiJoinRecipe(); 427 CompiledSubPlan parentCompiled) {
428 antiJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); 428 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan,
429 antiJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe()); 429 constraint.getActualParametersTuple());
430 430
431 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), antiJoinRecipe, primaryIndexer, 431 CompilerHelper.JoinHelper joinHelper = new CompilerHelper.JoinHelper(plan, parentCompiled, callTrace);
432 secondaryIndexer); 432 final RecipeTraceInfo primaryIndexer = joinHelper.getPrimaryIndexer();
433 } 433 final RecipeTraceInfo secondaryIndexer = joinHelper.getSecondaryIndexer();
434 434
435 private CompiledSubPlan compileDeferred(PatternMatchCounter constraint, SubPlan plan, SubPlan parentPlan, 435 AntiJoinRecipe antiJoinRecipe = FACTORY.createAntiJoinRecipe();
436 CompiledSubPlan parentCompiled) { 436 antiJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
437 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, 437 antiJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe());
438 constraint.getActualParametersTuple()); 438
439 439 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), antiJoinRecipe, primaryIndexer,
440 // hack: use some mask computations (+ the indexers) from a fake natural join against the called query 440 secondaryIndexer);
441 CompilerHelper.JoinHelper fakeJoinHelper = new CompilerHelper.JoinHelper(plan, parentCompiled, callTrace); 441 }
442 final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); 442
443 final RecipeTraceInfo callProjectionIndexer = fakeJoinHelper.getSecondaryIndexer(); 443 private CompiledSubPlan compileDeferred(PatternMatchCounter constraint, SubPlan plan, SubPlan parentPlan,
444 444 CompiledSubPlan parentCompiled) {
445 final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>( 445 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan,
446 fakeJoinHelper.getSecondaryMask().transform(callTrace.getVariablesTuple())); 446 constraint.getActualParametersTuple());
447 /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); 447
448 448 // hack: use some mask computations (+ the indexers) from a fake natural join against the called query
449 CountAggregatorRecipe aggregatorRecipe = FACTORY.createCountAggregatorRecipe(); 449 CompilerHelper.JoinHelper fakeJoinHelper = new CompilerHelper.JoinHelper(plan, parentCompiled, callTrace);
450 aggregatorRecipe.setParent((ProjectionIndexerRecipe) callProjectionIndexer.getRecipe()); 450 final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer();
451 PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, 451 final RecipeTraceInfo callProjectionIndexer = fakeJoinHelper.getSecondaryIndexer();
452 callProjectionIndexer); 452
453 453 final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>(
454 IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); 454 fakeJoinHelper.getSecondaryMask().transform(callTrace.getVariablesTuple()));
455 aggregatorIndexerRecipe.setParent(aggregatorRecipe); 455 /* if (!booleanCheck) */
456 // aggregatorIndexerRecipe.setMask(RecipesHelper.mask( 456 sideVariablesTuple.add(constraint.getResultVariable());
457 // sideVariablesTuple.size(), 457
458 // //use same indices as in the projection indexer 458 CountAggregatorRecipe aggregatorRecipe = FACTORY.createCountAggregatorRecipe();
459 // // EVEN if result variable already visible in left parent 459 aggregatorRecipe.setParent((ProjectionIndexerRecipe) callProjectionIndexer.getRecipe());
460 // fakeJoinHelper.getSecondaryMask().indices 460 PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe,
461 // )); 461 callProjectionIndexer);
462 462
463 int aggregatorWidth = sideVariablesTuple.size(); 463 IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe();
464 int aggregateResultIndex = aggregatorWidth - 1; 464 aggregatorIndexerRecipe.setParent(aggregatorRecipe);
465 465 // aggregatorIndexerRecipe.setMask(RecipesHelper.mask(
466 aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( 466 // sideVariablesTuple.size(),
467 // aggregate according all but the last index 467 // //use same indices as in the projection indexer
468 aggregateResultIndex, aggregatorWidth))); 468 // // EVEN if result variable already visible in left parent
469 PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, 469 // fakeJoinHelper.getSecondaryMask().indices
470 aggregatorTrace); 470 // ));
471 471
472 JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); 472 int aggregatorWidth = sideVariablesTuple.size();
473 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); 473 int aggregateResultIndex = aggregatorWidth - 1;
474 naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); 474
475 naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, 475 aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit(
476 // extend with last element only - the computation value 476 // aggregate according all but the last index
477 aggregateResultIndex)); 477 aggregateResultIndex, aggregatorWidth)));
478 478 PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe,
479 // what if the new variable already has a value? 479 aggregatorTrace);
480 // even if already known, we add the new result variable, so that it can be filtered at the end 480
481 // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); 481 JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe();
482 482 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
483 final List<PVariable> aggregatedVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple()); 483 naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe);
484 aggregatedVariablesTuple.add(constraint.getResultVariable()); 484 naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth,
485 485 // extend with last element only - the computation value
486 PlanningTrace joinTrace = new PlanningTrace(plan, aggregatedVariablesTuple, naturalJoinRecipe, primaryIndexer, 486 aggregateResultIndex));
487 aggregatorIndexerTrace); 487
488 488 // what if the new variable already has a value?
489 return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); 489 // even if already known, we add the new result variable, so that it can be filtered at the end
490 // if (!alreadyKnown) { 490 // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable());
491 // return joinTrace.cloneFor(plan); 491
492 // } else { 492 final List<PVariable> aggregatedVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple());
493 // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); 493 aggregatedVariablesTuple.add(constraint.getResultVariable());
494 // } 494
495 } 495 PlanningTrace joinTrace = new PlanningTrace(plan, aggregatedVariablesTuple, naturalJoinRecipe, primaryIndexer,
496 496 aggregatorIndexerTrace);
497 private CompiledSubPlan compileDeferred(AggregatorConstraint constraint, SubPlan plan, SubPlan parentPlan, 497
498 CompiledSubPlan parentCompiled) { 498 return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan);
499 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, 499 // if (!alreadyKnown) {
500 constraint.getActualParametersTuple()); 500 // return joinTrace.cloneFor(plan);
501 501 // } else {
502 // hack: use some mask computations (+ the indexers) from a fake natural join against the called query 502 // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple());
503 CompilerHelper.JoinHelper fakeJoinHelper = new CompilerHelper.JoinHelper(plan, parentCompiled, callTrace); 503 // }
504 final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); 504 }
505 TupleMask callGroupMask = fakeJoinHelper.getSecondaryMask(); 505
506 506 private CompiledSubPlan compileDeferred(AggregatorConstraint constraint, SubPlan plan, SubPlan parentPlan,
507 final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>( 507 CompiledSubPlan parentCompiled) {
508 callGroupMask.transform(callTrace.getVariablesTuple())); 508 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan,
509 /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); 509 constraint.getActualParametersTuple());
510 510
511 IMultisetAggregationOperator<?, ?, ?> operator = constraint.getAggregator().getOperator(); 511 // hack: use some mask computations (+ the indexers) from a fake natural join against the called query
512 512 CompilerHelper.JoinHelper fakeJoinHelper = new CompilerHelper.JoinHelper(plan, parentCompiled, callTrace);
513 SingleColumnAggregatorRecipe columnAggregatorRecipe = FACTORY.createSingleColumnAggregatorRecipe(); 513 final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer();
514 columnAggregatorRecipe.setParent(callTrace.getRecipe()); 514 TupleMask callGroupMask = fakeJoinHelper.getSecondaryMask();
515 columnAggregatorRecipe.setMultisetAggregationOperator(operator); 515
516 516 final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>(
517 int columnIndex = constraint.getAggregatedColumn(); 517 callGroupMask.transform(callTrace.getVariablesTuple()));
518 IPosetComparator posetComparator = null; 518 /* if (!booleanCheck) */
519 Mask groupMask = CompilerHelper.toRecipeMask(callGroupMask); 519 sideVariablesTuple.add(constraint.getResultVariable());
520 520
521 // temporary solution to support the deprecated option for now 521 IMultisetAggregationOperator<?, ?, ?> operator = constraint.getAggregator().getOperator();
522 final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(plan)); 522
523 523 SingleColumnAggregatorRecipe columnAggregatorRecipe = FACTORY.createSingleColumnAggregatorRecipe();
524 columnAggregatorRecipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); 524 columnAggregatorRecipe.setParent(callTrace.getRecipe());
525 if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { 525 columnAggregatorRecipe.setMultisetAggregationOperator(operator);
526 List<PParameter> parameters = constraint.getReferredQuery().getParameters(); 526
527 IInputKey key = parameters.get(columnIndex).getDeclaredUnaryType(); 527 int columnIndex = constraint.getAggregatedColumn();
528 if (key != null && metaContext.isPosetKey(key)) { 528 IPosetComparator posetComparator = null;
529 posetComparator = metaContext.getPosetComparator(Collections.singleton(key)); 529 Mask groupMask = CompilerHelper.toRecipeMask(callGroupMask);
530 } 530
531 } 531 // temporary solution to support the deprecated option for now
532 532 final boolean deleteAndRederiveEvaluationDep =
533 if (posetComparator == null) { 533 this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(plan));
534 columnAggregatorRecipe.setGroupByMask(groupMask); 534
535 columnAggregatorRecipe.setAggregableIndex(columnIndex); 535 columnAggregatorRecipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep);
536 } else { 536 if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) {
537 MonotonicityInfo monotonicityInfo = FACTORY.createMonotonicityInfo(); 537 List<PParameter> parameters = constraint.getReferredQuery().getParameters();
538 monotonicityInfo.setCoreMask(groupMask); 538 IInputKey key = parameters.get(columnIndex).getDeclaredUnaryType();
539 monotonicityInfo.setPosetMask(CompilerHelper.toRecipeMask( 539 if (key != null && metaContext.isPosetKey(key)) {
540 TupleMask.selectSingle(columnIndex, constraint.getActualParametersTuple().getSize()))); 540 posetComparator = metaContext.getPosetComparator(Collections.singleton(key));
541 monotonicityInfo.setPosetComparator(posetComparator); 541 }
542 columnAggregatorRecipe.setOptionalMonotonicityInfo(monotonicityInfo); 542 }
543 } 543
544 544 if (posetComparator == null) {
545 ReteNodeRecipe aggregatorRecipe = columnAggregatorRecipe; 545 columnAggregatorRecipe.setGroupByMask(groupMask);
546 PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, callTrace); 546 columnAggregatorRecipe.setAggregableIndex(columnIndex);
547 547 } else {
548 IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); 548 MonotonicityInfo monotonicityInfo = FACTORY.createMonotonicityInfo();
549 aggregatorIndexerRecipe.setParent(aggregatorRecipe); 549 monotonicityInfo.setCoreMask(groupMask);
550 550 monotonicityInfo.setPosetMask(CompilerHelper.toRecipeMask(
551 int aggregatorWidth = sideVariablesTuple.size(); 551 TupleMask.selectSingle(columnIndex, constraint.getActualParametersTuple().getSize())));
552 int aggregateResultIndex = aggregatorWidth - 1; 552 monotonicityInfo.setPosetComparator(posetComparator);
553 553 columnAggregatorRecipe.setOptionalMonotonicityInfo(monotonicityInfo);
554 aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( 554 }
555 // aggregate according all but the last index 555
556 aggregateResultIndex, aggregatorWidth))); 556 ReteNodeRecipe aggregatorRecipe = columnAggregatorRecipe;
557 PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, 557 PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, callTrace);
558 aggregatorTrace); 558
559 559 IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe();
560 JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); 560 aggregatorIndexerRecipe.setParent(aggregatorRecipe);
561 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); 561
562 naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); 562 int aggregatorWidth = sideVariablesTuple.size();
563 naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, 563 int aggregateResultIndex = aggregatorWidth - 1;
564 // extend with last element only - the computation value 564
565 aggregateResultIndex)); 565 aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit(
566 566 // aggregate according all but the last index
567 // what if the new variable already has a value? 567 aggregateResultIndex, aggregatorWidth)));
568 // even if already known, we add the new result variable, so that it can be filtered at the end 568 PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe,
569 // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); 569 aggregatorTrace);
570 570
571 final List<PVariable> finalVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple()); 571 JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe();
572 finalVariablesTuple.add(constraint.getResultVariable()); 572 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
573 573 naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe);
574 PlanningTrace joinTrace = new PlanningTrace(plan, finalVariablesTuple, naturalJoinRecipe, primaryIndexer, 574 naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth,
575 aggregatorIndexerTrace); 575 // extend with last element only - the computation value
576 576 aggregateResultIndex));
577 return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); 577
578 // if (!alreadyKnown) { 578 // what if the new variable already has a value?
579 // return joinTrace.cloneFor(plan); 579 // even if already known, we add the new result variable, so that it can be filtered at the end
580 // } else { 580 // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable());
581 // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); 581
582 // } 582 final List<PVariable> finalVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple());
583 } 583 finalVariablesTuple.add(constraint.getResultVariable());
584 584
585 private CompiledSubPlan compileDeferred(ExpressionEvaluation constraint, SubPlan plan, SubPlan parentPlan, 585 PlanningTrace joinTrace = new PlanningTrace(plan, finalVariablesTuple, naturalJoinRecipe, primaryIndexer,
586 CompiledSubPlan parentCompiled) { 586 aggregatorIndexerTrace);
587 Map<String, Integer> tupleNameMap = new HashMap<String, Integer>(); 587
588 for (String name : constraint.getEvaluator().getInputParameterNames()) { 588 return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan);
589 Map<? extends Object, Integer> index = parentCompiled.getPosMapping(); 589 // if (!alreadyKnown) {
590 PVariable variable = constraint.getPSystem().getVariableByNameChecked(name); 590 // return joinTrace.cloneFor(plan);
591 Integer position = index.get(variable); 591 // } else {
592 tupleNameMap.put(name, position); 592 // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple());
593 } 593 // }
594 594 }
595 final PVariable outputVariable = constraint.getOutputVariable(); 595
596 final boolean booleanCheck = outputVariable == null; 596 private CompiledSubPlan compileDeferred(LeftJoinConstraint constraint, SubPlan plan,
597 597 CompiledSubPlan parentCompiled) {
598 // TODO determine whether expression is costly 598 var callTrace = referQuery(constraint.getReferredQuery(), plan, constraint.getActualParametersTuple());
599 boolean cacheOutput = ReteHintOptions.cacheOutputOfEvaluatorsByDefault.getValueOrDefault(getHints(plan)); 599 var fakeJoinHelper = new CompilerHelper.JoinHelper(plan, parentCompiled, callTrace);
600 // for (PAnnotation pAnnotation : 600 var primaryIndexer = fakeJoinHelper.getPrimaryIndexer();
601 // plan.getBody().getPattern().getAnnotationsByName(EXPRESSION_EVALUATION_ANNOTATION"")) { 601 var secondaryIndexer = fakeJoinHelper.getSecondaryIndexer();
602 // for (Object value : pAnnotation.getAllValues("expensive")) { 602
603 // if (value instanceof Boolean) 603 var sideVariablesTuple = CompilerHelper.convertVariablesTuple(constraint.getActualParametersTuple());
604 // cacheOutput = (boolean) value; 604 var resultVariable = constraint.getResultVariable();
605 // } 605 sideVariablesTuple.set(constraint.getOptionalColumn(), resultVariable);
606 // } 606
607 607 var leftNodeRecipe = FACTORY.createOuterJoinNodeRecipe();
608 ExpressionEnforcerRecipe enforcerRecipe = booleanCheck ? FACTORY.createCheckRecipe() 608 var secondaryIndexerRecipe = (ProjectionIndexerRecipe) secondaryIndexer.getRecipe();
609 : FACTORY.createEvalRecipe(); 609 leftNodeRecipe.setParent(secondaryIndexerRecipe);
610 enforcerRecipe.setParent(parentCompiled.getRecipe()); 610 leftNodeRecipe.setDefaultValue(constraint.getDefaultValue());
611 enforcerRecipe.setExpression(RecipesHelper.expressionDefinition(constraint.getEvaluator())); 611 var leftNodeTrace = new PlanningTrace(plan, sideVariablesTuple, leftNodeRecipe, secondaryIndexer);
612 enforcerRecipe.setCacheOutput(cacheOutput); 612
613 if (enforcerRecipe instanceof EvalRecipe) { 613 var leftIndexerRecipe = FACTORY.createOuterJoinIndexerRecipe();
614 ((EvalRecipe) enforcerRecipe).setUnwinding(constraint.isUnwinding()); 614 leftIndexerRecipe.setParent(leftIndexerRecipe);
615 } 615 // Must make a copy of the mask here, because we are already using secondaryIndexerRecipe in the plan an mask
616 for (Entry<String, Integer> entry : tupleNameMap.entrySet()) { 616 // is a containment reference.
617 enforcerRecipe.getMappedIndices().put(entry.getKey(), entry.getValue()); 617 var copyOfMask = EcoreUtil.copy(secondaryIndexerRecipe.getMask());
618 } 618 leftIndexerRecipe.setMask(copyOfMask);
619 619 var leftIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, leftIndexerRecipe, leftNodeTrace);
620 final List<PVariable> enforcerVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple()); 620
621 if (!booleanCheck) 621 var naturalJoinRecipe = FACTORY.createJoinRecipe();
622 enforcerVariablesTuple.add(outputVariable); 622 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
623 PlanningTrace enforcerTrace = new PlanningTrace(plan, enforcerVariablesTuple, enforcerRecipe, parentCompiled); 623 naturalJoinRecipe.setRightParent(leftIndexerRecipe);
624 624 var complementaryMask = fakeJoinHelper.getNaturalJoinRecipe().getRightParentComplementaryMask();
625 return CompilerHelper.checkAndTrimEqualVariables(plan, enforcerTrace).cloneFor(plan); 625 naturalJoinRecipe.setRightParentComplementaryMask(complementaryMask);
626 } 626
627 627 var primaryVariablesTuple = parentCompiled.getVariablesTuple();
628 private CompiledSubPlan doCompileJoin(PJoin operation, SubPlan plan) { 628 var joinedVariablesTuple = new ArrayList<PVariable>(primaryVariablesTuple.size() + 1);
629 final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan); 629 joinedVariablesTuple.addAll(primaryVariablesTuple);
630 final CompiledSubPlan leftCompiled = compiledParents.get(0); 630 joinedVariablesTuple.add(resultVariable);
631 final CompiledSubPlan rightCompiled = compiledParents.get(1); 631 var joinTrace = new PlanningTrace(plan, joinedVariablesTuple, naturalJoinRecipe, primaryIndexer,
632 632 leftIndexerTrace);
633 return compileToNaturalJoin(plan, leftCompiled, rightCompiled); 633
634 } 634 return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan);
635 635 }
636 private CompiledSubPlan compileToNaturalJoin(SubPlan plan, final PlanningTrace leftCompiled, 636
637 final PlanningTrace rightCompiled) { 637 private CompiledSubPlan compileDeferred(ExpressionEvaluation constraint, SubPlan plan, SubPlan parentPlan,
638 // CHECK IF SPECIAL CASE 638 CompiledSubPlan parentCompiled) {
639 639 Map<String, Integer> tupleNameMap = new HashMap<String, Integer>();
640 // Is constant filtering applicable? 640 for (String name : constraint.getEvaluator().getInputParameterNames()) {
641 if (ReteHintOptions.useDiscriminatorDispatchersForConstantFiltering.getValueOrDefault(getHints(plan))) { 641 Map<? extends Object, Integer> index = parentCompiled.getPosMapping();
642 if (leftCompiled.getRecipe() instanceof ConstantRecipe 642 PVariable variable = constraint.getPSystem().getVariableByNameChecked(name);
643 && rightCompiled.getVariablesTuple().containsAll(leftCompiled.getVariablesTuple())) { 643 Integer position = index.get(variable);
644 return compileConstantFiltering(plan, rightCompiled, (ConstantRecipe) leftCompiled.getRecipe(), 644 tupleNameMap.put(name, position);
645 leftCompiled.getVariablesTuple()); 645 }
646 } 646
647 if (rightCompiled.getRecipe() instanceof ConstantRecipe 647 final PVariable outputVariable = constraint.getOutputVariable();
648 && leftCompiled.getVariablesTuple().containsAll(rightCompiled.getVariablesTuple())) { 648 final boolean booleanCheck = outputVariable == null;
649 return compileConstantFiltering(plan, leftCompiled, (ConstantRecipe) rightCompiled.getRecipe(), 649
650 rightCompiled.getVariablesTuple()); 650 // TODO determine whether expression is costly
651 } 651 boolean cacheOutput = ReteHintOptions.cacheOutputOfEvaluatorsByDefault.getValueOrDefault(getHints(plan));
652 } 652 // for (PAnnotation pAnnotation :
653 653 // plan.getBody().getPattern().getAnnotationsByName(EXPRESSION_EVALUATION_ANNOTATION"")) {
654 // ELSE: ACTUAL JOIN 654 // for (Object value : pAnnotation.getAllValues("expensive")) {
655 CompilerHelper.JoinHelper joinHelper = new CompilerHelper.JoinHelper(plan, leftCompiled, rightCompiled); 655 // if (value instanceof Boolean)
656 return new CompiledSubPlan(plan, joinHelper.getNaturalJoinVariablesTuple(), joinHelper.getNaturalJoinRecipe(), 656 // cacheOutput = (boolean) value;
657 joinHelper.getPrimaryIndexer(), joinHelper.getSecondaryIndexer()); 657 // }
658 } 658 // }
659 659
660 private CompiledSubPlan doCompileProject(PProject operation, SubPlan plan) { 660 ExpressionEnforcerRecipe enforcerRecipe = booleanCheck ? FACTORY.createCheckRecipe()
661 final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan); 661 : FACTORY.createEvalRecipe();
662 final CompiledSubPlan compiledParent = compiledParents.get(0); 662 enforcerRecipe.setParent(parentCompiled.getRecipe());
663 663 enforcerRecipe.setExpression(RecipesHelper.expressionDefinition(constraint.getEvaluator()));
664 List<PVariable> projectedVariables = new ArrayList<PVariable>(operation.getToVariables()); 664 enforcerRecipe.setCacheOutput(cacheOutput);
665 // Determinizing projection: try to keep original order (hopefully facilitates node reuse) 665 if (enforcerRecipe instanceof EvalRecipe) {
666 Map<PVariable, Integer> parentPosMapping = compiledParent.getPosMapping(); 666 ((EvalRecipe) enforcerRecipe).setUnwinding(constraint.isUnwinding());
667 Collections.sort(projectedVariables, Comparator.comparing(parentPosMapping::get)); 667 }
668 668 for (Entry<String, Integer> entry : tupleNameMap.entrySet()) {
669 return doProjectPlan(compiledParent, projectedVariables, true, 669 enforcerRecipe.getMappedIndices().put(entry.getKey(), entry.getValue());
670 parentTrace -> parentTrace.cloneFor(plan), 670 }
671 (recipe, parentTrace) -> new PlanningTrace(plan, projectedVariables, recipe, parentTrace), 671
672 (recipe, parentTrace) -> new CompiledSubPlan(plan, projectedVariables, recipe, parentTrace) 672 final List<PVariable> enforcerVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple());
673 ); 673 if (!booleanCheck)
674 } 674 enforcerVariablesTuple.add(outputVariable);
675 675 PlanningTrace enforcerTrace = new PlanningTrace(plan, enforcerVariablesTuple, enforcerRecipe, parentCompiled);
676 /** 676
677 * Projects a subplan onto the specified variable tuple 677 return CompilerHelper.checkAndTrimEqualVariables(plan, enforcerTrace).cloneFor(plan);
678 * @param compiledParentPlan the compiled form of the subplan 678 }
679 * @param targetVariables list of variables to project to 679
680 * @param enforceUniqueness whether distinctness shall be enforced after the projection. 680 private CompiledSubPlan doCompileJoin(PJoin operation, SubPlan plan) {
681 * Specify false only if directly connecting to a production node. 681 final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan);
682 * @param reinterpretTraceFactory constructs a reinterpreted trace that simply relabels the compiled parent plan, in case it is sufficient 682 final CompiledSubPlan leftCompiled = compiledParents.get(0);
683 * @param intermediateTraceFactory constructs a recipe trace for an intermediate node, given the recipe of the node and its parent trace 683 final CompiledSubPlan rightCompiled = compiledParents.get(1);
684 * @param finalTraceFactory constructs a recipe trace for the final resulting node, given the recipe of the node and its parent trace 684
685 * @since 2.1 685 return compileToNaturalJoin(plan, leftCompiled, rightCompiled);
686 */ 686 }
687 <ResultTrace extends RecipeTraceInfo> ResultTrace doProjectPlan( 687
688 final CompiledSubPlan compiledParentPlan, 688 private CompiledSubPlan compileToNaturalJoin(SubPlan plan, final PlanningTrace leftCompiled,
689 final List<PVariable> targetVariables, 689 final PlanningTrace rightCompiled) {
690 boolean enforceUniqueness, 690 // CHECK IF SPECIAL CASE
691 Function<CompiledSubPlan, ResultTrace> reinterpretTraceFactory, 691
692 BiFunction<ReteNodeRecipe, RecipeTraceInfo, RecipeTraceInfo> intermediateTraceFactory, 692 // Is constant filtering applicable?
693 BiFunction<ReteNodeRecipe, RecipeTraceInfo, ResultTrace> finalTraceFactory) 693 if (ReteHintOptions.useDiscriminatorDispatchersForConstantFiltering.getValueOrDefault(getHints(plan))) {
694 { 694 if (leftCompiled.getRecipe() instanceof ConstantRecipe
695 if (targetVariables.equals(compiledParentPlan.getVariablesTuple())) // no projection needed 695 && rightCompiled.getVariablesTuple().containsAll(leftCompiled.getVariablesTuple())) {
696 return reinterpretTraceFactory.apply(compiledParentPlan); 696 return compileConstantFiltering(plan, rightCompiled, (ConstantRecipe) leftCompiled.getRecipe(),
697 697 leftCompiled.getVariablesTuple());
698 // otherwise, we need at least a trimmer 698 }
699 TrimmerRecipe trimmerRecipe = CompilerHelper.makeTrimmerRecipe(compiledParentPlan, targetVariables); 699 if (rightCompiled.getRecipe() instanceof ConstantRecipe
700 700 && leftCompiled.getVariablesTuple().containsAll(rightCompiled.getVariablesTuple())) {
701 // do we need to eliminate duplicates? 701 return compileConstantFiltering(plan, leftCompiled, (ConstantRecipe) rightCompiled.getRecipe(),
702 SubPlan parentPlan = compiledParentPlan.getSubPlan(); 702 rightCompiled.getVariablesTuple());
703 if (!enforceUniqueness || BuildHelper.areAllVariablesDetermined( 703 }
704 parentPlan, 704 }
705 targetVariables, 705
706 queryAnalyzer, 706 // ELSE: ACTUAL JOIN
707 true)) 707 CompilerHelper.JoinHelper joinHelper = new CompilerHelper.JoinHelper(plan, leftCompiled, rightCompiled);
708 { 708 return new CompiledSubPlan(plan, joinHelper.getNaturalJoinVariablesTuple(), joinHelper.getNaturalJoinRecipe(),
709 // if uniqueness enforcess is unwanted or unneeeded, skip it 709 joinHelper.getPrimaryIndexer(), joinHelper.getSecondaryIndexer());
710 return finalTraceFactory.apply(trimmerRecipe, compiledParentPlan); 710 }
711 } else { 711
712 // add a uniqueness enforcer 712 private CompiledSubPlan doCompileProject(PProject operation, SubPlan plan) {
713 UniquenessEnforcerRecipe recipe = FACTORY.createUniquenessEnforcerRecipe(); 713 final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan);
714 recipe.getParents().add(trimmerRecipe); 714 final CompiledSubPlan compiledParent = compiledParents.get(0);
715 715
716 // temporary solution to support the deprecated option for now 716 List<PVariable> projectedVariables = new ArrayList<PVariable>(operation.getToVariables());
717 final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(parentPlan)); 717 // Determinizing projection: try to keep original order (hopefully facilitates node reuse)
718 718 Map<PVariable, Integer> parentPosMapping = compiledParent.getPosMapping();
719 recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); 719 Collections.sort(projectedVariables, Comparator.comparing(parentPosMapping::get));
720 if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { 720
721 CompilerHelper.PosetTriplet triplet = CompilerHelper.computePosetInfo(targetVariables, parentPlan.getBody(), metaContext); 721 return doProjectPlan(compiledParent, projectedVariables, true,
722 722 parentTrace -> parentTrace.cloneFor(plan),
723 if (triplet.comparator != null) { 723 (recipe, parentTrace) -> new PlanningTrace(plan, projectedVariables, recipe, parentTrace),
724 MonotonicityInfo info = FACTORY.createMonotonicityInfo(); 724 (recipe, parentTrace) -> new CompiledSubPlan(plan, projectedVariables, recipe, parentTrace)
725 info.setCoreMask(triplet.coreMask); 725 );
726 info.setPosetMask(triplet.posetMask); 726 }
727 info.setPosetComparator(triplet.comparator); 727
728 recipe.setOptionalMonotonicityInfo(info); 728 /**
729 } 729 * Projects a subplan onto the specified variable tuple
730 } 730 *
731 731 * @param compiledParentPlan the compiled form of the subplan
732 RecipeTraceInfo trimmerTrace = intermediateTraceFactory.apply(trimmerRecipe, compiledParentPlan); 732 * @param targetVariables list of variables to project to
733 return finalTraceFactory.apply(recipe, trimmerTrace); 733 * @param enforceUniqueness whether distinctness shall be enforced after the projection.
734 } 734 * Specify false only if directly connecting to a production node.
735 } 735 * @param reinterpretTraceFactory constructs a reinterpreted trace that simply relabels the compiled parent plan,
736 736 * in case it is sufficient
737 /** 737 * @param intermediateTraceFactory constructs a recipe trace for an intermediate node, given the recipe of the
738 * Projects the final compiled form of a PBody onto the parameter tuple 738 * node and its parent trace
739 * @param compiledBody the compiled form of the body, with all constraints enforced, not yet projected to query parameters 739 * @param finalTraceFactory constructs a recipe trace for the final resulting node, given the recipe of the
740 * @param enforceUniqueness whether distinctness shall be enforced after the projection. 740 * node and its parent trace
741 * Specify false only if directly connecting to a production node. 741 * @since 2.1
742 * @since 2.1 742 */
743 */ 743 <ResultTrace extends RecipeTraceInfo> ResultTrace doProjectPlan(
744 RecipeTraceInfo projectBodyFinalToParameters( 744 final CompiledSubPlan compiledParentPlan,
745 final CompiledSubPlan compiledBody, 745 final List<PVariable> targetVariables,
746 boolean enforceUniqueness) 746 boolean enforceUniqueness,
747 { 747 Function<CompiledSubPlan, ResultTrace> reinterpretTraceFactory,
748 final PBody body = compiledBody.getSubPlan().getBody(); 748 BiFunction<ReteNodeRecipe, RecipeTraceInfo, RecipeTraceInfo> intermediateTraceFactory,
749 final List<PVariable> parameterList = body.getSymbolicParameterVariables(); 749 BiFunction<ReteNodeRecipe, RecipeTraceInfo, ResultTrace> finalTraceFactory) {
750 750 if (targetVariables.equals(compiledParentPlan.getVariablesTuple())) // no projection needed
751 return doProjectPlan(compiledBody, parameterList, enforceUniqueness, 751 return reinterpretTraceFactory.apply(compiledParentPlan);
752 parentTrace -> parentTrace, 752
753 (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace), 753 // otherwise, we need at least a trimmer
754 (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace) 754 TrimmerRecipe trimmerRecipe = CompilerHelper.makeTrimmerRecipe(compiledParentPlan, targetVariables);
755 ); 755
756 } 756 // do we need to eliminate duplicates?
757 757 SubPlan parentPlan = compiledParentPlan.getSubPlan();
758 private CompiledSubPlan doCompileStart(PStart operation, SubPlan plan) { 758 if (!enforceUniqueness || BuildHelper.areAllVariablesDetermined(
759 if (!operation.getAPrioriVariables().isEmpty()) { 759 parentPlan,
760 throw new IllegalArgumentException("Input variables unsupported by Rete: " + plan.toShortString()); 760 targetVariables,
761 } 761 queryAnalyzer,
762 final ConstantRecipe recipe = FACTORY.createConstantRecipe(); 762 true)) {
763 recipe.getConstantValues().clear(); 763 // if uniqueness enforcess is unwanted or unneeeded, skip it
764 764 return finalTraceFactory.apply(trimmerRecipe, compiledParentPlan);
765 return new CompiledSubPlan(plan, new ArrayList<PVariable>(), recipe); 765 } else {
766 } 766 // add a uniqueness enforcer
767 767 UniquenessEnforcerRecipe recipe = FACTORY.createUniquenessEnforcerRecipe();
768 private CompiledSubPlan doCompileEnumerate(EnumerablePConstraint constraint, SubPlan plan) { 768 recipe.getParents().add(trimmerRecipe);
769 final PlanningTrace trimmedTrace = doEnumerateAndDeduplicate(constraint, plan); 769
770 770 // temporary solution to support the deprecated option for now
771 return trimmedTrace.cloneFor(plan); 771 final boolean deleteAndRederiveEvaluationDep =
772 } 772 this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(parentPlan));
773 773
774 private PlanningTrace doEnumerateAndDeduplicate(EnumerablePConstraint constraint, SubPlan plan) { 774 recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep);
775 final PlanningTrace coreTrace = doEnumerateDispatch(plan, constraint); 775 if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) {
776 final PlanningTrace trimmedTrace = CompilerHelper.checkAndTrimEqualVariables(plan, coreTrace); 776 CompilerHelper.PosetTriplet triplet = CompilerHelper.computePosetInfo(targetVariables,
777 return trimmedTrace; 777 parentPlan.getBody(), metaContext);
778 } 778
779 779 if (triplet.comparator != null) {
780 private PlanningTrace doEnumerateDispatch(SubPlan plan, EnumerablePConstraint constraint) { 780 MonotonicityInfo info = FACTORY.createMonotonicityInfo();
781 if (constraint instanceof RelationEvaluation) { 781 info.setCoreMask(triplet.coreMask);
782 return compileEnumerable(plan, (RelationEvaluation) constraint); 782 info.setPosetMask(triplet.posetMask);
783 } else if (constraint instanceof BinaryTransitiveClosure) { 783 info.setPosetComparator(triplet.comparator);
784 return compileEnumerable(plan, (BinaryTransitiveClosure) constraint); 784 recipe.setOptionalMonotonicityInfo(info);
785 } else if (constraint instanceof BinaryReflexiveTransitiveClosure) { 785 }
786 return compileEnumerable(plan, (BinaryReflexiveTransitiveClosure) constraint); 786 }
787
788 RecipeTraceInfo trimmerTrace = intermediateTraceFactory.apply(trimmerRecipe, compiledParentPlan);
789 return finalTraceFactory.apply(recipe, trimmerTrace);
790 }
791 }
792
793 /**
794 * Projects the final compiled form of a PBody onto the parameter tuple
795 *
796 * @param compiledBody the compiled form of the body, with all constraints enforced, not yet projected to
797 * query parameters
798 * @param enforceUniqueness whether distinctness shall be enforced after the projection.
799 * Specify false only if directly connecting to a production node.
800 * @since 2.1
801 */
802 RecipeTraceInfo projectBodyFinalToParameters(
803 final CompiledSubPlan compiledBody,
804 boolean enforceUniqueness) {
805 final PBody body = compiledBody.getSubPlan().getBody();
806 final List<PVariable> parameterList = body.getSymbolicParameterVariables();
807
808 return doProjectPlan(compiledBody, parameterList, enforceUniqueness,
809 parentTrace -> parentTrace,
810 (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace),
811 (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace)
812 );
813 }
814
815 private CompiledSubPlan doCompileStart(PStart operation, SubPlan plan) {
816 if (!operation.getAPrioriVariables().isEmpty()) {
817 throw new IllegalArgumentException("Input variables unsupported by Rete: " + plan.toShortString());
818 }
819 final ConstantRecipe recipe = FACTORY.createConstantRecipe();
820 recipe.getConstantValues().clear();
821
822 return new CompiledSubPlan(plan, new ArrayList<PVariable>(), recipe);
823 }
824
825 private CompiledSubPlan doCompileEnumerate(EnumerablePConstraint constraint, SubPlan plan) {
826 final PlanningTrace trimmedTrace = doEnumerateAndDeduplicate(constraint, plan);
827
828 return trimmedTrace.cloneFor(plan);
829 }
830
831 private PlanningTrace doEnumerateAndDeduplicate(EnumerablePConstraint constraint, SubPlan plan) {
832 final PlanningTrace coreTrace = doEnumerateDispatch(plan, constraint);
833 final PlanningTrace trimmedTrace = CompilerHelper.checkAndTrimEqualVariables(plan, coreTrace);
834 return trimmedTrace;
835 }
836
837 private PlanningTrace doEnumerateDispatch(SubPlan plan, EnumerablePConstraint constraint) {
838 if (constraint instanceof RelationEvaluation) {
839 return compileEnumerable(plan, (RelationEvaluation) constraint);
840 } else if (constraint instanceof BinaryTransitiveClosure) {
841 return compileEnumerable(plan, (BinaryTransitiveClosure) constraint);
842 } else if (constraint instanceof BinaryReflexiveTransitiveClosure) {
843 return compileEnumerable(plan, (BinaryReflexiveTransitiveClosure) constraint);
787 } else if (constraint instanceof RepresentativeElectionConstraint) { 844 } else if (constraint instanceof RepresentativeElectionConstraint) {
788 return compileEnumerable(plan, (RepresentativeElectionConstraint) constraint); 845 return compileEnumerable(plan, (RepresentativeElectionConstraint) constraint);
789 } else if (constraint instanceof ConstantValue) { 846 } else if (constraint instanceof ConstantValue) {
790 return compileEnumerable(plan, (ConstantValue) constraint); 847 return compileEnumerable(plan, (ConstantValue) constraint);
791 } else if (constraint instanceof PositivePatternCall) { 848 } else if (constraint instanceof PositivePatternCall) {
792 return compileEnumerable(plan, (PositivePatternCall) constraint); 849 return compileEnumerable(plan, (PositivePatternCall) constraint);
793 } else if (constraint instanceof TypeConstraint) { 850 } else if (constraint instanceof TypeConstraint) {
794 return compileEnumerable(plan, (TypeConstraint) constraint); 851 return compileEnumerable(plan, (TypeConstraint) constraint);
795 } 852 }
796 throw new UnsupportedOperationException("Unknown enumerable constraint " + constraint); 853 throw new UnsupportedOperationException("Unknown enumerable constraint " + constraint);
797 } 854 }
798 855
799 private PlanningTrace compileEnumerable(SubPlan plan, BinaryReflexiveTransitiveClosure constraint) { 856 private PlanningTrace compileEnumerable(SubPlan plan, BinaryReflexiveTransitiveClosure constraint) {
800 // TODO the implementation would perform better if an inequality check would be used after tcRecipe and 857 // TODO the implementation would perform better if an inequality check would be used after tcRecipe and
801 // uniqueness enforcer be replaced by a transparent node with multiple parents, but such a node is not available 858 // uniqueness enforcer be replaced by a transparent node with multiple parents, but such a node is not
802 // in recipe metamodel in VIATRA 2.0 859 // available
803 860 // in recipe metamodel in VIATRA 2.0
804 // Find called query 861
805 final PQuery referredQuery = constraint.getSupplierKey(); 862 // Find called query
806 final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); 863 final PQuery referredQuery = constraint.getSupplierKey();
807 864 final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple());
808 // Calculate irreflexive transitive closure 865
809 final TransitiveClosureRecipe tcRecipe = FACTORY.createTransitiveClosureRecipe(); 866 // Calculate irreflexive transitive closure
810 tcRecipe.setParent(callTrace.getRecipe()); 867 final TransitiveClosureRecipe tcRecipe = FACTORY.createTransitiveClosureRecipe();
811 final PlanningTrace tcTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), tcRecipe, callTrace); 868 tcRecipe.setParent(callTrace.getRecipe());
812 869 final PlanningTrace tcTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint),
813 // Enumerate universe type 870 tcRecipe, callTrace);
814 final IInputKey inputKey = constraint.getUniverseType(); 871
815 final InputRecipe universeTypeRecipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); 872 // Enumerate universe type
816 final PlanningTrace universeTypeTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple( 873 final IInputKey inputKey = constraint.getUniverseType();
817 Tuples.staticArityFlatTupleOf(constraint.getVariablesTuple().get(0))), universeTypeRecipe); 874 final InputRecipe universeTypeRecipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(),
818 875 inputKey.getArity());
819 // Calculate reflexive access by duplicating universe type column 876 final PlanningTrace universeTypeTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(
820 final TrimmerRecipe reflexiveRecipe = FACTORY.createTrimmerRecipe(); 877 Tuples.staticArityFlatTupleOf(constraint.getVariablesTuple().get(0))), universeTypeRecipe);
821 reflexiveRecipe.setMask(RecipesHelper.mask(1, 0, 0)); 878
822 reflexiveRecipe.setParent(universeTypeRecipe); 879 // Calculate reflexive access by duplicating universe type column
823 final PlanningTrace reflexiveTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), reflexiveRecipe, universeTypeTrace); 880 final TrimmerRecipe reflexiveRecipe = FACTORY.createTrimmerRecipe();
824 881 reflexiveRecipe.setMask(RecipesHelper.mask(1, 0, 0));
825 // Finally, reduce duplicates after a join 882 reflexiveRecipe.setParent(universeTypeRecipe);
826 final UniquenessEnforcerRecipe brtcRecipe = FACTORY.createUniquenessEnforcerRecipe(); 883 final PlanningTrace reflexiveTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint),
827 brtcRecipe.getParents().add(tcRecipe); 884 reflexiveRecipe, universeTypeTrace);
828 brtcRecipe.getParents().add(reflexiveRecipe); 885
829 886 // Finally, reduce duplicates after a join
830 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), brtcRecipe, reflexiveTrace, tcTrace); 887 final UniquenessEnforcerRecipe brtcRecipe = FACTORY.createUniquenessEnforcerRecipe();
831 } 888 brtcRecipe.getParents().add(tcRecipe);
889 brtcRecipe.getParents().add(reflexiveRecipe);
890
891 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), brtcRecipe, reflexiveTrace,
892 tcTrace);
893 }
832 894
833 private PlanningTrace compileEnumerable(SubPlan plan, RepresentativeElectionConstraint constraint) { 895 private PlanningTrace compileEnumerable(SubPlan plan, RepresentativeElectionConstraint constraint) {
834 var referredQuery = constraint.getSupplierKey(); 896 var referredQuery = constraint.getSupplierKey();
@@ -839,109 +901,110 @@ public class ReteRecipeCompiler {
839 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); 901 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace);
840 } 902 }
841 903
842 private PlanningTrace compileEnumerable(SubPlan plan, BinaryTransitiveClosure constraint) { 904 private PlanningTrace compileEnumerable(SubPlan plan, BinaryTransitiveClosure constraint) {
843 final PQuery referredQuery = constraint.getSupplierKey(); 905 final PQuery referredQuery = constraint.getSupplierKey();
844 final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); 906 final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple());
845 907
846 final TransitiveClosureRecipe recipe = FACTORY.createTransitiveClosureRecipe(); 908 final TransitiveClosureRecipe recipe = FACTORY.createTransitiveClosureRecipe();
847 recipe.setParent(callTrace.getRecipe()); 909 recipe.setParent(callTrace.getRecipe());
848 910
849 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); 911 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace);
850 } 912 }
851 913
852 private PlanningTrace compileEnumerable(SubPlan plan, RelationEvaluation constraint) { 914 private PlanningTrace compileEnumerable(SubPlan plan, RelationEvaluation constraint) {
853 final List<ReteNodeRecipe> parentRecipes = new ArrayList<ReteNodeRecipe>(); 915 final List<ReteNodeRecipe> parentRecipes = new ArrayList<ReteNodeRecipe>();
854 final List<RecipeTraceInfo> parentTraceInfos = new ArrayList<RecipeTraceInfo>(); 916 final List<RecipeTraceInfo> parentTraceInfos = new ArrayList<RecipeTraceInfo>();
855 for (final PQuery inputQuery : constraint.getReferredQueries()) { 917 for (final PQuery inputQuery : constraint.getReferredQueries()) {
856 final CompiledQuery compiledQuery = getCompiledForm(inputQuery); 918 final CompiledQuery compiledQuery = getCompiledForm(inputQuery);
857 parentRecipes.add(compiledQuery.getRecipe()); 919 parentRecipes.add(compiledQuery.getRecipe());
858 parentTraceInfos.add(compiledQuery); 920 parentTraceInfos.add(compiledQuery);
859 } 921 }
860 final RelationEvaluationRecipe recipe = FACTORY.createRelationEvaluationRecipe(); 922 final RelationEvaluationRecipe recipe = FACTORY.createRelationEvaluationRecipe();
861 recipe.getParents().addAll(parentRecipes); 923 recipe.getParents().addAll(parentRecipes);
862 recipe.setEvaluator(RecipesHelper.expressionDefinition(constraint.getEvaluator())); 924 recipe.setEvaluator(RecipesHelper.expressionDefinition(constraint.getEvaluator()));
863 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, parentTraceInfos); 925 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, parentTraceInfos);
864 } 926 }
865 927
866 private PlanningTrace compileEnumerable(SubPlan plan, PositivePatternCall constraint) { 928 private PlanningTrace compileEnumerable(SubPlan plan, PositivePatternCall constraint) {
867 final PQuery referredQuery = constraint.getReferredQuery(); 929 final PQuery referredQuery = constraint.getReferredQuery();
868 return referQuery(referredQuery, plan, constraint.getVariablesTuple()); 930 return referQuery(referredQuery, plan, constraint.getVariablesTuple());
869 } 931 }
870 932
871 private PlanningTrace compileEnumerable(SubPlan plan, TypeConstraint constraint) { 933 private PlanningTrace compileEnumerable(SubPlan plan, TypeConstraint constraint) {
872 final IInputKey inputKey = constraint.getSupplierKey(); 934 final IInputKey inputKey = constraint.getSupplierKey();
873 final InputRecipe recipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); 935 final InputRecipe recipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity());
874 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); 936 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe);
875 } 937 }
876 938
877 private PlanningTrace compileEnumerable(SubPlan plan, ConstantValue constraint) { 939 private PlanningTrace compileEnumerable(SubPlan plan, ConstantValue constraint) {
878 final ConstantRecipe recipe = FACTORY.createConstantRecipe(); 940 final ConstantRecipe recipe = FACTORY.createConstantRecipe();
879 recipe.getConstantValues().add(constraint.getSupplierKey()); 941 recipe.getConstantValues().add(constraint.getSupplierKey());
880 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); 942 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe);
881 } 943 }
882 944
883 // TODO handle recursion 945 // TODO handle recursion
884 private PlanningTrace referQuery(PQuery query, SubPlan plan, Tuple actualParametersTuple) { 946 private PlanningTrace referQuery(PQuery query, SubPlan plan, Tuple actualParametersTuple) {
885 RecipeTraceInfo referredQueryTrace = originalTraceOfReferredQuery(query); 947 RecipeTraceInfo referredQueryTrace = originalTraceOfReferredQuery(query);
886 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(actualParametersTuple), 948 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(actualParametersTuple),
887 referredQueryTrace.getRecipe(), referredQueryTrace.getParentRecipeTracesForCloning()); 949 referredQueryTrace.getRecipe(), referredQueryTrace.getParentRecipeTracesForCloning());
888 } 950 }
889 951
890 private RecipeTraceInfo originalTraceOfReferredQuery(PQuery query) { 952 private RecipeTraceInfo originalTraceOfReferredQuery(PQuery query) {
891 // eliminate superfluous production node? 953 // eliminate superfluous production node?
892 if (PVisibility.EMBEDDED == query.getVisibility()) { // currently inline patterns only 954 if (PVisibility.EMBEDDED == query.getVisibility()) { // currently inline patterns only
893 Set<PBody> rewrittenBodies = normalizer.rewrite(query).getBodies(); 955 Set<PBody> rewrittenBodies = normalizer.rewrite(query).getBodies();
894 if (1 == rewrittenBodies.size()) { // non-disjunctive 956 if (1 == rewrittenBodies.size()) { // non-disjunctive
895 // TODO in the future, check if non-recursive - (not currently permitted) 957 // TODO in the future, check if non-recursive - (not currently permitted)
896 958
897 PBody pBody = rewrittenBodies.iterator().next(); 959 PBody pBody = rewrittenBodies.iterator().next();
898 SubPlan bodyFinalPlan = getPlan(pBody); 960 SubPlan bodyFinalPlan = getPlan(pBody);
899 961
900 // skip over any projections at the end 962 // skip over any projections at the end
901 bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); 963 bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan);
902 964
903 // TODO checkAndTrimEqualVariables may introduce superfluous trim, 965 // TODO checkAndTrimEqualVariables may introduce superfluous trim,
904 // but whatever (no uniqueness enforcer needed) 966 // but whatever (no uniqueness enforcer needed)
905 967
906 // compile body 968 // compile body
907 final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); 969 final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan);
908 970
909 // project to parameter list, add uniqueness enforcer if necessary 971 // project to parameter list, add uniqueness enforcer if necessary
910 return projectBodyFinalToParameters(compiledBody, true /* ensure uniqueness, as no production node is used */); 972 return projectBodyFinalToParameters(compiledBody, true /* ensure uniqueness, as no production node is
911 } 973 used */);
912 } 974 }
913 975 }
914 // otherwise, regular reference to recipe realizing the query 976
915 return getCompiledForm(query); 977 // otherwise, regular reference to recipe realizing the query
916 } 978 return getCompiledForm(query);
917 979 }
918 protected List<CompiledSubPlan> getCompiledFormOfParents(SubPlan plan) { 980
919 List<CompiledSubPlan> results = new ArrayList<CompiledSubPlan>(); 981 protected List<CompiledSubPlan> getCompiledFormOfParents(SubPlan plan) {
920 for (SubPlan parentPlan : plan.getParentPlans()) { 982 List<CompiledSubPlan> results = new ArrayList<CompiledSubPlan>();
921 results.add(getCompiledForm(parentPlan)); 983 for (SubPlan parentPlan : plan.getParentPlans()) {
922 } 984 results.add(getCompiledForm(parentPlan));
923 return results; 985 }
924 } 986 return results;
925 987 }
926 /** 988
927 * Returns an unmodifiable view of currently cached compiled queries. 989 /**
928 */ 990 * Returns an unmodifiable view of currently cached compiled queries.
929 public Map<PQuery, CompiledQuery> getCachedCompiledQueries() { 991 */
930 return Collections.unmodifiableMap(queryCompilerCache); 992 public Map<PQuery, CompiledQuery> getCachedCompiledQueries() {
931 } 993 return Collections.unmodifiableMap(queryCompilerCache);
932 994 }
933 /** 995
934 * Returns an unmodifiable view of currently cached query plans. 996 /**
935 */ 997 * Returns an unmodifiable view of currently cached query plans.
936 public Map<PBody, SubPlan> getCachedQueryPlans() { 998 */
937 return Collections.unmodifiableMap(plannerCache); 999 public Map<PBody, SubPlan> getCachedQueryPlans() {
938 } 1000 return Collections.unmodifiableMap(plannerCache);
939 1001 }
940 private QueryEvaluationHint getHints(SubPlan plan) { 1002
941 return getHints(plan.getBody().getPattern()); 1003 private QueryEvaluationHint getHints(SubPlan plan) {
942 } 1004 return getHints(plan.getBody().getPattern());
943 1005 }
944 private QueryEvaluationHint getHints(PQuery pattern) { 1006
945 return hintProvider.getQueryEvaluationHint(pattern); 1007 private QueryEvaluationHint getHints(PQuery pattern) {
946 } 1008 return hintProvider.getQueryEvaluationHint(pattern);
1009 }
947} 1010}
diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java
index c69757b6..fe70cbc3 100644
--- a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java
+++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java
@@ -11,6 +11,7 @@ package tools.refinery.interpreter.rete.network;
11 11
12import tools.refinery.interpreter.matchers.tuple.Tuple; 12import tools.refinery.interpreter.matchers.tuple.Tuple;
13import tools.refinery.interpreter.rete.aggregation.IndexerBasedAggregatorNode; 13import tools.refinery.interpreter.rete.aggregation.IndexerBasedAggregatorNode;
14import tools.refinery.interpreter.rete.aggregation.LeftJoinNode;
14import tools.refinery.interpreter.rete.boundary.InputConnector; 15import tools.refinery.interpreter.rete.boundary.InputConnector;
15import tools.refinery.interpreter.rete.eval.RelationEvaluatorNode; 16import tools.refinery.interpreter.rete.eval.RelationEvaluatorNode;
16import tools.refinery.interpreter.rete.index.DualInputNode; 17import tools.refinery.interpreter.rete.index.DualInputNode;
@@ -78,9 +79,12 @@ class ConnectionFactory {
78 Slots slots = avoidActiveNodeConflict(parentTraces.get(0), parentTraces.get(1)); 79 Slots slots = avoidActiveNodeConflict(parentTraces.get(0), parentTraces.get(1));
79 beta.connectToIndexers(slots.primary, slots.secondary); 80 beta.connectToIndexers(slots.primary, slots.secondary);
80 } else if (recipe instanceof IndexerBasedAggregatorRecipe) { 81 } else if (recipe instanceof IndexerBasedAggregatorRecipe) {
81 final IndexerBasedAggregatorNode aggregator = (IndexerBasedAggregatorNode) freshNode; 82 final IndexerBasedAggregatorNode aggregator = (IndexerBasedAggregatorNode) freshNode;
82 final IndexerBasedAggregatorRecipe aggregatorRecipe = (IndexerBasedAggregatorRecipe) recipe; 83 final IndexerBasedAggregatorRecipe aggregatorRecipe = (IndexerBasedAggregatorRecipe) recipe;
83 aggregator.initializeWith((ProjectionIndexer) resolveIndexer(aggregatorRecipe.getParent())); 84 aggregator.initializeWith((ProjectionIndexer) resolveIndexer(aggregatorRecipe.getParent()));
85 } else if (recipe instanceof OuterJoinNodeRecipe outerJoinNodeRecipe) {
86 var leftJoinNode = (LeftJoinNode) freshNode;
87 leftJoinNode.initializeWith((ProjectionIndexer) resolveIndexer(outerJoinNodeRecipe.getParent()));
84 } else if (recipe instanceof MultiParentNodeRecipe) { 88 } else if (recipe instanceof MultiParentNodeRecipe) {
85 final Receiver receiver = (Receiver) freshNode; 89 final Receiver receiver = (Receiver) freshNode;
86 List<ReteNodeRecipe> parentRecipes = ((MultiParentNodeRecipe) recipe).getParents(); 90 List<ReteNodeRecipe> parentRecipes = ((MultiParentNodeRecipe) recipe).getParents();
diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java
index 301b757d..1f6a01ae 100644
--- a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java
+++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java
@@ -20,6 +20,7 @@ import tools.refinery.interpreter.matchers.tuple.Tuples;
20import tools.refinery.interpreter.rete.aggregation.ColumnAggregatorNode; 20import tools.refinery.interpreter.rete.aggregation.ColumnAggregatorNode;
21import tools.refinery.interpreter.rete.aggregation.CountNode; 21import tools.refinery.interpreter.rete.aggregation.CountNode;
22import tools.refinery.interpreter.rete.aggregation.IAggregatorNode; 22import tools.refinery.interpreter.rete.aggregation.IAggregatorNode;
23import tools.refinery.interpreter.rete.aggregation.LeftJoinNode;
23import tools.refinery.interpreter.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode; 24import tools.refinery.interpreter.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode;
24import tools.refinery.interpreter.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode; 25import tools.refinery.interpreter.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode;
25import tools.refinery.interpreter.rete.aggregation.timely.FirstOnlyParallelTimelyColumnAggregatorNode; 26import tools.refinery.interpreter.rete.aggregation.timely.FirstOnlyParallelTimelyColumnAggregatorNode;
@@ -72,18 +73,25 @@ class NodeFactory {
72 return parentNode.constructIndex(toMask(recipe.getMask()), traces); 73 return parentNode.constructIndex(toMask(recipe.getMask()), traces);
73 // already traced 74 // already traced
74 } else if (recipe instanceof AggregatorIndexerRecipe) { 75 } else if (recipe instanceof AggregatorIndexerRecipe) {
75 int indexOfAggregateResult = recipe.getParent().getArity(); 76 int indexOfAggregateResult = recipe.getParent().getArity();
76 int resultPosition = recipe.getMask().getSourceIndices().lastIndexOf(indexOfAggregateResult); 77 int resultPosition = recipe.getMask().getSourceIndices().lastIndexOf(indexOfAggregateResult);
77 78
78 IAggregatorNode aggregatorNode = (IAggregatorNode) parentNode; 79 IAggregatorNode aggregatorNode = (IAggregatorNode) parentNode;
79 final Indexer result = (resultPosition == -1) ? aggregatorNode.getAggregatorOuterIndexer() 80 final Indexer result = (resultPosition == -1) ? aggregatorNode.getAggregatorOuterIndexer()
80 : aggregatorNode.getAggregatorOuterIdentityIndexer(resultPosition); 81 : aggregatorNode.getAggregatorOuterIdentityIndexer(resultPosition);
81 82
82 for (TraceInfo traceInfo : traces) 83 for (TraceInfo traceInfo : traces)
83 result.assignTraceInfo(traceInfo); 84 result.assignTraceInfo(traceInfo);
84 return result; 85 return result;
85 } else 86 } else if (recipe instanceof OuterJoinIndexerRecipe) {
86 throw new IllegalArgumentException("Unkown Indexer recipe: " + recipe); 87 var leftJoinNode = (LeftJoinNode) parentNode;
88 var result = leftJoinNode.getOuterIndexer();
89 for (TraceInfo traceInfo : traces)
90 result.assignTraceInfo(traceInfo);
91 return result;
92 } else {
93 throw new IllegalArgumentException("Unkown Indexer recipe: " + recipe);
94 }
87 } 95 }
88 96
89 /** 97 /**
@@ -134,6 +142,8 @@ class NodeFactory {
134 return instantiateNode(reteContainer, (CountAggregatorRecipe) recipe); 142 return instantiateNode(reteContainer, (CountAggregatorRecipe) recipe);
135 if (recipe instanceof SingleColumnAggregatorRecipe) 143 if (recipe instanceof SingleColumnAggregatorRecipe)
136 return instantiateNode(reteContainer, (SingleColumnAggregatorRecipe) recipe); 144 return instantiateNode(reteContainer, (SingleColumnAggregatorRecipe) recipe);
145 if (recipe instanceof OuterJoinNodeRecipe outerJoinNodeRecipe)
146 return instantiateNode(reteContainer, outerJoinNodeRecipe);
137 if (recipe instanceof DiscriminatorDispatcherRecipe) 147 if (recipe instanceof DiscriminatorDispatcherRecipe)
138 return instantiateNode(reteContainer, (DiscriminatorDispatcherRecipe) recipe); 148 return instantiateNode(reteContainer, (DiscriminatorDispatcherRecipe) recipe);
139 if (recipe instanceof DiscriminatorBucketRecipe) 149 if (recipe instanceof DiscriminatorBucketRecipe)
@@ -246,6 +256,10 @@ class NodeFactory {
246 } 256 }
247 } 257 }
248 258
259 private Supplier instantiateNode(ReteContainer reteContainer, OuterJoinNodeRecipe recipe) {
260 return new LeftJoinNode(reteContainer, recipe.getDefaultValue());
261 }
262
249 private Supplier instantiateNode(ReteContainer reteContainer, TransitiveClosureRecipe recipe) { 263 private Supplier instantiateNode(ReteContainer reteContainer, TransitiveClosureRecipe recipe) {
250 return new TransitiveClosureNode(reteContainer); 264 return new TransitiveClosureNode(reteContainer);
251 } 265 }
diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/LeftJoinConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/LeftJoinConstraint.java
new file mode 100644
index 00000000..1c1a895e
--- /dev/null
+++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/LeftJoinConstraint.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro
3 * Copyright (c) 2024 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.interpreter.matchers.psystem.basicdeferred;
10
11import tools.refinery.interpreter.matchers.context.IQueryMetaContext;
12import tools.refinery.interpreter.matchers.psystem.ITypeInfoProviderConstraint;
13import tools.refinery.interpreter.matchers.psystem.PBody;
14import tools.refinery.interpreter.matchers.psystem.PVariable;
15import tools.refinery.interpreter.matchers.psystem.TypeJudgement;
16import tools.refinery.interpreter.matchers.psystem.queries.PQuery;
17import tools.refinery.interpreter.matchers.tuple.Tuple;
18import tools.refinery.interpreter.matchers.tuple.Tuples;
19
20import java.util.Collections;
21import java.util.Set;
22
23public class LeftJoinConstraint extends PatternCallBasedDeferred implements ITypeInfoProviderConstraint {
24 protected PVariable resultVariable;
25 protected int optionalColumn;
26 protected Object defaultValue;
27
28 public LeftJoinConstraint(PBody pBody, Tuple actualParametersTuple, PQuery query, PVariable resultVariable,
29 int optionalColumn, Object defaultValue) {
30 super(pBody, actualParametersTuple, query, Collections.singleton(resultVariable));
31 this.resultVariable = resultVariable;
32 this.optionalColumn = optionalColumn;
33 this.defaultValue = defaultValue;
34 }
35
36 public PVariable getResultVariable() {
37 return resultVariable;
38 }
39
40 public int getOptionalColumn() {
41 return optionalColumn;
42 }
43
44 public Object getDefaultValue() {
45 return defaultValue;
46 }
47
48 @Override
49 public Set<PVariable> getDeducedVariables() {
50 return Collections.singleton(resultVariable);
51 }
52
53 @Override
54 protected void doDoReplaceVariables(PVariable obsolete, PVariable replacement) {
55 if (resultVariable.equals(obsolete)) {
56 resultVariable = replacement;
57 }
58 }
59
60 @Override
61 protected Set<PVariable> getCandidateQuantifiedVariables() {
62 return actualParametersTuple.getDistinctElements();
63 }
64
65 @Override
66 protected String toStringRest() {
67 return query.getFullyQualifiedName() + "@" + actualParametersTuple.toString() + "->"
68 + resultVariable.toString();
69 }
70
71 @Override
72 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context) {
73 var optionalParameter = getReferredQuery().getParameters().get(optionalColumn);
74 var unaryType = optionalParameter.getDeclaredUnaryType();
75 if (unaryType != null && !context.isEnumerable(unaryType)) {
76 // The outer join makes the result variable non-enumerable, since the default value might not be present in
77 // the model.
78 return Set.of(new TypeJudgement(unaryType, Tuples.staticArityFlatTupleOf(resultVariable)));
79 }
80 return Set.of();
81 }
82}
diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java
index 99350185..1e580599 100644
--- a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java
+++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java
@@ -137,7 +137,9 @@ public class PBodyCopier extends AbstractRewriterTraceSource {
137 } else if (constraint instanceof PatternMatchCounter) { 137 } else if (constraint instanceof PatternMatchCounter) {
138 copyPatternMatchCounterConstraint((PatternMatchCounter) constraint); 138 copyPatternMatchCounterConstraint((PatternMatchCounter) constraint);
139 } else if (constraint instanceof AggregatorConstraint) { 139 } else if (constraint instanceof AggregatorConstraint) {
140 copyAggregatorConstraint((AggregatorConstraint) constraint); 140 copyAggregatorConstraint((AggregatorConstraint) constraint);
141 } else if (constraint instanceof LeftJoinConstraint leftJoinConstraint) {
142 copyLeftJoinConstraint((LeftJoinConstraint) constraint);
141 } else if (constraint instanceof ExpressionEvaluation) { 143 } else if (constraint instanceof ExpressionEvaluation) {
142 copyExpressionEvaluationConstraint((ExpressionEvaluation) constraint); 144 copyExpressionEvaluationConstraint((ExpressionEvaluation) constraint);
143 } else { 145 } else {
@@ -256,6 +258,15 @@ public class PBodyCopier extends AbstractRewriterTraceSource {
256 constraint.getReferredQuery(), mappedResultVariable, constraint.getAggregatedColumn())); 258 constraint.getReferredQuery(), mappedResultVariable, constraint.getAggregatedColumn()));
257 } 259 }
258 260
261 protected void copyLeftJoinConstraint(LeftJoinConstraint constraint) {
262 PVariable[] mappedVariables = extractMappedVariables(constraint);
263 PVariable mappedResultVariable = variableMapping.get(constraint.getResultVariable());
264 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
265 addTrace(constraint, new LeftJoinConstraint(body, variablesTuple,
266 constraint.getReferredQuery(), mappedResultVariable, constraint.getOptionalColumn(),
267 constraint.getDefaultValue()));
268 }
269
259 protected void copyExpressionEvaluationConstraint(ExpressionEvaluation expressionEvaluation) { 270 protected void copyExpressionEvaluationConstraint(ExpressionEvaluation expressionEvaluation) {
260 PVariable mappedOutputVariable = variableMapping.get(expressionEvaluation.getOutputVariable()); 271 PVariable mappedOutputVariable = variableMapping.get(expressionEvaluation.getOutputVariable());
261 addTrace(expressionEvaluation, new ExpressionEvaluation(body, 272 addTrace(expressionEvaluation, new ExpressionEvaluation(body,
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
index 587f9acb..a8849b7c 100644
--- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
+++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
@@ -21,7 +21,6 @@ import tools.refinery.language.model.problem.*;
21import tools.refinery.language.utils.ProblemDesugarer; 21import tools.refinery.language.utils.ProblemDesugarer;
22import tools.refinery.language.utils.ProblemUtil; 22import tools.refinery.language.utils.ProblemUtil;
23 23
24import javax.xml.crypto.Data;
25import java.util.List; 24import java.util.List;
26 25
27public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighlightingCalculator { 26public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighlightingCalculator {
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java
index 24205cf4..4d30f998 100644
--- a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/pquery/Dnf2PQuery.java
@@ -1,17 +1,31 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.query.interpreter.internal.pquery; 6package tools.refinery.store.query.interpreter.internal.pquery;
7 7
8import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory;
9import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
10import tools.refinery.interpreter.matchers.context.IInputKey;
11import tools.refinery.interpreter.matchers.psystem.PBody;
12import tools.refinery.interpreter.matchers.psystem.PVariable;
13import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator;
14import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator;
15import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation;
8import tools.refinery.interpreter.matchers.psystem.annotations.ParameterReference; 16import tools.refinery.interpreter.matchers.psystem.annotations.ParameterReference;
9import tools.refinery.interpreter.matchers.psystem.basicdeferred.*; 17import tools.refinery.interpreter.matchers.psystem.basicdeferred.*;
10import tools.refinery.interpreter.matchers.psystem.basicenumerables.*;
11import tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity; 18import tools.refinery.interpreter.matchers.psystem.basicenumerables.Connectivity;
19import tools.refinery.interpreter.matchers.psystem.basicenumerables.*;
20import tools.refinery.interpreter.matchers.psystem.queries.PParameter;
21import tools.refinery.interpreter.matchers.psystem.queries.PParameterDirection;
22import tools.refinery.interpreter.matchers.psystem.queries.PQuery;
23import tools.refinery.interpreter.matchers.tuple.Tuple;
24import tools.refinery.interpreter.matchers.tuple.Tuples;
12import tools.refinery.store.query.Constraint; 25import tools.refinery.store.query.Constraint;
13import tools.refinery.store.query.dnf.Dnf; 26import tools.refinery.store.query.dnf.Dnf;
14import tools.refinery.store.query.dnf.DnfClause; 27import tools.refinery.store.query.dnf.DnfClause;
28import tools.refinery.store.query.dnf.FunctionalDependency;
15import tools.refinery.store.query.dnf.SymbolicParameter; 29import tools.refinery.store.query.dnf.SymbolicParameter;
16import tools.refinery.store.query.literal.*; 30import tools.refinery.store.query.literal.*;
17import tools.refinery.store.query.term.ConstantTerm; 31import tools.refinery.store.query.term.ConstantTerm;
@@ -20,19 +34,6 @@ import tools.refinery.store.query.term.StatelessAggregator;
20import tools.refinery.store.query.term.Variable; 34import tools.refinery.store.query.term.Variable;
21import tools.refinery.store.query.view.AnySymbolView; 35import tools.refinery.store.query.view.AnySymbolView;
22import tools.refinery.store.util.CycleDetectingMapper; 36import tools.refinery.store.util.CycleDetectingMapper;
23import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory;
24import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint;
25import tools.refinery.interpreter.matchers.context.IInputKey;
26import tools.refinery.interpreter.matchers.psystem.PBody;
27import tools.refinery.interpreter.matchers.psystem.PVariable;
28import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator;
29import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator;
30import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation;
31import tools.refinery.interpreter.matchers.psystem.queries.PParameter;
32import tools.refinery.interpreter.matchers.psystem.queries.PParameterDirection;
33import tools.refinery.interpreter.matchers.psystem.queries.PQuery;
34import tools.refinery.interpreter.matchers.tuple.Tuple;
35import tools.refinery.interpreter.matchers.tuple.Tuples;
36 37
37import java.util.ArrayList; 38import java.util.ArrayList;
38import java.util.HashMap; 39import java.util.HashMap;
@@ -79,15 +80,7 @@ public class Dnf2PQuery {
79 pQuery.setParameters(parameterList); 80 pQuery.setParameters(parameterList);
80 81
81 for (var functionalDependency : dnfQuery.getFunctionalDependencies()) { 82 for (var functionalDependency : dnfQuery.getFunctionalDependencies()) {
82 var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency"); 83 var functionalDependencyAnnotation = getFunctionalDependencyAnnotation(functionalDependency);
83 for (var forEachVariable : functionalDependency.forEach()) {
84 var reference = new ParameterReference(forEachVariable.getUniqueName());
85 functionalDependencyAnnotation.addAttribute("forEach", reference);
86 }
87 for (var uniqueVariable : functionalDependency.unique()) {
88 var reference = new ParameterReference(uniqueVariable.getUniqueName());
89 functionalDependencyAnnotation.addAttribute("unique", reference);
90 }
91 pQuery.addAnnotation(functionalDependencyAnnotation); 84 pQuery.addAnnotation(functionalDependencyAnnotation);
92 } 85 }
93 86
@@ -108,26 +101,33 @@ public class Dnf2PQuery {
108 return pQuery; 101 return pQuery;
109 } 102 }
110 103
111 private void translateLiteral(Literal literal, PBody body) { 104 private static PAnnotation getFunctionalDependencyAnnotation(FunctionalDependency<Variable> functionalDependency) {
112 if (literal instanceof EquivalenceLiteral equivalenceLiteral) { 105 var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency");
113 translateEquivalenceLiteral(equivalenceLiteral, body); 106 for (var forEachVariable : functionalDependency.forEach()) {
114 } else if (literal instanceof CallLiteral callLiteral) { 107 var reference = new ParameterReference(forEachVariable.getUniqueName());
115 translateCallLiteral(callLiteral, body); 108 functionalDependencyAnnotation.addAttribute("forEach", reference);
116 } else if (literal instanceof ConstantLiteral constantLiteral) { 109 }
117 translateConstantLiteral(constantLiteral, body); 110 for (var uniqueVariable : functionalDependency.unique()) {
118 } else if (literal instanceof AssignLiteral<?> assignLiteral) { 111 var reference = new ParameterReference(uniqueVariable.getUniqueName());
119 translateAssignLiteral(assignLiteral, body); 112 functionalDependencyAnnotation.addAttribute("unique", reference);
120 } else if (literal instanceof CheckLiteral checkLiteral) {
121 translateCheckLiteral(checkLiteral, body);
122 } else if (literal instanceof CountLiteral countLiteral) {
123 translateCountLiteral(countLiteral, body);
124 } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) {
125 translateAggregationLiteral(aggregationLiteral, body);
126 } else if (literal instanceof RepresentativeElectionLiteral representativeElectionLiteral) {
127 translateRepresentativeElectionLiteral(representativeElectionLiteral, body);
128 } else {
129 throw new IllegalArgumentException("Unknown literal: " + literal.toString());
130 } 113 }
114 return functionalDependencyAnnotation;
115 }
116
117 private void translateLiteral(Literal literal, PBody body) {
118 switch (literal) {
119 case EquivalenceLiteral equivalenceLiteral -> translateEquivalenceLiteral(equivalenceLiteral, body);
120 case CallLiteral callLiteral -> translateCallLiteral(callLiteral, body);
121 case ConstantLiteral constantLiteral -> translateConstantLiteral(constantLiteral, body);
122 case AssignLiteral<?> assignLiteral -> translateAssignLiteral(assignLiteral, body);
123 case CheckLiteral checkLiteral -> translateCheckLiteral(checkLiteral, body);
124 case CountLiteral countLiteral -> translateCountLiteral(countLiteral, body);
125 case AggregationLiteral<?, ?> aggregationLiteral -> translateAggregationLiteral(aggregationLiteral, body);
126 case LeftJoinLiteral<?> leftJoinLiteral -> translateLeftJoinLiteral(leftJoinLiteral, body);
127 case RepresentativeElectionLiteral representativeElectionLiteral ->
128 translateRepresentativeElectionLiteral(representativeElectionLiteral, body);
129 case null, default -> throw new IllegalArgumentException("Unknown literal: " + literal);
130 }
131 } 131 }
132 132
133 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) { 133 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) {
@@ -244,6 +244,21 @@ public class Dnf2PQuery {
244 aggregatedColumn); 244 aggregatedColumn);
245 } 245 }
246 246
247 private <T> void translateLeftJoinLiteral(LeftJoinLiteral<T> leftJoinLiteral, PBody body) {
248 var wrappedCall = wrapperFactory.maybeWrapConstraint(leftJoinLiteral);
249 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
250 var placeholderVariable = body.getOrCreateVariableByName(
251 leftJoinLiteral.getPlaceholderVariable().getUniqueName());
252 var optionalColumn = substitution.invertIndex().get(placeholderVariable);
253 if (optionalColumn == null) {
254 throw new IllegalStateException("Placeholder variable %s not found in substitution %s"
255 .formatted(placeholderVariable, substitution));
256 }
257 var resultVariable = body.getOrCreateVariableByName(leftJoinLiteral.getResultVariable().getUniqueName());
258 new LeftJoinConstraint(body, substitution, wrappedCall.pattern(), resultVariable, optionalColumn,
259 leftJoinLiteral.getDefaultValue());
260 }
261
247 private void translateRepresentativeElectionLiteral(RepresentativeElectionLiteral literal, PBody body) { 262 private void translateRepresentativeElectionLiteral(RepresentativeElectionLiteral literal, PBody body) {
248 var substitution = translateSubstitution(literal.getArguments(), body); 263 var substitution = translateSubstitution(literal.getArguments(), body);
249 var pattern = wrapConstraintWithIdentityArguments(literal.getTarget()); 264 var pattern = wrapConstraintWithIdentityArguments(literal.getTarget());
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java
index b6c96676..ca1512d8 100644
--- a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java
+++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java
@@ -554,7 +554,6 @@ class FunctionalQueryTest {
554 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 554 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
555 555
556 queryEngine.flushChanges(); 556 queryEngine.flushChanges();
557 queryEngine.flushChanges();
558 assertNullableResults(Map.of( 557 assertNullableResults(Map.of(
559 Tuple.of(0), Optional.of(25), 558 Tuple.of(0), Optional.of(25),
560 Tuple.of(1), Optional.of(32), 559 Tuple.of(1), Optional.of(32),
diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java
new file mode 100644
index 00000000..4c849e9d
--- /dev/null
+++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/LeftJoinTest.java
@@ -0,0 +1,129 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.interpreter;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.int_.IntTerms;
13import tools.refinery.store.query.view.AnySymbolView;
14import tools.refinery.store.query.view.FunctionView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.List;
20import java.util.Map;
21import java.util.Optional;
22
23import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults;
24import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults;
25
26class LeftJoinTest {
27 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
28 private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class);
29 private static final AnySymbolView personView = new KeyOnlyView<>(person);
30 private static final FunctionView<Integer> ageView = new FunctionView<>(age);
31
32 @Test
33 void unarySymbolTest() {
34 var query = Query.of("Query", Integer.class, (builder, p1, output) -> builder
35 .clause(
36 personView.call(p1),
37 output.assign(ageView.leftJoin(18, p1))
38 ));
39
40 var store = ModelStore.builder()
41 .symbols(person, age)
42 .with(QueryInterpreterAdapter.builder()
43 .queries(query))
44 .build();
45
46 var model = store.createEmptyModel();
47 var personInterpretation = model.getInterpretation(person);
48 var ageInterpretation = model.getInterpretation(age);
49 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
50 var queryResultSet = queryEngine.getResultSet(query);
51
52 personInterpretation.put(Tuple.of(0), true);
53 personInterpretation.put(Tuple.of(1), true);
54 personInterpretation.put(Tuple.of(2), true);
55
56 ageInterpretation.put(Tuple.of(2), 24);
57
58 queryEngine.flushChanges();
59 assertNullableResults(Map.of(
60 Tuple.of(0), Optional.of(18),
61 Tuple.of(1), Optional.of(18),
62 Tuple.of(2), Optional.of(24),
63 Tuple.of(3), Optional.empty()
64 ), queryResultSet);
65
66 personInterpretation.put(Tuple.of(0), false);
67
68 ageInterpretation.put(Tuple.of(1), 20);
69 ageInterpretation.put(Tuple.of(2), null);
70
71 queryEngine.flushChanges();
72 assertNullableResults(Map.of(
73 Tuple.of(0), Optional.empty(),
74 Tuple.of(1), Optional.of(20),
75 Tuple.of(2), Optional.of(18),
76 Tuple.of(3), Optional.empty()
77 ), queryResultSet);
78 }
79
80 @Test
81 void unarySymbolWithAssignmentTest() {
82 // Tests an edge case where the outer joined variable is already bound in the query plan.
83 var query = Query.of("Query", (builder, p1) -> builder
84 .clause(Integer.class, v1 -> List.of(
85 personView.call(p1),
86 v1.assign(IntTerms.constant(18)),
87 v1.assign(ageView.leftJoin(18, p1))
88 )));
89
90 var store = ModelStore.builder()
91 .symbols(person, age)
92 .with(QueryInterpreterAdapter.builder()
93 .queries(query))
94 .build();
95
96 var model = store.createEmptyModel();
97 var personInterpretation = model.getInterpretation(person);
98 var ageInterpretation = model.getInterpretation(age);
99 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
100 var queryResultSet = queryEngine.getResultSet(query);
101
102 personInterpretation.put(Tuple.of(0), true);
103 personInterpretation.put(Tuple.of(1), true);
104 personInterpretation.put(Tuple.of(2), true);
105
106 ageInterpretation.put(Tuple.of(2), 24);
107
108 queryEngine.flushChanges();
109 assertResults(Map.of(
110 Tuple.of(0), true,
111 Tuple.of(1), true,
112 Tuple.of(2), false,
113 Tuple.of(3), false
114 ), queryResultSet);
115
116 personInterpretation.put(Tuple.of(0), false);
117
118 ageInterpretation.put(Tuple.of(1), 20);
119 ageInterpretation.put(Tuple.of(2), null);
120
121 queryEngine.flushChanges();
122 assertResults(Map.of(
123 Tuple.of(0), false,
124 Tuple.of(1), false,
125 Tuple.of(2), true,
126 Tuple.of(3), false
127 ), queryResultSet);
128 }
129}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
index 916fb35c..375c582a 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -69,4 +69,14 @@ public interface Constraint {
69 Variable... arguments) { 69 Variable... arguments) {
70 return aggregateBy(inputVariable, aggregator, List.of(arguments)); 70 return aggregateBy(inputVariable, aggregator, List.of(arguments));
71 } 71 }
72
73 default <T> AssignedValue<T> leftJoinBy(DataVariable<T> placeholderVariable, T defaultValue,
74 List<Variable> arguments) {
75 return targetVariable -> new LeftJoinLiteral<>(targetVariable, placeholderVariable, defaultValue, this,
76 arguments);
77 }
78
79 default <T> AssignedValue<T> leftJoinBy(DataVariable<T> inputVariable, T defaultValue, Variable... arguments) {
80 return leftJoinBy(inputVariable, defaultValue, List.of(arguments));
81 }
72} 82}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
index 8800a155..7cd05364 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -112,7 +112,7 @@ class ClausePostProcessor {
112 .formatted(variable, representative)); 112 .formatted(variable, representative));
113 } 113 }
114 return equivalencePartition.computeIfAbsent(variable, key -> { 114 return equivalencePartition.computeIfAbsent(variable, key -> {
115 var set = new HashSet<Variable>(1); 115 var set = HashSet.<Variable>newHashSet(1);
116 set.add(key); 116 set.add(key);
117 return set; 117 return set;
118 }); 118 });
@@ -193,7 +193,7 @@ class ClausePostProcessor {
193 } 193 }
194 194
195 private void topologicallySortLiterals() { 195 private void topologicallySortLiterals() {
196 topologicallySortedLiterals = new LinkedHashSet<>(substitutedLiterals.size()); 196 topologicallySortedLiterals = LinkedHashSet.newLinkedHashSet(substitutedLiterals.size());
197 variableToLiteralInputMap = new HashMap<>(); 197 variableToLiteralInputMap = new HashMap<>();
198 literalsWithAllInputsBound = new PriorityQueue<>(); 198 literalsWithAllInputsBound = new PriorityQueue<>();
199 int size = substitutedLiterals.size(); 199 int size = substitutedLiterals.size();
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java
index 50236642..01344b59 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -26,7 +26,7 @@ class DnfPostProcessor {
26 26
27 public List<DnfClause> postProcessClauses() { 27 public List<DnfClause> postProcessClauses() {
28 var parameterInfoMap = getParameterInfoMap(); 28 var parameterInfoMap = getParameterInfoMap();
29 var postProcessedClauses = new LinkedHashSet<CanonicalClause>(clauses.size()); 29 var postProcessedClauses = LinkedHashSet.<CanonicalClause>newLinkedHashSet(clauses.size());
30 int index = 0; 30 int index = 0;
31 for (var literals : clauses) { 31 for (var literals : clauses) {
32 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals); 32 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
index 225f6844..b0a03c7d 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -94,6 +94,22 @@ public final class FunctionalQuery<T> extends Query<T> {
94 return aggregate(aggregator, List.of(arguments)); 94 return aggregate(aggregator, List.of(arguments));
95 } 95 }
96 96
97 public AssignedValue<T> leftJoin(T defaultValue, List<NodeVariable> arguments) {
98 return targetVariable -> {
99 var placeholderVariable = Variable.of(type);
100 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
101 argumentsWithPlaceholder.addAll(arguments);
102 argumentsWithPlaceholder.add(placeholderVariable);
103 return getDnf()
104 .leftJoinBy(placeholderVariable, defaultValue, argumentsWithPlaceholder)
105 .toLiteral(targetVariable);
106 };
107 }
108
109 public AssignedValue<T> leftJoin(T defaultValue, NodeVariable... arguments) {
110 return leftJoin(defaultValue, List.of(arguments));
111 }
112
97 @Override 113 @Override
98 public boolean equals(Object o) { 114 public boolean equals(Object o) {
99 if (this == o) return true; 115 if (this == o) return true;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
index e3acfacc..b6861de0 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -129,7 +129,12 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
129 } 129 }
130 builder.append(argument); 130 builder.append(argument);
131 while (argumentIterator.hasNext()) { 131 while (argumentIterator.hasNext()) {
132 builder.append(", ").append(argumentIterator.next()); 132 builder.append(", ");
133 argument = argumentIterator.next();
134 if (inputVariable.equals(argument)) {
135 builder.append("@Aggregate(\"").append(aggregator).append("\") ");
136 }
137 builder.append(argument);
133 } 138 }
134 } 139 }
135 builder.append(")"); 140 builder.append(")");
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LeftJoinLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LeftJoinLiteral.java
new file mode 100644
index 00000000..bdddf120
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LeftJoinLiteral.java
@@ -0,0 +1,140 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.ConstantTerm;
14import tools.refinery.store.query.term.DataVariable;
15import tools.refinery.store.query.term.ParameterDirection;
16import tools.refinery.store.query.term.Variable;
17
18import java.util.*;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public class LeftJoinLiteral<T> extends AbstractCallLiteral {
23 private final DataVariable<T> resultVariable;
24 private final DataVariable<T> placeholderVariable;
25 private final T defaultValue;
26
27 public LeftJoinLiteral(DataVariable<T> resultVariable, DataVariable<T> placeholderVariable,
28 T defaultValue, Constraint target, List<Variable> arguments) {
29 super(target, arguments);
30 this.resultVariable = resultVariable;
31 this.placeholderVariable = placeholderVariable;
32 this.defaultValue = defaultValue;
33 if (defaultValue == null) {
34 throw new InvalidQueryException("Default value must not be null");
35 }
36 if (!resultVariable.getType().isInstance(defaultValue)) {
37 throw new InvalidQueryException("Default value %s must be assignable to result variable %s type %s"
38 .formatted(defaultValue, resultVariable, resultVariable.getType().getName()));
39 }
40 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(placeholderVariable)) {
41 throw new InvalidQueryException(
42 "Placeholder variable %s must be bound with direction %s in the argument list"
43 .formatted(resultVariable, ParameterDirection.OUT));
44 }
45 if (arguments.contains(resultVariable)) {
46 throw new InvalidQueryException("Result variable must not appear in the argument list");
47 }
48 }
49
50 public DataVariable<T> getResultVariable() {
51 return resultVariable;
52 }
53
54 public DataVariable<T> getPlaceholderVariable() {
55 return placeholderVariable;
56 }
57
58 public T getDefaultValue() {
59 return defaultValue;
60 }
61
62 @Override
63 public Set<Variable> getOutputVariables() {
64 return Set.of(resultVariable);
65 }
66
67 @Override
68 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
69 var inputVariables = new LinkedHashSet<>(getArguments());
70 inputVariables.remove(placeholderVariable);
71 return Collections.unmodifiableSet(inputVariables);
72 }
73
74 @Override
75 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
76 return Set.of(placeholderVariable);
77 }
78
79 @Override
80 public Literal reduce() {
81 var reduction = getTarget().getReduction();
82 return switch (reduction) {
83 case ALWAYS_FALSE -> resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), defaultValue));
84 case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to left join an infinite set");
85 case NOT_REDUCIBLE -> this;
86 };
87 }
88
89 @Override
90 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
91 return new LeftJoinLiteral<>(substitution.getTypeSafeSubstitute(resultVariable),
92 substitution.getTypeSafeSubstitute(placeholderVariable), defaultValue, getTarget(),
93 substitutedArguments);
94 }
95
96 @Override
97 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
98 return new LeftJoinLiteral<>(resultVariable, placeholderVariable, defaultValue, newTarget, newArguments);
99 }
100
101 @Override
102 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
103 if (!super.equalsWithSubstitution(helper, other)) {
104 return false;
105 }
106 var otherLeftJoinLiteral = (LeftJoinLiteral<?>) other;
107 return helper.variableEqual(resultVariable, otherLeftJoinLiteral.resultVariable) &&
108 helper.variableEqual(placeholderVariable, otherLeftJoinLiteral.placeholderVariable) &&
109 Objects.equals(defaultValue, otherLeftJoinLiteral.defaultValue);
110 }
111
112 @Override
113 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
114 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable),
115 helper.getVariableHashCode(placeholderVariable), defaultValue);
116 }
117
118 @Override
119 public String toString() {
120 var builder = new StringBuilder();
121 var argumentIterator = getArguments().iterator();
122 if (argumentIterator.hasNext()) {
123 appendArgument(builder, argumentIterator.next());
124 while (argumentIterator.hasNext()) {
125 builder.append(", ");
126 appendArgument(builder, argumentIterator.next());
127 }
128 }
129 builder.append(")");
130 return builder.toString();
131 }
132
133 private void appendArgument(StringBuilder builder, Variable argument) {
134 if (placeholderVariable.equals(argument)) {
135 builder.append("@Default(").append(defaultValue).append(") ");
136 argument = resultVariable;
137 }
138 builder.append(argument);
139 }
140}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java
index 74a5be07..3dfb6777 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -33,4 +33,18 @@ public final class FunctionView<T> extends AbstractFunctionView<T> {
33 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) { 33 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
34 return aggregate(aggregator, List.of(arguments)); 34 return aggregate(aggregator, List.of(arguments));
35 } 35 }
36
37 public AssignedValue<T> leftJoin(T defaultValue, List<NodeVariable> arguments) {
38 return targetVariable -> {
39 var placeholderVariable = Variable.of(getSymbol().valueType());
40 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
41 argumentsWithPlaceholder.addAll(arguments);
42 argumentsWithPlaceholder.add(placeholderVariable);
43 return leftJoinBy(placeholderVariable, defaultValue, argumentsWithPlaceholder).toLiteral(targetVariable);
44 };
45 }
46
47 public AssignedValue<T> leftJoin(T defaultValue, NodeVariable... arguments) {
48 return leftJoin(defaultValue, List.of(arguments));
49 }
36} 50}