This is an automated email from the ASF dual-hosted git repository.
jhyde 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 79f2f61dcc [CALCITE-6038] Remove 'ORDER BY ... LIMIT n' when input has
at most one row, n >= 1, and there is no 'OFFSET' clause
79f2f61dcc is described below
commit 79f2f61dcca9ab57600d3723b147b3e05d25ecb2
Author: shenlang <[email protected]>
AuthorDate: Wed Oct 11 21:43:58 2023 +0800
[CALCITE-6038] Remove 'ORDER BY ... LIMIT n' when input has at most one
row, n >= 1, and there is no 'OFFSET' clause
This change extends SortRemoveRedundantRule, which was added
in [CALCITE-5994].
Following [CALCITE-5940], which added SortMergeRule, rename
field LIMIT_MREGE to LIMIT_MERGE.
Close apache/calcite#3467
---
.../org/apache/calcite/rel/rules/CoreRules.java | 2 +-
.../apache/calcite/rel/rules/SortMergeRule.java | 2 +-
.../calcite/rel/rules/SortRemoveRedundantRule.java | 117 ++++++++++++++-------
.../org/apache/calcite/test/RelOptRulesTest.java | 62 +++++++++--
.../org/apache/calcite/test/RelOptRulesTest.xml | 65 ++++++++++++
5 files changed, 201 insertions(+), 47 deletions(-)
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 395a04c3b3..77c19d58ab 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
@@ -722,7 +722,7 @@ public class CoreRules {
/** Rule that merge a {@link Sort} representing the Limit semantics and
* another {@link Sort} representing the Limit or TOPN semantics. */
- public static final SortMergeRule LIMIT_MREGE =
+ public static final SortMergeRule LIMIT_MERGE =
SortMergeRule.Config.LIMIT_MERGE.toRule();
/** Rule that removes keys from a {@link Sort}
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SortMergeRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/SortMergeRule.java
index b4a484e333..a69acb9206 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SortMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SortMergeRule.java
@@ -65,7 +65,7 @@ import org.immutables.value.Value;
*
* <p> In the future,we could also extend other sort merge logic in this rule.
*
- * @see CoreRules#LIMIT_MREGE
+ * @see CoreRules#LIMIT_MERGE
*/
@Value.Enclosing
public class SortMergeRule
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 18b3839163..766849a443 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,39 +26,66 @@ import org.immutables.value.Value;
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 maximum row count is less than or equal to specified row count.
+ * All of them are represented by {@link Sort}.
*
- * <p> 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.
+ * <p>If a {@code Sort} is an {@code ORDER BY} with no {@code OFFSET}, and if
+ * input's maximum row count is less than or equal to 1, then the sort is
+ * redundant (because every relation with 1 or fewer rows is sorted), and the
+ * rule removes the redundant sort.
+ *
+ * <p>For example:
*
- * <p> For example:
* <blockquote><pre>{@code
- * select max(totalprice) from orders order by 1}
- * </pre></blockquote>
+ * SELECT max(totalprice)
+ * FROM orders
+ * ORDER BY 1
+ * }</pre></blockquote>
+ *
+ * <p>could be converted to
*
- * <p> could be converted to
* <blockquote><pre>{@code
- * select max(totalprice) from orders}
- * </pre></blockquote>
+ * SELECT max(totalprice)
+ * FROM orders
+ * }</pre></blockquote>
*
- * <p> 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.
+ * <p>For example:
*
- * <p> For example:
* <blockquote><pre>{@code
- * SELECT * FROM (VALUES 1,2,3,4,5,6) AS t1 LIMIT 10}
- * </pre></blockquote>
+ * SELECT count(*)
+ * FROM orders
+ * ORDER BY 1 LIMIT 10
+ * }</pre></blockquote>
+ *
+ * <p>could be converted to
+ *
+ * <blockquote><pre>{@code
+ * SELECT count(*)
+ * FROM orders
+ * }</pre></blockquote>
+ *
+ * <p>If a {@code Sort} is a pure {@code LIMIT} (with no {@code ORDER BY} or
+ * {@code OFFSET}), and its input RelNode's maximum row count is less than or
+ * equal to the limit's fetch, the rule removes the redundant {@code LIMIT}.
+ *
+ * <p>For example:
*
- * <p> The above values max row count is 6 rows, and the limit's fetch is 10,
+ * <blockquote><pre>{@code
+ * SELECT *
+ * FROM (VALUES 1, 2, 3, 4, 5, 6) AS t1
+ * LIMIT 10
+ * }</pre></blockquote>
+ *
+ * <p>The above {@code VALUES} row count is 6 rows, and the limit's fetch is
10,
* so we could remove the redundant sort.
*
- * <p> It could be converted to:
+ * <p>It could be converted to:
+ *
* <blockquote><pre>{@code
- * SELECT * FROM (VALUES 1,2,3,4,5,6) AS t1}
- * </pre></blockquote>
+ * SELECT * FROM (VALUES 1,2,3,4,5,6) AS t1
+ * }</pre></blockquote>
*
* @see CoreRules#SORT_REMOVE_REDUNDANT
*/
@@ -77,34 +104,48 @@ public class SortRemoveRedundantRule
return;
}
- // Get the max row count for sort's input RelNode.
- final Double inputMaxRowCount =
call.getMetadataQuery().getMaxRowCount(sort.getInput());
+ // Get the maximum row count for sort's input RelNode.
+ 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<Double> targetMaxRowCount =
getSortInputSpecificMaxRowCount(sort);
+ // Get the target threshold with sort's semantics.
+ // If sort is 'order by x' or 'order by x limit n', target threshold is 1.
+ // If sort is pure limit, the target threshold 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<Integer> rowCountThreshold = getRowCountThreshold(sort);
- if (!targetMaxRowCount.isPresent()) {
+ if (!rowCountThreshold.isPresent()) {
return;
}
- // If the max row count is not null and less than or equal to
targetMaxRowCount,
+ // If the threshold is not null and less than or equal to
targetMaxRowCount,
// then we could remove the redundant sort.
- if (inputMaxRowCount != null && inputMaxRowCount <=
targetMaxRowCount.get()) {
+ if (inputMaxRowCount != null && inputMaxRowCount <=
rowCountThreshold.get()) {
call.transformTo(sort.getInput());
}
}
- private static Optional<Double> 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);
+ private Optional<Integer> getRowCountThreshold(Sort sort) {
+ if (RelOptUtil.isLimit(sort)) {
+ final int fetch =
+ sort.fetch instanceof RexLiteral ? RexLiteral.intValue(sort.fetch) :
0;
+
+ // We don't need to deal with fetch is 0.
+ if (fetch == 0) {
+ return Optional.empty();
+ }
+
+ // If sort is 'order by x limit n', the target threshold is 1.
+ if (RelOptUtil.isOrder(sort)) {
+ return Optional.of(1);
+ }
+
+ // If sort is 'limit n', the target threshold is the limit's fetch.
+ return 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.of(1);
}
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 25ee34b4c8..789adc9377 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,54 @@ class RelOptRulesTest extends RelOptTestBase {
.checkUnchanged();
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
+ * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
+ * and there is no 'OFFSET' clause</a>. */
+ @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
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
+ * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
+ * and there is no 'OFFSET' clause</a>. */
+ @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();
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
+ * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
+ * and there is no 'OFFSET' clause</a>. */
+ @Test void testSortNotRemoveWhenLimitFetchIsZeroHasOrder() {
+ final String sql = "select * from\n"
+ + "(SELECT * FROM sales.emp limit 1)\n"
+ + "ORDER BY 1 LIMIT 0";
+ sql(sql)
+ .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
+ .checkUnchanged();
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
+ * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
+ * and there is no 'OFFSET' clause</a>. */
+ @Test void testSortNotRemoveWhenLimitFetchIsZeroWithoutOrder() {
+ final String sql = "SELECT count(*) FROM sales.emp LIMIT 0";
+ sql(sql)
+ .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
+ .checkUnchanged();
+ }
+
/** Tests that an {@link EnumerableLimit} and {@link EnumerableSort} are
* replaced by an {@link EnumerableLimitSort}, per
* <a
href="https://issues.apache.org/jira/browse/CALCITE-3920">[CALCITE-3920]
@@ -1417,7 +1465,7 @@ class RelOptRulesTest extends RelOptTestBase {
+ "limit 10";
sql(sql)
.withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
- .withRule(CoreRules.LIMIT_MREGE, CoreRules.PROJECT_REMOVE)
+ .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_REMOVE)
.check();
}
@@ -1430,7 +1478,7 @@ class RelOptRulesTest extends RelOptTestBase {
+ "limit 10";
sql(sql)
.withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
- .withRule(CoreRules.LIMIT_MREGE, CoreRules.PROJECT_MERGE)
+ .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
.check();
}
@@ -1443,7 +1491,7 @@ class RelOptRulesTest extends RelOptTestBase {
+ "limit 10000";
sql(sql)
.withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
- .withRule(CoreRules.LIMIT_MREGE, CoreRules.PROJECT_MERGE)
+ .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
.check();
}
@@ -1456,7 +1504,7 @@ class RelOptRulesTest extends RelOptTestBase {
+ "order by deptno desc";
sql(sql)
.withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
- .withRule(CoreRules.LIMIT_MREGE, CoreRules.PROJECT_REMOVE)
+ .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_REMOVE)
.check();
}
@@ -1469,7 +1517,7 @@ class RelOptRulesTest extends RelOptTestBase {
+ "order by deptno limit 10000";
sql(sql)
.withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
- .withRule(CoreRules.LIMIT_MREGE, CoreRules.PROJECT_MERGE)
+ .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
.check();
}
@@ -1482,7 +1530,7 @@ class RelOptRulesTest extends RelOptTestBase {
+ "order by deptno limit 10";
sql(sql)
.withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
- .withRule(CoreRules.LIMIT_MREGE, CoreRules.PROJECT_MERGE)
+ .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
.check();
}
@@ -1495,7 +1543,7 @@ class RelOptRulesTest extends RelOptTestBase {
+ "limit 10";
sql(sql)
.withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
- .withRule(CoreRules.LIMIT_MREGE)
+ .withRule(CoreRules.LIMIT_MERGE)
.checkUnchanged();
}
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 a3656f9374..90a846762e 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,51 @@ LogicalProject(DEPTNO=[$0], EMPNO=[$2])
LogicalJoin(condition=[=($0, $9)], joinType=[left])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testSortNotRemoveWhenIsOrderAndLimit">
+ <Resource name="sql">
+ <![CDATA[select * from
+(SELECT * FROM sales.emp limit 100)
+ORDER BY 1 LIMIT 10]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalSort(sort0=[$0], dir0=[ASC], fetch=[10])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalSort(fetch=[100])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testSortNotRemoveWhenLimitFetchIsZeroHasOrder">
+ <Resource name="sql">
+ <![CDATA[select * from
+(SELECT * FROM sales.emp limit 1)
+ORDER BY 1 LIMIT 0]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalSort(sort0=[$0], dir0=[ASC], fetch=[0])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalSort(fetch=[1])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testSortNotRemoveWhenLimitFetchIsZeroWithoutOrder">
+ <Resource name="sql">
+ <![CDATA[SELECT count(*) FROM sales.emp LIMIT 0]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalSort(fetch=[0])
+ LogicalAggregate(group=[{}], EXPR$0=[COUNT()])
+ LogicalProject($f0=[0])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
@@ -14295,6 +14340,26 @@ LogicalSort(fetch=[10])
<![CDATA[
LogicalProject(T1=[$0])
LogicalValues(tuples=[[{ 1 }, { 2 }, { 3 }, { 4 }, { 5 }, { 6 }]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testSortRemoveWhenIsOrderAndLimit">
+ <Resource name="sql">
+ <![CDATA[SELECT count(*) FROM sales.emp ORDER BY 1 LIMIT 10]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalSort(sort0=[$0], dir0=[ASC], fetch=[10])
+ LogicalAggregate(group=[{}], EXPR$0=[COUNT()])
+ LogicalProject($f0=[0])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[COUNT()])
+ LogicalProject($f0=[0])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>