/* * 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.nio.ByteBuffer; import java.util.ArrayList; import java.util.Date; import java.util.List; import com.google.common.reflect.TypeToken; import org.junit.Assert; import org.junit.Test; import com.datastax.driver.core.*; import com.datastax.driver.core.exceptions.InvalidQueryException; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.JavaBasedUDFunction; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.transport.*; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; public class UFTest extends CQLTester { @Test public void testJavaSourceName() { Assert.assertEquals("String", JavaBasedUDFunction.javaSourceName(TypeToken.of(String.class))); Assert.assertEquals("java.util.Map<Integer, String>", JavaBasedUDFunction.javaSourceName(TypeTokens.mapOf(Integer.class, String.class))); Assert.assertEquals("com.datastax.driver.core.UDTValue", JavaBasedUDFunction.javaSourceName(TypeToken.of(UDTValue.class))); Assert.assertEquals("java.util.Set<com.datastax.driver.core.UDTValue>", JavaBasedUDFunction.javaSourceName(TypeTokens.setOf(UDTValue.class))); } @Test public void testNonExistingOnes() throws Throwable { assertInvalidThrowMessage("Cannot drop non existing function", InvalidRequestException.class, "DROP FUNCTION " + KEYSPACE + ".func_does_not_exist"); assertInvalidThrowMessage("Cannot drop non existing function", InvalidRequestException.class, "DROP FUNCTION " + KEYSPACE + ".func_does_not_exist(int,text)"); assertInvalidThrowMessage("Cannot drop non existing function", InvalidRequestException.class, "DROP FUNCTION keyspace_does_not_exist.func_does_not_exist"); assertInvalidThrowMessage("Cannot drop non existing function", InvalidRequestException.class, "DROP FUNCTION keyspace_does_not_exist.func_does_not_exist(int,text)"); execute("DROP FUNCTION IF EXISTS " + KEYSPACE + ".func_does_not_exist"); execute("DROP FUNCTION IF EXISTS " + KEYSPACE + ".func_does_not_exist(int,text)"); execute("DROP FUNCTION IF EXISTS keyspace_does_not_exist.func_does_not_exist"); execute("DROP FUNCTION IF EXISTS keyspace_does_not_exist.func_does_not_exist(int,text)"); } @Test public void testSchemaChange() throws Throwable { String f = createFunction(KEYSPACE, "double, double", "CREATE OR REPLACE FUNCTION %s(state double, val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE javascript " + "AS '\"string\";';"); assertLastSchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.FUNCTION, KEYSPACE, parseFunctionName(f).name, "double", "double"); createFunctionOverload(f, "double, double", "CREATE OR REPLACE FUNCTION %s(state int, val int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS int " + "LANGUAGE javascript " + "AS '\"string\";';"); assertLastSchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.FUNCTION, KEYSPACE, parseFunctionName(f).name, "int", "int"); schemaChange("CREATE OR REPLACE FUNCTION " + f + "(state int, val int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS int " + "LANGUAGE javascript " + "AS '\"string\";';"); assertLastSchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.FUNCTION, KEYSPACE, parseFunctionName(f).name, "int", "int"); schemaChange("DROP FUNCTION " + f + "(double, double)"); assertLastSchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.FUNCTION, KEYSPACE, parseFunctionName(f).name, "double", "double"); } @Test public void testFunctionDropOnKeyspaceDrop() throws Throwable { String fSin = createFunction(KEYSPACE_PER_TEST, "double", "CREATE FUNCTION %s ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java " + "AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); FunctionName fSinName = parseFunctionName(fSin); Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fSin)).size()); assertRows(execute("SELECT function_name, language FROM system_schema.functions WHERE keyspace_name=?", KEYSPACE_PER_TEST), row(fSinName.name, "java")); dropPerTestKeyspace(); assertRows(execute("SELECT function_name, language FROM system_schema.functions WHERE keyspace_name=?", KEYSPACE_PER_TEST)); Assert.assertEquals(0, Schema.instance.getFunctions(fSinName).size()); } @Test public void testFunctionDropPreparedStatement() throws Throwable { createTable("CREATE TABLE %s (key int PRIMARY KEY, d double)"); String fSin = createFunction(KEYSPACE_PER_TEST, "double", "CREATE FUNCTION %s ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java " + "AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); FunctionName fSinName = parseFunctionName(fSin); Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fSin)).size()); // create a pairs of Select and Inserts. One statement in each pair uses the function so when we // drop it those statements should be removed from the cache in QueryProcessor. The other statements // should be unaffected. ResultMessage.Prepared preparedSelect1 = QueryProcessor.prepare( String.format("SELECT key, %s(d) FROM %s.%s", fSin, KEYSPACE, currentTable()), ClientState.forInternalCalls()); ResultMessage.Prepared preparedSelect2 = QueryProcessor.prepare( String.format("SELECT key FROM %s.%s", KEYSPACE, currentTable()), ClientState.forInternalCalls()); ResultMessage.Prepared preparedInsert1 = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, d) VALUES (?, %s(?))", KEYSPACE, currentTable(), fSin), ClientState.forInternalCalls()); ResultMessage.Prepared preparedInsert2 = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, d) VALUES (?, ?)", KEYSPACE, currentTable()), ClientState.forInternalCalls()); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect2.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert2.statementId)); execute("DROP FUNCTION " + fSin + "(double);"); // the statements which use the dropped function should be removed from cache, with the others remaining Assert.assertNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect2.statementId)); Assert.assertNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert2.statementId)); execute("CREATE FUNCTION " + fSin + " ( input double ) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE java " + "AS 'return Double.valueOf(Math.sin(input));'"); Assert.assertEquals(1, Schema.instance.getFunctions(fSinName).size()); preparedSelect1= QueryProcessor.prepare( String.format("SELECT key, %s(d) FROM %s.%s", fSin, KEYSPACE, currentTable()), ClientState.forInternalCalls()); preparedInsert1 = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, d) VALUES (?, %s(?))", KEYSPACE, currentTable(), fSin), ClientState.forInternalCalls()); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); dropPerTestKeyspace(); // again, only the 2 statements referencing the function should be removed from cache // this time because the statements select from tables in KEYSPACE, only the function // is scoped to KEYSPACE_PER_TEST Assert.assertNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect2.statementId)); Assert.assertNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert2.statementId)); } @Test public void testDropFunctionDropsPreparedStatementsWithDelayedValues() throws Throwable { // test that dropping a function removes stmts which use // it to provide a DelayedValue collection from the // cache in QueryProcessor checkDelayedValuesCorrectlyIdentifyFunctionsInUse(false); } @Test public void testDropKeyspaceContainingFunctionDropsPreparedStatementsWithDelayedValues() throws Throwable { // test that dropping a function removes stmts which use // it to provide a DelayedValue collection from the // cache in QueryProcessor checkDelayedValuesCorrectlyIdentifyFunctionsInUse(true); } private ResultMessage.Prepared prepareStatementWithDelayedValue(CollectionType.Kind kind, String function) { String collectionType; String literalArgs; switch (kind) { case LIST: collectionType = "list<double>"; literalArgs = String.format("[%s(0.0)]", function); break; case SET: collectionType = "set<double>"; literalArgs = String.format("{%s(0.0)}", function); break; case MAP: collectionType = "map<double, double>"; literalArgs = String.format("{%s(0.0):0.0}", function); break; default: Assert.fail("Unsupported collection type " + kind); collectionType = null; literalArgs = null; } createTable("CREATE TABLE %s (" + " key int PRIMARY KEY," + " val " + collectionType + ')'); ResultMessage.Prepared prepared = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, val) VALUES (?, %s)", KEYSPACE, currentTable(), literalArgs), ClientState.forInternalCalls()); Assert.assertNotNull(QueryProcessor.instance.getPrepared(prepared.statementId)); return prepared; } private ResultMessage.Prepared prepareStatementWithDelayedValueTuple(String function) { createTable("CREATE TABLE %s (" + " key int PRIMARY KEY," + " val tuple<double> )"); ResultMessage.Prepared prepared = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, val) VALUES (?, (%s(0.0)))", KEYSPACE, currentTable(), function), ClientState.forInternalCalls()); Assert.assertNotNull(QueryProcessor.instance.getPrepared(prepared.statementId)); return prepared; } private void checkDelayedValuesCorrectlyIdentifyFunctionsInUse(boolean dropKeyspace) throws Throwable { // prepare a statement which doesn't use any function for a control createTable("CREATE TABLE %s (" + " key int PRIMARY KEY," + " val double)"); ResultMessage.Prepared control = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, val) VALUES (?, ?)", KEYSPACE, currentTable()), ClientState.forInternalCalls()); Assert.assertNotNull(QueryProcessor.instance.getPrepared(control.statementId)); // a function that we'll drop and verify that statements which use it to // provide a DelayedValue are removed from the cache in QueryProcessor String function = createFunction(KEYSPACE_PER_TEST, "double", "CREATE FUNCTION %s ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE javascript " + "AS 'input'"); Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(function)).size()); List<ResultMessage.Prepared> prepared = new ArrayList<>(); // prepare statements which use the function to provide a DelayedValue prepared.add(prepareStatementWithDelayedValue(CollectionType.Kind.LIST, function)); prepared.add(prepareStatementWithDelayedValue(CollectionType.Kind.SET, function)); prepared.add(prepareStatementWithDelayedValue(CollectionType.Kind.MAP, function)); prepared.add(prepareStatementWithDelayedValueTuple(function)); // what to drop - the function is scoped to the per-test keyspace, but the prepared statements // select from the per-fixture keyspace. So if we drop the per-test keyspace, the function // should be removed along with the statements that reference it. The control statement should // remain present in the cache. Likewise, if we actually drop the function itself the control // statement should not be removed, but the others should be if (dropKeyspace) dropPerTestKeyspace(); else execute("DROP FUNCTION " + function); Assert.assertNotNull(QueryProcessor.instance.getPrepared(control.statementId)); for (ResultMessage.Prepared removed : prepared) Assert.assertNull(QueryProcessor.instance.getPrepared(removed.statementId)); } @Test public void testFunctionCreationAndDrop() throws Throwable { createTable("CREATE TABLE %s (key int PRIMARY KEY, d double)"); execute("INSERT INTO %s(key, d) VALUES (?, ?)", 1, 1d); execute("INSERT INTO %s(key, d) VALUES (?, ?)", 2, 2d); execute("INSERT INTO %s(key, d) VALUES (?, ?)", 3, 3d); // simple creation String fSin = createFunction(KEYSPACE_PER_TEST, "double", "CREATE FUNCTION %s ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java " + "AS 'return Math.sin(input);'"); // check we can't recreate the same function assertInvalidMessage("already exists", "CREATE FUNCTION " + fSin + " ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); // but that it doesn't comply with "IF NOT EXISTS" execute("CREATE FUNCTION IF NOT EXISTS " + fSin + " ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); // Validate that it works as expected assertRows(execute("SELECT key, " + fSin + "(d) FROM %s"), row(1, Math.sin(1d)), row(2, Math.sin(2d)), row(3, Math.sin(3d)) ); // Replace the method with incompatible return type assertInvalidMessage("the new return type text is not compatible with the return type double of existing function", "CREATE OR REPLACE FUNCTION " + fSin + " ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java AS 'return Double.valueOf(42d);'"); // proper replacement execute("CREATE OR REPLACE FUNCTION " + fSin + " ( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java AS 'return Double.valueOf(42d);'"); // Validate the method as been replaced assertRows(execute("SELECT key, " + fSin + "(d) FROM %s"), row(1, 42.0), row(2, 42.0), row(3, 42.0) ); // same function but other keyspace String fSin2 = createFunction(KEYSPACE, "double", "CREATE FUNCTION %s ( input double ) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE java " + "AS 'return Math.sin(input);'"); assertRows(execute("SELECT key, " + fSin2 + "(d) FROM %s"), row(1, Math.sin(1d)), row(2, Math.sin(2d)), row(3, Math.sin(3d)) ); // Drop execute("DROP FUNCTION " + fSin); execute("DROP FUNCTION " + fSin2); // Drop unexisting function assertInvalidMessage("Cannot drop non existing function", "DROP FUNCTION " + fSin); // but don't complain with "IF EXISTS" execute("DROP FUNCTION IF EXISTS " + fSin); // can't drop native functions assertInvalidMessage("system keyspace is not user-modifiable", "DROP FUNCTION totimestamp"); assertInvalidMessage("system keyspace is not user-modifiable", "DROP FUNCTION uuid"); // sin() no longer exists assertInvalidMessage("Unknown function", "SELECT key, sin(d) FROM %s"); } @Test public void testFunctionExecution() throws Throwable { createTable("CREATE TABLE %s (v text PRIMARY KEY)"); execute("INSERT INTO %s(v) VALUES (?)", "aaa"); String fRepeat = createFunction(KEYSPACE_PER_TEST, "text,int", "CREATE FUNCTION %s(v text, n int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java " + "AS 'StringBuilder sb = new StringBuilder();\n" + " for (int i = 0; i < n; i++)\n" + " sb.append(v);\n" + " return sb.toString();'"); assertRows(execute("SELECT v FROM %s WHERE v=" + fRepeat + "(?, ?)", "a", 3), row("aaa")); assertEmpty(execute("SELECT v FROM %s WHERE v=" + fRepeat + "(?, ?)", "a", 2)); } @Test public void testFunctionExecutionWithReversedTypeAsOutput() throws Throwable { createTable("CREATE TABLE %s (k int, v text, PRIMARY KEY(k, v)) WITH CLUSTERING ORDER BY (v DESC)"); String fRepeat = createFunction(KEYSPACE_PER_TEST, "text", "CREATE FUNCTION %s(v text) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java " + "AS 'return v + v;'"); execute("INSERT INTO %s(k, v) VALUES (?, " + fRepeat + "(?))", 1, "a"); } @Test public void testFunctionOverloading() throws Throwable { createTable("CREATE TABLE %s (k text PRIMARY KEY, v int)"); execute("INSERT INTO %s(k, v) VALUES (?, ?)", "f2", 1); String fOverload = createFunction(KEYSPACE_PER_TEST, "varchar", "CREATE FUNCTION %s ( input varchar ) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java " + "AS 'return \"f1\";'"); createFunctionOverload(fOverload, "int", "CREATE OR REPLACE FUNCTION %s(i int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java " + "AS 'return \"f2\";'"); createFunctionOverload(fOverload, "text,text", "CREATE OR REPLACE FUNCTION %s(v1 text, v2 text) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java " + "AS 'return \"f3\";'"); createFunctionOverload(fOverload, "ascii", "CREATE OR REPLACE FUNCTION %s(v ascii) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java " + "AS 'return \"f1\";'"); // text == varchar, so this should be considered as a duplicate assertInvalidMessage("already exists", "CREATE FUNCTION " + fOverload + "(v varchar) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java AS 'return \"f1\";'"); assertRows(execute("SELECT " + fOverload + "(k), " + fOverload + "(v), " + fOverload + "(k, k) FROM %s"), row("f1", "f2", "f3") ); forcePreparedValues(); // This shouldn't work if we use preparation since there no way to know which overload to use assertInvalidMessage("Ambiguous call to function", "SELECT v FROM %s WHERE k = " + fOverload + "(?)", "foo"); stopForcingPreparedValues(); // but those should since we specifically cast assertEmpty(execute("SELECT v FROM %s WHERE k = " + fOverload + "((text)?)", "foo")); assertRows(execute("SELECT v FROM %s WHERE k = " + fOverload + "((int)?)", 3), row(1)); assertEmpty(execute("SELECT v FROM %s WHERE k = " + fOverload + "((ascii)?)", "foo")); // And since varchar == text, this should work too assertEmpty(execute("SELECT v FROM %s WHERE k = " + fOverload + "((varchar)?)", "foo")); // no such functions exist... assertInvalidMessage("non existing function", "DROP FUNCTION " + fOverload + "(boolean)"); assertInvalidMessage("non existing function", "DROP FUNCTION " + fOverload + "(bigint)"); // 'overloaded' has multiple overloads - so it has to fail (CASSANDRA-7812) assertInvalidMessage("matches multiple function definitions", "DROP FUNCTION " + fOverload); execute("DROP FUNCTION " + fOverload + "(varchar)"); assertInvalidMessage("none of its type signatures match", "SELECT v FROM %s WHERE k = " + fOverload + "((text)?)", "foo"); execute("DROP FUNCTION " + fOverload + "(text, text)"); assertInvalidMessage("none of its type signatures match", "SELECT v FROM %s WHERE k = " + fOverload + "((text)?,(text)?)", "foo", "bar"); execute("DROP FUNCTION " + fOverload + "(ascii)"); assertInvalidMessage("cannot be passed as argument 0 of function", "SELECT v FROM %s WHERE k = " + fOverload + "((ascii)?)", "foo"); // single-int-overload must still work assertRows(execute("SELECT v FROM %s WHERE k = " + fOverload + "((int)?)", 3), row(1)); // overloaded has just one overload now - so the following DROP FUNCTION is not ambigious (CASSANDRA-7812) execute("DROP FUNCTION " + fOverload); } @Test public void testCreateOrReplaceJavaFunction() throws Throwable { createTable("CREATE TABLE %s (key int primary key, val double)"); 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); String fName = createFunction(KEYSPACE_PER_TEST, "double", "CREATE FUNCTION %s( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java " + "AS '\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" + "';"); // just check created function 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)) ); execute("CREATE OR REPLACE FUNCTION " + fName + "( input double ) " + "CALLED ON NULL INPUT " + "RETURNS double " + "LANGUAGE java\n" + "AS '\n" + " return input;\n" + "';"); // check if replaced function returns correct result assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"), row(1, 1d, 1d), row(2, 2d, 2d), row(3, 3d, 3d) ); } @Test public void testFunctionInTargetKeyspace() throws Throwable { createTable("CREATE TABLE %s (key int primary key, val double)"); execute("CREATE TABLE " + KEYSPACE_PER_TEST + ".second_tab (key int primary key, val double)"); String fName = createFunction(KEYSPACE_PER_TEST, "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, 1d); execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); assertInvalidMessage("Unknown function", "SELECT key, val, " + parseFunctionName(fName).name + "(val) FROM %s"); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 1, 1d); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 2, 2d); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 3, 3d); assertRows(execute("SELECT key, val, " + fName + "(val) FROM " + KEYSPACE_PER_TEST + ".second_tab"), row(1, 1d, 1d), row(2, 2d, 2d), row(3, 3d, 3d) ); } @Test public void testFunctionWithReservedName() throws Throwable { execute("CREATE TABLE " + KEYSPACE_PER_TEST + ".second_tab (key int primary key, val double)"); String fName = createFunction(KEYSPACE_PER_TEST, "", "CREATE OR REPLACE FUNCTION %s() " + "RETURNS NULL ON NULL INPUT " + "RETURNS timestamp " + "LANGUAGE JAVA " + "AS 'return null;';"); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 1, 1d); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 2, 2d); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 3, 3d); // ensure that system now() is executed UntypedResultSet rows = execute("SELECT key, val, now() FROM " + KEYSPACE_PER_TEST + ".second_tab"); Assert.assertEquals(3, rows.size()); UntypedResultSet.Row row = rows.iterator().next(); Date ts = row.getTimestamp(row.getColumns().get(2).name.toString()); Assert.assertNotNull(ts); // ensure that KEYSPACE_PER_TEST's now() is executed rows = execute("SELECT key, val, " + fName + "() FROM " + KEYSPACE_PER_TEST + ".second_tab"); Assert.assertEquals(3, rows.size()); row = rows.iterator().next(); Assert.assertFalse(row.has(row.getColumns().get(2).name.toString())); } @Test public void testFunctionInSystemKS() throws Throwable { execute("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".totimestamp(val timeuuid) " + "RETURNS NULL ON NULL INPUT " + "RETURNS timestamp " + "LANGUAGE JAVA\n" + "AS 'return null;';"); assertInvalidMessage("system keyspace is not user-modifiable", "CREATE OR REPLACE FUNCTION system.jnft(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE JAVA\n" + "AS 'return null;';"); assertInvalidMessage("system keyspace is not user-modifiable", "CREATE OR REPLACE FUNCTION system.totimestamp(val timeuuid) " + "RETURNS NULL ON NULL INPUT " + "RETURNS timestamp " + "LANGUAGE JAVA\n" + "AS 'return null;';"); assertInvalidMessage("system keyspace is not user-modifiable", "DROP FUNCTION system.now"); // KS for executeInternal() is system assertInvalidMessage("system keyspace is not user-modifiable", "CREATE OR REPLACE FUNCTION jnft(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE JAVA\n" + "AS 'return null;';"); assertInvalidMessage("system keyspace is not user-modifiable", "CREATE OR REPLACE FUNCTION totimestamp(val timeuuid) " + "RETURNS NULL ON NULL INPUT " + "RETURNS timestamp " + "LANGUAGE JAVA\n" + "AS 'return null;';"); assertInvalidMessage("system keyspace is not user-modifiable", "DROP FUNCTION now"); } @Test public void testFunctionNonExistingKeyspace() throws Throwable { assertInvalidMessage("Keyspace this_ks_does_not_exist doesn't exist", "CREATE OR REPLACE FUNCTION this_ks_does_not_exist.jnft(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE JAVA\n" + "AS 'return null;';"); } @Test public void testFunctionAfterOnDropKeyspace() throws Throwable { dropPerTestKeyspace(); assertInvalidMessage("Keyspace " + KEYSPACE_PER_TEST + " doesn't exist", "CREATE OR REPLACE FUNCTION " + KEYSPACE_PER_TEST + ".jnft(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE JAVA\n" + "AS 'return null;';"); } @Test public void testWrongKeyspace() throws Throwable { String typeName = createType("CREATE TYPE %s (txt text, i int)"); String type = KEYSPACE + '.' + typeName; assertInvalidMessage(String.format("Statement on keyspace %s cannot refer to a user type in keyspace %s; user types can only be used in the keyspace they are defined in", KEYSPACE_PER_TEST, KEYSPACE), "CREATE FUNCTION " + KEYSPACE_PER_TEST + ".test_wrong_ks( val int ) " + "CALLED ON NULL INPUT " + "RETURNS " + type + " " + "LANGUAGE java\n" + "AS $$return val;$$;"); assertInvalidMessage(String.format("Statement on keyspace %s cannot refer to a user type in keyspace %s; user types can only be used in the keyspace they are defined in", KEYSPACE_PER_TEST, KEYSPACE), "CREATE FUNCTION " + KEYSPACE_PER_TEST + ".test_wrong_ks( val " + type + " ) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java\n" + "AS $$return val;$$;"); } @Test public void testUserTypeDrop() 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 + " ) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java " + "AS $$return " + " Integer.valueOf(udt.getInt(\"i\"));$$;"); FunctionName fNameName = parseFunctionName(fName); Assert.assertEquals(1, Schema.instance.getFunctions(fNameName).size()); ResultMessage.Prepared prepared = QueryProcessor.prepare(String.format("SELECT key, %s(udt) FROM %s.%s", fName, KEYSPACE, currentTable()), ClientState.forInternalCalls()); Assert.assertNotNull(QueryProcessor.instance.getPrepared(prepared.statementId)); // UT still referenced by table assertInvalidMessage("Cannot drop user type", "DROP TYPE " + type); execute("DROP TABLE %s"); // UT still referenced by UDF assertInvalidMessage("as it is still used by function", "DROP TYPE " + type); Assert.assertNull(QueryProcessor.instance.getPrepared(prepared.statementId)); // function stays Assert.assertEquals(1, Schema.instance.getFunctions(fNameName).size()); } @Test public void testDuplicateArgNames() throws Throwable { assertInvalidMessage("duplicate argument names for given function", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double, val text) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE javascript\n" + "AS '\"foo bar\";';"); } @Test public void testReplaceAllowNulls() throws Throwable { String fNulls = createFunction(KEYSPACE, "int", "CREATE OR REPLACE FUNCTION %s(val int) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return \"foo bar\";';"); String fNoNulls = createFunction(KEYSPACE, "int", "CREATE OR REPLACE FUNCTION %s(val int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return \"foo bar\";';"); assertInvalid("CREATE OR REPLACE FUNCTION " + fNulls + "(val int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return \"foo bar\";';"); assertInvalid("CREATE OR REPLACE FUNCTION " + fNoNulls + "(val int) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return \"foo bar\";';"); execute("CREATE OR REPLACE FUNCTION " + fNulls + "(val int) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return \"foo bar\";';"); execute("CREATE OR REPLACE FUNCTION " + fNoNulls + "(val int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return \"foo bar\";';"); } @Test public void testBrokenFunction() throws Throwable { createTable("CREATE TABLE %s (key int primary key, dval double)"); execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); 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 'throw new RuntimeException();';"); KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(KEYSPACE_PER_TEST); UDFunction f = (UDFunction) ksm.functions.get(parseFunctionName(fName)).iterator().next(); UDFunction broken = UDFunction.createBrokenFunction(f.name(), f.argNames(), f.argTypes(), f.returnType(), true, "java", f.body(), new InvalidRequestException("foo bar is broken")); Schema.instance.load(ksm.withSwapped(ksm.functions.without(f.name(), f.argTypes()).with(broken))); assertInvalidThrowMessage("foo bar is broken", InvalidRequestException.class, "SELECT key, " + fName + "(dval) FROM %s"); } @Test public void testFunctionExecutionExceptionNet() throws Throwable { createTable("CREATE TABLE %s (key int primary key, dval double)"); execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); 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 'throw new RuntimeException();'"); for (ProtocolVersion version : PROTOCOL_VERSIONS) { try { assertRowsNet(version, executeNet(version, "SELECT " + fName + "(dval) FROM %s WHERE key = 1")); Assert.fail(); } catch (com.datastax.driver.core.exceptions.FunctionExecutionException fee) { // Java driver neither throws FunctionExecutionException nor does it set the exception code correctly Assert.assertTrue(version.isGreaterOrEqualTo(ProtocolVersion.V4)); } catch (InvalidQueryException e) { Assert.assertTrue(version.isSmallerThan(ProtocolVersion.V4)); } } } @Test public void testEmptyString() throws Throwable { createTable("CREATE TABLE %s (key int primary key, sval text, aval ascii, bval blob, empty_int int)"); execute("INSERT INTO %s (key, sval, aval, bval, empty_int) VALUES (?, ?, ?, ?, blobAsInt(0x))", 1, "", "", ByteBuffer.allocate(0)); String fNameSRC = createFunction(KEYSPACE_PER_TEST, "text", "CREATE OR REPLACE FUNCTION %s(val text) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE JAVA\n" + "AS 'return val;'"); String fNameSCC = createFunction(KEYSPACE_PER_TEST, "text", "CREATE OR REPLACE FUNCTION %s(val text) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE JAVA\n" + "AS 'return \"\";'"); String fNameSRN = createFunction(KEYSPACE_PER_TEST, "text", "CREATE OR REPLACE FUNCTION %s(val text) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE JAVA\n" + "AS 'return val;'"); String fNameSCN = createFunction(KEYSPACE_PER_TEST, "text", "CREATE OR REPLACE FUNCTION %s(val text) " + "RETURNS NULL ON NULL INPUT " + "RETURNS text " + "LANGUAGE JAVA\n" + "AS 'return \"\";'"); String fNameBRC = createFunction(KEYSPACE_PER_TEST, "blob", "CREATE OR REPLACE FUNCTION %s(val blob) " + "CALLED ON NULL INPUT " + "RETURNS blob " + "LANGUAGE JAVA\n" + "AS 'return val;'"); String fNameBCC = createFunction(KEYSPACE_PER_TEST, "blob", "CREATE OR REPLACE FUNCTION %s(val blob) " + "CALLED ON NULL INPUT " + "RETURNS blob " + "LANGUAGE JAVA\n" + "AS 'return ByteBuffer.allocate(0);'"); String fNameBRN = createFunction(KEYSPACE_PER_TEST, "blob", "CREATE OR REPLACE FUNCTION %s(val blob) " + "RETURNS NULL ON NULL INPUT " + "RETURNS blob " + "LANGUAGE JAVA\n" + "AS 'return val;'"); String fNameBCN = createFunction(KEYSPACE_PER_TEST, "blob", "CREATE OR REPLACE FUNCTION %s(val blob) " + "RETURNS NULL ON NULL INPUT " + "RETURNS blob " + "LANGUAGE JAVA\n" + "AS 'return ByteBuffer.allocate(0);'"); String fNameIRC = createFunction(KEYSPACE_PER_TEST, "int", "CREATE OR REPLACE FUNCTION %s(val int) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE JAVA\n" + "AS 'return val;'"); String fNameICC = createFunction(KEYSPACE_PER_TEST, "int", "CREATE OR REPLACE FUNCTION %s(val int) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE JAVA\n" + "AS 'return 0;'"); String fNameIRN = createFunction(KEYSPACE_PER_TEST, "int", "CREATE OR REPLACE FUNCTION %s(val int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS int " + "LANGUAGE JAVA\n" + "AS 'return val;'"); String fNameICN = createFunction(KEYSPACE_PER_TEST, "int", "CREATE OR REPLACE FUNCTION %s(val int) " + "RETURNS NULL ON NULL INPUT " + "RETURNS int " + "LANGUAGE JAVA\n" + "AS 'return 0;'"); assertRows(execute("SELECT " + fNameSRC + "(sval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameSRN + "(sval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameSCC + "(sval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameSCN + "(sval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameSRC + "(aval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameSRN + "(aval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameSCC + "(aval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameSCN + "(aval) FROM %s"), row("")); assertRows(execute("SELECT " + fNameBRC + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); assertRows(execute("SELECT " + fNameBRN + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); assertRows(execute("SELECT " + fNameBCC + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); assertRows(execute("SELECT " + fNameBCN + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); assertRows(execute("SELECT " + fNameIRC + "(empty_int) FROM %s"), row(new Object[]{ null })); assertRows(execute("SELECT " + fNameIRN + "(empty_int) FROM %s"), row(new Object[]{ null })); assertRows(execute("SELECT " + fNameICC + "(empty_int) FROM %s"), row(0)); assertRows(execute("SELECT " + fNameICN + "(empty_int) FROM %s"), row(new Object[]{ null })); } }