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;
}
}