blob: 88d408dcfe9e1fbbf413a6f32b7f1c9330ef426e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.rel.rules;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptRuleOperand;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.plan.SubstitutionRule;
import org.apache.calcite.plan.hep.HepRelVertex;
import org.apache.calcite.plan.volcano.RelSubset;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.core.Aggregate;
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.core.RelFactories;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.core.Values;
import org.apache.calcite.rel.logical.LogicalIntersect;
import org.apache.calcite.rel.logical.LogicalMinus;
import org.apache.calcite.rel.logical.LogicalUnion;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rex.RexDynamicParam;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import static org.apache.calcite.plan.RelOptRule.any;
import static org.apache.calcite.plan.RelOptRule.none;
import static org.apache.calcite.plan.RelOptRule.operand;
import static org.apache.calcite.plan.RelOptRule.operandJ;
import static org.apache.calcite.plan.RelOptRule.some;
import static org.apache.calcite.plan.RelOptRule.unordered;
/**
* Collection of rules which remove sections of a query plan known never to
* produce any rows.
*
* <p>Conventionally, the way to represent an empty relational expression is
* with a {@link Values} that has no tuples.
*
* @see LogicalValues#createEmpty
*/
public abstract class PruneEmptyRules {
//~ Static fields/initializers ---------------------------------------------
/**
* Abstract prune empty rule that implements SubstitutionRule interface.
*/
protected abstract static class PruneEmptyRule extends RelOptRule
implements SubstitutionRule {
public PruneEmptyRule(final RelOptRuleOperand operand,
final String description) {
super(operand, description);
}
public PruneEmptyRule(final RelOptRuleOperand operand,
final RelBuilderFactory relBuilderFactory, final String description) {
super(operand, relBuilderFactory, description);
}
@Override public boolean autoPruneOld() {
return true;
}
}
/**
* Rule that removes empty children of a
* {@link org.apache.calcite.rel.logical.LogicalUnion}.
*
* <p>Examples:
*
* <ul>
* <li>Union(Rel, Empty, Rel2) becomes Union(Rel, Rel2)
* <li>Union(Rel, Empty, Empty) becomes Rel
* <li>Union(Empty, Empty) becomes Empty
* </ul>
*/
public static final RelOptRule UNION_INSTANCE =
new PruneEmptyRule(
operand(LogicalUnion.class,
unordered(operandJ(Values.class, null, Values::isEmpty, none()))),
"Union") {
public void onMatch(RelOptRuleCall call) {
final LogicalUnion union = call.rel(0);
final List<RelNode> inputs = union.getInputs();
assert inputs != null;
final RelBuilder builder = call.builder();
int nonEmptyInputs = 0;
for (RelNode input : inputs) {
if (!isEmpty(input)) {
builder.push(input);
nonEmptyInputs++;
}
}
assert nonEmptyInputs < inputs.size()
: "planner promised us at least one Empty child: " + RelOptUtil.toString(union);
if (nonEmptyInputs == 0) {
builder.push(union).empty();
} else {
builder.union(union.all, nonEmptyInputs);
builder.convert(union.getRowType(), true);
}
call.transformTo(builder.build());
}
};
/**
* Rule that removes empty children of a
* {@link org.apache.calcite.rel.logical.LogicalMinus}.
*
* <p>Examples:
*
* <ul>
* <li>Minus(Rel, Empty, Rel2) becomes Minus(Rel, Rel2)
* <li>Minus(Empty, Rel) becomes Empty
* </ul>
*/
public static final RelOptRule MINUS_INSTANCE =
new PruneEmptyRule(
operand(LogicalMinus.class,
unordered(
operandJ(Values.class, null, Values::isEmpty, none()))),
"Minus") {
public void onMatch(RelOptRuleCall call) {
final LogicalMinus minus = call.rel(0);
final List<RelNode> inputs = minus.getInputs();
assert inputs != null;
int nonEmptyInputs = 0;
final RelBuilder builder = call.builder();
for (RelNode input : inputs) {
if (!isEmpty(input)) {
builder.push(input);
nonEmptyInputs++;
} else if (nonEmptyInputs == 0) {
// If the first input of Minus is empty, the whole thing is
// empty.
break;
}
}
assert nonEmptyInputs < inputs.size()
: "planner promised us at least one Empty child: " + RelOptUtil.toString(minus);
if (nonEmptyInputs == 0) {
builder.push(minus).empty();
} else {
builder.minus(minus.all, nonEmptyInputs);
builder.convert(minus.getRowType(), true);
}
call.transformTo(builder.build());
}
};
/**
* Rule that converts a
* {@link org.apache.calcite.rel.logical.LogicalIntersect} to
* empty if any of its children are empty.
*
* <p>Examples:
*
* <ul>
* <li>Intersect(Rel, Empty, Rel2) becomes Empty
* <li>Intersect(Empty, Rel) becomes Empty
* </ul>
*/
public static final RelOptRule INTERSECT_INSTANCE =
new PruneEmptyRule(
operand(LogicalIntersect.class,
unordered(
operandJ(Values.class, null, Values::isEmpty, none()))),
"Intersect") {
public void onMatch(RelOptRuleCall call) {
LogicalIntersect intersect = call.rel(0);
final RelBuilder builder = call.builder();
builder.push(intersect).empty();
call.transformTo(builder.build());
}
};
private static boolean isEmpty(RelNode node) {
if (node instanceof Values) {
return ((Values) node).getTuples().isEmpty();
}
if (node instanceof HepRelVertex) {
return isEmpty(((HepRelVertex) node).getCurrentRel());
}
// Note: relation input might be a RelSubset, so we just iterate over the relations
// in order to check if the subset is equivalent to an empty relation.
if (!(node instanceof RelSubset)) {
return false;
}
RelSubset subset = (RelSubset) node;
for (RelNode rel : subset.getRels()) {
if (isEmpty(rel)) {
return true;
}
}
return false;
}
/**
* Rule that converts a {@link org.apache.calcite.rel.logical.LogicalProject}
* to empty if its child is empty.
*
* <p>Examples:
*
* <ul>
* <li>Project(Empty) becomes Empty
* </ul>
*/
public static final RelOptRule PROJECT_INSTANCE =
new RemoveEmptySingleRule(Project.class,
(Predicate<Project>) project -> true, RelFactories.LOGICAL_BUILDER,
"PruneEmptyProject");
/**
* Rule that converts a {@link org.apache.calcite.rel.logical.LogicalFilter}
* to empty if its child is empty.
*
* <p>Examples:
*
* <ul>
* <li>Filter(Empty) becomes Empty
* </ul>
*/
public static final RelOptRule FILTER_INSTANCE =
new RemoveEmptySingleRule(Filter.class, "PruneEmptyFilter");
/**
* Rule that converts a {@link org.apache.calcite.rel.core.Sort}
* to empty if its child is empty.
*
* <p>Examples:
*
* <ul>
* <li>Sort(Empty) becomes Empty
* </ul>
*/
public static final RelOptRule SORT_INSTANCE =
new RemoveEmptySingleRule(Sort.class, "PruneEmptySort");
/**
* Rule that converts a {@link org.apache.calcite.rel.core.Sort}
* to empty if it has {@code LIMIT 0}.
*
* <p>Examples:
*
* <ul>
* <li>Sort[fetch=0] becomes Empty
* </ul>
*/
public static final RelOptRule SORT_FETCH_ZERO_INSTANCE =
new PruneEmptyRule(
operand(Sort.class, any()), "PruneSortLimit0") {
@Override public void onMatch(RelOptRuleCall call) {
Sort sort = call.rel(0);
if (sort.fetch != null
&& !(sort.fetch instanceof RexDynamicParam)
&& RexLiteral.intValue(sort.fetch) == 0) {
RelNode emptyValues = call.builder().push(sort).empty().build();
RelTraitSet traits = sort.getTraitSet();
// propagate all traits (except convention) from the original sort into the empty values
if (emptyValues.getConvention() != null) {
traits = traits.replace(emptyValues.getConvention());
}
emptyValues = emptyValues.copy(traits, Collections.emptyList());
call.transformTo(emptyValues);
}
}
};
/**
* Rule that converts an {@link org.apache.calcite.rel.core.Aggregate}
* to empty if its child is empty.
*
* <p>Examples:
*
* <ul>
* <li>{@code Aggregate(key: [1, 3], Empty)} &rarr; {@code Empty}
*
* <li>{@code Aggregate(key: [], Empty)} is unchanged, because an aggregate
* without a GROUP BY key always returns 1 row, even over empty input
* </ul>
*
* @see AggregateValuesRule
*/
public static final RelOptRule AGGREGATE_INSTANCE =
new RemoveEmptySingleRule(Aggregate.class,
(Predicate<Aggregate>) Aggregate::isNotGrandTotal,
RelFactories.LOGICAL_BUILDER, "PruneEmptyAggregate");
/**
* Rule that converts a {@link org.apache.calcite.rel.core.Join}
* to empty if its left child is empty.
*
* <p>Examples:
*
* <ul>
* <li>Join(Empty, Scan(Dept), INNER) becomes Empty
* <li>Join(Empty, Scan(Dept), LEFT) becomes Empty
* <li>Join(Empty, Scan(Dept), SEMI) becomes Empty
* <li>Join(Empty, Scan(Dept), ANTI) becomes Empty
* </ul>
*/
public static final RelOptRule JOIN_LEFT_INSTANCE =
new PruneEmptyRule(
operand(Join.class,
some(
operandJ(Values.class, null, Values::isEmpty, none()),
operand(RelNode.class, any()))),
"PruneEmptyJoin(left)") {
@Override public void onMatch(RelOptRuleCall call) {
Join join = call.rel(0);
if (join.getJoinType().generatesNullsOnLeft()) {
// "select * from emp right join dept" is not necessarily empty if
// emp is empty
return;
}
call.transformTo(call.builder().push(join).empty().build());
}
};
/**
* Rule that converts a {@link org.apache.calcite.rel.core.Join}
* to empty if its right child is empty.
*
* <p>Examples:
*
* <ul>
* <li>Join(Scan(Emp), Empty, INNER) becomes Empty
* <li>Join(Scan(Emp), Empty, RIGHT) becomes Empty
* <li>Join(Scan(Emp), Empty, SEMI) becomes Empty
* <li>Join(Scan(Emp), Empty, ANTI) becomes Scan(Emp)
* </ul>
*/
public static final RelOptRule JOIN_RIGHT_INSTANCE =
new PruneEmptyRule(
operand(Join.class,
some(
operand(RelNode.class, any()),
operandJ(Values.class, null, Values::isEmpty, none()))),
"PruneEmptyJoin(right)") {
@Override public void onMatch(RelOptRuleCall call) {
Join join = call.rel(0);
if (join.getJoinType().generatesNullsOnRight()) {
// "select * from emp left join dept" is not necessarily empty if
// dept is empty
return;
}
if (join.getJoinType() == JoinRelType.ANTI) {
// In case of anti join: Join(X, Empty, ANTI) becomes X
call.transformTo(join.getLeft());
return;
}
call.transformTo(call.builder().push(join).empty().build());
}
};
/** Planner rule that converts a single-rel (e.g. project, sort, aggregate or
* filter) on top of the empty relational expression into empty. */
public static class RemoveEmptySingleRule extends PruneEmptyRule {
/** Creates a simple RemoveEmptySingleRule. */
public <R extends SingleRel> RemoveEmptySingleRule(Class<R> clazz,
String description) {
this(clazz, (Predicate<R>) singleRel -> true, RelFactories.LOGICAL_BUILDER,
description);
}
/** Creates a RemoveEmptySingleRule. */
public <R extends SingleRel> RemoveEmptySingleRule(Class<R> clazz,
Predicate<R> predicate, RelBuilderFactory relBuilderFactory,
String description) {
super(
operandJ(clazz, null, predicate,
operandJ(Values.class, null, Values::isEmpty, none())),
relBuilderFactory, description);
}
@SuppressWarnings("Guava")
@Deprecated // to be removed before 2.0
public <R extends SingleRel> RemoveEmptySingleRule(Class<R> clazz,
com.google.common.base.Predicate<R> predicate,
RelBuilderFactory relBuilderFactory, String description) {
this(clazz, (Predicate<R>) predicate::apply, relBuilderFactory,
description);
}
public void onMatch(RelOptRuleCall call) {
SingleRel singleRel = call.rel(0);
RelNode emptyValues = call.builder().push(singleRel).empty().build();
RelTraitSet traits = singleRel.getTraitSet();
// propagate all traits (except convention) from the original singleRel into the empty values
if (emptyValues.getConvention() != null) {
traits = traits.replace(emptyValues.getConvention());
}
emptyValues = emptyValues.copy(traits, Collections.emptyList());
call.transformTo(emptyValues);
}
}
}