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

zhenchen 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 f89d8862e1 [CALCITE-7303] Subqueries cannot be decorrelated if filter 
condition have multi CorrelationId
f89d8862e1 is described below

commit f89d8862e19a878a6af3d8fa0d2f4f4c9420c80a
Author: iwanttobepowerful <[email protected]>
AuthorDate: Fri Nov 28 18:40:13 2025 +0800

    [CALCITE-7303] Subqueries cannot be decorrelated if filter condition have 
multi CorrelationId
---
 .../apache/calcite/sql2rel/RelDecorrelator.java    | 14 +++-
 .../calcite/sql2rel/RelDecorrelatorTest.java       | 84 ++++++++++++++++++++++
 core/src/test/resources/sql/sub-query.iq           | 62 ++++++++++++++++
 3 files changed, 157 insertions(+), 3 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 e9d7f498f9..e45edff7e5 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -1495,17 +1495,25 @@ private Frame 
decorrelateInputWithValueGenerator(RelNode rel, Frame frame) {
   }
 
   /** Finds a {@link RexInputRef} that is equivalent to a {@link CorRef},
-   * and if found, throws a {@link org.apache.calcite.util.Util.FoundOne}. */
+   * and if found, throws a {@link org.apache.calcite.util.Util.FoundOne}.
+   *
+   * <p>The equivalent expression must not contain any {@link RexFieldAccess},
+   * ensuring that we only map the correlation variable to a local field or
+   * expression from the current relational expression (e.g., a {@link 
RexInputRef}),
+   * rather than to another correlation variable.
+   */
   private static void findCorrelationEquivalent(CorRef correlation, RexNode e)
       throws Util.FoundOne {
     switch (e.getKind()) {
     case EQUALS:
       final RexCall call = (RexCall) e;
       final List<RexNode> operands = call.getOperands();
-      if (references(operands.get(0), correlation)) {
+      if (!RexUtil.containsFieldAccess(operands.get(1))
+          && references(operands.get(0), correlation)) {
         throw new Util.FoundOne(operands.get(1));
       }
-      if (references(operands.get(1), correlation)) {
+      if (!RexUtil.containsFieldAccess(operands.get(0))
+          && references(operands.get(1), correlation)) {
         throw new Util.FoundOne(operands.get(0));
       }
       break;
diff --git 
a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java 
b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
index c079af7740..f6504fdef9 100644
--- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
@@ -272,6 +272,90 @@ public static Frameworks.ConfigBuilder config() {
     assertThat(after, hasTree(planAfter));
   }
 
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7303";>[CALCITE-7303]
+   * Subqueries cannot be decorrelated if filter condition have multi 
CorrelationId</a>. */
+  @Test void testCorrelationEquivalent() {
+    final FrameworkConfig frameworkConfig = config().build();
+    final RelBuilder builder = RelBuilder.create(frameworkConfig);
+    final RelOptCluster cluster = builder.getCluster();
+    final Planner planner = Frameworks.getPlanner(frameworkConfig);
+    final String sql = "SELECT deptno\n"
+        + "FROM emp e\n"
+        + "WHERE EXISTS (\n"
+        + "  SELECT *\n"
+        + "  FROM dept d\n"
+        + "  WHERE EXISTS(\n"
+        + "    SELECT *\n"
+        + "    FROM bonus b\n"
+        + "    WHERE b.ename = e.ename\n"
+        + "    AND b.job = d.dname\n"
+        + "    AND d.deptno = e.deptno))";
+    final RelNode originalRel;
+    try {
+      final SqlNode parse = planner.parse(sql);
+      final SqlNode validate = planner.validate(parse);
+      originalRel = planner.rel(validate).rel;
+    } catch (Exception e) {
+      throw TestUtil.rethrow(e);
+    }
+
+    final HepProgram hepProgram = HepProgram.builder()
+        .addRuleCollection(
+            ImmutableList.of(
+                // SubQuery program rules
+                CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
+                CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
+                CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
+        .build();
+    final Program program =
+        Programs.of(hepProgram, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode before =
+        program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+    final String planBefore = ""
+        + "LogicalProject(DEPTNO=[$7])\n"
+        + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7])\n"
+        + "    LogicalCorrelate(correlation=[$cor0], joinType=[inner], 
requiredColumns=[{1, 7}])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n"
+        + "      LogicalAggregate(group=[{0}])\n"
+        + "        LogicalProject(i=[true])\n"
+        + "          LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2])\n"
+        + "            LogicalCorrelate(correlation=[$cor1], joinType=[inner], 
requiredColumns=[{0, 1}])\n"
+        + "              LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "              LogicalAggregate(group=[{0}])\n"
+        + "                LogicalProject(i=[true])\n"
+        + "                  LogicalFilter(condition=[AND(=($0, $cor0.ENAME), 
=(CAST($1):VARCHAR(14), $cor1.DNAME), =($cor1.DEPTNO, $cor0.DEPTNO))])\n"
+        + "                    LogicalTableScan(table=[[scott, BONUS]])\n";
+    assertThat(before, hasTree(planBefore));
+
+    // Decorrelate without any rules, just "purely" decorrelation algorithm on 
RelDecorrelator
+    final RelNode after =
+        RelDecorrelator.decorrelateQuery(before, builder, 
RuleSets.ofList(Collections.emptyList()),
+            RuleSets.ofList(Collections.emptyList()));
+    // Verify plan
+    final String planAfter = ""
+        + "LogicalProject(DEPTNO=[$7])\n"
+        + "  LogicalJoin(condition=[AND(=($1, $8), =($7, $9))], 
joinType=[inner])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalProject(ENAME0=[$0], DEPTNO0=[$1], $f2=[true])\n"
+        + "      LogicalAggregate(group=[{0, 1}])\n"
+        + "        LogicalProject(ENAME0=[$3], DEPTNO0=[$4])\n"
+        + "          LogicalJoin(condition=[AND(=($0, $5), =($1, $6))], 
joinType=[inner])\n"
+        + "            LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "            LogicalProject(ENAME0=[$0], DEPTNO=[$1], DEPTNO0=[$2], 
DNAME=[$3], $f4=[true])\n"
+        + "              LogicalAggregate(group=[{0, 1, 2, 3}])\n"
+        + "                LogicalProject(ENAME0=[$4], DEPTNO=[$5], 
DEPTNO0=[$6], DNAME=[$7])\n"
+        + "                  LogicalJoin(condition=[AND(=($0, $4), 
=(CAST($1):VARCHAR(14), $7))], joinType=[inner])\n"
+        + "                    LogicalTableScan(table=[[scott, BONUS]])\n"
+        + "                    LogicalJoin(condition=[=($2, $1)], 
joinType=[inner])\n"
+        + "                      LogicalProject(ENAME=[$1], DEPTNO=[$7])\n"
+        + "                        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "                      LogicalProject(DEPTNO=[$0], DNAME=[$1])\n"
+        + "                        LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(after, hasTree(planAfter));
+  }
+
   /**
    * Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6468";>[CALCITE-6468] 
RelDecorrelator
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index ab9a52fa79..2fa71122fb 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -5443,4 +5443,66 @@ ORDER BY deptno;
 
 !ok
 
+# [CALCITE-7303] Subqueries cannot be decorrelated if filter condition have 
multi CorrelationId
+SELECT deptno
+FROM emp e
+WHERE EXISTS
+    (SELECT *
+    FROM dept d
+    WHERE EXISTS
+        (SELECT *
+        FROM bonus b
+        WHERE b.ename = e.ename
+        AND d.deptno = e.deptno));
++--------+
+| DEPTNO |
++--------+
++--------+
+(0 rows)
+
+!ok
+
+# [CALCITE-7303] Subqueries cannot be decorrelated if filter condition have 
multi CorrelationId
+SELECT deptno
+FROM emp e
+WHERE EXISTS (
+  SELECT *
+  FROM dept d
+  WHERE EXISTS(
+    SELECT *
+    FROM bonus b
+    WHERE b.ename = e.ename
+    AND b.job = d.dname
+    AND d.deptno = e.deptno));
++--------+
+| DEPTNO |
++--------+
++--------+
+(0 rows)
+
+!ok
+
+# [CALCITE-7303] Subqueries cannot be decorrelated if filter condition have 
multi CorrelationId
+WITH t0(t0a, t0b) AS (VALUES (1, 1), (1, 2), (2, 1), (2, 2), (2, 0)),
+     t1(t1a, t1b, t1c) AS (VALUES (1, 1, 3)),
+     t2(t2a, t2b, t2c) AS (VALUES (1, 1, 5), (2, 2, 7))
+SELECT t0a
+FROM t0 e
+WHERE EXISTS
+    (SELECT *
+    FROM t1 d
+    WHERE EXISTS
+        (SELECT *
+        FROM t2 b
+        WHERE b.t2b = e.t0b
+        AND d.t1a = e.t0a));
++-----+
+| T0A |
++-----+
+|   1 |
+|   1 |
++-----+
+(2 rows)
+
+!ok
 # End sub-query.iq

Reply via email to