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