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

Reply via email to