blob: 7a7ce342899722b07021047b2b85a62fe49ebb01 [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.fn.interpreter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Objects;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.apache.drill.exec.expr.BasicTypeHelper;
import com.google.common.base.Function;
import org.apache.drill.common.FunctionNames;
import org.apache.drill.common.exceptions.DrillRuntimeException;
import org.apache.drill.common.expression.BooleanOperator;
import org.apache.drill.common.expression.ConvertExpression;
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.LogicalExpression;
import org.apache.drill.common.expression.NullExpression;
import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.common.expression.TypedNullConstant;
import org.apache.drill.common.expression.ValueExpressions;
import org.apache.drill.common.expression.visitors.AbstractExprVisitor;
import org.apache.drill.common.types.TypeProtos;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.exec.expr.DrillFuncHolderExpr;
import org.apache.drill.exec.expr.DrillSimpleFunc;
import org.apache.drill.exec.expr.TypeHelper;
import org.apache.drill.exec.expr.ValueVectorReadExpression;
import org.apache.drill.exec.expr.annotations.FunctionTemplate;
import org.apache.drill.exec.expr.annotations.Output;
import org.apache.drill.exec.expr.annotations.Param;
import org.apache.drill.exec.expr.fn.DrillSimpleFuncHolder;
import org.apache.drill.exec.expr.holders.BitHolder;
import org.apache.drill.exec.expr.holders.NullableBitHolder;
import org.apache.drill.exec.expr.holders.ValueHolder;
import org.apache.drill.exec.ops.UdfUtilities;
import org.apache.drill.exec.record.RecordBatch;
import org.apache.drill.exec.record.VectorAccessible;
import org.apache.drill.exec.vector.ValueHolderHelper;
import org.apache.drill.exec.vector.ValueVector;
import com.google.common.base.Preconditions;
import io.netty.buffer.DrillBuf;
public class InterpreterEvaluator {
public static ValueHolder evaluateConstantExpr(UdfUtilities udfUtilities, LogicalExpression expr) {
InitVisitor initVisitor = new InitVisitor(udfUtilities);
EvalVisitor evalVisitor = new EvalVisitor(null, udfUtilities);
expr.accept(initVisitor, null);
return expr.accept(evalVisitor, -1);
}
public static void evaluate(RecordBatch incoming, ValueVector outVV, LogicalExpression expr) {
evaluate(incoming.getRecordCount(), incoming.getContext(), incoming, outVV, expr);
}
public static void evaluate(int recordCount, UdfUtilities udfUtilities, VectorAccessible incoming, ValueVector outVV, LogicalExpression expr) {
InitVisitor initVisitor = new InitVisitor(udfUtilities);
EvalVisitor evalVisitor = new EvalVisitor(incoming, udfUtilities);
expr.accept(initVisitor, incoming);
for (int i = 0; i < recordCount; i++) {
ValueHolder out = expr.accept(evalVisitor, i);
TypeHelper.setValueSafe(outVV, i, out);
}
outVV.getMutator().setValueCount(recordCount);
}
/**
* Assigns specified {@code Object[] args} to the function arguments,
* evaluates function and returns its result.
*
* @param interpreter function to be evaluated
* @param args function arguments
* @param funcName name of the function
* @return result of function call stored in {@link ValueHolder}
* @throws Exception if {@code args} types does not match function input arguments types
*/
public static ValueHolder evaluateFunction(DrillSimpleFunc interpreter, Object[] args, String funcName) throws Exception {
Preconditions.checkArgument(interpreter != null,
"interpreter could not be null when use interpreted model to evaluate function " + funcName);
// the current input index to assign into the next available parameter, found using the @Param notation
// the order parameters are declared in the java class for the DrillFunc is meaningful
int currParameterIndex = 0;
Field outField = null;
try {
Field[] fields = interpreter.getClass().getDeclaredFields();
for (Field f : fields) {
// if this is annotated as a parameter to the function
if (f.getAnnotation(Param.class) != null) {
f.setAccessible(true);
if (f.getType().getComponentType() != null) {
Object array = Array.newInstance(f.getType().getComponentType(), args.length - currParameterIndex);
for (int i = 0; i < args.length - currParameterIndex; i++) {
Array.set(array, i, args[currParameterIndex + i]);
}
currParameterIndex = args.length;
f.set(interpreter, array);
} else if (currParameterIndex < args.length) {
f.set(interpreter, args[currParameterIndex]);
currParameterIndex++;
}
} else if (f.getAnnotation(Output.class) != null) {
f.setAccessible(true);
outField = f;
// create an instance of the holder for the output to be stored in
f.set(interpreter, f.getType().newInstance());
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
if (args.length != currParameterIndex ) {
throw new DrillRuntimeException(
String.format("Wrong number of parameters provided to interpreted expression evaluation " +
"for function %s, expected %d parameters, but received %d.",
funcName, currParameterIndex, args.length));
}
if (outField == null) {
throw new DrillRuntimeException("Malformed DrillFunction without a return type: " + funcName);
}
interpreter.setup();
interpreter.eval();
return (ValueHolder) outField.get(interpreter);
}
private static class InitVisitor extends AbstractExprVisitor<LogicalExpression, VectorAccessible, RuntimeException> {
private final UdfUtilities udfUtilities;
protected InitVisitor(UdfUtilities udfUtilities) {
super();
this.udfUtilities = udfUtilities;
}
@Override
public LogicalExpression visitFunctionHolderExpression(FunctionHolderExpression holderExpr, VectorAccessible incoming) {
if (!(holderExpr.getHolder() instanceof DrillSimpleFuncHolder)) {
throw new UnsupportedOperationException("Only Drill simple UDF can be used in interpreter mode!");
}
DrillSimpleFuncHolder holder = (DrillSimpleFuncHolder) holderExpr.getHolder();
for (int i = 0; i < holderExpr.args.size(); i++) {
holderExpr.args.get(i).accept(this, incoming);
}
try {
DrillSimpleFunc interpreter = holder.createInterpreter();
Field[] fields = interpreter.getClass().getDeclaredFields();
for (Field f : fields) {
if (f.getAnnotation(Inject.class) != null) {
f.setAccessible(true);
Class<?> fieldType = f.getType();
if (UdfUtilities.INJECTABLE_GETTER_METHODS.get(fieldType) != null) {
Method method = udfUtilities.getClass().getMethod(UdfUtilities.INJECTABLE_GETTER_METHODS.get(fieldType));
f.set(interpreter, method.invoke(udfUtilities));
} else {
// Invalid injectable type provided, this should have been caught in FunctionConverter
throw new DrillRuntimeException("Invalid injectable type requested in UDF: " + fieldType.getSimpleName());
}
} else { // do nothing with non-inject fields here
continue;
}
}
((DrillFuncHolderExpr) holderExpr).setInterpreter(interpreter);
return holderExpr;
} catch (Exception ex) {
throw new RuntimeException("Error in evaluating function of " + holderExpr.getName() + ": ", ex);
}
}
@Override
public LogicalExpression visitUnknown(LogicalExpression e, VectorAccessible incoming) throws RuntimeException {
for (LogicalExpression child : e) {
child.accept(this, incoming);
}
return e;
}
}
public static class EvalVisitor extends AbstractExprVisitor<ValueHolder, Integer, RuntimeException> {
private final VectorAccessible incoming;
private final UdfUtilities udfUtilities;
protected EvalVisitor(VectorAccessible incoming, UdfUtilities udfUtilities) {
super();
this.incoming = incoming;
this.udfUtilities = udfUtilities;
}
@Override
public ValueHolder visitFunctionCall(FunctionCall call, Integer value) throws RuntimeException {
return visitUnknown(call, value);
}
@Override
public ValueHolder visitSchemaPath(SchemaPath path,Integer value) throws RuntimeException {
return visitUnknown(path, value);
}
@Override
public ValueHolder visitDecimal9Constant(ValueExpressions.Decimal9Expression decExpr,Integer value) throws RuntimeException {
return ValueHolderHelper.getDecimal9Holder(decExpr.getIntFromDecimal(), decExpr.getPrecision(), decExpr.getScale());
}
@Override
public ValueHolder visitDecimal18Constant(ValueExpressions.Decimal18Expression decExpr,Integer value) throws RuntimeException {
return ValueHolderHelper.getDecimal18Holder(decExpr.getLongFromDecimal(), decExpr.getPrecision(), decExpr.getScale());
}
@Override
public ValueHolder visitDecimal28Constant(final ValueExpressions.Decimal28Expression decExpr,Integer value) throws RuntimeException {
return getConstantValueHolder(decExpr.getBigDecimal().toString(), decExpr.getMajorType().getMinorType(), new Function<DrillBuf, ValueHolder>() {
@Nullable
@Override
public ValueHolder apply(DrillBuf buffer) {
return ValueHolderHelper.getDecimal28Holder(buffer, decExpr.getBigDecimal());
}
});
}
@Override
public ValueHolder visitDecimal38Constant(final ValueExpressions.Decimal38Expression decExpr,Integer value) throws RuntimeException {
return getConstantValueHolder(decExpr.getBigDecimal().toString(), decExpr.getMajorType().getMinorType(), new Function<DrillBuf, ValueHolder>() {
@Nullable
@Override
public ValueHolder apply(DrillBuf buffer) {
return ValueHolderHelper.getDecimal38Holder(buffer, decExpr.getBigDecimal());
}
});
}
@Override
public ValueHolder visitVarDecimalConstant(final ValueExpressions.VarDecimalExpression decExpr, Integer value) throws RuntimeException {
return getConstantValueHolder(decExpr.getBigDecimal().toString(), decExpr.getMajorType().getMinorType(),
buffer -> ValueHolderHelper.getVarDecimalHolder(Objects.requireNonNull(buffer), decExpr.getBigDecimal()));
}
@Override
public ValueHolder visitDateConstant(ValueExpressions.DateExpression dateExpr,Integer value) throws RuntimeException {
return ValueHolderHelper.getDateHolder(dateExpr.getDate());
}
@Override
public ValueHolder visitTimeConstant(ValueExpressions.TimeExpression timeExpr,Integer value) throws RuntimeException {
return ValueHolderHelper.getTimeHolder(timeExpr.getTime());
}
@Override
public ValueHolder visitTimeStampConstant(ValueExpressions.TimeStampExpression timestampExpr,Integer value) throws RuntimeException {
return ValueHolderHelper.getTimeStampHolder(timestampExpr.getTimeStamp());
}
@Override
public ValueHolder visitIntervalYearConstant(ValueExpressions.IntervalYearExpression intExpr,Integer value) throws RuntimeException {
return ValueHolderHelper.getIntervalYearHolder(intExpr.getIntervalYear());
}
@Override
public ValueHolder visitIntervalDayConstant(ValueExpressions.IntervalDayExpression intExpr,Integer value) throws RuntimeException {
return ValueHolderHelper.getIntervalDayHolder(intExpr.getIntervalDay(), intExpr.getIntervalMillis());
}
@Override
public ValueHolder visitBooleanConstant(ValueExpressions.BooleanExpression e,Integer value) throws RuntimeException {
return ValueHolderHelper.getBitHolder(e.getBoolean() == false ? 0 : 1);
}
@Override
public ValueHolder visitNullConstant(TypedNullConstant e,Integer value) throws RuntimeException {
// create a value holder for the given type, defaults to NULL value if not set
return TypeHelper.createValueHolder(e.getMajorType());
}
// TODO - review what to do with these
// **********************************
@Override
public ValueHolder visitConvertExpression(ConvertExpression e,Integer value) throws RuntimeException {
return visitUnknown(e, value);
}
@Override
public ValueHolder visitNullExpression(NullExpression e,Integer value) throws RuntimeException {
return visitUnknown(e, value);
}
// TODO - review what to do with these (2 functions above)
//********************************************
@Override
public ValueHolder visitFunctionHolderExpression(FunctionHolderExpression holderExpr, Integer inIndex) {
if (! (holderExpr.getHolder() instanceof DrillSimpleFuncHolder)) {
throw new UnsupportedOperationException("Only Drill simple UDF can be used in interpreter mode!");
}
DrillSimpleFuncHolder holder = (DrillSimpleFuncHolder) holderExpr.getHolder();
// function arguments may have different types:
// usually ValueHolder inheritors but sometimes FieldReader ones
Object[] args = new Object[holderExpr.args.size()];
for (int i = 0; i < holderExpr.args.size(); i++) {
ValueHolder valueHolder = holderExpr.args.get(i).accept(this, inIndex);
Object resultArg = valueHolder;
TypeProtos.MajorType argType = TypeHelper.getValueHolderType(valueHolder);
TypeProtos.MajorType holderParamType = holder.getParamMajorType(i);
// In case function use "NULL_IF_NULL" policy.
if (holder.getNullHandling() == FunctionTemplate.NullHandling.NULL_IF_NULL) {
// Case 1: parameter is non-nullable, argument is nullable.
if (holderParamType.getMode() == TypeProtos.DataMode.REQUIRED
&& argType.getMode() == TypeProtos.DataMode.OPTIONAL) {
// Case 1.1 : argument is null, return null value holder directly.
if (TypeHelper.isNull(valueHolder)) {
return TypeHelper.createValueHolder(holderExpr.getMajorType());
} else {
// Case 1.2: argument is nullable but not null value, deNullify it.
resultArg = TypeHelper.deNullify(valueHolder);
}
} else if (holderParamType.getMode() == TypeProtos.DataMode.OPTIONAL
&& argType.getMode() == TypeProtos.DataMode.REQUIRED) {
// Case 2: parameter is nullable, argument is non-nullable. Nullify it.
resultArg = TypeHelper.nullify(valueHolder);
}
}
if (holder.getAttributeParameter(i).isFieldReader()) {
resultArg = BasicTypeHelper.getHolderReaderImpl(argType, valueHolder);
}
args[i] = resultArg;
}
try {
DrillSimpleFunc interpreter = ((DrillFuncHolderExpr) holderExpr).getInterpreter();
ValueHolder out = evaluateFunction(interpreter, args, holderExpr.getName());
TypeProtos.MajorType outputType = TypeHelper.getValueHolderType(out);
if (outputType.getMode() == TypeProtos.DataMode.OPTIONAL &&
holderExpr.getMajorType().getMode() == TypeProtos.DataMode.REQUIRED) {
return TypeHelper.deNullify(out);
} else if (outputType.getMode() == TypeProtos.DataMode.REQUIRED &&
holderExpr.getMajorType().getMode() == TypeProtos.DataMode.OPTIONAL) {
return TypeHelper.nullify(out);
} else {
return out;
}
} catch (Exception ex) {
throw new RuntimeException("Error in evaluating function of " + holderExpr.getName(), ex);
}
}
@Override
public ValueHolder visitBooleanOperator(BooleanOperator op, Integer inIndex) {
// Apply short circuit evaluation to boolean operator.
if (op.getName().equals(FunctionNames.AND)) {
return visitBooleanAnd(op, inIndex);
} else if(op.getName().equals(FunctionNames.OR)) {
return visitBooleanOr(op, inIndex);
} else {
throw new UnsupportedOperationException(
String.format("BooleanOperator can only be %s, %s. You are using %s",
FunctionNames.AND, FunctionNames.OR, op.getName()));
}
}
@Override
public ValueHolder visitIfExpression(IfExpression ifExpr, Integer inIndex) throws RuntimeException {
ValueHolder condHolder = ifExpr.ifCondition.condition.accept(this, inIndex);
Preconditions.checkArgument (condHolder instanceof BitHolder || condHolder instanceof NullableBitHolder,
"IfExpression's condition does not have type of BitHolder or NullableBitHolder.");
Trivalent flag = isBitOn(condHolder);
switch (flag) {
case TRUE:
return ifExpr.ifCondition.expression.accept(this, inIndex);
case FALSE:
case NULL:
return ifExpr.elseExpression.accept(this, inIndex);
default:
throw new UnsupportedOperationException("No other possible choice. Something is not right");
}
}
@Override
public ValueHolder visitIntConstant(ValueExpressions.IntExpression e, Integer inIndex) throws RuntimeException {
return ValueHolderHelper.getIntHolder(e.getInt());
}
@Override
public ValueHolder visitFloatConstant(ValueExpressions.FloatExpression fExpr, Integer value) throws RuntimeException {
return ValueHolderHelper.getFloat4Holder(fExpr.getFloat());
}
@Override
public ValueHolder visitLongConstant(ValueExpressions.LongExpression intExpr, Integer value) throws RuntimeException {
return ValueHolderHelper.getBigIntHolder(intExpr.getLong());
}
@Override
public ValueHolder visitDoubleConstant(ValueExpressions.DoubleExpression dExpr, Integer value) throws RuntimeException {
return ValueHolderHelper.getFloat8Holder(dExpr.getDouble());
}
@Override
public ValueHolder visitQuotedStringConstant(final ValueExpressions.QuotedString e, Integer value) throws RuntimeException {
return getConstantValueHolder(e.value, e.getMajorType().getMinorType(), new Function<DrillBuf, ValueHolder>() {
@Nullable
@Override
public ValueHolder apply(DrillBuf buffer) {
return ValueHolderHelper.getVarCharHolder(buffer, e.value);
}
});
}
@Override
public ValueHolder visitUnknown(LogicalExpression e, Integer inIndex) throws RuntimeException {
if (e instanceof ValueVectorReadExpression) {
return visitValueVectorReadExpression((ValueVectorReadExpression) e, inIndex);
} else {
return super.visitUnknown(e, inIndex);
}
}
protected ValueHolder visitValueVectorReadExpression(ValueVectorReadExpression e, Integer inIndex)
throws RuntimeException {
TypeProtos.MajorType type = e.getMajorType();
ValueVector vv;
ValueHolder holder;
try {
switch (type.getMode()) {
case OPTIONAL:
case REQUIRED:
vv = incoming.getValueAccessorById(TypeHelper.getValueVectorClass(type.getMinorType(),type.getMode()), e.getFieldId().getFieldIds()).getValueVector();
holder = TypeHelper.getValue(vv, inIndex.intValue());
return holder;
default:
throw new UnsupportedOperationException("Type of " + type + " is not supported yet in interpreted expression evaluation!");
}
} catch (Exception ex){
throw new DrillRuntimeException("Error when evaluate a ValueVectorReadExpression: " + ex);
}
}
// Use Kleene algebra for three-valued logic :
// value of boolean "and" when one side is null
// p q p and q
// true null null
// false null false
// null true null
// null false false
// null null null
// "and" : 1) if any argument is false, return false. false is earlyExitValue.
// 2) if none argument is false, but at least one is null, return null.
// 3) finally, return true (finalValue).
private ValueHolder visitBooleanAnd(BooleanOperator op, Integer inIndex) {
ValueHolder [] args = new ValueHolder [op.argCount()];
boolean hasNull = false;
for (int i = 0; i < op.argCount(); i++) {
args[i] = op.arg(i).accept(this, inIndex);
Trivalent flag = isBitOn(args[i]);
switch (flag) {
case FALSE:
return op.getMajorType().getMode() == TypeProtos.DataMode.OPTIONAL? TypeHelper.nullify(ValueHolderHelper.getBitHolder(0)) : ValueHolderHelper.getBitHolder(0);
case NULL:
hasNull = true;
case TRUE:
}
}
if (hasNull) {
return ValueHolderHelper.getNullableBitHolder(true, 0);
} else {
return op.getMajorType().getMode() == TypeProtos.DataMode.OPTIONAL? TypeHelper.nullify(ValueHolderHelper.getBitHolder(1)) : ValueHolderHelper.getBitHolder(1);
}
}
// value of boolean "or" when one side is null
// p q p and q
// true null true
// false null null
// null true true
// null false null
// null null null
private ValueHolder visitBooleanOr(BooleanOperator op, Integer inIndex) {
ValueHolder [] args = new ValueHolder [op.argCount()];
boolean hasNull = false;
for (int i = 0; i < op.argCount(); i++) {
args[i] = op.arg(i).accept(this, inIndex);
Trivalent flag = isBitOn(args[i]);
switch (flag) {
case TRUE:
return op.getMajorType().getMode() == TypeProtos.DataMode.OPTIONAL? TypeHelper.nullify(ValueHolderHelper.getBitHolder(1)) : ValueHolderHelper.getBitHolder(1);
case NULL:
hasNull = true;
case FALSE:
}
}
if (hasNull) {
return ValueHolderHelper.getNullableBitHolder(true, 0);
} else {
return op.getMajorType().getMode() == TypeProtos.DataMode.OPTIONAL? TypeHelper.nullify(ValueHolderHelper.getBitHolder(0)) : ValueHolderHelper.getBitHolder(0);
}
}
public enum Trivalent {
FALSE,
TRUE,
NULL
}
private Trivalent isBitOn(ValueHolder holder) {
Preconditions.checkArgument(holder instanceof BitHolder || holder instanceof NullableBitHolder,
"Input does not have type of BitHolder or NullableBitHolder.");
if ( (holder instanceof BitHolder && ((BitHolder) holder).value == 1)) {
return Trivalent.TRUE;
} else if (holder instanceof NullableBitHolder && ((NullableBitHolder) holder).isSet == 1 && ((NullableBitHolder) holder).value == 1) {
return Trivalent.TRUE;
} else if (holder instanceof NullableBitHolder && ((NullableBitHolder) holder).isSet == 0) {
return Trivalent.NULL;
} else {
return Trivalent.FALSE;
}
}
private ValueHolder getConstantValueHolder(String value, MinorType type, Function<DrillBuf, ValueHolder> holderInitializer) {
return udfUtilities.getConstantValueHolder(value, type, holderInitializer);
}
}
}