diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java index 5c5cc04c71a6..c02b1ab12f75 100644 --- a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java +++ b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java @@ -727,8 +727,9 @@ private CoreRules() {} public static final SortRemoveConstantKeysRule SORT_REMOVE_CONSTANT_KEYS = SortRemoveConstantKeysRule.Config.DEFAULT.toRule(); - /** Rule that removes redundant {@link Sort} if its input max row number - * is less than or equal to one. */ + /** Rule that removes redundant {@code Order By} or {@code Limit} when its input RelNode's + * max row count is less than or equal to specified row count.All of them + * are represented by {@link Sort}*/ public static final SortRemoveRedundantRule SORT_REMOVE_REDUNDANT = SortRemoveRedundantRule.Config.DEFAULT.toRule(); diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRedundantRule.java b/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRedundantRule.java index 68a449b43d4c..8dd2e722e299 100644 --- a/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRedundantRule.java +++ b/core/src/main/java/org/apache/calcite/rel/rules/SortRemoveRedundantRule.java @@ -17,15 +17,21 @@ package org.apache.calcite.rel.rules; import org.apache.calcite.plan.RelOptRuleCall; +import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.plan.RelRule; import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rex.RexLiteral; import org.immutables.value.Value; -/** - * Planner rule that removes - * the redundant {@link org.apache.calcite.rel.core.Sort} if its input - * max row number is less than or equal to one. +import java.util.Optional; + +/** Rule that removes redundant {@code Order By} or {@code Limit} when its input RelNode's + * max row count is less than or equal to specified row count.All of them + * are represented by {@link Sort} + * + *

If a {@code Sort} is pure order by,and its offset is null,when its input RelNode's + * max row count is less than or equal to 1,then we could remove the redundant sort. * *

For example: *

{@code
@@ -37,6 +43,23 @@
  *  select max(totalprice) from orders}
  *  
* + *

If a {@code Sort} is pure limit,and its offset is null, when its input + * RelNode's max row count is less than or equal to the limit's fetch,then we could + * remove the redundant sort. + * + *

For example: + *

{@code
+ * SELECT * FROM (VALUES 1,2,3,4,5,6) AS t1 LIMIT 10}
+ * 
+ * + *

The above values max row count is 6 rows, and the limit's fetch is 10, + * so we could remove the redundant sort. + * + *

It could be converted to: + *

{@code
+ * SELECT * FROM (VALUES 1,2,3,4,5,6) AS t1}
+ * 
+ * * @see CoreRules#SORT_REMOVE_REDUNDANT */ @Value.Enclosing @@ -49,20 +72,43 @@ protected SortRemoveRedundantRule(final SortRemoveRedundantRule.Config config) { @Override public void onMatch(final RelOptRuleCall call) { final Sort sort = call.rel(0); - if (sort.offset != null || sort.fetch != null) { - // Don't remove sort if it has explicit OFFSET and LIMIT + if (RelOptUtil.isOffset(sort)) { + // Don't remove sort if it has explicit OFFSET return; } // Get the max row count for sort's input RelNode. - final Double maxRowCount = call.getMetadataQuery().getMaxRowCount(sort.getInput()); - // If the max row count is not null and less than or equal to 1, - // then we could remove the sort. - if (maxRowCount != null && maxRowCount <= 1D) { + final Double inputMaxRowCount = call.getMetadataQuery().getMaxRowCount(sort.getInput()); + + // Get the target max row count with sort's semantics. + // If sort is pure order by, the target max row count is 1. + // If sort is pure limit, the target max row count is the limit's fetch. + final Optional targetMaxRowCount = getSortInputSpecificMaxRowCount(sort); + + if (!targetMaxRowCount.isPresent()) { + return; + } + + // If the max row count is not null and less than or equal to targetMaxRowCount, + // then we could remove the redundant sort. + if (inputMaxRowCount != null && inputMaxRowCount <= targetMaxRowCount.get()) { call.transformTo(sort.getInput()); } } + private Optional getSortInputSpecificMaxRowCount(Sort sort) { + // If the sort is pure limit, the specific max row count is limit's fetch. + if (RelOptUtil.isPureLimit(sort)) { + final double limit = + sort.fetch instanceof RexLiteral ? RexLiteral.intValue(sort.fetch) : -1D; + return Optional.of(limit); + } else if (RelOptUtil.isPureOrder(sort)) { + // If the sort is pure order by, the specific max row count is 1. + return Optional.of(1D); + } + return Optional.empty(); + } + /** Rule configuration. */ @Value.Immutable public interface Config extends RelRule.Config { 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 71b80771ce90..2a153e74aa88 100644 --- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java @@ -1261,6 +1261,42 @@ private void checkSemiOrAntiJoinProjectTranspose(JoinRelType type) { .check(); } + /** Test case for + * [CALCITE-6009] + * Add optimization to remove redundant Limit when its input's row number + * is less or equal to Limit's fetch. */ + @Test void testSortRemoveWhenInputValuesMaxRowCntLessOrEqualLimitFetch() { + final String sql = "select * from\n" + + "(VALUES 1,2,3,4,5,6) as t1 limit 10"; + sql(sql) + .withRule(CoreRules.SORT_REMOVE_REDUNDANT) + .check(); + } + + /** Test case for + * [CALCITE-6009] + * Add optimization to remove redundant Limit when its input's row number + * is less or equal to Limit's fetch. */ + @Test void testSortRemoveWhenInputAggregateMaxRowCntLessOrEqualLimitFetch() { + final String sql = "select count(*) as c\n" + + "from sales.emp limit 20"; + sql(sql) + .withRule(CoreRules.SORT_REMOVE_REDUNDANT) + .check(); + } + + /** Test case for + * [CALCITE-6009] + * Add optimization to remove redundant Limit when its input's row number + * is less or equal to Limit's fetch. */ + @Test void testSortRemoveWhenHasOffset() { + final String sql = "select * from\n" + + "(select * from sales.emp limit 10) t limit 20 offset 1"; + sql(sql) + .withRule(CoreRules.SORT_REMOVE_REDUNDANT) + .checkUnchanged(); + } + /** Tests that an {@link EnumerableLimit} and {@link EnumerableSort} are * replaced by an {@link EnumerableLimitSort}, per * [CALCITE-3920] 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 65f8268c2232..291c911d0221 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -14142,6 +14142,61 @@ LogicalSort(sort0=[$0], dir0=[ASC]) LogicalAggregate(group=[{}], C=[COUNT()]) LogicalProject($f0=[0]) LogicalTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +