This is an automated email from the ASF dual-hosted git repository.

zhehu 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 575fc1a583 [CALCITE-6665] Add isEmpty metadata to check if a 
relational expression returns no rows
575fc1a583 is described below

commit 575fc1a583b3f5b5febdee40da4c5cb46c9022b1
Author: Xiong Duan <[email protected]>
AuthorDate: Thu Dec 19 12:21:29 2024 +0800

    [CALCITE-6665] Add isEmpty metadata to check if a relational expression 
returns no rows
---
 .../org/apache/calcite/rel/metadata/RelMdUtil.java | 12 ++++++++++++
 .../calcite/rel/metadata/RelMetadataQuery.java     | 22 +++++++++++++++++++++-
 .../apache/calcite/rel/rules/PruneEmptyRules.java  |  4 ++--
 .../calcite/rel/rules/SubQueryRemoveRule.java      | 19 ++++++++-----------
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  6 +++---
 .../java/org/apache/calcite/tools/RelBuilder.java  |  8 ++++----
 6 files changed, 50 insertions(+), 21 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
index 48d72bbfe7..29236bf8bc 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
@@ -210,6 +210,18 @@ public class RelMdUtil {
     return b != null && b;
   }
 
+  public static boolean isRelDefinitelyEmpty(RelMetadataQuery mq,
+      RelNode rel) {
+    Boolean b = mq.isEmpty(rel);
+    return b != null && b;
+  }
+
+  public static boolean isRelDefinitelyNotEmpty(RelMetadataQuery mq,
+      RelNode rel) {
+    Boolean b = mq.isEmpty(rel);
+    return b != null && !b;
+  }
+
   public static @Nullable Boolean areColumnsUnique(RelMetadataQuery mq, 
RelNode rel,
       List<RexInputRef> columnRefs) {
     ImmutableBitSet.Builder colMask = ImmutableBitSet.builder();
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
index b2ea8c23da..bf8e09b2b2 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
@@ -293,7 +293,7 @@ public class RelMetadataQuery extends RelMetadataQueryBase {
    * statistic.
    *
    * @param rel the relational expression
-   * @return max row count
+   * @return min row count
    */
   public @Nullable Double getMinRowCount(RelNode rel) {
     for (;;) {
@@ -305,6 +305,26 @@ public class RelMetadataQuery extends RelMetadataQueryBase 
{
     }
   }
 
+  /**
+   * Returns whether the return rows of a given relational expression are 
empty.
+   *
+   * @param relNode the relational expression
+   * @return true or false depending on whether the return rows are empty, or
+   * null if not enough information is available to make that determination
+   */
+  public @Nullable Boolean isEmpty(RelNode relNode) {
+    Double minRowCount = getMinRowCount(relNode);
+    if (minRowCount != null && minRowCount > 0D) {
+      return Boolean.FALSE;
+    }
+    Double maxRowCount = getMaxRowCount(relNode);
+    if (maxRowCount != null && maxRowCount <= 0D) {
+      return Boolean.TRUE;
+    }
+    return null;
+  }
+
+
   /**
    * Returns the
    * {@link BuiltInMetadata.CumulativeCost#getCumulativeCost()}
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 a201f8f870..58a5caf02a 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
@@ -38,6 +38,7 @@ import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexDynamicParam;
 import org.apache.calcite.rex.RexLiteral;
@@ -645,8 +646,7 @@ public abstract class PruneEmptyRules {
       return new RemoveEmptySingleRule(this) {
         @Override public boolean matches(RelOptRuleCall call) {
           RelNode node = call.rel(0);
-          Double maxRowCount = call.getMetadataQuery().getMaxRowCount(node);
-          return maxRowCount != null && maxRowCount == 0.0;
+          return RelMdUtil.isRelDefinitelyEmpty(call.getMetadataQuery(), node);
         }
       };
     }
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 3e9473f0b6..4aa31a65f4 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
@@ -27,6 +27,7 @@ import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.LogicVisitor;
 import org.apache.calcite.rex.RexCorrelVariable;
@@ -170,11 +171,10 @@ public class SubQueryRemoveRule
    */
   private static RexNode rewriteSome(RexSubQuery e, Set<CorrelationId> 
variablesSet,
       RelBuilder builder, int subQueryIndex) {
-    // If the sub-query is guaranteed to return 0 row, just return
+    // If the sub-query is guaranteed empty, just return
     // FALSE.
     final RelMetadataQuery mq = e.rel.getCluster().getMetadataQuery();
-    final Double maxRowCount = mq.getMaxRowCount(e.rel);
-    if (maxRowCount != null && maxRowCount <= 0D) {
+    if (RelMdUtil.isRelDefinitelyEmpty(mq, e.rel)) {
       return builder.getRexBuilder().makeLiteral(Boolean.FALSE, e.getType(), 
true);
     }
     // Most general case, where the left and right keys might have nulls, and
@@ -462,15 +462,13 @@ 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
+    // If the sub-query is guaranteed never empty, just return
     // TRUE.
     final RelMetadataQuery mq = e.rel.getCluster().getMetadataQuery();
-    final Double minRowCount = mq.getMinRowCount(e.rel);
-    if (minRowCount != null && minRowCount > 0D) {
+    if (RelMdUtil.isRelDefinitelyNotEmpty(mq, e.rel)) {
       return builder.literal(true);
     }
-    final Double maxRowCount = mq.getMaxRowCount(e.rel);
-    if (maxRowCount != null && maxRowCount <= 0D) {
+    if (RelMdUtil.isRelDefinitelyEmpty(mq, e.rel)) {
       return builder.literal(false);
     }
     builder.push(e.rel);
@@ -562,11 +560,10 @@ public class SubQueryRemoveRule
    */
   private static RexNode rewriteIn(RexSubQuery e, Set<CorrelationId> 
variablesSet,
       RelOptUtil.Logic logic, RelBuilder builder, int offset, int 
subQueryIndex) {
-    // If the sub-query is guaranteed to return 0 row, just return
+    // If the sub-query is guaranteed empty, just return
     // FALSE.
     final RelMetadataQuery mq = e.rel.getCluster().getMetadataQuery();
-    final Double maxRowCount = mq.getMaxRowCount(e.rel);
-    if (maxRowCount != null && maxRowCount <= 0D) {
+    if (RelMdUtil.isRelDefinitelyEmpty(mq, e.rel)) {
       return builder.getRexBuilder().makeLiteral(Boolean.FALSE, e.getType(), 
true);
     }
     // Most general case, where the left and right keys might have nulls, and
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index c22d17328c..e4fe01b2fc 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -68,6 +68,7 @@ import org.apache.calcite.rel.logical.LogicalTableModify;
 import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.logical.LogicalValues;
 import org.apache.calcite.rel.metadata.RelColumnMapping;
+import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.stream.Delta;
 import org.apache.calcite.rel.stream.LogicalDelta;
@@ -1366,12 +1367,11 @@ public class SqlToRelConverter {
       final Blackboard seekBb = createBlackboard(seekScope, null, false);
       final RelNode seekRel = convertQueryOrInList(seekBb, query, null);
       requireNonNull(seekRel, () -> "seekRel is null for query " + query);
-      // An EXIST sub-query whose inner child has at least 1 tuple
+      // An EXIST sub-query whose inner child guaranteed never empty
       // (e.g. an Aggregate with no grouping columns or non-empty Values
       // node) should be simplified to a Boolean constant expression.
       final RelMetadataQuery mq = seekRel.getCluster().getMetadataQuery();
-      final Double minRowCount = mq.getMinRowCount(seekRel);
-      if (minRowCount != null && minRowCount >= 1D) {
+      if (RelMdUtil.isRelDefinitelyNotEmpty(mq, seekRel)) {
         subQuery.expr = rexBuilder.makeLiteral(true);
         return;
       }
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java 
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 31c8e27d43..98f966b711 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -63,6 +63,7 @@ import org.apache.calcite.rel.logical.LogicalAsofJoin;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.metadata.RelColumnMapping;
+import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.rules.AggregateRemoveRule;
 import org.apache.calcite.rel.type.RelDataType;
@@ -2678,10 +2679,9 @@ public class RelBuilder {
       List<RexNode> extraNodes) {
     final RelMetadataQuery mq = peek().getCluster().getMetadataQuery();
     if (aggCallList.isEmpty() && groupSet.isEmpty()) {
-      final Double minRowCount = mq.getMinRowCount(peek());
-      if (minRowCount == null || minRowCount < 1d) {
-        // We can't remove "GROUP BY ()" if there's a chance the rel could be
-        // empty.
+      // We can't remove "GROUP BY ()" if there's a chance the rel could be
+      // empty.
+      if (RelMdUtil.isRelDefinitelyEmpty(mq, peek())) {
         return false;
       }
     }

Reply via email to