cassandra-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From slebre...@apache.org
Subject [2/2] git commit: UDF cleanups
Date Wed, 27 Aug 2014 08:40:44 GMT
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 <sylvain@datastax.com>
Authored: Mon Aug 18 18:29:09 2014 +0200
Committer: Sylvain Lebresne <sylvain@datastax.com>
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);
+        }
+    }
+}


Mime
View raw message