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

xiong 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 7563e44b23 [CALCITE-6955] PruneEmptyRules does not handle the all 
attribute of SetOp correctly
7563e44b23 is described below

commit 7563e44b2320ef2d90ca4284ea668ea13836abc7
Author: Zhen Chen <[email protected]>
AuthorDate: Thu Apr 17 10:07:42 2025 +0800

    [CALCITE-6955] PruneEmptyRules does not handle the all attribute of SetOp 
correctly
---
 .../apache/calcite/rel/rules/PruneEmptyRules.java  |  6 +++
 .../org/apache/calcite/test/RelOptRulesTest.java   | 35 +++++++++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 52 ++++++++++++++++++++++
 core/src/test/resources/sql/set-op.iq              | 37 +++++++++++++++
 4 files changed, 130 insertions(+)

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 58a5caf02a..1afbef100e 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
@@ -406,6 +406,9 @@ public interface UnionEmptyPruneRuleConfig extends 
PruneEmptyRule.Config {
               + RelOptUtil.toString(union);
           if (nonEmptyInputs == 0) {
             relBuilder.push(union).empty();
+          } else if (nonEmptyInputs == 1 && !union.all) {
+            relBuilder.distinct();
+            relBuilder.convert(union.getRowType(), true);
           } else {
             relBuilder.union(union.all, nonEmptyInputs);
             relBuilder.convert(union.getRowType(), true);
@@ -447,6 +450,9 @@ public interface MinusEmptyPruneRuleConfig extends 
PruneEmptyRule.Config {
               + RelOptUtil.toString(minus);
           if (nonEmptyInputs == 0) {
             relBuilder.push(minus).empty();
+          } else if (nonEmptyInputs == 1 && !minus.all) {
+            relBuilder.distinct();
+            relBuilder.convert(minus.getRowType(), true);
           } else {
             relBuilder.minus(minus.all, nonEmptyInputs);
             relBuilder.convert(minus.getRowType(), 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 08abdd46e8..7345adb00d 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -4694,6 +4694,26 @@ RelOptFixture checkDynamicFunctions(boolean 
treatDynamicCallsAsConstant) {
         .check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6955";>[CALCITE-6955]
+   * PruneEmptyRules does not handle the all attribute of SetOp correctly</a>. 
*/
+  @Test void testEmptyFilterProjectUnion2() {
+    final String sql = "select * from (\n"
+        + "select * from (values (10, 1), (30, 3), (30, 3)) as t (x, y)\n"
+        + "union\n"
+        + "select * from (values (20, 2))\n"
+        + ")\n"
+        + "where x + y > 30";
+    sql(sql)
+        .withRule(CoreRules.FILTER_SET_OP_TRANSPOSE,
+            CoreRules.FILTER_PROJECT_TRANSPOSE,
+            CoreRules.PROJECT_MERGE,
+            CoreRules.PROJECT_FILTER_VALUES_MERGE,
+            PruneEmptyRules.PROJECT_INSTANCE,
+            PruneEmptyRules.UNION_INSTANCE)
+        .check();
+  }
+
   /** Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1488";>[CALCITE-1488]
    * ValuesReduceRule should ignore empty Values</a>. */
@@ -4766,6 +4786,21 @@ RelOptFixture checkDynamicFunctions(boolean 
treatDynamicCallsAsConstant) {
         .check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6955";>[CALCITE-6955]
+   * PruneEmptyRules does not handle the all attribute of SetOp correctly</a>. 
*/
+  @Test void testEmptyMinus3() {
+    // First input is empty; therefore whole expression is empty
+    final String sql = "select * from (values (30, 3), (30, 3)) as t (x, y)\n"
+        + "except\n"
+        + "select * from (values (20, 2)) as t (x, y) where x > 30";
+    sql(sql)
+        .withRule(CoreRules.PROJECT_FILTER_VALUES_MERGE,
+            PruneEmptyRules.PROJECT_INSTANCE,
+            PruneEmptyRules.MINUS_INSTANCE)
+        .check();
+  }
+
   @Test void testEmptyTable() {
     // table is transformed to empty values and extra project will be removed.
     final String sql = "select * from EMPTY_PRODUCTS\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 1a13759208..7f283af937 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -3741,6 +3741,34 @@ LogicalProject(X=[$0], Y=[$1])
       <![CDATA[
 LogicalProject(X=[$0], Y=[$1])
   LogicalValues(tuples=[[{ 30, 3 }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyFilterProjectUnion2">
+    <Resource name="sql">
+      <![CDATA[select * from (
+select * from (values (10, 1), (30, 3), (30, 3)) as t (x, y)
+union
+select * from (values (20, 2))
+)
+where x + y > 30]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(X=[$0], Y=[$1])
+  LogicalFilter(condition=[>(+($0, $1), 30)])
+    LogicalUnion(all=[false])
+      LogicalProject(X=[$0], Y=[$1])
+        LogicalValues(tuples=[[{ 10, 1 }, { 30, 3 }, { 30, 3 }]])
+      LogicalProject(EXPR$0=[$0], EXPR$1=[$1])
+        LogicalValues(tuples=[[{ 20, 2 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(X=[$0], Y=[$1])
+  LogicalAggregate(group=[{0, 1}])
+    LogicalValues(tuples=[[{ 30, 3 }, { 30, 3 }]])
 ]]>
     </Resource>
   </TestCase>
@@ -3850,6 +3878,30 @@ LogicalMinus(all=[false])
     LogicalValues(tuples=[[{ 30, 3 }]])
   LogicalProject(EXPR$0=[$0], EXPR$1=[$1])
     LogicalValues(tuples=[[{ 40, 4 }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyMinus3">
+    <Resource name="sql">
+      <![CDATA[select * from (values (30, 3), (30, 3)) as t (x, y)
+except
+select * from (values (20, 2)) as t (x, y) where x > 30]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalMinus(all=[false])
+  LogicalProject(X=[$0], Y=[$1])
+    LogicalValues(tuples=[[{ 30, 3 }, { 30, 3 }]])
+  LogicalProject(X=[$0], Y=[$1])
+    LogicalFilter(condition=[>($0, 30)])
+      LogicalValues(tuples=[[{ 20, 2 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalAggregate(group=[{0, 1}])
+  LogicalProject(X=[$0], Y=[$1])
+    LogicalValues(tuples=[[{ 30, 3 }, { 30, 3 }]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/set-op.iq 
b/core/src/test/resources/sql/set-op.iq
index 3e6c56b1d2..4b3917ad30 100644
--- a/core/src/test/resources/sql/set-op.iq
+++ b/core/src/test/resources/sql/set-op.iq
@@ -258,4 +258,41 @@ UNION
 (2 rows)
 
 !ok
+
+# [CALCITE-6955] PruneEmptyRules does not handle the all attribute of SetOp 
correctly
+select * from (values (10, 1), (30, 3), (30, 3)) as t (x, y)
+union
+select * from (values (20, 2)) as t (x, y) where x > 30;
++----+---+
+| X  | Y |
++----+---+
+| 10 | 1 |
+| 30 | 3 |
++----+---+
+(2 rows)
+
+!ok
+
+EnumerableAggregate(group=[{0, 1}])
+  EnumerableValues(tuples=[[{ 10, 1 }, { 30, 3 }, { 30, 3 }]])
+!plan
+
+# [CALCITE-6955] PruneEmptyRules does not handle the all attribute of SetOp 
correctly
+select * from (values (30, 3), (30, 3)) as t (x, y)
+except
+select * from (values (20, 2)) as t (x, y) where x > 30;
++----+---+
+| X  | Y |
++----+---+
+| 30 | 3 |
++----+---+
+(1 row)
+
+!ok
+
+EnumerableCalc(expr#0=[{inputs}], expr#1=[3], proj#0..1=[{exprs}])
+  EnumerableAggregate(group=[{0}])
+    EnumerableValues(tuples=[[{ 30, 3 }, { 30, 3 }]])
+!plan
+
 # End set-op.iq

Reply via email to