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

xuzifu666 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 4a46a9bf87 [CALCITE-7672] PruneEmptyRules should support pruning empty 
Calc
4a46a9bf87 is described below

commit 4a46a9bf870eb7ceb3c87ce1b7a04222bbf74be8
Author: Yu Xu <[email protected]>
AuthorDate: Mon Jul 27 11:38:50 2026 +0800

    [CALCITE-7672] PruneEmptyRules should support pruning empty Calc
---
 .../java/org/apache/calcite/plan/RelOptRules.java    |  1 +
 .../apache/calcite/rel/rules/PruneEmptyRules.java    | 17 +++++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.java     | 16 ++++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml      | 20 ++++++++++++++++++++
 4 files changed, 54 insertions(+)

diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptRules.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptRules.java
index 2abce5f327..434e278dcb 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptRules.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptRules.java
@@ -101,6 +101,7 @@ private RelOptRules() {
           PruneEmptyRules.MINUS_INSTANCE,
           PruneEmptyRules.PROJECT_INSTANCE,
           PruneEmptyRules.FILTER_INSTANCE,
+          PruneEmptyRules.CALC_INSTANCE,
           PruneEmptyRules.SORT_INSTANCE,
           PruneEmptyRules.AGGREGATE_INSTANCE,
           PruneEmptyRules.WINDOW_INSTANCE,
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 5cb60bc906..95331c69a4 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
@@ -26,6 +26,7 @@
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.SingleRel;
 import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.Calc;
 import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.Intersect;
@@ -191,6 +192,19 @@ private static boolean isEmpty(RelNode node) {
   public static final RelOptRule FILTER_INSTANCE =
       RemoveEmptySingleRule.RemoveEmptySingleRuleConfig.FILTER.toRule();
 
+  /**
+   * Rule that converts a {@link org.apache.calcite.rel.core.Calc}
+   * to empty if its child is empty.
+   *
+   * <p>Examples:
+   *
+   * <ul>
+   * <li>Calc(Empty) becomes Empty
+   * </ul>
+   */
+  public static final RelOptRule CALC_INSTANCE =
+      RemoveEmptySingleRule.RemoveEmptySingleRuleConfig.CALC.toRule();
+
   /**
    * Rule that converts a {@link org.apache.calcite.rel.core.Sort}
    * to empty if its child is empty.
@@ -373,6 +387,9 @@ public interface RemoveEmptySingleRuleConfig extends 
PruneEmptyRule.Config {
       RemoveEmptySingleRuleConfig FILTER = 
ImmutableRemoveEmptySingleRuleConfig.of()
           .withDescription("PruneEmptyFilter")
           .withOperandFor(Filter.class, singleRel -> true);
+      RemoveEmptySingleRuleConfig CALC = 
ImmutableRemoveEmptySingleRuleConfig.of()
+          .withDescription("PruneEmptyCalc")
+          .withOperandFor(Calc.class, singleRel -> true);
       RemoveEmptySingleRuleConfig SORT = 
ImmutableRemoveEmptySingleRuleConfig.of()
           .withDescription("PruneEmptySort")
           .withOperandFor(Sort.class, singleRel -> true);
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 52c1875e36..5c687519da 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -5349,6 +5349,22 @@ RelOptFixture checkDynamicFunctions(boolean 
treatDynamicCallsAsConstant) {
         .check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7672";>[CALCITE-7672]
+   * PruneEmptyRules should support pruning empty Calc</a>.
+   */
+  @Test void testEmptyCalc() {
+    final String sql = "select z + x from (\n"
+        + "  select x + y as z, x from (\n"
+        + "    select * from (values (10, 1), (30, 3)) as t (x, y)\n"
+        + "    where x + y > 50))";
+    sql(sql)
+        .withRule(CoreRules.FILTER_VALUES_MERGE,
+            CoreRules.PROJECT_TO_CALC,
+            PruneEmptyRules.CALC_INSTANCE)
+        .check();
+  }
+
   @Test void testEmptyIntersect() {
     final String sql = "select * from (values (30, 3))"
         + "intersect\n"
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 e2b725a7d6..f1bb4248e5 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -4212,6 +4212,26 @@ LogicalAggregate(group=[{}], EXPR$0=[COUNT()], 
EXPR$1=[SUM($0)])
     <Resource name="planAfter">
       <![CDATA[
 LogicalValues(tuples=[[{ 0, null }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyCalc">
+    <Resource name="sql">
+      <![CDATA[select z + x from (
+  select x + y as z, x from (
+    select * from (values (10, 1), (30, 3)) as t (x, y)
+    where x + y > 50))]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(EXPR$0=[+(+($0, $1), $0)])
+  LogicalFilter(condition=[>(+($0, $1), 50)])
+    LogicalValues(tuples=[[{ 10, 1 }, { 30, 3 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalValues(tuples=[[]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to