|
8 | 8 | import org.apache.calcite.rel.core.CorrelationId; |
9 | 9 | import org.apache.calcite.rel.core.Filter; |
10 | 10 | import org.apache.calcite.rel.core.Project; |
11 | | -import org.apache.calcite.rex.RexCorrelVariable; |
12 | | -import org.apache.calcite.rex.RexFieldAccess; |
13 | 11 | import org.apache.calcite.rex.RexNode; |
14 | 12 | import org.apache.calcite.rex.RexShuttle; |
15 | 13 | import org.apache.calcite.rex.RexSubQuery; |
16 | 14 | import org.apache.calcite.rex.RexUtil.SubQueryCollector; |
17 | 15 |
|
18 | 16 | /** |
19 | | - * Resolve correlated variables and compute a depth map for {@link RexFieldAccess}. |
| 17 | + * Assigns id-based outer-reference anchors to a {@link RelNode} tree. |
20 | 18 | * |
21 | | - * <p>Traverses a {@link RelNode} tree and: |
| 19 | + * <p>Calcite models correlation with {@link CorrelationId}s, each bound by a single relation (a |
| 20 | + * {@link Correlate}'s left input, or the input of a {@link Filter}/{@link Project} that declares |
| 21 | + * the id). This maps directly onto Substrait's id-based outer references: the bound relation |
| 22 | + * carries a {@code rel_anchor} ({@link io.substrait.relation.Rel#getRelAnchor()}) and every |
| 23 | + * correlated field reference carries the matching {@code rel_reference} ({@link |
| 24 | + * io.substrait.expression.FieldReference#outerReferenceRelReference()}). |
| 25 | + * |
| 26 | + * <p>This resolver walks the tree (including subquery relations) and produces: |
22 | 27 | * |
23 | 28 | * <ul> |
24 | | - * <li>Tracks nesting depth of {@link CorrelationId}s across filters, projects, subqueries, and |
25 | | - * correlates |
26 | | - * <li>Computes "steps out" for each {@link RexFieldAccess} referencing a {@link |
27 | | - * RexCorrelVariable} |
| 29 | + * <li>{@link #anchorForCorrelationId(CorrelationId)} — the anchor a correlated field reference |
| 30 | + * should emit; |
| 31 | + * <li>{@link #anchorForTarget(RelNode)} — the anchor to stamp on a bound relation. |
28 | 32 | * </ul> |
29 | 33 | * |
30 | | - * See OuterReferenceResolver.md for details on how the depth map is computed. |
| 34 | + * Anchors are plan-wide unique and assigned per bound relation, so multiple correlation ids sharing |
| 35 | + * a relation share its anchor. |
31 | 36 | */ |
32 | 37 | public class OuterReferenceResolver extends RelNodeVisitor<RelNode, RuntimeException> { |
33 | 38 |
|
34 | | - private final Map<CorrelationId, Integer> nestedDepth; |
35 | | - private final Map<RexFieldAccess, Integer> fieldAccessDepthMap; |
| 39 | + private final Map<CorrelationId, Integer> anchorByCorrelationId = new HashMap<>(); |
| 40 | + private final Map<RelNode, Integer> anchorByTarget = new IdentityHashMap<>(); |
36 | 41 |
|
37 | 42 | private final RexVisitor rexVisitor = new RexVisitor(this); |
38 | 43 |
|
39 | | - /** Creates a new resolver with empty depth tracking maps. */ |
40 | | - public OuterReferenceResolver() { |
41 | | - nestedDepth = new HashMap<>(); |
42 | | - fieldAccessDepthMap = new IdentityHashMap<>(); |
43 | | - } |
| 44 | + private int nextAnchor = 1; |
| 45 | + |
| 46 | + /** Creates a new resolver with empty anchor maps. */ |
| 47 | + public OuterReferenceResolver() {} |
44 | 48 |
|
45 | 49 | /** |
46 | | - * Applies the resolver to a {@link RelNode} tree, computing the depth map. |
| 50 | + * Walks a {@link RelNode} tree, assigning outer-reference anchors. |
47 | 51 | * |
48 | | - * @param r the root relational node |
| 52 | + * @param root the root relational node |
49 | 53 | * @return the same node after traversal |
50 | | - * @throws RuntimeException if the visitor encounters an unrecoverable condition |
51 | 54 | */ |
52 | | - public Map<RexFieldAccess, Integer> apply(RelNode r) { |
53 | | - reverseAccept(r); |
54 | | - return fieldAccessDepthMap; |
| 55 | + public RelNode resolve(RelNode root) { |
| 56 | + reverseAccept(root); |
| 57 | + return root; |
55 | 58 | } |
56 | 59 |
|
57 | 60 | /** |
58 | | - * Visits a {@link Filter}, registering any correlation variables and visiting its condition. |
| 61 | + * Returns the anchor a correlated field reference bound to the given correlation id must emit as |
| 62 | + * its {@code rel_reference}. |
59 | 63 | * |
60 | | - * @param filter the filter node |
61 | | - * @return the result of {@link RelNodeVisitor#visit(Filter)} |
62 | | - * @throws RuntimeException if traversal fails |
| 64 | + * @param id the correlation id |
| 65 | + * @return the anchor, or {@code null} if the id was not resolved |
63 | 66 | */ |
64 | | - @Override |
65 | | - public RelNode visit(Filter filter) throws RuntimeException { |
66 | | - for (CorrelationId id : filter.getVariablesSet()) { |
67 | | - nestedDepth.putIfAbsent(id, 0); |
68 | | - } |
69 | | - // Also register any CorrelationIds referenced directly in the condition expression. |
70 | | - // This covers partially-decorrelated plans where a Calcite optimizer (e.g. HepPlanner) |
71 | | - // rewrites correlated IN-subqueries into joins but leaves scalar-aggregate subquery |
72 | | - // correlations as RexFieldAccess($corN.col) inside a Filter condition without a |
73 | | - // surrounding Correlate node. In that case getVariablesSet() is empty but the condition |
74 | | - // still contains RexCorrelVariable references whose IDs must be in nestedDepth before |
75 | | - // rexVisitor.visitFieldAccess() is called. |
76 | | - filter |
77 | | - .getCondition() |
78 | | - .accept( |
79 | | - new RexShuttle() { |
80 | | - @Override |
81 | | - public RexNode visitFieldAccess(RexFieldAccess fieldAccess) { |
82 | | - if (fieldAccess.getReferenceExpr() instanceof RexCorrelVariable) { |
83 | | - CorrelationId id = ((RexCorrelVariable) fieldAccess.getReferenceExpr()).id; |
84 | | - nestedDepth.putIfAbsent(id, 0); |
85 | | - } |
86 | | - return fieldAccess; |
87 | | - } |
88 | | - }); |
89 | | - filter.getCondition().accept(rexVisitor); |
90 | | - return super.visit(filter); |
| 67 | + public Integer anchorForCorrelationId(CorrelationId id) { |
| 68 | + return anchorByCorrelationId.get(id); |
91 | 69 | } |
92 | 70 |
|
93 | 71 | /** |
94 | | - * Visits a {@link Correlate}, handling correlation depth for both sides. |
95 | | - * |
96 | | - * <p>Special case: the right side is a correlated subquery in the rel tree (not a REX), so we |
97 | | - * manually adjust depth before/after visiting it. |
| 72 | + * Returns the anchor to stamp on the given relation, if it is the binding point of any outer |
| 73 | + * reference. |
98 | 74 | * |
99 | | - * @param correlate the correlate (correlated join) node |
100 | | - * @return the correlate node |
101 | | - * @throws RuntimeException if traversal fails |
| 75 | + * @param rel the relation |
| 76 | + * @return the anchor, or {@code null} if the relation binds no outer reference |
102 | 77 | */ |
103 | | - @Override |
104 | | - public RelNode visit(Correlate correlate) throws RuntimeException { |
105 | | - for (CorrelationId id : correlate.getVariablesSet()) { |
106 | | - nestedDepth.putIfAbsent(id, 0); |
107 | | - } |
108 | | - |
109 | | - apply(correlate.getLeft()); |
110 | | - |
111 | | - // Correlated join is a special case. The right-rel is a correlated sub-query but not a REX. So, |
112 | | - // the RexVisitor cannot be applied to it to correctly compute the depth map. Hence, we need to |
113 | | - // manually compute the depth map for the right-rel. |
114 | | - nestedDepth.replaceAll((k, v) -> v + 1); |
115 | | - |
116 | | - apply(correlate.getRight()); // look inside sub-queries |
117 | | - |
118 | | - nestedDepth.replaceAll((k, v) -> v - 1); |
| 78 | + public Integer anchorForTarget(RelNode rel) { |
| 79 | + return anchorByTarget.get(rel); |
| 80 | + } |
119 | 81 |
|
120 | | - return correlate; |
| 82 | + /** Binds {@code id} to {@code target}, allocating {@code target}'s anchor on first use. */ |
| 83 | + private void bind(CorrelationId id, RelNode target) { |
| 84 | + int anchor = anchorByTarget.computeIfAbsent(target, t -> nextAnchor++); |
| 85 | + anchorByCorrelationId.put(id, anchor); |
121 | 86 | } |
122 | 87 |
|
123 | | - /** |
124 | | - * Visits a generic {@link RelNode}, applying the resolver to all the node inputs. |
125 | | - * |
126 | | - * @param other the node to visit |
127 | | - * @return the node |
128 | | - * @throws RuntimeException if traversal fails |
129 | | - */ |
130 | 88 | @Override |
131 | | - public RelNode visitOther(RelNode other) throws RuntimeException { |
132 | | - for (RelNode child : other.getInputs()) { |
133 | | - apply(child); |
| 89 | + public RelNode visit(Filter filter) throws RuntimeException { |
| 90 | + for (CorrelationId id : filter.getVariablesSet()) { |
| 91 | + bind(id, filter.getInput()); |
134 | 92 | } |
135 | | - return other; |
| 93 | + filter.getCondition().accept(rexVisitor); |
| 94 | + return visitOther(filter); |
136 | 95 | } |
137 | 96 |
|
138 | | - /** |
139 | | - * Visits a {@link Project}, registering correlation variables and visiting any subqueries within |
140 | | - * its expressions. |
141 | | - * |
142 | | - * @param project the project node |
143 | | - * @return the result of {@link RelNodeVisitor#visit(Project)} |
144 | | - * @throws RuntimeException if traversal fails |
145 | | - */ |
146 | 97 | @Override |
147 | 98 | public RelNode visit(Project project) throws RuntimeException { |
148 | 99 | for (CorrelationId id : project.getVariablesSet()) { |
149 | | - nestedDepth.putIfAbsent(id, 0); |
| 100 | + bind(id, project.getInput()); |
150 | 101 | } |
151 | | - |
152 | 102 | for (RexSubQuery subQuery : SubQueryCollector.collect(project)) { |
153 | 103 | subQuery.accept(rexVisitor); |
154 | 104 | } |
| 105 | + return visitOther(project); |
| 106 | + } |
155 | 107 |
|
156 | | - return super.visit(project); |
| 108 | + @Override |
| 109 | + public RelNode visit(Correlate correlate) throws RuntimeException { |
| 110 | + bind(correlate.getCorrelationId(), correlate.getLeft()); |
| 111 | + reverseAccept(correlate.getLeft()); |
| 112 | + reverseAccept(correlate.getRight()); |
| 113 | + return correlate; |
157 | 114 | } |
158 | 115 |
|
159 | | - /** Rex visitor used to track correlation depth within expressions and subqueries. */ |
| 116 | + @Override |
| 117 | + public RelNode visitOther(RelNode other) throws RuntimeException { |
| 118 | + for (RelNode child : other.getInputs()) { |
| 119 | + reverseAccept(child); |
| 120 | + } |
| 121 | + return other; |
| 122 | + } |
| 123 | + |
| 124 | + /** Rex visitor that descends into subquery relations to reach nested correlations. */ |
160 | 125 | private static class RexVisitor extends RexShuttle { |
161 | 126 | final OuterReferenceResolver referenceResolver; |
162 | 127 |
|
163 | | - /** |
164 | | - * Creates a new Rex visitor bound to the given reference resolver. |
165 | | - * |
166 | | - * @param referenceResolver the parent resolver maintaining depth maps |
167 | | - */ |
168 | 128 | RexVisitor(OuterReferenceResolver referenceResolver) { |
169 | 129 | this.referenceResolver = referenceResolver; |
170 | 130 | } |
171 | 131 |
|
172 | | - /** |
173 | | - * Increments correlation depth when entering a subquery and decrements when exiting. |
174 | | - * |
175 | | - * @param subQuery the subquery expression |
176 | | - * @return the same subquery |
177 | | - */ |
178 | 132 | @Override |
179 | 133 | public RexNode visitSubQuery(RexSubQuery subQuery) { |
180 | | - referenceResolver.nestedDepth.replaceAll((k, v) -> v + 1); |
181 | | - |
182 | | - referenceResolver.apply(subQuery.rel); // look inside sub-queries |
183 | | - |
184 | | - referenceResolver.nestedDepth.replaceAll((k, v) -> v - 1); |
| 134 | + referenceResolver.reverseAccept(subQuery.rel); |
185 | 135 | return subQuery; |
186 | 136 | } |
187 | | - |
188 | | - /** |
189 | | - * Records depth for {@link RexFieldAccess} referencing a {@link RexCorrelVariable}. |
190 | | - * |
191 | | - * @param fieldAccess the field access expression |
192 | | - * @return the same field access |
193 | | - */ |
194 | | - @Override |
195 | | - public RexNode visitFieldAccess(RexFieldAccess fieldAccess) { |
196 | | - if (fieldAccess.getReferenceExpr() instanceof RexCorrelVariable) { |
197 | | - CorrelationId id = ((RexCorrelVariable) fieldAccess.getReferenceExpr()).id; |
198 | | - if (referenceResolver.nestedDepth.containsKey(id)) { |
199 | | - referenceResolver.fieldAccessDepthMap.put( |
200 | | - fieldAccess, referenceResolver.nestedDepth.get(id)); |
201 | | - } |
202 | | - } |
203 | | - return fieldAccess; |
204 | | - } |
205 | 137 | } |
206 | 138 | } |
0 commit comments