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 4bdd19a4c1 [CALCITE-7396] PruneEmptyRules does not support LEFT_MARK
JOIN
4bdd19a4c1 is described below
commit 4bdd19a4c18db4a7ad6f0b9651b624a47dc61731
Author: Zhen Chen <[email protected]>
AuthorDate: Sun Jan 25 22:07:29 2026 +0800
[CALCITE-7396] PruneEmptyRules does not support LEFT_MARK JOIN
---
.../apache/calcite/rel/rules/PruneEmptyRules.java | 14 +++++++++++++
.../org/apache/calcite/test/RelOptRulesTest.java | 16 +++++++++++++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 24 ++++++++++++++++++++++
core/src/test/resources/sql/new-decorr.iq | 20 +++++++++++++++++-
4 files changed, 73 insertions(+), 1 deletion(-)
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
index 221cfac09d..0ee85558aa 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
@@ -42,12 +42,14 @@
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexDynamicParam;
import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;
import org.immutables.value.Value;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
@@ -260,6 +262,7 @@ private static boolean isEmpty(RelNode node) {
* <li>Join(Scan(Emp), Empty, RIGHT) becomes Empty
* <li>Join(Scan(Emp), Empty, SEMI) becomes Empty
* <li>Join(Scan(Emp), Empty, ANTI) becomes Scan(Emp)
+ * <li>Join(Scan(Emp), Empty, LEFT_MARK) becomes Project(Scan(Emp), FALSE)
* </ul>
*/
public static final RelOptRule JOIN_RIGHT_INSTANCE =
@@ -566,6 +569,17 @@ public interface JoinRightEmptyRuleConfig extends
PruneEmptyRule.Config {
call.transformTo(join.getLeft());
return;
}
+ if (join.getJoinType() == JoinRelType.LEFT_MARK) {
+ // In case of left mark join with empty right: Join(X, Empty,
LEFT_MARK)
+ // The mark column is always FALSE when right is empty
+ relBuilder.push(left);
+ List<RexNode> projects = new ArrayList<>(relBuilder.fields());
+ projects.add(relBuilder.literal(false));
+ relBuilder.project(projects)
+ .convert(join.getRowType(), true);
+ call.transformTo(relBuilder.build());
+ return;
+ }
call.transformTo(relBuilder.push(join).empty().build());
}
};
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 f9cedb9d81..a7dd7dd614 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -12165,6 +12165,22 @@ private void
checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
.check();
}
+ /** Test case of
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7396">[CALCITE-7396]
+ * PruneEmptyRules does not support LEFT_MARK JOIN</a>. */
+ @Test void testPruneEmptyRuleForLeftMarkJoin() {
+ final String sql = "select * from dept"
+ + " where deptno not in (select deptno from emp where false)";
+
+ sql(sql)
+ .withPreRule(
+ CoreRules.FILTER_SUB_QUERY_TO_MARK_CORRELATE,
+ CoreRules.FILTER_REDUCE_EXPRESSIONS,
+ PruneEmptyRules.PROJECT_INSTANCE)
+ .withRule(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
+ .check();
+ }
+
/** Test case of
* <a
href="https://issues.apache.org/jira/browse/CALCITE-7395">[CALCITE-7395]
* ProjectMergeRule incorrectly merges PROJECTs with correlation
variables</a>. */
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 785bce7839..977367cf19 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -12087,6 +12087,30 @@ LogicalProject(COL1=[$2], COL2=[$3])
<![CDATA[
LogicalProject(COL1=[SUM(100) OVER (PARTITION BY $7 ORDER BY $5)],
COL2=[SUM(1000) OVER (PARTITION BY $7 ORDER BY $5)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testPruneEmptyRuleForLeftMarkJoin">
+ <Resource name="sql">
+ <![CDATA[select * from dept where deptno not in (select deptno from emp
where false)]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+ LogicalProject(DEPTNO=[$0], NAME=[$1])
+ LogicalFilter(condition=[NOT($2)])
+ LogicalJoin(condition=[=($0, $2)], joinType=[left_mark])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalValues(tuples=[[]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+ LogicalProject(DEPTNO=[$0], NAME=[$1])
+ LogicalFilter(condition=[NOT($2)])
+ LogicalProject(DEPTNO=[$0], NAME=[$1], markCol=[false])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
]]>
</Resource>
</TestCase>
diff --git a/core/src/test/resources/sql/new-decorr.iq
b/core/src/test/resources/sql/new-decorr.iq
index 8c1bc0b23d..9257f05495 100644
--- a/core/src/test/resources/sql/new-decorr.iq
+++ b/core/src/test/resources/sql/new-decorr.iq
@@ -169,7 +169,7 @@ EnumerableCalc(expr#0..7=[{inputs}],
expr#8=[CAST($t7):INTEGER], expr#9=[10], ex
!plan
!}
-# # This case comes from scalar.iq [CALCITE-709]
+# This case comes from scalar.iq [CALCITE-709]
# Aggregate functions do not support type promotion, so a cast is added to
pass the test.
select deptno, (select sum(cast(empno as bigint)) from "scott".emp where
deptno = dept.deptno limit 0) as x from "scott".dept;
+--------+---+
@@ -205,4 +205,22 @@ EnumerableCalc(expr#0..3=[{inputs}], DEPTNO=[$t0],
EXPR$0=[$t2])
!plan
!}
+# [CALCITE-7396] PruneEmptyRules does not support LEFT_MARK JOIN
+# This case comes from sub-query.iq
+!use post
+select * from dept where deptno not in (select deptno from emp where false);
++--------+-------------+
+| DEPTNO | DNAME |
++--------+-------------+
+| 10 | Sales |
+| 20 | Marketing |
+| 30 | Engineering |
+| 40 | Empty |
++--------+-------------+
+(4 rows)
+
+!ok
+EnumerableValues(tuples=[[{ 10, 'Sales ' }, { 20, 'Marketing ' }, { 30,
'Engineering' }, { 40, 'Empty ' }]])
+!plan
+
# End new-decorr.iq