This is an automated email from the ASF dual-hosted git repository.

silun 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 869fe15f36 [CALCITE-7411] When a SCALAR_QUERY in PROJECT contains 
correlated variables execution fails using TopDownGeneralDecorrelator
869fe15f36 is described below

commit 869fe15f36fa5579f2a1cf289958fa763af36a9b
Author: Zhen Chen <[email protected]>
AuthorDate: Mon Feb 9 10:45:21 2026 +0800

    [CALCITE-7411] When a SCALAR_QUERY in PROJECT contains correlated variables 
execution fails using TopDownGeneralDecorrelator
---
 .../apache/calcite/sql2rel/SqlToRelConverter.java  | 21 ++++++++-
 .../sql2rel/TopDownGeneralDecorrelator.java        |  9 +++-
 .../org/apache/calcite/test/CoreQuidemTest2.java   |  1 -
 .../org/apache/calcite/test/RelOptRulesTest.java   | 24 ++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 53 +++++++++++++++++++++
 core/src/test/resources/sql/measure-paper.iq       | 55 ++++++++++++++++++++++
 6 files changed, 159 insertions(+), 4 deletions(-)

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 8fc83104c1..4622176a07 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -3882,7 +3882,26 @@ private void createAggImpl(Blackboard bb,
     // implement the SELECT list
     relBuilder.project(projects.leftList(), projects.rightList())
         .rename(projects.rightList());
-    bb.setRoot(relBuilder.build(), false);
+
+    RelNode tmpProject = relBuilder.build();
+
+    // Check for correlation variables that may be used in the SELECT list
+    final RelNode finalProject;
+    final CorrelationUse correlationUse = getCorrelationUse(bb, tmpProject);
+    if (correlationUse != null) {
+      assert correlationUse.r instanceof Project;
+      // correlation variables have been normalized in correlationUse.r,
+      // we should use expressions in correlationUse.r
+      Project project1 = (Project) correlationUse.r;
+      finalProject = relBuilder.push(tmpProject.getInput(0))
+          .project(project1.getProjects(), 
project1.getRowType().getFieldNames(), true,
+              ImmutableSet.of(correlationUse.id))
+          .build();
+    } else {
+      finalProject = tmpProject;
+    }
+
+    bb.setRoot(finalProject, false);
 
     // Tell bb which of group columns are sorted.
     bb.columnMonotonicities.clear();
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/TopDownGeneralDecorrelator.java 
b/core/src/main/java/org/apache/calcite/sql2rel/TopDownGeneralDecorrelator.java
index 6959d56354..50b2006154 100644
--- 
a/core/src/main/java/org/apache/calcite/sql2rel/TopDownGeneralDecorrelator.java
+++ 
b/core/src/main/java/org/apache/calcite/sql2rel/TopDownGeneralDecorrelator.java
@@ -919,10 +919,15 @@ static List<RexNode> rewrite(
 
     @Override public RexNode visitInputRef(RexInputRef inputRef) {
       int newIndex = 
requireNonNull(unnestedQuery.oldToNewOutputs.get(inputRef.getIndex()));
-      if (newIndex == inputRef.getIndex()) {
+      if (newIndex == inputRef.getIndex()
+          && inputRef.getType().equals(
+              
unnestedQuery.r.getRowType().getFieldList().get(newIndex).getType())) {
         return inputRef;
       }
-      return new RexInputRef(newIndex, inputRef.getType());
+      // Use the type from the new row type to handle nullability changes
+      // (e.g., after LEFT JOIN, right-side fields become nullable)
+      return new RexInputRef(newIndex,
+          unnestedQuery.r.getRowType().getFieldList().get(newIndex).getType());
     }
 
     @Override public RexNode visitFieldAccess(RexFieldAccess fieldAccess) {
diff --git a/core/src/test/java/org/apache/calcite/test/CoreQuidemTest2.java 
b/core/src/test/java/org/apache/calcite/test/CoreQuidemTest2.java
index 0164eeea6e..b8ef8562bc 100644
--- a/core/src/test/java/org/apache/calcite/test/CoreQuidemTest2.java
+++ b/core/src/test/java/org/apache/calcite/test/CoreQuidemTest2.java
@@ -47,7 +47,6 @@ public static void main(String[] args) throws Exception {
     // TODO: Support measure
     paths.remove("sql/measure.iq");
     paths.remove("sql/sub-query.iq");
-    paths.remove("sql/measure-paper.iq");
     return paths;
   }
 
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 81f07a6f6a..98beae87c7 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -12243,4 +12243,28 @@ private void 
checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
     sql(sql).withPlanner(hepPlanner)
             .check();
   }
+
+  /** Test case of
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7411";>[CALCITE-7411]
+   * When a SCALAR_QUERY in PROJECT contains correlated variables execution 
fails
+   * using TopDownGeneralDecorrelator</a>. */
+  @Test void testTopDownGeneralDecorrelateForMeasure() {
+    final String sql = "SELECT job,\n"
+        + "  (SELECT\n"
+        + "      CAST(SUM(i.sal) - SUM(COALESCE(i.comm, 0)) AS DECIMAL(10, 2)) 
/ SUM(i.sal)\n"
+        + "    FROM emp AS i\n"
+        + "    WHERE i.job = e.job) AS profitMargin,\n"
+        + "  COUNT(*) AS \"count\"\n"
+        + "FROM emp AS e\n"
+        + "GROUP BY job";
+
+    sql(sql)
+        .withRule(
+            CoreRules.PROJECT_SUB_QUERY_TO_MARK_CORRELATE,
+            CoreRules.PROJECT_MERGE,
+            CoreRules.PROJECT_REMOVE)
+        .withLateDecorrelate(true)
+        .withTopDownGeneralDecorrelate(true)
+        .check();
+  }
 }
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 103744e311..e2f83bf2c1 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -20169,6 +20169,59 @@ LogicalProject(EMPNO=[$0])
   LogicalJoin(condition=[IS NOT DISTINCT FROM($7, $9)], joinType=[inner])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
     LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testTopDownGeneralDecorrelateForMeasure">
+    <Resource name="sql">
+      <![CDATA[SELECT job,
+  (SELECT
+      CAST(SUM(i.sal) - SUM(COALESCE(i.comm, 0)) AS DECIMAL(10, 2)) / 
SUM(i.sal)
+    FROM emp AS i
+    WHERE i.job = e.job) AS profitMargin,
+  COUNT(*) AS "count"
+FROM emp AS e
+GROUP BY job]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(variablesSet=[[$cor0]], JOB=[$0], PROFITMARGIN=[$SCALAR_QUERY({
+LogicalProject(EXPR$0=[/(CAST(-($0, $1)):DECIMAL(10, 2), $0)])
+  LogicalAggregate(group=[{}], agg#0=[SUM($0)], agg#1=[SUM($1)])
+    LogicalProject(SAL=[$5], $f1=[$6])
+      LogicalFilter(condition=[=($2, $cor0.JOB)])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+})], count=[$1])
+  LogicalAggregate(group=[{0}], count=[COUNT()])
+    LogicalProject(JOB=[$2])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+    <Resource name="planMid">
+      <![CDATA[
+LogicalProject(JOB=[$0], PROFITMARGIN=[$2], count=[$1])
+  LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{0}])
+    LogicalAggregate(group=[{0}], count=[COUNT()])
+      LogicalProject(JOB=[$2])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalProject(EXPR$0=[/(CAST(-($0, $1)):DECIMAL(10, 2), $0)])
+      LogicalAggregate(group=[{}], agg#0=[SUM($0)], agg#1=[SUM($1)])
+        LogicalProject(SAL=[$5], $f1=[$6])
+          LogicalFilter(condition=[=($2, $cor0.JOB)])
+            LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(JOB=[$0], PROFITMARGIN=[$2], count=[$1])
+  LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $3)], joinType=[left])
+    LogicalAggregate(group=[{0}], count=[COUNT()])
+      LogicalProject(JOB=[$2])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalProject($f0=[/(CAST(-($1, $2)):DECIMAL(10, 2), $1)], JOB=[$0])
+      LogicalAggregate(group=[{2}], agg#0=[SUM($0)], agg#1=[SUM($1)])
+        LogicalProject(SAL=[$5], COMM=[$6], JOB=[$2])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/measure-paper.iq 
b/core/src/test/resources/sql/measure-paper.iq
index 190713d811..c64150fdba 100644
--- a/core/src/test/resources/sql/measure-paper.iq
+++ b/core/src/test/resources/sql/measure-paper.iq
@@ -224,6 +224,7 @@ SELECT "prodName",
   COUNT(*) AS "count"
 FROM "Orders" AS o
 GROUP BY "prodName";
+!if (use_old_decorr) {
 +----------+--------------+-------+
 | prodName | profitMargin | count |
 +----------+--------------+-------+
@@ -234,6 +235,20 @@ GROUP BY "prodName";
 (3 rows)
 
 !ok
+!}
+
+!if (use_new_decorr) {
++----------+--------------------+-------+
+| prodName | profitMargin       | count |
++----------+--------------------+-------+
+| Acme     |               0.60 |     1 |
+| Happy    | 0.4705882352941176 |     3 |
+| Whizz    | 0.6666666666666667 |     1 |
++----------+--------------------+-------+
+(3 rows)
+
+!ok
+!}
 
 # Profit margin for 'Happy' orders for each customer
 SELECT "custName",
@@ -505,4 +520,44 @@ FROM
 !ok
 !}
 
+# [CALCITE-7411] When a SCALAR_QUERY in PROJECT contains correlated variables
+#   execution fails using TopDownGeneralDecorrelator
+!use scott
+SELECT job,
+  (SELECT
+      CAST(SUM(i.sal) - SUM(COALESCE(i.comm, 0)) AS DECIMAL(10, 2)) / 
SUM(i.sal)
+    FROM emp AS i
+    WHERE i.job = e.job) AS profitMargin,
+  COUNT(*) AS "count"
+FROM emp AS e
+GROUP BY job;
+!if (use_old_decorr) {
++-----------+--------------+-------+
+| JOB       | PROFITMARGIN | count |
++-----------+--------------+-------+
+| ANALYST   |     1.000000 |     2 |
+| CLERK     |     1.000000 |     4 |
+| MANAGER   |     1.000000 |     3 |
+| PRESIDENT |     1.000000 |     1 |
+| SALESMAN  |     0.607142 |     4 |
++-----------+--------------+-------+
+(5 rows)
+
+!ok
+!}
+
+!if (use_new_decorr) {
++-----------+--------------------+-------+
+| JOB       | PROFITMARGIN       | count |
++-----------+--------------------+-------+
+| ANALYST   |                  1 |     2 |
+| CLERK     |                  1 |     4 |
+| MANAGER   |                  1 |     3 |
+| PRESIDENT |                  1 |     1 |
+| SALESMAN  | 0.6071428571428571 |     4 |
++-----------+--------------------+-------+
+(5 rows)
+
+!ok
+!}
 # End measure-paper.iq

Reply via email to