You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sn...@apache.org on 2015/11/27 11:02:15 UTC

[04/10] cassandra git commit: Merge branch 'cassandra-2.1' into cassandra-2.2

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b3e6a433/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java
index 25566ad,0000000..0d11a82
mode 100644,000000..100644
--- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java
@@@ -1,2649 -1,0 +1,2663 @@@
 +/*
 + * 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.math.BigDecimal;
 +import java.math.BigInteger;
 +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 org.junit.Assert;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
 +
 +import com.datastax.driver.core.DataType;
 +import com.datastax.driver.core.Row;
 +import com.datastax.driver.core.TupleType;
 +import com.datastax.driver.core.TupleValue;
 +import com.datastax.driver.core.UDTValue;
 +import com.datastax.driver.core.exceptions.InvalidQueryException;
 +import org.apache.cassandra.config.DatabaseDescriptor;
 +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.cql3.functions.FunctionName;
 +import org.apache.cassandra.cql3.functions.Functions;
 +import org.apache.cassandra.cql3.functions.UDFunction;
 +import org.apache.cassandra.db.marshal.CollectionType;
 +import org.apache.cassandra.dht.ByteOrderedPartitioner;
 +import org.apache.cassandra.exceptions.FunctionExecutionException;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +import org.apache.cassandra.service.ClientState;
 +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
 +{
 +    @BeforeClass
 +    public static void setUp()
 +    {
 +        DatabaseDescriptor.setPartitioner(ByteOrderedPartitioner.instance);
 +    }
 +
 +    @Test
++    public void testNonExistingOnes() throws Throwable
++    {
++        assertInvalidMessage("Cannot drop non existing function", "DROP FUNCTION " + KEYSPACE + ".func_does_not_exist");
++        assertInvalidMessage("Cannot drop non existing function", "DROP FUNCTION " + KEYSPACE + ".func_does_not_exist(int,text)");
++        assertInvalidMessage("Cannot drop non existing function", "DROP FUNCTION keyspace_does_not_exist.func_does_not_exist");
++        assertInvalidMessage("Cannot drop non existing function", "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, Functions.find(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, Functions.find(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, Functions.find(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, Functions.find(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, Functions.find(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 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("to non existing keyspace",
 +                             "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("to non existing keyspace",
 +                             "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 = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +            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 = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +        {
 +            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));
 +
 +        TupleType tType = TupleType.of(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 = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +        {
 +            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 = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +        {
 +            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 = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +        {
 +            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, Functions.find(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, Functions.find(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, Functions.find(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, Functions.find(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, Functions.find(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, Functions.find(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 = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +            assertRowsNet(version,
 +                          executeNet(version, "SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"),
 +                          row("three", "one", "two"));
 +    }
 +
 +    @Test
 +    public void testJavascriptSimpleCollections() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (key int primary key, lst list<double>, st set<text>, mp map<int, boolean>)");
 +
 +        String fName1 = createFunction(KEYSPACE_PER_TEST, "list<double>",
 +                "CREATE FUNCTION %s( lst list<double> ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS list<double> " +
 +                "LANGUAGE javascript\n" +
 +                "AS 'lst;';");
 +        String fName2 = createFunction(KEYSPACE_PER_TEST, "set<text>",
 +                "CREATE FUNCTION %s( st set<text> ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS set<text> " +
 +                "LANGUAGE javascript\n" +
 +                "AS 'st;';");
 +        String fName3 = createFunction(KEYSPACE_PER_TEST, "map<int, boolean>",
 +                "CREATE FUNCTION %s( mp map<int, boolean> ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS map<int, boolean> " +
 +                "LANGUAGE javascript\n" +
 +                "AS '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 lst, st, mp FROM %s WHERE key = 1"),
 +                   row(list, set, map));
 +
 +        assertRows(execute("SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"),
 +                   row(list, set, map));
 +
 +        for (int version = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +            assertRowsNet(version,
 +                          executeNet(version, "SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"),
 +                          row(list, set, map));
 +    }
 +
 +    @Test
 +    public void testJavascriptTupleType() throws Throwable
 +    {
 +        createTable("CREATE TABLE %s (key int primary key, tup frozen<tuple<double, text, int, boolean>>)");
 +
 +        String fName = createFunction(KEYSPACE_PER_TEST, "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 javascript\n" +
 +                "AS $$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 testJavascriptTupleTypeCollection() 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 fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
 +                "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS tuple<double, list<double>, set<text>, map<int, boolean>> " +
 +                "LANGUAGE javascript\n" +
 +                "AS $$" +
 +                "       tup;$$;");
 +        String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
 +                "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS double " +
 +                "LANGUAGE javascript\n" +
 +                "AS $$" +
 +                "       tup.getDouble(0);$$;");
 +        String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
 +                "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS list<double> " +
 +                "LANGUAGE javascript\n" +
 +                "AS $$" +
 +                "       tup.getList(1, java.lang.Class.forName(\"java.lang.Double\"));$$;");
 +        String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
 +                "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS set<text> " +
 +                "LANGUAGE javascript\n" +
 +                "AS $$" +
 +                "       tup.getSet(2, java.lang.Class.forName(\"java.lang.String\"));$$;");
 +        String fTup5 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
 +                "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
 +                "RETURNS NULL ON NULL INPUT " +
 +                "RETURNS map<int, boolean> " +
 +                "LANGUAGE javascript\n" +
 +                "AS $$" +
 +                "       tup.getMap(3, java.lang.Class.forName(\"java.lang.Integer\"), java.lang.Class.forName(\"java.lang.Boolean\"));$$;");
 +
 +        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 " + fTup1 + "(tup) FROM %s WHERE key = 1"),
 +                   row(t));
 +        assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
 +                   row(1d));
 +        assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
 +                   row(list));
 +        assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
 +                   row(set));
 +        assertRows(execute("SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"),
 +                   row(map));
 +
 +        // same test - but via native protocol
 +        TupleType tType = TupleType.of(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 = Server.VERSION_2; version <= maxProtocolVersion; version++)
 +        {
 +            assertRowsNet(version,
 +                          executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
 +                          row(tup));
 +            assertRowsNet(version,
 +                          executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
 

<TRUNCATED>