UDF cleanups patch by slebresne; reviewed by thobbs for CASSANDRA-7809
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/44fa12ec Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/44fa12ec Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/44fa12ec Branch: refs/heads/trunk Commit: 44fa12ec435451710cf37463820b708ea637993a Parents: 75a30c5 Author: Sylvain Lebresne <[email protected]> Authored: Mon Aug 18 18:29:09 2014 +0200 Committer: Sylvain Lebresne <[email protected]> Committed: Wed Aug 27 10:39:58 2014 +0200 ---------------------------------------------------------------------- .../org/apache/cassandra/config/CFMetaData.java | 2 +- .../org/apache/cassandra/config/UFMetaData.java | 306 ------------------- .../apache/cassandra/cql3/AbstractMarker.java | 4 +- .../cassandra/cql3/AssignementTestable.java | 26 -- .../cassandra/cql3/AssignmentTestable.java | 72 +++++ .../org/apache/cassandra/cql3/Constants.java | 42 +-- src/java/org/apache/cassandra/cql3/Cql.g | 37 ++- src/java/org/apache/cassandra/cql3/Lists.java | 22 +- src/java/org/apache/cassandra/cql3/Maps.java | 32 +- src/java/org/apache/cassandra/cql3/Sets.java | 24 +- src/java/org/apache/cassandra/cql3/Term.java | 2 +- src/java/org/apache/cassandra/cql3/Tuples.java | 8 +- .../org/apache/cassandra/cql3/TypeCast.java | 17 +- .../apache/cassandra/cql3/UntypedResultSet.java | 5 + .../org/apache/cassandra/cql3/UserTypes.java | 8 +- .../cql3/functions/AbstractFunction.java | 61 ++-- .../cql3/functions/BytesConversionFcts.java | 8 +- .../cassandra/cql3/functions/Function.java | 14 +- .../cassandra/cql3/functions/FunctionCall.java | 62 ++-- .../cassandra/cql3/functions/FunctionName.java | 66 ++++ .../cassandra/cql3/functions/Functions.java | 243 +++++++++------ .../cql3/functions/NativeFunction.java | 50 +++ .../cql3/functions/ReflectionBasedUDF.java | 126 ++++++++ .../cassandra/cql3/functions/TimeuuidFcts.java | 10 +- .../cassandra/cql3/functions/TokenFct.java | 11 +- .../cassandra/cql3/functions/UDFunction.java | 250 +++++++++++++++ .../cassandra/cql3/functions/UuidFcts.java | 2 +- .../statements/CreateFunctionStatement.java | 161 +++------- .../cql3/statements/DropFunctionStatement.java | 41 +-- .../cassandra/cql3/statements/Selectable.java | 9 +- .../cassandra/cql3/statements/Selection.java | 37 +-- .../cql3/udf/UDFFunctionOverloads.java | 87 ------ .../apache/cassandra/cql3/udf/UDFRegistry.java | 146 --------- .../apache/cassandra/cql3/udf/UDFunction.java | 182 ----------- .../org/apache/cassandra/db/DefsTables.java | 58 ++-- .../cassandra/service/CassandraDaemon.java | 8 +- .../cassandra/service/MigrationManager.java | 38 +-- .../org/apache/cassandra/cql3/CQLTester.java | 19 +- .../org/apache/cassandra/cql3/TypeCastTest.java | 54 ++++ test/unit/org/apache/cassandra/cql3/UFTest.java | 212 ++++++------- 40 files changed, 1227 insertions(+), 1335 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/config/CFMetaData.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/CFMetaData.java b/src/java/org/apache/cassandra/config/CFMetaData.java index 2a93b39..b42ce69 100644 --- a/src/java/org/apache/cassandra/config/CFMetaData.java +++ b/src/java/org/apache/cassandra/config/CFMetaData.java @@ -217,7 +217,7 @@ public final class CFMetaData public static final CFMetaData SchemaFunctionsCf = compile("CREATE TABLE " + SystemKeyspace.SCHEMA_FUNCTIONS_CF + " (" + "namespace text," + "name text," - + "signature text," + + "signature blob," + "argument_names list<text>," + "argument_types list<text>," + "return_type text," http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/config/UFMetaData.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/UFMetaData.java b/src/java/org/apache/cassandra/config/UFMetaData.java deleted file mode 100644 index 4d5f6d3..0000000 --- a/src/java/org/apache/cassandra/config/UFMetaData.java +++ /dev/null @@ -1,306 +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.config; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.builder.ToStringBuilder; - -import org.antlr.runtime.ANTLRStringStream; -import org.antlr.runtime.CharStream; -import org.antlr.runtime.CommonTokenStream; -import org.antlr.runtime.RecognitionException; -import org.antlr.runtime.TokenStream; -import org.apache.cassandra.cql3.AssignementTestable; -import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.CqlLexer; -import org.apache.cassandra.cql3.CqlParser; -import org.apache.cassandra.cql3.QueryProcessor; -import org.apache.cassandra.cql3.UntypedResultSet; -import org.apache.cassandra.cql3.udf.UDFFunctionOverloads; -import org.apache.cassandra.cql3.udf.UDFRegistry; -import org.apache.cassandra.db.CFRowAdder; -import org.apache.cassandra.db.ColumnFamily; -import org.apache.cassandra.db.Keyspace; -import org.apache.cassandra.db.Mutation; -import org.apache.cassandra.db.RangeTombstone; -import org.apache.cassandra.db.Row; -import org.apache.cassandra.db.SystemKeyspace; -import org.apache.cassandra.db.composites.Composite; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.CompositeType; -import org.apache.cassandra.db.marshal.UTF8Type; -import org.apache.cassandra.exceptions.InvalidRequestException; - -/** - * Defined (and loaded) user functions. - * <p/> - * In practice, because user functions are global, we have only one instance of - * this class that retrieve through the Schema class. - */ -public final class UFMetaData -{ - public final String namespace; - public final String functionName; - public final String qualifiedName; - public final String returnType; - public final List<String> argumentNames; - public final List<String> argumentTypes; - public final String language; - public final String body; - public final boolean deterministic; - - public final String signature; - public final List<CQL3Type> cqlArgumentTypes; - public final CQL3Type cqlReturnType; - - static final CompositeType partKey = (CompositeType) CFMetaData.SchemaFunctionsCf.getKeyValidator(); - - // TODO tracking "valid" status via an exception field is really bad style - but we need some way to mark a function as "dead" - public InvalidRequestException invalid; - - public UFMetaData(String namespace, String functionName, boolean deterministic, List<String> argumentNames, - List<String> argumentTypes, String returnType, String language, String body) - { - this.namespace = namespace != null ? namespace.toLowerCase() : ""; - this.functionName = functionName.toLowerCase(); - this.qualifiedName = qualifiedName(namespace, functionName); - this.returnType = returnType; - this.argumentNames = argumentNames; - this.argumentTypes = argumentTypes; - this.language = language == null ? "class" : language.toLowerCase(); - this.body = body; - this.deterministic = deterministic; - - this.cqlArgumentTypes = new ArrayList<>(argumentTypes.size()); - InvalidRequestException inv = null; - CQL3Type rt = null; - try - { - rt = parseCQLType(returnType); - for (String argumentType : argumentTypes) - cqlArgumentTypes.add(parseCQLType(argumentType)); - } - catch (InvalidRequestException e) - { - inv = e; - } - this.invalid = inv; - this.cqlReturnType = rt; - - StringBuilder signature = new StringBuilder(); - signature.append(qualifiedName); - for (String argumentType : argumentTypes) - { - signature.append(','); - signature.append(argumentType); - } - this.signature = signature.toString(); - } - - public boolean compatibleArgs(String ksName, String cfName, List<? extends AssignementTestable> providedArgs) - { - int cnt = cqlArgumentTypes.size(); - if (cnt != providedArgs.size()) - return false; - for (int i = 0; i < cnt; i++) - { - AssignementTestable provided = providedArgs.get(i); - - if (provided == null) - continue; - - AbstractType<?> argType = cqlArgumentTypes.get(i).getType(); - - ColumnSpecification expected = makeArgSpec(ksName, cfName, argType, i); - if (!provided.isAssignableTo(ksName, expected)) - return false; - } - - return true; - } - - public ColumnSpecification makeArgSpec(String ksName, String cfName, AbstractType<?> argType, int i) - { - return new ColumnSpecification(ksName, - cfName, - new ColumnIdentifier("arg" + i + "(" + qualifiedName + ")", true), argType); - } - - private static CQL3Type parseCQLType(String cqlType) - throws InvalidRequestException - { - CharStream stream = new ANTLRStringStream(cqlType); - CqlLexer lexer = new CqlLexer(stream); - - TokenStream tokenStream = new CommonTokenStream(lexer); - CqlParser parser = new CqlParser(tokenStream); - try - { - CQL3Type.Raw rawType = parser.comparatorType(); - // TODO CASSANDRA-7563 use appropiate keyspace here ... keyspace must be fully qualified - CQL3Type t = rawType.prepare(null); - // TODO CASSANDRA-7563 support "complex" types (UDT, tuples, collections), remove catch-NPE below - if (!(t instanceof CQL3Type.Native)) - throw new InvalidRequestException("non-native CQL type '" + cqlType + "' not supported"); - return t; - } - catch (NullPointerException | InvalidRequestException | RecognitionException e) - { - throw new InvalidRequestException("invalid CQL type '" + cqlType + "'"); - } - } - - public static String qualifiedName(String namespace, String functionName) - { - if (namespace == null) - return "::" + functionName; - return (namespace + "::" + functionName).toLowerCase(); - } - - public static Mutation dropFunction(long timestamp, String namespace, String functionName) - { - UDFFunctionOverloads sigMap = UDFRegistry.getFunctionSigMap(UFMetaData.qualifiedName(namespace, functionName)); - if (sigMap == null || sigMap.isEmpty()) - return null; - - Mutation mutation = new Mutation(Keyspace.SYSTEM_KS, partKey.decompose(namespace, functionName)); - ColumnFamily cf = mutation.addOrGet(SystemKeyspace.SCHEMA_FUNCTIONS_CF); - - int ldt = (int) (System.currentTimeMillis() / 1000); - for (UFMetaData f : sigMap.values()) - udfRemove(timestamp, cf, ldt, f); - - return mutation; - } - - private static Composite udfSignatureKey(UFMetaData function) - { - return CFMetaData.SchemaFunctionsCf.comparator.make(function.signature); - } - - private static void udfRemove(long timestamp, ColumnFamily cf, int ldt, UFMetaData f) - { - Composite prefix = udfSignatureKey(f); - cf.addAtom(new RangeTombstone(prefix, prefix.end(), timestamp, ldt)); - } - - public static Mutation createOrReplaceFunction(long timestamp, UFMetaData f) - { - Mutation mutation = new Mutation(Keyspace.SYSTEM_KS, partKey.decompose(f.namespace, f.functionName)); - ColumnFamily cf = mutation.addOrGet(SystemKeyspace.SCHEMA_FUNCTIONS_CF); - - Composite prefix = udfSignatureKey(f); - CFRowAdder adder = new CFRowAdder(cf, prefix, timestamp); - - adder.resetCollection("argument_names"); - adder.resetCollection("argument_types"); - adder.add("name", f.functionName); - adder.add("return_type", f.returnType); - adder.add("language", f.language); - adder.add("body", f.body); - adder.add("deterministic", f.deterministic); - - for (String argName : f.argumentNames) - adder.addListEntry("argument_names", argName); - for (String argType : f.argumentTypes) - adder.addListEntry("argument_types", argType); - - return mutation; - } - - public static UFMetaData fromSchema(UntypedResultSet.Row row) - { - String namespace = row.getString("namespace"); - String name = row.getString("name"); - List<String> argumentNames = row.getList("argument_names", UTF8Type.instance); - List<String> argumentTypes = row.getList("argument_types", UTF8Type.instance); - String returnType = row.getString("return_type"); - boolean deterministic = row.getBoolean("deterministic"); - String language = row.getString("language"); - String body = row.getString("body"); - - return new UFMetaData(namespace, name, deterministic, argumentNames, argumentTypes, returnType, language, body); - } - - public static Map<String, UFMetaData> fromSchema(Row row) - { - UntypedResultSet results = QueryProcessor.resultify("SELECT * FROM system." + SystemKeyspace.SCHEMA_FUNCTIONS_CF, row); - Map<String, UFMetaData> udfs = new HashMap<>(results.size()); - for (UntypedResultSet.Row result : results) - { - UFMetaData udf = fromSchema(result); - udfs.put(udf.signature, udf); - } - return udfs; - } - - public boolean equals(Object o) - { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - UFMetaData that = (UFMetaData) o; - if (!signature.equals(that.signature)) - return false; - if (deterministic != that.deterministic) - return false; - if (argumentNames != null ? !argumentNames.equals(that.argumentNames) : that.argumentNames != null) - return false; - if (body != null ? !body.equals(that.body) : that.body != null) - return false; - if (!namespace.equals(that.namespace)) - return false; - if (!language.equals(that.language)) - return false; - if (returnType != null ? !returnType.equals(that.returnType) : that.returnType != null) - return false; - - return true; - } - - public int hashCode() - { - int result = signature.hashCode(); - result = 31 * result + (returnType != null ? returnType.hashCode() : 0); - result = 31 * result + (argumentNames != null ? argumentNames.hashCode() : 0); - result = 31 * result + (argumentTypes.hashCode()); - result = 31 * result + (language.hashCode()); - result = 31 * result + (body != null ? body.hashCode() : 0); - result = 31 * result + (deterministic ? 1 : 0); - return result; - } - - public String toString() - { - return new ToStringBuilder(this) - .append("signature", signature) - .append("returnType", returnType) - .append("deterministic", deterministic) - .append("language", language) - .append("body", body) - .toString(); - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/AbstractMarker.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java b/src/java/org/apache/cassandra/cql3/AbstractMarker.java index 10a4dff..7e39bfc 100644 --- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java +++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java @@ -71,9 +71,9 @@ public abstract class AbstractMarker extends Term.NonTerminal throw new AssertionError(); } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/AssignementTestable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/AssignementTestable.java b/src/java/org/apache/cassandra/cql3/AssignementTestable.java deleted file mode 100644 index 02b3013..0000000 --- a/src/java/org/apache/cassandra/cql3/AssignementTestable.java +++ /dev/null @@ -1,26 +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; - -public interface AssignementTestable -{ - /** - * @return whether this object can be assigned to the provided receiver - */ - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver); -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/AssignmentTestable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/AssignmentTestable.java b/src/java/org/apache/cassandra/cql3/AssignmentTestable.java new file mode 100644 index 0000000..41b80eb --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/AssignmentTestable.java @@ -0,0 +1,72 @@ +/* + * 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; + +import java.util.Collection; + +public interface AssignmentTestable +{ + /** + * @return whether this object can be assigned to the provided receiver. We distinguish + * between 3 values: + * - EXACT_MATCH if this object is exactly of the type expected by the receiver + * - WEAKLY_ASSIGNABLE if this object is not exactly the expected type but is assignable nonetheless + * - NOT_ASSIGNABLE if it's not assignable + * Most caller should just call the isAssignable() method on the result, though functions have a use for + * testing "strong" equality to decide the most precise overload to pick when multiple could match. + */ + public TestResult testAssignment(String keyspace, ColumnSpecification receiver); + + public enum TestResult + { + EXACT_MATCH, WEAKLY_ASSIGNABLE, NOT_ASSIGNABLE; + + public boolean isAssignable() + { + return this != NOT_ASSIGNABLE; + } + + public boolean isExactMatch() + { + return this == EXACT_MATCH; + } + + // Test all elements of toTest for assignment. If all are exact match, return exact match. If any is not assignable, + // return not assignable. Otherwise, return weakly assignable. + public static TestResult testAll(String keyspace, ColumnSpecification receiver, Collection<? extends AssignmentTestable> toTest) + { + TestResult res = EXACT_MATCH; + for (AssignmentTestable rt : toTest) + { + if (rt == null) + { + res = WEAKLY_ASSIGNABLE; + continue; + } + + TestResult t = rt.testAssignment(keyspace, receiver); + if (t == NOT_ASSIGNABLE) + return NOT_ASSIGNABLE; + + if (t == WEAKLY_ASSIGNABLE) + res = WEAKLY_ASSIGNABLE; + } + return res; + } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/Constants.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Constants.java b/src/java/org/apache/cassandra/cql3/Constants.java index c6cc4b8..9d4ce87 100644 --- a/src/java/org/apache/cassandra/cql3/Constants.java +++ b/src/java/org/apache/cassandra/cql3/Constants.java @@ -67,15 +67,17 @@ public abstract class Constants public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - if (!isAssignableTo(keyspace, receiver)) + if (!testAssignment(keyspace, receiver).isAssignable()) throw new InvalidRequestException("Invalid null value for counter increment/decrement"); return NULL_VALUE; } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - return !(receiver.type instanceof CounterColumnType); + return receiver.type instanceof CounterColumnType + ? AssignmentTestable.TestResult.NOT_ASSIGNABLE + : AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } @Override @@ -129,7 +131,7 @@ public abstract class Constants public Value prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - if (!isAssignableTo(keyspace, receiver)) + if (!testAssignment(keyspace, receiver).isAssignable()) throw new InvalidRequestException(String.format("Invalid %s constant (%s) for \"%s\" of type %s", type, text, receiver.name, receiver.type.asCQL3Type())); return new Value(parsedValue(receiver.type)); @@ -159,15 +161,15 @@ public abstract class Constants return text; } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { CQL3Type receiverType = receiver.type.asCQL3Type(); if (receiverType.isCollection()) - return false; + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; if (!(receiverType instanceof CQL3Type.Native)) // Skip type validation for custom types. May or may not be a good idea - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; CQL3Type.Native nt = (CQL3Type.Native)receiverType; switch (type) @@ -180,9 +182,9 @@ public abstract class Constants case INET: case VARCHAR: case TIMESTAMP: - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } - return false; + break; case INTEGER: switch (nt) { @@ -194,42 +196,42 @@ public abstract class Constants case INT: case TIMESTAMP: case VARINT: - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } - return false; + break; case UUID: switch (nt) { case UUID: case TIMEUUID: - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } - return false; + break; case FLOAT: switch (nt) { case DECIMAL: case DOUBLE: case FLOAT: - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } - return false; + break; case BOOLEAN: switch (nt) { case BOOLEAN: - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } - return false; + break; case HEX: switch (nt) { case BLOB: - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } - return false; + break; } - return false; + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/Cql.g ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index 5cf61df..054f1a2 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -42,7 +42,7 @@ options { import org.apache.cassandra.auth.IResource; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.statements.*; - import org.apache.cassandra.cql3.functions.FunctionCall; + import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.InvalidRequestException; @@ -300,8 +300,7 @@ unaliasedSelector returns [Selectable s] : ( c=cident { tmp = c; } | K_WRITETIME '(' c=cident ')' { tmp = new Selectable.WritetimeOrTTL(c, true); } | K_TTL '(' c=cident ')' { tmp = new Selectable.WritetimeOrTTL(c, false); } - | f=functionName args=selectionFunctionArgs { tmp = new Selectable.WithFunction("", f, args); } - | bn=udfName '::' fn=udfName args=selectionFunctionArgs { tmp = new Selectable.WithFunction(bn, fn, args); } + | f=functionName args=selectionFunctionArgs { tmp = new Selectable.WithFunction(f, args); } ) ( '.' fi=cident { tmp = new Selectable.WithFieldSelection(tmp, fi); } )* { $s = tmp; } ; @@ -494,20 +493,20 @@ createFunctionStatement returns [CreateFunctionStatement expr] boolean ifNotExists = false; boolean deterministic = true; - String language = "CLASS"; + String language = "class"; String bodyOrClassName = null; - List<CreateFunctionStatement.Argument> args = new ArrayList<CreateFunctionStatement.Argument>(); + List<ColumnIdentifier> argsNames = new ArrayList<>(); + List<CQL3Type.Raw> argsTypes = new ArrayList<>(); } : K_CREATE (K_OR K_REPLACE { orReplace = true; })? ((K_NON { deterministic = false; })? K_DETERMINISTIC)? K_FUNCTION (K_IF K_NOT K_EXISTS { ifNotExists = true; })? - ( bn=udfName '::' )? - fn=udfName + fn=functionName '(' ( - k=cident v=comparatorType { args.add(new CreateFunctionStatement.Argument(k, v)); } - ( ',' k=cident v=comparatorType { args.add(new CreateFunctionStatement.Argument(k, v)); } )* + k=cident v=comparatorType { argsNames.add(k); argsTypes.add(v); } + ( ',' k=cident v=comparatorType { argsNames.add(k); argsTypes.add(v); } )* )? ')' K_RETURNS @@ -523,7 +522,7 @@ createFunctionStatement returns [CreateFunctionStatement expr] ) ) ) - { $expr = new CreateFunctionStatement(bn, fn, language, bodyOrClassName, deterministic, rt, args, orReplace, ifNotExists); } + { $expr = new CreateFunctionStatement(fn, language.toLowerCase(), bodyOrClassName, deterministic, argsNames, argsTypes, rt, orReplace, ifNotExists); } ; dropFunctionStatement returns [DropFunctionStatement expr] @@ -532,9 +531,8 @@ dropFunctionStatement returns [DropFunctionStatement expr] } : K_DROP K_FUNCTION (K_IF K_EXISTS { ifExists = true; } )? - ( bn=udfName '::' )? - fn=udfName - { $expr = new DropFunctionStatement(bn, fn, ifExists); } + fn=functionName + { $expr = new DropFunctionStatement(fn, ifExists); } ; /** @@ -968,15 +966,15 @@ intValue returns [Term.Raw value] | QMARK { $value = newBindVariables(null); } ; -functionName returns [String s] - : f=IDENT { $s = $f.text; } - | u=unreserved_function_keyword { $s = u; } - | K_TOKEN { $s = "token"; } +functionName returns [FunctionName s] + : f=allowedFunctionName { $s = new FunctionName(f); } + | b=allowedFunctionName '::' f=allowedFunctionName { $s = new FunctionName(b, f); } ; -udfName returns [String s] +allowedFunctionName returns [String s] : f=IDENT { $s = $f.text; } | u=unreserved_function_keyword { $s = u; } + | K_TOKEN { $s = "token"; } ; functionArgs returns [List<Term.Raw> a] @@ -988,8 +986,7 @@ functionArgs returns [List<Term.Raw> a] term returns [Term.Raw term] : v=value { $term = v; } - | f=functionName args=functionArgs { $term = new FunctionCall.Raw("", f, args); } - | bn=udfName '::' fn=udfName args=functionArgs { $term = new FunctionCall.Raw(bn, fn, args); } + | f=functionName args=functionArgs { $term = new FunctionCall.Raw(f, args); } | '(' c=comparatorType ')' t=term { $term = new TypeCast(c, t); } ; http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/Lists.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Lists.java b/src/java/org/apache/cassandra/cql3/Lists.java index c8598cc..51af727 100644 --- a/src/java/org/apache/cassandra/cql3/Lists.java +++ b/src/java/org/apache/cassandra/cql3/Lists.java @@ -97,22 +97,22 @@ public abstract class Lists ColumnSpecification valueSpec = Lists.valueSpecOf(receiver); for (Term.Raw rt : elements) { - if (!rt.isAssignableTo(keyspace, valueSpec)) + if (!rt.testAssignment(keyspace, valueSpec).isAssignable()) throw new InvalidRequestException(String.format("Invalid list literal for %s: value %s is not of type %s", receiver.name, rt, valueSpec.type.asCQL3Type())); } } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - try - { - validateAssignableTo(keyspace, receiver); - return true; - } - catch (InvalidRequestException e) - { - return false; - } + if (!(receiver.type instanceof ListType)) + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + + // If there is no elements, we can't say it's an exact match (an empty list if fundamentally polymorphic). + if (elements.isEmpty()) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + + ColumnSpecification valueSpec = Lists.valueSpecOf(receiver); + return AssignmentTestable.TestResult.testAll(keyspace, valueSpec, elements); } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/Maps.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Maps.java b/src/java/org/apache/cassandra/cql3/Maps.java index e385438..00bdbec 100644 --- a/src/java/org/apache/cassandra/cql3/Maps.java +++ b/src/java/org/apache/cassandra/cql3/Maps.java @@ -98,24 +98,36 @@ public abstract class Maps ColumnSpecification valueSpec = Maps.valueSpecOf(receiver); for (Pair<Term.Raw, Term.Raw> entry : entries) { - if (!entry.left.isAssignableTo(keyspace, keySpec)) + if (!entry.left.testAssignment(keyspace, keySpec).isAssignable()) throw new InvalidRequestException(String.format("Invalid map literal for %s: key %s is not of type %s", receiver.name, entry.left, keySpec.type.asCQL3Type())); - if (!entry.right.isAssignableTo(keyspace, valueSpec)) + if (!entry.right.testAssignment(keyspace, valueSpec).isAssignable()) throw new InvalidRequestException(String.format("Invalid map literal for %s: value %s is not of type %s", receiver.name, entry.right, valueSpec.type.asCQL3Type())); } } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - try - { - validateAssignableTo(keyspace, receiver); - return true; - } - catch (InvalidRequestException e) + if (!(receiver.type instanceof MapType)) + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + + // If there is no elements, we can't say it's an exact match (an empty map if fundamentally polymorphic). + if (entries.isEmpty()) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + + ColumnSpecification keySpec = Maps.keySpecOf(receiver); + ColumnSpecification valueSpec = Maps.valueSpecOf(receiver); + // It's an exact match if all are exact match, but is not assignable as soon as any is non assignable. + AssignmentTestable.TestResult res = AssignmentTestable.TestResult.EXACT_MATCH; + for (Pair<Term.Raw, Term.Raw> entry : entries) { - return false; + AssignmentTestable.TestResult t1 = entry.left.testAssignment(keyspace, keySpec); + AssignmentTestable.TestResult t2 = entry.right.testAssignment(keyspace, valueSpec); + if (t1 == AssignmentTestable.TestResult.NOT_ASSIGNABLE || t2 == AssignmentTestable.TestResult.NOT_ASSIGNABLE) + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + if (t1 != AssignmentTestable.TestResult.EXACT_MATCH || t2 != AssignmentTestable.TestResult.EXACT_MATCH) + res = AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } + return res; } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/Sets.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Sets.java b/src/java/org/apache/cassandra/cql3/Sets.java index 20d9ac5..7360889 100644 --- a/src/java/org/apache/cassandra/cql3/Sets.java +++ b/src/java/org/apache/cassandra/cql3/Sets.java @@ -106,22 +106,28 @@ public abstract class Sets ColumnSpecification valueSpec = Sets.valueSpecOf(receiver); for (Term.Raw rt : elements) { - if (!rt.isAssignableTo(keyspace, valueSpec)) + if (!rt.testAssignment(keyspace, valueSpec).isAssignable()) throw new InvalidRequestException(String.format("Invalid set literal for %s: value %s is not of type %s", receiver.name, rt, valueSpec.type.asCQL3Type())); } } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - try - { - validateAssignableTo(keyspace, receiver); - return true; - } - catch (InvalidRequestException e) + if (!(receiver.type instanceof SetType)) { - return false; + // We've parsed empty maps as a set literal to break the ambiguity so handle that case now + if (receiver.type instanceof MapType && elements.isEmpty()) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } + + // If there is no elements, we can't say it's an exact match (an empty set if fundamentally polymorphic). + if (elements.isEmpty()) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + + ColumnSpecification valueSpec = Sets.valueSpecOf(receiver); + return AssignmentTestable.TestResult.testAll(keyspace, valueSpec, elements); } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/Term.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/Term.java index e5206c8..5440626 100644 --- a/src/java/org/apache/cassandra/cql3/Term.java +++ b/src/java/org/apache/cassandra/cql3/Term.java @@ -76,7 +76,7 @@ public interface Term * - a function call * - a marker */ - public interface Raw extends AssignementTestable + public interface Raw extends AssignmentTestable { /** * This method validates this RawTerm is valid for provided column http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/Tuples.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Tuples.java b/src/java/org/apache/cassandra/cql3/Tuples.java index cc04ebc..23552b8 100644 --- a/src/java/org/apache/cassandra/cql3/Tuples.java +++ b/src/java/org/apache/cassandra/cql3/Tuples.java @@ -110,21 +110,21 @@ public class Tuples Term.Raw value = elements.get(i); ColumnSpecification spec = componentSpecOf(receiver, i); - if (!value.isAssignableTo(keyspace, spec)) + if (!value.testAssignment(keyspace, spec).isAssignable()) throw new InvalidRequestException(String.format("Invalid tuple literal for %s: component %d is not of type %s", receiver.name, i, spec.type.asCQL3Type())); } } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { try { validateAssignableTo(keyspace, receiver); - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } catch (InvalidRequestException e) { - return false; + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/TypeCast.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/TypeCast.java b/src/java/org/apache/cassandra/cql3/TypeCast.java index 3250e3b..10b040e 100644 --- a/src/java/org/apache/cassandra/cql3/TypeCast.java +++ b/src/java/org/apache/cassandra/cql3/TypeCast.java @@ -17,6 +17,7 @@ */ package org.apache.cassandra.cql3; +import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; public class TypeCast implements Term.Raw @@ -32,11 +33,11 @@ public class TypeCast implements Term.Raw public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - if (!term.isAssignableTo(keyspace, castedSpecOf(keyspace, receiver))) + if (!term.testAssignment(keyspace, castedSpecOf(keyspace, receiver)).isAssignable()) throw new InvalidRequestException(String.format("Cannot cast value %s to type %s", term, type)); - if (!isAssignableTo(keyspace, receiver)) - throw new InvalidRequestException(String.format("Cannot assign value %s to %s of type %s", this, receiver, receiver.type.asCQL3Type())); + if (!testAssignment(keyspace, receiver).isAssignable()) + throw new InvalidRequestException(String.format("Cannot assign value %s to %s of type %s", this, receiver.name, receiver.type.asCQL3Type())); return term.prepare(keyspace, receiver); } @@ -46,11 +47,17 @@ public class TypeCast implements Term.Raw return new ColumnSpecification(receiver.ksName, receiver.cfName, new ColumnIdentifier(toString(), true), type.prepare(keyspace).getType()); } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { try { - return receiver.type.isValueCompatibleWith(type.prepare(keyspace).getType()); + AbstractType<?> castedType = type.prepare(keyspace).getType(); + if (receiver.type.equals(castedType)) + return AssignmentTestable.TestResult.EXACT_MATCH; + else if (receiver.type.isValueCompatibleWith(castedType)) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + else + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } catch (InvalidRequestException e) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/UntypedResultSet.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java index 42d0cb8..0cec9a6 100644 --- a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java +++ b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java @@ -219,6 +219,11 @@ public abstract class UntypedResultSet implements Iterable<UntypedResultSet.Row> return data.get(column) != null; } + public ByteBuffer getBlob(String column) + { + return data.get(column); + } + public String getString(String column) { return UTF8Type.instance.compose(data.get(column)); http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/UserTypes.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java index 9d66c16..763791b 100644 --- a/src/java/org/apache/cassandra/cql3/UserTypes.java +++ b/src/java/org/apache/cassandra/cql3/UserTypes.java @@ -99,21 +99,21 @@ public abstract class UserTypes continue; ColumnSpecification fieldSpec = fieldSpecOf(receiver, i); - if (!value.isAssignableTo(keyspace, fieldSpec)) + if (!value.testAssignment(keyspace, fieldSpec).isAssignable()) throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s", receiver, field, fieldSpec.type.asCQL3Type())); } } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { try { validateAssignableTo(keyspace, receiver); - return true; + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } catch (InvalidRequestException e) { - return false; + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java b/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java index 5dbfbf3..d5a40a0 100644 --- a/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java @@ -17,32 +17,36 @@ */ package org.apache.cassandra.cql3.functions; -import java.util.Arrays; import java.util.List; +import com.google.common.base.Objects; + import org.apache.cassandra.db.marshal.AbstractType; +/** + * Base class for our native/hardcoded functions. + */ public abstract class AbstractFunction implements Function { - public final String name; - public final List<AbstractType<?>> argsType; - public final AbstractType<?> returnType; + protected final FunctionName name; + protected final List<AbstractType<?>> argTypes; + protected final AbstractType<?> returnType; - protected AbstractFunction(String name, AbstractType<?> returnType, AbstractType<?>... argsType) + protected AbstractFunction(FunctionName name, List<AbstractType<?>> argTypes, AbstractType<?> returnType) { this.name = name; - this.argsType = Arrays.asList(argsType); + this.argTypes = argTypes; this.returnType = returnType; } - public String name() + public FunctionName name() { return name; } - public List<AbstractType<?>> argsType() + public List<AbstractType<?>> argTypes() { - return argsType; + return argTypes; } public AbstractType<?> returnType() @@ -50,23 +54,36 @@ public abstract class AbstractFunction implements Function return returnType; } - // Most of our functions are pure, the other ones should override this - public boolean isPure() + @Override + public boolean equals(Object o) + { + if (!(o instanceof AbstractFunction)) + return false; + + AbstractFunction that = (AbstractFunction)o; + return Objects.equal(this.name, that.name) + && Objects.equal(this.argTypes, that.argTypes) + && Objects.equal(this.returnType, that.returnType); + } + + @Override + public int hashCode() { - return true; + return Objects.hashCode(name, argTypes, returnType); } - /** - * Creates a trivial factory that always return the provided function. - */ - public static Function.Factory factory(final Function fun) + @Override + public String toString() { - return new Function.Factory() + StringBuilder sb = new StringBuilder(); + sb.append(name).append(" : ("); + for (int i = 0; i < argTypes.size(); i++) { - public Function create(String ksName, String cfName) - { - return fun; - } - }; + if (i > 0) + sb.append(", "); + sb.append(argTypes.get(i).asCQL3Type()); + } + sb.append(") -> ").append(returnType.asCQL3Type()); + return sb.toString(); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java b/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java index e3023db..6ea0a55 100644 --- a/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java @@ -34,7 +34,7 @@ public abstract class BytesConversionFcts public static Function makeToBlobFunction(AbstractType<?> fromType) { String name = fromType.asCQL3Type() + "asblob"; - return new AbstractFunction(name, BytesType.instance, fromType) + return new NativeFunction(name, BytesType.instance, fromType) { public ByteBuffer execute(List<ByteBuffer> parameters) { @@ -46,7 +46,7 @@ public abstract class BytesConversionFcts public static Function makeFromBlobFunction(final AbstractType<?> toType) { final String name = "blobas" + toType.asCQL3Type(); - return new AbstractFunction(name, toType, BytesType.instance) + return new NativeFunction(name, toType, BytesType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException { @@ -66,7 +66,7 @@ public abstract class BytesConversionFcts }; } - public static final Function VarcharAsBlobFct = new AbstractFunction("varcharasblob", BytesType.instance, UTF8Type.instance) + public static final Function VarcharAsBlobFct = new NativeFunction("varcharasblob", BytesType.instance, UTF8Type.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { @@ -74,7 +74,7 @@ public abstract class BytesConversionFcts } }; - public static final Function BlobAsVarcharFact = new AbstractFunction("blobasvarchar", UTF8Type.instance, BytesType.instance) + public static final Function BlobAsVarcharFact = new NativeFunction("blobasvarchar", UTF8Type.instance, BytesType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/Function.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/Function.java b/src/java/org/apache/cassandra/cql3/functions/Function.java index ba5ae20..dc2a0db 100644 --- a/src/java/org/apache/cassandra/cql3/functions/Function.java +++ b/src/java/org/apache/cassandra/cql3/functions/Function.java @@ -25,8 +25,8 @@ import org.apache.cassandra.exceptions.InvalidRequestException; public interface Function { - public String name(); - public List<AbstractType<?>> argsType(); + public FunctionName name(); + public List<AbstractType<?>> argTypes(); public AbstractType<?> returnType(); public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException; @@ -34,12 +34,6 @@ public interface Function // Whether the function is a pure function (as in doesn't depend on, nor produce side effects). public boolean isPure(); - public interface Factory - { - // We allow the function to be parametered by the keyspace it is part of because the - // "token" function needs it (the argument depends on the keyValidator). However, - // for most function, the factory will just always the same function object (see - // AbstractFunction). - public Function create(String ksName, String cfName); - } + // Whether the function is a native/harcoded one. + public boolean isNative(); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java index d83d48f..d2ef90c 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java @@ -22,8 +22,6 @@ import java.util.ArrayList; import java.util.List; import org.apache.cassandra.cql3.*; -import org.apache.cassandra.cql3.udf.UDFunction; -import org.apache.cassandra.cql3.udf.UDFRegistry; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.db.marshal.ListType; @@ -113,39 +111,33 @@ public class FunctionCall extends Term.NonTerminal public static class Raw implements Term.Raw { - private final String namespace; - private final String functionName; + private final FunctionName name; private final List<Term.Raw> terms; - public Raw(String namespace, String functionName, List<Term.Raw> terms) + public Raw(FunctionName name, List<Term.Raw> terms) { - this.namespace = namespace; - this.functionName = functionName; + this.name = name; this.terms = terms; } public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - Function fun = null; - if (namespace.isEmpty()) - fun = Functions.get(keyspace, functionName, terms, receiver); - + Function fun = Functions.get(keyspace, name, terms, receiver.ksName, receiver.cfName); if (fun == null) - { - UDFunction udf = UDFRegistry.resolveFunction(namespace, functionName, receiver.ksName, receiver.cfName, terms); - if (udf != null) - // got a user defined function to call - fun = udf.create(terms); - } + throw new InvalidRequestException(String.format("Unknown function %s called", name)); - if (fun == null) - throw new InvalidRequestException(String.format("Unknown function %s called", namespace.isEmpty() ? functionName : namespace + "::" + functionName)); + // Functions.get() will complain if no function "name" type check with the provided arguments. + // We still have to validate that the return type matches however + if (!receiver.type.isValueCompatibleWith(fun.returnType())) + throw new InvalidRequestException(String.format("Type error: cannot assign result of function %s (type %s) to %s (type %s)", + fun.name(), fun.returnType().asCQL3Type(), + receiver.name, receiver.type.asCQL3Type())); List<Term> parameters = new ArrayList<Term>(terms.size()); boolean allTerminal = true; for (int i = 0; i < terms.size(); i++) { - Term t = terms.get(i).prepare(keyspace, Functions.makeArgSpec(receiver, fun, i)); + Term t = terms.get(i).prepare(keyspace, Functions.makeArgSpec(receiver.ksName, receiver.cfName, fun, i)); if (t instanceof NonTerminal) allTerminal = false; parameters.add(t); @@ -171,23 +163,33 @@ public class FunctionCall extends Term.NonTerminal return executeInternal(fun, buffers); } - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - AbstractType<?> returnType = Functions.getReturnType(functionName, receiver.ksName, receiver.cfName); - // Note: if returnType == null, it means the function doesn't exist. We may get this if an undefined function - // is used as argument of another, existing, function. In that case, we return true here because we'll catch - // the fact that the method is undefined latter anyway and with a more helpful error message that if we were - // to return false here. - return returnType == null || receiver.type.isValueCompatibleWith(returnType); + // Note: Functions.get() will return null if the function doesn't exist, or throw is no function matching + // the arguments can be found. We may get one of those if an undefined/wrong function is used as argument + // of another, existing, function. In that case, we return true here because we'll throw a proper exception + // later with a more helpful error message that if we were to return false here. + try + { + Function fun = Functions.get(keyspace, name, terms, receiver.ksName, receiver.cfName); + if (fun != null && receiver.type.equals(fun.returnType())) + return AssignmentTestable.TestResult.EXACT_MATCH; + else if (fun == null || receiver.type.isValueCompatibleWith(fun.returnType())) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + else + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + } + catch (InvalidRequestException e) + { + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } } @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (!namespace.isEmpty()) - sb.append(namespace).append("::"); - sb.append(functionName).append("("); + sb.append(name).append("("); for (int i = 0; i < terms.size(); i++) { if (i > 0) http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/FunctionName.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionName.java b/src/java/org/apache/cassandra/cql3/functions/FunctionName.java new file mode 100644 index 0000000..814bbbf --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionName.java @@ -0,0 +1,66 @@ +/* + * 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 com.google.common.base.Objects; + +public class FunctionName +{ + public final String namespace; + public final String name; + + // Use by toString rather than built from 'bundle' and 'name' so as to + // preserve the original case. + private final String displayName; + + public FunctionName(String name) + { + this("", name); + } + + public FunctionName(String namespace, String name) + { + this.namespace = namespace.toLowerCase(); + this.name = name.toLowerCase(); + + this.displayName = namespace.isEmpty() ? name : namespace + "::" + name; + } + + @Override + public final int hashCode() + { + return Objects.hashCode(namespace, name); + } + + @Override + public final boolean equals(Object o) + { + if (!(o instanceof FunctionName)) + return false; + + FunctionName that = (FunctionName)o; + return Objects.equal(this.namespace, that.namespace) + && Objects.equal(this.name, that.name); + } + + @Override + public String toString() + { + return displayName; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/Functions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/Functions.java b/src/java/org/apache/cassandra/cql3/functions/Functions.java index 977d242..18feb36 100644 --- a/src/java/org/apache/cassandra/cql3/functions/Functions.java +++ b/src/java/org/apache/cassandra/cql3/functions/Functions.java @@ -17,34 +17,43 @@ */ package org.apache.cassandra.cql3.functions; +import java.util.ArrayList; import java.util.List; import com.google.common.collect.ArrayListMultimap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.AssignementTestable; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; public abstract class Functions { + private static final Logger logger = LoggerFactory.getLogger(Functions.class); + + // We special case the token function because that's the only function whose argument types actually + // depend on the table on which the function is called. Because it's the sole exception, it's easier + // to handle it as a special case. + private static final FunctionName TOKEN_FUNCTION_NAME = new FunctionName("token"); + + private static final String SELECT_UDFS = "SELECT * FROM " + Keyspace.SYSTEM_KS + '.' + SystemKeyspace.SCHEMA_FUNCTIONS_CF; + private Functions() {} - // If we ever allow this to be populated at runtime, this will need to be thread safe. - private static final ArrayListMultimap<String, Function.Factory> declared = ArrayListMultimap.create(); + private static final ArrayListMultimap<FunctionName, Function> declared = ArrayListMultimap.create(); + static { - // All method sharing the same name must have the same returnType. We could find a way to make that clear. - declared.put("token", TokenFct.factory); - - declared.put("now", AbstractFunction.factory(TimeuuidFcts.nowFct)); - declared.put("mintimeuuid", AbstractFunction.factory(TimeuuidFcts.minTimeuuidFct)); - declared.put("maxtimeuuid", AbstractFunction.factory(TimeuuidFcts.maxTimeuuidFct)); - declared.put("dateof", AbstractFunction.factory(TimeuuidFcts.dateOfFct)); - declared.put("unixtimestampof", AbstractFunction.factory(TimeuuidFcts.unixTimestampOfFct)); - declared.put("uuid", AbstractFunction.factory(UuidFcts.uuidFct)); + declare(TimeuuidFcts.nowFct); + declare(TimeuuidFcts.minTimeuuidFct); + declare(TimeuuidFcts.maxTimeuuidFct); + declare(TimeuuidFcts.dateOfFct); + declare(TimeuuidFcts.unixTimestampOfFct); + declare(UuidFcts.uuidFct); for (CQL3Type type : CQL3Type.Native.values()) { @@ -53,137 +62,189 @@ public abstract class Functions if (type == CQL3Type.Native.VARCHAR || type == CQL3Type.Native.BLOB) continue; - Function toBlob = BytesConversionFcts.makeToBlobFunction(type.getType()); - Function fromBlob = BytesConversionFcts.makeFromBlobFunction(type.getType()); - declared.put(toBlob.name(), AbstractFunction.factory(toBlob)); - declared.put(fromBlob.name(), AbstractFunction.factory(fromBlob)); + declare(BytesConversionFcts.makeToBlobFunction(type.getType())); + declare(BytesConversionFcts.makeFromBlobFunction(type.getType())); } - declared.put("varcharasblob", AbstractFunction.factory(BytesConversionFcts.VarcharAsBlobFct)); - declared.put("blobasvarchar", AbstractFunction.factory(BytesConversionFcts.BlobAsVarcharFact)); + declare(BytesConversionFcts.VarcharAsBlobFct); + declare(BytesConversionFcts.BlobAsVarcharFact); } - public static boolean contains(String functionName) + private static void declare(Function fun) { - return declared.containsKey(functionName); + declared.put(fun.name(), fun); } - public static AbstractType<?> getReturnType(String functionName, String ksName, String cfName) + /** + * Loading existing UDFs from the schema. + */ + public static void loadUDFFromSchema() { - List<Function.Factory> factories = declared.get(functionName.toLowerCase()); - return factories.isEmpty() - ? null // That's ok, we'll complain later - : factories.get(0).create(ksName, cfName).returnType(); + logger.debug("Loading UDFs"); + for (UntypedResultSet.Row row : QueryProcessor.executeOnceInternal(SELECT_UDFS)) + addFunction(UDFunction.fromSchema(row)); } - public static ColumnSpecification makeArgSpec(ColumnSpecification receiver, Function fun, int i) + public static ColumnSpecification makeArgSpec(String receiverKs, String receiverCf, Function fun, int i) { - return new ColumnSpecification(receiver.ksName, - receiver.cfName, - new ColumnIdentifier("arg" + i + "(" + fun.name() + ")", true), - fun.argsType().get(i)); + return new ColumnSpecification(receiverKs, + receiverCf, + new ColumnIdentifier("arg" + i + "(" + fun.name().toString().toLowerCase() + ")", true), + fun.argTypes().get(i)); } - public static Function get(String keyspace, String name, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver) throws InvalidRequestException + public static Function get(String keyspace, + FunctionName name, + List<? extends AssignmentTestable> providedArgs, + String receiverKs, + String receiverCf) + throws InvalidRequestException { - List<Function.Factory> factories = declared.get(name.toLowerCase()); - if (factories.isEmpty()) + if (name.equals(TOKEN_FUNCTION_NAME)) + return new TokenFct(Schema.instance.getCFMetaData(receiverKs, receiverCf)); + + List<Function> candidates = declared.get(name); + if (candidates.isEmpty()) return null; - // Fast path if there is not choice - if (factories.size() == 1) + // Fast path if there is only one choice + if (candidates.size() == 1) { - Function fun = factories.get(0).create(receiver.ksName, receiver.cfName); - validateTypes(keyspace, fun, providedArgs, receiver); + Function fun = candidates.get(0); + validateTypes(keyspace, fun, providedArgs, receiverKs, receiverCf); return fun; } - Function candidate = null; - for (Function.Factory factory : factories) + List<Function> compatibles = null; + for (Function toTest : candidates) { - Function toTest = factory.create(receiver.ksName, receiver.cfName); - if (!isValidType(keyspace, toTest, providedArgs, receiver)) - continue; - - if (candidate == null) - candidate = toTest; - else - throw new InvalidRequestException(String.format("Ambiguous call to function %s (can match both type signature %s and %s): use type casts to disambiguate", name, signature(candidate), signature(toTest))); + AssignmentTestable.TestResult r = matchAguments(keyspace, toTest, providedArgs, receiverKs, receiverCf); + switch (r) + { + case EXACT_MATCH: + // We always favor exact matches + return toTest; + case WEAKLY_ASSIGNABLE: + if (compatibles == null) + compatibles = new ArrayList<>(); + compatibles.add(toTest); + break; + } } - if (candidate == null) - throw new InvalidRequestException(String.format("Invalid call to function %s, none of its type signature matches (known type signatures: %s)", name, signatures(factories, receiver))); - return candidate; + + if (compatibles == null || compatibles.isEmpty()) + throw new InvalidRequestException(String.format("Invalid call to function %s, none of its type signatures match (known type signatures: %s)", + name, toString(candidates))); + + if (compatibles.size() > 1) + throw new InvalidRequestException(String.format("Ambiguous call to function %s (can be matched by following signatures: %s): use type casts to disambiguate", + name, toString(compatibles))); + + return compatibles.get(0); + } + + public static List<Function> find(FunctionName name) + { + return declared.get(name); } - private static void validateTypes(String keyspace, Function fun, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver) throws InvalidRequestException + public static Function find(FunctionName name, List<AbstractType<?>> argTypes) { - if (!receiver.type.isValueCompatibleWith(fun.returnType())) - throw new InvalidRequestException(String.format("Type error: cannot assign result of function %s (type %s) to %s (type %s)", fun.name(), fun.returnType().asCQL3Type(), receiver, receiver.type.asCQL3Type())); + for (Function f : declared.get(name)) + { + if (f.argTypes().equals(argTypes)) + return f; + } + return null; + } - if (providedArgs.size() != fun.argsType().size()) - throw new InvalidRequestException(String.format("Invalid number of arguments in call to function %s: %d required but %d provided", fun.name(), fun.argsType().size(), providedArgs.size())); + // This method and matchArguments are somewhat duplicate, but this method allows us to provide more precise errors in the common + // case where there is no override for a given function. This is thus probably worth the minor code duplication. + private static void validateTypes(String keyspace, + Function fun, + List<? extends AssignmentTestable> providedArgs, + String receiverKs, + String receiverCf) + throws InvalidRequestException + { + if (providedArgs.size() != fun.argTypes().size()) + throw new InvalidRequestException(String.format("Invalid number of arguments in call to function %s: %d required but %d provided", fun.name(), fun.argTypes().size(), providedArgs.size())); for (int i = 0; i < providedArgs.size(); i++) { - AssignementTestable provided = providedArgs.get(i); + AssignmentTestable provided = providedArgs.get(i); // If the concrete argument is a bind variables, it can have any type. // We'll validate the actually provided value at execution time. if (provided == null) continue; - ColumnSpecification expected = makeArgSpec(receiver, fun, i); - if (!provided.isAssignableTo(keyspace, expected)) + ColumnSpecification expected = makeArgSpec(receiverKs, receiverCf, fun, i); + if (!provided.testAssignment(keyspace, expected).isAssignable()) throw new InvalidRequestException(String.format("Type error: %s cannot be passed as argument %d of function %s of type %s", provided, i, fun.name(), expected.type.asCQL3Type())); } } - private static boolean isValidType(String keyspace, Function fun, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver) + private static AssignmentTestable.TestResult matchAguments(String keyspace, + Function fun, + List<? extends AssignmentTestable> providedArgs, + String receiverKs, + String receiverCf) { - if (!receiver.type.isValueCompatibleWith(fun.returnType())) - return false; - - if (providedArgs.size() != fun.argsType().size()) - return false; + if (providedArgs.size() != fun.argTypes().size()) + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + // It's an exact match if all are exact match, but is not assignable as soon as any is non assignable. + AssignmentTestable.TestResult res = AssignmentTestable.TestResult.EXACT_MATCH; for (int i = 0; i < providedArgs.size(); i++) { - AssignementTestable provided = providedArgs.get(i); - - // If the concrete argument is a bind variables, it can have any type. - // We'll validate the actually provided value at execution time. + AssignmentTestable provided = providedArgs.get(i); if (provided == null) + { + res = AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; continue; - - ColumnSpecification expected = makeArgSpec(receiver, fun, i); - if (!provided.isAssignableTo(keyspace, expected)) - return false; + } + + ColumnSpecification expected = makeArgSpec(receiverKs, receiverCf, fun, i); + AssignmentTestable.TestResult argRes = provided.testAssignment(keyspace, expected); + if (argRes == AssignmentTestable.TestResult.NOT_ASSIGNABLE) + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + if (argRes == AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE) + res = AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } - return true; + return res; } - private static String signature(Function fun) + private static String toString(List<Function> funs) { - List<AbstractType<?>> args = fun.argsType(); StringBuilder sb = new StringBuilder(); - sb.append("("); - for (int i = 0; i < args.size(); i++) + for (int i = 0; i < funs.size(); i++) { if (i > 0) sb.append(", "); - sb.append(args.get(i).asCQL3Type()); + sb.append(funs.get(i)); } - sb.append(") -> "); - sb.append(fun.returnType().asCQL3Type()); return sb.toString(); } - private static String signatures(List<Function.Factory> factories, ColumnSpecification receiver) + // This is *not* thread safe but is only called in DefsTables that is synchronized. + public static void addFunction(UDFunction fun) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < factories.size(); i++) - { - if (i > 0) sb.append(", "); - sb.append(signature(factories.get(i).create(receiver.ksName, receiver.cfName))); - } - return sb.toString(); + // We shouldn't get there unless that function don't exist + assert find(fun.name(), fun.argTypes()) == null; + declare(fun); + } + + // Same remarks than for addFunction + public static void removeFunction(FunctionName name, List<AbstractType<?>> argsTypes) + { + Function old = find(name, argsTypes); + assert old != null && !old.isNative(); + declared.remove(old.name(), old); + } + + // Same remarks than for addFunction + public static void replaceFunction(UDFunction fun) + { + removeFunction(fun.name(), fun.argTypes()); + addFunction(fun); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java b/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java new file mode 100644 index 0000000..d658d9d --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java @@ -0,0 +1,50 @@ +/* + * 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.util.Arrays; + +import org.apache.cassandra.db.marshal.AbstractType; + +/** + * Base class for our native/hardcoded functions. + */ +public abstract class NativeFunction extends AbstractFunction +{ + protected NativeFunction(String name, AbstractType<?> returnType, AbstractType<?>... argTypes) + { + this(new FunctionName(name), returnType, argTypes); + } + + protected NativeFunction(FunctionName name, AbstractType<?> returnType, AbstractType<?>... argTypes) + { + super(name, Arrays.asList(argTypes), returnType); + } + + // Most of our functions are pure, the other ones should override this + public boolean isPure() + { + return true; + } + + public boolean isNative() + { + return true; + } +} + http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/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 new file mode 100644 index 0000000..68e388d --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/functions/ReflectionBasedUDF.java @@ -0,0 +1,126 @@ +/* + * 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.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * User-defined function using a method in a class loaded on the classpath by + * reflection. + * + * This is used when the LANGUAGE of the UDF definition is "class". + */ +class ReflectionBasedUDF extends UDFunction +{ + public final Method method; + + ReflectionBasedUDF(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("class"); + this.method = resolveClassMethod(); + } + + private Method resolveClassMethod() throws InvalidRequestException + { + Class<?> jReturnType = returnType.getSerializer().getType(); + Class<?> paramTypes[] = new Class[argTypes.size()]; + for (int i = 0; i < paramTypes.length; i++) + paramTypes[i] = argTypes.get(i).getSerializer().getType(); + + String className; + String methodName; + int i = body.indexOf('#'); + if (i != -1) + { + methodName = body.substring(i + 1); + className = body.substring(0, i); + } + else + { + methodName = name.name; + className = body; + } + try + { + 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 + ')'); + } + + return method; + } + catch (ClassNotFoundException e) + { + throw new InvalidRequestException("Class " + className + " does not exist"); + } + catch (NoSuchMethodException e) + { + throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") does not exist"); + } + } + + 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", name, c); + throw new InvalidRequestException("Invocation of function " + name + " failed: " + c); + } + } +}
