/* * 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.Arrays; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.UUID; import java.security.AccessControlException; import org.junit.Assert; import org.junit.Test; import com.datastax.driver.core.*; import com.datastax.driver.core.exceptions.InvalidQueryException; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.config.Config; import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UDHelper; import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.exceptions.FunctionExecutionException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientWarn; 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 { @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(), false); ResultMessage.Prepared preparedSelect2 = QueryProcessor.prepare( String.format("SELECT key FROM %s.%s", KEYSPACE, currentTable()), ClientState.forInternalCalls(), false); ResultMessage.Prepared preparedInsert1 = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, d) VALUES (?, %s(?))", KEYSPACE, currentTable(), fSin), ClientState.forInternalCalls(), false); ResultMessage.Prepared preparedInsert2 = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, d) VALUES (?, ?)", KEYSPACE, currentTable()), ClientState.forInternalCalls(), false); 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(), false); preparedInsert1 = QueryProcessor.prepare( String.format("INSERT INTO %s.%s (key, d) VALUES (?, %s(?))", KEYSPACE, currentTable(), fSin), ClientState.forInternalCalls(), false); 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(), false); 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(), false); Assert.assertNotNull(QueryProcessor.instance.getPrepared(prepared.statementId)); return prepared; } public 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(), false); 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 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 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 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 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 testComplexNullValues() throws Throwable { String type = KEYSPACE + '.' + createType("CREATE TYPE %s (txt text, i int)"); createTable("CREATE TABLE %s (key int primary key, lst list<double>, st set<text>, mp map<int, boolean>," + "tup frozen<tuple<double, text, int, boolean>>, udt frozen<" + type + ">)"); String fList = createFunction(KEYSPACE, "list<double>", "CREATE FUNCTION %s( coll list<double> ) " + "CALLED ON NULL INPUT " + "RETURNS list<double> " + "LANGUAGE java\n" + "AS $$return coll;$$;"); String fSet = createFunction(KEYSPACE, "set<text>", "CREATE FUNCTION %s( coll set<text> ) " + "CALLED ON NULL INPUT " + "RETURNS set<text> " + "LANGUAGE java\n" + "AS $$return coll;$$;"); String fMap = createFunction(KEYSPACE, "map<int, boolean>", "CREATE FUNCTION %s( coll map<int, boolean> ) " + "CALLED ON NULL INPUT " + "RETURNS map<int, boolean> " + "LANGUAGE java\n" + "AS $$return coll;$$;"); String fTup = createFunction(KEYSPACE, "tuple<double, text, int, boolean>", "CREATE FUNCTION %s( val tuple<double, text, int, boolean> ) " + "CALLED ON NULL INPUT " + "RETURNS tuple<double, text, int, boolean> " + "LANGUAGE java\n" + "AS $$return val;$$;"); String fUdt = createFunction(KEYSPACE, type, "CREATE FUNCTION %s( val " + type + " ) " + "CALLED ON NULL INPUT " + "RETURNS " + type + " " + "LANGUAGE java\n" + "AS $$return val;$$;"); 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, "one", 42, false); execute("INSERT INTO %s (key, lst, st, mp, tup, udt) VALUES (1, ?, ?, ?, ?, {txt: 'one', i:1})", list, set, map, t); execute("INSERT INTO %s (key, lst, st, mp, tup, udt) VALUES (2, ?, ?, ?, ?, null)", null, null, null, null); execute("SELECT " + fList + "(lst), " + fSet + "(st), " + fMap + "(mp), " + fTup + "(tup), " + fUdt + "(udt) FROM %s WHERE key = 1"); UntypedResultSet.Row row = execute("SELECT " + fList + "(lst) as l, " + fSet + "(st) as s, " + fMap + "(mp) as m, " + fTup + "(tup) as t, " + fUdt + "(udt) as u " + "FROM %s WHERE key = 1").one(); Assert.assertNotNull(row.getBytes("l")); Assert.assertNotNull(row.getBytes("s")); Assert.assertNotNull(row.getBytes("m")); Assert.assertNotNull(row.getBytes("t")); Assert.assertNotNull(row.getBytes("u")); row = execute("SELECT " + fList + "(lst) as l, " + fSet + "(st) as s, " + fMap + "(mp) as m, " + fTup + "(tup) as t, " + fUdt + "(udt) as u " + "FROM %s WHERE key = 2").one(); Assert.assertNull(row.getBytes("l")); Assert.assertNull(row.getBytes("s")); Assert.assertNull(row.getBytes("m")); Assert.assertNull(row.getBytes("t")); Assert.assertNull(row.getBytes("u")); for (int version : PROTOCOL_VERSIONS) { Row r = executeNet(version, "SELECT " + fList + "(lst) as l, " + fSet + "(st) as s, " + fMap + "(mp) as m, " + fTup + "(tup) as t, " + fUdt + "(udt) as u " + "FROM %s WHERE key = 1").one(); Assert.assertNotNull(r.getBytesUnsafe("l")); Assert.assertNotNull(r.getBytesUnsafe("s")); Assert.assertNotNull(r.getBytesUnsafe("m")); Assert.assertNotNull(r.getBytesUnsafe("t")); Assert.assertNotNull(r.getBytesUnsafe("u")); r = executeNet(version, "SELECT " + fList + "(lst) as l, " + fSet + "(st) as s, " + fMap + "(mp) as m, " + fTup + "(tup) as t, " + fUdt + "(udt) as u " + "FROM %s WHERE key = 2").one(); Assert.assertNull(r.getBytesUnsafe("l")); Assert.assertNull(r.getBytesUnsafe("s")); Assert.assertNull(r.getBytesUnsafe("m")); Assert.assertNull(r.getBytesUnsafe("t")); Assert.assertNull(r.getBytesUnsafe("u")); } } @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 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(), false); 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 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 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 testJavascriptCompileFailure() throws Throwable { assertInvalidMessage("Failed to compile function 'cql_test_keyspace.scrinv'", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE javascript\n" + "AS 'foo bar';"); } @Test public void testScriptInvalidLanguage() throws Throwable { assertInvalidMessage("Invalid language 'artificial_intelligence' for function 'cql_test_keyspace.scrinv'", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE artificial_intelligence\n" + "AS 'question for 42?';"); } 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 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)"); 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 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.getKSMetaData(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.setKeyspaceMetadata(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 (int 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 >= Server.VERSION_4); } catch (InvalidQueryException e) { Assert.assertTrue(version < Server.VERSION_4); } } } @Test public void testFunctionWithFrozenSetType() throws Throwable { createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<set<int>>)"); createIndex("CREATE INDEX ON %s (FULL(b))"); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 0, set()); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 1, set(1, 2, 3)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 2, set(4, 5, 6)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 3, set(7, 8, 9)); assertInvalidMessage("The function arguments should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".frozenSetArg(values frozen<set<int>>) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java\n" + "AS 'int sum = 0; for (Object value : values) {sum += value;} return sum;';"); assertInvalidMessage("The function return type should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".frozenReturnType(values set<int>) " + "CALLED ON NULL INPUT " + "RETURNS frozen<set<int>> " + "LANGUAGE java\n" + "AS 'return values;';"); String functionName = createFunction(KEYSPACE, "set<int>", "CREATE FUNCTION %s (values set<int>) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java\n" + "AS 'int sum = 0; for (Object value : values) {sum += ((Integer) value);} return sum;';"); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 0"), row(0, 0)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 1"), row(1, 6)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 2"), row(2, 15)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 3"), row(3, 24)); functionName = createFunction(KEYSPACE, "set<int>", "CREATE FUNCTION %s (values set<int>) " + "CALLED ON NULL INPUT " + "RETURNS set<int> " + "LANGUAGE java\n" + "AS 'return values;';"); assertRows(execute("SELECT a FROM %s WHERE b = " + functionName + "(?)", set(1, 2, 3)), row(1)); assertInvalidMessage("The function arguments should not be frozen", "DROP FUNCTION " + functionName + "(frozen<set<int>>);"); } @Test public void testFunctionWithFrozenListType() throws Throwable { createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<list<int>>)"); createIndex("CREATE INDEX ON %s (FULL(b))"); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 0, list()); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 1, list(1, 2, 3)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 2, list(4, 5, 6)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 3, list(7, 8, 9)); assertInvalidMessage("The function arguments should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".withFrozenArg(values frozen<list<int>>) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java\n" + "AS 'int sum = 0; for (Object value : values) {sum += value;} return sum;';"); assertInvalidMessage("The function return type should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".frozenReturnType(values list<int>) " + "CALLED ON NULL INPUT " + "RETURNS frozen<list<int>> " + "LANGUAGE java\n" + "AS 'return values;';"); String functionName = createFunction(KEYSPACE, "list<int>", "CREATE FUNCTION %s (values list<int>) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java\n" + "AS 'int sum = 0; for (Object value : values) {sum += ((Integer) value);} return sum;';"); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 0"), row(0, 0)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 1"), row(1, 6)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 2"), row(2, 15)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 3"), row(3, 24)); functionName = createFunction(KEYSPACE, "list<int>", "CREATE FUNCTION %s (values list<int>) " + "CALLED ON NULL INPUT " + "RETURNS list<int> " + "LANGUAGE java\n" + "AS 'return values;';"); assertRows(execute("SELECT a FROM %s WHERE b = " + functionName + "(?)", set(1, 2, 3)), row(1)); assertInvalidMessage("The function arguments should not be frozen", "DROP FUNCTION " + functionName + "(frozen<list<int>>);"); } @Test public void testFunctionWithFrozenMapType() throws Throwable { createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<map<int, int>>)"); createIndex("CREATE INDEX ON %s (FULL(b))"); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 0, map()); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 1, map(1, 1, 2, 2, 3, 3)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 2, map(4, 4, 5, 5, 6, 6)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 3, map(7, 7, 8, 8, 9, 9)); assertInvalidMessage("The function arguments should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".withFrozenArg(values frozen<map<int, int>>) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java\n" + "AS 'int sum = 0; for (Object value : values.values()) {sum += value;} return sum;';"); assertInvalidMessage("The function return type should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".frozenReturnType(values map<int, int>) " + "CALLED ON NULL INPUT " + "RETURNS frozen<map<int, int>> " + "LANGUAGE java\n" + "AS 'return values;';"); String functionName = createFunction(KEYSPACE, "map<int, int>", "CREATE FUNCTION %s (values map<int, int>) " + "CALLED ON NULL INPUT " + "RETURNS int " + "LANGUAGE java\n" + "AS 'int sum = 0; for (Object value : values.values()) {sum += ((Integer) value);} return sum;';"); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 0"), row(0, 0)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 1"), row(1, 6)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 2"), row(2, 15)); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 3"), row(3, 24)); functionName = createFunction(KEYSPACE, "map<int, int>", "CREATE FUNCTION %s (values map<int, int>) " + "CALLED ON NULL INPUT " + "RETURNS map<int, int> " + "LANGUAGE java\n" + "AS 'return values;';"); assertRows(execute("SELECT a FROM %s WHERE b = " + functionName + "(?)", map(1, 1, 2, 2, 3, 3)), row(1)); assertInvalidMessage("The function arguments should not be frozen", "DROP FUNCTION " + functionName + "(frozen<map<int, int>>);"); } @Test public void testFunctionWithFrozenTupleType() throws Throwable { createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<tuple<int, int>>)"); createIndex("CREATE INDEX ON %s (b)"); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 0, tuple()); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 1, tuple(1, 2)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 2, tuple(4, 5)); execute("INSERT INTO %s (a, b) VALUES (?, ?)", 3, tuple(7, 8)); assertInvalidMessage("The function arguments should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".withFrozenArg(values frozen<tuple<int, int>>) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return values.toString();';"); assertInvalidMessage("The function return type should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".frozenReturnType(values tuple<int, int>) " + "CALLED ON NULL INPUT " + "RETURNS frozen<tuple<int, int>> " + "LANGUAGE java\n" + "AS 'return values;';"); String functionName = createFunction(KEYSPACE, "tuple<int, int>", "CREATE FUNCTION %s (values tuple<int, int>) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return values.toString();';"); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 0"), row(0, "(NULL,NULL)")); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 1"), row(1, "(1,2)")); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 2"), row(2, "(4,5)")); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 3"), row(3, "(7,8)")); functionName = createFunction(KEYSPACE, "tuple<int, int>", "CREATE FUNCTION %s (values tuple<int, int>) " + "CALLED ON NULL INPUT " + "RETURNS tuple<int, int> " + "LANGUAGE java\n" + "AS 'return values;';"); assertRows(execute("SELECT a FROM %s WHERE b = " + functionName + "(?)", tuple(1, 2)), row(1)); assertInvalidMessage("The function arguments should not be frozen", "DROP FUNCTION " + functionName + "(frozen<tuple<int, int>>);"); } @Test public void testFunctionWithFrozenUDType() throws Throwable { String myType = createType("CREATE TYPE %s (f int)"); createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<" + myType + ">)"); createIndex("CREATE INDEX ON %s (b)"); execute("INSERT INTO %s (a, b) VALUES (?, {f : ?})", 0, 0); execute("INSERT INTO %s (a, b) VALUES (?, {f : ?})", 1, 1); execute("INSERT INTO %s (a, b) VALUES (?, {f : ?})", 2, 4); execute("INSERT INTO %s (a, b) VALUES (?, {f : ?})", 3, 7); assertInvalidMessage("The function arguments should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".withFrozenArg(values frozen<" + myType + ">) " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return values.toString();';"); assertInvalidMessage("The function return type should not be frozen", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".frozenReturnType(values " + myType + ") " + "CALLED ON NULL INPUT " + "RETURNS frozen<" + myType + "> " + "LANGUAGE java\n" + "AS 'return values;';"); String functionName = createFunction(KEYSPACE, myType, "CREATE FUNCTION %s (values " + myType + ") " + "CALLED ON NULL INPUT " + "RETURNS text " + "LANGUAGE java\n" + "AS 'return values.toString();';"); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 0"), row(0, "{f:0}")); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 1"), row(1, "{f:1}")); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 2"), row(2, "{f:4}")); assertRows(execute("SELECT a, " + functionName + "(b) FROM %s WHERE a = 3"), row(3, "{f:7}")); functionName = createFunction(KEYSPACE, myType, "CREATE FUNCTION %s (values " + myType + ") " + "CALLED ON NULL INPUT " + "RETURNS " + myType + " " + "LANGUAGE java\n" + "AS 'return values;';"); assertRows(execute("SELECT a FROM %s WHERE b = " + functionName + "({f: ?})", 1), row(1)); assertInvalidMessage("The function arguments should not be frozen", "DROP FUNCTION " + functionName + "(frozen<" + myType + ">);"); } @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 })); } @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;'"); } } @Test public void testSecurityPermissions() throws Throwable { createTable("CREATE TABLE %s (key int primary key, dval double)"); execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); // Java UDFs try { 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 'System.getProperty(\"foo.bar.baz\"); return 0d;';"); execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); Assert.fail(); } catch (FunctionExecutionException e) { assertAccessControlException("System.getProperty(\"foo.bar.baz\"); return 0d;", e); } String[][] typesAndSources = { {"", "try { Class.forName(\"" + UDHelper.class.getName() + "\"); } catch (Exception e) { throw new RuntimeException(e); } return 0d;"}, {"sun.misc.Unsafe", "sun.misc.Unsafe.getUnsafe(); return 0d;"}, {"", "try { Class.forName(\"sun.misc.Unsafe\"); } catch (Exception e) { throw new RuntimeException(e); } return 0d;"}, {"java.nio.file.FileSystems", "try {" + " java.nio.file.FileSystems.getDefault(); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"java.nio.channels.FileChannel", "try {" + " java.nio.channels.FileChannel.open(java.nio.file.FileSystems.getDefault().getPath(\"/etc/passwd\")).close(); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"java.nio.channels.SocketChannel", "try {" + " java.nio.channels.SocketChannel.open().close(); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"java.io.FileInputStream", "try {" + " new java.io.FileInputStream(\"./foobar\").close(); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"java.lang.Runtime", "try {" + " java.lang.Runtime.getRuntime(); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"org.apache.cassandra.service.StorageService", "try {" + " org.apache.cassandra.service.StorageService v = org.apache.cassandra.service.StorageService.instance; v.isInShutdownHook(); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"java.net.ServerSocket", "try {" + " new java.net.ServerSocket().bind(); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"java.io.FileOutputStream","try {" + " new java.io.FileOutputStream(\".foo\"); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'}, {"java.lang.Runtime", "try {" + " java.lang.Runtime.getRuntime().exec(\"/tmp/foo\"); return 0d;" + "} catch (Exception t) {" + " throw new RuntimeException(t);" + '}'} }; for (String[] typeAndSource : typesAndSources) { assertInvalidMessage(typeAndSource[0] + " cannot be resolved", "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".invalid_class_access(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE JAVA\n" + "AS '" + typeAndSource[1] + "';"); } // JavaScript UDFs try { String fName = createFunction(KEYSPACE_PER_TEST, "double", "CREATE OR REPLACE FUNCTION %s(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE javascript\n" + "AS 'org.apache.cassandra.service.StorageService.instance.isInShutdownHook(); 0;';"); execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); Assert.fail("Javascript security check failed"); } catch (FunctionExecutionException e) { assertAccessControlException("", e); } String[] javascript = { "java.lang.management.ManagmentFactory.getThreadMXBean(); 0;", "new java.io.FileInputStream(\"/tmp/foo\"); 0;", "new java.io.FileOutputStream(\"/tmp/foo\"); 0;", "java.nio.file.FileSystems.getDefault().createFileExclusively(\"./foo_bar_baz\"); 0;", "java.nio.channels.FileChannel.open(java.nio.file.FileSystems.getDefault().getPath(\"/etc/passwd\")); 0;", "java.nio.channels.SocketChannel.open(); 0;", "new java.net.ServerSocket().bind(null); 0;", "var thread = new java.lang.Thread(); thread.start(); 0;", "java.lang.System.getProperty(\"foo.bar.baz\"); 0;", "java.lang.Class.forName(\"java.lang.System\"); 0;", "java.lang.Runtime.getRuntime().exec(\"/tmp/foo\"); 0;", "java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;", "java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;", // TODO these (ugly) calls are still possible - these can consume CPU (as one could do with an evil loop, too) // "java.lang.Runtime.getRuntime().traceMethodCalls(true); 0;", // "java.lang.Runtime.getRuntime().gc(); 0;", // "java.lang.Runtime.getRuntime(); 0;", }; for (String script : javascript) { try { String fName = createFunction(KEYSPACE_PER_TEST, "double", "CREATE OR REPLACE FUNCTION %s(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE javascript\n" + "AS '" + script + "';"); execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); Assert.fail("Javascript security check failed: " + script); } catch (FunctionExecutionException e) { assertAccessControlException(script, e); } } } private static void assertAccessControlException(String script, FunctionExecutionException e) { for (Throwable t = e; t != null && t != t.getCause(); t = t.getCause()) if (t instanceof AccessControlException) return; Assert.fail("no AccessControlException for " + script + " (got " + e + ')'); } @Test public void testAmokUDF() throws Throwable { createTable("CREATE TABLE %s (key int primary key, dval double)"); execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); long udfWarnTimeout = DatabaseDescriptor.getUserDefinedFunctionWarnTimeout(); long udfFailTimeout = DatabaseDescriptor.getUserDefinedFunctionFailTimeout(); int maxTries = 5; for (int i = 1; i <= maxTries; i++) { try { // short timeout DatabaseDescriptor.setUserDefinedFunctionWarnTimeout(10); DatabaseDescriptor.setUserDefinedFunctionFailTimeout(250); // don't kill the unit test... - default policy is "die" DatabaseDescriptor.setUserFunctionTimeoutPolicy(Config.UserFunctionTimeoutPolicy.ignore); ClientWarn.instance.captureWarnings(); 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 'long t=System.currentTimeMillis()+110; while (t>System.currentTimeMillis()) { }; return 0d;'"); execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); List<String> warnings = ClientWarn.instance.getWarnings(); Assert.assertNotNull(warnings); Assert.assertFalse(warnings.isEmpty()); ClientWarn.instance.resetWarnings(); // Java UDF fName = createFunction(KEYSPACE_PER_TEST, "double", "CREATE OR REPLACE FUNCTION %s(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE JAVA\n" + "AS 'long t=System.currentTimeMillis()+500; while (t>System.currentTimeMillis()) { }; return 0d;';"); assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1"); // Javascript UDF fName = createFunction(KEYSPACE_PER_TEST, "double", "CREATE OR REPLACE FUNCTION %s(val double) " + "RETURNS NULL ON NULL INPUT " + "RETURNS double " + "LANGUAGE JAVASCRIPT\n" + "AS 'var t=java.lang.System.currentTimeMillis()+500; while (t>java.lang.System.currentTimeMillis()) { }; 0;';"); assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1"); return; } catch (Error | RuntimeException e) { if (i == maxTries) throw e; } finally { // reset to defaults DatabaseDescriptor.setUserDefinedFunctionWarnTimeout(udfWarnTimeout); DatabaseDescriptor.setUserDefinedFunctionFailTimeout(udfFailTimeout); } } } }