blob: 33d50cf83fb192616e1e439e4988b3cb04046f43 [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.impala.rewrite;
import java.util.ArrayList;
import java.util.List;
import org.apache.impala.analysis.Analyzer;
import org.apache.impala.analysis.BinaryPredicate;
import org.apache.impala.analysis.BoolLiteral;
import org.apache.impala.analysis.CaseExpr;
import org.apache.impala.analysis.CaseWhenClause;
import org.apache.impala.analysis.CompoundPredicate;
import org.apache.impala.analysis.Expr;
import org.apache.impala.analysis.FunctionCallExpr;
import org.apache.impala.analysis.NullLiteral;
import org.apache.impala.common.AnalysisException;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/***
* This rule simplifies conditional functions with constant conditions. It relies on
* FoldConstantsRule to replace the constant conditions with a BoolLiteral or NullLiteral
* first, and on NormalizeExprsRule to normalize CompoundPredicates.
*
* Examples:
* if (true, 0, 1) -> 0
* id = 0 OR false -> id = 0
* false AND id = 1 -> false
* case when false then 0 when true then 1 end -> 1
* coalesce(1, 0) -> 1
*
* Unary functions like isfalse, isnotfalse, istrue, isnottrue, nullvalue,
* and nonnullvalue don't need special handling as the fold constants rule
* will handle them. nullif and nvl2 are converted to an if in FunctionCallExpr,
* and therefore don't need handling here.
*/
public class SimplifyConditionalsRule implements ExprRewriteRule {
public static ExprRewriteRule INSTANCE = new SimplifyConditionalsRule();
private static List<String> IFNULL_ALIASES = ImmutableList.of(
"ifnull", "isnull", "nvl");
@Override
public Expr apply(Expr expr, Analyzer analyzer) throws AnalysisException {
if (!expr.isAnalyzed()) return expr;
Expr simplified;
if (expr instanceof FunctionCallExpr) {
simplified = simplifyFunctionCallExpr((FunctionCallExpr) expr);
} else if (expr instanceof CompoundPredicate) {
simplified = simplifyCompoundPredicate((CompoundPredicate) expr);
} else if (expr instanceof CaseExpr) {
simplified = simplifyCaseExpr((CaseExpr) expr, analyzer);
} else {
return expr;
}
// IMPALA-5125: We can't eliminate aggregates as this may change the meaning of the
// query, for example:
// 'select if (true, 0, sum(id)) from alltypes' != 'select 0 from alltypes'
if (expr != simplified) {
simplified.analyze(analyzer);
if (expr.contains(Expr.IS_AGGREGATE)
&& !simplified.contains(Expr.IS_AGGREGATE)) {
return expr;
}
}
return simplified;
}
/**
* Simplifies IF by returning the corresponding child if the condition has a constant
* TRUE, FALSE, or NULL (equivalent to FALSE) value.
*/
private Expr simplifyIfFunctionCallExpr(FunctionCallExpr expr) {
Preconditions.checkState(expr.getChildren().size() == 3);
Expr head = expr.getChild(0);
if (Expr.IS_TRUE_LITERAL.apply(head)) {
// IF(TRUE)
return expr.getChild(1);
} else if (Expr.IS_FALSE_LITERAL.apply(head)) {
// IF(FALSE)
return expr.getChild(2);
} else if (Expr.IS_NULL_LITERAL.apply(head)) {
// IF(NULL)
return expr.getChild(2);
}
return expr;
}
/**
* Simplifies IFNULL if the condition is a literal, using the
* following transformations:
* IFNULL(NULL, x) -> x
* IFNULL(a, x) -> a, if a is a non-null literal
*/
private Expr simplifyIfNullFunctionCallExpr(FunctionCallExpr expr) {
Preconditions.checkState(expr.getChildren().size() == 2);
Expr child0 = expr.getChild(0);
if (Expr.IS_NULL_LITERAL.apply(child0)) return expr.getChild(1);
if (Expr.IS_LITERAL.apply(child0)) return child0;
return expr;
}
/**
* Simplify COALESCE by skipping leading nulls and applying the following transformations:
* COALESCE(null, a, b) -> COALESCE(a, b);
* COALESCE(<literal>, a, b) -> <literal>, when literal is not NullLiteral;
*/
private Expr simplifyCoalesceFunctionCallExpr(FunctionCallExpr expr) {
int numChildren = expr.getChildren().size();
Expr result = NullLiteral.create(expr.getType());
for (int i = 0; i < numChildren; ++i) {
Expr childExpr = expr.getChildren().get(i);
// Skip leading nulls.
if (Expr.IS_NULL_VALUE.apply(childExpr)) continue;
if ((i == numChildren - 1) || Expr.IS_LITERAL.apply(childExpr)) {
result = childExpr;
} else if (i == 0) {
result = expr;
} else {
List<Expr> newChildren = Lists.newArrayList(expr.getChildren().subList(i, numChildren));
result = new FunctionCallExpr(expr.getFnName(), newChildren);
}
break;
}
return result;
}
private Expr simplifyFunctionCallExpr(FunctionCallExpr expr) {
String fnName = expr.getFnName().getFunction();
if (fnName.equals("if")) {
return simplifyIfFunctionCallExpr(expr);
} else if (fnName.equals("coalesce")) {
return simplifyCoalesceFunctionCallExpr(expr);
} else if (IFNULL_ALIASES.contains(fnName)) {
return simplifyIfNullFunctionCallExpr(expr);
}
return expr;
}
/**
* Simplifies compound predicates with at least one BoolLiteral child, which
* NormalizeExprsRule ensures will be the left child, according to the following rules:
* true AND 'expr' -> 'expr'
* false AND 'expr' -> false
* true OR 'expr' -> true
* false OR 'expr' -> 'expr'
*
* Unlike other rules here such as IF, we cannot in general simplify CompoundPredicates
* with a NullLiteral child (unless the other child is a BoolLiteral), eg. null and
* 'expr' is false if 'expr' is false but null if 'expr' is true.
*
* NOT is covered by FoldConstantRule.
*/
private Expr simplifyCompoundPredicate(CompoundPredicate expr) {
Expr leftChild = expr.getChild(0);
if (!(leftChild instanceof BoolLiteral)) return expr;
if (expr.getOp() == CompoundPredicate.Operator.AND) {
if (Expr.IS_TRUE_LITERAL.apply(leftChild)) {
// TRUE AND 'expr', so return 'expr'.
return expr.getChild(1);
} else {
// FALSE AND 'expr', so return FALSE.
return leftChild;
}
} else if (expr.getOp() == CompoundPredicate.Operator.OR) {
if (Expr.IS_TRUE_LITERAL.apply(leftChild)) {
// TRUE OR 'expr', so return TRUE.
return leftChild;
} else {
// FALSE OR 'expr', so return 'expr'.
return expr.getChild(1);
}
}
return expr;
}
/**
* Simplifies CASE and DECODE. If any of the 'when's have constant FALSE/NULL values,
* they are removed. If all of the 'when's are removed, just the ELSE is returned. If
* any of the 'when's have constant TRUE values, the leftmost one becomes the ELSE
* clause and all following cases are removed.
*
* Note that FunctionalCallExpr.createExpr() converts "nvl2" into "if",
* "decode" into "case", and "nullif" into "if".
*/
private Expr simplifyCaseExpr(CaseExpr expr, Analyzer analyzer)
throws AnalysisException {
Expr caseExpr = expr.hasCaseExpr() ? expr.getChild(0) : null;
if (expr.hasCaseExpr() && !Expr.IS_LITERAL.apply(caseExpr)) return expr;
int numChildren = expr.getChildren().size();
int loopStart = expr.hasCaseExpr() ? 1 : 0;
// Check and return early if there's nothing that can be simplified.
boolean canSimplify = false;
for (int i = loopStart; i < numChildren - 1; i += 2) {
if (Expr.IS_LITERAL.apply(expr.getChild(i))) {
canSimplify = true;
break;
}
}
if (!canSimplify) return expr;
// Contains all 'when' clauses with non-constant conditions, used to construct the new
// CASE expr while removing any FALSE or NULL cases.
List<CaseWhenClause> newWhenClauses = new ArrayList<CaseWhenClause>();
// Set to THEN of first constant TRUE clause, if any.
Expr elseExpr = null;
for (int i = loopStart; i < numChildren - 1; i += 2) {
Expr child = expr.getChild(i);
if (Expr.IS_NULL_LITERAL.apply(child)) continue;
Expr whenExpr;
if (expr.hasCaseExpr()) {
if (Expr.IS_LITERAL.apply(child)) {
BinaryPredicate pred = new BinaryPredicate(
BinaryPredicate.Operator.EQ, caseExpr, expr.getChild(i));
pred.analyze(analyzer);
whenExpr = analyzer.getConstantFolder().rewrite(pred, analyzer);
} else {
whenExpr = null;
}
} else {
whenExpr = child;
}
if (whenExpr instanceof BoolLiteral) {
if (((BoolLiteral) whenExpr).getValue()) {
if (newWhenClauses.size() == 0) {
// This WHEN is always TRUE, and any cases preceding it are constant
// FALSE/NULL, so just return its THEN.
return expr.getChild(i + 1);
} else {
// This WHEN is always TRUE, so the cases after it can never be reached.
elseExpr = expr.getChild(i + 1);
break;
}
} else {
// This WHEN is always FALSE, so it can be removed.
}
} else {
newWhenClauses.add(new CaseWhenClause(child, expr.getChild(i + 1)));
}
}
if (expr.hasElseExpr() && elseExpr == null) elseExpr = expr.getChild(numChildren - 1);
if (newWhenClauses.size() == 0) {
// All of the WHEN clauses were FALSE, return the ELSE.
if (elseExpr == null) return NullLiteral.create(expr.getType());
return elseExpr;
}
return new CaseExpr(caseExpr, newWhenClauses, elseExpr);
}
}