You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2014/09/24 17:47:42 UTC
git commit: Optimize java source-based UDF invocation
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 <sn...@snazy.de>
Authored: Wed Sep 24 10:46:53 2014 -0500
Committer: Tyler Hobbs <ty...@datastax.com>
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