This is an automated email from the ASF dual-hosted git repository.
xiong 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 6ba1b536a6 [CALCITE-7274] RexFieldAccess has wrong index when use trim
unused fields
6ba1b536a6 is described below
commit 6ba1b536a6a17de776386681b45a9d4c0d7cf2a7
Author: Xiong Duan <[email protected]>
AuthorDate: Sun Dec 28 08:26:55 2025 +0800
[CALCITE-7274] RexFieldAccess has wrong index when use trim unused fields
---
.../apache/calcite/sql/validate/SelectScope.java | 11 ++
.../apache/calcite/sql2rel/RelFieldTrimmer.java | 57 +++++++-
.../apache/calcite/sql2rel/SqlToRelConverter.java | 15 ++-
.../org/apache/calcite/test/RelOptRulesTest.xml | 8 +-
.../apache/calcite/test/SqlToRelConverterTest.xml | 4 +-
core/src/test/resources/sql/sub-query.iq | 144 +++++++++++++++++++++
6 files changed, 230 insertions(+), 9 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SelectScope.java
b/core/src/main/java/org/apache/calcite/sql/validate/SelectScope.java
index e4cfce77d9..376a7c6482 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SelectScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SelectScope.java
@@ -222,4 +222,15 @@ public boolean existingWindowName(String winName) {
public void setExpandedSelectList(@Nullable List<SqlNode> selectList) {
expandedSelectList = selectList;
}
+
+ @Override public boolean isWithin(SqlValidatorScope scope2) {
+ if (this == scope2) {
+ return true;
+ }
+ // go from the JOIN to the enclosing SELECT
+ if (scope2 instanceof JoinScope) {
+ return isWithin(requireNonNull(((JoinScope) scope2).getUsingScope(),
"usingScope"));
+ }
+ return false;
+ }
}
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
index be08ee7ec2..0c9c761b33 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -233,6 +233,58 @@ protected TrimResult trimChild(
return dispatchTrimFields(input, fieldsUsedBuilder.build(), extraFields);
}
+ /**
+ * Trims the fields of an input relational expression for RelNode with
multiple inputs.
+ *
+ * @param rel Relational expression
+ * @param input Input relational expression, whose fields to trim
+ * @param startIndex Start index of the field range to process
+ * @param endIndex End index of the field range to process (exclusive)
+ * @param fieldsUsed Bitmap of fields needed by the consumer
+ * @return New relational expression and its field mapping
+ */
+ protected TrimResult trimChild(
+ RelNode rel,
+ RelNode input,
+ int startIndex,
+ int endIndex,
+ final ImmutableBitSet fieldsUsed,
+ Set<RelDataTypeField> extraFields) {
+ final ImmutableBitSet.Builder fieldsUsedBuilder = fieldsUsed.rebuild();
+
+ // Fields that define the collation cannot be discarded.
+ final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
+ final ImmutableList<RelCollation> collations = mq.collations(input);
+ if (collations != null) {
+ for (RelCollation collation : collations) {
+ for (RelFieldCollation fieldCollation :
collation.getFieldCollations()) {
+ fieldsUsedBuilder.set(fieldCollation.getFieldIndex());
+ }
+ }
+ }
+
+ // Correlating variables are a means for other relational expressions to
use
+ // fields.
+ for (final CorrelationId correlation : rel.getVariablesSet()) {
+ rel.accept(
+ new CorrelationReferenceFinder() {
+ @Override protected RexNode handle(RexFieldAccess fieldAccess) {
+ final RexCorrelVariable v =
+ (RexCorrelVariable) fieldAccess.getReferenceExpr();
+ if (v.id.equals(correlation)) {
+ if (fieldAccess.getField().getIndex() >= startIndex
+ && fieldAccess.getField().getIndex() < endIndex) {
+ fieldsUsedBuilder.set(fieldAccess.getField().getIndex() -
startIndex);
+ }
+ }
+ return fieldAccess;
+ }
+ });
+ }
+
+ return dispatchTrimFields(input, fieldsUsedBuilder.build(), extraFields);
+ }
+
/**
* Trims a child relational expression, then adds back a dummy project to
* restore the fields that were removed.
@@ -865,7 +917,8 @@ public TrimResult trimFields(
: combinedInputExtraFields;
inputExtraFieldCounts.add(inputExtraFields.size());
TrimResult trimResult =
- trimChild(join, input, inputFieldsUsed.build(), inputExtraFields);
+ trimChild(join, input, offset, offset + inputFieldCount,
+ inputFieldsUsed.build(), inputExtraFields);
newInputs.add(trimResult.left);
if (trimResult.left != input) {
++changeCount;
@@ -946,7 +999,7 @@ public TrimResult trimFields(
requireNonNull(newMatchConditionExpr, "newMatchConditionExpr"));
break;
default:
- relBuilder.join(join.getJoinType(), newConditionExpr);
+ relBuilder.join(join.getJoinType(), newConditionExpr,
join.getVariablesSet());
break;
}
return result(relBuilder.build(), mapping, join);
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index dd97b2a8fd..1ad87eaed6 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -3103,12 +3103,25 @@ protected RelNode createJoin(
p.id, requiredCols, joinType);
}
- final RelNode node =
+ RelNode node =
relBuilder.push(leftRel)
.push(rightRel)
.join(joinType, joinCond)
.build();
+ final CorrelationUse correlationUseInJoin = getCorrelationUse(bb, node);
+ if (correlationUseInJoin != null) {
+ assert correlationUseInJoin.r instanceof Join;
+ Join joinRelTemp = (Join) correlationUseInJoin.r;
+ node =
+ LogicalJoin.create(joinRelTemp.getLeft(),
+ joinRelTemp.getRight(),
+ joinRelTemp.getHints(),
+ joinRelTemp.getCondition(),
+ ImmutableSet.of(correlationUseInJoin.id),
+ joinRelTemp.getJoinType());
+ }
+
// If join conditions are pushed down, update the leaves.
if (node instanceof Project) {
final Join newJoin = (Join) node.getInputs().get(0);
diff --git
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 2b2deaa226..7075571d95 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -8502,7 +8502,7 @@ LogicalProject(ID=[$0], ID0=[$1])
LogicalJoin(condition=[AND(=($0, $1), NOT(EXISTS({
LogicalFilter(condition=[=($0, $cor0.ID)])
LogicalValues(tuples=[[{ 3 }]])
-})))], joinType=[left])
+})))], joinType=[left], variablesSet=[[$cor0]])
LogicalValues(tuples=[[{ 1 }, { 2 }]])
LogicalValues(tuples=[[{ 2 }]])
]]>
@@ -8536,7 +8536,7 @@ LogicalProject(ID=[$0], ID0=[$1])
LogicalJoin(condition=[NOT(EXISTS({
LogicalFilter(condition=[=($0, $cor0.ID0)])
LogicalValues(tuples=[[{ 3 }]])
-}))], joinType=[left])
+}))], joinType=[left], variablesSet=[[$cor0]])
LogicalValues(tuples=[[{ 1 }]])
LogicalValues(tuples=[[{ 2 }]])
]]>
@@ -8598,7 +8598,7 @@ LogicalProject(ID=[$0], ID0=[$1])
LogicalJoin(condition=[OR(=($0, $1), EXISTS({
LogicalFilter(condition=[=($0, $cor0.ID0)])
LogicalValues(tuples=[[{ 3 }]])
-}))], joinType=[left])
+}))], joinType=[left], variablesSet=[[$cor0]])
LogicalValues(tuples=[[{ 1 }]])
LogicalValues(tuples=[[{ 2 }]])
]]>
@@ -8632,7 +8632,7 @@ LogicalProject(ID=[$0], ID0=[$1])
LogicalJoin(condition=[OR(=($0, $1), NOT(EXISTS({
LogicalFilter(condition=[=($0, $cor0.ID0)])
LogicalValues(tuples=[[{ 3 }]])
-})))], joinType=[left])
+})))], joinType=[left], variablesSet=[[$cor0]])
LogicalValues(tuples=[[{ 1 }]])
LogicalValues(tuples=[[{ 2 }]])
]]>
diff --git
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index cbb228b0ed..0c8ce21928 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -3765,7 +3765,7 @@ LogicalAggregate(group=[{}], EXPR$0=[AVG($0)])
LogicalProject(SAL=[$5])
LogicalFilter(condition=[=($7, $cor0.DEPTNO)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
-})))], joinType=[inner])
+})))], joinType=[inner], variablesSet=[[$cor0]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
@@ -3986,7 +3986,7 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2],
MGR=[$3], HIREDATE=[$4], SAL=[$
LogicalJoin(condition=[OR(=($0, 1), EXISTS({
LogicalFilter(condition=[>($0, +($cor0.DEPTNO0, 5))])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
-}))], joinType=[left])
+}))], joinType=[left], variablesSet=[[$cor0]])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
]]>
diff --git a/core/src/test/resources/sql/sub-query.iq
b/core/src/test/resources/sql/sub-query.iq
index 6aa385690a..971e8c0065 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -7505,4 +7505,148 @@ SELECT deptno FROM dept WHERE 1000.00 >
!ok
+# [CALCITE-7274] RexFieldAccess has wrong index when use trim unused fields
+!set trimfields true
+
+SELECT empno
+ FROM emp AS e
+ LEFT JOIN dept AS d
+ ON d.deptno = e.deptno
+ AND (EXISTS (
+ SELECT e2.deptno FROM emp AS e2
+ WHERE e2.deptno = d.deptno
+ GROUP BY e2.deptno
+ HAVING SUM(e2.sal) > 1000000));
+EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
+ EnumerableTableScan(table=[[scott, EMP]])
+!plan
++-------+
+| EMPNO |
++-------+
+| 7369 |
+| 7499 |
+| 7521 |
+| 7566 |
+| 7654 |
+| 7698 |
+| 7782 |
+| 7788 |
+| 7839 |
+| 7844 |
+| 7876 |
+| 7900 |
+| 7902 |
+| 7934 |
++-------+
+(14 rows)
+
+!ok
+
+SELECT empno
+ FROM emp AS e
+ LEFT JOIN dept AS d
+ ON d.dname = e.ename
+ AND (EXISTS (
+ SELECT e2.deptno FROM emp AS e2
+ WHERE e2.deptno = e.deptno
+ GROUP BY e2.deptno
+ HAVING SUM(e2.sal) > 1000000));
+
+EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
+ EnumerableTableScan(table=[[scott, EMP]])
+!plan
++-------+
+| EMPNO |
++-------+
+| 7369 |
+| 7499 |
+| 7521 |
+| 7566 |
+| 7654 |
+| 7698 |
+| 7782 |
+| 7788 |
+| 7839 |
+| 7844 |
+| 7876 |
+| 7900 |
+| 7902 |
+| 7934 |
++-------+
+(14 rows)
+
+!ok
+
+# Same as previous; but don't trim fields
+!set trimfields false
+
+SELECT empno
+ FROM emp AS e
+ LEFT JOIN dept AS d
+ ON d.deptno = e.deptno
+ AND (EXISTS (
+ SELECT e2.deptno FROM emp AS e2
+ WHERE e2.deptno = d.deptno
+ GROUP BY e2.deptno
+ HAVING SUM(e2.sal) > 1000000));
+EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
+ EnumerableTableScan(table=[[scott, EMP]])
+!plan
++-------+
+| EMPNO |
++-------+
+| 7369 |
+| 7499 |
+| 7521 |
+| 7566 |
+| 7654 |
+| 7698 |
+| 7782 |
+| 7788 |
+| 7839 |
+| 7844 |
+| 7876 |
+| 7900 |
+| 7902 |
+| 7934 |
++-------+
+(14 rows)
+
+!ok
+
+SELECT empno
+ FROM emp AS e
+ LEFT JOIN dept AS d
+ ON d.dname = e.ename
+ AND (EXISTS (
+ SELECT e2.deptno FROM emp AS e2
+ WHERE e2.deptno = e.deptno
+ GROUP BY e2.deptno
+ HAVING SUM(e2.sal) > 1000000));
+
+EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0])
+ EnumerableTableScan(table=[[scott, EMP]])
+!plan
++-------+
+| EMPNO |
++-------+
+| 7369 |
+| 7499 |
+| 7521 |
+| 7566 |
+| 7654 |
+| 7698 |
+| 7782 |
+| 7788 |
+| 7839 |
+| 7844 |
+| 7876 |
+| 7900 |
+| 7902 |
+| 7934 |
++-------+
+(14 rows)
+
+!ok
+
# End sub-query.iq