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

zhenchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 84e35bafd4 [CALCITE-7389] PruneJoinSingleValue rule causes type 
mismatch in EXISTS
84e35bafd4 is described below

commit 84e35bafd42784138a2c63cf0c70e1f9744d34d7
Author: Zhen Chen <[email protected]>
AuthorDate: Wed Jan 21 21:54:11 2026 +0800

    [CALCITE-7389] PruneJoinSingleValue rule causes type mismatch in EXISTS
---
 .../rel/rules/SingleValuesOptimizationRules.java   | 67 +++++++++++-----------
 .../org/apache/calcite/test/RelOptRulesTest.java   | 29 ++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 35 +++++++++++
 core/src/test/resources/sql/new-decorr.iq          | 45 +++++++++++++++
 4 files changed, 143 insertions(+), 33 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SingleValuesOptimizationRules.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SingleValuesOptimizationRules.java
index c10504b31e..3212e98686 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SingleValuesOptimizationRules.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SingleValuesOptimizationRules.java
@@ -34,6 +34,8 @@
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.ImmutableBitSet;
 
+import com.google.common.collect.ImmutableList;
+
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.immutables.value.Value;
 
@@ -121,41 +123,38 @@ protected SingleValuesRelTransformer(
       if (!transformable.test(join)) {
         return null;
       }
-      int end = valuesAsLeftChild
-          ? join.getLeft().getRowType().getFieldCount()
-          : join.getRowType().getFieldCount();
-
-      int start = valuesAsLeftChild
-          ? 0
-          : join.getLeft().getRowType().getFieldCount();
-      ImmutableBitSet bitSet = ImmutableBitSet.range(start, end);
-      RexNode trueNode = relBuilder.getRexBuilder().makeLiteral(true);
-      final RexNode filterCondition =
-          new RexNodeReplacer(bitSet,
-              literals,
-              (valuesAsLeftChild ? 0 : -1) * 
join.getLeft().getRowType().getFieldCount())
+      final int leftCount = join.getLeft().getRowType().getFieldCount();
+      final int rightCount = join.getRight().getRowType().getFieldCount();
+      final int start = valuesAsLeftChild ? 0 : leftCount;
+      final int end = start + (valuesAsLeftChild ? leftCount : rightCount);
+      final int offset = valuesAsLeftChild ? 0 : -leftCount;
+
+      final ImmutableBitSet bitSet = ImmutableBitSet.range(start, end);
+      RexNode condition =
+          new RexNodeReplacer(bitSet, literals, offset)
               .go(join.getCondition());
 
-      RexNode fixedCondition =
-          valuesAsLeftChild
-              ? RexUtil.shift(filterCondition,
-              -1 * join.getLeft().getRowType().getFieldCount())
-              : filterCondition;
-
-      List<RexNode> rexLiterals = litTransformer.apply(fixedCondition, 
literals);
-      relBuilder.push(relNode)
-          .filter(join.getJoinType().isOuterJoin() ? trueNode : 
fixedCondition);
-
-      List<RexNode> rexNodes = relNode
-          .getRowType()
-          .getFieldList()
-          .stream()
-          .map(fld -> relBuilder.field(fld.getIndex()))
-          .collect(Collectors.toList());
-
-      List<RexNode> projects = new ArrayList<>();
-      projects.addAll(valuesAsLeftChild ? rexLiterals : rexNodes);
-      projects.addAll(valuesAsLeftChild ? rexNodes : rexLiterals);
+      if (valuesAsLeftChild) {
+        condition = RexUtil.shift(condition, -leftCount);
+      }
+
+      relBuilder.push(relNode);
+      if (!join.getJoinType().isOuterJoin()
+          && join.getJoinType() != JoinRelType.LEFT_MARK) {
+        relBuilder.filter(condition);
+      }
+
+      final List<RexNode> otherNodes = relBuilder.fields();
+      final List<RexNode> valuesNodes = litTransformer.apply(condition, 
literals);
+
+      final List<RexNode> joinLeftNodes = valuesAsLeftChild ? valuesNodes : 
otherNodes;
+      final List<RexNode> joinRightNodes = valuesAsLeftChild ? otherNodes : 
valuesNodes;
+
+      final List<RexNode> projects = new ArrayList<>(joinLeftNodes);
+      if (join.getJoinType().projectsRight()
+          || join.getJoinType() == JoinRelType.LEFT_MARK) {
+        projects.addAll(joinRightNodes);
+      }
       return relBuilder.project(projects).build();
     }
   }
@@ -216,6 +215,8 @@ protected PruneSingleValueRule(PruneSingleValueRule.Config 
config) {
         return (condition, rexLiterals) -> rexLiterals.stream().map(lit ->
             rexBuilder.makeCall(SqlStdOperatorTable.CASE, condition,
                 lit, 
rexBuilder.makeNullLiteral(lit.getType()))).collect(Collectors.toList());
+      case LEFT_MARK:
+        return (condition, rexLiterals) -> ImmutableList.of(condition);
       default:
         return (condition, rexLiterals) -> rexLiterals;
       }
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index ff0e5ac9d2..2c8c5eab43 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -5341,6 +5341,35 @@ RelOptFixture checkDynamicFunctions(boolean 
treatDynamicCallsAsConstant) {
         .check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7389";>[CALCITE-7389]
+   * PruneJoinSingleValue rule causes type mismatch in EXISTS</a>. */
+  @Test void testExistsDecorrelationProducesSingleValues() {
+    final String sql = "select empno, deptno in (select 10) from emp";
+    sql(sql)
+        .withPreRule(
+            CoreRules.PROJECT_SUB_QUERY_TO_MARK_CORRELATE,
+            CoreRules.PROJECT_MERGE,
+            CoreRules.PROJECT_REMOVE)
+        .withTopDownGeneralDecorrelate(true)
+        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_INSTANCE)
+        .check();
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7389";>[CALCITE-7389]
+   * PruneJoinSingleValue rule causes type mismatch in EXISTS</a>. */
+  @Test void testLeftMarkJoinWithSingleValues() {
+    relFn(builder -> builder
+        .scan("EMP")
+        .values(new String[]{"val"}, 1)
+        .join(JoinRelType.LEFT_MARK,
+            builder.equals(builder.field(2, 0, 0), builder.field(2, 1, 0)))
+        .build())
+        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_INSTANCE)
+        .check();
+  }
+
   @Test void testInnerJoinWithTimeStampSingleRowOnRight() {
     final String sql = "select e.empno, e.ename, c.t"
         + " from emp e inner join (select 7934 as ono, current_timestamp as t) 
c on e.empno=c.ono";
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index d33273b466..5fa2ddc3eb 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -4407,6 +4407,26 @@ LogicalSortExchange(distribution=[hash[1]], 
collation=[[1]])
     LogicalExchange(distribution=[single])
       LogicalFilter(condition=[=($0, 10)])
         LogicalTableScan(table=[[scott, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testExistsDecorrelationProducesSingleValues">
+    <Resource name="sql">
+      <![CDATA[select empno, deptno in (select 10) from emp]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EMPNO=[$0], EXPR$1=[$9])
+  LogicalJoin(condition=[=($7, $9)], joinType=[left_mark])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalValues(tuples=[[{ 10 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(EMPNO=[$0], EXPR$1=[$9])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f9=[=($7, 10)])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
   </TestCase>
@@ -9181,6 +9201,21 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], T=[$10])
 LogicalProject(EMPNO=[$0], ENAME=[$1], T=[$10])
   LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f9=[CASE(=($0, 7934), 7934, 
null:INTEGER)], $f10=[CASE(=($0, 7934), CURRENT_TIMESTAMP, null:TIMESTAMP(0))])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testLeftMarkJoinWithSingleValues">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalJoin(condition=[=($0, $8)], joinType=[left_mark])
+  LogicalTableScan(table=[[scott, EMP]])
+  LogicalValues(tuples=[[{ 1 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[=($0, 1)])
+  LogicalTableScan(table=[[scott, EMP]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/new-decorr.iq 
b/core/src/test/resources/sql/new-decorr.iq
index 4e804687da..1c60b58a1d 100644
--- a/core/src/test/resources/sql/new-decorr.iq
+++ b/core/src/test/resources/sql/new-decorr.iq
@@ -124,4 +124,49 @@ SELECT dname, (SELECT empno FROM emp WHERE dept.deptno = 
emp.deptno LIMIT 1) FRO
 
 !ok
 
+# [CALCITE-7389] PruneJoinSingleValue rule causes type mismatch in EXISTS
+!use scott
+select count(*) as c from "scott".dept where exists (select 1);
++---+
+| C |
++---+
+| 4 |
++---+
+(1 row)
+
+!ok
+
+EnumerableAggregate(group=[{}], C=[COUNT()])
+  EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+select empno, deptno in (select 10) from emp;
++-------+--------+
+| EMPNO | EXPR$1 |
++-------+--------+
+|  7782 | true   |
+|  7839 | true   |
+|  7934 | true   |
+|  7369 | false  |
+|  7499 | false  |
+|  7521 | false  |
+|  7566 | false  |
+|  7654 | false  |
+|  7698 | false  |
+|  7788 | false  |
+|  7844 | false  |
+|  7876 | false  |
+|  7900 | false  |
+|  7902 | false  |
++-------+--------+
+(14 rows)
+
+!ok
+
+!if (use_new_decorr) {
+EnumerableCalc(expr#0..7=[{inputs}], expr#8=[CAST($t7):INTEGER], expr#9=[10], 
expr#10=[=($t8, $t9)], EMPNO=[$t0], EXPR$1=[$t10])
+  EnumerableTableScan(table=[[scott, EMP]])
+!plan
+!}
+
 # End new-decorr.iq

Reply via email to