content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.cassandra.cql3.validation.entities;
  
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeMap;
  import java.util.TreeSet;
  
  import org.junit.Assert;
  import org.junit.Test;
  
  import com.datastax.driver.core.DataType;
  import com.datastax.driver.core.Row;
  import com.datastax.driver.core.TupleType;
  import com.datastax.driver.core.TupleValue;
  import com.datastax.driver.core.UDTValue;
  import org.apache.cassandra.cql3.CQL3Type;
  import org.apache.cassandra.cql3.CQLTester;
  import org.apache.cassandra.cql3.ColumnIdentifier;
  import org.apache.cassandra.cql3.UntypedResultSet;
  import org.apache.cassandra.cql3.functions.FunctionName;
  import org.apache.cassandra.cql3.functions.UDAggregate;
  import org.apache.cassandra.cql3.functions.UDFunction;
  import org.apache.cassandra.db.marshal.Int32Type;
  import org.apache.cassandra.db.marshal.UTF8Type;
  import org.apache.cassandra.exceptions.FunctionExecutionException;
  import org.apache.cassandra.exceptions.InvalidRequestException;
  import org.apache.cassandra.schema.Schema;
  import org.apache.cassandra.transport.ProtocolVersion;
  
  public class UFJavaTest extends CQLTester
  {
      @Test
      public void testJavaFunctionNoParameters() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, val double)");
  
          String functionBody = "\n  return 1L;\n";
  
          String fName = createFunction(KEYSPACE, "",
                                        "CREATE OR REPLACE FUNCTION %s() " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS bigint " +
                                        "LANGUAGE JAVA\n" +
                                        "AS '" + functionBody + "';");
  
          assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?",
                             KEYSPACE, parseFunctionName(fName).name),
                     row("java", functionBody));
  
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d);
          assertRows(execute("SELECT key, val, " + fName + "() FROM %s"),
                     row(1, 1d, 1L),
                     row(2, 2d, 1L),
                     row(3, 3d, 1L)
          );
      }
  
      @Test
      public void testJavaFunctionInvalidBodies() throws Throwable
      {
          try
          {
              execute("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".jfinv() " +
                      "RETURNS NULL ON NULL INPUT " +
                      "RETURNS bigint " +
                      "LANGUAGE JAVA\n" +
                      "AS '\n" +
                      "foobarbaz" +
                      "\n';");
              Assert.fail();
          }
          catch (InvalidRequestException e)
          {
              Assert.assertTrue(e.getMessage(), e.getMessage().contains("Java source compilation failed"));
              Assert.assertTrue(e.getMessage(), e.getMessage().contains("insert \";\" to complete BlockStatements"));
          }
  
          try
          {
              execute("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".jfinv() " +
                      "RETURNS NULL ON NULL INPUT " +
                      "RETURNS bigint " +
                      "LANGUAGE JAVA\n" +
                      "AS '\n" +
                      "foobarbaz;" +
                      "\n';");
              Assert.fail();
          }
          catch (InvalidRequestException e)
          {
              Assert.assertTrue(e.getMessage(), e.getMessage().contains("Java source compilation failed"));
              Assert.assertTrue(e.getMessage(), e.getMessage().contains("foobarbaz cannot be resolved"));
          }
      }
  
      @Test
      public void testJavaFunctionInvalidReturn() throws Throwable
      {
          assertInvalidMessage("cannot convert from boolean to double",
                               "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".jfir(val double) " +
                               "RETURNS NULL ON NULL INPUT " +
                               "RETURNS double " +
                               "LANGUAGE JAVA\n" +
                               "AS 'return true;';");
      }
  
      @Test
      public void testJavaFunctionArgumentTypeMismatch() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, val bigint)");
  
          String fName = createFunction(KEYSPACE, "double",
                                        "CREATE OR REPLACE FUNCTION %s(val double)" +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS double " +
                                        "LANGUAGE JAVA " +
                                        "AS 'return Double.valueOf(val);';");
  
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1L);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2L);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3L);
          assertInvalidMessage("val cannot be passed as argument 0 of function",
                               "SELECT key, val, " + fName + "(val) FROM %s");
      }
  
      @Test
      public void testJavaFunction() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, val double)");
  
          String functionBody = '\n' +
                                "  // parameter val is of type java.lang.Double\n" +
                                "  /* return type is of type java.lang.Double */\n" +
                                "  if (val == null) {\n" +
                                "    return null;\n" +
                                "  }\n" +
                                "  return Math.sin(val);\n";
  
          String fName = createFunction(KEYSPACE, "double",
                                        "CREATE OR REPLACE FUNCTION %s(val double) " +
                                        "CALLED ON NULL INPUT " +
                                        "RETURNS double " +
                                        "LANGUAGE JAVA " +
                                        "AS '" + functionBody + "';");
  
          FunctionName fNameName = parseFunctionName(fName);
  
          assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?",
                             fNameName.keyspace, fNameName.name),
                     row("java", functionBody));
  
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d);
          assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
                     row(1, 1d, Math.sin(1d)),
                     row(2, 2d, Math.sin(2d)),
                     row(3, 3d, Math.sin(3d))
          );
      }
  
      @Test
      public void testJavaFunctionCounter() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, val counter)");
  
          String fName = createFunction(KEYSPACE, "counter",
                                        "CREATE OR REPLACE FUNCTION %s(val counter) " +
                                        "CALLED ON NULL INPUT " +
                                        "RETURNS bigint " +
                                        "LANGUAGE JAVA " +
                                        "AS 'return val + 1;';");
  
          execute("UPDATE %s SET val = val + 1 WHERE key = 1");
          assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
                     row(1, 1L, 2L));
          execute("UPDATE %s SET val = val + 1 WHERE key = 1");
          assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
                     row(1, 2L, 3L));
          execute("UPDATE %s SET val = val + 2 WHERE key = 1");
          assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
                     row(1, 4L, 5L));
          execute("UPDATE %s SET val = val - 2 WHERE key = 1");
          assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
                     row(1, 2L, 3L));
      }
  
      @Test
      public void testJavaKeyspaceFunction() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, val double)");
  
          String functionBody = '\n' +
                                "  // parameter val is of type java.lang.Double\n" +
                                "  /* return type is of type java.lang.Double */\n" +
                                "  if (val == null) {\n" +
                                "    return null;\n" +
                                "  }\n" +
                                "  return Math.sin( val );\n";
  
          String fName = createFunction(KEYSPACE_PER_TEST, "double",
                                        "CREATE OR REPLACE FUNCTION %s(val double) " +
                                        "CALLED ON NULL INPUT " +
                                        "RETURNS double " +
                                        "LANGUAGE JAVA " +
                                        "AS '" + functionBody + "';");
  
          FunctionName fNameName = parseFunctionName(fName);
  
          assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?",
                             fNameName.keyspace, fNameName.name),
                     row("java", functionBody));
  
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d);
          assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
                     row(1, 1d, Math.sin(1d)),
                     row(2, 2d, Math.sin(2d)),
                     row(3, 3d, Math.sin(3d))
          );
      }
  
      @Test
      public void testJavaRuntimeException() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, val double)");
  
          String functionBody = '\n' +
                                "  throw new RuntimeException(\"oh no!\");\n";
  
          String fName = createFunction(KEYSPACE_PER_TEST, "double",
                                        "CREATE OR REPLACE FUNCTION %s(val double) " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS double " +
                                        "LANGUAGE JAVA\n" +
                                        "AS '" + functionBody + "';");
  
          FunctionName fNameName = parseFunctionName(fName);
  
          assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?",
                             fNameName.keyspace, fNameName.name),
                     row("java", functionBody));
  
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d);
          execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d);
  
          // function throws a RuntimeException which is wrapped by FunctionExecutionException
          assertInvalidThrowMessage("java.lang.RuntimeException: oh no", FunctionExecutionException.class,
                                    "SELECT key, val, " + fName + "(val) FROM %s");
      }
  
      @Test
      public void testJavaDollarQuotedFunction() throws Throwable
      {
          String functionBody = '\n' +
                                "  // parameter val is of type java.lang.Double\n" +
                                "  /* return type is of type java.lang.Double */\n" +
                                "  if (input == null) {\n" +
                                "    return null;\n" +
                                "  }\n" +
                                "  return \"'\"+Math.sin(input)+'\\\'';\n";
  
          String fName = createFunction(KEYSPACE_PER_TEST, "double",
                                        "CREATE FUNCTION %s( input double ) " +
                                        "CALLED ON NULL INPUT " +
                                        "RETURNS text " +
                                        "LANGUAGE java\n" +
                                        "AS $$" + functionBody + "$$;");
  
          FunctionName fNameName = parseFunctionName(fName);
  
          assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?",
                             fNameName.keyspace, fNameName.name),
                     row("java", functionBody));
      }
  
      @Test
      public void testJavaSimpleCollections() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, lst list<double>, st set<text>, mp map<int, boolean>)");
  
          String fList = createFunction(KEYSPACE_PER_TEST, "list<double>",
                                        "CREATE FUNCTION %s( lst list<double> ) " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS list<double> " +
                                        "LANGUAGE java\n" +
                                        "AS $$return lst;$$;");
          String fSet = createFunction(KEYSPACE_PER_TEST, "set<text>",
                                       "CREATE FUNCTION %s( st set<text> ) " +
                                       "RETURNS NULL ON NULL INPUT " +
                                       "RETURNS set<text> " +
                                       "LANGUAGE java\n" +
                                       "AS $$return st;$$;");
          String fMap = createFunction(KEYSPACE_PER_TEST, "map<int, boolean>",
                                       "CREATE FUNCTION %s( mp map<int, boolean> ) " +
                                       "RETURNS NULL ON NULL INPUT " +
                                       "RETURNS map<int, boolean> " +
                                       "LANGUAGE java\n" +
                                       "AS $$return mp;$$;");
  
          List<Double> list = Arrays.asList(1d, 2d, 3d);
          Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two"));
          Map<Integer, Boolean> map = new TreeMap<>();
          map.put(1, true);
          map.put(2, false);
          map.put(3, true);
  
          execute("INSERT INTO %s (key, lst, st, mp) VALUES (1, ?, ?, ?)", list, set, map);
  
          assertRows(execute("SELECT " + fList + "(lst), " + fSet + "(st), " + fMap + "(mp) FROM %s WHERE key = 1"),
                     row(list, set, map));
  
          // same test - but via native protocol
          for (ProtocolVersion version : PROTOCOL_VERSIONS)
              assertRowsNet(version,
                            executeNet(version, "SELECT " + fList + "(lst), " + fSet + "(st), " + fMap + "(mp) FROM %s WHERE key = 1"),
                            row(list, set, map));
      }
  
      @Test
      public void testJavaTupleType() throws Throwable
      {
          createTable("CREATE TABLE %s (key int primary key, tup frozen<tuple<double, text, int, boolean>>)");
  
          String fName = createFunction(KEYSPACE, "tuple<double, text, int, boolean>",
                                        "CREATE FUNCTION %s( tup tuple<double, text, int, boolean> ) " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS tuple<double, text, int, boolean> " +
                                        "LANGUAGE java\n" +
                                        "AS $$return tup;$$;");
  
          Object t = tuple(1d, "foo", 2, true);
  
          execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t);
  
          assertRows(execute("SELECT tup FROM %s WHERE key = 1"),
                     row(t));
  
          assertRows(execute("SELECT " + fName + "(tup) FROM %s WHERE key = 1"),
                     row(t));
      }
  
      @Test
      public void testJavaTupleTypeCollection() throws Throwable
      {
          String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>";
  
          createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)");
  
          String fTup0 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
                                        "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
                                        "CALLED ON NULL INPUT " +
                                        "RETURNS " + tupleTypeDef + ' ' +
                                        "LANGUAGE java\n" +
                                        "AS $$return " +
                                        "       tup;$$;");
          String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
                                        "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
                                        "CALLED ON NULL INPUT " +
                                        "RETURNS double " +
                                        "LANGUAGE java\n" +
                                        "AS $$return " +
                                        "       Double.valueOf(tup.getDouble(0));$$;");
          String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
                                        "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS list<double> " +
                                        "LANGUAGE java\n" +
                                        "AS $$return " +
                                        "       tup.getList(1, Double.class);$$;");
          String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
                                        "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS set<text> " +
                                        "LANGUAGE java\n" +
                                        "AS $$return " +
                                        "       tup.getSet(2, String.class);$$;");
          String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
                                        "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS map<int, boolean> " +
                                        "LANGUAGE java\n" +
                                        "AS $$return " +
                                        "       tup.getMap(3, Integer.class, Boolean.class);$$;");
  
          List<Double> list = Arrays.asList(1d, 2d, 3d);
          Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two"));
          Map<Integer, Boolean> map = new TreeMap<>();
          map.put(1, true);
          map.put(2, false);
          map.put(3, true);
  
          Object t = tuple(1d, list, set, map);
  
          execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t);
  
          assertRows(execute("SELECT " + fTup0 + "(tup) FROM %s WHERE key = 1"),
                     row(t));
          assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
                     row(1d));
          assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
                     row(list));
          assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
                     row(set));
          assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
                     row(map));
  
          // same test - but via native protocol
          // we use protocol V3 here to encode the expected version because the server
          // always serializes Collections using V3 - see CollectionSerializer's
          // serialize and deserialize methods.
          TupleType tType = tupleTypeOf(ProtocolVersion.V3,
                                        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 (ProtocolVersion 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 (ProtocolVersion 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());
                  com.datastax.driver.core.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 (ProtocolVersion version : PROTOCOL_VERSIONS)
          {
              List<Row> rowsNet = executeNet(version, "SELECT " + fUdt0 + "(udt) FROM %s WHERE key = 1").all();
              Assert.assertEquals(1, rowsNet.size());
              UDTValue udtVal = rowsNet.get(0).getUDTValue(0);
              Assert.assertEquals("one", udtVal.getString("txt"));
              Assert.assertEquals(1, udtVal.getInt("i"));
              assertRowsNet(version,
                            executeNet(version, "SELECT " + fUdt1 + "(udt) FROM %s WHERE key = 1"),
                            row("one"));
              assertRowsNet(version,
                            executeNet(version, "SELECT " + fUdt2 + "(udt) FROM %s WHERE key = 1"),
                            row(1));
          }
      }
  
      @Test
      public void testJavaUserTypeRenameField() throws Throwable
      {
          String type = KEYSPACE + '.' + createType("CREATE TYPE %s (txt text, i int)");
  
          createTable("CREATE TABLE %s (key int primary key, udt frozen<" + type + ">)");
  
          String fName = createFunction(KEYSPACE, type,
                                        "CREATE FUNCTION %s( udt " + type + " ) " +
                                        "RETURNS NULL ON NULL INPUT " +
                                        "RETURNS text " +
                                        "LANGUAGE java\n" +
                                        "AS $$return udt.getString(\"txt\");$$;");
  
          execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})");
  
          assertRows(execute("SELECT " + fName + "(udt) FROM %s WHERE key = 1"),
                     row("one"));
  
          execute("ALTER TYPE " + type + " RENAME txt TO str");
  
          assertInvalidMessage("txt is not a field defined in this UDT",
                               "SELECT " + fName + "(udt) FROM %s WHERE key = 1");
  
          execute("ALTER TYPE " + type + " RENAME str TO txt");
  
          assertRows(execute("SELECT " + fName + "(udt) FROM %s WHERE key = 1"),
                     row("one"));
      }
  
      @Test
      public void testJavaUserTypeAddFieldWithReplace() throws Throwable
      {
          String type = KEYSPACE + '.' + createType("CREATE TYPE %s (txt text, i int)");
  
          createTable("CREATE TABLE %s (key int primary key, udt frozen<" + type + ">)");
  
          String fName1replace = createFunction(KEYSPACE, type,
                                                "CREATE FUNCTION %s( udt " + type + ") " +
                                                "RETURNS NULL ON NULL INPUT " +
                                                "RETURNS text " +
                                                "LANGUAGE java\n" +
                                                "AS $$return udt.getString(\"txt\");$$;");
          String fName2replace = createFunction(KEYSPACE, type,
                                                "CREATE FUNCTION %s( udt " + type + " ) " +
                                                "CALLED ON NULL INPUT " +
                                                "RETURNS int " +
                                                "LANGUAGE java\n" +
                                                "AS $$return Integer.valueOf(udt.getInt(\"i\"));$$;");
          String fName3replace = createFunction(KEYSPACE, type,
                                                "CREATE FUNCTION %s( udt " + type + " ) " +
                                                "CALLED ON NULL INPUT " +
                                                "RETURNS double " +
                                                "LANGUAGE java\n" +
                                                "AS $$return Double.valueOf(udt.getDouble(\"added\"));$$;");
          String fName4replace = createFunction(KEYSPACE, type,
                                                "CREATE FUNCTION %s( udt " + type + " ) " +
                                                "RETURNS NULL ON NULL INPUT " +
                                                "RETURNS " + type + " " +
                                                "LANGUAGE java\n" +
                                                "AS $$return udt;$$;");
  
          String fName1noReplace = createFunction(KEYSPACE, type,
                                                  "CREATE FUNCTION %s( udt " + type + " ) " +
                                                  "RETURNS NULL ON NULL INPUT " +
                                                  "RETURNS text " +
                                                  "LANGUAGE java\n" +
                                                  "AS $$return udt.getString(\"txt\");$$;");
          String fName2noReplace = createFunction(KEYSPACE, type,
                                                  "CREATE FUNCTION %s( udt " + type + " ) " +
                                                  "CALLED ON NULL INPUT " +
                                                  "RETURNS int " +
                                                  "LANGUAGE java\n" +
                                                  "AS $$return Integer.valueOf(udt.getInt(\"i\"));$$;");
          String fName3noReplace = createFunction(KEYSPACE, type,
                                                  "CREATE FUNCTION %s( udt " + type + " ) " +
                                                  "CALLED ON NULL INPUT " +
                                                  "RETURNS double " +
                                                  "LANGUAGE java\n" +
                                                  "AS $$return Double.valueOf(udt.getDouble(\"added\"));$$;");
          String fName4noReplace = createFunction(KEYSPACE, type,
                                                  "CREATE FUNCTION %s( udt " + type + " ) " +
                                                  "RETURNS NULL ON NULL INPUT " +
                                                  "RETURNS " + type + " " +
                                                  "LANGUAGE java\n" +
                                                  "AS $$return udt;$$;");
  
          execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})");
  
          assertRows(execute("SELECT " + fName1replace + "(udt) FROM %s WHERE key = 1"),
                     row("one"));
          assertRows(execute("SELECT " + fName2replace + "(udt) FROM %s WHERE key = 1"),
                     row(1));
  
          // add field
  
          execute("ALTER TYPE " + type + " ADD added double");
  
          execute("INSERT INTO %s (key, udt) VALUES (2, {txt: 'two', i:2, added: 2})");
  
          // note: type references of functions remain at the state _before_ the type mutation
          // means we need to recreate the functions
  
          execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " +
                                "RETURNS NULL ON NULL INPUT " +
                                "RETURNS text " +
                                "LANGUAGE java\n" +
                                "AS $$return " +
                                "     udt.getString(\"txt\");$$;",
                                fName1replace, type));
          Assert.assertEquals(1, Schema.instance.getUserFunctions(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.getUserFunctions(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.getUserFunctions(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.getUserFunctions(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));
  
          // The mix of the package names org.apache.cassandra.cql3.functions.types and com.datastax.driver.core is
          // intentional to test the replacement of com.datastax.driver.core with org.apache.cassandra.cql3.functions.types.
          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 $$" +
                                         "     org.apache.cassandra.cql3.functions.types.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 = (org.apache.cassandra.cql3.functions.types.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 $$" +
                                         "     org.apache.cassandra.cql3.functions.types.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 (ProtocolVersion version : PROTOCOL_VERSIONS)
              assertRowsNet(version,
                            executeNet(version, "SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"),
                            row("three", "one", "two"));
      }
  
      @Test
      public void testAllNativeTypes() throws Throwable
      {
          StringBuilder sig = new StringBuilder();
          StringBuilder args = new StringBuilder();
          for (CQL3Type.Native type : CQL3Type.Native.values())
          {
              if (type == CQL3Type.Native.EMPTY)
                  continue;
  
              if (sig.length() > 0)
                  sig.append(',');
              sig.append(type.toString());
  
              if (args.length() > 0)
                  args.append(',');
              args.append("arg").append(type.toString()).append(' ').append(type.toString());
          }
          createFunction(KEYSPACE, sig.toString(),
                         "CREATE OR REPLACE FUNCTION %s(" + args + ") " +
                         "RETURNS NULL ON NULL INPUT " +
                         "RETURNS int " +
                         "LANGUAGE JAVA\n" +
                         "AS 'return 0;'");
  
          for (CQL3Type.Native type : CQL3Type.Native.values())
          {
              if (type == CQL3Type.Native.EMPTY)
                  continue;
  
              createFunction(KEYSPACE_PER_TEST, type.toString(),
                             "CREATE OR REPLACE FUNCTION %s(val " + type.toString() + ") " +
                             "RETURNS NULL ON NULL INPUT " +
                             "RETURNS int " +
                             "LANGUAGE JAVA\n" +
                             "AS 'return 0;'");
          }
      }
  
      @Test
      public void testUDFToCqlString()
      {
          UDFunction function = UDFunction.create(new FunctionName("my_ks", "my_function"),
                                                  Arrays.asList(ColumnIdentifier.getInterned("column", false)),
                                                  Arrays.asList(UTF8Type.instance),
                                                  Int32Type.instance,
                                                  false,
                                                  "java",
                                                  "return 0;");
  
          Assert.assertTrue(function.toCqlString(true, true, true).contains("CREATE FUNCTION IF NOT EXISTS"));
          Assert.assertFalse(function.toCqlString(true, true, false).contains("CREATE FUNCTION IF NOT EXISTS"));
  
          Assert.assertEquals(function.toCqlString(true, true, true), function.toCqlString(true, false, true));
          Assert.assertEquals(function.toCqlString(true, true, false), function.toCqlString(true, false, false));
      }
  
      @Test
      public void testUDAToCqlString() throws Throwable
      {
          // we have to create this function in DB otherwise UDAggregate creation below fails
          String stateFunctionName = createFunction(KEYSPACE, "int,int",
                                                    "CREATE OR REPLACE FUNCTION %s(state int, val int)\n" +
                                                    "    CALLED ON NULL INPUT\n" +
                                                    "    RETURNS int\n" +
                                                    "    LANGUAGE java\n" +
                                                    "    AS $$\n" +
                                                    "        return state + val;\n" +
                                                    "    $$;");
  
          // Java representation of state function so we can construct aggregate programmatically
          UDFunction stateFunction = UDFunction.create(new FunctionName(KEYSPACE, stateFunctionName.split("\\.")[1]),
                                                       Arrays.asList(ColumnIdentifier.getInterned("state", false),
                                                                     ColumnIdentifier.getInterned("val", false)),
                                                       Arrays.asList(Int32Type.instance, Int32Type.instance),
                                                       Int32Type.instance,
                                                       true,
                                                       "java",
                                                       "return state + val;");
  
          UDAggregate aggregate = UDAggregate.create(Collections.singleton(stateFunction),
                                                     new FunctionName(KEYSPACE, "my_aggregate"),
                                                     Collections.singletonList(Int32Type.instance),
                                                     Int32Type.instance,
                                                     new FunctionName(KEYSPACE, stateFunctionName.split("\\.")[1]),
                                                     null,
                                                     Int32Type.instance,
                                                     null);
  
          Assert.assertTrue(aggregate.toCqlString(true, true, true).contains("CREATE AGGREGATE IF NOT EXISTS"));
          Assert.assertFalse(aggregate.toCqlString(true, true, false).contains("CREATE AGGREGATE IF NOT EXISTS"));
  
          Assert.assertEquals(aggregate.toCqlString(true, true, true), aggregate.toCqlString(true, false, true));
          Assert.assertEquals(aggregate.toCqlString(true, true, false), aggregate.toCqlString(true, false, false));
      }
  }
  