This is an automated email from the ASF dual-hosted git repository.
mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new c368970b29 [CALCITE-7885] RelDecorrelator merges distinct correlation
fields and produces extra rows
c368970b29 is described below
commit c368970b2950903432ed61ecd7ff404a67e16fb1
Author: zzwqqq <[email protected]>
AuthorDate: Fri Oct 23 17:17:40 2026 +0800
[CALCITE-7885] RelDecorrelator merges distinct correlation fields and
produces extra rows
---
.../apache/calcite/sql2rel/RelDecorrelator.java | 63 ++++++++++------------
core/src/test/resources/sql/sub-query.iq | 17 ++++++
2 files changed, 44 insertions(+), 36 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index 5207db8566..02ec8afb66 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -854,8 +854,6 @@ protected RexNode removeCorrelationExpr(
newRel = rewriteScalarAggregate(rel, newRel, outputMap, corDefOutputs);
}
- // Aggregate does not change input ordering so corVars will be
- // located at the same position as the input newProject.
return register(rel, newRel, outputMap, corDefOutputs);
}
@@ -966,16 +964,6 @@ private RelNode rewriteScalarAggregate(Aggregate oldRel,
requireNonNull(createValueGenerator(corVarList, 0,
valueGenCorDefOutputs));
final int valueGenFieldCount = valueGen.getRowType().getFieldCount();
- final Map<Integer, RexNode> newProjectMap = new HashMap<>();
- for (Map.Entry<CorDef, Integer> corDefOutput : corDefOutputs.entrySet()) {
- final CorDef corDef = corDefOutput.getKey();
- final int leftPos = requireNonNull(valueGenCorDefOutputs.get(corDef));
- final int rightPos = corDefOutput.getValue();
- final RelDataType leftType =
valueGen.getRowType().getFieldList().get(leftPos).getType();
- final RexNode leftRef = new RexInputRef(leftPos, leftType);
- newProjectMap.put(valueGenFieldCount + rightPos, leftRef);
- }
-
// Build join conditions
final List<RexNode> conditions =
buildCorDefJoinConditions(valueGenCorDefOutputs, corDefOutputs,
@@ -987,36 +975,39 @@ private RelNode rewriteScalarAggregate(Aggregate oldRel,
// to ensure each corVar's aggregate result is output.
final RelNode join = relBuilder.push(valueGen).push(newRel)
.join(JoinRelType.LEFT, joinCond).build();
- RelDataType joinRowType = join.getRowType();
+ final RelDataType joinRowType = join.getRowType();
+ final PairList<RexNode, String> projects = PairList.of();
- RexBuilder rexBuilder = relBuilder.getRexBuilder();
- // Here we record the mapping between the original index and the new
project.
- // For the count, we map it as `case when x is null then 0 else x`.
- for (int i1 = 0; i1 < oldRel.getAggCallList().size(); i1++) {
- AggregateCall aggCall = oldRel.getAggCallList().get(i1);
- if (aggCall.getAggregation() instanceof SqlCountAggFunction) {
- int index = requireNonNull(outputMap.get(i1 + oldRel.getGroupCount()));
- final RexInputRef ref = RexInputRef.of(index + valueGenFieldCount,
joinRowType);
- ImmutableList<RexNode> exprs =
+ // Correlation fields must come from the value generator. Fields that share
+ // an aggregate output can have different values on unmatched outer rows.
+ for (Map.Entry<CorDef, Integer> entry : corDefOutputs.entrySet()) {
+ final int index =
requireNonNull(valueGenCorDefOutputs.get(entry.getKey()));
+ final String name =
newRel.getRowType().getFieldNames().get(entry.getValue());
+ entry.setValue(projects.size());
+ projects.add(RexInputRef.of(index, joinRowType), name);
+ }
+
+ // Project the original aggregate outputs, preserving COUNT's empty-input
+ // value, and rebuild their positions independently of the correlation
fields.
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+ for (RelDataTypeField field : oldRel.getRowType().getFieldList()) {
+ final int index = requireNonNull(outputMap.get(field.getIndex()));
+ final RexInputRef ref = RexInputRef.of(valueGenFieldCount + index,
joinRowType);
+ RexNode expression = ref;
+ final int aggIndex = field.getIndex() - oldRel.getGroupCount();
+ if (aggIndex >= 0
+ && oldRel.getAggCallList().get(aggIndex).getAggregation()
+ instanceof SqlCountAggFunction) {
+ final ImmutableList<RexNode> operands =
ImmutableList.of(relBuilder.isNotNull(ref), ref,
relBuilder.literal(0));
- RexNode specificCountValue =
rexBuilder.makeCall(SqlStdOperatorTable.CASE, exprs);
- newProjectMap.put(ref.getIndex(), specificCountValue);
- }
- }
-
- // Build [07] LogicalProject(DEPTNO=[$0], EXPR$0=[CASE(IS NOT NULL($2),
$2, 0)])
- // to handle COUNT function by converting nulls to zero.
- final List<RexNode> newProjects = new ArrayList<>();
- for (int index : ImmutableBitSet.range(valueGenFieldCount,
joinRowType.getFieldCount())) {
- if (newProjectMap.containsKey(index)) {
- newProjects.add(requireNonNull(newProjectMap.get(index)));
- } else {
- newProjects.add(RexInputRef.of(index, joinRowType));
+ expression = rexBuilder.makeCall(SqlStdOperatorTable.CASE, operands);
}
+ outputMap.put(field.getIndex(), projects.size());
+ projects.add(expression, newRel.getRowType().getFieldNames().get(index));
}
return relBuilder.push(join)
- .project(newProjects, newRel.getRowType().getFieldNames())
+ .project(projects.leftList(), projects.rightList())
.build();
}
diff --git a/core/src/test/resources/sql/sub-query.iq
b/core/src/test/resources/sql/sub-query.iq
index 4a2231a3f0..6ea6492808 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -10986,3 +10986,20 @@ order by emp.deptno, emp.sal;
!ok
!set expand false
+
+# [CALCITE-7885] RelDecorrelator merges distinct correlation fields and
produces extra rows
+# The result of this query was validated using Postgres.
+with t(x, y) as (values (1, 1), (2, 1)),
+ s(z) as (values (1))
+select t.x, t.y,
+ (select count(*) from s where s.z = t.x and s.z = t.y) as c
+from t;
++---+---+---+
+| X | Y | C |
++---+---+---+
+| 1 | 1 | 1 |
+| 2 | 1 | 0 |
++---+---+---+
+(2 rows)
+
+!ok