This is an automated email from the ASF dual-hosted git repository.

aleksey pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 174cf76  Validate token() arguments early instead of throwing NPE at 
execution
174cf76 is described below

commit 174cf761f7897443080b8a840b649b7eab17ae25
Author: Dinesh A. Joshi <dinesh.jo...@apple.com>
AuthorDate: Mon Jan 28 12:29:46 2019 +0000

    Validate token() arguments early instead of throwing NPE at execution
    
    patch by Dinesh Joshi; reviewed by Aleksey Yeschenko and Jon Meredith
    for CASSANDRA-14989
---
 CHANGES.txt                                        |  1 +
 .../cassandra/cql3/functions/FunctionResolver.java | 52 ++++++++++-----
 .../cql3/validation/operations/SelectTest.java     | 73 ++++++++++++++++++++++
 3 files changed, 111 insertions(+), 15 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index a0c51f0..852cccf 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0
+ * Validate token() arguments early instead of throwing NPE at execution 
(CASSANDRA-14989)
  * Add a new tool to dump audit logs (CASSANDRA-14885)
  * Fix generating javadoc with Java11 (CASSANDRA-14988)
  * Only cancel conflicting compactions when starting anticompactions and sub 
range compactions (CASSANDRA-14935)
diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java 
b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java
index 99c0fca..ae5d17e 100644
--- a/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java
+++ b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java
@@ -69,8 +69,32 @@ public final class FunctionResolver
                                AbstractType<?> receiverType)
     throws InvalidRequestException
     {
+        Collection<Function> candidates = collectCandidates(keyspace, name, 
receiverKs, receiverCf, receiverType);
+
+        if (candidates.isEmpty())
+            return null;
+
+        // Fast path if there is only one choice
+        if (candidates.size() == 1)
+        {
+            Function fun = candidates.iterator().next();
+            validateTypes(keyspace, fun, providedArgs, receiverKs, receiverCf);
+            return fun;
+        }
+
+        return pickBestMatch(keyspace, name, providedArgs, receiverKs, 
receiverCf, receiverType, candidates);
+    }
+
+    private static Collection<Function> collectCandidates(String keyspace,
+                                                          FunctionName name,
+                                                          String receiverKs,
+                                                          String receiverCf,
+                                                          AbstractType<?> 
receiverType)
+    {
+        Collection<Function> candidates = new ArrayList<>();
+
         if (name.equalsNativeFunction(TOKEN_FUNCTION_NAME))
-            return new TokenFct(Schema.instance.getTableMetadata(receiverKs, 
receiverCf));
+            candidates.add(new 
TokenFct(Schema.instance.getTableMetadata(receiverKs, receiverCf)));
 
         // The toJson() function can accept any type of argument, so instances 
of it are not pre-declared.  Instead,
         // we create new instances as needed while handling selectors (which 
is the only place that toJson() is supported,
@@ -83,14 +107,12 @@ public final class FunctionResolver
         {
             if (receiverType == null)
                 throw new InvalidRequestException("fromJson() cannot be used 
in the selection clause of a SELECT statement");
-            return FromJsonFct.getInstance(receiverType);
+            candidates.add(FromJsonFct.getInstance(receiverType));
         }
 
-        Collection<Function> candidates;
         if (!name.hasKeyspace())
         {
             // function name not fully qualified
-            candidates = new ArrayList<>();
             // add 'SYSTEM' (native) candidates
             
candidates.addAll(Schema.instance.getFunctions(name.asNativeFunction()));
             // add 'current keyspace' candidates
@@ -99,20 +121,19 @@ public final class FunctionResolver
         else
         {
             // function name is fully qualified (keyspace + name)
-            candidates = Schema.instance.getFunctions(name);
+            candidates.addAll(Schema.instance.getFunctions(name));
         }
 
-        if (candidates.isEmpty())
-            return null;
-
-        // Fast path if there is only one choice
-        if (candidates.size() == 1)
-        {
-            Function fun = candidates.iterator().next();
-            validateTypes(keyspace, fun, providedArgs, receiverKs, receiverCf);
-            return fun;
-        }
+        return candidates;
+    }
 
+    private static Function pickBestMatch(String keyspace,
+                                          FunctionName name,
+                                          List<? extends AssignmentTestable> 
providedArgs,
+                                          String receiverKs,
+                                          String receiverCf, AbstractType<?> 
receiverType,
+                                          Collection<Function> candidates)
+    {
         List<Function> compatibles = null;
         for (Function toTest : candidates)
         {
@@ -166,6 +187,7 @@ public final class FunctionResolver
             throw invalidRequest("Ambiguous call to function %s (can be 
matched by following signatures: %s): use type casts to disambiguate",
                                  name, format(compatibles));
         }
+
         return compatibles.get(0);
     }
 
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java 
b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
index 54e079e..d7c1e25 100644
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
@@ -3086,4 +3086,77 @@ public class SelectTest extends CQLTester
         }
     }
 
+    @Test // CASSANDRA-14989
+    public void testTokenFctAcceptsValidArguments() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertRowCount(execute("SELECT token(k1, k2) FROM %s"), 1);
+    }
+
+    @Test
+    public void testTokenFctRejectsInvalidColumnName() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertInvalidMessage("Undefined column name ", "SELECT token(s1, k1) 
FROM %s");
+    }
+
+    @Test
+    public void testTokenFctRejectsInvalidColumnType() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertInvalidMessage("Type error: k2 cannot be passed as argument 0 of 
function system.token of type uuid",
+                             "SELECT token(k2, k1) FROM %s");
+    }
+
+    @Test
+    public void testTokenFctRejectsInvalidColumnCount() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertInvalidMessage("Invalid number of arguments in call to function 
system.token: 2 required but 1 provided",
+                             "SELECT token(k1) FROM %s");
+    }
+
+    @Test
+    public void 
testCreatingUDFWithSameNameAsBuiltin_PrefersCompatibleArgs_SameKeyspace() 
throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        createFunctionOverload(KEYSPACE + ".token", "double",
+                               "CREATE FUNCTION %s (val double) RETURNS null 
ON null INPUT RETURNS double LANGUAGE java AS 'return 10.0d;'");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertRows(execute("SELECT token(10) FROM %s"), row(10.0d));
+    }
+
+    @Test
+    public void 
testCreatingUDFWithSameNameAsBuiltin_FullyQualifiedFunctionNameWorks() throws 
Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        createFunctionOverload(KEYSPACE + ".token", "double",
+                               "CREATE FUNCTION %s (val double) RETURNS null 
ON null INPUT RETURNS double LANGUAGE java AS 'return 10.0d;'");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertRows(execute("SELECT " + KEYSPACE + ".token(10) FROM %s"), 
row(10.0d));
+    }
+
+    @Test
+    public void testCreatingUDFWithSameNameAsBuiltin_PrefersCompatibleArgs() 
throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        createFunctionOverload(KEYSPACE + ".token", "double",
+                               "CREATE FUNCTION %s (val double) RETURNS null 
ON null INPUT RETURNS double LANGUAGE java AS 'return 10.0d;'");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertRowCount(execute("SELECT token(k1, k2) FROM %s"), 1);
+    }
+
+    @Test
+    public void 
testCreatingUDFWithSameNameAsBuiltin_FullyQualifiedFunctionNameWorks_SystemKeyspace()
 throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
+        createFunctionOverload(KEYSPACE + ".token", "double",
+                               "CREATE FUNCTION %s (val double) RETURNS null 
ON null INPUT RETURNS double LANGUAGE java AS 'return 10.0d;'");
+        execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
+        assertRowCount(execute("SELECT system.token(k1, k2) FROM %s"), 1);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to