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 c46978659b [CALCITE-7442] Correlated variable has wrong index inside 
subquery
c46978659b is described below

commit c46978659bb997241c4aef0e193b34746770d731
Author: Yash Limbad <[email protected]>
AuthorDate: Wed Mar 18 16:26:45 2026 +0530

    [CALCITE-7442] Correlated variable has wrong index inside subquery
---
 .../java/org/apache/calcite/plan/RelOptUtil.java   |  87 +++++-
 .../org/apache/calcite/rel/rules/CoreRules.java    |   2 +-
 .../main/java/org/apache/calcite/rex/RexUtil.java  |  49 +++-
 .../calcite/sql2rel/RelDecorrelatorTest.java       | 295 +++++++++++++++++++++
 4 files changed, 423 insertions(+), 10 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index 274e91f14d..9b24cf2ad2 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -2934,15 +2934,39 @@ public static boolean classifyFilters(
     ImmutableBitSet rightBitmap =
         ImmutableBitSet.range(nSysFields + nFieldsLeft, nTotalFields);
 
+    // Correlation variables introduced by this join itself: i.e. ids whose
+    // binding is established by *this* join. A predicate that references any
+    // of these cannot be pushed to either input -- the binder lives on the
+    // join, so pushing the reference below it would strand the variable.
+    // Such predicates must stay on the join itself.
+    final Set<CorrelationId> joinCorrelationIds = joinRel instanceof Join
+        ? joinRel.getVariablesSet()
+        : ImmutableSet.of();
+
     final List<RexNode> filtersToRemove = new ArrayList<>();
     for (RexNode filter : filters) {
-      final InputFinder inputFinder = InputFinder.analyze(filter);
+
+      // Only consider correlation ids bound by *this* join when computing
+      // the input bitmap of a sub-query inside the predicate. Foreign
+      // correlation ids are bound by an outer scope and their
+      // correlationColumns indices would otherwise alias onto unrelated
+      // columns of this join's row type, mis-classifying the predicate.
+      final InputFinder inputFinder = InputFinder.analyze(filter, 
joinCorrelationIds);
       final ImmutableBitSet inputBits = inputFinder.build();
 
+      // Block pushing to either input for filters that reference a
+      // CorrelationId bound by this join; they must remain on the join.
+      // pushing down correlated subqueries carries risks and involves 
extremely complex logic,
+      // and therefore pushing down is prohibited.
+      final boolean blockPush =
+          RexUtil.containsCorrelation(filter, joinCorrelationIds);
+      final boolean effectivePushLeft = pushLeft && !blockPush && 
leftBitmap.contains(inputBits);
+      final boolean effectivePushRight = pushRight && !blockPush && 
rightBitmap.contains(inputBits);
+
       // REVIEW - are there any expressions that need special handling
       // and therefore cannot be pushed?
 
-      if (pushLeft && leftBitmap.contains(inputBits)) {
+      if (effectivePushLeft) {
         // ignore filters that always evaluate to true
         if (!filter.isAlwaysTrue()) {
           // adjust the field references in the filter to reflect
@@ -2962,7 +2986,7 @@ public static boolean classifyFilters(
           leftFilters.add(shiftedFilter);
         }
         filtersToRemove.add(filter);
-      } else if (pushRight && rightBitmap.contains(inputBits)) {
+      } else if (effectivePushRight) {
         if (!filter.isAlwaysTrue()) {
           // adjust the field references in the filter to reflect
           // that fields in the right now shift over to the left
@@ -4678,12 +4702,29 @@ public RexCorrelVariableMapShuttle(final CorrelationId 
correlationId,
   public static class InputFinder extends RexVisitorImpl<Void> {
     private final ImmutableBitSet.Builder bitBuilder;
     private final @Nullable Set<RelDataTypeField> extraFields;
+    /** Correlation ids whose binder is the current scope. When non-null,
+     * {@link #visitSubQuery} projects bits for each id in this set by looking
+     * up its {@code correlationColumns} against the sub-query's inner plan
+     * and adding those column indices to the bitmap. Correlation ids bound
+     * by an outer scope are skipped, since their column indices are relative
+     * to a foreign row type and would otherwise alias onto unrelated columns
+     * of the current scope. When null, {@link #visitSubQuery} contributes no
+     * correlation-related bits and simply descends into the sub-query's
+     * operands (legacy behaviour). */
+    private final @Nullable Set<CorrelationId> localCorrelationIds;
 
     private InputFinder(@Nullable Set<RelDataTypeField> extraFields,
-        ImmutableBitSet.Builder bitBuilder) {
+        ImmutableBitSet.Builder bitBuilder,
+        @Nullable Set<CorrelationId> localCorrelationIds) {
       super(true);
       this.bitBuilder = bitBuilder;
       this.extraFields = extraFields;
+      this.localCorrelationIds = localCorrelationIds;
+    }
+
+    private InputFinder(@Nullable Set<RelDataTypeField> extraFields,
+        ImmutableBitSet.Builder bitBuilder) {
+      this(extraFields, bitBuilder, null);
     }
 
     public InputFinder() {
@@ -4706,6 +4747,22 @@ public static InputFinder analyze(RexNode node) {
       return inputFinder;
     }
 
+    /** Returns an input finder that has analyzed a given expression,
+     * treating {@code localCorrelationIds} as the set of correlation ids
+     * bound by the current scope. For each nested {@link RexSubQuery},
+     * any correlation id used inside it that belongs to this set
+     * contributes its {@code correlationColumns} indices to the bitmap;
+     * correlation ids bound by an outer scope are ignored, because their
+     * indices are relative to a foreign row type and would otherwise
+     * alias onto unrelated columns of the current scope. */
+    public static InputFinder analyze(RexNode node,
+        Set<CorrelationId> localCorrelationIds) {
+      final InputFinder inputFinder =
+          new InputFinder(null, ImmutableBitSet.builder(), 
localCorrelationIds);
+      node.accept(inputFinder);
+      return inputFinder;
+    }
+
     /**
      * Returns a bit set describing the inputs used by an expression.
      */
@@ -4752,6 +4809,28 @@ public ImmutableBitSet build() {
       }
       return super.visitCall(call);
     }
+
+    @Override public Void visitSubQuery(RexSubQuery subQuery) {
+      if (localCorrelationIds == null) {
+        return super.visitSubQuery(subQuery);
+      }
+
+      final Set<CorrelationId> variablesSet = 
RelOptUtil.getVariablesUsed(subQuery.rel);
+      for (CorrelationId id : variablesSet) {
+        // Skip correlation ids that are not bound by the *current* scope.
+        // Their requiredColumns indices are relative to whichever outer
+        // RelNode produces them and would otherwise alias onto unrelated
+        // columns of the current row type.
+        if (!localCorrelationIds.contains(id)) {
+          continue;
+        }
+        ImmutableBitSet requiredColumns = RelOptUtil.correlationColumns(id, 
subQuery.rel);
+        for (int index : requiredColumns) {
+          bitBuilder.set(index);
+        }
+      }
+      return super.visitSubQuery(subQuery);
+    }
   }
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java 
b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
index 444c89ccdd..0337b1d5ab 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
@@ -284,7 +284,7 @@ private CoreRules() {}
    * {@link org.apache.calcite.rel.rules.FilterProjectTransposeRule}.
    *
    * <p>It does not allow a Filter to be pushed past the Project if
-   * {@link RexUtil#containsCorrelation there is a correlation condition}
+   * {@link RexUtil#containsCorrelation(org.apache.calcite.rex.RexNode) there 
is a correlation condition}
    * anywhere in the Filter, since in some cases it can prevent a
    * {@link Correlate} from being de-correlated.
    */
diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java 
b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
index 8c45ffc97a..3604e98dfd 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -2412,6 +2412,23 @@ public static boolean containsCorrelation(RexNode 
condition) {
     }
   }
 
+  /** Returns whether an expression references a {@link RexCorrelVariable}
+   * whose id is in {@code ids}, either directly (typically through a
+   * {@link RexFieldAccess}) or transitively via the inner plan of a
+   * {@link RexSubQuery}. */
+  public static boolean containsCorrelation(RexNode condition,
+      Set<CorrelationId> ids) {
+    if (ids.isEmpty()) {
+      return false;
+    }
+    try {
+      condition.accept(new CorrelationFinder(ids));
+      return false;
+    } catch (Util.FoundOne e) {
+      return true;
+    }
+  }
+
   /**
    * Given an expression, it will swap the table references contained in its
    * {@link RexTableInputRef} using the contents in the map.
@@ -3116,14 +3133,28 @@ private static class RexShiftShuttle extends RexShuttle 
{
   /** Visitor that throws {@link org.apache.calcite.util.Util.FoundOne} if
    * applied to an expression that contains a {@link RexCorrelVariable}. */
   private static class CorrelationFinder extends RexVisitorImpl<Void> {
-    static final CorrelationFinder INSTANCE = new CorrelationFinder();
+    static final CorrelationFinder INSTANCE = new CorrelationFinder(null);
 
-    private CorrelationFinder() {
+    /** Optional filter: when non-null, only correlation ids in this set
+     * trigger a match; when null, every correlation id matches. */
+    private final @Nullable Set<CorrelationId> ids;
+
+    /**
+     * Creates a CorrelationFinder.
+     *
+     * @param ids correlation ids to look for; pass {@code null} to match any
+     *            {@link RexCorrelVariable} regardless of its id
+     */
+    private CorrelationFinder(@Nullable Set<CorrelationId> ids) {
       super(true);
+      this.ids = ids;
     }
 
     @Override public Void visitCorrelVariable(RexCorrelVariable var) {
-      throw Util.FoundOne.NULL;
+      if (ids == null || ids.contains(var.id)) {
+        throw Util.FoundOne.NULL;
+      }
+      return null;
     }
 
     @Override public Void visitSubQuery(RexSubQuery subQuery) {
@@ -3135,8 +3166,16 @@ private CorrelationFinder() {
         operand.accept(this);
       }
 
-      if (!RelOptUtil.getVariablesUsed(subQuery.rel).isEmpty()) {
-        throw Util.FoundOne.NULL;
+      Set<CorrelationId> used = RelOptUtil.getVariablesUsed(subQuery.rel);
+      if (!used.isEmpty()) {
+        if (ids == null) {
+          throw Util.FoundOne.NULL;
+        }
+        for (CorrelationId id : used) {
+          if (ids.contains(id)) {
+            throw Util.FoundOne.NULL;
+          }
+        }
       }
       return null;
     }
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 2b406c3c01..e91b138861 100644
--- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
@@ -1830,4 +1830,299 @@ public static Frameworks.ConfigBuilder config() {
         + "                LogicalTableScan(table=[[scott, EMP]])\n";
     assertThat(after, hasTree(planAfter));
   }
+
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7442";>[CALCITE-7442]
+  * Getting Wrong index of Correlated variable inside Subquery after 
FilterJoinRule</a>. */
+  @Test void testCorrelatedVariableIndexForInClause() {
+    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 e.empno, d.dname, b.ename\n"
+        + "from emp e\n"
+        + "inner join dept d\n"
+        + "  on d.deptno = e.deptno\n"
+        + "inner join bonus b\n"
+        + "  on e.ename = b.ename\n"
+        + "  and b.job in (\n"
+        + "    select b2.job\n"
+        + "    from bonus b2\n"
+        + "    where b2.ename = b.ename)\n"
+        + "where e.sal > 1000 and d.dname = 'SALES'";
+
+    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(CoreRules.FILTER_INTO_JOIN))
+        .build();
+    final Program program =
+        Programs.of(hepProgram, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode afterFilterIntoJoin =
+        program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+
+    final String planAfterFilterIntoJoin = "LogicalProject(EMPNO=[$0], 
DNAME=[$9], ENAME=[$11])\n"
+        + "  LogicalJoin(condition=[AND(=($1, $11), IN($12, {\n"
+        + "LogicalProject(JOB=[$1])\n"
+        + "  LogicalFilter(condition=[=($0, $cor0.ENAME0)])\n"
+        + "    LogicalTableScan(table=[[scott, BONUS]])\n"
+        + "}))], joinType=[inner], variablesSet=[[$cor0]])\n"
+        + "    LogicalJoin(condition=[=($8, $7)], joinType=[inner])\n"
+        + "      LogicalFilter(condition=[>(CAST($5):DECIMAL(12, 2), 
1000.00)])\n"
+        + "        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "      LogicalFilter(condition=[=($1, 'SALES')])\n"
+        + "        LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "    LogicalTableScan(table=[[scott, BONUS]])\n";
+    assertThat(afterFilterIntoJoin, hasTree(planAfterFilterIntoJoin));
+
+    final HepProgram hepProgram1 = HepProgram.builder()
+        
.addRuleCollection(ImmutableList.of(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
+        .build();
+    final Program program1 =
+        Programs.of(hepProgram1, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode afterJoinSubqueryCorrelate =
+        program1.run(cluster.getPlanner(), afterFilterIntoJoin, 
cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+
+    final String planAfterJoinSubqueryCorrelate =
+        "LogicalProject(EMPNO=[$0], DNAME=[$9], ENAME=[$11])\n"
+            + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4],"
+            + " SAL=[$5], COMM=[$6], DEPTNO=[$7], DEPTNO0=[$8], DNAME=[$9], 
LOC=[$10],"
+            + " ENAME0=[$11], JOB0=[$12], SAL0=[$13], COMM0=[$14])\n"
+            + "    LogicalJoin(condition=[=($1, $11)], joinType=[inner])\n"
+            + "      LogicalJoin(condition=[=($8, $7)], joinType=[inner])\n"
+            + "        LogicalFilter(condition=[>(CAST($5):DECIMAL(12, 2), 
1000.00)])\n"
+            + "          LogicalTableScan(table=[[scott, EMP]])\n"
+            + "        LogicalFilter(condition=[=($1, 'SALES')])\n"
+            + "          LogicalTableScan(table=[[scott, DEPT]])\n"
+            + "      LogicalFilter(condition=[=($1, $4)])\n"
+            + "        LogicalCorrelate(correlation=[$cor0], joinType=[inner],"
+            + " requiredColumns=[{0}])\n"
+            + "          LogicalTableScan(table=[[scott, BONUS]])\n"
+            + "          LogicalProject(JOB=[$1])\n"
+            + "            LogicalFilter(condition=[=($0, $cor0.ENAME)])\n"
+            + "              LogicalTableScan(table=[[scott, BONUS]])\n";
+    assertThat(afterJoinSubqueryCorrelate, 
hasTree(planAfterJoinSubqueryCorrelate));
+
+    final RelNode afterDecorrelation =
+        RelDecorrelator.decorrelateQuery(afterJoinSubqueryCorrelate, builder,
+            RuleSets.ofList(Collections.emptyList()), 
RuleSets.ofList(Collections.emptyList()));
+    final String planAfterDecorrelation = "LogicalProject(EMPNO=[$0], 
DNAME=[$9], ENAME=[$11])\n"
+        + "  LogicalJoin(condition=[=($1, $11)], joinType=[inner])\n"
+        + "    LogicalJoin(condition=[=($8, $7)], joinType=[inner])\n"
+        + "      LogicalFilter(condition=[>(CAST($5):DECIMAL(12, 2), 
1000.00)])\n"
+        + "        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "      LogicalFilter(condition=[=($1, 'SALES')])\n"
+        + "        LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "    LogicalJoin(condition=[AND(=($0, $5), =($1, $4))], 
joinType=[inner])\n"
+        + "      LogicalTableScan(table=[[scott, BONUS]])\n"
+        + "      LogicalProject(JOB=[$1], ENAME=[$0])\n"
+        + "        LogicalFilter(condition=[IS NOT NULL($0)])\n"
+        + "          LogicalTableScan(table=[[scott, BONUS]])\n";
+    assertThat(afterDecorrelation, hasTree(planAfterDecorrelation));
+  }
+
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7442";>[CALCITE-7442]
+  * Getting Wrong index of Correlated variable inside Subquery after 
FilterJoinRule</a>.
+  * Same as {@link #testCorrelatedVariableIndexForInClause()} but uses EXISTS
+  * instead of IN. */
+  @Test void testCorrelatedVariableIndexForExistsClause() {
+    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 e.empno, d.dname, b.ename\n"
+        + "from emp e\n"
+        + "inner join dept d\n"
+        + "  on d.deptno = e.deptno\n"
+        + "inner join bonus b\n"
+        + "  on e.ename = b.ename\n"
+        + "  and exists (\n"
+        + "    select b2.job\n"
+        + "    from bonus b2\n"
+        + "    where b2.ename = b.ename\n"
+        + "    and b2.job = b.job)\n"
+        + "where e.sal > 1000 and d.dname = 'SALES'";
+
+    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(CoreRules.FILTER_INTO_JOIN))
+        .build();
+    final Program program =
+        Programs.of(hepProgram, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode afterFilterIntoJoin =
+        program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+
+    final String planAfterFilterIntoJoin = "LogicalProject(EMPNO=[$0], 
DNAME=[$9], ENAME=[$11])\n"
+        + "  LogicalJoin(condition=[AND(=($1, $11), EXISTS({\n"
+        + "LogicalFilter(condition=[AND(=($0, $cor0.ENAME0), =($1, 
$cor0.JOB0))])\n"
+        + "  LogicalTableScan(table=[[scott, BONUS]])\n"
+        + "}))], joinType=[inner], variablesSet=[[$cor0]])\n"
+        + "    LogicalJoin(condition=[=($8, $7)], joinType=[inner])\n"
+        + "      LogicalFilter(condition=[>(CAST($5):DECIMAL(12, 2), 
1000.00)])\n"
+        + "        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "      LogicalFilter(condition=[=($1, 'SALES')])\n"
+        + "        LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "    LogicalTableScan(table=[[scott, BONUS]])\n";
+    assertThat(afterFilterIntoJoin, hasTree(planAfterFilterIntoJoin));
+
+    final HepProgram hepProgram1 = HepProgram.builder()
+        
.addRuleCollection(ImmutableList.of(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
+        .build();
+    final Program program1 =
+        Programs.of(hepProgram1, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode afterJoinSubqueryCorrelate =
+        program1.run(cluster.getPlanner(), afterFilterIntoJoin, 
cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+
+    final String planAfterJoinSubqueryCorrelate =
+        "LogicalProject(EMPNO=[$0], DNAME=[$9], ENAME=[$11])\n"
+            + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4],"
+            + " SAL=[$5], COMM=[$6], DEPTNO=[$7], DEPTNO0=[$8], DNAME=[$9], 
LOC=[$10],"
+            + " ENAME0=[$11], JOB0=[$12], SAL0=[$13], COMM0=[$14])\n"
+            + "    LogicalJoin(condition=[=($1, $11)], joinType=[inner])\n"
+            + "      LogicalJoin(condition=[=($8, $7)], joinType=[inner])\n"
+            + "        LogicalFilter(condition=[>(CAST($5):DECIMAL(12, 2), 
1000.00)])\n"
+            + "          LogicalTableScan(table=[[scott, EMP]])\n"
+            + "        LogicalFilter(condition=[=($1, 'SALES')])\n"
+            + "          LogicalTableScan(table=[[scott, DEPT]])\n"
+            + "      LogicalCorrelate(correlation=[$cor0], joinType=[inner], "
+            + "requiredColumns=[{0, 1}])\n"
+            + "        LogicalTableScan(table=[[scott, BONUS]])\n"
+            + "        LogicalAggregate(group=[{0}])\n"
+            + "          LogicalProject(i=[true])\n"
+            + "            LogicalFilter(condition=[AND(=($0, $cor0.ENAME), 
=($1, $cor0.JOB))])\n"
+            + "              LogicalTableScan(table=[[scott, BONUS]])\n";
+    assertThat(afterJoinSubqueryCorrelate, 
hasTree(planAfterJoinSubqueryCorrelate));
+
+    final RelNode afterDecorrelation =
+        RelDecorrelator.decorrelateQuery(afterJoinSubqueryCorrelate, builder,
+            RuleSets.ofList(Collections.emptyList()), 
RuleSets.ofList(Collections.emptyList()));
+    final String planAfterDecorrelation = "LogicalProject(EMPNO=[$0], 
DNAME=[$9], ENAME=[$11])\n"
+        + "  LogicalJoin(condition=[=($1, $11)], joinType=[inner])\n"
+        + "    LogicalJoin(condition=[=($8, $7)], joinType=[inner])\n"
+        + "      LogicalFilter(condition=[>(CAST($5):DECIMAL(12, 2), 
1000.00)])\n"
+        + "        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "      LogicalFilter(condition=[=($1, 'SALES')])\n"
+        + "        LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "    LogicalJoin(condition=[AND(=($0, $4), =($1, $5))], 
joinType=[inner])\n"
+        + "      LogicalTableScan(table=[[scott, BONUS]])\n"
+        + "      LogicalProject(ENAME=[$0], JOB=[$1], $f2=[true])\n"
+        + "        LogicalFilter(condition=[AND(IS NOT NULL($0), IS NOT 
NULL($1))])\n"
+        + "          LogicalTableScan(table=[[scott, BONUS]])\n";
+    assertThat(afterDecorrelation, hasTree(planAfterDecorrelation));
+  }
+
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7442";>[CALCITE-7442]
+   * Getting Wrong index of Correlated variable inside Subquery after 
FilterJoinRule</a>.
+   * Same as {@link #testCorrelatedVariableIndexForInClause()} EXISTS edge 
case */
+  @Test void testCorrelatedVariableIndexForExistsClause2() {
+    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 e1.empno \n"
+        + "from emp e1, \n"
+        + "lateral(\n"
+        + "  select d.deptno \n"
+        + "  from emp e2 inner join dept d \n"
+        + "  on exists(\n"
+        + "    select e3.empno from emp e3 where e3.empno = e2.empno and 
e3.ename = e1.ename\n"
+        + "  )\n"
+        + ")";
+
+    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(CoreRules.JOIN_CONDITION_PUSH))
+        .build();
+    final Program program =
+        Programs.of(hepProgram, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode afterJoinConditionPush =
+        program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+
+    final String planAfterJoinConditionPush = "LogicalProject(EMPNO=[$0])\n"
+        + "  LogicalCorrelate(correlation=[$cor1], joinType=[inner], 
requiredColumns=[{1}])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalProject(DEPTNO=[$8])\n"
+        + "      LogicalJoin(condition=[EXISTS({\n"
+        + "LogicalFilter(condition=[AND(=($0, $cor0.EMPNO), =($1, 
$cor1.ENAME))])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n"
+        + "})], joinType=[inner], variablesSet=[[$cor0]])\n"
+        + "        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "        LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(afterJoinConditionPush, hasTree(planAfterJoinConditionPush));
+
+    final HepProgram hepProgram1 = HepProgram.builder()
+        
.addRuleCollection(ImmutableList.of(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
+        .build();
+    final Program program1 =
+        Programs.of(hepProgram1, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode afterJoinSubqueryCorrelate =
+        program1.run(cluster.getPlanner(), afterJoinConditionPush, 
cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+
+    final String planAfterJoinSubqueryCorrelate = 
"LogicalProject(EMPNO=[$0])\n"
+        + "  LogicalCorrelate(correlation=[$cor1], joinType=[inner], 
requiredColumns=[{1}])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalProject(DEPTNO=[$8])\n"
+        + "      LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4],"
+        + " SAL=[$5], COMM=[$6], DEPTNO=[$7], DEPTNO0=[$9], DNAME=[$10], 
LOC=[$11])\n"
+        + "        LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "          LogicalJoin(condition=[true], joinType=[inner],"
+        + " variablesSet=[[$cor0, $cor1]])\n"
+        + "            LogicalTableScan(table=[[scott, EMP]])\n"
+        + "            LogicalAggregate(group=[{0}])\n"
+        + "              LogicalProject(i=[true])\n"
+        + "                LogicalFilter(condition=[AND(=($0, $cor0.EMPNO), 
=($1, $cor1.ENAME))])\n"
+        + "                  LogicalTableScan(table=[[scott, EMP]])\n"
+        + "          LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(afterJoinSubqueryCorrelate, 
hasTree(planAfterJoinSubqueryCorrelate));
+
+    final RelNode afterDecorrelation =
+        RelDecorrelator.decorrelateQuery(afterJoinSubqueryCorrelate, builder,
+            RuleSets.ofList(Collections.emptyList()), 
RuleSets.ofList(Collections.emptyList()));
+    final String planAfterDecorrelation = "LogicalProject(EMPNO=[$0])\n"
+        + "  LogicalJoin(condition=[=($1, $10)], joinType=[inner])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalProject(DEPTNO=[$11], EMPNO0=[$8], ENAME0=[$9])\n"
+        + "      LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "        LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "          LogicalTableScan(table=[[scott, EMP]])\n"
+        + "          LogicalProject(EMPNO=[$0], ENAME=[$1], $f2=[true])\n"
+        + "            LogicalFilter(condition=[IS NOT NULL($1)])\n"
+        + "              LogicalTableScan(table=[[scott, EMP]])\n"
+        + "        LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(afterDecorrelation, hasTree(planAfterDecorrelation));
+  }
 }

Reply via email to