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

zwh 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 69a7a06025 [CALCITE-5390] RelDecorrelator throws NullPointerException
69a7a06025 is described below

commit 69a7a060259153b2c2a105cea13b9d0fb588772e
Author: iwanttobepowerful <[email protected]>
AuthorDate: Wed Jan 28 22:29:15 2026 +0800

    [CALCITE-5390] RelDecorrelator throws NullPointerException
---
 .../apache/calcite/sql2rel/RelDecorrelator.java    |  46 ++++-
 .../sql2rel/TopDownGeneralDecorrelator.java        |   3 -
 .../calcite/sql2rel/RelDecorrelatorTest.java       |  89 +++++++++
 core/src/test/resources/sql/sub-query.iq           |  75 ++++++++
 .../org/apache/calcite/adapter/tpch/TpchTest.java  | 212 +++++++++++++++++++++
 5 files changed, 418 insertions(+), 7 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 47c0040fc3..45596c1d7d 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -918,7 +918,7 @@ protected RexNode removeCorrelationExpr(
    *                     CASE WHEN cnt0 IS NOT NULL THEN cnt0 ELSE 0 END AS cnt
    *              FROM (SELECT deptno FROM dept GROUP BY deptno) d2
    *              LEFT JOIN (
-   *                  SELECT deptno, COUNT(e.empno) cnt0
+   *                  SELECT deptno, COUNT(emp.empno) cnt0
    *                  FROM emp
    *                  WHERE deptno IS NOT NULL
    *                  GROUP BY deptno) e
@@ -1421,7 +1421,8 @@ private static void shiftMapping(Map<Integer, Integer> 
mapping, int startIndex,
     for (CorRef corVar : correlations) {
       final int oldCorVarOffset = corVar.field;
 
-      final RelNode oldInput = requireNonNull(getCorRel(corVar));
+      final RelNode oldInput = findInputRel(corVar);
+
       final Frame frame = requireNonNull(getOrCreateFrame(oldInput));
       final RelNode newInput = frame.r;
 
@@ -1453,7 +1454,7 @@ private static void shiftMapping(Map<Integer, Integer> 
mapping, int startIndex,
 
     RelNode r = null;
     for (CorRef corVar : correlations) {
-      final RelNode oldInput = requireNonNull(getCorRel(corVar));
+      final RelNode oldInput = findInputRel(corVar);
       final RelNode newInput = requireNonNull(getOrCreateFrame(oldInput).r);
 
       if (!joinedInputs.contains(newInput)) {
@@ -1487,7 +1488,7 @@ private static void shiftMapping(Map<Integer, Integer> 
mapping, int startIndex,
     for (CorRef corRef : correlations) {
       // The first input of a Correlate is always the rel defining
       // the correlated variables.
-      final RelNode oldInput = requireNonNull(getCorRel(corRef));
+      final RelNode oldInput = findInputRel(corRef);
       final Frame frame = getOrCreateFrame(oldInput);
       final RelNode newInput = requireNonNull(frame.r);
 
@@ -1533,6 +1534,39 @@ private RelNode getCorRel(CorRef corVar) {
         () -> "r.getInput(0) is null for " + r);
   }
 
+  /**
+   * Finds the RelNode that produces the given correlation variable.
+   *
+   * <p>This method resolves correlation variables by inspecting the {@link 
#frameStack},
+   * which maintains the active correlation contexts during the top-down 
traversal.
+   *
+   * <p>The lookup logic implements <b>Lexical Scoping</b> (with Shadowing):
+   * <ul>
+   *   <li>The {@code frameStack} is traversed from top to bottom (most 
recently pushed to
+   *       least recently pushed). This ensures that if multiple nested 
queries use the same
+   *       {@link CorrelationId}, the innermost definition takes precedence, 
shadowing outer ones.
+   *   </li>
+   * </ul>
+   *
+   * <p>If the variable is not found in the {@code frameStack} (e.g., it might 
be defined outside
+   * the current traversal path or in a global context), the method falls back 
to looking it up
+   * in the global {@link #cm} (CorelMap).
+   *
+   * @param corVar The correlation variable reference to resolve.
+   * @return The {@link RelNode} that produces the correlation variable.
+   */
+  private RelNode findInputRel(CorRef corVar) {
+    final int oldCorVarOffset = corVar.field;
+    for (Pair<CorrelationId, Frame> pair : frameStack) {
+      if (pair.left.equals(corVar.corr)) {
+        if (oldCorVarOffset < pair.right.oldRel.getRowType().getFieldCount()) {
+          return pair.right.oldRel;
+        }
+      }
+    }
+    return getCorRel(corVar);
+  }
+
   /** Adds a value generator to satisfy the correlating variables used by
    * a relational expression, if those variables are not already provided by
    * its input. */
@@ -3766,12 +3800,16 @@ private RexVisitorImpl<Void> rexVisitor(final RelNode 
rel) {
    * and where to find the output fields and correlation variables
    * among its output fields. */
   static class Frame {
+    // The original relational expression before decorrelation
+    final RelNode oldRel;
+    // The decorrelated relational expression
     final RelNode r;
     final ImmutableSortedMap<CorDef, Integer> corDefOutputs;
     final ImmutableSortedMap<Integer, Integer> oldToNewOutputs;
 
     Frame(RelNode oldRel, RelNode r, NavigableMap<CorDef, Integer> 
corDefOutputs,
         Map<Integer, Integer> oldToNewOutputs) {
+      this.oldRel = requireNonNull(oldRel, "oldRel");
       this.r = requireNonNull(r, "r");
       this.corDefOutputs = ImmutableSortedMap.copyOf(corDefOutputs);
       this.oldToNewOutputs = ImmutableSortedMap.copyOf(oldToNewOutputs);
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 8246579946..c1f8494ebd 100644
--- 
a/core/src/main/java/org/apache/calcite/sql2rel/TopDownGeneralDecorrelator.java
+++ 
b/core/src/main/java/org/apache/calcite/sql2rel/TopDownGeneralDecorrelator.java
@@ -973,8 +973,6 @@ public TopDownGeneralDecorrelator getVisitor() {
    * Unnesting information.
    */
   static class UnnestedQuery extends Frame {
-    final RelNode oldRel;
-
     /**
      * Creates a UnnestedQuery.
      *
@@ -986,7 +984,6 @@ static class UnnestedQuery extends Frame {
     UnnestedQuery(RelNode oldRel, RelNode r, NavigableMap<CorDef, Integer> 
corDefOutputs,
         Map<Integer, Integer> oldToNewOutputs) {
       super(oldRel, r, corDefOutputs, oldToNewOutputs);
-      this.oldRel = oldRel;
     }
 
     /**
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 1fabc39d8c..2b406c3c01 100644
--- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
@@ -1669,6 +1669,95 @@ public static Frameworks.ConfigBuilder config() {
     assertThat(after, hasTree(planAfter));
   }
 
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-5390";>[CALCITE-5390]
+   * RelDecorrelator throws NullPointerException</a>. */
+  @Test void testCorrelationLexicalScoping() {
+    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"
+        + "  (select min(1) from emp where empno > d.deptno) as i0,\n"
+        + "  (select min(0) from emp where deptno = d.deptno and "
+        + "ename = 'SMITH' and d.deptno > 0) as i1\n"
+        + "from dept as d";
+    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=[$0], I0=[$3], I1=[$4])\n"
+        + "  LogicalCorrelate(correlation=[$cor0], joinType=[left], 
requiredColumns=[{0}])\n"
+        + "    LogicalCorrelate(correlation=[$cor0], joinType=[left], 
requiredColumns=[{0}])\n"
+        + "      LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "      LogicalAggregate(group=[{}], EXPR$0=[MIN($0)])\n"
+        + "        LogicalProject($f0=[1])\n"
+        + "          LogicalFilter(condition=[>($0, 
CAST($cor0.DEPTNO):SMALLINT NOT NULL)])\n"
+        + "            LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalAggregate(group=[{}], EXPR$0=[MIN($0)])\n"
+        + "      LogicalProject($f0=[0])\n"
+        + "        LogicalFilter(condition=[AND(=($7, $cor0.DEPTNO), =($1, 
'SMITH'), >(CAST($cor0.DEPTNO):INTEGER NOT NULL, 0))])\n"
+        + "          LogicalTableScan(table=[[scott, EMP]])\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()));
+    final String planAfter = ""
+        + "LogicalProject(DEPTNO=[$0], I0=[$3], I1=[$8])\n"
+        + "  LogicalJoin(condition=[AND(=($0, $6), =($5, $7))], 
joinType=[left])\n"
+        + "    LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], EXPR$0=[$5], 
DEPTNO0=[$0], $f5=[>(CAST($0):INTEGER NOT NULL, 0)])\n"
+        + "      LogicalJoin(condition=[=($3, $4)], joinType=[left])\n"
+        + "        LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], 
DEPTNO0=[CAST($0):SMALLINT NOT NULL])\n"
+        + "          LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "        LogicalAggregate(group=[{0}], EXPR$0=[MIN($1)])\n"
+        + "          LogicalProject(DEPTNO0=[$8], $f0=[1])\n"
+        + "            LogicalJoin(condition=[>($0, $8)], joinType=[inner])\n"
+        + "              LogicalTableScan(table=[[scott, EMP]])\n"
+        + "              LogicalAggregate(group=[{0}])\n"
+        + "                LogicalProject(DEPTNO0=[CAST($0):SMALLINT NOT 
NULL])\n"
+        + "                  LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "    LogicalAggregate(group=[{0, 1}], EXPR$0=[MIN($2)])\n"
+        + "      LogicalProject(DEPTNO0=[$8], $f5=[$9], $f0=[0])\n"
+        + "        LogicalJoin(condition=[=($7, $8)], joinType=[inner])\n"
+        + "          LogicalFilter(condition=[=($1, 'SMITH')])\n"
+        + "            LogicalTableScan(table=[[scott, EMP]])\n"
+        + "          LogicalFilter(condition=[$1])\n"
+        + "            LogicalProject(DEPTNO=[$0], $f5=[>(CAST($0):INTEGER NOT 
NULL, 0)])\n"
+        + "              LogicalJoin(condition=[=($3, $4)], joinType=[left])\n"
+        + "                LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], 
DEPTNO0=[CAST($0):SMALLINT NOT NULL])\n"
+        + "                  LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "                LogicalAggregate(group=[{0}], EXPR$0=[MIN($1)])\n"
+        + "                  LogicalProject(DEPTNO0=[$8], $f0=[1])\n"
+        + "                    LogicalJoin(condition=[>($0, $8)], 
joinType=[inner])\n"
+        + "                      LogicalTableScan(table=[[scott, EMP]])\n"
+        + "                      LogicalAggregate(group=[{0}])\n"
+        + "                        LogicalProject(DEPTNO0=[CAST($0):SMALLINT 
NOT NULL])\n"
+        + "                          LogicalTableScan(table=[[scott, 
DEPT]])\n";
+    assertThat(after, hasTree(planAfter));
+  }
+
   /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7320";>[CALCITE-7320]
    * AggregateProjectMergeRule throws AssertionError when Project maps 
multiple grouping keys
    * to the same field</a>. */
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index 5fb0655372..2b6e459cda 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -8967,4 +8967,79 @@ where e.deptno = 10;
 
 !ok
 
+# [CALCITE-5390] RelDecorrelator throws NullPointerException
+# Verified against PostgreSQL.
+select deptno,
+  (select min(1) from emp where empno > d.deptno) as i0,
+  (select min(0) from emp where deptno = d.deptno and ename = 'SMITH' and 
d.deptno > 0) as i1
+from dept as d;
++--------+----+----+
+| DEPTNO | I0 | I1 |
++--------+----+----+
+|     10 |  1 |    |
+|     20 |  1 |  0 |
+|     30 |  1 |    |
+|     40 |  1 |    |
++--------+----+----+
+(4 rows)
+
+!ok
+
+# [CALCITE-5390] RelDecorrelator throws NullPointerException
+# Verified against PostgreSQL.
+SELECT
+  (SELECT 1 FROM emp d WHERE d.job = a.job LIMIT 1) AS t1,
+  (SELECT a.job = 'PRESIDENT' FROM emp s LIMIT 1) as t2
+FROM emp a;
++----+-------+
+| T1 | T2    |
++----+-------+
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | false |
+|  1 | true  |
++----+-------+
+(14 rows)
+
+!ok
+
+# [CALCITE-5390] RelDecorrelator throws NullPointerException
+# Verified against PostgreSQL.
+SELECT *
+FROM emp e
+WHERE e.ename NOT IN (
+    SELECT d.dname
+    FROM dept d
+    WHERE e.deptno = d.deptno OR e.sal > 2000.0);
++-------+--------+-----------+------+------------+---------+---------+--------+
+| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
++-------+--------+-----------+------+------------+---------+---------+--------+
+|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |         |     20 |
+|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
+|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
+|  7566 | JONES  | MANAGER   | 7839 | 1981-02-04 | 2975.00 |         |     20 |
+|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
+|  7698 | BLAKE  | MANAGER   | 7839 | 1981-01-05 | 2850.00 |         |     30 |
+|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |         |     10 |
+|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |         |     20 |
+|  7839 | KING   | PRESIDENT |      | 1981-11-17 | 5000.00 |         |     10 |
+|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
+|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |         |     20 |
+|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |         |     30 |
+|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |         |     20 |
+|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |         |     10 |
++-------+--------+-----------+------+------------+---------+---------+--------+
+(14 rows)
+
+!ok
 # End sub-query.iq
diff --git a/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java 
b/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java
index 5f4e93d9d7..39aa2f3f16 100644
--- a/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java
+++ b/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.adapter.tpch;
 
+import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.hep.HepPlanner;
@@ -27,11 +28,16 @@
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.calcite.sql2rel.RelDecorrelator;
 import org.apache.calcite.test.CalciteAssert;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.tools.Planner;
+import org.apache.calcite.tools.Program;
+import org.apache.calcite.tools.Programs;
+import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.tools.RelConversionException;
+import org.apache.calcite.tools.RuleSets;
 import org.apache.calcite.tools.ValidationException;
 import org.apache.calcite.util.TestUtil;
 
@@ -41,15 +47,19 @@
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import static org.apache.calcite.test.Matchers.containsStringLinux;
+import static org.apache.calcite.test.Matchers.hasTree;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import static java.util.Objects.requireNonNull;
+
 /** Unit test for {@link org.apache.calcite.adapter.tpch.TpchSchema}.
  *
  * <p>Because the TPC-H data generator takes time and memory to instantiate,
@@ -1001,6 +1011,208 @@ private CalciteAssert.AssertThat with() {
     checkQuery(22);
   }
 
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-5390";>[CALCITE-5390]
+   * RelDecorrelator throws NullPointerException</a>. */
+  @Test public void test5390()
+      throws SqlParseException, ValidationException, RelConversionException {
+    SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    TpchSchema tpchSchema = new TpchSchema(1.0, 0, 1, false);
+    rootSchema.add("TPCH", tpchSchema);
+    FrameworkConfig config = Frameworks.newConfigBuilder()
+        .defaultSchema(rootSchema)
+        .build();
+    final RelBuilder builder = RelBuilder.create(config);
+    final RelOptCluster cluster = builder.getCluster();
+
+    Planner planner = Frameworks.getPlanner(config);
+
+    String sql = "select\n"
+        + " (select count(*) from tpch.part where p_partkey = 
tpch.partsupp.ps_partkey),\n"
+        + " (select count(*) from tpch.supplier\n"
+        + "  where s_suppkey = case when s_acctbal > 0\n"
+        + "  then tpch.partsupp.ps_partkey + 1\n"
+        + "  else 1234 end)\n"
+        + "from tpch.partsupp";
+
+    SqlNode parsed = planner.parse(sql);
+    SqlNode validated = planner.validate(parsed);
+    RelRoot root = planner.rel(validated);
+    final RelNode originalRel = root.rel;
+
+    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(EXPR$0=[$5], EXPR$1=[$6])\n"
+        + "  LogicalCorrelate(correlation=[$cor0], joinType=[left], 
requiredColumns=[{0}])\n"
+        + "    LogicalCorrelate(correlation=[$cor0], joinType=[left], 
requiredColumns=[{0}])\n"
+        + "      LogicalTableScan(table=[[TPCH, PARTSUPP]])\n"
+        + "      LogicalAggregate(group=[{}], EXPR$0=[COUNT()])\n"
+        + "        LogicalFilter(condition=[=($0, $cor0.PS_PARTKEY)])\n"
+        + "          LogicalTableScan(table=[[TPCH, PART]])\n"
+        + "    LogicalAggregate(group=[{}], EXPR$0=[COUNT()])\n"
+        + "      LogicalFilter(condition=[=(CAST($0):BIGINT, 
CASE(>(CAST($5):DOUBLE, CAST(0):DOUBLE NOT NULL), +($cor0.PS_PARTKEY, 1), 
1234:BIGINT))])\n"
+        + "        LogicalTableScan(table=[[TPCH, SUPPLIER]])\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()));
+    final String planAfter = ""
+        + "LogicalProject(EXPR$0=[$5], EXPR$1=[$8])\n"
+        + "  LogicalJoin(condition=[IS NOT DISTINCT FROM($6, $7)], 
joinType=[left])\n"
+        + "    LogicalProject(PS_PARTKEY=[$0], PS_SUPPKEY=[$1], 
PS_AVAILQTY=[$2], PS_SUPPLYCOST=[$3], PS_COMMENT=[$4], EXPR$0=[$6], $f6=[+($0, 
1)])\n"
+        + "      LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $5)], 
joinType=[left])\n"
+        + "        LogicalTableScan(table=[[TPCH, PARTSUPP]])\n"
+        + "        LogicalProject(P_PARTKEY=[$0], EXPR$0=[CASE(IS NOT 
NULL($2), $2, 0)])\n"
+        + "          LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], 
joinType=[left])\n"
+        + "            LogicalAggregate(group=[{0}])\n"
+        + "              LogicalProject(PS_PARTKEY=[$0])\n"
+        + "                LogicalTableScan(table=[[TPCH, PARTSUPP]])\n"
+        + "            LogicalAggregate(group=[{0}], EXPR$0=[COUNT()])\n"
+        + "              LogicalProject(P_PARTKEY=[$0])\n"
+        + "                LogicalFilter(condition=[IS NOT NULL($0)])\n"
+        + "                  LogicalTableScan(table=[[TPCH, PART]])\n"
+        + "    LogicalProject($f6=[$0], EXPR$0=[CASE(IS NOT NULL($2), $2, 
0)])\n"
+        + "      LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], 
joinType=[left])\n"
+        + "        LogicalAggregate(group=[{0}])\n"
+        + "          LogicalProject($f6=[+($0, 1)])\n"
+        + "            LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $5)], 
joinType=[left])\n"
+        + "              LogicalTableScan(table=[[TPCH, PARTSUPP]])\n"
+        + "              LogicalProject(P_PARTKEY=[$0], EXPR$0=[CASE(IS NOT 
NULL($2), $2, 0)])\n"
+        + "                LogicalJoin(condition=[IS NOT DISTINCT FROM($0, 
$1)], joinType=[left])\n"
+        + "                  LogicalAggregate(group=[{0}])\n"
+        + "                    LogicalProject(PS_PARTKEY=[$0])\n"
+        + "                      LogicalTableScan(table=[[TPCH, PARTSUPP]])\n"
+        + "                  LogicalAggregate(group=[{0}], EXPR$0=[COUNT()])\n"
+        + "                    LogicalProject(P_PARTKEY=[$0])\n"
+        + "                      LogicalFilter(condition=[IS NOT NULL($0)])\n"
+        + "                        LogicalTableScan(table=[[TPCH, PART]])\n"
+        + "        LogicalAggregate(group=[{0}], EXPR$0=[COUNT()])\n"
+        + "          LogicalProject($f6=[$7])\n"
+        + "            LogicalJoin(condition=[=(CAST($0):BIGINT, 
CASE(>(CAST($5):DOUBLE, 0.0E0), $7, 1234:BIGINT))], joinType=[inner])\n"
+        + "              LogicalTableScan(table=[[TPCH, SUPPLIER]])\n"
+        + "              LogicalAggregate(group=[{0}])\n"
+        + "                LogicalProject($f6=[+($0, 1)])\n"
+        + "                  LogicalJoin(condition=[IS NOT DISTINCT FROM($0, 
$5)], joinType=[left])\n"
+        + "                    LogicalTableScan(table=[[TPCH, PARTSUPP]])\n"
+        + "                    LogicalProject(P_PARTKEY=[$0], EXPR$0=[CASE(IS 
NOT NULL($2), $2, 0)])\n"
+        + "                      LogicalJoin(condition=[IS NOT DISTINCT 
FROM($0, $1)], joinType=[left])\n"
+        + "                        LogicalAggregate(group=[{0}])\n"
+        + "                          LogicalProject(PS_PARTKEY=[$0])\n"
+        + "                            LogicalTableScan(table=[[TPCH, 
PARTSUPP]])\n"
+        + "                        LogicalAggregate(group=[{0}], 
EXPR$0=[COUNT()])\n"
+        + "                          LogicalProject(P_PARTKEY=[$0])\n"
+        + "                            LogicalFilter(condition=[IS NOT 
NULL($0)])\n"
+        + "                              LogicalTableScan(table=[[TPCH, 
PART]])\n";
+    assertThat(after, hasTree(planAfter));
+  }
+
+  @Test public void test53902()
+      throws SqlParseException, ValidationException, RelConversionException {
+    SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    TpchSchema tpchSchema = new TpchSchema(1.0, 0, 1, false);
+    rootSchema.add("TPCH", tpchSchema);
+    FrameworkConfig config = Frameworks.newConfigBuilder()
+        .defaultSchema(rootSchema)
+        .build();
+    final RelBuilder builder = RelBuilder.create(config);
+    final RelOptCluster cluster = builder.getCluster();
+
+    Planner planner = Frameworks.getPlanner(config);
+
+    String sql = ""
+        + "SELECT *\n"
+        + "FROM tpch.customer\n"
+        + "WHERE c_mktsegment = 'AUTOMOBILE'\n"
+        + "    AND (SELECT COUNT(*)\n"
+        + "        FROM tpch.orders\n"
+        + "        WHERE o_custkey = c_custkey\n"
+        + "            AND (SELECT SUM(l_extendedprice)\n"
+        + "                FROM tpch.lineitem\n"
+        + "                WHERE l_orderkey = o_orderkey\n"
+        + "            ) > 300000\n"
+        + "    ) > 5";
+
+    SqlNode parsed = planner.parse(sql);
+    SqlNode validated = planner.validate(parsed);
+    RelRoot root = planner.rel(validated);
+    final RelNode originalRel = root.rel;
+
+    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(C_CUSTKEY=[$0], C_NAME=[$1], C_ADDRESS=[$2], 
C_NATIONKEY=[$3], C_PHONE=[$4], C_ACCTBAL=[$5], C_MKTSEGMENT=[$6], 
C_COMMENT=[$7])\n"
+        + "  LogicalProject(C_CUSTKEY=[$0], C_NAME=[$1], C_ADDRESS=[$2], 
C_NATIONKEY=[$3], C_PHONE=[$4], C_ACCTBAL=[$5], C_MKTSEGMENT=[$6], 
C_COMMENT=[$7])\n"
+        + "    LogicalFilter(condition=[AND(=(CAST($6):VARCHAR, 'AUTOMOBILE'), 
>($8, 5))])\n"
+        + "      LogicalCorrelate(correlation=[$cor0], joinType=[left], 
requiredColumns=[{0}])\n"
+        + "        LogicalTableScan(table=[[TPCH, CUSTOMER]])\n"
+        + "        LogicalAggregate(group=[{}], EXPR$0=[COUNT()])\n"
+        + "          LogicalProject(O_ORDERKEY=[$0], O_CUSTKEY=[$1], 
O_ORDERSTATUS=[$2], O_TOTALPRICE=[$3], O_ORDERDATE=[$4], O_ORDERPRIORITY=[$5], 
O_CLERK=[$6], O_SHIPPRIORITY=[$7], O_COMMENT=[$8])\n"
+        + "            LogicalFilter(condition=[AND(=($1, $cor0.C_CUSTKEY), 
>(CAST($9):DOUBLE, 300000.0E0))])\n"
+        + "              LogicalCorrelate(correlation=[$cor1], 
joinType=[left], requiredColumns=[{0}])\n"
+        + "                LogicalTableScan(table=[[TPCH, ORDERS]])\n"
+        + "                LogicalAggregate(group=[{}], EXPR$0=[SUM($0)])\n"
+        + "                  LogicalProject(L_EXTENDEDPRICE=[$5])\n"
+        + "                    LogicalFilter(condition=[=($0, 
$cor1.O_ORDERKEY)])\n"
+        + "                      LogicalTableScan(table=[[TPCH, LINEITEM]])\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()));
+    final String planAfter = ""
+        + "LogicalProject(C_CUSTKEY=[$0], C_NAME=[$1], C_ADDRESS=[$2], 
C_NATIONKEY=[$3], C_PHONE=[$4], C_ACCTBAL=[$5], C_MKTSEGMENT=[$6], 
C_COMMENT=[$7])\n"
+        + "  LogicalProject(C_CUSTKEY=[$0], C_NAME=[$1], C_ADDRESS=[$2], 
C_NATIONKEY=[$3], C_PHONE=[$4], C_ACCTBAL=[$5], C_MKTSEGMENT=[$6], 
C_COMMENT=[$7], O_CUSTKEY9=[$8], EXPR$0=[CAST($9):BIGINT])\n"
+        + "    LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $8)], 
joinType=[inner])\n"
+        + "      LogicalFilter(condition=[=(CAST($6):VARCHAR, 
'AUTOMOBILE')])\n"
+        + "        LogicalTableScan(table=[[TPCH, CUSTOMER]])\n"
+        + "      LogicalFilter(condition=[>($1, 5)])\n"
+        + "        LogicalProject(O_CUSTKEY9=[$0], EXPR$0=[CASE(IS NOT 
NULL($2), $2, 0)])\n"
+        + "          LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], 
joinType=[left])\n"
+        + "            LogicalAggregate(group=[{0}])\n"
+        + "              LogicalProject(C_CUSTKEY=[$0])\n"
+        + "                LogicalTableScan(table=[[TPCH, CUSTOMER]])\n"
+        + "            LogicalAggregate(group=[{0}], EXPR$0=[COUNT()])\n"
+        + "              LogicalProject(O_CUSTKEY9=[$1])\n"
+        + "                LogicalJoin(condition=[=($0, $9)], 
joinType=[inner])\n"
+        + "                  LogicalFilter(condition=[IS NOT NULL($1)])\n"
+        + "                    LogicalTableScan(table=[[TPCH, ORDERS]])\n"
+        + "                  LogicalFilter(condition=[>(CAST($1):DOUBLE, 
300000.0E0)])\n"
+        + "                    LogicalAggregate(group=[{0}], 
EXPR$0=[SUM($1)])\n"
+        + "                      LogicalProject(L_ORDERKEY=[$0], 
L_EXTENDEDPRICE=[$5])\n"
+        + "                        LogicalFilter(condition=[IS NOT 
NULL($0)])\n"
+        + "                          LogicalTableScan(table=[[TPCH, 
LINEITEM]])\n";
+    assertThat(after, hasTree(planAfter));
+  }
+
   private void checkQuery(int i) {
     query(i).runs();
   }

Reply via email to