diff options
author | Volodymyr Vysotskyi <vvovyk@gmail.com> | 2019-01-22 00:18:19 +0200 |
---|---|---|
committer | Volodymyr Vysotskyi <vvovyk@gmail.com> | 2019-01-25 17:23:46 +0200 |
commit | c230ba55cceb6d48ac9c1ab0701a167d91842a11 (patch) | |
tree | face3767754e9fba81484e8a12098f3b421963be | |
parent | 72cba88f058b072040c701a7e1dbbe4fc4eb8d48 (diff) |
DRILL-6533: Allow using literal values in functions which expect FieldReader instead of ValueHolder
closes #1617
3 files changed, 187 insertions, 26 deletions
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/interpreter/InterpreterEvaluator.java b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/interpreter/InterpreterEvaluator.java index a0373d9a7..7648ff4f4 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/interpreter/InterpreterEvaluator.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/interpreter/InterpreterEvaluator.java @@ -24,6 +24,7 @@ import java.util.Objects; import javax.annotation.Nullable; import javax.inject.Inject; +import org.apache.drill.exec.expr.BasicTypeHelper; import org.apache.drill.shaded.guava.com.google.common.base.Function; import org.apache.drill.common.exceptions.DrillRuntimeException; import org.apache.drill.common.expression.BooleanOperator; @@ -89,7 +90,17 @@ public class InterpreterEvaluator { } - public static ValueHolder evaluateFunction(DrillSimpleFunc interpreter, ValueHolder[] args, String funcName) throws Exception { + /** + * 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 @@ -100,13 +111,13 @@ public class InterpreterEvaluator { 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 ) { + if (f.getAnnotation(Param.class) != null) { f.setAccessible(true); if (currParameterIndex < args.length) { f.set(interpreter, args[currParameterIndex]); } currParameterIndex++; - } else if ( f.getAnnotation(Output.class) != null ) { + } 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 @@ -127,9 +138,8 @@ public class InterpreterEvaluator { } interpreter.setup(); interpreter.eval(); - ValueHolder out = (ValueHolder) outField.get(interpreter); - return out; + return (ValueHolder) outField.get(interpreter); } private static class InitVisitor extends AbstractExprVisitor<LogicalExpression, VectorAccessible, RuntimeException> { @@ -307,25 +317,36 @@ public class InterpreterEvaluator { DrillSimpleFuncHolder holder = (DrillSimpleFuncHolder) holderExpr.getHolder(); - ValueHolder [] args = new ValueHolder [holderExpr.args.size()]; + // 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++) { - args[i] = holderExpr.args.get(i).accept(this, inIndex); + ValueHolder valueHolder = holderExpr.args.get(i).accept(this, inIndex); + Object resultArg = valueHolder; + TypeProtos.MajorType argType = TypeHelper.getValueHolderType(valueHolder); + TypeProtos.MajorType holderParamType = holder.getParameters()[i].getType(); // 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 (holder.getParameters()[i].getType().getMode() == TypeProtos.DataMode.REQUIRED && TypeHelper.getValueHolderType(args[i]).getMode() == TypeProtos.DataMode.OPTIONAL) { + 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(args[i])) { + if (TypeHelper.isNull(valueHolder)) { return TypeHelper.createValueHolder(holderExpr.getMajorType()); } else { // Case 1.2: argument is nullable but not null value, deNullify it. - args[i] = TypeHelper.deNullify(args[i]); + resultArg = TypeHelper.deNullify(valueHolder); } - } else if (holder.getParameters()[i].getType().getMode() == TypeProtos.DataMode.OPTIONAL && TypeHelper.getValueHolderType(args[i]).getMode() == TypeProtos.DataMode.REQUIRED) { + } else if (holderParamType.getMode() == TypeProtos.DataMode.OPTIONAL + && argType.getMode() == TypeProtos.DataMode.REQUIRED) { // Case 2: parameter is nullable, argument is non-nullable. Nullify it. - args[i] = TypeHelper.nullify(args[i]); + resultArg = TypeHelper.nullify(valueHolder); } } + if (holder.getParameters()[i].isFieldReader()) { + resultArg = BasicTypeHelper.getHolderReaderImpl(argType, valueHolder); + } + args[i] = resultArg; } try { @@ -333,10 +354,11 @@ public class InterpreterEvaluator { ValueHolder out = evaluateFunction(interpreter, args, holderExpr.getName()); - if (TypeHelper.getValueHolderType(out).getMode() == TypeProtos.DataMode.OPTIONAL && + TypeProtos.MajorType outputType = TypeHelper.getValueHolderType(out); + if (outputType.getMode() == TypeProtos.DataMode.OPTIONAL && holderExpr.getMajorType().getMode() == TypeProtos.DataMode.REQUIRED) { return TypeHelper.deNullify(out); - } else if (TypeHelper.getValueHolderType(out).getMode() == TypeProtos.DataMode.REQUIRED && + } else if (outputType.getMode() == TypeProtos.DataMode.REQUIRED && holderExpr.getMajorType().getMode() == TypeProtos.DataMode.OPTIONAL) { return TypeHelper.nullify(out); } else { diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/impl/TestTypeFns.java b/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/impl/TestTypeFns.java index 02d664f53..46e321648 100644 --- a/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/impl/TestTypeFns.java +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/expr/fn/impl/TestTypeFns.java @@ -75,19 +75,19 @@ public class TestTypeFns extends ClusterTest { // typeof() returns types using the internal names. String sql = "SELECT typeof(CAST(a AS " + castType + ")) FROM (VALUES (1)) AS T(a)"; - String result = client.queryBuilder().sql(sql).singletonString(); + String result = queryBuilder().sql(sql).singletonString(); assertEquals(resultType, result); // For typeof(), null values annoyingly report a type of "NULL" sql = "SELECT typeof(CAST(a AS " + castType + ")) FROM cp.`functions/null.json`"; - result = client.queryBuilder().sql(sql).singletonString(); + result = queryBuilder().sql(sql).singletonString(); assertEquals("NULL", result); } private void doTypeOfTestSpecial(String expr, String value, String resultType) throws RpcException { String sql = "SELECT typeof(" + expr + ") FROM (VALUES (" + value + ")) AS T(a)"; - String result = client.queryBuilder().sql(sql).singletonString(); + String result = queryBuilder().sql(sql).singletonString(); assertEquals(resultType, result); } @@ -124,19 +124,25 @@ public class TestTypeFns extends ClusterTest { // sqlTypeOf() returns SQL type names: the names used in CAST. String sql = "SELECT sqlTypeOf(CAST(a AS " + type + ")) FROM (VALUES (1)) AS T(a)"; - String result = client.queryBuilder().sql(sql).singletonString(); + String result = queryBuilder().sql(sql).singletonString(); + assertEquals(type, result); + + // sqlTypeOf() returns SQL type names: the names used in CAST. + + sql = "SELECT sqlTypeOf(CAST(1 AS " + type + "))"; + result = queryBuilder().sql(sql).singletonString(); assertEquals(type, result); // Returns same type even value is null. sql = "SELECT sqlTypeOf(CAST(a AS " + type + ")) FROM cp.`functions/null.json`"; - result = client.queryBuilder().sql(sql).singletonString(); + result = queryBuilder().sql(sql).singletonString(); assertEquals(type, result); } private void doSqlTypeOfTestSpecial(String expr, String value, String resultType) throws RpcException { String sql = "SELECT sqlTypeof(" + expr + ") FROM (VALUES (" + value + ")) AS T(a)"; - String result = client.queryBuilder().sql(sql).singletonString(); + String result = queryBuilder().sql(sql).singletonString(); assertEquals(resultType, result); } @@ -163,13 +169,17 @@ public class TestTypeFns extends ClusterTest { // drillTypeOf() returns types using the internal names. String sql = "SELECT drillTypeOf(CAST(a AS " + castType + ")) FROM (VALUES (1)) AS T(a)"; - String result = client.queryBuilder().sql(sql).singletonString(); + String result = queryBuilder().sql(sql).singletonString(); + assertEquals(resultType, result); + + sql = "SELECT drillTypeOf(CAST(1 AS " + castType + "))"; + result = queryBuilder().sql(sql).singletonString(); assertEquals(resultType, result); // Returns same type even value is null. sql = "SELECT drillTypeOf(CAST(a AS " + castType + ")) FROM cp.`functions/null.json`"; - result = client.queryBuilder().sql(sql).singletonString(); + result = queryBuilder().sql(sql).singletonString(); assertEquals(resultType, result); } @@ -179,19 +189,120 @@ public class TestTypeFns extends ClusterTest { // CSV files with headers use REQUIRED mode String sql = "SELECT modeOf(`name`) FROM cp.`store/text/data/cars.csvh`"; - String result = client.queryBuilder().sql(sql).singletonString(); + String result = queryBuilder().sql(sql).singletonString(); assertEquals("NOT NULL", result); // CSV files without headers use REPEATED mode sql = "SELECT modeOf(`columns`) FROM cp.`textinput/input2.csv`"; - result = client.queryBuilder().sql(sql).singletonString(); + result = queryBuilder().sql(sql).singletonString(); assertEquals("ARRAY", result); // JSON files use OPTIONAL mode sql = "SELECT modeOf(`name`) FROM cp.`jsoninput/specialchar.json`"; - result = client.queryBuilder().sql(sql).singletonString(); + result = queryBuilder().sql(sql).singletonString(); assertEquals("NULLABLE", result); } + + @Test + public void testTypeOfLiteral() throws Exception { + String sql = + "SELECT typeOf(1) c1," + + "typeOf('a') c2," + + "typeOf(date '2018-01-22') c3," + + "typeOf(time '01:00:20.123') c4," + + "typeOf(timestamp '2018-01-22 01:00:20.123') c5," + + "typeOf(false) c6," + + "typeOf(12.3) c7," + + "typeOf(1>2) c8," + + "typeOf(cast(null as int)) c9"; + + testBuilder() + .sqlQuery(sql) + .unOrdered() + .baselineColumns("c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9") + .baselineValues("INT", "VARCHAR", "DATE", "TIME", "TIMESTAMP", "BIT", "VARDECIMAL", "BIT", "NULL") + .go(); + } + + @Test + public void testSqlTypeOfLiteral() throws Exception { + String sql = + "SELECT sqlTypeOf(1) c1," + + "sqlTypeOf('a') c2," + + "sqlTypeOf(date '2018-01-22') c3," + + "sqlTypeOf(time '01:00:20.123') c4," + + "sqlTypeOf(timestamp '2018-01-22 01:00:20.123') c5," + + "sqlTypeOf(false) c6," + + "sqlTypeOf(12.3) c7," + + "sqlTypeOf(1>2) c8," + + "sqlTypeOf(cast(null as int)) c9"; + + testBuilder() + .sqlQuery(sql) + .unOrdered() + .baselineColumns("c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9") + .baselineValues("INTEGER", "CHARACTER VARYING", "DATE", "TIME", + "TIMESTAMP", "BOOLEAN", "DECIMAL(3, 1)", "BOOLEAN", "INTEGER") + .go(); + } + + @Test + public void testDrillTypeOfLiteral() throws Exception { + String sql = + "SELECT drillTypeOf(1) c1," + + "drillTypeOf('a') c2," + + "drillTypeOf(date '2018-01-22') c3," + + "drillTypeOf(time '01:00:20.123') c4," + + "drillTypeOf(timestamp '2018-01-22 01:00:20.123') c5," + + "drillTypeOf(false) c6," + + "drillTypeOf(12.3) c7," + + "drillTypeOf(1>2) c8," + + "drillTypeOf(cast(null as int)) c9"; + + testBuilder() + .sqlQuery(sql) + .unOrdered() + .baselineColumns("c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9") + .baselineValues("INT", "VARCHAR", "DATE", "TIME", + "TIMESTAMP", "BIT", "VARDECIMAL", "BIT", "INT") + .go(); + } + + @Test + public void testModeOfLiteral() throws Exception { + String sql = + "SELECT modeOf(1) c1," + + "modeOf('a') c2," + + "modeOf(cast(null as int)) c3," + + "modeOf(case when true then null else 'a' end) c4," + + "modeOf(case when false then null else 'a' end) c5"; + + testBuilder() + .sqlQuery(sql) + .unOrdered() + .baselineColumns("c1", "c2", "c3", "c4", "c5") + .baselineValues("NOT NULL", "NOT NULL", "NULLABLE", "NULLABLE", "NULLABLE") + .go(); + } + + @Test + public void testCompareTypeLiteral() throws Exception { + String sql = + "SELECT compareType(1, 2) c1," + + "compareType('a', 1) c2," + + "compareType(1, 'a') c3," + + "compareType(a, '01:00:20.123') c4," + + "compareType(3, t.a) c5," + + "compareType(t.a, 3) c6\n" + + "from (values(1)) t(a)"; + + testBuilder() + .sqlQuery(sql) + .unOrdered() + .baselineColumns("c1", "c2", "c3", "c4", "c5", "c6") + .baselineValues(0, 1, -1, -1, 0, 0) + .go(); + } } diff --git a/exec/vector/src/main/codegen/templates/BasicTypeHelper.java b/exec/vector/src/main/codegen/templates/BasicTypeHelper.java index 430a41b43..383e195a0 100644 --- a/exec/vector/src/main/codegen/templates/BasicTypeHelper.java +++ b/exec/vector/src/main/codegen/templates/BasicTypeHelper.java @@ -207,7 +207,35 @@ public class BasicTypeHelper { throw new UnsupportedOperationException(buildErrorMessage("get writer implementation", type, mode)); } - public static Class<?> getHolderReaderImpl( MinorType type, DataMode mode){ + /** + * Creates and returns {@link FieldReader} instance for specified {@code MajorType type} using specisied {@code ValueHolder} + * + * @param type type of resulting {@link FieldReader} instance + * @param holder value holder for {@link FieldReader} creation + * @return {@link FieldReader} instance + */ + public static FieldReader getHolderReaderImpl(MajorType type, ValueHolder holder) { + switch (type.getMinorType()) { + <#list vv.types as type> + <#list type.minor as minor> + case ${minor.class?upper_case}: + switch (type.getMode()) { + case REQUIRED: + return new ${minor.class}HolderReaderImpl((${minor.class}Holder) holder); + case OPTIONAL: + return new Nullable${minor.class}HolderReaderImpl((Nullable${minor.class}Holder) holder); + case REPEATED: + return new Repeated${minor.class}HolderReaderImpl((Repeated${minor.class}Holder) holder); + } + </#list> + </#list> + case NULL: + return new UntypedHolderReaderImpl((UntypedNullHolder) holder); + } + throw new UnsupportedOperationException(buildErrorMessage("get holder reader implementation", type.getMinorType(), type.getMode())); + } + + public static Class<?> getHolderReaderImpl(MinorType type, DataMode mode) { switch (type) { <#list vv.types as type> <#list type.minor as minor> |