Skip to content

Commit

Permalink
[CALCITE-6665] Add isEmpty metadata to check if a relational expressi…
Browse files Browse the repository at this point in the history
…on returns no rows
  • Loading branch information
NobiGo authored and ILuffZhe committed Dec 23, 2024
1 parent e621c33 commit 575fc1a
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 21 deletions.
12 changes: 12 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,18 @@ public static boolean areColumnsDefinitelyUnique(RelMetadataQuery mq,
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public static RelMetadataQuery instance() {
* statistic.
*
* @param rel the relational expression
* @return max row count
* @return min row count
*/
public @Nullable Double getMinRowCount(RelNode rel) {
for (;;) {
Expand All @@ -305,6 +305,26 @@ public static RelMetadataQuery instance() {
}
}

/**
* 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()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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;
Expand Down Expand Up @@ -645,8 +646,7 @@ public interface ZeroMaxRowsRuleConfig extends PruneEmptyRule.Config {
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);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;
Expand Down Expand Up @@ -170,11 +171,10 @@ private static RexNode rewriteCollection(RexSubQuery e,
*/
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
Expand Down Expand Up @@ -462,15 +462,13 @@ private static RexNode rewriteSome(RexSubQuery e, Set<CorrelationId> variablesSe
*/
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);
Expand Down Expand Up @@ -562,11 +560,10 @@ private static RexNode rewriteUnique(RexSubQuery e, RelBuilder builder) {
*/
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
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;
Expand Down Expand Up @@ -1366,12 +1367,11 @@ private void substituteSubQuery(Blackboard bb, SubQuery subQuery) {
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;
}
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/java/org/apache/calcite/tools/RelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
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;
Expand Down Expand Up @@ -2678,10 +2679,9 @@ private boolean alreadyUnique(List<AggCallPlus> aggCallList,
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;
}
}
Expand Down

0 comments on commit 575fc1a

Please sign in to comment.