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

Reply via email to