Repository: cassandra Updated Branches: refs/heads/trunk 1925a014b -> 64cfcf055
Generic Java UDF types patch by Robert Stupp; reviewed by DOAN DuyHai for CASSANDRA-10819 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/64cfcf05 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/64cfcf05 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/64cfcf05 Branch: refs/heads/trunk Commit: 64cfcf05568e1d0216ee7b1d8719417291dd752a Parents: 1925a01 Author: Robert Stupp <[email protected]> Authored: Thu Feb 4 20:31:37 2016 +0100 Committer: Robert Stupp <[email protected]> Committed: Thu Feb 4 20:31:37 2016 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/functions/JavaBasedUDFunction.java | 28 +++++++----- .../cql3/functions/ScriptBasedUDFunction.java | 2 +- .../cassandra/cql3/functions/UDHelper.java | 16 ++++--- .../cql3/validation/entities/UFTest.java | 48 +++++++++++++++++++- 5 files changed, 75 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/64cfcf05/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index ffe50a3..3fcca42 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.4 + * Generic Java UDF types (CASSANDRA-10819) * cqlsh: Include sub-second precision in timestamps by default (CASSANDRA-10428) * Set javac encoding to utf-8 (CASSANDRA-11077) * Integrate SASI index into Cassandra (CASSANDRA-10661) http://git-wip-us.apache.org/repos/asf/cassandra/blob/64cfcf05/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java index c61e72e..b1dd9f9 100644 --- a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java @@ -35,8 +35,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; +import com.google.common.annotations.VisibleForTesting; import com.google.common.io.ByteStreams; +import com.google.common.reflect.TypeToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,10 +60,12 @@ import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; -final class JavaBasedUDFunction extends UDFunction +public final class JavaBasedUDFunction extends UDFunction { private static final String BASE_PACKAGE = "org.apache.cassandra.cql3.udf.gen"; + private static final Pattern JAVA_LANG_PREFIX = Pattern.compile("\\bjava\\.lang\\."); + static final Logger logger = LoggerFactory.getLogger(JavaBasedUDFunction.class); private static final AtomicInteger classSequence = new AtomicInteger(); @@ -185,9 +190,9 @@ final class JavaBasedUDFunction extends UDFunction returnType, UDHelper.driverType(returnType), calledOnNullInput, "java", body); // javaParamTypes is just the Java representation for argTypes resp. argDataTypes - Class<?>[] javaParamTypes = UDHelper.javaTypes(argDataTypes, calledOnNullInput); + TypeToken<?>[] javaParamTypes = UDHelper.typeTokens(argDataTypes, calledOnNullInput); // javaReturnType is just the Java representation for returnType resp. returnDataType - Class<?> javaReturnType = UDHelper.asJavaClass(returnDataType); + TypeToken<?> javaReturnType = UDHelper.asTypeToken(returnDataType); // put each UDF in a separate package to prevent cross-UDF code access String pkgName = BASE_PACKAGE + '.' + generateClassName(name, 'p'); @@ -244,7 +249,7 @@ final class JavaBasedUDFunction extends UDFunction { EcjCompilationUnit compilationUnit = new EcjCompilationUnit(javaSource, targetClassName); - org.eclipse.jdt.internal.compiler.Compiler compiler = new Compiler(compilationUnit, + Compiler compiler = new Compiler(compilationUnit, errorHandlingPolicy, compilerOptions, compilationUnit, @@ -392,13 +397,14 @@ final class JavaBasedUDFunction extends UDFunction return sb.toString(); } - private static String javaSourceName(Class<?> type) + @VisibleForTesting + public static String javaSourceName(TypeToken<?> type) { - String n = type.getName(); - return n.startsWith("java.lang.") ? type.getSimpleName() : n; + String n = type.toString(); + return JAVA_LANG_PREFIX.matcher(n).replaceAll(""); } - private static String generateArgumentList(Class<?>[] paramTypes, List<ColumnIdentifier> argNames) + private static String generateArgumentList(TypeToken<?>[] paramTypes, List<ColumnIdentifier> argNames) { // initial builder size can just be a guess (prevent temp object allocations) StringBuilder code = new StringBuilder(32 * paramTypes.length); @@ -413,7 +419,7 @@ final class JavaBasedUDFunction extends UDFunction return code.toString(); } - private static String generateArguments(Class<?>[] paramTypes, List<ColumnIdentifier> argNames) + private static String generateArguments(TypeToken<?>[] paramTypes, List<ColumnIdentifier> argNames) { StringBuilder code = new StringBuilder(64 * paramTypes.length); for (int i = 0; i < paramTypes.length; i++) @@ -433,9 +439,9 @@ final class JavaBasedUDFunction extends UDFunction return code.toString(); } - private static String composeMethod(Class<?> type) + private static String composeMethod(TypeToken<?> type) { - return (type.isPrimitive()) ? ("super.compose_" + type.getName()) : "super.compose"; + return (type.isPrimitive()) ? ("super.compose_" + type.getRawType().getName()) : "super.compose"; } // Java source UDFs are a very simple compilation task, which allows us to let one class implement http://git-wip-us.apache.org/repos/asf/cassandra/blob/64cfcf05/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java index 4ffb992..bf28663 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java @@ -189,7 +189,7 @@ final class ScriptBasedUDFunction extends UDFunction if (result == null) return null; - Class<?> javaReturnType = UDHelper.asJavaClass(returnDataType); + Class<?> javaReturnType = UDHelper.asTypeToken(returnDataType).getRawType(); Class<?> resultType = result.getClass(); if (!javaReturnType.isAssignableFrom(resultType)) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/64cfcf05/src/java/org/apache/cassandra/cql3/functions/UDHelper.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDHelper.java b/src/java/org/apache/cassandra/cql3/functions/UDHelper.java index cc62c84..6237369 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDHelper.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDHelper.java @@ -23,6 +23,8 @@ import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.List; +import com.google.common.reflect.TypeToken; + import com.datastax.driver.core.CodecRegistry; import com.datastax.driver.core.DataType; import com.datastax.driver.core.ProtocolVersion; @@ -68,15 +70,16 @@ public final class UDHelper * @param calledOnNullInput whether to allow {@code null} as an argument value * @return array of same size with UDF arguments */ - public static Class<?>[] javaTypes(DataType[] dataTypes, boolean calledOnNullInput) + public static TypeToken<?>[] typeTokens(DataType[] dataTypes, boolean calledOnNullInput) { - Class<?>[] paramTypes = new Class[dataTypes.length]; + TypeToken<?>[] paramTypes = new TypeToken[dataTypes.length]; for (int i = 0; i < paramTypes.length; i++) { - Class<?> clazz = asJavaClass(dataTypes[i]); + TypeToken<?> typeToken = asTypeToken(dataTypes[i]); if (!calledOnNullInput) { // only care about classes that can be used in a data type + Class<?> clazz = typeToken.getRawType(); if (clazz == Integer.class) clazz = int.class; else if (clazz == Long.class) @@ -91,8 +94,9 @@ public final class UDHelper clazz = double.class; else if (clazz == Boolean.class) clazz = boolean.class; + typeToken = TypeToken.of(clazz); } - paramTypes[i] = clazz; + paramTypes[i] = typeToken; } return paramTypes; } @@ -149,9 +153,9 @@ public final class UDHelper return codec.serialize(value, ProtocolVersion.fromInt(protocolVersion)); } - public static Class<?> asJavaClass(DataType dataType) + public static TypeToken<?> asTypeToken(DataType dataType) { - return codecFor(dataType).getJavaType().getRawType(); + return codecFor(dataType).getJavaType(); } public static boolean isNullOrEmpty(AbstractType<?> type, ByteBuffer bb) http://git-wip-us.apache.org/repos/asf/cassandra/blob/64cfcf05/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java index cc0e806..f482d54 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java @@ -18,6 +18,7 @@ package org.apache.cassandra.cql3.validation.entities; import java.nio.ByteBuffer; +import java.security.AccessControlException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -27,21 +28,22 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.UUID; -import java.security.AccessControlException; +import com.google.common.reflect.TypeToken; import org.junit.Assert; import org.junit.Test; import com.datastax.driver.core.*; import com.datastax.driver.core.exceptions.InvalidQueryException; +import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; -import org.apache.cassandra.config.Config; import org.apache.cassandra.cql3.functions.FunctionName; +import org.apache.cassandra.cql3.functions.JavaBasedUDFunction; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UDHelper; import org.apache.cassandra.db.marshal.CollectionType; @@ -59,6 +61,15 @@ import org.apache.cassandra.utils.UUIDGen; public class UFTest extends CQLTester { @Test + public void testJavaSourceName() + { + Assert.assertEquals("String", JavaBasedUDFunction.javaSourceName(TypeToken.of(String.class))); + Assert.assertEquals("java.util.Map<Integer, String>", JavaBasedUDFunction.javaSourceName(TypeTokens.mapOf(Integer.class, String.class))); + Assert.assertEquals("com.datastax.driver.core.UDTValue", JavaBasedUDFunction.javaSourceName(TypeToken.of(UDTValue.class))); + Assert.assertEquals("java.util.Set<com.datastax.driver.core.UDTValue>", JavaBasedUDFunction.javaSourceName(TypeTokens.setOf(UDTValue.class))); + } + + @Test public void testNonExistingOnes() throws Throwable { assertInvalidThrowMessage("Cannot drop non existing function", InvalidRequestException.class, "DROP FUNCTION " + KEYSPACE + ".func_does_not_exist"); @@ -2485,4 +2496,37 @@ public class UFTest extends CQLTester } } } + + @Test + public void testArgumentGenerics() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, sval text, aval ascii, bval blob, empty_int int)"); + + String typeName = createType("CREATE TYPE %s (txt text, i int)"); + + String f = createFunction(KEYSPACE, "text", + "CREATE OR REPLACE FUNCTION %s(" + + " listText list<text>," + + " setText set<text>," + + " mapTextInt map<text, int>," + + " mapListTextSetInt map<frozen<list<text>>, frozen<set<int>>>," + + " mapTextTuple map<text, frozen<tuple<int, text>>>," + + " mapTextType map<text, frozen<" + typeName + ">>" + + ") " + + "CALLED ON NULL INPUT " + + "RETURNS map<frozen<list<text>>, frozen<set<int>>> " + + "LANGUAGE JAVA\n" + + "AS $$" + + " for (String s : listtext) {};" + + " for (String s : settext) {};" + + " for (String s : maptextint.keySet()) {};" + + " for (Integer s : maptextint.values()) {};" + + " for (java.util.List<String> l : maplisttextsetint.keySet()) {};" + + " for (java.util.Set<Integer> s : maplisttextsetint.values()) {};" + + " for (com.datastax.driver.core.TupleValue t : maptexttuple.values()) {};" + + " for (com.datastax.driver.core.UDTValue u : maptexttype.values()) {};" + + " return maplisttextsetint;" + + "$$"); + + } }
