From 0963738814d3865f566dd4f3b4831b7b364f8506 Mon Sep 17 00:00:00 2001 From: shenlang Date: Wed, 11 Oct 2023 21:43:58 +0800 Subject: [PATCH] [CALCITE-6038] Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1, and there is no 'OFFSET' clause --- .../rel/rules/SortRemoveRedundantRule.java | 39 +++++++++++++------ .../apache/calcite/test/RelOptRulesTest.java | 24 ++++++++++++ .../apache/calcite/test/RelOptRulesTest.xml | 36 +++++++++++++++++ 3 files changed, 87 insertions(+), 12 deletions(-) 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 8dd2e722e29..21dffc228bb 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 @@ -26,11 +26,12 @@ 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} +/** + * 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 + *

If a {@code Sort} is 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: @@ -43,9 +44,19 @@ * select max(totalprice) from orders} * * + *

For example: + *

{@code
+ *  SELECT count(*) FROM orders ORDER BY 1 LIMIT 10 }
+ *  
+ * + *

could be converted to + *

{@code
+ *  SELECT count(*) 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. + * remove the redundant {@code Limit}. * *

For example: *

{@code
@@ -81,8 +92,10 @@ protected SortRemoveRedundantRule(final SortRemoveRedundantRule.Config config) {
     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 'order by x' or 'order by x limit n', the target max row count is 1.
     // If sort is pure limit, the target max row count is the limit's fetch.
+    // If the limit's fetch is 0, we could use CoreRules.SORT_FETCH_ZERO_INSTANCE to deal with it,
+    // so we don't need to deal with it in this rule.
     final Optional targetMaxRowCount = getSortInputSpecificMaxRowCount(sort);
 
     if (!targetMaxRowCount.isPresent()) {
@@ -97,13 +110,15 @@ protected SortRemoveRedundantRule(final SortRemoveRedundantRule.Config config) {
   }
 
   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);
+    if (RelOptUtil.isLimit(sort)) {
+      final double fetch =
+          sort.fetch instanceof RexLiteral ? RexLiteral.intValue(sort.fetch) : 0D;
+      if (fetch == 0D) {
+        return Optional.empty();
+      }
+      // If sort is 'order by x limit n', the target max row count is 1.
+      return RelOptUtil.isOrder(sort) ? Optional.of(1D) : Optional.of(fetch);
     } 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();
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 25ee34b4c8e..af0818d87c2 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -1368,6 +1368,30 @@ private void checkSemiOrAntiJoinProjectTranspose(JoinRelType type) {
         .checkUnchanged();
   }
 
+  /** Test case for
+   * [CALCITE-6038]
+   * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
+   * and there is no 'OFFSET' clause. */
+  @Test void testSortRemoveWhenIsOrderAndLimit() {
+    final String sql = "SELECT count(*) FROM sales.emp ORDER BY 1 LIMIT 10";
+    sql(sql)
+        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
+        .check();
+  }
+
+  /** Test case for
+   * [CALCITE-6038]
+   * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
+   * and there is no 'OFFSET' clause. */
+  @Test void testSortNotRemoveWhenIsOrderAndLimit() {
+    final String sql = "select * from\n"
+        + "(SELECT * FROM sales.emp limit 100)\n"
+        + "ORDER BY 1 LIMIT 10";
+    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 a3656f93740..a25d9722280 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -14100,6 +14100,22 @@ LogicalProject(DEPTNO=[$0], EMPNO=[$2])
     LogicalJoin(condition=[=($0, $9)], joinType=[left])
       LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
       LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    
+  
+  
+    
+      
+    
+    
+      
     
   
@@ -14295,6 +14311,26 @@ LogicalSort(fetch=[10])
       
+    
+  
+  
+    
+      
+    
+    
+      
+    
+    
+