| /* |
| * 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.cassandra.cql3.validation.entities; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| import com.datastax.driver.core.DataType; |
| import com.datastax.driver.core.Row; |
| import com.datastax.driver.core.TupleType; |
| import com.datastax.driver.core.TupleValue; |
| import com.datastax.driver.core.UDTValue; |
| import org.apache.cassandra.config.Schema; |
| import org.apache.cassandra.cql3.CQL3Type; |
| import org.apache.cassandra.cql3.CQLTester; |
| import org.apache.cassandra.cql3.UntypedResultSet; |
| import org.apache.cassandra.cql3.functions.FunctionName; |
| import org.apache.cassandra.exceptions.FunctionExecutionException; |
| import org.apache.cassandra.exceptions.InvalidRequestException; |
| import org.apache.cassandra.transport.Server; |
| |
| public class UFJavaTest extends CQLTester |
| { |
| @Test |
| public void testJavaFunctionNoParameters() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, val double)"); |
| |
| String functionBody = "\n return 1L;\n"; |
| |
| String fName = createFunction(KEYSPACE, "", |
| "CREATE OR REPLACE FUNCTION %s() " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS bigint " + |
| "LANGUAGE JAVA\n" + |
| "AS '" +functionBody + "';"); |
| |
| assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?", |
| KEYSPACE, parseFunctionName(fName).name), |
| row("java", functionBody)); |
| |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); |
| assertRows(execute("SELECT key, val, " + fName + "() FROM %s"), |
| row(1, 1d, 1L), |
| row(2, 2d, 1L), |
| row(3, 3d, 1L) |
| ); |
| } |
| |
| @Test |
| public void testJavaFunctionInvalidBodies() throws Throwable |
| { |
| try |
| { |
| execute("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".jfinv() " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS bigint " + |
| "LANGUAGE JAVA\n" + |
| "AS '\n" + |
| "foobarbaz" + |
| "\n';"); |
| Assert.fail(); |
| } |
| catch (InvalidRequestException e) |
| { |
| Assert.assertTrue(e.getMessage(), e.getMessage().contains("Java source compilation failed")); |
| Assert.assertTrue(e.getMessage(), e.getMessage().contains("insert \";\" to complete BlockStatements")); |
| } |
| |
| try |
| { |
| execute("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".jfinv() " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS bigint " + |
| "LANGUAGE JAVA\n" + |
| "AS '\n" + |
| "foobarbaz;" + |
| "\n';"); |
| Assert.fail(); |
| } |
| catch (InvalidRequestException e) |
| { |
| Assert.assertTrue(e.getMessage(), e.getMessage().contains("Java source compilation failed")); |
| Assert.assertTrue(e.getMessage(), e.getMessage().contains("foobarbaz cannot be resolved to a type")); |
| } |
| } |
| |
| @Test |
| public void testJavaFunctionInvalidReturn() throws Throwable |
| { |
| assertInvalidMessage("system keyspace is not user-modifiable", |
| "CREATE OR REPLACE FUNCTION jfir(val double) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE JAVA\n" + |
| "AS 'return 1L;';"); |
| } |
| |
| @Test |
| public void testJavaFunctionArgumentTypeMismatch() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, val bigint)"); |
| |
| String fName = createFunction(KEYSPACE, "double", |
| "CREATE OR REPLACE FUNCTION %s(val double)" + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE JAVA " + |
| "AS 'return Double.valueOf(val);';"); |
| |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1L); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2L); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3L); |
| assertInvalidMessage("val cannot be passed as argument 0 of function", |
| "SELECT key, val, " + fName + "(val) FROM %s"); |
| } |
| |
| @Test |
| public void testJavaFunction() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, val double)"); |
| |
| String functionBody = '\n' + |
| " // parameter val is of type java.lang.Double\n" + |
| " /* return type is of type java.lang.Double */\n" + |
| " if (val == null) {\n" + |
| " return null;\n" + |
| " }\n" + |
| " return Math.sin(val);\n"; |
| |
| String fName = createFunction(KEYSPACE, "double", |
| "CREATE OR REPLACE FUNCTION %s(val double) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE JAVA " + |
| "AS '" + functionBody + "';"); |
| |
| FunctionName fNameName = parseFunctionName(fName); |
| |
| assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?", |
| fNameName.keyspace, fNameName.name), |
| row("java", functionBody)); |
| |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); |
| assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), |
| row(1, 1d, Math.sin(1d)), |
| row(2, 2d, Math.sin(2d)), |
| row(3, 3d, Math.sin(3d)) |
| ); |
| } |
| |
| @Test |
| public void testJavaFunctionCounter() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, val counter)"); |
| |
| String fName = createFunction(KEYSPACE, "counter", |
| "CREATE OR REPLACE FUNCTION %s(val counter) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS bigint " + |
| "LANGUAGE JAVA " + |
| "AS 'return val + 1;';"); |
| |
| execute("UPDATE %s SET val = val + 1 WHERE key = 1"); |
| assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), |
| row(1, 1L, 2L)); |
| execute("UPDATE %s SET val = val + 1 WHERE key = 1"); |
| assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), |
| row(1, 2L, 3L)); |
| execute("UPDATE %s SET val = val + 2 WHERE key = 1"); |
| assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), |
| row(1, 4L, 5L)); |
| execute("UPDATE %s SET val = val - 2 WHERE key = 1"); |
| assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), |
| row(1, 2L, 3L)); |
| } |
| |
| @Test |
| public void testJavaKeyspaceFunction() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, val double)"); |
| |
| String functionBody = '\n' + |
| " // parameter val is of type java.lang.Double\n" + |
| " /* return type is of type java.lang.Double */\n" + |
| " if (val == null) {\n" + |
| " return null;\n" + |
| " }\n" + |
| " return Math.sin( val );\n"; |
| |
| String fName = createFunction(KEYSPACE_PER_TEST, "double", |
| "CREATE OR REPLACE FUNCTION %s(val double) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE JAVA " + |
| "AS '" + functionBody + "';"); |
| |
| FunctionName fNameName = parseFunctionName(fName); |
| |
| assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?", |
| fNameName.keyspace, fNameName.name), |
| row("java", functionBody)); |
| |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); |
| assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), |
| row(1, 1d, Math.sin(1d)), |
| row(2, 2d, Math.sin(2d)), |
| row(3, 3d, Math.sin(3d)) |
| ); |
| } |
| |
| @Test |
| public void testJavaRuntimeException() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, val double)"); |
| |
| String functionBody = '\n' + |
| " throw new RuntimeException(\"oh no!\");\n"; |
| |
| String fName = createFunction(KEYSPACE_PER_TEST, "double", |
| "CREATE OR REPLACE FUNCTION %s(val double) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE JAVA\n" + |
| "AS '" + functionBody + "';"); |
| |
| FunctionName fNameName = parseFunctionName(fName); |
| |
| assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?", |
| fNameName.keyspace, fNameName.name), |
| row("java", functionBody)); |
| |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); |
| execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); |
| |
| // function throws a RuntimeException which is wrapped by FunctionExecutionException |
| assertInvalidThrowMessage("java.lang.RuntimeException: oh no", FunctionExecutionException.class, |
| "SELECT key, val, " + fName + "(val) FROM %s"); |
| } |
| |
| @Test |
| public void testJavaDollarQuotedFunction() throws Throwable |
| { |
| String functionBody = '\n' + |
| " // parameter val is of type java.lang.Double\n" + |
| " /* return type is of type java.lang.Double */\n" + |
| " if (input == null) {\n" + |
| " return null;\n" + |
| " }\n" + |
| " return \"'\"+Math.sin(input)+'\\\'';\n"; |
| |
| String fName = createFunction(KEYSPACE_PER_TEST, "double", |
| "CREATE FUNCTION %s( input double ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$" + functionBody + "$$;"); |
| |
| FunctionName fNameName = parseFunctionName(fName); |
| |
| assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?", |
| fNameName.keyspace, fNameName.name), |
| row("java", functionBody)); |
| } |
| |
| @Test |
| public void testJavaSimpleCollections() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, lst list<double>, st set<text>, mp map<int, boolean>)"); |
| |
| String fList = createFunction(KEYSPACE_PER_TEST, "list<double>", |
| "CREATE FUNCTION %s( lst list<double> ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS list<double> " + |
| "LANGUAGE java\n" + |
| "AS $$return lst;$$;"); |
| String fSet = createFunction(KEYSPACE_PER_TEST, "set<text>", |
| "CREATE FUNCTION %s( st set<text> ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS set<text> " + |
| "LANGUAGE java\n" + |
| "AS $$return st;$$;"); |
| String fMap = createFunction(KEYSPACE_PER_TEST, "map<int, boolean>", |
| "CREATE FUNCTION %s( mp map<int, boolean> ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS map<int, boolean> " + |
| "LANGUAGE java\n" + |
| "AS $$return mp;$$;"); |
| |
| List<Double> list = Arrays.asList(1d, 2d, 3d); |
| Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two")); |
| Map<Integer, Boolean> map = new TreeMap<>(); |
| map.put(1, true); |
| map.put(2, false); |
| map.put(3, true); |
| |
| execute("INSERT INTO %s (key, lst, st, mp) VALUES (1, ?, ?, ?)", list, set, map); |
| |
| assertRows(execute("SELECT " + fList + "(lst), " + fSet + "(st), " + fMap + "(mp) FROM %s WHERE key = 1"), |
| row(list, set, map)); |
| |
| // same test - but via native protocol |
| for (int version : PROTOCOL_VERSIONS) |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fList + "(lst), " + fSet + "(st), " + fMap + "(mp) FROM %s WHERE key = 1"), |
| row(list, set, map)); |
| } |
| |
| @Test |
| public void testJavaTupleType() throws Throwable |
| { |
| createTable("CREATE TABLE %s (key int primary key, tup frozen<tuple<double, text, int, boolean>>)"); |
| |
| String fName = createFunction(KEYSPACE, "tuple<double, text, int, boolean>", |
| "CREATE FUNCTION %s( tup tuple<double, text, int, boolean> ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS tuple<double, text, int, boolean> " + |
| "LANGUAGE java\n" + |
| "AS $$return tup;$$;"); |
| |
| Object t = tuple(1d, "foo", 2, true); |
| |
| execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t); |
| |
| assertRows(execute("SELECT tup FROM %s WHERE key = 1"), |
| row(t)); |
| |
| assertRows(execute("SELECT " + fName + "(tup) FROM %s WHERE key = 1"), |
| row(t)); |
| } |
| |
| @Test |
| public void testJavaTupleTypeCollection() throws Throwable |
| { |
| String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>"; |
| |
| createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)"); |
| |
| String fTup0 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, |
| "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS " + tupleTypeDef + ' ' + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " tup;$$;"); |
| String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, |
| "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " Double.valueOf(tup.getDouble(0));$$;"); |
| String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, |
| "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS list<double> " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " tup.getList(1, Double.class);$$;"); |
| String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, |
| "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS set<text> " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " tup.getSet(2, String.class);$$;"); |
| String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, |
| "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS map<int, boolean> " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " tup.getMap(3, Integer.class, Boolean.class);$$;"); |
| |
| List<Double> list = Arrays.asList(1d, 2d, 3d); |
| Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two")); |
| Map<Integer, Boolean> map = new TreeMap<>(); |
| map.put(1, true); |
| map.put(2, false); |
| map.put(3, true); |
| |
| Object t = tuple(1d, list, set, map); |
| |
| execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t); |
| |
| assertRows(execute("SELECT " + fTup0 + "(tup) FROM %s WHERE key = 1"), |
| row(t)); |
| assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), |
| row(1d)); |
| assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), |
| row(list)); |
| assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), |
| row(set)); |
| assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), |
| row(map)); |
| |
| // same test - but via native protocol |
| // we use protocol V3 here to encode the expected version because the server |
| // always serializes Collections using V3 - see CollectionSerializer's |
| // serialize and deserialize methods. |
| TupleType tType = tupleTypeOf(Server.VERSION_3, |
| DataType.cdouble(), |
| DataType.list(DataType.cdouble()), |
| DataType.set(DataType.text()), |
| DataType.map(DataType.cint(), DataType.cboolean())); |
| TupleValue tup = tType.newValue(1d, list, set, map); |
| for (int version : PROTOCOL_VERSIONS) |
| { |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fTup0 + "(tup) FROM %s WHERE key = 1"), |
| row(tup)); |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), |
| row(1d)); |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), |
| row(list)); |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), |
| row(set)); |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), |
| row(map)); |
| } |
| } |
| |
| @Test |
| public void testJavaUserTypeWithUse() throws Throwable |
| { |
| String type = createType("CREATE TYPE %s (txt text, i int)"); |
| createTable("CREATE TABLE %s (key int primary key, udt frozen<" + KEYSPACE + '.' + type + ">)"); |
| execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})"); |
| |
| for (int version : PROTOCOL_VERSIONS) |
| { |
| executeNet(version, "USE " + KEYSPACE); |
| |
| executeNet(version, |
| "CREATE FUNCTION f_use1( udt " + type + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS " + type + " " + |
| "LANGUAGE java " + |
| "AS $$return " + |
| " udt;$$;"); |
| try |
| { |
| List<Row> rowsNet = executeNet(version, "SELECT f_use1(udt) FROM %s WHERE key = 1").all(); |
| Assert.assertEquals(1, rowsNet.size()); |
| UDTValue udtVal = rowsNet.get(0).getUDTValue(0); |
| Assert.assertEquals("one", udtVal.getString("txt")); |
| Assert.assertEquals(1, udtVal.getInt("i")); |
| } |
| finally |
| { |
| executeNet(version, "DROP FUNCTION f_use1"); |
| } |
| } |
| } |
| |
| @Test |
| public void testJavaUserType() throws Throwable |
| { |
| String type = KEYSPACE + '.' + createType("CREATE TYPE %s (txt text, i int)"); |
| |
| createTable("CREATE TABLE %s (key int primary key, udt frozen<" + type + ">)"); |
| |
| String fUdt0 = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS " + type + " " + |
| "LANGUAGE java " + |
| "AS $$return " + |
| " udt;$$;"); |
| String fUdt1 = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + ") " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java " + |
| "AS $$return " + |
| " udt.getString(\"txt\");$$;"); |
| String fUdt2 = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + ") " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS int " + |
| "LANGUAGE java " + |
| "AS $$return " + |
| " Integer.valueOf(udt.getInt(\"i\"));$$;"); |
| |
| execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})"); |
| |
| UntypedResultSet rows = execute("SELECT " + fUdt0 + "(udt) FROM %s WHERE key = 1"); |
| Assert.assertEquals(1, rows.size()); |
| assertRows(execute("SELECT " + fUdt1 + "(udt) FROM %s WHERE key = 1"), |
| row("one")); |
| assertRows(execute("SELECT " + fUdt2 + "(udt) FROM %s WHERE key = 1"), |
| row(1)); |
| |
| for (int version : PROTOCOL_VERSIONS) |
| { |
| List<Row> rowsNet = executeNet(version, "SELECT " + fUdt0 + "(udt) FROM %s WHERE key = 1").all(); |
| Assert.assertEquals(1, rowsNet.size()); |
| UDTValue udtVal = rowsNet.get(0).getUDTValue(0); |
| Assert.assertEquals("one", udtVal.getString("txt")); |
| Assert.assertEquals(1, udtVal.getInt("i")); |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fUdt1 + "(udt) FROM %s WHERE key = 1"), |
| row("one")); |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fUdt2 + "(udt) FROM %s WHERE key = 1"), |
| row(1)); |
| } |
| } |
| |
| @Test |
| public void testJavaUserTypeRenameField() throws Throwable |
| { |
| String type = KEYSPACE + '.' + createType("CREATE TYPE %s (txt text, i int)"); |
| |
| createTable("CREATE TABLE %s (key int primary key, udt frozen<" + type + ">)"); |
| |
| String fName = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$return udt.getString(\"txt\");$$;"); |
| |
| execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})"); |
| |
| assertRows(execute("SELECT " + fName + "(udt) FROM %s WHERE key = 1"), |
| row("one")); |
| |
| execute("ALTER TYPE " + type + " RENAME txt TO str"); |
| |
| assertInvalidMessage("txt is not a field defined in this UDT", |
| "SELECT " + fName + "(udt) FROM %s WHERE key = 1"); |
| |
| execute("ALTER TYPE " + type + " RENAME str TO txt"); |
| |
| assertRows(execute("SELECT " + fName + "(udt) FROM %s WHERE key = 1"), |
| row("one")); |
| } |
| |
| @Test |
| public void testJavaUserTypeAddFieldWithReplace() throws Throwable |
| { |
| String type = KEYSPACE + '.' + createType("CREATE TYPE %s (txt text, i int)"); |
| |
| createTable("CREATE TABLE %s (key int primary key, udt frozen<" + type + ">)"); |
| |
| String fName1replace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + ") " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$return udt.getString(\"txt\");$$;"); |
| String fName2replace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS int " + |
| "LANGUAGE java\n" + |
| "AS $$return Integer.valueOf(udt.getInt(\"i\"));$$;"); |
| String fName3replace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE java\n" + |
| "AS $$return Double.valueOf(udt.getDouble(\"added\"));$$;"); |
| String fName4replace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS " + type + " " + |
| "LANGUAGE java\n" + |
| "AS $$return udt;$$;"); |
| |
| String fName1noReplace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$return udt.getString(\"txt\");$$;"); |
| String fName2noReplace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS int " + |
| "LANGUAGE java\n" + |
| "AS $$return Integer.valueOf(udt.getInt(\"i\"));$$;"); |
| String fName3noReplace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE java\n" + |
| "AS $$return Double.valueOf(udt.getDouble(\"added\"));$$;"); |
| String fName4noReplace = createFunction(KEYSPACE, type, |
| "CREATE FUNCTION %s( udt " + type + " ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS " + type + " " + |
| "LANGUAGE java\n" + |
| "AS $$return udt;$$;"); |
| |
| execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})"); |
| |
| assertRows(execute("SELECT " + fName1replace + "(udt) FROM %s WHERE key = 1"), |
| row("one")); |
| assertRows(execute("SELECT " + fName2replace + "(udt) FROM %s WHERE key = 1"), |
| row(1)); |
| |
| // add field |
| |
| execute("ALTER TYPE " + type + " ADD added double"); |
| |
| execute("INSERT INTO %s (key, udt) VALUES (2, {txt: 'two', i:2, added: 2})"); |
| |
| // note: type references of functions remain at the state _before_ the type mutation |
| // means we need to recreate the functions |
| |
| execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " udt.getString(\"txt\");$$;", |
| fName1replace, type)); |
| Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName1replace)).size()); |
| execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS int " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " Integer.valueOf(udt.getInt(\"i\"));$$;", |
| fName2replace, type)); |
| Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName2replace)).size()); |
| execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " + |
| "CALLED ON NULL INPUT " + |
| "RETURNS double " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " Double.valueOf(udt.getDouble(\"added\"));$$;", |
| fName3replace, type)); |
| Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName3replace)).size()); |
| execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS %s " + |
| "LANGUAGE java\n" + |
| "AS $$return " + |
| " udt;$$;", |
| fName4replace, type, type)); |
| Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName4replace)).size()); |
| |
| assertRows(execute("SELECT " + fName1replace + "(udt) FROM %s WHERE key = 2"), |
| row("two")); |
| assertRows(execute("SELECT " + fName2replace + "(udt) FROM %s WHERE key = 2"), |
| row(2)); |
| assertRows(execute("SELECT " + fName3replace + "(udt) FROM %s WHERE key = 2"), |
| row(2d)); |
| assertRows(execute("SELECT " + fName3replace + "(udt) FROM %s WHERE key = 1"), |
| row(0d)); |
| |
| // un-replaced functions will work since the user type has changed |
| // and the UDF has exchanged the user type reference |
| |
| assertRows(execute("SELECT " + fName1noReplace + "(udt) FROM %s WHERE key = 2"), |
| row("two")); |
| assertRows(execute("SELECT " + fName2noReplace + "(udt) FROM %s WHERE key = 2"), |
| row(2)); |
| assertRows(execute("SELECT " + fName3noReplace + "(udt) FROM %s WHERE key = 2"), |
| row(2d)); |
| assertRows(execute("SELECT " + fName3noReplace + "(udt) FROM %s WHERE key = 1"), |
| row(0d)); |
| |
| execute("DROP FUNCTION " + fName1replace); |
| execute("DROP FUNCTION " + fName2replace); |
| execute("DROP FUNCTION " + fName3replace); |
| execute("DROP FUNCTION " + fName4replace); |
| execute("DROP FUNCTION " + fName1noReplace); |
| execute("DROP FUNCTION " + fName2noReplace); |
| execute("DROP FUNCTION " + fName3noReplace); |
| execute("DROP FUNCTION " + fName4noReplace); |
| } |
| |
| @Test |
| public void testJavaUTCollections() throws Throwable |
| { |
| String type = KEYSPACE + '.' + createType("CREATE TYPE %s (txt text, i int)"); |
| |
| createTable(String.format("CREATE TABLE %%s " + |
| "(key int primary key, lst list<frozen<%s>>, st set<frozen<%s>>, mp map<int, frozen<%s>>)", |
| type, type, type)); |
| |
| String fName1 = createFunction(KEYSPACE, "list<frozen<" + type + ">>", |
| "CREATE FUNCTION %s( lst list<frozen<" + type + ">> ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$" + |
| " com.datastax.driver.core.UDTValue udtVal = (com.datastax.driver.core.UDTValue)lst.get(1);" + |
| " return udtVal.getString(\"txt\");$$;"); |
| String fName2 = createFunction(KEYSPACE, "set<frozen<" + type + ">>", |
| "CREATE FUNCTION %s( st set<frozen<" + type + ">> ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$" + |
| " com.datastax.driver.core.UDTValue udtVal = (com.datastax.driver.core.UDTValue)st.iterator().next();" + |
| " return udtVal.getString(\"txt\");$$;"); |
| String fName3 = createFunction(KEYSPACE, "map<int, frozen<" + type + ">>", |
| "CREATE FUNCTION %s( mp map<int, frozen<" + type + ">> ) " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS text " + |
| "LANGUAGE java\n" + |
| "AS $$" + |
| " com.datastax.driver.core.UDTValue udtVal = (com.datastax.driver.core.UDTValue)mp.get(Integer.valueOf(3));" + |
| " return udtVal.getString(\"txt\");$$;"); |
| |
| execute("INSERT INTO %s (key, lst, st, mp) values (1, " + |
| "[ {txt: 'one', i:1}, {txt: 'three', i:1}, {txt: 'one', i:1} ] , " + |
| "{ {txt: 'one', i:1}, {txt: 'three', i:3}, {txt: 'two', i:2} }, " + |
| "{ 1: {txt: 'one', i:1}, 2: {txt: 'one', i:3}, 3: {txt: 'two', i:2} })"); |
| |
| assertRows(execute("SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"), |
| row("three", "one", "two")); |
| |
| for (int version : PROTOCOL_VERSIONS) |
| assertRowsNet(version, |
| executeNet(version, "SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"), |
| row("three", "one", "two")); |
| } |
| |
| @Test |
| public void testAllNativeTypes() throws Throwable |
| { |
| StringBuilder sig = new StringBuilder(); |
| StringBuilder args = new StringBuilder(); |
| for (CQL3Type.Native type : CQL3Type.Native.values()) |
| { |
| if (type == CQL3Type.Native.EMPTY) |
| continue; |
| |
| if (sig.length() > 0) |
| sig.append(','); |
| sig.append(type.toString()); |
| |
| if (args.length() > 0) |
| args.append(','); |
| args.append("arg").append(type.toString()).append(' ').append(type.toString()); |
| } |
| createFunction(KEYSPACE, sig.toString(), |
| "CREATE OR REPLACE FUNCTION %s(" + args + ") " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS int " + |
| "LANGUAGE JAVA\n" + |
| "AS 'return 0;'"); |
| |
| for (CQL3Type.Native type : CQL3Type.Native.values()) |
| { |
| if (type == CQL3Type.Native.EMPTY) |
| continue; |
| |
| createFunction(KEYSPACE_PER_TEST, type.toString(), |
| "CREATE OR REPLACE FUNCTION %s(val " + type.toString() + ") " + |
| "RETURNS NULL ON NULL INPUT " + |
| "RETURNS int " + |
| "LANGUAGE JAVA\n" + |
| "AS 'return 0;'"); |
| } |
| } |
| } |