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