/**
* 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.hadoop.hive.ql.optimizer.calcite.rules;
import java.util.List;
import org.apache.calcite.plan.RelOptPredicateList;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.rules.ReduceExpressionsRule;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.hadoop.hive.ql.optimizer.calcite.HiveRelFactories;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveFilter;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveJoin;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
/**
* Collection of planner rules that apply various simplifying transformations on
* RexNode trees. Currently, there are two transformations:
*
* <ul>
* <li>Constant reduction, which evaluates constant subtrees, replacing them
* with a corresponding RexLiteral
* <li>Removal of redundant casts, which occurs when the argument into the cast
* is the same as the type of the resulting cast expression
* </ul>
*/
public abstract class HiveReduceExpressionsRule extends ReduceExpressionsRule {
protected static final Logger LOG = LoggerFactory.getLogger(HiveReduceExpressionsRule.class);
//~ Static fields/initializers ---------------------------------------------
/**
* Singleton rule that reduces constants inside a
* {@link org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveFilter}.
*/
public static final ReduceExpressionsRule FILTER_INSTANCE =
new FilterReduceExpressionsRule(HiveFilter.class, HiveRelFactories.HIVE_BUILDER);
/**
* Singleton rule that reduces constants inside a
* {@link org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveProject}.
*/
public static final ReduceExpressionsRule PROJECT_INSTANCE =
new ProjectReduceExpressionsRule(HiveProject.class, HiveRelFactories.HIVE_BUILDER);
/**
* Singleton rule that reduces constants inside a
* {@link org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveJoin}.
*/
public static final ReduceExpressionsRule JOIN_INSTANCE =
new JoinReduceExpressionsRule(HiveJoin.class, HiveRelFactories.HIVE_BUILDER);
//~ Constructors -----------------------------------------------------------
/**
* Creates a HiveReduceExpressionsRule.
*
* @param clazz class of rels to which this rule should apply
*/
protected HiveReduceExpressionsRule(Class<? extends RelNode> clazz,
RelBuilderFactory relBuilderFactory, String desc) {
super(clazz, relBuilderFactory, desc);
}
/**
* Rule that reduces constants inside a {@link org.apache.calcite.rel.core.Filter}.
* If the condition is a constant, the filter is removed (if TRUE) or replaced with
* an empty {@link org.apache.calcite.rel.core.Values} (if FALSE or NULL).
*/
public static class FilterReduceExpressionsRule extends ReduceExpressionsRule {
public FilterReduceExpressionsRule(Class<? extends Filter> filterClass,
RelBuilderFactory relBuilderFactory) {
super(filterClass, relBuilderFactory, "ReduceExpressionsRule(Filter)");
}
@Override public void onMatch(RelOptRuleCall call) {
final Filter filter = call.rel(0);
final List<RexNode> expList =
Lists.newArrayList(filter.getCondition());
RexNode newConditionExp;
boolean reduced;
final RelMetadataQuery mq = RelMetadataQuery.instance();
final RelOptPredicateList predicates =
mq.getPulledUpPredicates(filter.getInput());
if (reduceExpressions(filter, expList, predicates, true)) {
assert expList.size() == 1;
newConditionExp = expList.get(0);
reduced = true;
} else {
// No reduction, but let's still test the original
// predicate to see if it was already a constant,
// in which case we don't need any runtime decision
// about filtering.
newConditionExp = filter.getCondition();
reduced = false;
}
// Even if no reduction, let's still test the original
// predicate to see if it was already a constant,
// in which case we don't need any runtime decision
// about filtering.
if (newConditionExp.isAlwaysTrue()) {
call.transformTo(
filter.getInput());
} else if (reduced) {
if (RexUtil.isNullabilityCast(filter.getCluster().getTypeFactory(),
newConditionExp)) {
newConditionExp = ((RexCall) newConditionExp).getOperands().get(0);
}
// reduce might end up creating an expression with null type
// e.g condition(null = null) is reduced to condition (null) with null type
// since this is a condition which will always be boolean type we cast it to
// boolean type
if(newConditionExp.getType().getSqlTypeName() == SqlTypeName.NULL) {
newConditionExp = call.builder().cast(newConditionExp, SqlTypeName.BOOLEAN);
}
call.transformTo(call.builder().
push(filter.getInput()).filter(newConditionExp).build());
} else {
if (newConditionExp instanceof RexCall) {
RexCall rexCall = (RexCall) newConditionExp;
boolean reverse = rexCall.getKind() == SqlKind.NOT;
if (reverse) {
if (!(rexCall.getOperands().get(0) instanceof RexCall)) {
// If child is not a RexCall instance, we can bail out
return;
}
rexCall = (RexCall) rexCall.getOperands().get(0);
}
reduceNotNullableFilter(call, filter, rexCall, reverse);
}
return;
}
// New plan is absolutely better than old plan.
call.getPlanner().setImportance(filter, 0.0);
}
/**
* For static schema systems, a filter that is always false or null can be
* replaced by a values operator that produces no rows, as the schema
* information can just be taken from the input Rel. In dynamic schema
* environments, the filter might have an unknown input type, in these cases
* they must define a system specific alternative to a Values operator, such
* as inserting a limit 0 instead of a filter on top of the original input.
*
* <p>The default implementation of this method is to call
* {@link RelBuilder#empty}, which for the static schema will be optimized
* to an empty
* {@link org.apache.calcite.rel.core.Values}.
*
* @param input rel to replace, assumes caller has already determined
* equivalence to Values operation for 0 records or a
* false filter.
* @return equivalent but less expensive replacement rel
*/
protected RelNode createEmptyRelOrEquivalent(RelOptRuleCall call, Filter input) {
return call.builder().push(input).empty().build();
}
private void reduceNotNullableFilter(
RelOptRuleCall call,
Filter filter,
RexCall rexCall,
boolean reverse) {
// If the expression is a IS [NOT] NULL on a non-nullable
// column, then we can either remove the filter or replace
// it with an Empty.
boolean alwaysTrue;
switch (rexCall.getKind()) {
case IS_NULL:
case IS_UNKNOWN:
alwaysTrue = false;
break;
case IS_NOT_NULL:
alwaysTrue = true;
break;
default:
return;
}
if (reverse) {
alwaysTrue = !alwaysTrue;
}
RexNode operand = rexCall.getOperands().get(0);
if (operand instanceof RexInputRef) {
RexInputRef inputRef = (RexInputRef) operand;
if (!inputRef.getType().isNullable()) {
if (alwaysTrue) {
call.transformTo(filter.getInput());
} else {
call.transformTo(createEmptyRelOrEquivalent(call, filter));
}
}
}
}
}
}
// End HiveReduceExpressionsRule.java