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 3db4dd1ebf [CALCITE-5117]  Optimize the EXISTS sub-query by Metadata 
RowCount
3db4dd1ebf is described below

commit 3db4dd1ebf04abaf74d29464ae311a598ae929f7
Author: nobigo <[email protected]>
AuthorDate: Wed Apr 27 09:35:40 2022 +0800

    [CALCITE-5117]  Optimize the EXISTS sub-query by Metadata RowCount
---
 .../calcite/rel/rules/SubQueryRemoveRule.java      | 12 +++-
 .../org/apache/calcite/test/RelOptRulesTest.java   | 17 +++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 57 ++++++++++++++++
 core/src/test/resources/sql/sub-query.iq           | 79 ++++++++++++++++++++++
 4 files changed, 164 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
index ba2eeb592e..b387724153 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
@@ -450,8 +450,18 @@ public class SubQueryRemoveRule
    */
   private static RexNode rewriteExists(RexSubQuery e, Set<CorrelationId> 
variablesSet,
       RelOptUtil.Logic logic, RelBuilder builder) {
+    // If the sub-query is guaranteed to produce at least one row, just return
+    // TRUE.
+    final RelMetadataQuery mq = e.rel.getCluster().getMetadataQuery();
+    final Double minRowCount = mq.getMinRowCount(e.rel);
+    if (minRowCount != null && minRowCount >= 1D) {
+      return builder.literal(true);
+    }
+    final Double maxRowCount = mq.getMaxRowCount(e.rel);
+    if (maxRowCount != null && maxRowCount < 1D) {
+      return builder.literal(false);
+    }
     builder.push(e.rel);
-
     builder.project(builder.alias(builder.literal(true), "i"));
     switch (logic) {
     case 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 ad714d26d3..1431407c89 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -3333,6 +3333,23 @@ class RelOptRulesTest extends RelOptTestBase {
         .check();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-5117";>[CALCITE-5117]
+   * Optimize the EXISTS sub-query by Metadata RowCount</a>. */
+  @Test void testExistsWithAtLeastOneRowSubQuery() {
+    final String sql = "select * from dept as d\n"
+        + "where EXISTS (\n"
+        + "  select count(*) from emp e where d.deptno = e.deptno)";
+    sql(sql).withSubQueryRules().check();
+  }
+
+  @Test void testExistsWithNoRowSubQuery() {
+    final String sql = "select * from dept as d\n"
+        + "where NOT EXISTS (\n"
+        + "  select count(*) from emp e having false)";
+    sql(sql).withSubQueryRules().check();
+  }
+
   /** Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4848";>[CALCITE-4848]
    * Adding a HAVING condition to a query with a dynamic parameter makes the 
result always empty
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 e2774111bb..c30ab69e9a 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -3097,6 +3097,63 @@ LogicalSortExchange(distribution=[hash[1]], 
collation=[[1]])
     LogicalExchange(distribution=[single])
       LogicalFilter(condition=[=($0, 10)])
         LogicalTableScan(table=[[scott, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testExistsWithAtLeastOneRowSubQuery">
+    <Resource name="sql">
+      <![CDATA[select * from dept as d
+where EXISTS(select count(*) from emp e where d.deptno = e.deptno)]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalFilter(condition=[EXISTS({
+LogicalAggregate(group=[{}], EXPR$0=[COUNT()])
+  LogicalProject($f0=[0])
+    LogicalFilter(condition=[=($cor0.DEPTNO, $7)])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+})], variablesSet=[[$cor0]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planMid">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testExistsWithNoRowSubQuery">
+    <Resource name="sql">
+      <![CDATA[select * from dept as d
+where NOT EXISTS(select count(*) from emp e having false)]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalFilter(condition=[NOT(EXISTS({
+LogicalValues(tuples=[[]])
+}))])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planMid">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index bbad2b6f2b..2d4936b696 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -3383,4 +3383,83 @@ EnumerableCalc(expr#0..12=[{inputs}], expr#13=[IS NOT 
TRUE($t12)], expr#14=[IS N
         EnumerableValues(tuples=[[{ 7369, 20 }, { 7499, 30 }]])
 !plan
 
+# [CALCITE-5117] Optimize the EXISTS sub-query by Metadata RowCount
+
+# Test case about sub-query is guaranteed to produce at least one row
+select *
+from dept as d
+where EXISTS (select count(*) from emp e where d.deptno = e.deptno);
++--------+------------+----------+
+| DEPTNO | DNAME      | LOC      |
++--------+------------+----------+
+|     10 | ACCOUNTING | NEW YORK |
+|     20 | RESEARCH   | DALLAS   |
+|     30 | SALES      | CHICAGO  |
+|     40 | OPERATIONS | BOSTON   |
++--------+------------+----------+
+(4 rows)
+
+!ok
+
+EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# As above, but the filter condition always false
+select *
+from dept as d
+where EXISTS (select count(*) from emp e where d.deptno = e.deptno and 1 = 2);
++--------+------------+----------+
+| DEPTNO | DNAME      | LOC      |
++--------+------------+----------+
+|     10 | ACCOUNTING | NEW YORK |
+|     20 | RESEARCH   | DALLAS   |
+|     30 | SALES      | CHICAGO  |
+|     40 | OPERATIONS | BOSTON   |
++--------+------------+----------+
+(4 rows)
+
+!ok
+
+EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+
+# As above, but the Sum aggregation function
+select *
+from dept as d
+where EXISTS (select sum(1) from emp e where d.deptno = e.deptno and 1 = 2);
++--------+------------+----------+
+| DEPTNO | DNAME      | LOC      |
++--------+------------+----------+
+|     10 | ACCOUNTING | NEW YORK |
+|     20 | RESEARCH   | DALLAS   |
+|     30 | SALES      | CHICAGO  |
+|     40 | OPERATIONS | BOSTON   |
++--------+------------+----------+
+(4 rows)
+
+!ok
+
+EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test case about sub-query is guaranteed to produce no row
+select *
+from dept as d
+where NOT EXISTS (select count(*) from emp e having false);
++--------+------------+----------+
+| DEPTNO | DNAME      | LOC      |
++--------+------------+----------+
+|     10 | ACCOUNTING | NEW YORK |
+|     20 | RESEARCH   | DALLAS   |
+|     30 | SALES      | CHICAGO  |
+|     40 | OPERATIONS | BOSTON   |
++--------+------------+----------+
+(4 rows)
+
+!ok
+
+EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
 # End sub-query.iq

Reply via email to