All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.rel.rules.PruneEmptyRules Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.com.liance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.com.hazelcast.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 com.hazelcast.org.apache.calcite.rel.rules;

import com.hazelcast.org.apache.calcite.plan.RelOptRule;
import com.hazelcast.org.apache.calcite.plan.RelOptRuleCall;
import com.hazelcast.org.apache.calcite.plan.RelOptRuleOperand;
import com.hazelcast.org.apache.calcite.plan.RelOptUtil;
import com.hazelcast.org.apache.calcite.plan.RelTraitSet;
import com.hazelcast.org.apache.calcite.plan.hep.HepRelVertex;
import com.hazelcast.org.apache.calcite.plan.volcano.RelSubset;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.SingleRel;
import com.hazelcast.org.apache.calcite.rel.core.Aggregate;
import com.hazelcast.org.apache.calcite.rel.core.Filter;
import com.hazelcast.org.apache.calcite.rel.core.Join;
import com.hazelcast.org.apache.calcite.rel.core.JoinRelType;
import com.hazelcast.org.apache.calcite.rel.core.Project;
import com.hazelcast.org.apache.calcite.rel.core.RelFactories;
import com.hazelcast.org.apache.calcite.rel.core.Sort;
import com.hazelcast.org.apache.calcite.rel.core.Values;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalIntersect;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalMinus;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalUnion;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalValues;
import com.hazelcast.org.apache.calcite.rex.RexDynamicParam;
import com.hazelcast.org.apache.calcite.rex.RexLiteral;
import com.hazelcast.org.apache.calcite.tools.RelBuilder;
import com.hazelcast.org.apache.calcite.tools.RelBuilderFactory;

import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

import static com.hazelcast.org.apache.calcite.plan.RelOptRule.any;
import static com.hazelcast.org.apache.calcite.plan.RelOptRule.none;
import static com.hazelcast.org.apache.calcite.plan.RelOptRule.operand;
import static com.hazelcast.org.apache.calcite.plan.RelOptRule.operandJ;
import static com.hazelcast.org.apache.calcite.plan.RelOptRule.some;
import static com.hazelcast.org.apache.calcite.plan.RelOptRule.unordered;

/**
 * Collection of rules which remove sections of a query plan known never to
 * produce any rows.
 *
 * 

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 com.hazelcast.org.apache.calcite.rel.logical.LogicalUnion}. * *

Examples: * *

    *
  • Union(Rel, Empty, Rel2) becomes Union(Rel, Rel2) *
  • Union(Rel, Empty, Empty) becomes Rel *
  • Union(Empty, Empty) becomes Empty *
*/ 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 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 com.hazelcast.org.apache.calcite.rel.logical.LogicalMinus}. * *

Examples: * *

    *
  • Minus(Rel, Empty, Rel2) becomes Minus(Rel, Rel2) *
  • Minus(Empty, Rel) becomes Empty *
*/ 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 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 com.hazelcast.org.apache.calcite.rel.logical.LogicalIntersect} to * empty if any of its children are empty. * *

Examples: * *

    *
  • Intersect(Rel, Empty, Rel2) becomes Empty *
  • Intersect(Empty, Rel) becomes Empty *
*/ 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 com.hazelcast.org.apache.calcite.rel.logical.LogicalProject} * to empty if its child is empty. * *

Examples: * *

    *
  • Project(Empty) becomes Empty *
*/ public static final RelOptRule PROJECT_INSTANCE = new RemoveEmptySingleRule(Project.class, (Predicate) project -> true, RelFactories.LOGICAL_BUILDER, "PruneEmptyProject"); /** * Rule that converts a {@link com.hazelcast.org.apache.calcite.rel.logical.LogicalFilter} * to empty if its child is empty. * *

Examples: * *

    *
  • Filter(Empty) becomes Empty *
*/ public static final RelOptRule FILTER_INSTANCE = new RemoveEmptySingleRule(Filter.class, "PruneEmptyFilter"); /** * Rule that converts a {@link com.hazelcast.org.apache.calcite.rel.core.Sort} * to empty if its child is empty. * *

Examples: * *

    *
  • Sort(Empty) becomes Empty *
*/ public static final RelOptRule SORT_INSTANCE = new RemoveEmptySingleRule(Sort.class, "PruneEmptySort"); /** * Rule that converts a {@link com.hazelcast.org.apache.calcite.rel.core.Sort} * to empty if it has {@code LIMIT 0}. * *

Examples: * *

    *
  • Sort[fetch=0] becomes Empty *
*/ 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 com.hazelcast.org.apache.calcite.rel.core.Aggregate} * to empty if its child is empty. * *

Examples: * *

    *
  • {@code Aggregate(key: [1, 3], Empty)} → {@code Empty} * *
  • {@code Aggregate(key: [], Empty)} is unchanged, because an aggregate * without a GROUP BY key always returns 1 row, even over empty input *
* * @see AggregateValuesRule */ public static final RelOptRule AGGREGATE_INSTANCE = new RemoveEmptySingleRule(Aggregate.class, (Predicate) Aggregate::isNotGrandTotal, RelFactories.LOGICAL_BUILDER, "PruneEmptyAggregate"); /** * Rule that converts a {@link com.hazelcast.org.apache.calcite.rel.core.Join} * to empty if its left child is empty. * *

Examples: * *

    *
  • Join(Empty, Scan(Dept), INNER) becomes Empty *
  • Join(Empty, Scan(Dept), LEFT) becomes Empty *
  • Join(Empty, Scan(Dept), SEMI) becomes Empty *
  • Join(Empty, Scan(Dept), ANTI) becomes Empty *
*/ 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 com.hazelcast.org.apache.calcite.rel.core.Join} * to empty if its right child is empty. * *

Examples: * *

    *
  • Join(Scan(Emp), Empty, INNER) becomes Empty *
  • Join(Scan(Emp), Empty, RIGHT) becomes Empty *
  • Join(Scan(Emp), Empty, SEMI) becomes Empty *
  • Join(Scan(Emp), Empty, ANTI) becomes Scan(Emp) *
*/ 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 RemoveEmptySingleRule(Class clazz, String description) { this(clazz, (Predicate) singleRel -> true, RelFactories.LOGICAL_BUILDER, description); } /** Creates a RemoveEmptySingleRule. */ public RemoveEmptySingleRule(Class clazz, Predicate 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 RemoveEmptySingleRule(Class clazz, com.hazelcast.com.google.com.hazelcast.com.on.base.Predicate predicate, RelBuilderFactory relBuilderFactory, String description) { this(clazz, (Predicate) 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy