blob: 2d098ad14b61f050ecece97c5f09d2b2f1e5aa53 [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.drill.exec.expr;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import org.apache.drill.common.exceptions.DrillRuntimeException;
import org.apache.drill.common.expression.BooleanOperator;
import org.apache.drill.common.expression.CastExpression;
import org.apache.drill.common.expression.ConvertExpression;
import org.apache.drill.common.expression.ErrorCollector;
import org.apache.drill.common.expression.ErrorCollectorImpl;
import org.apache.drill.common.expression.ExpressionPosition;
import org.apache.drill.common.expression.FunctionCall;
import org.apache.drill.common.expression.FunctionHolderExpression;
import org.apache.drill.common.expression.IfExpression;
import org.apache.drill.common.expression.IfExpression.IfCondition;
import org.apache.drill.common.expression.LogicalExpression;
import org.apache.drill.common.expression.NullExpression;
import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.common.expression.TypedFieldExpr;
import org.apache.drill.common.expression.TypedNullConstant;
import org.apache.drill.common.expression.ValueExpressions;
import org.apache.drill.common.expression.ValueExpressions.BooleanExpression;
import org.apache.drill.common.expression.ValueExpressions.DateExpression;
import org.apache.drill.common.expression.ValueExpressions.Decimal18Expression;
import org.apache.drill.common.expression.ValueExpressions.Decimal28Expression;
import org.apache.drill.common.expression.ValueExpressions.Decimal38Expression;
import org.apache.drill.common.expression.ValueExpressions.Decimal9Expression;
import org.apache.drill.common.expression.ValueExpressions.DoubleExpression;
import org.apache.drill.common.expression.ValueExpressions.FloatExpression;
import org.apache.drill.common.expression.ValueExpressions.IntExpression;
import org.apache.drill.common.expression.ValueExpressions.IntervalDayExpression;
import org.apache.drill.common.expression.ValueExpressions.IntervalYearExpression;
import org.apache.drill.common.expression.ValueExpressions.LongExpression;
import org.apache.drill.common.expression.ValueExpressions.QuotedString;
import org.apache.drill.common.expression.ValueExpressions.TimeExpression;
import org.apache.drill.common.expression.ValueExpressions.TimeStampExpression;
import org.apache.drill.common.expression.ValueExpressions.VarDecimalExpression;
import org.apache.drill.common.expression.fn.FunctionReplacementUtils;
import org.apache.drill.common.expression.visitors.AbstractExprVisitor;
import org.apache.drill.common.expression.visitors.ConditionalExprOptimizer;
import org.apache.drill.common.expression.visitors.ExpressionValidator;
import org.apache.drill.common.types.TypeProtos;
import org.apache.drill.common.types.TypeProtos.DataMode;
import org.apache.drill.common.types.TypeProtos.MajorType;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.common.types.Types;
import org.apache.drill.exec.expr.annotations.FunctionTemplate;
import org.apache.drill.exec.expr.fn.AbstractFuncHolder;
import org.apache.drill.exec.expr.fn.DrillComplexWriterFuncHolder;
import org.apache.drill.exec.expr.fn.DrillFuncHolder;
import org.apache.drill.exec.expr.fn.ExceptionFunction;
import org.apache.drill.exec.expr.fn.FunctionLookupContext;
import org.apache.drill.exec.record.TypedFieldId;
import org.apache.drill.exec.record.VectorAccessible;
import org.apache.drill.exec.record.metadata.ColumnMetadata;
import org.apache.drill.exec.record.metadata.TupleMetadata;
import org.apache.drill.exec.resolver.FunctionResolver;
import org.apache.drill.exec.resolver.FunctionResolverFactory;
import org.apache.drill.exec.resolver.TypeCastRules;
import org.apache.drill.exec.util.DecimalUtility;
import org.apache.drill.metastore.util.SchemaPathUtils;
import org.apache.drill.shaded.guava.com.google.common.base.Preconditions;
import org.apache.drill.shaded.guava.com.google.common.collect.ImmutableList;
import org.apache.drill.shaded.guava.com.google.common.collect.Lists;
import org.apache.drill.shaded.guava.com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExpressionTreeMaterializer {
static final Logger logger = LoggerFactory.getLogger(ExpressionTreeMaterializer.class);
private ExpressionTreeMaterializer() { }
public static LogicalExpression materialize(LogicalExpression expr,
VectorAccessible batch, ErrorCollector errorCollector,
FunctionLookupContext functionLookupContext) {
return ExpressionTreeMaterializer.materialize(expr, batch, errorCollector, functionLookupContext, false, false);
}
public static LogicalExpression materializeAndCheckErrors(LogicalExpression expr,
VectorAccessible batch, FunctionLookupContext functionLookupContext) {
ErrorCollector collector = new ErrorCollectorImpl();
LogicalExpression e = ExpressionTreeMaterializer.materialize(expr, batch, collector, functionLookupContext, false, false);
collector.reportErrors(logger);
return e;
}
public static LogicalExpression materialize(LogicalExpression expr,
VectorAccessible batch, ErrorCollector errorCollector, FunctionLookupContext functionLookupContext,
boolean allowComplexWriterExpr) {
return materialize(expr, batch, errorCollector, functionLookupContext, allowComplexWriterExpr, false);
}
public static LogicalExpression materializeFilterExpr(LogicalExpression expr,
TupleMetadata fieldTypes, ErrorCollector errorCollector, FunctionLookupContext functionLookupContext) {
final FilterMaterializeVisitor filterMaterializeVisitor = new FilterMaterializeVisitor(fieldTypes, errorCollector);
return expr.accept(filterMaterializeVisitor, functionLookupContext);
}
/**
* Materializes logical expression taking into account passed parameters.
* Is used to materialize logical expression that contains reference to one batch.
*
* @param expr logical expression to be materialized
* @param batch batch instance
* @param errorCollector error collector
* @param functionLookupContext context to find drill function holder
* @param allowComplexWriterExpr true if complex expressions are allowed
* @param unionTypeEnabled true if union type is enabled
* @return materialized logical expression
*/
public static LogicalExpression materialize(LogicalExpression expr,
VectorAccessible batch,
ErrorCollector errorCollector,
FunctionLookupContext functionLookupContext,
boolean allowComplexWriterExpr,
boolean unionTypeEnabled) {
Map<VectorAccessible, BatchReference> batches = Maps.newHashMap();
batches.put(batch, null);
return materialize(expr, batches, errorCollector, functionLookupContext,
allowComplexWriterExpr, unionTypeEnabled);
}
/**
* Materializes logical expression taking into account passed parameters. Is
* used to materialize logical expression that can contain several batches
* with or without custom batch reference.
*
* @param expr
* logical expression to be materialized
* @param batches
* one or more batch instances used in expression
* @param errorCollector
* error collector
* @param functionLookupContext
* context to find drill function holder
* @param allowComplexWriterExpr
* true if complex expressions are allowed
* @param unionTypeEnabled
* true if union type is enabled
* @return materialized logical expression
*/
public static LogicalExpression materialize(LogicalExpression expr,
Map<VectorAccessible, BatchReference> batches,
ErrorCollector errorCollector,
FunctionLookupContext functionLookupContext,
boolean allowComplexWriterExpr,
boolean unionTypeEnabled) {
LogicalExpression out = expr.accept(
new MaterializeVisitor(batches, errorCollector, allowComplexWriterExpr, unionTypeEnabled),
functionLookupContext);
if (!errorCollector.hasErrors()) {
out = out.accept(ConditionalExprOptimizer.INSTANCE, null);
}
if (out instanceof NullExpression) {
return new TypedNullConstant(Types.optional(MinorType.INT));
} else {
return out;
}
}
public static LogicalExpression convertToNullableType(LogicalExpression fromExpr,
MinorType toType, FunctionLookupContext functionLookupContext, ErrorCollector errorCollector) {
String funcName = "convertToNullable" + toType.toString();
List<LogicalExpression> args = new ArrayList<>();
args.add(fromExpr);
FunctionCall funcCall = new FunctionCall(funcName, args, ExpressionPosition.UNKNOWN);
FunctionResolver resolver = FunctionResolverFactory.getResolver(funcCall);
DrillFuncHolder matchedConvertToNullableFuncHolder = functionLookupContext.findDrillFunction(resolver, funcCall);
if (matchedConvertToNullableFuncHolder == null) {
logFunctionResolutionError(errorCollector, funcCall);
return NullExpression.INSTANCE;
}
return matchedConvertToNullableFuncHolder.getExpr(funcName, args, ExpressionPosition.UNKNOWN);
}
public static LogicalExpression addCastExpression(LogicalExpression fromExpr,
MajorType toType, FunctionLookupContext functionLookupContext, ErrorCollector errorCollector) {
return addCastExpression(fromExpr, toType, functionLookupContext, errorCollector, true);
}
public static LogicalExpression addCastExpression(LogicalExpression fromExpr, MajorType toType,
FunctionLookupContext functionLookupContext, ErrorCollector errorCollector, boolean exactResolver) {
String castFuncName = FunctionReplacementUtils.getCastFunc(toType.getMinorType());
List<LogicalExpression> castArgs = new ArrayList<>();
castArgs.add(fromExpr); //input_expr
if (fromExpr.getMajorType().getMinorType() == MinorType.UNION && toType.getMinorType() == MinorType.UNION) {
return fromExpr;
}
if (Types.isDecimalType(toType)) {
// Add the scale and precision to the arguments of the implicit cast
castArgs.add(new ValueExpressions.IntExpression(toType.getPrecision(), null));
castArgs.add(new ValueExpressions.IntExpression(toType.getScale(), null));
} else if (!Types.isFixedWidthType(toType) && !Types.isUnion(toType)) {
/* We are implicitly casting to VARCHAR so we don't have a max length,
* using an arbitrary value. We trim down the size of the stored bytes
* to the actual size so this size doesn't really matter.
*/
castArgs.add(new ValueExpressions.LongExpression(Types.MAX_VARCHAR_LENGTH, null));
}
FunctionCall castCall = new FunctionCall(castFuncName, castArgs, ExpressionPosition.UNKNOWN);
FunctionResolver resolver;
if (exactResolver) {
resolver = FunctionResolverFactory.getExactResolver(castCall);
} else {
resolver = FunctionResolverFactory.getResolver(castCall);
}
DrillFuncHolder matchedCastFuncHolder = functionLookupContext.findDrillFunction(resolver, castCall);
if (matchedCastFuncHolder == null) {
logFunctionResolutionError(errorCollector, castCall);
return NullExpression.INSTANCE;
}
return matchedCastFuncHolder.getExpr(castFuncName, castArgs, ExpressionPosition.UNKNOWN);
}
private static void logFunctionResolutionError(ErrorCollector errorCollector, FunctionCall call) {
// add error to collector
StringBuilder sb = new StringBuilder();
sb.append("Missing function implementation: ");
sb.append("[");
sb.append(call.getName());
sb.append("(");
boolean first = true;
for(LogicalExpression e : call.args) {
TypeProtos.MajorType mt = e.getMajorType();
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(mt.getMinorType().name());
sb.append("-");
sb.append(mt.getMode().name());
}
sb.append(")");
sb.append("]");
errorCollector.addGeneralError(call.getPosition(), sb.toString());
}
/**
* Visitor that wraps schema path into value vector read expression
* if schema path is present in one of the batches,
* otherwise instance of null expression.
*/
private static class MaterializeVisitor extends AbstractMaterializeVisitor {
private final Map<VectorAccessible, BatchReference> batches;
public MaterializeVisitor(Map<VectorAccessible, BatchReference> batches,
ErrorCollector errorCollector,
boolean allowComplexWriter,
boolean unionTypeEnabled) {
super(errorCollector, allowComplexWriter, unionTypeEnabled);
this.batches = batches;
}
@Override
public LogicalExpression visitSchemaPath(final SchemaPath path, FunctionLookupContext functionLookupContext) {
TypedFieldId tfId = null;
BatchReference batchRef = null;
for (Map.Entry<VectorAccessible, BatchReference> entry : batches.entrySet()) {
tfId = entry.getKey().getValueVectorId(path);
if (tfId != null) {
batchRef = entry.getValue();
break;
}
}
if (tfId == null) {
logger.warn("Unable to find value vector of path {}, returning null instance.", path);
return NullExpression.INSTANCE;
} else {
return new ValueVectorReadExpression(tfId, batchRef);
}
}
}
private static class FilterMaterializeVisitor extends AbstractMaterializeVisitor {
private final TupleMetadata types;
public FilterMaterializeVisitor(TupleMetadata types, ErrorCollector errorCollector) {
super(errorCollector, false, false);
this.types = types;
}
@Override
public LogicalExpression visitSchemaPath(SchemaPath path, FunctionLookupContext functionLookupContext) {
MajorType type = null;
ColumnMetadata columnMetadata = SchemaPathUtils.getColumnMetadata(path.getUnIndexed(), types);
if (columnMetadata != null) {
type = columnMetadata.majorType();
// for the case when specified path refers to array element, makes its type optional
if (path.isArray()) {
type = type.toBuilder().setMode(DataMode.OPTIONAL).build();
}
}
if (type != null) {
return new TypedFieldExpr(path, type);
} else {
logger.warn("Unable to find value vector of path {}, returning null-int instance.", path);
return new TypedFieldExpr(path, Types.OPTIONAL_INT);
}
}
}
private abstract static class AbstractMaterializeVisitor extends AbstractExprVisitor<LogicalExpression, FunctionLookupContext, RuntimeException> {
private final ExpressionValidator validator = new ExpressionValidator();
private ErrorCollector errorCollector;
private final Deque<ErrorCollector> errorCollectors = new ArrayDeque<>();
private final boolean allowComplexWriter;
/**
* If this is false, the materializer will not handle or create UnionTypes
* Once this code is more well tested, we will probably remove this flag
*/
private final boolean unionTypeEnabled;
public AbstractMaterializeVisitor(ErrorCollector errorCollector, boolean allowComplexWriter, boolean unionTypeEnabled) {
this.errorCollector = errorCollector;
this.allowComplexWriter = allowComplexWriter;
this.unionTypeEnabled = unionTypeEnabled;
}
private LogicalExpression validateNewExpr(LogicalExpression newExpr) {
newExpr.accept(validator, errorCollector);
return newExpr;
}
@Override
public abstract LogicalExpression visitSchemaPath(SchemaPath path, FunctionLookupContext functionLookupContext);
@Override
public LogicalExpression visitUnknown(LogicalExpression e, FunctionLookupContext functionLookupContext) {
return e;
}
@Override
public LogicalExpression visitFunctionHolderExpression(FunctionHolderExpression holder,
FunctionLookupContext functionLookupContext) {
// A function holder is already materialized, no need to rematerialize.
// generally this won't be used unless we materialize a partial tree and
// rematerialize the whole tree.
return holder;
}
@Override
public LogicalExpression visitBooleanOperator(BooleanOperator op, FunctionLookupContext functionLookupContext) {
List<LogicalExpression> args = new ArrayList<>();
for (int i = 0; i < op.args.size(); ++i) {
LogicalExpression newExpr = op.args.get(i).accept(this, functionLookupContext);
assert newExpr != null : String.format("Materialization of %s return a null expression.", op.args.get(i));
args.add(newExpr);
}
// Replace with a new function call, since its argument could be changed.
return new BooleanOperator(op.getName(), args, op.getPosition());
}
private int computePrecision(LogicalExpression currentArg) {
int precision = currentArg.getMajorType().getPrecision();
return DecimalUtility.getDefaultPrecision(currentArg.getMajorType().getMinorType(), precision);
}
@Override
public LogicalExpression visitFunctionCall(FunctionCall call, FunctionLookupContext functionLookupContext) {
// Possibly convert input expressions with a rewritten expression.
List<LogicalExpression> args = new ArrayList<>();
for (int i = 0; i < call.args.size(); ++i) {
LogicalExpression newExpr = call.args.get(i).accept(this, functionLookupContext);
assert newExpr != null : String.format("Materialization of %s returned a null expression.", call.args.get(i));
args.add(newExpr);
}
// Replace with a new function call, since its argument could be changed.
call = new FunctionCall(call.getName(), args, call.getPosition());
// Resolve the function
FunctionResolver resolver = FunctionResolverFactory.getResolver(call);
DrillFuncHolder matchedFuncHolder = functionLookupContext.findDrillFunction(resolver, call);
if (matchedFuncHolder instanceof DrillComplexWriterFuncHolder && ! allowComplexWriter) {
errorCollector.addGeneralError(call.getPosition(),
"Only ProjectRecordBatch could have complex writer function. You are using complex writer function "
+ call.getName() + " in a non-project operation!");
}
if (matchedFuncHolder != null) {
return bindDrillFunc(call, functionLookupContext, matchedFuncHolder);
}
// as no drill func is found, search for a non-Drill function.
AbstractFuncHolder matchedNonDrillFuncHolder = functionLookupContext.findNonDrillFunction(call);
if (matchedNonDrillFuncHolder != null) {
return bindNonDrillFunc(call, functionLookupContext,
matchedNonDrillFuncHolder);
}
if (hasUnionInput(call)) {
return rewriteUnionFunction(call, functionLookupContext);
}
// No match found. Add an error (which will fail the query), but also
// return a NULL instance so that analysis can continue.
logFunctionResolutionError(errorCollector, call);
return NullExpression.INSTANCE;
}
/**
* Bind a call to a Drill function, casting input and output arguments
* as needed. Casts done here are of SQL types, not internal Drill types
* (FieldReader vs. Holder).
*
* @param call the logical call expression
* @param functionLookupContext function registry
* @param matchedFuncHolder the matched Drill function declaration
* @return a new expression that represents the actual function call
*/
private LogicalExpression bindDrillFunc(FunctionCall call,
FunctionLookupContext functionLookupContext,
DrillFuncHolder matchedFuncHolder) {
// New arg lists, possible with implicit cast inserted.
List<LogicalExpression> argsWithCast = new ArrayList<>();
// Compare param type against arg type. Insert cast on top of arg, whenever necessary.
for (int i = 0; i < call.args.size(); ++i) {
LogicalExpression currentArg = call.args.get(i);
TypeProtos.MajorType parmType = matchedFuncHolder.getParamMajorType(i);
// Case 1: If 1) the argument is NullExpression
// 2) the minor type of parameter of matchedFuncHolder is not LATE
// (the type of null expression is still unknown)
// 3) the parameter of matchedFuncHolder allows null input, or func's null_handling
// is NULL_IF_NULL (means null and non-null are exchangeable).
// then replace NullExpression with a TypedNullConstant
if (currentArg.equals(NullExpression.INSTANCE) && !MinorType.LATE.equals(parmType.getMinorType()) &&
(TypeProtos.DataMode.OPTIONAL.equals(parmType.getMode()) ||
matchedFuncHolder.getNullHandling() == FunctionTemplate.NullHandling.NULL_IF_NULL)) {
// Case 1: argument is a null expression, convert it to a typed null
argsWithCast.add(new TypedNullConstant(parmType));
} else if (Types.softEquals(parmType, currentArg.getMajorType(),
matchedFuncHolder.getNullHandling() == FunctionTemplate.NullHandling.NULL_IF_NULL) ||
matchedFuncHolder.isFieldReader(i)) {
// Case 2: argument and parameter matches, or parameter is FieldReader. Do nothing.
argsWithCast.add(currentArg);
} else {
// Case 3: insert cast if param type is different from arg type.
if (Types.isDecimalType(parmType)) {
// We are implicitly promoting a decimal type, set the required scale and precision
parmType = MajorType.newBuilder().setMinorType(parmType.getMinorType()).setMode(parmType.getMode()).
setScale(currentArg.getMajorType().getScale()).setPrecision(computePrecision(currentArg)).build();
}
argsWithCast.add(addCastExpression(currentArg, parmType, functionLookupContext, errorCollector));
}
}
FunctionHolderExpression funcExpr = matchedFuncHolder.getExpr(call.getName(), argsWithCast, call.getPosition());
// Convert old-style Decimal return type to VarDecimal
MajorType funcExprMajorType = funcExpr.getMajorType();
if (DecimalUtility.isObsoleteDecimalType(funcExprMajorType.getMinorType())) {
MajorType majorType =
MajorType.newBuilder()
.setMinorType(MinorType.VARDECIMAL)
.setMode(funcExprMajorType.getMode())
.setScale(funcExprMajorType.getScale())
.setPrecision(funcExprMajorType.getPrecision())
.build();
return addCastExpression(funcExpr, majorType, functionLookupContext, errorCollector);
}
return funcExpr;
}
private LogicalExpression bindNonDrillFunc(FunctionCall call,
FunctionLookupContext functionLookupContext,
AbstractFuncHolder matchedNonDrillFuncHolder) {
// Insert implicit cast function holder expressions if required
List<LogicalExpression> extArgsWithCast = new ArrayList<>();
for (int i = 0; i < call.args.size(); ++i) {
LogicalExpression currentArg = call.args.get(i);
TypeProtos.MajorType paramType = matchedNonDrillFuncHolder.getParamMajorType(i);
if (Types.softEquals(paramType, currentArg.getMajorType(), true)) {
extArgsWithCast.add(currentArg);
} else {
// Insert cast if param type is different from arg type.
if (Types.isDecimalType(paramType)) {
// We are implicitly promoting a decimal type, set the required scale and precision
paramType = MajorType.newBuilder().setMinorType(paramType.getMinorType()).setMode(paramType.getMode()).
setScale(currentArg.getMajorType().getScale()).setPrecision(computePrecision(currentArg)).build();
}
extArgsWithCast.add(addCastExpression(call.args.get(i), paramType, functionLookupContext, errorCollector));
}
}
return matchedNonDrillFuncHolder.getExpr(call.getName(), extArgsWithCast, call.getPosition());
}
private boolean hasUnionInput(FunctionCall call) {
for (LogicalExpression arg : call.args) {
if (arg.getMajorType().getMinorType() == MinorType.UNION) {
return true;
}
}
return false;
}
/**
* Converts a function call with a Union type input into a case statement,
* where each branch of the case corresponds to one of the subtypes of the
* Union type. The function call is materialized in each of the branches,
* with the union input cast to the specific type corresponding to the
* branch of the case statement
*/
private LogicalExpression rewriteUnionFunction(FunctionCall call, FunctionLookupContext functionLookupContext) {
LogicalExpression[] args = new LogicalExpression[call.args.size()];
call.args.toArray(args);
for (int i = 0; i < args.length; i++) {
LogicalExpression arg = call.args.get(i);
MajorType majorType = arg.getMajorType();
if (majorType.getMinorType() != MinorType.UNION) {
continue;
}
List<MinorType> subTypes = majorType.getSubTypeList();
Preconditions.checkState(subTypes.size() > 0, "Union type has no subtypes");
Queue<IfCondition> ifConditions = Lists.newLinkedList();
for (MinorType minorType : subTypes) {
LogicalExpression ifCondition = getIsTypeExpressionForType(minorType, arg.accept(new CloneVisitor(), null));
args[i] = getUnionAssertFunctionForType(minorType, arg.accept(new CloneVisitor(), null));
List<LogicalExpression> newArgs = new ArrayList<>();
for (LogicalExpression e : args) {
newArgs.add(e.accept(new CloneVisitor(), null));
}
// When expanding the expression tree to handle the different
// subtypes, we will not throw an exception if one
// of the branches fails to find a function match, since it is
// possible that code path will never occur in execution.
// So instead of failing to materialize, we generate code to throw the
// exception during execution if that code path is hit.
errorCollectors.push(errorCollector);
errorCollector = new ErrorCollectorImpl();
LogicalExpression thenExpression = new FunctionCall(call.getName(), newArgs, call.getPosition()).accept(this, functionLookupContext);
if (errorCollector.hasErrors()) {
thenExpression = getExceptionFunction(errorCollector.toErrorString());
}
errorCollector = errorCollectors.pop();
IfExpression.IfCondition condition = new IfCondition(ifCondition, thenExpression);
ifConditions.add(condition);
}
LogicalExpression ifExpression = ifConditions.poll().expression;
while (!ifConditions.isEmpty()) {
ifExpression = IfExpression.newBuilder().setIfCondition(ifConditions.poll()).setElse(ifExpression).build();
}
args[i] = ifExpression;
return ifExpression.accept(this, functionLookupContext);
}
throw new UnsupportedOperationException("Did not find any Union input types");
}
/**
* Returns the function call whose purpose is to throw an Exception if that code is hit during execution
* @param message the exception message
* @return
*/
private LogicalExpression getExceptionFunction(String message) {
QuotedString msg = new QuotedString(message, message.length(), ExpressionPosition.UNKNOWN);
List<LogicalExpression> args = new ArrayList<>();
args.add(msg);
return new FunctionCall(ExceptionFunction.EXCEPTION_FUNCTION_NAME, args, ExpressionPosition.UNKNOWN);
}
/**
* Returns the function which asserts that the current subtype of a union type is a specific type, and allows the materializer
* to bind to that specific type when doing function resolution
* @param type
* @param arg
* @return
*/
private LogicalExpression getUnionAssertFunctionForType(MinorType type, LogicalExpression arg) {
if (type == MinorType.UNION) {
return arg;
}
if (type == MinorType.LIST || type == MinorType.MAP) {
return getExceptionFunction("Unable to cast union to " + type);
}
String castFuncName = String.format("assert_%s", type.toString());
return new FunctionCall(castFuncName, Collections.singletonList(arg), ExpressionPosition.UNKNOWN);
}
/**
* Get the function that tests whether a union type is a specific type
* @param type
* @param arg
* @return
*/
private LogicalExpression getIsTypeExpressionForType(MinorType type, LogicalExpression arg) {
String isFuncName = String.format("is_%s", type.toString());
List<LogicalExpression> args = new ArrayList<>();
args.add(arg);
return new FunctionCall(isFuncName, args, ExpressionPosition.UNKNOWN);
}
@Override
public LogicalExpression visitIfExpression(IfExpression ifExpr, FunctionLookupContext functionLookupContext) {
IfExpression.IfCondition conditions = ifExpr.ifCondition;
LogicalExpression newElseExpr = ifExpr.elseExpression.accept(this, functionLookupContext);
LogicalExpression newCondition = conditions.condition.accept(this, functionLookupContext);
LogicalExpression newExpr = conditions.expression.accept(this, functionLookupContext);
conditions = new IfExpression.IfCondition(newCondition, newExpr);
MinorType thenType = conditions.expression.getMajorType().getMinorType();
MinorType elseType = newElseExpr.getMajorType().getMinorType();
boolean hasUnion = thenType == MinorType.UNION || elseType == MinorType.UNION;
MajorType outputType = ifExpr.outputType;
if (unionTypeEnabled) {
if (thenType != elseType && !(thenType == MinorType.NULL || elseType == MinorType.NULL)) {
MajorType.Builder builder = MajorType.newBuilder().setMinorType(MinorType.UNION).setMode(DataMode.OPTIONAL);
if (thenType == MinorType.UNION) {
for (MinorType subType : conditions.expression.getMajorType().getSubTypeList()) {
builder.addSubType(subType);
}
} else {
builder.addSubType(thenType);
}
if (elseType == MinorType.UNION) {
for (MinorType subType : newElseExpr.getMajorType().getSubTypeList()) {
builder.addSubType(subType);
}
} else {
builder.addSubType(elseType);
}
outputType = builder.build();
conditions = new IfExpression.IfCondition(newCondition,
addCastExpression(conditions.expression, outputType, functionLookupContext, errorCollector, false));
newElseExpr = addCastExpression(newElseExpr, outputType, functionLookupContext, errorCollector, false);
}
} else {
// Check if we need a cast
if (thenType != elseType && !(thenType == MinorType.NULL || elseType == MinorType.NULL)) {
MinorType leastRestrictive = TypeCastRules.getLeastRestrictiveType((Arrays.asList(thenType, elseType)));
if (leastRestrictive != thenType) {
// Implicitly cast the then expression
conditions = new IfExpression.IfCondition(newCondition,
addCastExpression(conditions.expression, newElseExpr.getMajorType(), functionLookupContext, errorCollector));
} else if (leastRestrictive != elseType) {
// Implicitly cast the else expression
newElseExpr = addCastExpression(newElseExpr, conditions.expression.getMajorType(), functionLookupContext, errorCollector);
} else {
/* Cannot cast one of the two expressions to make the output type of if and else expression
* to be the same. Raise error.
*/
throw new DrillRuntimeException("Case expression should have similar output type on all its branches");
}
}
}
// Resolve NullExpression into TypedNullConstant by visiting all conditions
// We need to do this because we want to give the correct MajorType to the Null constant
List<LogicalExpression> allExpressions = new ArrayList<>();
allExpressions.add(conditions.expression);
allExpressions.add(newElseExpr);
boolean containsNullType = allExpressions.stream()
.anyMatch(expression -> expression.getMajorType().getMinorType() == MinorType.NULL);
if (containsNullType) {
Optional<LogicalExpression> nonNullExpr = allExpressions.stream()
.filter(expression -> expression.getMajorType().getMinorType() != MinorType.NULL)
.findAny();
if (nonNullExpr.isPresent()) {
MajorType type = nonNullExpr.get().getMajorType();
conditions = new IfExpression.IfCondition(conditions.condition, rewriteNullExpression(conditions.expression, type));
newElseExpr = rewriteNullExpression(newElseExpr, type);
}
}
if (!hasUnion) {
// If the type of the IF expression is nullable, apply a convertToNullable*Holder function for "THEN"/"ELSE"
// expressions whose type is not nullable.
if (IfExpression.newBuilder().setElse(newElseExpr).setIfCondition(conditions).build().getMajorType().getMode()
== DataMode.OPTIONAL) {
IfExpression.IfCondition condition = conditions;
if (condition.expression.getMajorType().getMode() != DataMode.OPTIONAL) {
conditions = new IfExpression.IfCondition(condition.condition, getConvertToNullableExpr(ImmutableList.of(condition.expression),
condition.expression.getMajorType().getMinorType(), functionLookupContext));
}
if (newElseExpr.getMajorType().getMode() != DataMode.OPTIONAL) {
newElseExpr = getConvertToNullableExpr(ImmutableList.of(newElseExpr),
newElseExpr.getMajorType().getMinorType(), functionLookupContext);
}
}
}
return validateNewExpr(
IfExpression.newBuilder()
.setElse(newElseExpr)
.setIfCondition(conditions)
.setOutputType(outputType)
.build());
}
private LogicalExpression getConvertToNullableExpr(List<LogicalExpression> args, MinorType minorType,
FunctionLookupContext functionLookupContext) {
String funcName = "convertToNullable" + minorType.toString();
FunctionCall funcCall = new FunctionCall(funcName, args, ExpressionPosition.UNKNOWN);
FunctionResolver resolver = FunctionResolverFactory.getResolver(funcCall);
DrillFuncHolder matchedConvertToNullableFuncHolder = functionLookupContext.findDrillFunction(resolver, funcCall);
if (matchedConvertToNullableFuncHolder == null) {
logFunctionResolutionError(errorCollector, funcCall);
return NullExpression.INSTANCE;
}
return matchedConvertToNullableFuncHolder.getExpr(funcName, args, ExpressionPosition.UNKNOWN);
}
private LogicalExpression rewriteNullExpression(LogicalExpression expr, MajorType type) {
if(expr instanceof NullExpression) {
return new TypedNullConstant(type);
} else if (expr instanceof IfExpression) {
return rewriteIfWithNullExpression((IfExpression) expr, type);
} else {
return expr;
}
}
private LogicalExpression rewriteIfWithNullExpression(IfExpression expr, MajorType type) {
IfCondition condition = new IfExpression.IfCondition(expr.ifCondition.condition, rewriteNullExpression(expr.ifCondition.expression, type));
LogicalExpression elseExpression = rewriteNullExpression(expr.elseExpression, type);
return new IfExpression.Builder()
.setPosition(expr.getPosition())
.setOutputType(expr.outputType)
.setIfCondition(condition)
.setElse(elseExpression)
.build();
}
@Override
public LogicalExpression visitIntConstant(IntExpression intExpr, FunctionLookupContext functionLookupContext) {
return intExpr;
}
@Override
public LogicalExpression visitFloatConstant(FloatExpression fExpr, FunctionLookupContext functionLookupContext) {
return fExpr;
}
@Override
public LogicalExpression visitLongConstant(LongExpression intExpr, FunctionLookupContext functionLookupContext) {
return intExpr;
}
@Override
public LogicalExpression visitDateConstant(DateExpression intExpr, FunctionLookupContext functionLookupContext) {
return intExpr;
}
@Override
public LogicalExpression visitTimeConstant(TimeExpression intExpr, FunctionLookupContext functionLookupContext) {
return intExpr;
}
@Override
public LogicalExpression visitTimeStampConstant(TimeStampExpression intExpr, FunctionLookupContext functionLookupContext) {
return intExpr;
}
@Override
public LogicalExpression visitNullConstant(TypedNullConstant nullConstant, FunctionLookupContext functionLookupContext) throws RuntimeException {
return nullConstant;
}
@Override
public LogicalExpression visitIntervalYearConstant(IntervalYearExpression intExpr, FunctionLookupContext functionLookupContext) {
return intExpr;
}
@Override
public LogicalExpression visitIntervalDayConstant(IntervalDayExpression intExpr, FunctionLookupContext functionLookupContext) {
return intExpr;
}
@Override
public LogicalExpression visitDecimal9Constant(Decimal9Expression decExpr, FunctionLookupContext functionLookupContext) {
return decExpr;
}
@Override
public LogicalExpression visitDecimal18Constant(Decimal18Expression decExpr, FunctionLookupContext functionLookupContext) {
return decExpr;
}
@Override
public LogicalExpression visitDecimal28Constant(Decimal28Expression decExpr, FunctionLookupContext functionLookupContext) {
return decExpr;
}
@Override
public LogicalExpression visitDecimal38Constant(Decimal38Expression decExpr, FunctionLookupContext functionLookupContext) {
return decExpr;
}
@Override
public LogicalExpression visitVarDecimalConstant(VarDecimalExpression decExpr, FunctionLookupContext functionLookupContext) {
return decExpr;
}
@Override
public LogicalExpression visitDoubleConstant(DoubleExpression dExpr, FunctionLookupContext functionLookupContext) {
return dExpr;
}
@Override
public LogicalExpression visitBooleanConstant(BooleanExpression e, FunctionLookupContext functionLookupContext) {
return e;
}
@Override
public LogicalExpression visitQuotedStringConstant(QuotedString e, FunctionLookupContext functionLookupContext) {
return e;
}
@Override
public LogicalExpression visitConvertExpression(ConvertExpression e, FunctionLookupContext functionLookupContext) {
String convertFunctionName = e.getConvertFunction() + e.getEncodingType();
List<LogicalExpression> newArgs = new ArrayList<>();
newArgs.add(e.getInput()); //input_expr
FunctionCall fc = new FunctionCall(convertFunctionName, newArgs, e.getPosition());
return fc.accept(this, functionLookupContext);
}
@Override
public LogicalExpression visitCastExpression(CastExpression e, FunctionLookupContext functionLookupContext) {
// if the cast is pointless, remove it.
LogicalExpression input = e.getInput().accept(this, functionLookupContext);
MajorType newMajor = e.getMajorType(); // Output type
MinorType newMinor = input.getMajorType().getMinorType(); // Input type
if (castEqual(e.getPosition(), input.getMajorType(), newMajor)) {
return input; // don't do pointless cast.
}
if (newMinor == MinorType.LATE) {
// if the type still isn't fully bound, leave as cast expression.
return new CastExpression(input, e.getMajorType(), e.getPosition());
} else if (newMinor == MinorType.NULL) {
// if input is a NULL expression, remove cast expression and return a TypedNullConstant directly
// preserve original precision and scale if present
return new TypedNullConstant(e.getMajorType().toBuilder().setMode(DataMode.OPTIONAL).build());
} else {
// if the type is fully bound, convert to functioncall and materialze the function.
MajorType type = e.getMajorType();
// Get the cast function name from the map
String castFuncWithType = FunctionReplacementUtils.getCastFunc(type.getMinorType());
List<LogicalExpression> newArgs = new ArrayList<>();
newArgs.add(input); //input_expr
if (Types.isDecimalType(type)) {
newArgs.add(new ValueExpressions.IntExpression(type.getPrecision(), null));
newArgs.add(new ValueExpressions.IntExpression(type.getScale(), null));
} else if (!Types.isFixedWidthType(type)) { //VarLen type
newArgs.add(new ValueExpressions.LongExpression(type.getPrecision(), null));
}
FunctionCall fc = new FunctionCall(castFuncWithType, newArgs, e.getPosition());
return fc.accept(this, functionLookupContext);
}
}
private boolean castEqual(ExpressionPosition pos, MajorType from, MajorType to) {
if (!from.getMinorType().equals(to.getMinorType())) {
return false;
}
switch(from.getMinorType()) {
case FLOAT4:
case FLOAT8:
case INT:
case BIGINT:
case BIT:
case TINYINT:
case SMALLINT:
case UINT1:
case UINT2:
case UINT4:
case UINT8:
case TIME:
case TIMESTAMP:
case TIMESTAMPTZ:
case DATE:
case INTERVAL:
case INTERVALDAY:
case INTERVALYEAR:
// nothing else matters.
return true;
case DECIMAL9:
case DECIMAL18:
case DECIMAL28DENSE:
case DECIMAL28SPARSE:
case DECIMAL38DENSE:
case DECIMAL38SPARSE:
case VARDECIMAL:
return to.getScale() == from.getScale() && to.getPrecision() == from.getPrecision();
case FIXED16CHAR:
case FIXEDBINARY:
case FIXEDCHAR:
// width always matters
this.errorCollector.addGeneralError(pos, "Casting fixed width types are not yet supported..");
return false;
case VAR16CHAR:
case VARBINARY:
case VARCHAR:
// We could avoid redundant cast:
// 1) when "to" length is no smaller than "from" length and "from" length is known (>0),
// 2) or "to" length is unknown (0 means unknown length?).
// Case 1 and case 2 mean that cast will do nothing.
// In other cases, cast is required to trim the "from" according to "to" length.
return (to.getPrecision() >= from.getPrecision() && from.getPrecision() > 0) || to.getPrecision() == 0;
default:
errorCollector.addGeneralError(pos, String.format("Casting rules are unknown for type %s.", from));
return false;
}
}
}
}