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 2abce5f327ec..434e278dcb85 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 5d70c5e0dec4..02b0bd8af1b2 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; @@ -192,6 +193,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. + * + *

Examples: + * + *

+ */ + 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. @@ -374,6 +388,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 72d94a5a0858..b745b173cf40 100644 --- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java @@ -5321,6 +5321,22 @@ RelOptFixture checkDynamicFunctions(boolean treatDynamicCallsAsConstant) { .check(); } + /** Test case for + * [CALCITE-7672] + * PruneEmptyRules should support pruning empty Calc. + */ + @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 4eae45cc8aa3..96ea8dc39d36 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -3977,6 +3977,26 @@ LogicalAggregate(group=[{}], EXPR$0=[COUNT()], EXPR$1=[SUM($0)]) + + + + + 50))]]> + + + (+($0, $1), 50)]) + LogicalValues(tuples=[[{ 10, 1 }, { 30, 3 }]]) +]]> + + +