Repository: cassandra Updated Branches: refs/heads/trunk 85401dc76 -> dad0bfac6
Optimize java source-based UDF invocation Patch by Robert Stupp; reviewed by Benjamin Lerer for CASSANDRA-7924 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/dad0bfac Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/dad0bfac Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/dad0bfac Branch: refs/heads/trunk Commit: dad0bfac6e885176d83f802bc75763c5ba21d68e Parents: 85401dc Author: Robert Stupp <[email protected]> Authored: Wed Sep 24 10:46:53 2014 -0500 Committer: Tyler Hobbs <[email protected]> Committed: Wed Sep 24 10:46:53 2014 -0500 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/functions/AbstractJavaUDF.java | 91 ------- .../cql3/functions/JavaSourceBasedUDF.java | 112 --------- .../cql3/functions/JavaSourceUDFFactory.java | 252 +++++++++++++++++++ .../cql3/functions/ReflectionBasedUDF.java | 75 ++++-- .../cassandra/cql3/functions/UDFunction.java | 15 +- test/unit/org/apache/cassandra/cql3/UFTest.java | 31 ++- 7 files changed, 343 insertions(+), 234 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 267c4c2..cf8f263 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0 + * Optimize java source-based UDF invocation (CASSANDRA-7924) * Accept dollar quoted strings in CQL (CASSANDRA-7769) * Make assassinate a first class command (CASSANDRA-7935) * Support IN clause on any clustering column (CASSANDRA-4762) http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java b/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java deleted file mode 100644 index f147f00..0000000 --- a/src/java/org/apache/cassandra/cql3/functions/AbstractJavaUDF.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.functions; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.util.List; - -import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.exceptions.InvalidRequestException; - -/** - * UDF implementation base class for reflection and Java source UDFs. - */ -abstract class AbstractJavaUDF extends UDFunction -{ - private final Method method; - - AbstractJavaUDF(FunctionName name, - List<ColumnIdentifier> argNames, - List<AbstractType<?>> argTypes, - AbstractType<?> returnType, - String language, - String body, - boolean deterministic) - throws InvalidRequestException - { - super(name, argNames, argTypes, returnType, language, body, deterministic); - assert language.equals(requiredLanguage()); - this.method = resolveMethod(); - } - - abstract String requiredLanguage(); - - abstract Method resolveMethod() throws InvalidRequestException; - - protected Class<?>[] javaParamTypes() - { - Class<?> paramTypes[] = new Class[argTypes.size()]; - for (int i = 0; i < paramTypes.length; i++) - paramTypes[i] = argTypes.get(i).getSerializer().getType(); - return paramTypes; - } - - protected Class<?> javaReturnType() - { - return returnType.getSerializer().getType(); - } - - public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException - { - Object[] parms = new Object[argTypes.size()]; - for (int i = 0; i < parms.length; i++) - { - ByteBuffer bb = parameters.get(i); - if (bb != null) - parms[i] = argTypes.get(i).compose(bb); - } - - Object result; - try - { - result = method.invoke(null, parms); - @SuppressWarnings("unchecked") ByteBuffer r = result != null ? ((AbstractType) returnType).decompose(result) : null; - return r; - } - catch (InvocationTargetException | IllegalAccessException e) - { - Throwable c = e.getCause(); - logger.error("Invocation of function '{}' failed", this, c); - throw new InvalidRequestException("Invocation of function '" + this + "' failed: " + c); - } - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java b/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java deleted file mode 100644 index 7e483a0..0000000 --- a/src/java/org/apache/cassandra/cql3/functions/JavaSourceBasedUDF.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.functions; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import javassist.CannotCompileException; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtNewMethod; -import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.exceptions.InvalidRequestException; - -/** - * User-defined function using Java source code in UDF body. - * <p/> - * This is used when the LANGUAGE of the UDF definition is "java". - */ -final class JavaSourceBasedUDF extends AbstractJavaUDF -{ - static final AtomicInteger clsIdgen = new AtomicInteger(); - - JavaSourceBasedUDF(FunctionName name, - List<ColumnIdentifier> argNames, - List<AbstractType<?>> argTypes, - AbstractType<?> returnType, - String language, - String body, - boolean deterministic) - throws InvalidRequestException - { - super(name, argNames, argTypes, returnType, language, body, deterministic); - } - - String requiredLanguage() - { - return "java"; - } - - Method resolveMethod() throws InvalidRequestException - { - Class<?> jReturnType = javaReturnType(); - Class<?>[] paramTypes = javaParamTypes(); - - StringBuilder code = new StringBuilder(); - code.append("public static "). - append(jReturnType.getName()).append(' '). - append(name.name).append('('); - for (int i = 0; i < paramTypes.length; i++) - { - if (i > 0) - code.append(", "); - code.append(paramTypes[i].getName()). - append(' '). - append(argNames.get(i)); - } - code.append(") { "); - code.append(body); - code.append('}'); - - ClassPool classPool = ClassPool.getDefault(); - CtClass cc = classPool.makeClass("org.apache.cassandra.cql3.udf.gen.C" + javaIdentifierPart(name.toString()) + '_' + clsIdgen.incrementAndGet()); - try - { - cc.addMethod(CtNewMethod.make(code.toString(), cc)); - Class<?> clazz = cc.toClass(); - return clazz.getMethod(name.name, paramTypes); - } - catch (LinkageError e) - { - throw new InvalidRequestException("Could not compile function '" + name + "' from Java source: " + e.getMessage()); - } - catch (CannotCompileException e) - { - throw new InvalidRequestException("Could not compile function '" + name + "' from Java source: " + e.getReason()); - } - catch (NoSuchMethodException e) - { - throw new InvalidRequestException("Could not build function '" + name + "' from Java source"); - } - } - - private static String javaIdentifierPart(String qualifiedName) - { - StringBuilder sb = new StringBuilder(qualifiedName.length()); - for (int i = 0; i < qualifiedName.length(); i++) - { - char c = qualifiedName.charAt(i); - if (Character.isJavaIdentifierPart(c)) - sb.append(c); - } - return sb.toString(); - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java b/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java new file mode 100644 index 0000000..0f5fe48 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java @@ -0,0 +1,252 @@ +/* + * 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.functions; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtNewConstructor; +import javassist.CtNewMethod; +import javassist.NotFoundException; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * Java source UDF code generation. + */ +public final class JavaSourceUDFFactory +{ + private static final String GENERATED_CODE_PACKAGE = "org.apache.cassandra.cql3.udf.gen."; + + protected static final Logger logger = LoggerFactory.getLogger(JavaSourceUDFFactory.class); + + private static final AtomicInteger classSequence = new AtomicInteger(); + + static UDFunction buildUDF(FunctionName name, + List<ColumnIdentifier> argNames, + List<AbstractType<?>> argTypes, + AbstractType<?> returnType, + String body, + boolean deterministic) + throws InvalidRequestException + { + Class<?> javaReturnType = UDFunction.javaType(returnType); + Class<?>[] javaParamTypes = UDFunction.javaParamTypes(argTypes); + + String clsName = generateClassName(name); + + String codeCtor = generateConstructor(clsName); + + // Generate 'execute' method (implements org.apache.cassandra.cql3.functions.Function.execute) + String codeExec = generateExecuteMethod(argNames, javaParamTypes); + + // Generate the 'executeInternal' method + // It is separated to allow return type and argument type checks during compile time via javassist. + String codeExecInt = generateExecuteInternalMethod(argNames, body, javaReturnType, javaParamTypes); + + if (logger.isDebugEnabled()) + logger.debug("Generating java source UDF for {} with following c'tor and functions:\n{}\n{}\n{}", + name, codeCtor, codeExecInt, codeExec); + + try + { + ClassPool classPool = ClassPool.getDefault(); + + // get super class + CtClass base = classPool.get(UDFunction.class.getName()); + + // prepare class to generate + CtClass cc = classPool.makeClass(GENERATED_CODE_PACKAGE + clsName, base); + cc.setModifiers(cc.getModifiers() | Modifier.FINAL); + + // add c'tor plus methods (order matters) + cc.addConstructor(CtNewConstructor.make(codeCtor, cc)); + cc.addMethod(CtNewMethod.make(codeExecInt, cc)); + cc.addMethod(CtNewMethod.make(codeExec, cc)); + + Constructor ctor = + cc.toClass().getDeclaredConstructor( + FunctionName.class, List.class, List.class, + AbstractType.class, String.class, boolean.class); + return (UDFunction) ctor.newInstance(name, argNames, argTypes, returnType, body, deterministic); + } + catch (NotFoundException | CannotCompileException | NoSuchMethodException | LinkageError | InstantiationException | IllegalAccessException e) + { + throw new InvalidRequestException(String.format("Could not compile function '%s' from Java source: %s", name, e)); + } + catch (InvocationTargetException e) + { + // in case of an ITE, use the cause + throw new InvalidRequestException(String.format("Could not compile function '%s' from Java source: %s", name, e.getCause())); + } + } + + private static String generateClassName(FunctionName name) + { + String qualifiedName = name.toString(); + + StringBuilder sb = new StringBuilder(qualifiedName.length()+10); + sb.append('C'); + for (int i = 0; i < qualifiedName.length(); i++) + { + char c = qualifiedName.charAt(i); + if (Character.isJavaIdentifierPart(c)) + sb.append(c); + } + sb.append('_'); + sb.append(classSequence.incrementAndGet()); + return sb.toString(); + } + + /** + * Generates constructor with just a call super class (UDFunction) constructor with constant 'java' as language. + */ + private static String generateConstructor(String clsName) + { + return "public " + clsName + + "(org.apache.cassandra.cql3.functions.FunctionName name, " + + "java.util.List argNames, " + + "java.util.List argTypes, " + + "org.apache.cassandra.db.marshal.AbstractType returnType, " + + "String body," + + "boolean deterministic)\n{" + + " super(name, argNames, argTypes, returnType, \"java\", body, deterministic);\n" + + "}"; + } + + /** + * Generate executeInternal method (just there to find return and argument type mismatches in UDF body). + * + * Generated looks like this: + * <code><pre> + * private <JAVA_RETURN_TYPE> executeInternal(<JAVA_ARG_TYPE> paramOne, <JAVA_ARG_TYPE> nextParam) + * { + * <UDF_BODY> + * } + * </pre></code> + */ + private static String generateExecuteInternalMethod(List<ColumnIdentifier> argNames, String body, Class<?> returnType, Class<?>[] paramTypes) + { + // initial builder size can just be a guess (prevent temp object allocations) + StringBuilder codeInt = new StringBuilder(64 + 32*paramTypes.length + body.length()); + codeInt.append("private ").append(returnType.getName()).append(" executeInternal("); + for (int i = 0; i < paramTypes.length; i++) + { + if (i > 0) + codeInt.append(", "); + codeInt.append(paramTypes[i].getName()). + append(' '). + append(argNames.get(i)); + } + codeInt.append(")\n{"). + append(body). + append('}'); + return codeInt.toString(); + } + + /** + * + * Generate public execute() method implementation. + * + * Generated looks like this: + * <code><pre> + * + * public java.nio.ByteBuffer execute(java.util.List params) + * throws org.apache.cassandra.exceptions.InvalidRequestException + * { + * try + * { + * Object result = executeInternal( + * (<cast to JAVA_ARG_TYPE>)org.apache.cassandra.cql3.functions.JavaSourceUDFFactory.compose(argTypes, params, 0) + * ); + * return result != null ? returnType.decompose(result) : null; + * } + * catch (Throwable t) + * { + * logger.error("Invocation of function '{}' failed", this, t); + * if (t instanceof VirtualMachineError) + * throw (VirtualMachineError)t; + * throw new org.apache.cassandra.exceptions.InvalidRequestException("Invocation of function '" + this + "' failed: " + t); + * } + * } + * </pre></code> + */ + private static String generateExecuteMethod(List<ColumnIdentifier> argNames, Class<?>[] paramTypes) + { + // usual methods are 700-800 chars long (prevent temp object allocations) + StringBuilder code = new StringBuilder(1024); + // overrides org.apache.cassandra.cql3.functions.Function.execute(java.util.List) + code.append("public java.nio.ByteBuffer execute(java.util.List params)\n" + + "throws org.apache.cassandra.exceptions.InvalidRequestException\n" + + "{\n" + + " try\n" + + " {\n" + + " Object result = executeInternal("); + for (int i = 0; i < paramTypes.length; i++) + { + if (i > 0) + code.append(','); + + if (logger.isDebugEnabled()) + code.append("\n /* ").append(argNames.get(i)).append(" */ "); + + code. + // cast to Java type + append("\n (").append(paramTypes[i].getName()).append(")"). + // generate object representation of input parameter + append("org.apache.cassandra.cql3.functions.JavaSourceUDFFactory.compose(argTypes, params, ").append(i).append(')'); + } + + code.append("\n );\n" + + // generate serialized return value (returnType is a field in AbstractFunction class) + " return result != null ? returnType.decompose(result) : null;\n" + + // + // error handling ... + " }\n" + + " catch (Throwable t)\n" + + " {\n" + + " logger.error(\"Invocation of function '{}' failed\", this, t);\n" + + // handle OutOfMemoryError and other fatals not here! + " if (t instanceof VirtualMachineError)\n" + + " throw (VirtualMachineError)t;\n" + + " throw new org.apache.cassandra.exceptions.InvalidRequestException(\"Invocation of function '\" + this + \"' failed: \" + t);\n" + + " }\n" + + "}"); + + return code.toString(); + } + + // Used by execute() implementations of generated java source UDFs. + public static Object compose(List<AbstractType<?>> argTypes, List<ByteBuffer> parameters, int param) + { + ByteBuffer bb = parameters.get(param); + return bb == null ? null : argTypes.get(param).compose(bb); + } + +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java b/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java index e02147a..911537f 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java +++ b/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java @@ -17,8 +17,10 @@ */ package org.apache.cassandra.cql3.functions; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; @@ -32,8 +34,10 @@ import org.apache.cassandra.exceptions.InvalidRequestException; * * This is used when the LANGUAGE of the UDF definition is "class". */ -final class ReflectionBasedUDF extends AbstractJavaUDF +final class ReflectionBasedUDF extends UDFunction { + private final MethodHandle method; + ReflectionBasedUDF(FunctionName name, List<ColumnIdentifier> argNames, List<AbstractType<?>> argTypes, @@ -44,17 +48,14 @@ final class ReflectionBasedUDF extends AbstractJavaUDF throws InvalidRequestException { super(name, argNames, argTypes, returnType, language, body, deterministic); + assert language.equals("class"); + this.method = resolveMethod(); } - String requiredLanguage() - { - return "class"; - } - - Method resolveMethod() throws InvalidRequestException + private MethodHandle resolveMethod() throws InvalidRequestException { - Class<?> jReturnType = javaReturnType(); - Class<?>[] paramTypes = javaParamTypes(); + Class<?> jReturnType = javaType(returnType); + Class<?>[] paramTypes = javaParamTypes(argTypes); String className; String methodName; @@ -73,18 +74,11 @@ final class ReflectionBasedUDF extends AbstractJavaUDF { Class<?> cls = Class.forName(className, false, Thread.currentThread().getContextClassLoader()); - Method method = cls.getMethod(methodName, paramTypes); - - if (!Modifier.isStatic(method.getModifiers())) - throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") is not static"); - - if (!jReturnType.isAssignableFrom(method.getReturnType())) - { - throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") " + - "has incompatible return type " + method.getReturnType() + " (not assignable to " + jReturnType + ')'); - } + MethodHandles.Lookup handles = MethodHandles.lookup(); + MethodType methodType = MethodType.methodType(jReturnType, paramTypes); + MethodHandle handle = handles.findStatic(cls, methodName, methodType); - return method; + return handle; } catch (ClassNotFoundException e) { @@ -92,7 +86,42 @@ final class ReflectionBasedUDF extends AbstractJavaUDF } catch (NoSuchMethodException e) { - throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") does not exist"); + throw new InvalidRequestException("Method 'public static " + jReturnType.getName() + ' ' + + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + + ")' does not exist - check for static, argument types and return type"); + } + catch (IllegalAccessException e) + { + throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") is not accessible"); + } + } + + public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException + { + Object[] parms = new Object[argTypes.size()]; + for (int i = 0; i < parms.length; i++) + { + ByteBuffer bb = parameters.get(i); + if (bb != null) + parms[i] = argTypes.get(i).compose(bb); + } + + Object result; + try + { + result = method.invokeWithArguments(parms); + @SuppressWarnings("unchecked") ByteBuffer r = result != null ? ((AbstractType) returnType).decompose(result) : null; + return r; + } + catch (VirtualMachineError e) + { + // handle OutOfMemoryError and other fatals not here! + throw e; + } + catch (Throwable e) + { + logger.error("Invocation of function '{}' failed", this, e); + throw new InvalidRequestException("Invocation of function '" + this + "' failed: " + e); } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/src/java/org/apache/cassandra/cql3/functions/UDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java index b4a706d..3ef5764 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -77,11 +77,24 @@ public abstract class UDFunction extends AbstractFunction switch (language) { case "class": return new ReflectionBasedUDF(name, argNames, argTypes, returnType, language, body, deterministic); - case "java": return new JavaSourceBasedUDF(name, argNames, argTypes, returnType, language, body, deterministic); + case "java": return JavaSourceUDFFactory.buildUDF(name, argNames, argTypes, returnType, body, deterministic); default: throw new InvalidRequestException(String.format("Invalid language %s for '%s'", language, name)); } } + static Class<?>[] javaParamTypes(List<AbstractType<?>> argTypes) + { + Class<?> paramTypes[] = new Class[argTypes.size()]; + for (int i = 0; i < paramTypes.length; i++) + paramTypes[i] = javaType(argTypes.get(i)); + return paramTypes; + } + + static Class<?> javaType(AbstractType<?> type) + { + return type.getSerializer().getType(); + } + /** * It can happen that a function has been declared (is listed in the scheam) but cannot * be loaded (maybe only on some nodes). This is the case for instance if the class defining http://git-wip-us.apache.org/repos/asf/cassandra/blob/dad0bfac/test/unit/org/apache/cassandra/cql3/UFTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFTest.java b/test/unit/org/apache/cassandra/cql3/UFTest.java index 3a48500..5dd77bf 100644 --- a/test/unit/org/apache/cassandra/cql3/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/UFTest.java @@ -146,6 +146,9 @@ public class UFTest extends CQLTester // can't drop native functions assertInvalid("DROP FUNCTION dateof"); assertInvalid("DROP FUNCTION uuid"); + + // sin() no longer exists + assertInvalid("SELECT key, sin(d) FROM %s"); } @Test @@ -213,6 +216,11 @@ public class UFTest extends CQLTester @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); + execute("create function foo::corjf ( input double ) returns double language java\n" + "AS '\n" + " // parameter val is of type java.lang.Double\n" + @@ -223,16 +231,25 @@ public class UFTest extends CQLTester " double v = Math.sin( input.doubleValue() );\n" + " return Double.valueOf(v);\n" + "';"); + + // just check created function + assertRows(execute("SELECT key, val, foo::corjf(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 foo::corjf ( input double ) returns double language java\n" + "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" + - " double v = Math.sin( input.doubleValue() );\n" + - " return Double.valueOf(v);\n" + + " return input;\n" + "';"); + + // check if replaced function returns correct result + assertRows(execute("SELECT key, val, foo::corjf(val) FROM %s"), + row(1, 1d, 1d), + row(2, 2d, 2d), + row(3, 3d, 3d) + ); } @Test
