Skip to content

Commit 6338b8f

Browse files
author
ian.bertolacci
committed
[CALCITE-7405] Pre-process expressions for correlations before building projection in SqlToRelConverter. Fix some gitignore paths.
1 parent 077d355 commit 6338b8f

7 files changed

Lines changed: 228 additions & 68 deletions

File tree

.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
/buildSrc/build
2626
/buildSrc/subprojects/*/build
2727
/site/.jekyll-cache
28+
**/generated/
2829

2930
# VSCode Java plugin
3031
/example/*/bin
@@ -33,16 +34,15 @@
3334
/.vscode/*
3435

3536
# IDEA
36-
/out
37-
/*/out/
38-
/example/*/out
37+
**/out/
3938
# The star is required for further !/.idea/ to work, see https://git-scm.com/docs/gitignore
4039
/.idea/*
4140
# Icon for JetBrains Toolbox
4241
!/.idea/icon.png
4342
!/.idea/vcs.xml
4443
*.iml
4544

45+
4646
settings.xml
4747
.classpath.txt
4848
.fullclasspath.txt

core/src/main/java/org/apache/calcite/plan/RelOptUtil.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ public static Set<CorrelationId> getVariablesUsed(RelNode rel) {
303303
return visitor.vuv.variables;
304304
}
305305

306+
public static Set<CorrelationId> getVariablesUsed(RexNode node) {
307+
CorrelationCollector visitor = new CorrelationCollector();
308+
node.accept(visitor.vuv);
309+
return visitor.vuv.variables;
310+
}
311+
306312
/**
307313
* Returns the set of variables used by the given list of sub-queries and its descendants.
308314
*

core/src/main/java/org/apache/calcite/sql2rel/DeduplicateCorrelateVariables.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ public static RelNode go(RexBuilder builder, CorrelationId canonicalId,
5757
ImmutableSet.copyOf(alternateIds)));
5858
}
5959

60+
/**
61+
* Rewrites an expression, replacing alternate correlation variables
62+
* with a canonical correlation variable.
63+
*/
64+
public static RexNode go(RexBuilder builder, CorrelationId canonicalId,
65+
Iterable<? extends CorrelationId> alternateIds, RexNode r) {
66+
DeduplicateCorrelateVariables shuttle =
67+
new DeduplicateCorrelateVariables(builder, canonicalId,
68+
ImmutableSet.copyOf(alternateIds));
69+
return r.accept(shuttle.dedupRex);
70+
}
71+
6072
@Override public RelNode visit(RelNode other) {
6173
RelNode next = super.visit(other);
6274
return next.accept(dedupRex);

core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java

Lines changed: 168 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@
217217
import java.util.TreeSet;
218218
import java.util.function.BiConsumer;
219219
import java.util.function.BiFunction;
220+
import java.util.function.Consumer;
220221
import java.util.function.Supplier;
221222
import java.util.function.UnaryOperator;
222223
import java.util.stream.Collectors;
@@ -3261,12 +3262,12 @@ protected RelNode createAsofJoin(
32613262
return node;
32623263
}
32633264

3264-
private @Nullable CorrelationUse getCorrelationUse(Blackboard bb, final RelNode r0) {
3265-
final Set<CorrelationId> correlatedVariables =
3266-
RelOptUtil.getVariablesUsed(r0);
3267-
if (correlatedVariables.isEmpty()) {
3268-
return null;
3269-
}
3265+
/** Common utility for resolving correlation names, required columns and field mappings used by
3266+
* both {@link #massageExpressionsForCorrelation} and {@link #getCorrelationUse}.
3267+
* returns null if no correlation names are detected for this scope.
3268+
*/
3269+
private @Nullable ResolvedCorrelationInfo getCorrelationInfo(Blackboard bb,
3270+
final Set<CorrelationId> correlatedVariables) {
32703271
final ImmutableBitSet.Builder requiredColumns = ImmutableBitSet.builder();
32713272
final List<CorrelationId> correlNames = new ArrayList<>();
32723273
// Mapping from (correlId, originalFieldIndex) to projectedFieldIndex for aggregation
@@ -3350,29 +3351,92 @@ protected RelNode createAsofJoin(
33503351
if (correlNames.isEmpty()) {
33513352
// None of the correlating variables originated in this scope.
33523353
return null;
3354+
} else {
3355+
return new ResolvedCorrelationInfo(correlNames, requiredColumns.build(), fieldMapping);
3356+
}
3357+
}
3358+
3359+
private @Nullable CorrelationUse getCorrelationUse(Blackboard bb, final RelNode r0) {
3360+
final Set<CorrelationId> correlatedVariables =
3361+
RelOptUtil.getVariablesUsed(r0);
3362+
if (correlatedVariables.isEmpty()) {
3363+
return null;
3364+
}
3365+
3366+
ResolvedCorrelationInfo correlationInfo = getCorrelationInfo(bb, correlatedVariables);
3367+
if (correlationInfo == null) {
3368+
// None of the correlating variables originated in this scope.
3369+
return null;
33533370
}
33543371

33553372
RelNode r = r0;
3356-
if (correlNames.size() > 1) {
3373+
if (correlationInfo.correlNames.size() > 1) {
33573374
// The same table was referenced more than once.
33583375
// So we deduplicate.
33593376
r =
3360-
DeduplicateCorrelateVariables.go(rexBuilder, correlNames.get(0),
3361-
Util.skip(correlNames), r0);
3377+
DeduplicateCorrelateVariables.go(rexBuilder, correlationInfo.correlNames.get(0),
3378+
Util.skip(correlationInfo.correlNames), r0);
33623379
// Add new node to leaves.
33633380
leaves.put(r, r.getRowType().getFieldCount());
33643381
}
33653382

33663383
// If there are field mappings (due to aggregation), rewrite the RelNode tree
33673384
// to update correlation variable row type and field indices
3368-
if (!fieldMapping.isEmpty()) {
3385+
if (!correlationInfo.fieldMapping.isEmpty()) {
33693386
r =
33703387
r.accept(
3371-
new CorrelationFieldMappingShuttle(rexBuilder, correlNames.get(0),
3372-
bb.root().getRowType(), fieldMapping));
3388+
new CorrelationFieldMappingShuttle(rexBuilder, correlationInfo.correlNames.get(0),
3389+
bb.root().getRowType(), correlationInfo.fieldMapping));
33733390
}
33743391

3375-
return new CorrelationUse(correlNames.get(0), requiredColumns.build(), r);
3392+
return new CorrelationUse(correlationInfo.correlNames.get(0), correlationInfo.requiredColumns,
3393+
r);
3394+
}
3395+
3396+
/** Before building a RelNode tree with the provided expressions, detect and resolve correlation
3397+
* names, rewrite expressions, and return a callback to be called after the tree is built.
3398+
* Returns null if no correlation is detected.
3399+
*/
3400+
private @Nullable MassagedCorrelationExpressions massageExpressionsForCorrelation(Blackboard bb,
3401+
final List<RexNode> exprs) {
3402+
Set<CorrelationId> correlatedVariables = new HashSet<>();
3403+
for (RexNode e : exprs) {
3404+
correlatedVariables.addAll(RelOptUtil.getVariablesUsed(e));
3405+
}
3406+
if (correlatedVariables.isEmpty()) {
3407+
return null;
3408+
}
3409+
3410+
ResolvedCorrelationInfo correlationInfo = getCorrelationInfo(bb, correlatedVariables);
3411+
if (correlationInfo == null) {
3412+
// None of the correlating variables originated in this scope.
3413+
return null;
3414+
}
3415+
3416+
List<RexNode> newExprs = new ArrayList<>(exprs);
3417+
Consumer<RelNode> callback = (RelNode r) -> { };
3418+
if (correlationInfo.correlNames.size() > 1) {
3419+
// The same table was referenced more than once.
3420+
// So we deduplicate.
3421+
CorrelationId canonicalId = correlationInfo.correlNames.get(0);
3422+
List<CorrelationId> tail = Util.skip(correlationInfo.correlNames);
3423+
newExprs.replaceAll(e ->
3424+
DeduplicateCorrelateVariables.go(rexBuilder, canonicalId, tail, e));
3425+
// Add new node to leaves.
3426+
callback = r -> leaves.put(r, r.getRowType().getFieldCount());
3427+
}
3428+
3429+
// If there are field mappings (due to aggregation), rewrite the RelNode tree
3430+
// to update correlation variable row type and field indices
3431+
if (!correlationInfo.fieldMapping.isEmpty()) {
3432+
CorrelationFieldMappingRexShuttle shuttle =
3433+
new CorrelationFieldMappingRexShuttle(rexBuilder, correlationInfo.correlNames.get(0),
3434+
bb.root().getRowType(), correlationInfo.fieldMapping);
3435+
newExprs.replaceAll(e -> e.accept(shuttle));
3436+
}
3437+
3438+
return new MassagedCorrelationExpressions(newExprs, correlationInfo.correlNames.get(0),
3439+
callback);
33763440
}
33773441

33783442
/**
@@ -3773,26 +3837,22 @@ private void createAggImpl(Blackboard bb,
37733837

37743838
final RelNode inputRel = bb.root();
37753839

3776-
// Project the expressions required by agg and having.
3777-
RelNode intermediateProject = relBuilder.push(inputRel)
3778-
.projectNamed(preExprs.leftList(), preExprs.rightList(), false)
3779-
.build();
3780-
final RelNode r2;
3781-
// deal with correlation
3782-
final CorrelationUse p = getCorrelationUse(bb, intermediateProject);
3783-
if (p != null) {
3784-
assert p.r instanceof Project;
3785-
// correlation variables have been normalized in p.r, we should use expressions
3786-
// in p.r instead of the original exprs
3787-
Project project1 = (Project) p.r;
3788-
r2 = relBuilder.push(bb.root())
3789-
.projectNamed(project1.getProjects(), project1.getRowType().getFieldNames(),
3790-
true, ImmutableSet.of(p.id))
3791-
.build();
3840+
final @Nullable MassagedCorrelationExpressions massagedResult =
3841+
massageExpressionsForCorrelation(bb, preExprs.leftList());
3842+
relBuilder.push(inputRel);
3843+
if (massagedResult == null) {
3844+
relBuilder.projectNamed(preExprs.leftList(), preExprs.rightList(), false);
37923845
} else {
3793-
r2 = intermediateProject;
3846+
relBuilder.projectNamed(massagedResult.exprs, preExprs.rightList(), false,
3847+
ImmutableSet.of(massagedResult.correlId));
3848+
}
3849+
3850+
RelNode project = relBuilder.build();
3851+
if (massagedResult != null) {
3852+
massagedResult.callback.accept(project);
37943853
}
3795-
bb.setRoot(r2, false);
3854+
3855+
bb.setRoot(project, false);
37963856
bb.mapRootRelToFieldProjection.put(bb.root(), r.groupExprProjection);
37973857

37983858
// REVIEW jvs 31-Oct-2007: doesn't the declaration of
@@ -5006,27 +5066,22 @@ private void convertNonAggregateSelectList(
50065066
SqlValidatorUtil.uniquify(fieldNames,
50075067
catalogReader.nameMatcher().isCaseSensitive());
50085068

5009-
relBuilder.push(bb.root())
5010-
.projectNamed(exprs, uniqueFieldNames, true);
5069+
final @Nullable MassagedCorrelationExpressions massagedResult =
5070+
massageExpressionsForCorrelation(bb, exprs);
5071+
relBuilder.push(bb.root());
5072+
if (massagedResult == null) {
5073+
relBuilder.projectNamed(exprs, uniqueFieldNames, true);
5074+
} else {
5075+
relBuilder.projectNamed(massagedResult.exprs, uniqueFieldNames, true,
5076+
ImmutableSet.of(massagedResult.correlId));
5077+
}
50115078

50125079
RelNode project = relBuilder.build();
5013-
5014-
final RelNode r;
5015-
final CorrelationUse p = getCorrelationUse(bb, project);
5016-
if (p != null) {
5017-
assert p.r instanceof Project;
5018-
// correlation variables have been normalized in p.r, we should use expressions
5019-
// in p.r instead of the original exprs
5020-
Project project1 = (Project) p.r;
5021-
r = relBuilder.push(bb.root())
5022-
.projectNamed(project1.getProjects(), uniqueFieldNames, true,
5023-
ImmutableSet.of(p.id))
5024-
.build();
5025-
} else {
5026-
r = project;
5080+
if (massagedResult != null) {
5081+
massagedResult.callback.accept(project);
50275082
}
50285083

5029-
bb.setRoot(r, false);
5084+
bb.setRoot(project, false);
50305085

50315086
assert bb.columnMonotonicities.isEmpty();
50325087
bb.columnMonotonicities.addAll(columnMonotonicityList);
@@ -6179,13 +6234,13 @@ RexFieldAccess getFieldAccess(CorrelationId name) {
61796234
* Shuttle that rewrites correlation field accesses to use projected field indices
61806235
* when correlation references aggregated relations.
61816236
*/
6182-
private static class CorrelationFieldMappingShuttle extends RelHomogeneousShuttle {
6237+
private static class CorrelationFieldMappingRexShuttle extends RexShuttle {
61836238
private final RexBuilder rexBuilder;
61846239
private final CorrelationId targetCorrelId;
61856240
private final RelDataType newCorrelRowType;
61866241
private final Map<Pair<CorrelationId, Integer>, Integer> fieldMapping;
61876242

6188-
CorrelationFieldMappingShuttle(RexBuilder rexBuilder,
6243+
CorrelationFieldMappingRexShuttle(RexBuilder rexBuilder,
61896244
CorrelationId targetCorrelId,
61906245
RelDataType newCorrelRowType,
61916246
Map<Pair<CorrelationId, Integer>, Integer> fieldMapping) {
@@ -6195,23 +6250,40 @@ private static class CorrelationFieldMappingShuttle extends RelHomogeneousShuttl
61956250
this.fieldMapping = fieldMapping;
61966251
}
61976252

6198-
@Override public RelNode visit(RelNode other) {
6199-
return super.visit(other).accept(new RexShuttle() {
6200-
@Override public RexNode visitFieldAccess(RexFieldAccess fieldAccess) {
6201-
if (fieldAccess.getReferenceExpr() instanceof RexCorrelVariable) {
6202-
RexCorrelVariable correlVar = (RexCorrelVariable) fieldAccess.getReferenceExpr();
6203-
if (correlVar.id.equals(targetCorrelId)) {
6204-
Integer newIndex =
6205-
fieldMapping.get(Pair.of(correlVar.id, fieldAccess.getField().getIndex()));
6206-
if (newIndex != null) {
6207-
return rexBuilder.makeFieldAccess(
6208-
rexBuilder.makeCorrel(newCorrelRowType, correlVar.id), newIndex);
6209-
}
6210-
}
6253+
@Override public RexNode visitFieldAccess(RexFieldAccess fieldAccess) {
6254+
if (fieldAccess.getReferenceExpr() instanceof RexCorrelVariable) {
6255+
RexCorrelVariable correlVar = (RexCorrelVariable) fieldAccess.getReferenceExpr();
6256+
if (correlVar.id.equals(targetCorrelId)) {
6257+
Integer newIndex =
6258+
fieldMapping.get(Pair.of(correlVar.id, fieldAccess.getField().getIndex()));
6259+
if (newIndex != null) {
6260+
return rexBuilder.makeFieldAccess(
6261+
rexBuilder.makeCorrel(newCorrelRowType, correlVar.id), newIndex);
62116262
}
6212-
return super.visitFieldAccess(fieldAccess);
62136263
}
6214-
});
6264+
}
6265+
return super.visitFieldAccess(fieldAccess);
6266+
}
6267+
}
6268+
6269+
/**
6270+
* Shuttle that rewrites correlation field accesses to use projected field indices
6271+
* when correlation references aggregated relations.
6272+
*/
6273+
private static class CorrelationFieldMappingShuttle extends RelHomogeneousShuttle {
6274+
private final CorrelationFieldMappingRexShuttle rexShuttle;
6275+
6276+
CorrelationFieldMappingShuttle(RexBuilder rexBuilder,
6277+
CorrelationId targetCorrelId,
6278+
RelDataType newCorrelRowType,
6279+
Map<Pair<CorrelationId, Integer>, Integer> fieldMapping) {
6280+
this.rexShuttle =
6281+
new CorrelationFieldMappingRexShuttle(rexBuilder, targetCorrelId, newCorrelRowType,
6282+
fieldMapping);
6283+
}
6284+
6285+
@Override public RelNode visit(RelNode other) {
6286+
return super.visit(other).accept(this.rexShuttle);
62156287
}
62166288
}
62176289

@@ -6581,6 +6653,37 @@ private static class CorrelationUse {
65816653
}
65826654
}
65836655

6656+
/** Wrapper around information collected by {@link #getCorrelationInfo}. */
6657+
private static class ResolvedCorrelationInfo {
6658+
public final List<CorrelationId> correlNames;
6659+
public final ImmutableBitSet requiredColumns;
6660+
public final Map<Pair<CorrelationId, Integer>, Integer> fieldMapping;
6661+
6662+
ResolvedCorrelationInfo(List<CorrelationId> correlNames, ImmutableBitSet requiredColumns,
6663+
Map<Pair<CorrelationId, Integer>, Integer> fieldMapping) {
6664+
this.correlNames = correlNames;
6665+
this.requiredColumns = requiredColumns;
6666+
this.fieldMapping = fieldMapping;
6667+
}
6668+
}
6669+
6670+
/** Wrapper around optionally returned results from {@link #massageExpressionsForCorrelation}. */
6671+
private static class MassagedCorrelationExpressions {
6672+
/** Modified expressions. */
6673+
public final List<RexNode> exprs;
6674+
/** CorrelationId to use when building the relational expression. */
6675+
public final CorrelationId correlId;
6676+
/** Callback to be called after the RelNode tree which uses these expressions is built. */
6677+
public final Consumer<RelNode> callback;
6678+
6679+
MassagedCorrelationExpressions(List<RexNode> exprs, CorrelationId correlId,
6680+
Consumer<RelNode> callback) {
6681+
this.exprs = exprs;
6682+
this.correlId = correlId;
6683+
this.callback = callback;
6684+
}
6685+
}
6686+
65846687
/** Returns a default {@link Config}. */
65856688
public static Config config() {
65866689
return CONFIG;

core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12190,6 +12190,7 @@ private void checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
1219012190

1219112191
sql(sql)
1219212192
.withRule(CoreRules.PROJECT_MERGE)
12193+
.withRelBuilderConfig(rbc -> rbc.withBloat(-1))
1219312194
.checkUnchanged();
1219412195
}
1219512196

0 commit comments

Comments
 (0)