Add tinyint,smallint,time,date support for UDFs patch by Robert Stupp, reviewed by Sam Tunnicliffe CASSANDRA-9400
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/30be921d Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/30be921d Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/30be921d Branch: refs/heads/trunk Commit: 30be921d44ed62f0c29a40dc841190519f84cffc Parents: 0022e15 Author: Robert Stupp <sn...@snazy.de> Authored: Thu Jun 4 17:06:02 2015 +0200 Committer: Robert Stupp <sn...@snazy.de> Committed: Thu Jun 4 17:07:47 2015 +0200 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/functions/ScriptBasedUDF.java | 4 + .../cassandra/cql3/functions/UDFunction.java | 14 + test/unit/org/apache/cassandra/cql3/UFTest.java | 317 ++++++++++--------- 4 files changed, 188 insertions(+), 148 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/30be921d/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 47ed221..43a6cc5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.2 + * Add tinyint,smallint,time,date support for UDFs (CASSANDRA-9400) * Deprecates SSTableSimpleWriter and SSTableSimpleUnsortedWriter (CASSANDRA-9546) * Empty INITCOND treated as null in aggregate (CASSANDRA-9457) * Remove use of Cell in Thrift MapReduce classes (CASSANDRA-8609) http://git-wip-us.apache.org/repos/asf/cassandra/blob/30be921d/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java index 319c948..4d9a79f 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java +++ b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java @@ -114,6 +114,10 @@ public class ScriptBasedUDF extends UDFunction Number rNumber = (Number) result; if (javaReturnType == Integer.class) result = rNumber.intValue(); + else if (javaReturnType == Short.class) + result = rNumber.shortValue(); + else if (javaReturnType == Byte.class) + result = rNumber.byteValue(); else if (javaReturnType == Long.class) result = rNumber.longValue(); else if (javaReturnType == Float.class) http://git-wip-us.apache.org/repos/asf/cassandra/blob/30be921d/src/java/org/apache/cassandra/cql3/functions/UDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java index 0bf6078..aa6d555 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -212,6 +212,20 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct } // do not remove - used by generated Java UDFs + protected byte compose_byte(int protocolVersion, int argIndex, ByteBuffer value) + { + assert value != null && value.remaining() > 0; + return (byte)DataType.tinyint().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + } + + // do not remove - used by generated Java UDFs + protected short compose_short(int protocolVersion, int argIndex, ByteBuffer value) + { + assert value != null && value.remaining() > 0; + return (short)DataType.smallint().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + } + + // do not remove - used by generated Java UDFs protected int compose_int(int protocolVersion, int argIndex, ByteBuffer value) { assert value != null && value.remaining() > 0; http://git-wip-us.apache.org/repos/asf/cassandra/blob/30be921d/test/unit/org/apache/cassandra/cql3/UFTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFTest.java b/test/unit/org/apache/cassandra/cql3/UFTest.java index f041b3a..db94a4c 100644 --- a/test/unit/org/apache/cassandra/cql3/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/UFTest.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.UUID; import org.junit.Assert; import org.junit.Test; @@ -44,6 +45,7 @@ import org.apache.cassandra.transport.Event; import org.apache.cassandra.transport.Server; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.UUIDGen; public class UFTest extends CQLTester { @@ -1872,6 +1874,10 @@ public class UFTest extends CQLTester Object[][] variations = { new Object[] { "true", "boolean", true }, new Object[] { "false", "boolean", false }, + new Object[] { "100", "tinyint", (byte)100 }, + new Object[] { "100.", "tinyint", (byte)100 }, + new Object[] { "100", "smallint", (short)100 }, + new Object[] { "100.", "smallint", (short)100 }, new Object[] { "100", "int", 100 }, new Object[] { "100.", "int", 100 }, new Object[] { "100", "double", 100d }, @@ -1904,17 +1910,26 @@ public class UFTest extends CQLTester @Test public void testScriptParamReturnTypes() throws Throwable { - createTable("CREATE TABLE %s (key int primary key, ival int, lval bigint, fval float, dval double, vval varint, ddval decimal)"); - execute("INSERT INTO %s (key, ival, lval, fval, dval, vval, ddval) VALUES (?, ?, ?, ?, ?, ?, ?)", 1, - 1, 1L, 1f, 1d, BigInteger.valueOf(1L), BigDecimal.valueOf(1d)); + UUID ruuid = UUID.randomUUID(); + UUID tuuid = UUIDGen.getTimeUUID(); + + createTable("CREATE TABLE %s (key int primary key, " + + "tival tinyint, sival smallint, ival int, lval bigint, fval float, dval double, vval varint, ddval decimal, " + + "timval time, dtval date, tsval timestamp, uval uuid, tuval timeuuid)"); + execute("INSERT INTO %s (key, tival, sival, ival, lval, fval, dval, vval, ddval, timval, dtval, tsval, uval, tuval) VALUES " + + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 1, + (byte)1, (short)1, 1, 1L, 1f, 1d, BigInteger.valueOf(1L), BigDecimal.valueOf(1d), 1L, Integer.MAX_VALUE, new Date(1), ruuid, tuuid); Object[][] variations = { + new Object[] { "tinyint", "tival", (byte)1, (byte)2 }, + new Object[] { "smallint", "sival", (short)1, (short)2 }, new Object[] { "int", "ival", 1, 2 }, new Object[] { "bigint", "lval", 1L, 2L }, new Object[] { "float", "fval", 1f, 2f }, new Object[] { "double", "dval", 1d, 2d }, new Object[] { "varint", "vval", BigInteger.valueOf(1L), BigInteger.valueOf(2L) }, new Object[] { "decimal", "ddval", BigDecimal.valueOf(1d), BigDecimal.valueOf(2d) }, + new Object[] { "time", "timval", 1L, 2L }, }; for (Object[] variation : variations) @@ -1932,81 +1947,162 @@ public class UFTest extends CQLTester assertRows(execute("SELECT key, " + col + ", " + fName + '(' + col + ") FROM %s"), row(1, expected1, expected2)); } + + variations = new Object[][] { + new Object[] { "timestamp","tsval", new Date(1), new Date(1) }, + new Object[] { "uuid", "uval", ruuid, ruuid }, + new Object[] { "timeuuid", "tuval", tuuid, tuuid }, + new Object[] { "date", "dtval", Integer.MAX_VALUE, Integer.MAX_VALUE }, + }; + + for (Object[] variation : variations) + { + Object type = variation[0]; + Object col = variation[1]; + Object expected1 = variation[2]; + Object expected2 = variation[3]; + String fName = createFunction(KEYSPACE, type.toString(), + "CREATE OR REPLACE FUNCTION %s(val " + type + ") " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS " + type + ' ' + + "LANGUAGE javascript " + + "AS 'val;';"); + assertRows(execute("SELECT key, " + col + ", " + fName + '(' + col + ") FROM %s"), + row(1, expected1, expected2)); + } + } + + static class TypesTestDef + { + final String udfType; + final String tableType; + final String columnName; + final Object referenceValue; + + String fCheckArgAndReturn; + + String fCalledOnNull; + String fReturnsNullOnNull; + + TypesTestDef(String udfType, String tableType, String columnName, Object referenceValue) + { + this.udfType = udfType; + this.tableType = tableType; + this.columnName = columnName; + this.referenceValue = referenceValue; + } } @Test - public void testNullOnReturnsNullOnNullInput() throws Throwable + public void testTypesWithAndWithoutNulls() throws Throwable { + // test various combinations of types against UDFs with CALLED ON NULL or RETURNS NULL ON NULL + String type = createType("CREATE TYPE %s (txt text, i int)"); - createTable("CREATE TABLE %s (key int PRIMARY KEY, i int, b bigint, f float, d double, x boolean, t text, u frozen<"+type+">, tup frozen<tuple<int, text>>)"); - - execute("INSERT INTO %s (key, i, b, f, d, x, t, u, tup) VALUES (1, null, null, null, null, null, null, null, null)"); - - String fI = createFunction(KEYSPACE, - "int", - "CREATE OR REPLACE FUNCTION %s(val int) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fB = createFunction(KEYSPACE, - "bigint", - "CREATE OR REPLACE FUNCTION %s(val bigint) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fF = createFunction(KEYSPACE, - "float", - "CREATE OR REPLACE FUNCTION %s(val float) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fD = createFunction(KEYSPACE, - "double", - "CREATE OR REPLACE FUNCTION %s(val double) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fX = createFunction(KEYSPACE, - "boolean", - "CREATE OR REPLACE FUNCTION %s(val boolean) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fT = createFunction(KEYSPACE, - "text", - "CREATE OR REPLACE FUNCTION %s(val text) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fU = createFunction(KEYSPACE, - type, - "CREATE OR REPLACE FUNCTION %s(val " + type + ") " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fTup = createFunction(KEYSPACE, - "tuple<int, text>", - "CREATE OR REPLACE FUNCTION %s(val tuple<int, text>) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - - assertRows(execute("SELECT " + fI + "(i) FROM %s WHERE key=1"), row(new Object[]{null})); - assertRows(execute("SELECT " + fB + "(b) FROM %s WHERE key=1"), row(new Object[]{null})); - assertRows(execute("SELECT " + fF + "(f) FROM %s WHERE key=1"), row(new Object[]{null})); - assertRows(execute("SELECT " + fD + "(d) FROM %s WHERE key=1"), row(new Object[]{null})); - assertRows(execute("SELECT " + fX + "(x) FROM %s WHERE key=1"), row(new Object[]{null})); - assertRows(execute("SELECT " + fT + "(t) FROM %s WHERE key=1"), row(new Object[]{null})); - assertRows(execute("SELECT " + fU + "(u) FROM %s WHERE key=1"), row(new Object[]{null})); - assertRows(execute("SELECT " + fTup + "(tup) FROM %s WHERE key=1"), row(new Object[]{null})); + + TypesTestDef[] typeDefs = + { + // udf type, table type, column, reference value + new TypesTestDef("timestamp", "timestamp", "ts", new Date()), + new TypesTestDef("date", "date", "dt", 12345), + new TypesTestDef("time", "time", "tim", 12345L), + new TypesTestDef("uuid", "uuid", "uu", UUID.randomUUID()), + new TypesTestDef("timeuuid", "timeuuid", "tu", UUIDGen.getTimeUUID()), + new TypesTestDef("tinyint", "tinyint", "ti", (byte) 42), + new TypesTestDef("smallint", "smallint", "si", (short) 43), + new TypesTestDef("int", "int", "i", 44), + new TypesTestDef("bigint", "bigint", "b", 45L), + new TypesTestDef("float", "float", "f", 46f), + new TypesTestDef("double", "double", "d", 47d), + new TypesTestDef("boolean", "boolean", "x", true), + new TypesTestDef("ascii", "ascii", "a", "tqbfjutld"), + new TypesTestDef("text", "text", "t", "k\u00f6lsche jung"), + //new TypesTestDef(type, "frozen<" + type + '>', "u", null), + new TypesTestDef("tuple<int, text>", "frozen<tuple<int, text>>", "tup", tuple(1, "foo")) + }; + + String createTableDDL = "CREATE TABLE %s (key int PRIMARY KEY"; + String insertDML = "INSERT INTO %s (key"; + List<Object> values = new ArrayList<>(); + for (TypesTestDef typeDef : typeDefs) + { + createTableDDL += ", " + typeDef.columnName + ' ' + typeDef.tableType; + insertDML += ", " + typeDef.columnName; + String typeName = typeDef.udfType; + typeDef.fCheckArgAndReturn = createFunction(KEYSPACE, + typeName, + "CREATE OR REPLACE FUNCTION %s(val " + typeName + ") " + + "CALLED ON NULL INPUT " + + "RETURNS " + typeName + ' ' + + "LANGUAGE java\n" + + "AS 'return val;';"); + typeDef.fCalledOnNull = createFunction(KEYSPACE, + typeName, + "CREATE OR REPLACE FUNCTION %s(val " + typeName + ") " + + "CALLED ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE java\n" + + "AS 'return \"called\";';"); + typeDef.fReturnsNullOnNull = createFunction(KEYSPACE, + typeName, + "CREATE OR REPLACE FUNCTION %s(val " + typeName + ") " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE java\n" + + "AS 'return \"called\";';"); + values.add(typeDef.referenceValue); + } + + createTableDDL += ')'; + createTable(createTableDDL); + + insertDML += ") VALUES (1"; + for (TypesTestDef ignored : typeDefs) + insertDML += ", ?"; + insertDML += ')'; + + execute(insertDML, values.toArray()); + + // second row with null values + for (int i = 0; i < values.size(); i++) + values.set(i, null); + execute(insertDML.replace('1', '2'), values.toArray()); + + // check argument input + return + for (TypesTestDef typeDef : typeDefs) + { + assertRows(execute("SELECT " + typeDef.fCheckArgAndReturn + '(' + typeDef.columnName + ") FROM %s WHERE key = 1"), + row(new Object[]{ typeDef.referenceValue })); + } + + // check for CALLED ON NULL INPUT with non-null arguments + for (TypesTestDef typeDef : typeDefs) + { + assertRows(execute("SELECT " + typeDef.fCalledOnNull + '(' + typeDef.columnName + ") FROM %s WHERE key = 1"), + row(new Object[]{ "called" })); + } + + // check for CALLED ON NULL INPUT with null arguments + for (TypesTestDef typeDef : typeDefs) + { + assertRows(execute("SELECT " + typeDef.fCalledOnNull + '(' + typeDef.columnName + ") FROM %s WHERE key = 2"), + row(new Object[]{ "called" })); + } + + // check for RETURNS NULL ON NULL INPUT with non-null arguments + for (TypesTestDef typeDef : typeDefs) + { + assertRows(execute("SELECT " + typeDef.fReturnsNullOnNull + '(' + typeDef.columnName + ") FROM %s WHERE key = 1"), + row(new Object[]{ "called" })); + } + + // check for RETURNS NULL ON NULL INPUT with null arguments + for (TypesTestDef typeDef : typeDefs) + { + assertRows(execute("SELECT " + typeDef.fReturnsNullOnNull + '(' + typeDef.columnName + ") FROM %s WHERE key = 2"), + row(new Object[]{ null })); + } + } @Test @@ -2051,81 +2147,6 @@ public class UFTest extends CQLTester } @Test - public void testNullOnCalledOnNullInput() throws Throwable - { - String type = createType("CREATE TYPE %s (txt text, i int)"); - createTable("CREATE TABLE %s (key int PRIMARY KEY, i int, b bigint, f float, d double, x boolean, t text, u frozen<"+type+">, tup frozen<tuple<int, text>>)"); - - execute("INSERT INTO %s (key, i, b, f, d, x, t, u, tup) VALUES (1, null, null, null, null, null, null, null, null)"); - - String fI = createFunction(KEYSPACE, - "int", - "CREATE OR REPLACE FUNCTION %s(val int) " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fB = createFunction(KEYSPACE, - "bigint", - "CREATE OR REPLACE FUNCTION %s(val bigint) " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fF = createFunction(KEYSPACE, - "float", - "CREATE OR REPLACE FUNCTION %s(val float) " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fD = createFunction(KEYSPACE, - "double", - "CREATE OR REPLACE FUNCTION %s(val double) " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fX = createFunction(KEYSPACE, - "boolean", - "CREATE OR REPLACE FUNCTION %s(val boolean) " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fT = createFunction(KEYSPACE, - "text", - "CREATE OR REPLACE FUNCTION %s(val text) " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fU = createFunction(KEYSPACE, - type, - "CREATE OR REPLACE FUNCTION %s(val " + type + ") " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - String fTup = createFunction(KEYSPACE, - "tuple<int, text>", - "CREATE OR REPLACE FUNCTION %s(val tuple<int, text>) " + - "CALLED ON NULL INPUT " + - "RETURNS text " + - "LANGUAGE java\n" + - "AS 'return \"foo bar\";';"); - - assertRows(execute("SELECT " + fI + "(i) FROM %s WHERE key=1"), row("foo bar")); - assertRows(execute("SELECT " + fB + "(b) FROM %s WHERE key=1"), row("foo bar")); - assertRows(execute("SELECT " + fF + "(f) FROM %s WHERE key=1"), row("foo bar")); - assertRows(execute("SELECT " + fD + "(d) FROM %s WHERE key=1"), row("foo bar")); - assertRows(execute("SELECT " + fX + "(x) FROM %s WHERE key=1"), row("foo bar")); - assertRows(execute("SELECT " + fT + "(t) FROM %s WHERE key=1"), row("foo bar")); - assertRows(execute("SELECT " + fU + "(u) FROM %s WHERE key=1"), row("foo bar")); - assertRows(execute("SELECT " + fTup + "(tup) FROM %s WHERE key=1"), row("foo bar")); - } - - @Test public void testBrokenFunction() throws Throwable { createTable("CREATE TABLE %s (key int primary key, dval double)");