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

mbudiu 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 f451b2af9c [CALCITE-7379] LHS correlated variables are shadowed by 
nullable RHS outputs in LEFT JOIN
f451b2af9c is described below

commit f451b2af9ce5062abbd565d227f484b13003cd4f
Author: iwanttobepowerful <[email protected]>
AuthorDate: Fri Jan 16 11:46:45 2026 +0800

    [CALCITE-7379] LHS correlated variables are shadowed by nullable RHS 
outputs in LEFT JOIN
---
 .../apache/calcite/sql2rel/RelDecorrelator.java    | 286 ++++++++--
 .../calcite/sql2rel/RelDecorrelatorTest.java       | 166 ++++++
 core/src/test/resources/sql/sub-query.iq           | 588 +++++++++++++++++++++
 3 files changed, 990 insertions(+), 50 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 80f1f95335..9813ef27d1 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -959,18 +959,19 @@ private RelNode rewriteScalarAggregate(Aggregate oldRel,
 
     // Build join conditions
     final Map<Integer, RexNode> newProjectMap = new HashMap<>();
-    final List<RexNode> conditions = new ArrayList<>();
     for (Map.Entry<CorDef, Integer> corDefOutput : corDefOutputs.entrySet()) {
       final CorDef corDef = corDefOutput.getKey();
       final int leftPos = requireNonNull(valueGenCorDefOutputs.get(corDef));
       final int rightPos = corDefOutput.getValue();
       final RelDataType leftType = 
valueGen.getRowType().getFieldList().get(leftPos).getType();
-      final RelDataType rightType = 
newRel.getRowType().getFieldList().get(rightPos).getType();
       final RexNode leftRef = new RexInputRef(leftPos, leftType);
-      final RexNode rightRef = new RexInputRef(valueGenFieldCount + rightPos, 
rightType);
-      conditions.add(relBuilder.isNotDistinctFrom(leftRef, rightRef));
       newProjectMap.put(valueGenFieldCount + rightPos, leftRef);
     }
+
+    final List<RexNode> conditions =
+        buildCorDefJoinConditions(valueGenCorDefOutputs, corDefOutputs,
+            valueGen, newRel, relBuilder);
+
     final RexNode joinCond = 
RexUtil.composeConjunction(relBuilder.getRexBuilder(), conditions);
 
     // Build [08] LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], 
joinType=[left])
@@ -1277,21 +1278,13 @@ private static void shiftMapping(Map<Integer, Integer> 
mapping, int startIndex,
 
       // Build join conditions: for each CorDef of this branch that belongs
       // to the current outFrameCorrId, equate valueGen(col) with branch(col).
-      final List<RexNode> conditions = new ArrayList<>();
-      for (Map.Entry<CorDef, Integer> e : frame.corDefOutputs.entrySet()) {
-        final CorDef corDef = e.getKey();
-        final int leftPos = requireNonNull(valueGenCorDefOutputs.get(corDef));
-        final int rightPos = e.getValue();
-        final RelDataType leftType = 
valueGen.getRowType().getFieldList().get(leftPos).getType();
-        final RelDataType rightType = 
frame.r.getRowType().getFieldList().get(rightPos).getType();
-        final RexNode leftRef = new RexInputRef(leftPos, leftType);
-        final RexNode rightRef = new RexInputRef(valueGenFieldCount + 
rightPos, rightType);
-        conditions.add(relBuilder.isNotDistinctFrom(leftRef, rightRef));
-      }
+      final List<RexNode> conditions =
+          buildCorDefJoinConditions(valueGenCorDefOutputs, frame.corDefOutputs,
+              valueGen, frame.r, relBuilder);
       final RexNode joinCondition =
           RexUtil.composeConjunction(relBuilder.getRexBuilder(), conditions);
       RelNode join = relBuilder.push(valueGen).push(frame.r)
-              .join(JoinRelType.INNER, joinCondition).build();
+          .join(JoinRelType.INNER, joinCondition).build();
 
       final List<RelDataTypeField> joinFields = 
join.getRowType().getFieldList();
 
@@ -1935,10 +1928,16 @@ private static boolean isWidening(RelDataType type, 
RelDataType type1) {
       return decorrelateRel((RelNode) rel, isCorVarDefined, 
parentPropagatesNullValues);
     }
     //
-    // Rewrite logic:
+    // For other join types (INNER, LEFT, RIGHT, FULL):
     //
-    // 1. rewrite join condition.
-    // 2. map output positions and produce corVars if any.
+    // 1. Decorrelates the left and right inputs recursively.
+    // 2. Ensures that required correlated variables are present in the 
inputs, adding
+    //    value generators if necessary (e.g., for the nullable side of an 
outer join).
+    // 3. Constructs a new join condition that includes the original condition 
and
+    //    equality conditions for the correlated variables.
+    // 4. For {@link JoinRelType#FULL}, adds a projection on top of the join 
to coalesce
+    //    correlated variables that might be null on one side due to the join 
nature.
+    // 5. Updates the output mapping to reflect the new join structure.
     //
 
     final RelNode oldLeft = rel.getInput(0);
@@ -1952,53 +1951,178 @@ private static boolean isWidening(RelDataType type, 
RelDataType type1) {
       return null;
     }
 
+    // 1. Collect all CorRefs involved
+    final CorelMap localCorelMap = new CorelMapBuilder().build(rel);
+    final List<CorRef> corVarList = new 
ArrayList<>(localCorelMap.mapRefRelToCorRef.values());
+    Collections.sort(corVarList);
+
+    // 2. Ensure CorVars are present in inputs (adding ValueGenerators if 
needed)
     Frame newLeftFrame = leftFrame;
-    boolean joinConditionContainsFieldAccess = 
RexUtil.containsFieldAccess(rel.getCondition());
-    if (joinConditionContainsFieldAccess && isCorVarDefined) {
-      final CorelMap localCorelMap = new CorelMapBuilder().build(rel);
-      final List<CorRef> corVarList = new 
ArrayList<>(localCorelMap.mapRefRelToCorRef.values());
-      Collections.sort(corVarList);
+    Frame newRightFrame = rightFrame;
+    final NavigableMap<CorDef, Integer> leftCorDefOutputs = new TreeMap<>();
+    final NavigableMap<CorDef, Integer> rightCorDefOutputs = new TreeMap<>();
+    boolean generatesNullsOnRight = rel.getJoinType().generatesNullsOnRight();
+    boolean generatesNullsOnLeft = rel.getJoinType().generatesNullsOnLeft();
+
+    if (isCorVarDefined) {
+      // ensure CorVars are present in left input
+      if (generatesNullsOnRight || 
RexUtil.containsFieldAccess(rel.getCondition())) {
+        newLeftFrame = supplyMissingCorVars(oldLeft, leftFrame, corVarList, 
leftCorDefOutputs);
+        rightCorDefOutputs.putAll(rightFrame.corDefOutputs);
+      }
+      // ensure CorVars are present in right input
+      if (generatesNullsOnLeft) {
+        newRightFrame = supplyMissingCorVars(oldRight, rightFrame, corVarList, 
rightCorDefOutputs);
+        leftCorDefOutputs.putAll(leftFrame.corDefOutputs);
+      }
+    } else {
+      leftCorDefOutputs.putAll(leftFrame.corDefOutputs);
+      rightCorDefOutputs.putAll(rightFrame.corDefOutputs);
+    }
+
+    // 3. Build Join Conditions
+    final List<RexNode> joinConditions = new ArrayList<>();
+    RexNode originalCond = decorrelateExpr(castNonNull(currentRel), map, cm, 
rel.getCondition());
+    if (!originalCond.isAlwaysTrue()) {
+      joinConditions.add(originalCond);
+    }
 
-      final NavigableMap<CorDef, Integer> corDefOutputs = new TreeMap<>();
-      newLeftFrame = createFrameWithValueGenerator(oldLeft, leftFrame, 
corVarList, corDefOutputs);
+    if (generatesNullsOnLeft || generatesNullsOnRight) {
+      List<RexNode> conds =
+          buildCorDefJoinConditions(leftCorDefOutputs, rightCorDefOutputs,
+              newLeftFrame.r, newRightFrame.r, relBuilder);
+      joinConditions.addAll(conds);
     }
 
+    RexNode finalCondition = joinConditions.isEmpty()
+        ? relBuilder.literal(true)
+        : RexUtil.composeConjunction(relBuilder.getRexBuilder(), 
joinConditions);
+
     RelNode newJoin = relBuilder
         .push(newLeftFrame.r)
-        .push(rightFrame.r)
-        .join(rel.getJoinType(),
-            decorrelateExpr(castNonNull(currentRel), map, cm, 
rel.getCondition()),
-            ImmutableSet.of())
+        .push(newRightFrame.r)
+        .join(rel.getJoinType(), finalCondition, ImmutableSet.of())
         .build();
 
-    // Create the mapping between the output of the old correlation rel
-    // and the new join rel
-    Map<Integer, Integer> mapOldToNewOutputs = new HashMap<>();
-
-    int oldLeftFieldCount = oldLeft.getRowType().getFieldCount();
+    // 4. Handle Full Join Projections (Coalesce)
+    NavigableMap<CorDef, Integer> corDefOutputs = new 
TreeMap<>(newLeftFrame.corDefOutputs);
     int newLeftFieldCount = newLeftFrame.r.getRowType().getFieldCount();
+    if (rel.getJoinType() == JoinRelType.FULL && isCorVarDefined) {
+      //
+      // SELECT
+      //    d.dname,
+      //    (
+      //        SELECT COUNT(sub.empno)
+      //        FROM (
+      //            SELECT * FROM emp e2 WHERE e2.deptno = d.deptno
+      //        ) sub
+      //        FULL JOIN emp e
+      //        ON sub.mgr = e.mgr
+      //    ) as matched_subordinate_count
+      // FROM dept d
+      // order by d.dname;
+      //
+      // LogicalJoin(condition=[=($3, $11)], joinType=[full])
+      //   LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], ...)
+      //     LogicalFilter(condition=[=($7, $cor0.DEPTNO)])
+      //       LogicalTableScan(table=[[scott, EMP]])
+      //   LogicalTableScan(table=[[scott, EMP]])
+      //
+      // convert to:
+      //
+      // LogicalProject(_cor_$cor0_0=[COALESCE($8, $17)], EMPNO=[$0])
+      //   LogicalJoin(condition=[AND(=($3, $12), IS NOT DISTINCT FROM($8, 
$17))], joinType=[full])
+      //     LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], ...)
+      //       LogicalFilter(condition=[IS NOT NULL($7)])
+      //         LogicalTableScan(table=[[scott, EMP]])
+      //     LogicalJoin(condition=[true], joinType=[inner])
+      //       LogicalTableScan(table=[[scott, EMP]])
+      //       LogicalProject(DEPTNO=[$0])
+      //         LogicalTableScan(table=[[scott, DEPT]])
+      List<RelDataTypeField> joinFields = newJoin.getRowType().getFieldList();
+
+      // 4.1. Pass through existing fields
+      final PairList<RexNode, String> projects = PairList.of();
+      for (int i = 0; i < joinFields.size(); i++) {
+        RexInputRef.add2(projects, i, joinFields);
+      }
+
+      // 4.2. Build Coalesced CorVars
+      NavigableMap<CorDef, Integer> mergedCorDefOutputs = new 
TreeMap<>(corDefOutputs);
+      int projectedIndex = joinFields.size();
+      boolean appended = false;
+
+      for (CorRef corRef : corVarList) {
+        CorDef corDef = corRef.def();
+
+        Integer leftPos = leftCorDefOutputs.get(corDef);
+        Integer rightPos = rightCorDefOutputs.get(corDef);
+
+        // If missing on both sides, nothing to coalesce or project
+        if (leftPos == null && rightPos == null) {
+          continue;
+        }
+
+        // Create references
+        RexNode leftRef = null;
+        if (leftPos != null) {
+          leftRef = new RexInputRef(leftPos, 
joinFields.get(leftPos).getType());
+        }
+
+        RexNode rightRef = null;
+        if (rightPos != null) {
+          // Right side indices are offset by the left field count in the join
+          int actualRightIndex = rightPos + newLeftFieldCount;
+          rightRef = new RexInputRef(actualRightIndex, 
joinFields.get(actualRightIndex).getType());
+        }
+
+        // Determine the expression
+        RexNode expr;
+        if (leftRef == null) {
+          expr = rightRef;
+        } else if (rightRef == null) {
+          expr = leftRef;
+        } else {
+          // Both exist, create COALESCE
+          expr = relBuilder.call(SqlStdOperatorTable.COALESCE, leftRef, 
rightRef);
+        }
 
+        String name = "_cor_" + corDef.corr.getName() + "_" + corDef.field;
+        projects.add(requireNonNull(expr, "expr"), name);
+        mergedCorDefOutputs.put(corDef, projectedIndex++);
+        appended = true;
+      }
+
+      if (appended) {
+        newJoin = relBuilder.push(newJoin)
+            .projectNamed(projects.leftList(), projects.rightList(), true)
+            .build();
+        corDefOutputs.clear();
+        corDefOutputs.putAll(mergedCorDefOutputs);
+      }
+    } else {
+      // Standard output mapping for non-Full Join (or Full Join without 
CorVars)
+      // Right input positions are shifted.
+      for (Map.Entry<CorDef, Integer> entry : 
newRightFrame.corDefOutputs.entrySet()) {
+        final int shifted = entry.getValue() + newLeftFieldCount;
+        if (rel.getJoinType().generatesNullsOnRight()) {
+          corDefOutputs.putIfAbsent(entry.getKey(), shifted);
+        } else {
+          corDefOutputs.put(entry.getKey(), shifted);
+        }
+      }
+    }
+
+    // 5. Output Mapping
+    int oldLeftFieldCount = oldLeft.getRowType().getFieldCount();
     int oldRightFieldCount = oldRight.getRowType().getFieldCount();
-    //noinspection AssertWithSideEffects
-    assert rel.getRowType().getFieldCount()
-        == oldLeftFieldCount + oldRightFieldCount;
 
-    // Left input positions are not changed.
-    mapOldToNewOutputs.putAll(newLeftFrame.oldToNewOutputs);
-    // Right input positions are shifted by newLeftFieldCount.
+    Map<Integer, Integer> mapOldToNewOutputs = new 
HashMap<>(newLeftFrame.oldToNewOutputs);
     for (int i = 0; i < oldRightFieldCount; i++) {
       mapOldToNewOutputs.put(i + oldLeftFieldCount,
-          requireNonNull(rightFrame.oldToNewOutputs.get(i)) + 
newLeftFieldCount);
+          requireNonNull(newRightFrame.oldToNewOutputs.get(i)) + 
newLeftFieldCount);
     }
 
-    final NavigableMap<CorDef, Integer> corDefOutputs =
-        new TreeMap<>(newLeftFrame.corDefOutputs);
-    // Right input positions are shifted by newLeftFieldCount.
-    for (Map.Entry<CorDef, Integer> entry
-        : rightFrame.corDefOutputs.entrySet()) {
-      corDefOutputs.put(entry.getKey(),
-          entry.getValue() + newLeftFieldCount);
-    }
     return register(rel, newJoin, mapOldToNewOutputs, corDefOutputs);
   }
 
@@ -3719,6 +3843,68 @@ private static boolean isFieldNotNullRecursive(RelNode 
rel, int index) {
     }
   }
 
+  /**
+   * Ensures that the correlated variables in {@code allCorDefs} are present
+   * in the output of the frame.
+   * If any are missing, it creates a value generator to produce them and 
joins it with the frame.
+   *
+   * @param oldInput      The original input RelNode
+   * @param frame         The current frame for the input
+   * @param corVarList    List of all correlated variables
+   * @param corDefOutputs Map to populate with the output positions of the 
correlated variables
+   * @return A new Frame with all required correlated variables, or the 
original frame
+   * if all were present
+   */
+  private Frame supplyMissingCorVars(RelNode oldInput, Frame frame,
+      List<CorRef> corVarList, NavigableMap<CorDef, Integer> corDefOutputs) {
+    final ImmutableSortedSet<CorDef> haves = frame.corDefOutputs.keySet();
+    if (hasAll(corVarList, haves)) {
+      corDefOutputs.putAll(frame.corDefOutputs);
+      return frame;
+    }
+
+    final List<CorRef> miss = new ArrayList<>();
+    for (CorRef r : corVarList) {
+      if (!haves.contains(r.def())) {
+        miss.add(r);
+      }
+    }
+
+    return createFrameWithValueGenerator(oldInput, frame, miss, corDefOutputs);
+  }
+
+  /**
+   * Builds join conditions to equate correlated variables that are present in 
both left
+   * and right inputs.
+   *
+   * @param leftCorDefOutputs  Map of CorDefs to output positions in the left 
input
+   * @param rightCorDefOutputs Map of CorDefs to output positions in the right 
input
+   * @param leftRel            The left input RelNode
+   * @param rightRel           The right input RelNode
+   * @param relBuilder         RelBuilder for creating expressions
+   * @return A list of join conditions (IS NOT DISTINCT FROM) for matching 
correlated variables
+   */
+  private List<RexNode> buildCorDefJoinConditions(
+      NavigableMap<CorDef, Integer> leftCorDefOutputs,
+      NavigableMap<CorDef, Integer> rightCorDefOutputs,
+      RelNode leftRel, RelNode rightRel, RelBuilder relBuilder) {
+    List<RexNode> joinConditions = new ArrayList<>();
+    int leftFieldCount = leftRel.getRowType().getFieldCount();
+    for (Map.Entry<CorDef, Integer> leftEntry : leftCorDefOutputs.entrySet()) {
+      CorDef corDef = leftEntry.getKey();
+      if (rightCorDefOutputs.containsKey(corDef)) {
+        int leftPos = leftEntry.getValue();
+        int rightPos = rightCorDefOutputs.get(corDef);
+        final RelDataType leftType = 
leftRel.getRowType().getFieldList().get(leftPos).getType();
+        final RelDataType rightType = 
rightRel.getRowType().getFieldList().get(rightPos).getType();
+        final RexNode leftRef = new RexInputRef(leftPos, leftType);
+        final RexNode rightRef = new RexInputRef(leftFieldCount + rightPos, 
rightType);
+        joinConditions.add(relBuilder.isNotDistinctFrom(leftRef, rightRef));
+      }
+    }
+    return joinConditions;
+  }
+
   // -------------------------------------------------------------------------
   //  Getter/Setter
   // -------------------------------------------------------------------------
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 45ef07cc45..6d1d2a974b 100644
--- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
@@ -1272,4 +1272,170 @@ public static Frameworks.ConfigBuilder config() {
         + "              LogicalTableScan(table=[[scott, BONUS]])\n";
     assertThat(after, hasTree(planAfter));
   }
+
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7379";>[CALCITE-7379]
+   * LHS correlated variables are shadowed by nullable RHS outputs in LEFT 
JOIN</a>. */
+  @Test void testDecorrelateLeftJoinCorVarShadowing() {
+    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 = ""
+        + "WITH\n"
+        + "  t1(a, b, c) AS (VALUES (2, 2, 2), (3, 3, 3), (4, 4, 4)),\n"
+        + "  t2(a, b, c) AS (VALUES (1, 1, 1), (3, 3, 3), (4, 4, 4)),\n"
+        + "  t3(a, b, c) AS (VALUES (1, 1, 1), (2, 2, 2), (4, 4, 4))\n"
+        + "SELECT * FROM t1 WHERE EXISTS (\n"
+        + "SELECT * FROM t2\n"
+        + "LEFT JOIN\n"
+        + "(SELECT * FROM t3 WHERE t3.a = t1.a) foo\n"
+        + "ON t2.a = foo.a)";
+    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(A=[$0], B=[$1], C=[$2])\n"
+        + "  LogicalProject(EXPR$0=[$0], EXPR$1=[$1], EXPR$2=[$2])\n"
+        + "    LogicalCorrelate(correlation=[$cor0], joinType=[inner], 
requiredColumns=[{0}])\n"
+        + "      LogicalValues(tuples=[[{ 2, 2, 2 }, { 3, 3, 3 }, { 4, 4, 4 
}]])\n"
+        + "      LogicalAggregate(group=[{0}])\n"
+        + "        LogicalProject(i=[true])\n"
+        + "          LogicalJoin(condition=[=($0, $3)], joinType=[left])\n"
+        + "            LogicalValues(tuples=[[{ 1, 1, 1 }, { 3, 3, 3 }, { 4, 
4, 4 }]])\n"
+        + "            LogicalProject(A=[$0], B=[$1], C=[$2])\n"
+        + "              LogicalFilter(condition=[=($0, $cor0.A)])\n"
+        + "                LogicalValues(tuples=[[{ 1, 1, 1 }, { 2, 2, 2 }, { 
4, 4, 4 }]])\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()));
+    // The plan before fix:
+    //
+    // LogicalProject(A=[$0], B=[$1], C=[$2])
+    //  LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $3)], joinType=[inner])
+    //    LogicalValues(tuples=[[{ 2, 2, 2 }, { 3, 3, 3 }, { 4, 4, 4 }]])
+    //    LogicalProject(EXPR$00=[$0], $f1=[true])
+    //      LogicalAggregate(group=[{0}])
+    //        LogicalProject(EXPR$00=[$6])
+    //          LogicalJoin(condition=[=($0, $3)], joinType=[left])
+    //            LogicalValues(tuples=[[{ 1, 1, 1 }, { 3, 3, 3 }, { 4, 4, 4 
}]])
+    //            LogicalProject(A=[$0], B=[$1], C=[$2], EXPR$0=[$0])
+    //              LogicalValues(tuples=[[{ 1, 1, 1 }, { 2, 2, 2 }, { 4, 4, 4 
}]])
+    final String planAfter = ""
+        + "LogicalProject(A=[$0], B=[$1], C=[$2])\n"
+        + "  LogicalJoin(condition=[=($0, $3)], joinType=[inner])\n"
+        + "    LogicalValues(tuples=[[{ 2, 2, 2 }, { 3, 3, 3 }, { 4, 4, 4 
}]])\n"
+        + "    LogicalProject(EXPR$00=[$0], $f1=[true])\n"
+        + "      LogicalAggregate(group=[{0}])\n"
+        + "        LogicalProject(EXPR$00=[$3])\n"
+        + "          LogicalJoin(condition=[AND(=($0, $4), IS NOT DISTINCT 
FROM($3, $7))], joinType=[left])\n"
+        + "            LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "              LogicalValues(tuples=[[{ 1, 1, 1 }, { 3, 3, 3 }, { 4, 
4, 4 }]])\n"
+        + "              LogicalProject(EXPR$0=[$0])\n"
+        + "                LogicalValues(tuples=[[{ 2, 2, 2 }, { 3, 3, 3 }, { 
4, 4, 4 }]])\n"
+        + "            LogicalProject(A=[$0], B=[$1], C=[$2], EXPR$0=[$0])\n"
+        + "              LogicalValues(tuples=[[{ 1, 1, 1 }, { 2, 2, 2 }, { 4, 
4, 4 }]])\n";
+    assertThat(after, hasTree(planAfter));
+  }
+
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7379";>[CALCITE-7379]
+   * LHS correlated variables are shadowed by nullable RHS outputs in LEFT 
JOIN</a>. */
+  @Test void testDecorrelateFullJoinCorVarShadowing() {
+    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\n"
+        + "    d.dname,\n"
+        + "    (SELECT COUNT(sub.empno)\n"
+        + "        FROM (\n"
+        + "            SELECT * FROM emp e2 WHERE e2.deptno = d.deptno\n"
+        + "        ) sub\n"
+        + "        FULL JOIN emp e\n"
+        + "        ON sub.mgr = e.mgr\n"
+        + "    ) as matched_subordinate_count\n"
+        + "FROM dept 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(DNAME=[$1], MATCHED_SUBORDINATE_COUNT=[$3])\n"
+        + "  LogicalCorrelate(correlation=[$cor0], joinType=[left], 
requiredColumns=[{0}])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "    LogicalAggregate(group=[{}], EXPR$0=[COUNT($0)])\n"
+        + "      LogicalProject(EMPNO=[$0])\n"
+        + "        LogicalJoin(condition=[=($3, $11)], joinType=[full])\n"
+        + "          LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], 
MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7])\n"
+        + "            LogicalFilter(condition=[=($7, $cor0.DEPTNO)])\n"
+        + "              LogicalTableScan(table=[[scott, EMP]])\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(DNAME=[$1], MATCHED_SUBORDINATE_COUNT=[$4])\n"
+        + "  LogicalJoin(condition=[=($0, $3)], joinType=[left])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "    LogicalProject(_cor_$cor0_0=[$0], EXPR$0=[CASE(IS NOT NULL($2), 
$2, 0)])\n"
+        + "      LogicalJoin(condition=[IS NOT DISTINCT FROM($0, $1)], 
joinType=[left])\n"
+        + "        LogicalProject(DEPTNO=[$0])\n"
+        + "          LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "        LogicalAggregate(group=[{0}], EXPR$0=[COUNT($1)])\n"
+        + "          LogicalProject(_cor_$cor0_0=[COALESCE($8, $17)], 
EMPNO=[$0])\n"
+        + "            LogicalJoin(condition=[AND(=($3, $12), IS NOT DISTINCT 
FROM($8, $17))], joinType=[full])\n"
+        + "              LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], 
MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], DEPTNO8=[$7])\n"
+        + "                LogicalFilter(condition=[IS NOT NULL($7)])\n"
+        + "                  LogicalTableScan(table=[[scott, EMP]])\n"
+        + "              LogicalJoin(condition=[true], joinType=[inner])\n"
+        + "                LogicalTableScan(table=[[scott, EMP]])\n"
+        + "                LogicalProject(DEPTNO=[$0])\n"
+        + "                  LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(after, hasTree(planAfter));
+  }
 }
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index 73b8c939b4..9095ff3059 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -7880,4 +7880,592 @@ WHERE deptno NOT IN (
 # Reset to default value 20
 !set trimfields true
 
+!use scott
+# [CALCITE-7379] LHS correlated variables are shadowed by nullable RHS outputs 
in LEFT JOIN
+# Correlated scalar subquery with LEFT JOIN.
+# The correlation variable (d.deptno) is used in the RHS of the join.
+SELECT
+    d.dname,
+    (
+        SELECT COUNT(sub.empno)
+        FROM emp e
+        LEFT JOIN (
+            SELECT * FROM emp e2 WHERE e2.deptno = d.deptno
+        ) sub
+        ON e.mgr = sub.mgr
+    ) as matched_subordinate_count
+FROM dept d;
++------------+---------------------------+
+| DNAME      | MATCHED_SUBORDINATE_COUNT |
++------------+---------------------------+
+| ACCOUNTING |                         4 |
+| OPERATIONS |                         0 |
+| RESEARCH   |                         9 |
+| SALES      |                        28 |
++------------+---------------------------+
+(4 rows)
+
+!ok
+
+# Correlated scalar subquery with RIGHT JOIN.
+# The correlation variable (d.deptno) is used in the LHS of the join.
+SELECT
+    d.dname,
+    (
+        SELECT COUNT(sub.empno)
+        FROM (
+            SELECT * FROM emp e2 WHERE e2.deptno = d.deptno
+        ) sub
+        RIGHT JOIN emp e
+        ON sub.mgr = e.mgr
+    ) as matched_subordinate_count
+FROM dept d;
++------------+---------------------------+
+| DNAME      | MATCHED_SUBORDINATE_COUNT |
++------------+---------------------------+
+| ACCOUNTING |                         4 |
+| OPERATIONS |                         0 |
+| RESEARCH   |                         9 |
+| SALES      |                        28 |
++------------+---------------------------+
+(4 rows)
+
+!ok
+
+# Correlated scalar subquery with FULL JOIN.
+# The correlation variable (d.deptno) is used in the LHS of the join.
+SELECT
+    d.dname,
+    (
+        SELECT COUNT(sub.empno)
+        FROM (
+            SELECT * FROM emp e2 WHERE e2.deptno = d.deptno
+        ) sub
+        FULL JOIN emp e
+        ON sub.mgr = e.mgr
+    ) as matched_subordinate_count
+FROM dept d
+order by d.dname;
++------------+---------------------------+
+| DNAME      | MATCHED_SUBORDINATE_COUNT |
++------------+---------------------------+
+| ACCOUNTING |                         5 |
+| OPERATIONS |                         0 |
+| RESEARCH   |                         9 |
+| SALES      |                        28 |
++------------+---------------------------+
+(4 rows)
+
+!ok
+
+# Correlated NOT EXISTS subquery with LEFT JOIN.
+# The correlation variable (d.deptno) is used in the RHS of the join.
+SELECT * FROM dept d
+WHERE NOT EXISTS (
+    SELECT 1
+    FROM emp e
+    LEFT JOIN (
+        SELECT * FROM emp e3 WHERE e3.deptno = d.deptno
+    ) foo
+    ON e.empno = foo.mgr
+);
++--------+-------+-----+
+| DEPTNO | DNAME | LOC |
++--------+-------+-----+
++--------+-------+-----+
+(0 rows)
+
+!ok
+
+# Correlated NOT EXISTS subquery with RIGHT JOIN.
+# The correlation variable (d.deptno) is used in the LHS of the join.
+SELECT * FROM dept d
+WHERE NOT EXISTS (
+    SELECT 1
+    FROM (
+        SELECT * FROM emp e3 WHERE e3.deptno = d.deptno
+    ) foo
+    RIGHT JOIN emp e
+    ON foo.mgr = e.empno
+);
++--------+-------+-----+
+| DEPTNO | DNAME | LOC |
++--------+-------+-----+
++--------+-------+-----+
+(0 rows)
+
+!ok
+
+# Correlated NOT EXISTS subquery with FULL JOIN.
+# The correlation variable (d.deptno) is used in the LHS of the join.
+SELECT * FROM dept d
+WHERE NOT EXISTS (
+    SELECT 1
+    FROM (
+        SELECT * FROM emp e3 WHERE e3.deptno = d.deptno
+    ) foo
+    FULL JOIN emp e
+    ON foo.mgr = e.empno
+);
++--------+-------+-----+
+| DEPTNO | DNAME | LOC |
++--------+-------+-----+
++--------+-------+-----+
+(0 rows)
+
+!ok
+
+# Correlated EXISTS subquery with LEFT JOIN involving 'bonus' table.
+# The correlation variable (e.ename) is used in the RHS of the join.
+SELECT e.ename, e.job, e.sal
+FROM emp e
+WHERE EXISTS (
+    SELECT 1
+    FROM dept d
+    LEFT JOIN (
+        SELECT * FROM bonus b WHERE b.ename = e.ename
+    ) foo
+    ON d.loc = foo.job
+);
++--------+-----------+---------+
+| ENAME  | JOB       | SAL     |
++--------+-----------+---------+
+| ADAMS  | CLERK     | 1100.00 |
+| ALLEN  | SALESMAN  | 1600.00 |
+| BLAKE  | MANAGER   | 2850.00 |
+| CLARK  | MANAGER   | 2450.00 |
+| FORD   | ANALYST   | 3000.00 |
+| JAMES  | CLERK     |  950.00 |
+| JONES  | MANAGER   | 2975.00 |
+| KING   | PRESIDENT | 5000.00 |
+| MARTIN | SALESMAN  | 1250.00 |
+| MILLER | CLERK     | 1300.00 |
+| SCOTT  | ANALYST   | 3000.00 |
+| SMITH  | CLERK     |  800.00 |
+| TURNER | SALESMAN  | 1500.00 |
+| WARD   | SALESMAN  | 1250.00 |
++--------+-----------+---------+
+(14 rows)
+
+!ok
+
+# Correlated EXISTS subquery with RIGHT JOIN involving 'bonus' table.
+# The correlation variable (e.ename) is used in the LHS of the join.
+SELECT e.ename, e.job, e.sal
+FROM emp e
+WHERE EXISTS (
+    SELECT 1
+    FROM (
+        SELECT * FROM bonus b WHERE b.ename = e.ename
+    ) foo
+    RIGHT JOIN dept d
+    ON foo.job = d.loc
+);
++--------+-----------+---------+
+| ENAME  | JOB       | SAL     |
++--------+-----------+---------+
+| ADAMS  | CLERK     | 1100.00 |
+| ALLEN  | SALESMAN  | 1600.00 |
+| BLAKE  | MANAGER   | 2850.00 |
+| CLARK  | MANAGER   | 2450.00 |
+| FORD   | ANALYST   | 3000.00 |
+| JAMES  | CLERK     |  950.00 |
+| JONES  | MANAGER   | 2975.00 |
+| KING   | PRESIDENT | 5000.00 |
+| MARTIN | SALESMAN  | 1250.00 |
+| MILLER | CLERK     | 1300.00 |
+| SCOTT  | ANALYST   | 3000.00 |
+| SMITH  | CLERK     |  800.00 |
+| TURNER | SALESMAN  | 1500.00 |
+| WARD   | SALESMAN  | 1250.00 |
++--------+-----------+---------+
+(14 rows)
+
+!ok
+
+# Correlated EXISTS subquery with FULL JOIN involving 'bonus' table.
+# The correlation variable (e.ename) is used in the LHS of the join.
+SELECT e.ename, e.job, e.sal
+FROM emp e
+WHERE EXISTS (
+    SELECT 1
+    FROM (
+        SELECT * FROM bonus b WHERE b.ename = e.ename
+    ) foo
+    FULL JOIN dept d
+    ON foo.job = d.loc
+);
++--------+-----------+---------+
+| ENAME  | JOB       | SAL     |
++--------+-----------+---------+
+| ADAMS  | CLERK     | 1100.00 |
+| ALLEN  | SALESMAN  | 1600.00 |
+| BLAKE  | MANAGER   | 2850.00 |
+| CLARK  | MANAGER   | 2450.00 |
+| FORD   | ANALYST   | 3000.00 |
+| JAMES  | CLERK     |  950.00 |
+| JONES  | MANAGER   | 2975.00 |
+| KING   | PRESIDENT | 5000.00 |
+| MARTIN | SALESMAN  | 1250.00 |
+| MILLER | CLERK     | 1300.00 |
+| SCOTT  | ANALYST   | 3000.00 |
+| SMITH  | CLERK     |  800.00 |
+| TURNER | SALESMAN  | 1500.00 |
+| WARD   | SALESMAN  | 1250.00 |
++--------+-----------+---------+
+(14 rows)
+
+!ok
+
+# Correlated EXISTS subquery with LEFT JOIN and complex correlation condition.
+# The correlation variable (e1.sal, e1.comm) is used in the RHS of the join.
+SELECT empno FROM emp e1
+WHERE EXISTS (
+    SELECT 1
+    FROM dept d
+    LEFT JOIN (
+        SELECT * FROM emp e2
+        WHERE e2.sal > (e1.sal + COALESCE(e1.comm, 0))
+    ) foo
+    ON d.deptno = foo.deptno
+);
++-------+
+| EMPNO |
++-------+
+|  7369 |
+|  7499 |
+|  7521 |
+|  7566 |
+|  7654 |
+|  7698 |
+|  7782 |
+|  7788 |
+|  7839 |
+|  7844 |
+|  7876 |
+|  7900 |
+|  7902 |
+|  7934 |
++-------+
+(14 rows)
+
+!ok
+
+# Correlated EXISTS subquery with RIGHT JOIN and complex correlation condition.
+# The correlation variable (e1.sal, e1.comm) is used in the LHS of the join.
+SELECT empno FROM emp e1
+WHERE EXISTS (
+    SELECT 1
+    FROM (
+        SELECT * FROM emp e2
+        WHERE e2.sal > (e1.sal + COALESCE(e1.comm, 0))
+    ) foo
+    RIGHT JOIN dept d
+    ON foo.deptno = d.deptno
+);
++-------+
+| EMPNO |
++-------+
+|  7369 |
+|  7499 |
+|  7521 |
+|  7566 |
+|  7654 |
+|  7698 |
+|  7782 |
+|  7788 |
+|  7839 |
+|  7844 |
+|  7876 |
+|  7900 |
+|  7902 |
+|  7934 |
++-------+
+(14 rows)
+
+!ok
+
+# Correlated EXISTS subquery with FULL JOIN and complex correlation condition.
+# The correlation variable (e1.sal, e1.comm) is used in the LHS of the join.
+SELECT empno FROM emp e1
+WHERE EXISTS (
+    SELECT 1
+    FROM (
+        SELECT * FROM emp e2
+        WHERE e2.sal > (e1.sal + COALESCE(e1.comm, 0))
+    ) foo
+    FULL JOIN dept d
+    ON foo.deptno = d.deptno
+);
++-------+
+| EMPNO |
++-------+
+|  7369 |
+|  7499 |
+|  7521 |
+|  7566 |
+|  7654 |
+|  7698 |
+|  7782 |
+|  7788 |
+|  7839 |
+|  7844 |
+|  7876 |
+|  7900 |
+|  7902 |
+|  7934 |
++-------+
+(14 rows)
+
+!ok
+
+# Correlated EXISTS subquery with LEFT JOIN on TRUE condition.
+# The correlation variable (d.deptno) is used in the RHS of the join.
+SELECT d.deptno
+FROM dept d
+WHERE EXISTS (
+    SELECT 1
+    FROM emp e
+    LEFT JOIN (
+        SELECT deptno FROM emp WHERE deptno = d.deptno
+    ) foo
+    ON TRUE
+    WHERE foo.deptno IS NOT DISTINCT FROM d.deptno
+);
++--------+
+| DEPTNO |
++--------+
+|     10 |
+|     20 |
+|     30 |
++--------+
+(3 rows)
+
+!ok
+
+# Correlated EXISTS subquery with RIGHT JOIN on TRUE condition.
+# The correlation variable (d.deptno) is used in the LHS of the join.
+SELECT d.deptno
+FROM dept d
+WHERE EXISTS (
+    SELECT 1
+    FROM (
+        SELECT deptno FROM emp WHERE deptno = d.deptno
+    ) foo
+    RIGHT JOIN emp e
+    ON TRUE
+    WHERE foo.deptno IS NOT DISTINCT FROM d.deptno
+);
++--------+
+| DEPTNO |
++--------+
+|     10 |
+|     20 |
+|     30 |
++--------+
+(3 rows)
+
+!ok
+
+# Correlated EXISTS subquery with FULL JOIN on TRUE condition.
+# The correlation variable (d.deptno) is used in the LHS of the join.
+SELECT d.deptno
+FROM dept d
+WHERE EXISTS (
+    SELECT 1
+    FROM (
+        SELECT deptno FROM emp WHERE deptno = d.deptno
+    ) foo
+    FULL JOIN emp e
+    ON TRUE
+    WHERE foo.deptno IS NOT DISTINCT FROM d.deptno
+);
++--------+
+| DEPTNO |
++--------+
+|     10 |
+|     20 |
+|     30 |
++--------+
+(3 rows)
+
+!ok
+
+# Correlated EXISTS subquery with LEFT JOIN.
+# The correlation variable (dept.deptno) is used in the RHS of the join.
+SELECT * FROM dept
+WHERE EXISTS (
+    SELECT * FROM emp
+    LEFT JOIN (
+        SELECT * FROM emp e_sub
+        WHERE e_sub.deptno = dept.deptno
+    ) foo
+    ON emp.deptno = foo.deptno
+);
++--------+------------+----------+
+| DEPTNO | DNAME      | LOC      |
++--------+------------+----------+
+|     10 | ACCOUNTING | NEW YORK |
+|     20 | RESEARCH   | DALLAS   |
+|     30 | SALES      | CHICAGO  |
+|     40 | OPERATIONS | BOSTON   |
++--------+------------+----------+
+(4 rows)
+
+!ok
+
+# Correlated EXISTS subquery with RIGHT JOIN.
+# The correlation variable (dept.deptno) is used in the LHS of the join.
+SELECT * FROM dept
+WHERE EXISTS (
+    SELECT * FROM (
+        SELECT * FROM emp e_sub
+        WHERE e_sub.deptno = dept.deptno
+    ) foo
+    RIGHT JOIN emp
+    ON foo.deptno = emp.deptno
+);
++--------+------------+----------+
+| DEPTNO | DNAME      | LOC      |
++--------+------------+----------+
+|     10 | ACCOUNTING | NEW YORK |
+|     20 | RESEARCH   | DALLAS   |
+|     30 | SALES      | CHICAGO  |
+|     40 | OPERATIONS | BOSTON   |
++--------+------------+----------+
+(4 rows)
+
+!ok
+
+# Correlated EXISTS subquery with FULL JOIN.
+# The correlation variable (dept.deptno) is used in the LHS of the join.
+SELECT * FROM dept
+WHERE EXISTS (
+    SELECT * FROM (
+        SELECT * FROM emp e_sub
+        WHERE e_sub.deptno = dept.deptno
+    ) foo
+    FULL JOIN emp
+    ON foo.deptno = emp.deptno
+);
++--------+------------+----------+
+| DEPTNO | DNAME      | LOC      |
++--------+------------+----------+
+|     10 | ACCOUNTING | NEW YORK |
+|     20 | RESEARCH   | DALLAS   |
+|     30 | SALES      | CHICAGO  |
+|     40 | OPERATIONS | BOSTON   |
++--------+------------+----------+
+(4 rows)
+
+!ok
+
+# Correlated EXISTS subquery with LEFT JOIN using VALUES clause.
+# The correlation variable (t1.a) is used in the RHS of the join.
+WITH
+  t1(a, b, c) AS (VALUES (2, 2, 2), (3, 3, 3), (4, 4, 4)),
+  t2(a, b, c) AS (VALUES (1, 1, 1), (3, 3, 3), (4, 4, 4)),
+  t3(a, b, c) AS (VALUES (1, 1, 1), (2, 2, 2), (4, 4, 4))
+SELECT * FROM t1 WHERE EXISTS (
+SELECT * FROM t2
+LEFT JOIN
+(SELECT * FROM t3 WHERE t3.a = t1.a) foo
+ON t2.a = foo.a
+);
++---+---+---+
+| A | B | C |
++---+---+---+
+| 2 | 2 | 2 |
+| 3 | 3 | 3 |
+| 4 | 4 | 4 |
++---+---+---+
+(3 rows)
+
+!ok
+
+# Correlated EXISTS subquery with LEFT JOIN using VALUES clause.
+# The correlation variable (t1.a) is used in the LHS of the join.
+WITH
+  t1(a, b, c) AS (VALUES (2, 2, 2), (3, 3, 3), (4, 4, 4)),
+  t2(a, b, c) AS (VALUES (1, 1, 1), (3, 3, 3), (4, 4, 4)),
+  t3(a, b, c) AS (VALUES (1, 1, 1), (2, 2, 2), (4, 4, 4))
+SELECT * FROM t1 WHERE EXISTS (
+SELECT * FROM (SELECT * FROM t3 WHERE t3.a = t1.a) foo
+LEFT JOIN t2
+ON foo.a = t2.a
+);
++---+---+---+
+| A | B | C |
++---+---+---+
+| 2 | 2 | 2 |
+| 4 | 4 | 4 |
++---+---+---+
+(2 rows)
+
+!ok
+
+# Correlated EXISTS subquery with RIGHT JOIN using VALUES clause.
+# The correlation variable (t1.a) is used in the LHS of the join.
+WITH
+  t1(a, b, c) AS (VALUES (2, 2, 2), (3, 3, 3), (4, 4, 4)),
+  t2(a, b, c) AS (VALUES (1, 1, 1), (3, 3, 3), (4, 4, 4)),
+  t3(a, b, c) AS (VALUES (1, 1, 1), (2, 2, 2), (4, 4, 4))
+SELECT * FROM t1 WHERE EXISTS (
+SELECT * FROM (SELECT * FROM t3 WHERE t3.a = t1.a) foo
+RIGHT JOIN t2
+ON foo.a = t2.a
+);
++---+---+---+
+| A | B | C |
++---+---+---+
+| 2 | 2 | 2 |
+| 3 | 3 | 3 |
+| 4 | 4 | 4 |
++---+---+---+
+(3 rows)
+
+!ok
+
+# Correlated EXISTS subquery with FULL JOIN using VALUES clause.
+# The correlation variable (t1.a) is used in the LHS of the join.
+WITH
+  t1(a, b, c) AS (VALUES (2, 2, 2), (3, 3, 3), (4, 4, 4)),
+  t2(a, b, c) AS (VALUES (1, 1, 1), (3, 3, 3), (4, 4, 4)),
+  t3(a, b, c) AS (VALUES (1, 1, 1), (2, 2, 2), (4, 4, 4))
+SELECT * FROM t1 WHERE EXISTS (
+SELECT * FROM (SELECT * FROM t3 WHERE t3.a = t1.a) foo
+FULL JOIN t2
+ON foo.a = t2.a
+);
++---+---+---+
+| A | B | C |
++---+---+---+
+| 2 | 2 | 2 |
+| 3 | 3 | 3 |
+| 4 | 4 | 4 |
++---+---+---+
+(3 rows)
+
+!ok
+
+# Correlated EXISTS subquery with INNER JOIN using VALUES clause.
+# The correlation variable (t1.a) is used in the RHS of the join.
+WITH
+  t1(a, b, c) AS (VALUES (2, 2, 2), (3, 3, 3), (4, 4, 4)),
+  t2(a, b, c) AS (VALUES (1, 1, 1), (3, 3, 3), (4, 4, 4)),
+  t3(a, b, c) AS (VALUES (1, 1, 1), (2, 2, 2), (4, 4, 4))
+SELECT * FROM t1 WHERE EXISTS (
+SELECT * FROM t2
+INNER JOIN
+(SELECT * FROM t3 WHERE t3.a = t1.a) foo
+ON t2.a = foo.a
+);
++---+---+---+
+| A | B | C |
++---+---+---+
+| 4 | 4 | 4 |
++---+---+---+
+(1 row)
+
+!ok
 # End sub-query.iq


Reply via email to