Return-Path: X-Original-To: apmail-cassandra-commits-archive@www.apache.org Delivered-To: apmail-cassandra-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 26E6C17663 for ; Sun, 15 Feb 2015 19:07:48 +0000 (UTC) Received: (qmail 19268 invoked by uid 500); 15 Feb 2015 19:07:48 -0000 Delivered-To: apmail-cassandra-commits-archive@cassandra.apache.org Received: (qmail 19228 invoked by uid 500); 15 Feb 2015 19:07:48 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 19216 invoked by uid 99); 15 Feb 2015 19:07:47 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 15 Feb 2015 19:07:47 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id AECDBE03E9; Sun, 15 Feb 2015 19:07:47 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: snazy@apache.org To: commits@cassandra.apache.org Message-Id: <979e7f1d69164a5e9eea020e69afb0ac@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: cassandra git commit: Add an FunctionExecutionException to the protocol Date: Sun, 15 Feb 2015 19:07:47 +0000 (UTC) Repository: cassandra Updated Branches: refs/heads/trunk f61bc44a7 -> e3c018628 Add an FunctionExecutionException to the protocol Patch by Robert Stupp; Reviewed by Aleksey Yeschenko for CASSANDRA-8528 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/e3c01862 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/e3c01862 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/e3c01862 Branch: refs/heads/trunk Commit: e3c01862881362bba2b39ec01fe25e4945eafa36 Parents: f61bc44 Author: Robert Stupp Authored: Sun Feb 15 20:06:29 2015 +0100 Committer: Robert Stupp Committed: Sun Feb 15 20:06:29 2015 +0100 ---------------------------------------------------------------------- doc/native_protocol_v4.spec | 10 +- .../cql3/functions/JavaSourceUDFFactory.java | 4 +- .../cql3/functions/ScriptBasedUDF.java | 3 +- .../selection/AbstractFunctionSelector.java | 2 +- .../cassandra/cql3/selection/Selection.java | 2 +- .../statements/CreateAggregateStatement.java | 2 +- .../cql3/statements/DropFunctionStatement.java | 2 +- .../cassandra/exceptions/ExceptionCode.java | 3 +- .../exceptions/FunctionExecutionException.java | 47 +++++ .../transport/messages/ErrorMessage.java | 33 ++- .../apache/cassandra/cql3/AggregationTest.java | 199 ++++++++++++------- .../org/apache/cassandra/cql3/CQLTester.java | 32 ++- test/unit/org/apache/cassandra/cql3/UFTest.java | 166 +++++++++++----- 13 files changed, 358 insertions(+), 147 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/doc/native_protocol_v4.spec ---------------------------------------------------------------------- diff --git a/doc/native_protocol_v4.spec b/doc/native_protocol_v4.spec index 58896cf..5032bb5 100644 --- a/doc/native_protocol_v4.spec +++ b/doc/native_protocol_v4.spec @@ -1006,6 +1006,14 @@ Table of Contents is a single byte. If its value is 0, it means the replica that was asked for data had not responded. Otherwise, the value is != 0. + 0x1400 Function_failure: A (user defined) function failed during execution. + The rest of the ERROR message body will be + + where: + is the keyspace [string] of the failed function + is the name [string] of the failed function + [string list] one string for each argument type (as CQL type) of the failed function + 0x2000 Syntax_error: The submitted query has a syntax error. 0x2100 Unauthorized: The logged user doesn't have the right to perform the query. @@ -1031,4 +1039,4 @@ Table of Contents * The format of "SCHEMA_CHANGE" events (Section 4.2.6) (and implicitly "Schema_change" results (Section 4.2.5.5)) has been modified, and now includes changes related to user defined functions and user defined aggregates. * Read_failure error code was added. - + * Function_failure error code was added. http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java b/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java index e4e6a55..4bce2f0 100644 --- a/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java +++ b/src/java/org/apache/cassandra/cql3/functions/JavaSourceUDFFactory.java @@ -205,7 +205,7 @@ public final class JavaSourceUDFFactory * logger.error("Invocation of function '{}' failed", this, t); * if (t instanceof VirtualMachineError) * throw (VirtualMachineError)t; - * throw new org.apache.cassandra.exceptions.InvalidRequestException("Invocation of function '" + this + "' failed: " + t); + * throw org.apache.cassandra.exceptions.FunctionExecutionException.build(this, t); * } * } * @@ -248,7 +248,7 @@ public final class JavaSourceUDFFactory // handle OutOfMemoryError and other fatals not here! " if (t instanceof VirtualMachineError)\n" + " throw (VirtualMachineError)t;\n" + - " throw new org.apache.cassandra.exceptions.InvalidRequestException(\"Invocation of function '\" + this + \"' failed: \" + t);\n" + + " throw org.apache.cassandra.exceptions.FunctionExecutionException.build(this, t);\n" + " }\n" + "}"); http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java index 059a612..06452e6 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java +++ b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDF.java @@ -35,6 +35,7 @@ import javax.script.SimpleBindings; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.FunctionExecutionException; import org.apache.cassandra.exceptions.InvalidRequestException; public class ScriptBasedUDF extends UDFunction @@ -139,7 +140,7 @@ public class ScriptBasedUDF extends UDFunction catch (RuntimeException | ScriptException e) { logger.info("Execution of UDF '{}' failed", name, e); - throw new InvalidRequestException("Execution of user-defined function '" + name + "' failed: " + e); + throw FunctionExecutionException.create(this, e); } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java b/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java index 2bf169d..d6a0c71 100644 --- a/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java @@ -48,7 +48,7 @@ abstract class AbstractFunctionSelector extends Selector else { if (factories.doesAggregation() && !factories.containsOnlyAggregateFunctions()) - throw new InvalidRequestException(String.format("the %s function arguments must be either all aggregates or all none aggregates", + throw new InvalidRequestException(String.format("arguments of function %s must be either all aggregates or no aggregates", fun.name())); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/cql3/selection/Selection.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selection.java b/src/java/org/apache/cassandra/cql3/selection/Selection.java index 58e994a..5d3a125 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selection.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selection.java @@ -446,7 +446,7 @@ public abstract class Selection this.factories = factories; if (factories.doesAggregation() && !factories.containsOnlyAggregateFunctions()) - throw new InvalidRequestException("the select clause must either contains only aggregates or none"); + throw new InvalidRequestException("the select clause must either contain only aggregates or no aggregate"); } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java index e135ffe..df7e87e 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java @@ -132,7 +132,7 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement finalFuncName = new FunctionName(functionName.keyspace, finalFunc); f = Functions.find(finalFuncName, Collections.>singletonList(stateType)); if (!(f instanceof ScalarFunction)) - throw new InvalidRequestException("Final function " + finalFuncName + "(" + stateTypeRaw + ") does not exist"); + throw new InvalidRequestException("Final function " + finalFuncName + "(" + stateTypeRaw + ") does not exist or is not a scalar function"); fFinal = (ScalarFunction) f; returnType = fFinal.returnType(); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java index 083db45..8863ffe 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java @@ -136,7 +136,7 @@ public final class DropFunctionStatement extends SchemaAlteringStatement List references = Functions.getReferencesTo(old); if (!references.isEmpty()) - throw new InvalidRequestException(String.format("Function '%s' still referenced by %s", functionName, references)); + throw new InvalidRequestException(String.format("Function '%s' still referenced by %s", old, references)); this.old = old; http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/exceptions/ExceptionCode.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/exceptions/ExceptionCode.java b/src/java/org/apache/cassandra/exceptions/ExceptionCode.java index 7fcb2d2..80cd4df 100644 --- a/src/java/org/apache/cassandra/exceptions/ExceptionCode.java +++ b/src/java/org/apache/cassandra/exceptions/ExceptionCode.java @@ -40,6 +40,7 @@ public enum ExceptionCode WRITE_TIMEOUT (0x1100), READ_TIMEOUT (0x1200), READ_FAILURE (0x1300), + FUNCTION_FAILURE(0x1400), // 2xx: problem validating the request SYNTAX_ERROR (0x2000), @@ -50,7 +51,7 @@ public enum ExceptionCode UNPREPARED (0x2500); public final int value; - private static final Map valueToCode = new HashMap(ExceptionCode.values().length); + private static final Map valueToCode = new HashMap<>(ExceptionCode.values().length); static { for (ExceptionCode code : ExceptionCode.values()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/exceptions/FunctionExecutionException.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/exceptions/FunctionExecutionException.java b/src/java/org/apache/cassandra/exceptions/FunctionExecutionException.java new file mode 100644 index 0000000..9c8d7ae --- /dev/null +++ b/src/java/org/apache/cassandra/exceptions/FunctionExecutionException.java @@ -0,0 +1,47 @@ +/* + * 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.exceptions; + +import java.util.List; + +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.functions.FunctionName; +import org.apache.cassandra.db.marshal.AbstractType; + +public class FunctionExecutionException extends CassandraException +{ + public final FunctionName functionName; + public final List argTypes; + public final String detail; + + public static FunctionExecutionException create(Function function, Throwable cause) + { + List cqlTypes = AbstractType.asCQLTypeStringList(function.argTypes()); + FunctionExecutionException fee = new FunctionExecutionException(function.name(), cqlTypes, cause.toString()); + fee.initCause(cause); + return fee; + } + + public FunctionExecutionException(FunctionName functionName, List argTypes, String detail) + { + super(ExceptionCode.FUNCTION_FAILURE, "execution of '" + functionName + argTypes + "' failed: " + detail); + this.functionName = functionName; + this.argTypes = argTypes; + this.detail = detail; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java b/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java index 3097c5b..d30e4ef 100644 --- a/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java @@ -17,12 +17,15 @@ */ package org.apache.cassandra.transport.messages; +import java.util.List; + import io.netty.buffer.ByteBuf; import io.netty.handler.codec.CodecException; import com.google.common.base.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.WriteType; import org.apache.cassandra.exceptions.*; @@ -98,6 +101,12 @@ public class ErrorMessage extends Message.Response te = new ReadTimeoutException(cl, received, blockFor, dataPresent != 0); } break; + case FUNCTION_FAILURE: + String fKeyspace = CBUtil.readString(body); + String fName = CBUtil.readString(body); + List argTypes = CBUtil.readStringList(body); + te = new FunctionExecutionException(new FunctionName(fKeyspace, fName), argTypes, msg); + break; case UNPREPARED: { MD5Digest id = MD5Digest.wrap(CBUtil.readBytes(body)); @@ -166,6 +175,12 @@ public class ErrorMessage extends Message.Response else dest.writeByte((byte)(((ReadTimeoutException)rte).dataPresent ? 1 : 0)); break; + case FUNCTION_FAILURE: + FunctionExecutionException fee = (FunctionExecutionException)msg.error; + CBUtil.writeString(fee.functionName.keyspace, dest); + CBUtil.writeString(fee.functionName.name, dest); + CBUtil.writeStringList(fee.argTypes, dest); + break; case UNPREPARED: PreparedQueryNotFoundException pqnfe = (PreparedQueryNotFoundException)err; CBUtil.writeBytes(pqnfe.id.bytes, dest); @@ -201,6 +216,12 @@ public class ErrorMessage extends Message.Response size += CBUtil.sizeOfConsistencyLevel(rte.consistency) + 8; size += isWrite ? CBUtil.sizeOfString(((WriteTimeoutException)rte).writeType.toString()) : 1; break; + case FUNCTION_FAILURE: + FunctionExecutionException fee = (FunctionExecutionException)msg.error; + size += CBUtil.sizeOfString(fee.functionName.keyspace); + size += CBUtil.sizeOfString(fee.functionName.name); + size += CBUtil.sizeOfStringList(fee.argTypes); + break; case UNPREPARED: PreparedQueryNotFoundException pqnfe = (PreparedQueryNotFoundException)err; size += CBUtil.sizeOfBytes(pqnfe.id.bytes); @@ -217,10 +238,16 @@ public class ErrorMessage extends Message.Response private static TransportException getBackwardsCompatibleException(ErrorMessage msg, int version) { - if (msg.error.code() == ExceptionCode.READ_FAILURE && version < Server.VERSION_4) + if (version < Server.VERSION_4) { - ReadFailureException rfe = (ReadFailureException) msg.error; - return new ReadTimeoutException(rfe.consistency, rfe.received, rfe.blockFor, rfe.dataPresent); + switch (msg.error.code()) + { + case READ_FAILURE: + ReadFailureException rfe = (ReadFailureException) msg.error; + return new ReadTimeoutException(rfe.consistency, rfe.received, rfe.blockFor, rfe.dataPresent); + case FUNCTION_FAILURE: + return new InvalidRequestException(msg.toString()); + } } return msg.error; http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/test/unit/org/apache/cassandra/cql3/AggregationTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/AggregationTest.java b/test/unit/org/apache/cassandra/cql3/AggregationTest.java index 1ddd1f1..86bd8f2 100644 --- a/test/unit/org/apache/cassandra/cql3/AggregationTest.java +++ b/test/unit/org/apache/cassandra/cql3/AggregationTest.java @@ -27,6 +27,10 @@ import org.apache.commons.lang3.time.DateUtils; import org.junit.Assert; import org.junit.Test; +import org.apache.cassandra.cql3.functions.Functions; +import org.apache.cassandra.cql3.functions.UDAggregate; +import org.apache.cassandra.exceptions.FunctionExecutionException; +import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.transport.Event; import org.apache.cassandra.transport.messages.ResultMessage; @@ -88,9 +92,9 @@ public class AggregationTest extends CQLTester execute("INSERT INTO %s (a, b, c) VALUES (1, 3, 8)"); assertInvalidSyntax("SELECT max(b), max(c) FROM %s WHERE max(a) = 1"); - assertInvalid("SELECT max(b), c FROM %s"); - assertInvalid("SELECT b, max(c) FROM %s"); - assertInvalid("SELECT max(sum(c)) FROM %s"); + assertInvalidMessage("only aggregates or no aggregate", "SELECT max(b), c FROM %s"); + assertInvalidMessage("only aggregates or no aggregate", "SELECT b, max(c) FROM %s"); + assertInvalidMessage("aggregate functions cannot be used as arguments of aggregate functions", "SELECT max(sum(c)) FROM %s"); assertInvalidSyntax("SELECT COUNT(2) FROM %s"); } @@ -129,8 +133,8 @@ public class AggregationTest extends CQLTester assertRows(execute("SELECT " + copySign + "(max(c), min(c)) FROM %s"), row(-1.4)); assertRows(execute("SELECT " + copySign + "(c, d) FROM %s"), row(1.2), row(-1.3), row(1.4)); assertRows(execute("SELECT max(" + copySign + "(c, d)) FROM %s"), row(1.4)); - assertInvalid("SELECT " + copySign + "(c, max(c)) FROM %s"); - assertInvalid("SELECT " + copySign + "(max(c), c) FROM %s"); + assertInvalidMessage("must be either all aggregates or no aggregates", "SELECT " + copySign + "(c, max(c)) FROM %s"); + assertInvalidMessage("must be either all aggregates or no aggregates", "SELECT " + copySign + "(max(c), c) FROM %s"); } @Test @@ -203,8 +207,8 @@ public class AggregationTest extends CQLTester "AS '\"string\";';"); // DROP AGGREGATE must not succeed against a scalar - assertInvalid("DROP AGGREGATE " + f); - assertInvalid("DROP AGGREGATE " + f + "(double, double)"); + assertInvalidMessage("matches multiple function definitions", "DROP AGGREGATE " + f); + assertInvalidMessage("non existing", "DROP AGGREGATE " + f + "(double, double)"); String a = createAggregate(KEYSPACE, "double", @@ -218,12 +222,12 @@ public class AggregationTest extends CQLTester "STYPE int"); // DROP FUNCTION must not succeed against an aggregate - assertInvalid("DROP FUNCTION " + a); - assertInvalid("DROP FUNCTION " + a + "(double)"); + assertInvalidMessage("matches multiple function definitions", "DROP FUNCTION " + a); + assertInvalidMessage("non existing function", "DROP FUNCTION " + a + "(double)"); // ambigious - assertInvalid("DROP AGGREGATE " + a); - assertInvalid("DROP AGGREGATE IF EXISTS " + a); + assertInvalidMessage("matches multiple function definitions", "DROP AGGREGATE " + a); + assertInvalidMessage("matches multiple function definitions", "DROP AGGREGATE IF EXISTS " + a); execute("DROP AGGREGATE IF EXISTS " + KEYSPACE + ".non_existing"); execute("DROP AGGREGATE IF EXISTS " + a + "(int, text)"); @@ -250,7 +254,7 @@ public class AggregationTest extends CQLTester "STYPE double"); // DROP FUNCTION must not succeed because the function is still referenced by the aggregate - assertInvalid("DROP FUNCTION " + f); + assertInvalidMessage("still referenced by", "DROP FUNCTION " + f); execute("DROP AGGREGATE " + a + "(double)"); } @@ -289,7 +293,7 @@ public class AggregationTest extends CQLTester execute("DROP AGGREGATE " + a + "(int)"); - assertInvalid("SELECT " + a + "(b) FROM %s"); + assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s"); } @Test @@ -327,7 +331,7 @@ public class AggregationTest extends CQLTester execute("DROP AGGREGATE " + a + "(int)"); - assertInvalid("SELECT " + a + "(b) FROM %s"); + assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s"); } @Test @@ -347,11 +351,12 @@ public class AggregationTest extends CQLTester "LANGUAGE java " + "AS 'return a.toString();'"); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE int " + - "FINALFUNC " + shortFunctionName(fFinal) + " " + - "INITCOND 'foobar'"); + assertInvalidMessage("Invalid STRING constant (foobar)", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE int " + + "FINALFUNC " + shortFunctionName(fFinal) + " " + + "INITCOND 'foobar'"); } @Test @@ -385,34 +390,41 @@ public class AggregationTest extends CQLTester "LANGUAGE java " + "AS 'return a.toString();'"); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE double " + - "FINALFUNC " + shortFunctionName(fFinal)); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE double " + - "FINALFUNC " + shortFunctionName(fFinal)); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE int " + - "FINALFUNC " + shortFunctionName(fFinal)); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE int"); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE double"); - - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + - "SFUNC " + shortFunctionName(fState2) + " " + - "STYPE double " + - "FINALFUNC " + shortFunctionName(fFinal)); - - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE double " + - "FINALFUNC " + shortFunctionName(fFinal2)); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE double " + + "FINALFUNC " + shortFunctionName(fFinal)); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE double " + + "FINALFUNC " + shortFunctionName(fFinal)); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE int " + + "FINALFUNC " + shortFunctionName(fFinal)); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE int"); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE double"); + + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + + "SFUNC " + shortFunctionName(fState2) + " " + + "STYPE double " + + "FINALFUNC " + shortFunctionName(fFinal)); + + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(double)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE double " + + "FINALFUNC " + shortFunctionName(fFinal2)); } @Test @@ -432,15 +444,17 @@ public class AggregationTest extends CQLTester "LANGUAGE java " + "AS 'return a.toString();'"); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + - "SFUNC " + shortFunctionName(fState) + "_not_there " + - "STYPE int " + - "FINALFUNC " + shortFunctionName(fFinal)); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + + "SFUNC " + shortFunctionName(fState) + "_not_there " + + "STYPE int " + + "FINALFUNC " + shortFunctionName(fFinal)); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE int " + - "FINALFUNC " + shortFunctionName(fFinal) + "_not_there"); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE int " + + "FINALFUNC " + shortFunctionName(fFinal) + "_not_there"); execute("CREATE AGGREGATE " + KEYSPACE + ".aggrInvalid(int)" + "SFUNC " + shortFunctionName(fState) + " " + @@ -507,17 +521,18 @@ public class AggregationTest extends CQLTester "FINALFUNC " + shortFunctionName(fFinalOK) + " " + "INITCOND null"); - assertInvalid("SELECT " + a0 + "(b) FROM %s"); - assertInvalid("SELECT " + a1 + "(b) FROM %s"); + assertInvalidThrowMessage("java.lang.RuntimeException", FunctionExecutionException.class, "SELECT " + a0 + "(b) FROM %s"); + assertInvalidThrowMessage("java.lang.RuntimeException", FunctionExecutionException.class, "SELECT " + a1 + "(b) FROM %s"); assertRows(execute("SELECT " + a2 + "(b) FROM %s"), row("foobar")); } @Test public void testJavaAggregateWithoutStateOrFinal() throws Throwable { - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".jSumFooNE1(int) " + - "SFUNC jSumFooNEstate " + - "STYPE int"); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".jSumFooNE1(int) " + + "SFUNC jSumFooNEstate " + + "STYPE int"); String f = createFunction(KEYSPACE, "int, int", @@ -526,10 +541,11 @@ public class AggregationTest extends CQLTester "LANGUAGE java " + "AS 'return Integer.valueOf((a!=null?a.intValue():0) + b.intValue());'"); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".jSumFooNE2(int) " + - "SFUNC " + shortFunctionName(f) + " " + - "STYPE int " + - "FINALFUNC jSumFooNEfinal"); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".jSumFooNE2(int) " + + "SFUNC " + shortFunctionName(f) + " " + + "STYPE int " + + "FINALFUNC jSumFooNEfinal"); execute("DROP FUNCTION " + f + "(int, int)"); } @@ -572,7 +588,7 @@ public class AggregationTest extends CQLTester execute("DROP FUNCTION " + fFinal + "(int)"); execute("DROP FUNCTION " + fState + "(int, int)"); - assertInvalid("SELECT " + a + "(b) FROM %s"); + assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s"); } @Test @@ -603,7 +619,7 @@ public class AggregationTest extends CQLTester execute("DROP FUNCTION " + fState + "(int, int)"); - assertInvalid("SELECT " + a + "(b) FROM %s"); + assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s"); } @Test @@ -691,7 +707,7 @@ public class AggregationTest extends CQLTester execute("DROP FUNCTION " + fFinal + "(int)"); execute("DROP FUNCTION " + fState + "(int, int)"); - assertInvalid("SELECT " + a + "(b) FROM %s"); + assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s"); } @Test @@ -722,7 +738,7 @@ public class AggregationTest extends CQLTester execute("DROP FUNCTION " + fState + "(int, int)"); - assertInvalid("SELECT " + a + "(b) FROM %s"); + assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s"); } @Test @@ -785,18 +801,49 @@ public class AggregationTest extends CQLTester "AS 'a + b;'"); String a = createAggregate(KEYSPACE, - "int, int", + "int", "CREATE AGGREGATE %s(int) " + "SFUNC " + shortFunctionName(fState) + " " + "STYPE int "); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggInv(int) " + - "SFUNC " + shortFunctionName(a) + " " + - "STYPE int "); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggInv(int) " + + "SFUNC " + shortFunctionName(a) + " " + + "STYPE int "); - assertInvalid("CREATE AGGREGATE " + KEYSPACE + ".aggInv(int) " + - "SFUNC " + shortFunctionName(fState) + " " + - "STYPE int " + - "FINALFUNC " + shortFunctionName(a)); + assertInvalidMessage("does not exist or is not a scalar function", + "CREATE AGGREGATE " + KEYSPACE + ".aggInv(int) " + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE int " + + "FINALFUNC " + shortFunctionName(a)); } + + @Test + public void testBrokenAggregate() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, val int)"); + execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1); + + String fState = createFunction(KEYSPACE, + "int, int", + "CREATE FUNCTION %s(a int, b int) " + + "RETURNS int " + + "LANGUAGE javascript " + + "AS 'a + b;'"); + + String a = createAggregate(KEYSPACE, + "int", + "CREATE AGGREGATE %s(int) " + + "SFUNC " + shortFunctionName(fState) + " " + + "STYPE int "); + + UDAggregate f = (UDAggregate) Functions.find(parseFunctionName(a)).get(0); + + Functions.replaceFunction(UDAggregate.createBroken(f.name(), f.argTypes(), f.returnType(), + null, new InvalidRequestException("foo bar is broken"))); + + assertInvalidThrowMessage("foo bar is broken", InvalidRequestException.class, + "SELECT " + a + "(val) FROM %s"); + } + } http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/test/unit/org/apache/cassandra/cql3/CQLTester.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index efda704..1baebb6 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -619,7 +619,7 @@ public abstract class CQLTester protected void assertEmpty(UntypedResultSet result) throws Throwable { - if (result != null && result.size() != 0) + if (result != null && !result.isEmpty()) throw new AssertionError(String.format("Expected empty result but got %d rows", result.size())); } @@ -630,6 +630,16 @@ public abstract class CQLTester protected void assertInvalidMessage(String errorMessage, String query, Object... values) throws Throwable { + assertInvalidThrowMessage(errorMessage, null, query, values); + } + + protected void assertInvalidThrow(Class exception, String query, Object... values) throws Throwable + { + assertInvalidThrowMessage(null, exception, query, values); + } + + protected void assertInvalidThrowMessage(String errorMessage, Class exception, String query, Object... values) throws Throwable + { try { execute(query, values); @@ -638,8 +648,14 @@ public abstract class CQLTester : replaceValues(query, values); Assert.fail("Query should be invalid but no error was thrown. Query is: " + q); } - catch (InvalidRequestException e) + catch (CassandraException e) { + if (exception != null && !exception.isAssignableFrom(e.getClass())) + { + Assert.fail("Query should be invalid but wrong error was thrown. " + + "Expected: " + exception.getName() + ", got: " + e.getClass().getName() + ". " + + "Query is: " + queryInfo(query, values)); + } if (errorMessage != null) { assertMessageContains(errorMessage, e); @@ -647,6 +663,13 @@ public abstract class CQLTester } } + private static String queryInfo(String query, Object[] values) + { + return USE_PREPARED_VALUES + ? query + " (values: " + formatAllValues(values) + ")" + : replaceValues(query, values); + } + protected void assertInvalidSyntax(String query, Object... values) throws Throwable { assertInvalidSyntaxMessage(null, query, values); @@ -657,10 +680,7 @@ public abstract class CQLTester try { execute(query, values); - String q = USE_PREPARED_VALUES - ? query + " (values: " + formatAllValues(values) + ")" - : replaceValues(query, values); - Assert.fail("Query should have invalid syntax but no error was thrown. Query is: " + q); + Assert.fail("Query should have invalid syntax but no error was thrown. Query is: " + queryInfo(query, values)); } catch (SyntaxException e) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/e3c01862/test/unit/org/apache/cassandra/cql3/UFTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFTest.java b/test/unit/org/apache/cassandra/cql3/UFTest.java index ea1b2da..84a7dd9 100644 --- a/test/unit/org/apache/cassandra/cql3/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/UFTest.java @@ -22,11 +22,14 @@ import java.math.BigInteger; import java.util.*; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import com.datastax.driver.core.*; import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.cql3.functions.Functions; +import org.apache.cassandra.cql3.functions.UDFunction; +import org.apache.cassandra.exceptions.FunctionExecutionException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.transport.Event; @@ -159,7 +162,7 @@ public class UFTest extends CQLTester "LANGUAGE java " + "AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); // check we can't recreate the same function - assertInvalid("CREATE FUNCTION " + fSin + " ( input double ) RETURNS double LANGUAGE java AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); + assertInvalidMessage("already exists", "CREATE FUNCTION " + fSin + " ( input double ) RETURNS double LANGUAGE java AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); // but that it doesn't comply with "IF NOT EXISTS" execute("CREATE FUNCTION IF NOT EXISTS " + fSin + " ( input double ) RETURNS double LANGUAGE java AS 'return Double.valueOf(Math.sin(input.doubleValue()));'"); @@ -171,7 +174,8 @@ public class UFTest extends CQLTester ); // Replace the method with incompatible return type - assertInvalid("CREATE OR REPLACE FUNCTION " + fSin + " ( input double ) RETURNS text LANGUAGE java AS 'return Double.valueOf(42d);'"); + assertInvalidMessage("the new return type text is not compatible with the return type double of existing function", + "CREATE OR REPLACE FUNCTION " + fSin + " ( input double ) RETURNS text LANGUAGE java AS 'return Double.valueOf(42d);'"); // proper replacement execute("CREATE OR REPLACE FUNCTION " + fSin + " ( input double ) RETURNS double LANGUAGE java AS 'return Double.valueOf(42d);'"); @@ -199,16 +203,16 @@ public class UFTest extends CQLTester execute("DROP FUNCTION " + fSin2); // Drop unexisting function - assertInvalid("DROP FUNCTION " + fSin); + assertInvalidMessage("Cannot drop non existing function", "DROP FUNCTION " + fSin); // but don't complain with "IF EXISTS" execute("DROP FUNCTION IF EXISTS " + fSin); // can't drop native functions - assertInvalid("DROP FUNCTION dateof"); - assertInvalid("DROP FUNCTION uuid"); + assertInvalidMessage("system keyspace is not user-modifiable", "DROP FUNCTION dateof"); + assertInvalidMessage("system keyspace is not user-modifiable", "DROP FUNCTION uuid"); // sin() no longer exists - assertInvalid("SELECT key, sin(d) FROM %s"); + assertInvalidMessage("Unknown function", "SELECT key, sin(d) FROM %s"); } @Test @@ -254,7 +258,8 @@ public class UFTest extends CQLTester "CREATE OR REPLACE FUNCTION %s(v ascii) RETURNS text LANGUAGE java AS 'return \"f1\";'"); // text == varchar, so this should be considered as a duplicate - assertInvalid("CREATE FUNCTION " + fOverload + "(v varchar) RETURNS text LANGUAGE java AS 'return \"f1\";'"); + assertInvalidMessage("already exists", + "CREATE FUNCTION " + fOverload + "(v varchar) RETURNS text LANGUAGE java AS 'return \"f1\";'"); assertRows(execute("SELECT " + fOverload + "(k), " + fOverload + "(v), " + fOverload + "(k, k) FROM %s"), row("f1", "f2", "f3") @@ -262,7 +267,7 @@ public class UFTest extends CQLTester forcePreparedValues(); // This shouldn't work if we use preparation since there no way to know which overload to use - assertInvalid("SELECT v FROM %s WHERE k = " + fOverload + "(?)", "foo"); + assertInvalidMessage("Ambiguous call to function", "SELECT v FROM %s WHERE k = " + fOverload + "(?)", "foo"); stopForcingPreparedValues(); // but those should since we specifically cast @@ -273,17 +278,17 @@ public class UFTest extends CQLTester assertEmpty(execute("SELECT v FROM %s WHERE k = " + fOverload + "((varchar)?)", "foo")); // no such functions exist... - assertInvalid("DROP FUNCTION " + fOverload + "(boolean)"); - assertInvalid("DROP FUNCTION " + fOverload + "(bigint)"); + assertInvalidMessage("non existing function", "DROP FUNCTION " + fOverload + "(boolean)"); + assertInvalidMessage("non existing function", "DROP FUNCTION " + fOverload + "(bigint)"); // 'overloaded' has multiple overloads - so it has to fail (CASSANDRA-7812) - assertInvalid("DROP FUNCTION " + fOverload); + assertInvalidMessage("matches multiple function definitions", "DROP FUNCTION " + fOverload); execute("DROP FUNCTION " + fOverload + "(varchar)"); - assertInvalid("SELECT v FROM %s WHERE k = " + fOverload + "((text)?)", "foo"); + assertInvalidMessage("none of its type signatures match", "SELECT v FROM %s WHERE k = " + fOverload + "((text)?)", "foo"); execute("DROP FUNCTION " + fOverload + "(text, text)"); - assertInvalid("SELECT v FROM %s WHERE k = " + fOverload + "((text)?,(text)?)", "foo", "bar"); + assertInvalidMessage("none of its type signatures match", "SELECT v FROM %s WHERE k = " + fOverload + "((text)?,(text)?)", "foo", "bar"); execute("DROP FUNCTION " + fOverload + "(ascii)"); - assertInvalid("SELECT v FROM %s WHERE k = " + fOverload + "((ascii)?)", "foo"); + assertInvalidMessage("cannot be passed as argument 0 of function", "SELECT v FROM %s WHERE k = " + fOverload + "((ascii)?)", "foo"); // single-int-overload must still work assertRows(execute("SELECT v FROM %s WHERE k = " + fOverload + "((int)?)", 3), row(1)); // overloaded has just one overload now - so the following DROP FUNCTION is not ambigious (CASSANDRA-7812) @@ -392,8 +397,9 @@ public class UFTest extends CQLTester @Test public void testJavaFunctionInvalidReturn() throws Throwable { - assertInvalid("CREATE OR REPLACE FUNCTION jfir(val double) RETURNS double LANGUAGE JAVA\n" + - "AS 'return Long.valueOf(1L);';"); + assertInvalidMessage("system keyspace is not user-modifiable", + "CREATE OR REPLACE FUNCTION jfir(val double) RETURNS double LANGUAGE JAVA\n" + + "AS 'return Long.valueOf(1L);';"); } @Test @@ -409,7 +415,8 @@ public class UFTest extends CQLTester execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1L); execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2L); execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3L); - assertInvalid("SELECT key, val, " + fName + "(val) FROM %s"); + assertInvalidMessage("val cannot be passed as argument 0 of function", + "SELECT key, val, " + fName + "(val) FROM %s"); } @Test @@ -462,7 +469,8 @@ public class UFTest extends CQLTester execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); - assertInvalid("SELECT key, val, " + parseFunctionName(fName).name + "(val) FROM %s"); + assertInvalidMessage("Unknown function", + "SELECT key, val, " + parseFunctionName(fName).name + "(val) FROM %s"); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 1, 1d); execute("INSERT INTO " + KEYSPACE_PER_TEST + ".second_tab (key, val) VALUES (?, ?)", 2, 2d); @@ -507,25 +515,32 @@ public class UFTest extends CQLTester execute("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".dateof(val timeuuid) RETURNS timestamp LANGUAGE JAVA\n" + "AS 'return null;';"); - assertInvalid("CREATE OR REPLACE FUNCTION system.jnft(val double) RETURNS double LANGUAGE JAVA\n" + - "AS 'return null;';"); - assertInvalid("CREATE OR REPLACE FUNCTION system.dateof(val timeuuid) RETURNS timestamp LANGUAGE JAVA\n" + - "AS 'return null;';"); - assertInvalid("DROP FUNCTION system.now"); + assertInvalidMessage("system keyspace is not user-modifiable", + "CREATE OR REPLACE FUNCTION system.jnft(val double) RETURNS double LANGUAGE JAVA\n" + + "AS 'return null;';"); + assertInvalidMessage("system keyspace is not user-modifiable", + "CREATE OR REPLACE FUNCTION system.dateof(val timeuuid) RETURNS timestamp LANGUAGE JAVA\n" + + "AS 'return null;';"); + assertInvalidMessage("system keyspace is not user-modifiable", + "DROP FUNCTION system.now"); // KS for executeInternal() is system - assertInvalid("CREATE OR REPLACE FUNCTION jnft(val double) RETURNS double LANGUAGE JAVA\n" + - "AS 'return null;';"); - assertInvalid("CREATE OR REPLACE FUNCTION dateof(val timeuuid) RETURNS timestamp LANGUAGE JAVA\n" + - "AS 'return null;';"); - assertInvalid("DROP FUNCTION now"); + assertInvalidMessage("system keyspace is not user-modifiable", + "CREATE OR REPLACE FUNCTION jnft(val double) RETURNS double LANGUAGE JAVA\n" + + "AS 'return null;';"); + assertInvalidMessage("system keyspace is not user-modifiable", + "CREATE OR REPLACE FUNCTION dateof(val timeuuid) RETURNS timestamp LANGUAGE JAVA\n" + + "AS 'return null;';"); + assertInvalidMessage("system keyspace is not user-modifiable", + "DROP FUNCTION now"); } @Test public void testFunctionNonExistingKeyspace() throws Throwable { - assertInvalid("CREATE OR REPLACE FUNCTION this_ks_does_not_exist.jnft(val double) RETURNS double LANGUAGE JAVA\n" + - "AS 'return null;';"); + assertInvalidMessage("to non existing keyspace", + "CREATE OR REPLACE FUNCTION this_ks_does_not_exist.jnft(val double) RETURNS double LANGUAGE JAVA\n" + + "AS 'return null;';"); } @Test @@ -533,8 +548,9 @@ public class UFTest extends CQLTester { dropPerTestKeyspace(); - assertInvalid("CREATE OR REPLACE FUNCTION " + KEYSPACE_PER_TEST + ".jnft(val double) RETURNS double LANGUAGE JAVA\n" + - "AS 'return null;';"); + assertInvalidMessage("to non existing keyspace", + "CREATE OR REPLACE FUNCTION " + KEYSPACE_PER_TEST + ".jnft(val double) RETURNS double LANGUAGE JAVA\n" + + "AS 'return null;';"); } @Test @@ -593,8 +609,9 @@ public class UFTest extends CQLTester execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); - // function throws a RuntimeException which is wrapped by InvalidRequestException - assertInvalid("SELECT key, val, " + fName + "(val) FROM %s"); + // function throws a RuntimeException which is wrapped by FunctionExecutionException + assertInvalidThrowMessage("java.lang.RuntimeException: oh no", FunctionExecutionException.class, + "SELECT key, val, " + fName + "(val) FROM %s"); } @Test @@ -991,12 +1008,12 @@ public class UFTest extends CQLTester Assert.assertNotNull(QueryProcessor.instance.getPrepared(prepared.statementId)); // UT still referenced by table - assertInvalid("DROP TYPE " + type); + assertInvalidMessage("Cannot drop user type", "DROP TYPE " + type); execute("DROP TABLE %s"); // UT still referenced by UDF - assertInvalid("DROP TYPE " + type); + assertInvalidMessage("as it is still used by function", "DROP TYPE " + type); Assert.assertNull(QueryProcessor.instance.getPrepared(prepared.statementId)); @@ -1023,7 +1040,8 @@ public class UFTest extends CQLTester execute("ALTER TYPE " + type + " RENAME txt TO str"); - assertInvalid("SELECT " + fName + "(udt) FROM %s WHERE key = 1"); + assertInvalidMessage("txt is not a field defined in this UDT", + "SELECT " + fName + "(udt) FROM %s WHERE key = 1"); execute("ALTER TYPE " + type + " RENAME str TO txt"); @@ -1393,11 +1411,11 @@ public class UFTest extends CQLTester "AS $$" + " st.iterator().next().getString(\"txt\");$$;"); createFunctionOverload(fName, "map>", - "CREATE FUNCTION %s( mp map> ) " + - "RETURNS text " + - "LANGUAGE javascript\n" + - "AS $$" + - " mp.get(java.lang.Integer.valueOf(3)).getString(\"txt\");$$;"); + "CREATE FUNCTION %s( mp map> ) " + + "RETURNS text " + + "LANGUAGE javascript\n" + + "AS $$" + + " mp.get(java.lang.Integer.valueOf(3)).getString(\"txt\");$$;"); execute("INSERT INTO %s (key, lst, st, mp) values (1, " + // list> @@ -1467,7 +1485,7 @@ public class UFTest extends CQLTester execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); // throws IRE with ClassCastException - assertInvalid("SELECT key, val, " + fName + "(val) FROM %s"); + assertInvalidMessage("Invalid value for CQL type double", "SELECT key, val, " + fName + "(val) FROM %s"); } @Test @@ -1483,31 +1501,35 @@ public class UFTest extends CQLTester execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); // throws IRE with ScriptException - assertInvalid("SELECT key, val, " + fName + "(val) FROM %s"); + assertInvalidThrowMessage("fool", FunctionExecutionException.class, + "SELECT key, val, " + fName + "(val) FROM %s"); } @Test public void testDuplicateArgNames() throws Throwable { - assertInvalid("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double, val text) " + - "RETURNS text LANGUAGE javascript\n" + - "AS '\"foo bar\";';"); + assertInvalidMessage("duplicate argument names for given function", + "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double, val text) " + + "RETURNS text LANGUAGE javascript\n" + + "AS '\"foo bar\";';"); } @Test public void testJavascriptCompileFailure() throws Throwable { - assertInvalid("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + - "RETURNS double LANGUAGE javascript\n" + - "AS 'foo bar';"); + assertInvalidMessage("Failed to compile function 'cql_test_keyspace.scrinv'", + "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + + "RETURNS double LANGUAGE javascript\n" + + "AS 'foo bar';"); } @Test public void testScriptInvalidLanguage() throws Throwable { - assertInvalid("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + - "RETURNS double LANGUAGE artificial_intelligence\n" + - "AS 'question for 42?';"); + assertInvalidMessage("Invalid language 'artificial_intelligence' for function 'cql_test_keyspace.scrinv'", + "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + + "RETURNS double LANGUAGE artificial_intelligence\n" + + "AS 'question for 42?';"); } @Test @@ -1580,4 +1602,42 @@ public class UFTest extends CQLTester row(1, expected1, expected2)); } } + + @Test + public void testBrokenFunction() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, dval double)"); + execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); + + String fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) RETURNS double LANGUAGE JAVA\n" + + "AS 'throw new RuntimeException();';"); + + UDFunction f = (UDFunction) Functions.find(parseFunctionName(fName)).get(0); + + Functions.replaceFunction(UDFunction.createBrokenFunction(f.name(), f.argNames(), f.argTypes(), f.returnType(), + "java", f.body(), new InvalidRequestException("foo bar is broken"))); + + assertInvalidThrowMessage("foo bar is broken", InvalidRequestException.class, + "SELECT key, " + fName + "(dval) FROM %s"); + } + + @Test + @Ignore("implement this unit test when Java Driver can handle new ExceptionCode.") + public void testFunctionExecutionExceptionNet() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, dval double)"); + execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); + + String fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) RETURNS double LANGUAGE JAVA\n" + + "AS 'throw new RuntimeException()';"); + + for (int version = Server.VERSION_2; version <= Server.CURRENT_VERSION; version++) + { + // TODO replace with appropiate code + assertRowsNet(version, + executeNet(version, "SELECT " + fName + "(dval) FROM %s WHERE key = 1")); + } + } }