From 010627e7841c38a78b6c9db2741590f5edc01e9d Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Fri, 21 Jun 2024 11:28:26 -0400 Subject: [PATCH] CE SQL v1 (#641) - add exception factory for cesql exceptions - extend EvaluationResult to be usable internally - expressions use results instead of a thrower interface - functions use results instead of a thrower interface - parser handles not equals correctly, does not eagerly evaluate when there may be an error - parser handles integer literals properly - updated test files to test v1 spec Signed-off-by: Calum Murray Co-authored-by: Pierangelo Di Pilato --- pom.xml | 1 - sql/src/main/antlr4/imports/CESQLLexer.g4 | 2 +- .../io/cloudevents/sql/EvaluationContext.java | 16 +-- .../cloudevents/sql/EvaluationException.java | 12 +- .../io/cloudevents/sql/EvaluationRuntime.java | 29 ----- .../io/cloudevents/sql/ExceptionFactory.java | 51 ++++++++ .../java/io/cloudevents/sql/Function.java | 5 +- .../io/cloudevents/sql/FunctionSignature.java | 5 + .../sql/impl/ExceptionFactory.java | 107 ---------------- .../sql/impl/ExceptionFactoryImpl.java | 116 ++++++++++++++++++ .../sql/impl/ExceptionThrower.java | 13 -- .../sql/impl/ExpressionInternal.java | 4 +- .../AccessAttributeExpression.java | 13 +- .../sql/impl/expressions/AndExpression.java | 17 ++- .../expressions/BaseBinaryExpression.java | 13 +- .../sql/impl/expressions/BaseExpression.java | 23 ++-- .../BaseIntegerBinaryExpression.java | 27 ++-- .../impl/expressions/BaseUnaryExpression.java | 17 ++- .../expressions/ComparisonExpression.java | 56 +++++++++ .../expressions/DifferenceExpression.java | 8 +- .../impl/expressions/DivisionExpression.java | 13 +- .../sql/impl/expressions/EqualExpression.java | 30 ----- .../impl/expressions/ExistsExpression.java | 8 +- .../FunctionInvocationExpression.java | 29 +++-- .../sql/impl/expressions/InExpression.java | 32 +++-- .../IntegerComparisonBinaryExpression.java | 25 +++- .../sql/impl/expressions/LikeExpression.java | 15 +-- .../impl/expressions/ModuleExpression.java | 13 +- .../expressions/MultiplicationExpression.java | 8 +- .../impl/expressions/NegateExpression.java | 15 ++- .../sql/impl/expressions/NotExpression.java | 14 ++- .../sql/impl/expressions/OrExpression.java | 18 ++- .../sql/impl/expressions/SumExpression.java | 8 +- .../sql/impl/expressions/ValueExpression.java | 8 +- .../sql/impl/expressions/XorExpression.java | 19 ++- .../sql/impl/functions/AbsFunction.java | 20 +++ .../functions/BaseOneArgumentFunction.java | 17 ++- .../functions/BaseThreeArgumentFunction.java | 18 ++- .../functions/BaseTwoArgumentFunction.java | 17 ++- .../sql/impl/functions/BoolFunction.java | 10 +- .../sql/impl/functions/ConcatFunction.java | 12 +- .../sql/impl/functions/ConcatWSFunction.java | 12 +- .../InfallibleOneArgumentFunction.java | 13 +- .../sql/impl/functions/IntFunction.java | 10 +- .../sql/impl/functions/IsBoolFunction.java | 10 +- .../sql/impl/functions/IsIntFunction.java | 10 +- .../sql/impl/functions/LeftFunction.java | 18 ++- .../sql/impl/functions/RightFunction.java | 17 ++- .../sql/impl/functions/StringFunction.java | 10 +- .../sql/impl/functions/SubstringFunction.java | 16 +-- .../SubstringWithLengthFunction.java | 16 +-- .../ConstantFoldingExpressionVisitor.java | 24 ---- .../parser/ExpressionTranslatorVisitor.java | 6 +- .../sql/impl/parser/ParserImpl.java | 5 +- .../impl/runtime/EvaluationContextImpl.java | 18 ++- .../sql/impl/runtime/EvaluationResult.java | 69 ++++++++++- .../runtime/EvaluationRuntimeBuilder.java | 1 - .../impl/runtime/EvaluationRuntimeImpl.java | 21 +--- .../sql/impl/runtime/ExceptionStore.java | 27 ---- .../sql/impl/runtime/ExpressionImpl.java | 10 +- .../runtime/FailFastExceptionThrower.java | 42 ------- .../sql/impl/runtime/FunctionTable.java | 10 +- .../sql/impl/runtime/TypeCastingProvider.java | 56 ++++----- .../cloudevents/sql/CustomFunctionsTest.java | 23 +++- .../java/io/cloudevents/sql/TCKTestSuite.java | 9 +- .../sql/impl/parser/ConstantFoldingTest.java | 14 --- .../runtime/EvaluationRuntimeImplTest.java | 25 ---- sql/src/test/resources/tck/README.md | 3 +- .../tck/binary_comparison_operators.yaml | 16 +++ .../tck/binary_logical_operators.yaml | 14 +++ .../resources/tck/binary_math_operators.yaml | 17 +-- .../test/resources/tck/casting_functions.yaml | 48 +++----- .../tck/context_attributes_access.yaml | 2 +- .../tck/integer_builtin_functions.yaml | 5 + .../test/resources/tck/like_expression.yaml | 11 ++ .../test/resources/tck/negate_operator.yaml | 8 +- sql/src/test/resources/tck/not_operator.yaml | 8 +- sql/src/test/resources/tck/spec_examples.yaml | 13 ++ .../tck/string_builtin_functions.yaml | 1 + .../tck/subscriptions_api_recreations.yaml | 19 +-- 80 files changed, 839 insertions(+), 702 deletions(-) create mode 100644 sql/src/main/java/io/cloudevents/sql/ExceptionFactory.java delete mode 100644 sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactory.java create mode 100644 sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactoryImpl.java delete mode 100644 sql/src/main/java/io/cloudevents/sql/impl/ExceptionThrower.java create mode 100644 sql/src/main/java/io/cloudevents/sql/impl/expressions/ComparisonExpression.java delete mode 100644 sql/src/main/java/io/cloudevents/sql/impl/expressions/EqualExpression.java create mode 100644 sql/src/main/java/io/cloudevents/sql/impl/functions/AbsFunction.java delete mode 100644 sql/src/main/java/io/cloudevents/sql/impl/runtime/ExceptionStore.java delete mode 100644 sql/src/main/java/io/cloudevents/sql/impl/runtime/FailFastExceptionThrower.java delete mode 100644 sql/src/test/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImplTest.java diff --git a/pom.xml b/pom.xml index 5f112295d..5975bb264 100644 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,6 @@ https://docs.spring.io/spring-framework/docs/current/javadoc-api/ https://jakarta.ee/specifications/platform/8/apidocs/ https://kafka.apache.org/30/javadoc/ - https://qpid.apache.org/releases/qpid-proton-j-0.33.7/api/ https://fasterxml.github.io/jackson-databind/javadoc/2.10/ 8 diff --git a/sql/src/main/antlr4/imports/CESQLLexer.g4 b/sql/src/main/antlr4/imports/CESQLLexer.g4 index a889c058a..d83124abd 100644 --- a/sql/src/main/antlr4/imports/CESQLLexer.g4 +++ b/sql/src/main/antlr4/imports/CESQLLexer.g4 @@ -70,7 +70,7 @@ FALSE: 'FALSE'; DQUOTED_STRING_LITERAL: DQUOTA_STRING; SQUOTED_STRING_LITERAL: SQUOTA_STRING; -INTEGER_LITERAL: INT_DIGIT+; +INTEGER_LITERAL: ('+' | '-')? INT_DIGIT+; // Identifiers diff --git a/sql/src/main/java/io/cloudevents/sql/EvaluationContext.java b/sql/src/main/java/io/cloudevents/sql/EvaluationContext.java index e1853836f..144d183fb 100644 --- a/sql/src/main/java/io/cloudevents/sql/EvaluationContext.java +++ b/sql/src/main/java/io/cloudevents/sql/EvaluationContext.java @@ -18,19 +18,5 @@ public interface EvaluationContext { */ String expressionText(); - /** - * Append a new exception to the evaluation context. - * This exception will be propagated back in the evaluation result. - * - * @param exception exception to append - */ - void appendException(EvaluationException exception); - - /** - * Append a new exception to the evaluation context. - * This exception will be propagated back in the evaluation result. - * - * @param exceptionFactory exception factory, which will automatically include expression interval and text - */ - void appendException(EvaluationException.EvaluationExceptionFactory exceptionFactory); + ExceptionFactory exceptionFactory(); } diff --git a/sql/src/main/java/io/cloudevents/sql/EvaluationException.java b/sql/src/main/java/io/cloudevents/sql/EvaluationException.java index e5ea1f663..fff1d627d 100644 --- a/sql/src/main/java/io/cloudevents/sql/EvaluationException.java +++ b/sql/src/main/java/io/cloudevents/sql/EvaluationException.java @@ -20,7 +20,7 @@ public enum ErrorKind { /** * An implicit or an explicit casting failed. */ - INVALID_CAST, + CAST, /** * An event attribute was addressed, but missing. */ @@ -28,15 +28,19 @@ public enum ErrorKind { /** * Error happened while dispatching a function invocation. Reasons may be invalid function name or invalid arguments number. */ - FUNCTION_DISPATCH, + MISSING_FUNCTION, /** * Error happened while executing a function. This usually contains a non null cause. */ - FUNCTION_EXECUTION, + FUNCTION_EVALUATION, /** * Error happened while executing a math operation. Reason may be a division by zero. */ - MATH + MATH, + /** + * Any error that does not fall into the other error kinds + */ + GENERIC, } private final ErrorKind errorKind; diff --git a/sql/src/main/java/io/cloudevents/sql/EvaluationRuntime.java b/sql/src/main/java/io/cloudevents/sql/EvaluationRuntime.java index 296b4f433..d4bdae48b 100644 --- a/sql/src/main/java/io/cloudevents/sql/EvaluationRuntime.java +++ b/sql/src/main/java/io/cloudevents/sql/EvaluationRuntime.java @@ -7,35 +7,6 @@ * The evaluation runtime takes care of the function resolution, casting and other core functionalities to execute an expression. */ public interface EvaluationRuntime { - - /** - * Check if the cast can be executed from {@code value} to the {@code target} type. - * - * @param value the value to cast - * @param target the type cast target - * @return false if the cast trigger an error, true otherwise. - */ - boolean canCast(Object value, Type target); - - /** - * Return the {@code value} casted to the {@code target} type. - * - * @param ctx the evaluation context - * @param value the value to cast - * @param target the type cast target - * @return the casted value, if the cast succeeds, otherwise the default value of the target type - */ - Object cast(EvaluationContext ctx, Object value, Type target); - - /** - * Return the {@code value} casted to the {@code target} type. If fails, this is going to throw an exception without the evaluation context. - * - * @param value the value to cast - * @param target the type cast target - * @return the casted value, if the cast succeeds, otherwise the default value of the target type - */ - Object cast(Object value, Type target) throws EvaluationException; - /** * Resolve a {@link Function} starting from its name and the concrete number of arguments. * diff --git a/sql/src/main/java/io/cloudevents/sql/ExceptionFactory.java b/sql/src/main/java/io/cloudevents/sql/ExceptionFactory.java new file mode 100644 index 000000000..e194be836 --- /dev/null +++ b/sql/src/main/java/io/cloudevents/sql/ExceptionFactory.java @@ -0,0 +1,51 @@ +package io.cloudevents.sql; + +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.misc.Interval; +import org.antlr.v4.runtime.tree.ParseTree; + +public interface ExceptionFactory { + EvaluationException.EvaluationExceptionFactory invalidCastTarget(Class from, Class to); + + EvaluationException.EvaluationExceptionFactory castError(Class from, Class to, Throwable cause); + + EvaluationException missingAttribute(Interval interval, String expression, String key); + + EvaluationException cannotDispatchFunction(Interval interval, String expression, String functionName, Throwable cause); + + EvaluationException.EvaluationExceptionFactory functionExecutionError(String functionName, Throwable cause); + + EvaluationException divisionByZero(Interval interval, String expression, Integer dividend); + + EvaluationException mathError(Interval interval, String expression, String errorMessage); + + static ParseException cannotParseValue(ParseTree node, Type target, Throwable cause) { + return new ParseException( + ParseException.ErrorKind.PARSE_VALUE, + node.getSourceInterval(), + node.getText(), + "Cannot parse to " + target.name() + ": " + cause.getMessage(), + cause + ); + } + + static ParseException recognitionError(RecognitionException e, String msg) { + return new ParseException( + ParseException.ErrorKind.RECOGNITION, + new Interval(e.getOffendingToken().getStartIndex(), e.getOffendingToken().getStopIndex()), + e.getOffendingToken().getText(), + "Cannot parse: " + msg, + e + ); + } + + static ParseException cannotEvaluateConstantExpression(EvaluationException exception) { + return new ParseException( + ParseException.ErrorKind.CONSTANT_EXPRESSION_EVALUATION, + exception.getExpressionInterval(), + exception.getExpressionText(), + "Cannot evaluate the constant expression: " + exception.getExpressionText(), + exception + ); + } +} diff --git a/sql/src/main/java/io/cloudevents/sql/Function.java b/sql/src/main/java/io/cloudevents/sql/Function.java index b0f6c6f12..314c5b89e 100644 --- a/sql/src/main/java/io/cloudevents/sql/Function.java +++ b/sql/src/main/java/io/cloudevents/sql/Function.java @@ -1,6 +1,7 @@ package io.cloudevents.sql; import io.cloudevents.CloudEvent; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import java.util.List; @@ -15,9 +16,9 @@ public interface Function extends FunctionSignature { * @param ctx the evaluation context * @param evaluationRuntime the evaluation runtime * @param event the expression input event - * @param arguments the arguments passed to this function. Note: the arguments are already casted to the appropriate type declared in the signature + * @param arguments the arguments passed to this function. Note: the arguments are already cast to the appropriate type declared in the signature * @return the return value of the function */ - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments); + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments); } diff --git a/sql/src/main/java/io/cloudevents/sql/FunctionSignature.java b/sql/src/main/java/io/cloudevents/sql/FunctionSignature.java index 894464260..b050b3a57 100644 --- a/sql/src/main/java/io/cloudevents/sql/FunctionSignature.java +++ b/sql/src/main/java/io/cloudevents/sql/FunctionSignature.java @@ -18,6 +18,11 @@ public interface FunctionSignature { */ Type typeOfParameter(int i) throws IllegalArgumentException; + /** + * @return function return type + */ + Type returnType(); + /** * @return the arity, excluding the vararg parameter if {@code isVariadic() == true} */ diff --git a/sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactory.java b/sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactory.java deleted file mode 100644 index ec03498e4..000000000 --- a/sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactory.java +++ /dev/null @@ -1,107 +0,0 @@ -package io.cloudevents.sql.impl; - -import io.cloudevents.sql.EvaluationException; -import io.cloudevents.sql.ParseException; -import io.cloudevents.sql.Type; -import org.antlr.v4.runtime.RecognitionException; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; - -/** - * This class includes a list of static methods to create {@link io.cloudevents.sql.ParseException} and {@link io.cloudevents.sql.EvaluationException}. - */ -public class ExceptionFactory { - - private ExceptionFactory() { - } - - public static EvaluationException.EvaluationExceptionFactory invalidCastTarget(Class from, Class to) { - return (interval, expression) -> new EvaluationException( - EvaluationException.ErrorKind.INVALID_CAST, - interval, - expression, - "Cannot cast " + from + " to " + to + ": no cast defined.", - null - ); - } - - public static EvaluationException.EvaluationExceptionFactory castError(Class from, Class to, Throwable cause) { - return (interval, expression) -> new EvaluationException( - EvaluationException.ErrorKind.INVALID_CAST, - interval, - expression, - "Cannot cast " + from + " to " + to + ": " + cause.getMessage(), - cause - ); - } - - public static EvaluationException missingAttribute(Interval interval, String expression, String key) { - return new EvaluationException( - EvaluationException.ErrorKind.MISSING_ATTRIBUTE, - interval, - expression, - "Missing attribute " + key + " in the input event. Perhaps you should check with 'EXISTS " + key + "' if the input contains the provided key?", - null - ); - } - - public static EvaluationException cannotDispatchFunction(Interval interval, String expression, String functionName, Throwable cause) { - return new EvaluationException( - EvaluationException.ErrorKind.FUNCTION_DISPATCH, - interval, - expression, - "Cannot dispatch function invocation to function " + functionName + ": " + cause.getMessage(), - cause - ); - } - - public static EvaluationException.EvaluationExceptionFactory functionExecutionError(String functionName, Throwable cause) { - return (interval, expression) -> new EvaluationException( - EvaluationException.ErrorKind.FUNCTION_EXECUTION, - interval, - expression, - "Error while executing " + functionName + ": " + cause.getMessage(), - cause - ); - } - - public static EvaluationException divisionByZero(Interval interval, String expression, Integer dividend) { - return new EvaluationException( - EvaluationException.ErrorKind.MATH, - interval, - expression, - "Division by zero: " + dividend + " / 0", - null - ); - } - - public static ParseException cannotParseValue(ParseTree node, Type target, Throwable cause) { - return new ParseException( - ParseException.ErrorKind.PARSE_VALUE, - node.getSourceInterval(), - node.getText(), - "Cannot parse to " + target.name() + ": " + cause.getMessage(), - cause - ); - } - - public static ParseException recognitionError(RecognitionException e, String msg) { - return new ParseException( - ParseException.ErrorKind.RECOGNITION, - new Interval(e.getOffendingToken().getStartIndex(), e.getOffendingToken().getStopIndex()), - e.getOffendingToken().getText(), - "Cannot parse: " + msg, - e - ); - } - - public static ParseException cannotEvaluateConstantExpression(EvaluationException exception) { - return new ParseException( - ParseException.ErrorKind.CONSTANT_EXPRESSION_EVALUATION, - exception.getExpressionInterval(), - exception.getExpressionText(), - "Cannot evaluate the constant expression: " + exception.getExpressionText(), - exception - ); - } -} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactoryImpl.java b/sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactoryImpl.java new file mode 100644 index 000000000..0f6b892a0 --- /dev/null +++ b/sql/src/main/java/io/cloudevents/sql/impl/ExceptionFactoryImpl.java @@ -0,0 +1,116 @@ +package io.cloudevents.sql.impl; + +import io.cloudevents.sql.EvaluationException; +import org.antlr.v4.runtime.misc.Interval; + +/** + * This class includes a list of static methods to create {@link io.cloudevents.sql.ParseException} and {@link io.cloudevents.sql.EvaluationException}. + */ +public class ExceptionFactoryImpl implements io.cloudevents.sql.ExceptionFactory { + private final boolean shouldThrow; + + public ExceptionFactoryImpl(boolean shouldThrow) { + this.shouldThrow = shouldThrow; + } + + public EvaluationException.EvaluationExceptionFactory invalidCastTarget(Class from, Class to) { + return (interval, expression) -> { + final EvaluationException exception = new EvaluationException( + EvaluationException.ErrorKind.CAST, + interval, + expression, + "Cannot cast " + from + " to " + to + ": no cast defined.", + null + ); + + if (this.shouldThrow) { + throw exception; + } + return exception; + }; + } + + public EvaluationException.EvaluationExceptionFactory castError(Class from, Class to, Throwable cause) { + return (interval, expression) -> { + final EvaluationException exception = new EvaluationException( + EvaluationException.ErrorKind.CAST, + interval, + expression, + "Cannot cast " + from + " to " + to + ": " + cause.getMessage(), + cause + ); + + if (this.shouldThrow) { + throw exception; + } + return exception; + }; + } + + public EvaluationException missingAttribute(Interval interval, String expression, String key) { + final EvaluationException exception = new EvaluationException( + EvaluationException.ErrorKind.MISSING_ATTRIBUTE, + interval, + expression, + "Missing attribute " + key + " in the input event. Perhaps you should check with 'EXISTS " + key + "' if the input contains the provided key?", + null + ); + + if (this.shouldThrow) { + throw exception; + } + return exception; + } + + public EvaluationException cannotDispatchFunction(Interval interval, String expression, String functionName, Throwable cause) { + final EvaluationException exception = new EvaluationException( + EvaluationException.ErrorKind.MISSING_FUNCTION, + interval, + expression, + "Cannot dispatch function invocation to function " + functionName + ": " + cause.getMessage(), + cause + ); + + if (this.shouldThrow) { + throw exception; + } + return exception; + } + + public EvaluationException.EvaluationExceptionFactory functionExecutionError(String functionName, Throwable cause) { + return (interval, expression) -> { + final EvaluationException exception = new EvaluationException( + EvaluationException.ErrorKind.FUNCTION_EVALUATION, + interval, + expression, + "Error while executing " + functionName + ": " + cause.getMessage(), + cause + ); + + if (this.shouldThrow) { + throw exception; + } + return exception; + }; + } + + public EvaluationException divisionByZero(Interval interval, String expression, Integer dividend) { + return mathError(interval, expression, "Division by zero: " + dividend + " / 0"); + } + + @Override + public EvaluationException mathError(Interval interval, String expression, String errorMessage) { + final EvaluationException exception = new EvaluationException( + EvaluationException.ErrorKind.MATH, + interval, + expression, + errorMessage, + null + ); + + if (this.shouldThrow) { + throw exception; + } + return exception; + } +} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/ExceptionThrower.java b/sql/src/main/java/io/cloudevents/sql/impl/ExceptionThrower.java deleted file mode 100644 index a5bbc0d9d..000000000 --- a/sql/src/main/java/io/cloudevents/sql/impl/ExceptionThrower.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.cloudevents.sql.impl; - -import io.cloudevents.sql.EvaluationException; - -public interface ExceptionThrower { - - /** - * This method might block the execution or not, depending on its implementation - * - * @param exception the exception to throw - */ - void throwException(EvaluationException exception); -} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/ExpressionInternal.java b/sql/src/main/java/io/cloudevents/sql/impl/ExpressionInternal.java index 1a3c7da7a..0ae114dfb 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/ExpressionInternal.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/ExpressionInternal.java @@ -2,6 +2,8 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public interface ExpressionInternal { @@ -10,7 +12,7 @@ public interface ExpressionInternal { String expressionText(); - Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower); + EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory); T visit(ExpressionInternalVisitor visitor); diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/AccessAttributeExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/AccessAttributeExpression.java index 63243a91a..7fd776bf4 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/AccessAttributeExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/AccessAttributeExpression.java @@ -3,9 +3,9 @@ import io.cloudevents.CloudEvent; import io.cloudevents.SpecVersion; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionFactory; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.impl.ExpressionInternalVisitor; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; import java.util.Base64; @@ -24,18 +24,15 @@ public AccessAttributeExpression(Interval expressionInterval, String expressionT } @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { Object value = this.getter.apply(event); if (value == null) { - thrower.throwException( - ExceptionFactory.missingAttribute(this.expressionInterval(), this.expressionText(), key) - ); - return ""; + return new EvaluationResult(false, exceptionFactory.missingAttribute(this.expressionInterval(), this.expressionText(), key)); } // Because the CESQL type system is smaller than the CE type system, // we need to coherce some values to string - return coherceTypes(value); + return new EvaluationResult(coherceTypes(value)); } @Override diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/AndExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/AndExpression.java index 382fdf701..2e86281e6 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/AndExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/AndExpression.java @@ -1,8 +1,10 @@ package io.cloudevents.sql.impl.expressions; +import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class AndExpression extends BaseBinaryExpression { @@ -12,12 +14,15 @@ public AndExpression(Interval expressionInterval, String expressionText, Express } @Override - public Object evaluate(EvaluationRuntime runtime, Object left, Object right, ExceptionThrower exceptions) { - boolean x = castToBoolean(runtime, exceptions, left); - if (!x) { + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationResult left = this.getLeftOperand().evaluate(runtime, event, exceptionFactory); + EvaluationResult x = castToBoolean(exceptionFactory, left); + if (!(Boolean)x.value()) { // Short circuit - return false; + return x; } - return castToBoolean(runtime, exceptions, right); + + EvaluationResult right = this.getRightOperand().evaluate(runtime, event, exceptionFactory); + return castToBoolean(exceptionFactory, right).wrapExceptions(left); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseBinaryExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseBinaryExpression.java index 47b2ef199..1fb5311e4 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseBinaryExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseBinaryExpression.java @@ -2,9 +2,11 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.ExpressionInternalVisitor; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public abstract class BaseBinaryExpression extends BaseExpression { @@ -18,14 +20,7 @@ protected BaseBinaryExpression(Interval expressionInterval, String expressionTex this.rightOperand = rightOperand; } - public abstract Object evaluate(EvaluationRuntime runtime, Object left, Object right, ExceptionThrower exceptions); - - @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { - Object left = leftOperand.evaluate(runtime, event, thrower); - Object right = rightOperand.evaluate(runtime, event, thrower); - return evaluate(runtime, left, right, thrower); - } + public abstract EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory); @Override public T visit(ExpressionInternalVisitor visitor) { diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseExpression.java index 1e62f1085..35d5923e3 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseExpression.java @@ -1,10 +1,11 @@ package io.cloudevents.sql.impl.expressions; -import io.cloudevents.sql.EvaluationRuntime; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.Type; -import io.cloudevents.sql.impl.ExceptionThrower; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.runtime.EvaluationContextImpl; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; import org.antlr.v4.runtime.misc.Interval; public abstract class BaseExpression implements ExpressionInternal { @@ -27,25 +28,25 @@ public String expressionText() { return this.expressionText; } - public Boolean castToBoolean(EvaluationRuntime runtime, ExceptionThrower exceptions, Object value) { - return (Boolean) runtime.cast( - new EvaluationContextImpl(expressionInterval(), expressionText(), exceptions), + public EvaluationResult castToBoolean(ExceptionFactory exceptionFactory, EvaluationResult value) { + return TypeCastingProvider.cast( + new EvaluationContextImpl(expressionInterval(), expressionText(), exceptionFactory), value, Type.BOOLEAN ); } - public Integer castToInteger(EvaluationRuntime runtime, ExceptionThrower exceptions, Object value) { - return (Integer) runtime.cast( - new EvaluationContextImpl(expressionInterval(), expressionText(), exceptions), + public EvaluationResult castToInteger(ExceptionFactory exceptionFactory, EvaluationResult value) { + return TypeCastingProvider.cast( + new EvaluationContextImpl(expressionInterval(), expressionText(), exceptionFactory), value, Type.INTEGER ); } - public String castToString(EvaluationRuntime runtime, ExceptionThrower exceptions, Object value) { - return (String) runtime.cast( - new EvaluationContextImpl(expressionInterval(), expressionText(), exceptions), + public EvaluationResult castToString(ExceptionFactory exceptionFactory, EvaluationResult value) { + return TypeCastingProvider.cast( + new EvaluationContextImpl(expressionInterval(), expressionText(), exceptionFactory), value, Type.STRING ); diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseIntegerBinaryExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseIntegerBinaryExpression.java index 4e5cceec0..6d53f8dc3 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseIntegerBinaryExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseIntegerBinaryExpression.java @@ -1,8 +1,11 @@ package io.cloudevents.sql.impl.expressions; +import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.Type; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public abstract class BaseIntegerBinaryExpression extends BaseBinaryExpression { @@ -11,16 +14,26 @@ public BaseIntegerBinaryExpression(Interval expressionInterval, String expressio super(expressionInterval, expressionText, leftOperand, rightOperand); } - abstract Object evaluate(EvaluationRuntime runtime, int left, int right, ExceptionThrower exceptions); + abstract EvaluationResult evaluate(EvaluationRuntime runtime, int left, int right, ExceptionFactory exceptionFactory); @Override - public Object evaluate(EvaluationRuntime runtime, Object left, Object right, ExceptionThrower exceptions) { + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationResult left = this.getLeftOperand().evaluate(runtime, event, exceptionFactory); + EvaluationResult right = this.getRightOperand().evaluate(runtime, event, exceptionFactory); + + if (left.isMissingAttributeException() || right.isMissingAttributeException()) { + return left.wrapExceptions(right).copyWithDefaultValueForType(Type.INTEGER); + } + + EvaluationResult x = castToInteger(exceptionFactory, left); + EvaluationResult y = castToInteger(exceptionFactory, right); + return this.evaluate( runtime, - castToInteger(runtime, exceptions, left).intValue(), - castToInteger(runtime, exceptions, right).intValue(), - exceptions - ); + (Integer)x.value(), + (Integer)y.value(), + exceptionFactory + ).wrapExceptions(x).wrapExceptions(y); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseUnaryExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseUnaryExpression.java index 650b8e927..2cf3e74c7 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseUnaryExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/BaseUnaryExpression.java @@ -2,9 +2,12 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.ExpressionInternalVisitor; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public abstract class BaseUnaryExpression extends BaseExpression { @@ -16,11 +19,17 @@ public BaseUnaryExpression(Interval expressionInterval, String expressionText, E this.internal = internal; } - public abstract Object evaluate(EvaluationRuntime runtime, Object value, ExceptionThrower exceptions); + public abstract EvaluationResult evaluate(EvaluationRuntime runtime, EvaluationResult result, ExceptionFactory exceptionFactory); + + public abstract Type returnType(); @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { - return evaluate(runtime, internal.evaluate(runtime, event, thrower), thrower); + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationResult value = internal.evaluate(runtime, event, exceptionFactory); + if (value.isMissingAttributeException()) { + return value.copyWithDefaultValueForType(this.returnType()); + } + return evaluate(runtime, value, exceptionFactory); } @Override diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/ComparisonExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ComparisonExpression.java new file mode 100644 index 000000000..a91f4d064 --- /dev/null +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ComparisonExpression.java @@ -0,0 +1,56 @@ +package io.cloudevents.sql.impl.expressions; + +import io.cloudevents.CloudEvent; +import io.cloudevents.sql.EvaluationRuntime; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationContextImpl; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; +import org.antlr.v4.runtime.misc.Interval; + +import java.util.Objects; +import java.util.function.BiFunction; + +public class ComparisonExpression extends BaseBinaryExpression { + public enum Comparison { + EQUALS(Objects::equals), + NOT_EQUALS((x, y) -> !Objects.equals(x, y)); + private final BiFunction fn; + Comparison(BiFunction fn) { + this.fn = fn; + } + boolean evaluate(Object a, Object b) { + return this.fn.apply(a, b); + } + } + + private final Comparison comparison; + + public ComparisonExpression(Interval expressionInterval, String expressionText, ExpressionInternal leftOperand, ExpressionInternal rightOperand, Comparison comparison) { + super(expressionInterval, expressionText, leftOperand, rightOperand); + this.comparison = comparison; + } + + // x = y: Boolean x Boolean -> Boolean + // x = y: Integer x Integer -> Boolean + // x = y: String x String -> Boolean + @Override + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationResult left = this.getLeftOperand().evaluate(runtime, event, exceptionFactory); + EvaluationResult right = this.getRightOperand().evaluate(runtime, event, exceptionFactory); + + if (left.isMissingAttributeException() || right.isMissingAttributeException()) { + return left.wrapExceptions(right).copyWithDefaultValueForType(Type.BOOLEAN); + } + + left = TypeCastingProvider.cast( + new EvaluationContextImpl(expressionInterval(), expressionText(), exceptionFactory), + left, + Type.fromValue(right.value()) + ); + + return new EvaluationResult(this.comparison.evaluate(left.value(), right.value()), null, left, right); + } +} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/DifferenceExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/DifferenceExpression.java index 4f0670cb6..1f5d7a8a0 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/DifferenceExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/DifferenceExpression.java @@ -1,8 +1,10 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class DifferenceExpression extends BaseIntegerBinaryExpression { @@ -12,8 +14,8 @@ public DifferenceExpression(Interval expressionInterval, String expressionText, } @Override - Object evaluate(EvaluationRuntime runtime, int left, int right, ExceptionThrower exceptions) { - return left - right; + EvaluationResult evaluate(EvaluationRuntime runtime, int left, int right, ExceptionFactory exceptions) { + return new EvaluationResult(left - right); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/DivisionExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/DivisionExpression.java index 8e54f0ac9..c5af62a9b 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/DivisionExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/DivisionExpression.java @@ -1,9 +1,9 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionFactory; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class DivisionExpression extends BaseIntegerBinaryExpression { @@ -13,14 +13,11 @@ public DivisionExpression(Interval expressionInterval, String expressionText, Ex } @Override - Object evaluate(EvaluationRuntime runtime, int left, int right, ExceptionThrower exceptions) { + EvaluationResult evaluate(EvaluationRuntime runtime, int left, int right, ExceptionFactory exceptionFactory) { if (right == 0) { - exceptions.throwException( - ExceptionFactory.divisionByZero(expressionInterval(), expressionText(), left) - ); - return 0; + return new EvaluationResult(0, exceptionFactory.divisionByZero(expressionInterval(), expressionText(), left)); } - return left / right; + return new EvaluationResult(left / right); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/EqualExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/EqualExpression.java deleted file mode 100644 index fcfe6dbf5..000000000 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/EqualExpression.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.cloudevents.sql.impl.expressions; - -import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.Type; -import io.cloudevents.sql.impl.ExceptionThrower; -import io.cloudevents.sql.impl.ExpressionInternal; -import io.cloudevents.sql.impl.runtime.EvaluationContextImpl; -import org.antlr.v4.runtime.misc.Interval; - -import java.util.Objects; - -public class EqualExpression extends BaseBinaryExpression { - - public EqualExpression(Interval expressionInterval, String expressionText, ExpressionInternal leftOperand, ExpressionInternal rightOperand) { - super(expressionInterval, expressionText, leftOperand, rightOperand); - } - - // x = y: Boolean x Boolean -> Boolean - // x = y: Integer x Integer -> Boolean - // x = y: String x String -> Boolean - @Override - public Object evaluate(EvaluationRuntime runtime, Object left, Object right, ExceptionThrower exceptions) { - left = runtime.cast( - new EvaluationContextImpl(expressionInterval(), expressionText(), exceptions), - left, - Type.fromValue(right) - ); - return Objects.equals(left, right); - } -} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/ExistsExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ExistsExpression.java index d74cc5486..267156649 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/ExistsExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ExistsExpression.java @@ -2,8 +2,10 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternalVisitor; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class ExistsExpression extends BaseExpression { @@ -16,8 +18,8 @@ public ExistsExpression(Interval expressionInterval, String expressionText, Stri } @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { - return hasContextAttribute(event, key); + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + return new EvaluationResult(hasContextAttribute(event, key)); } @Override diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/FunctionInvocationExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/FunctionInvocationExpression.java index 05183b851..830410997 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/FunctionInvocationExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/FunctionInvocationExpression.java @@ -1,14 +1,12 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.CloudEvent; -import io.cloudevents.sql.EvaluationContext; -import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.Function; -import io.cloudevents.sql.impl.ExceptionFactory; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.*; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.ExpressionInternalVisitor; import io.cloudevents.sql.impl.runtime.EvaluationContextImpl; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; import org.antlr.v4.runtime.misc.Interval; import java.util.ArrayList; @@ -26,26 +24,27 @@ public FunctionInvocationExpression(Interval expressionInterval, String expressi } @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { - EvaluationContext context = new EvaluationContextImpl(expressionInterval(), expressionText(), thrower); + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationContext context = new EvaluationContextImpl(expressionInterval(), expressionText(), exceptionFactory); Function function; try { function = runtime.resolveFunction(functionName, arguments.size()); } catch (Exception e) { - thrower.throwException( - ExceptionFactory.cannotDispatchFunction(expressionInterval(), expressionText(), functionName, e) - ); - return ""; + return new EvaluationResult(false, exceptionFactory.cannotDispatchFunction(expressionInterval(), expressionText(), functionName, e)); } List computedArguments = new ArrayList<>(arguments.size()); + List exceptions = new ArrayList<>(); // used to accumulate any exceptions encountered while evaluating the arguments to the function for (int i = 0; i < arguments.size(); i++) { ExpressionInternal expr = arguments.get(i); - Object computed = expr.evaluate(runtime, event, thrower); - Object casted = runtime + EvaluationResult computed = expr.evaluate(runtime, event, exceptionFactory); + EvaluationResult casted = TypeCastingProvider .cast(context, computed, function.typeOfParameter(i)); - computedArguments.add(casted); + if (casted.causes() != null) { + exceptions.addAll(casted.causes()); + } + computedArguments.add(casted.value()); } return function.invoke( @@ -53,7 +52,7 @@ public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThr runtime, event, computedArguments - ); + ).wrapExceptions(exceptions); } @Override diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/InExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/InExpression.java index d41c4f800..f80c13794 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/InExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/InExpression.java @@ -2,11 +2,13 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.Type; -import io.cloudevents.sql.impl.ExceptionThrower; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.ExpressionInternalVisitor; import io.cloudevents.sql.impl.runtime.EvaluationContextImpl; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; import org.antlr.v4.runtime.misc.Interval; import java.util.List; @@ -26,18 +28,22 @@ public InExpression(Interval expressionInterval, String expressionText, Expressi } @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { - Object leftValue = leftExpression.evaluate(runtime, event, thrower); - return setExpressions.stream() - .anyMatch(expr -> { - Object rightValue = runtime.cast( - new EvaluationContextImpl(expressionInterval(), expressionText(), thrower), - expr.evaluate(runtime, event, thrower), - Type.fromValue(leftValue) - ); - - return Objects.equals(leftValue, rightValue); - }); + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationResult leftValue = leftExpression.evaluate(runtime, event, exceptionFactory); + for (ExpressionInternal setExpression : this.setExpressions) { + EvaluationResult rightValue = TypeCastingProvider.cast( + new EvaluationContextImpl(expressionInterval(), expressionText(), exceptionFactory), + setExpression.evaluate(runtime, event, exceptionFactory), + Type.fromValue(leftValue.value()) + ); + + if (Objects.equals(leftValue.value(), rightValue.value())) { + return new EvaluationResult(true, null, leftValue, rightValue); + } else { + leftValue.wrapExceptions(rightValue); + } + } + return leftValue.copyWithValue(false); } @Override diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/IntegerComparisonBinaryExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/IntegerComparisonBinaryExpression.java index bc1e2c818..81f36701b 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/IntegerComparisonBinaryExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/IntegerComparisonBinaryExpression.java @@ -1,8 +1,11 @@ package io.cloudevents.sql.impl.expressions; +import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.Type; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; import java.util.function.BiFunction; @@ -34,11 +37,21 @@ public IntegerComparisonBinaryExpression(Interval expressionInterval, String exp } @Override - public Object evaluate(EvaluationRuntime runtime, Object left, Object right, ExceptionThrower exceptions) { - return this.operation.evaluate( - castToInteger(runtime, exceptions, left), - castToInteger(runtime, exceptions, right) - ); + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationResult left = this.getLeftOperand().evaluate(runtime, event, exceptionFactory); + EvaluationResult right = this.getRightOperand().evaluate(runtime, event, exceptionFactory); + + if (left.isMissingAttributeException() || right.isMissingAttributeException()) { + return left.wrapExceptions(right).copyWithDefaultValueForType(Type.BOOLEAN); + } + + EvaluationResult x = castToInteger(exceptionFactory, left); + EvaluationResult y = castToInteger(exceptionFactory, right); + + return new EvaluationResult(this.operation.evaluate( + (Integer)x.value(), + (Integer)y.value() + )).wrapExceptions(x).wrapExceptions(y); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/LikeExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/LikeExpression.java index dd6366cd3..13b7a2605 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/LikeExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/LikeExpression.java @@ -2,9 +2,11 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.ExpressionInternalVisitor; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; import java.util.regex.Pattern; @@ -22,14 +24,13 @@ public LikeExpression(Interval expressionInterval, String expressionText, Expres } @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { - String value = castToString( - runtime, - thrower, - internal.evaluate(runtime, event, thrower) + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptionFactory) { + EvaluationResult result = castToString( + exceptionFactory, + internal.evaluate(runtime, event, exceptionFactory) ); - return pattern.matcher(value).matches(); + return result.copyWithValue(pattern.matcher((String) result.value()).matches()); } private Pattern convertLikePatternToRegex(String pattern) { diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/ModuleExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ModuleExpression.java index aa11a38c3..7ab422c06 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/ModuleExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ModuleExpression.java @@ -1,9 +1,9 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionFactory; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class ModuleExpression extends BaseIntegerBinaryExpression { @@ -13,14 +13,11 @@ public ModuleExpression(Interval expressionInterval, String expressionText, Expr } @Override - Object evaluate(EvaluationRuntime runtime, int left, int right, ExceptionThrower exceptions) { + EvaluationResult evaluate(EvaluationRuntime runtime, int left, int right, ExceptionFactory exceptionFactory) { if (right == 0) { - exceptions.throwException( - ExceptionFactory.divisionByZero(expressionInterval(), expressionText(), left) - ); - return 0; + return new EvaluationResult(0, exceptionFactory.divisionByZero(expressionInterval(), expressionText(), left)); } - return left % right; + return new EvaluationResult(left % right); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/MultiplicationExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/MultiplicationExpression.java index 1a6da19e6..f240b5f20 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/MultiplicationExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/MultiplicationExpression.java @@ -1,8 +1,10 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class MultiplicationExpression extends BaseIntegerBinaryExpression { @@ -12,8 +14,8 @@ public MultiplicationExpression(Interval expressionInterval, String expressionTe } @Override - Object evaluate(EvaluationRuntime runtime, int left, int right, ExceptionThrower exceptions) { - return left * right; + EvaluationResult evaluate(EvaluationRuntime runtime, int left, int right, ExceptionFactory exceptions) { + return new EvaluationResult(left * right); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/NegateExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/NegateExpression.java index c3e359f60..ecccb1ad2 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/NegateExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/NegateExpression.java @@ -1,8 +1,11 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class NegateExpression extends BaseUnaryExpression { @@ -12,7 +15,13 @@ public NegateExpression(Interval expressionInterval, String expressionText, Expr } @Override - public Object evaluate(EvaluationRuntime runtime, Object value, ExceptionThrower exceptions) { - return -castToInteger(runtime, exceptions, value); + public Type returnType() { + return Type.INTEGER; + } + + @Override + public EvaluationResult evaluate(EvaluationRuntime runtime, EvaluationResult result, ExceptionFactory exceptions) { + EvaluationResult x = castToInteger(exceptions, result); + return x.copyWithValue(-(Integer)x.value()); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/NotExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/NotExpression.java index 7b0f5966d..5e1d1ec8a 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/NotExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/NotExpression.java @@ -1,8 +1,10 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.Type; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class NotExpression extends BaseUnaryExpression { @@ -12,7 +14,13 @@ public NotExpression(Interval expressionInterval, String expressionText, Express } @Override - public Object evaluate(EvaluationRuntime runtime, Object value, ExceptionThrower exceptions) { - return !castToBoolean(runtime, exceptions, value); + public Type returnType() { + return Type.BOOLEAN; + } + + @Override + public EvaluationResult evaluate(EvaluationRuntime runtime, EvaluationResult value, ExceptionFactory exceptions) { + EvaluationResult x = castToBoolean(exceptions, value); + return x.copyWithValue(!(Boolean)x.value()); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/OrExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/OrExpression.java index 58b72405f..618b22275 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/OrExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/OrExpression.java @@ -1,8 +1,11 @@ package io.cloudevents.sql.impl.expressions; +import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class OrExpression extends BaseBinaryExpression { @@ -12,12 +15,15 @@ public OrExpression(Interval expressionInterval, String expressionText, Expressi } @Override - public Object evaluate(EvaluationRuntime runtime, Object left, Object right, ExceptionThrower exceptions) { - boolean x = castToBoolean(runtime, exceptions, left); - if (x) { + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptions) { + EvaluationResult left = this.getLeftOperand().evaluate(runtime, event, exceptions); + EvaluationResult x = castToBoolean(exceptions, left); + if ((Boolean)x.value()) { // Short circuit - return true; + return x; } - return castToBoolean(runtime, exceptions, right); + + EvaluationResult right = this.getRightOperand().evaluate(runtime, event, exceptions); + return castToBoolean(exceptions, right); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/SumExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/SumExpression.java index cafc6cb69..94c1a71a3 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/SumExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/SumExpression.java @@ -1,8 +1,10 @@ package io.cloudevents.sql.impl.expressions; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class SumExpression extends BaseIntegerBinaryExpression { @@ -12,8 +14,8 @@ public SumExpression(Interval expressionInterval, String expressionText, Express } @Override - Object evaluate(EvaluationRuntime runtime, int left, int right, ExceptionThrower exceptions) { - return left + right; + EvaluationResult evaluate(EvaluationRuntime runtime, int left, int right, ExceptionFactory exceptions) { + return new EvaluationResult(left + right); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/ValueExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ValueExpression.java index f5847a5fd..09c341ed7 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/ValueExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/ValueExpression.java @@ -2,9 +2,11 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternalVisitor; import io.cloudevents.sql.impl.parser.LiteralUtils; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.TerminalNode; @@ -18,8 +20,8 @@ public ValueExpression(Interval expressionInterval, String expressionText, Objec } @Override - public Object evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionThrower thrower) { - return value; + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory thrower) { + return new EvaluationResult(value); } @Override diff --git a/sql/src/main/java/io/cloudevents/sql/impl/expressions/XorExpression.java b/sql/src/main/java/io/cloudevents/sql/impl/expressions/XorExpression.java index 67089819c..dc240f181 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/expressions/XorExpression.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/expressions/XorExpression.java @@ -1,8 +1,10 @@ package io.cloudevents.sql.impl.expressions; +import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.impl.ExpressionInternal; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import org.antlr.v4.runtime.misc.Interval; public class XorExpression extends BaseBinaryExpression { @@ -12,10 +14,15 @@ public XorExpression(Interval expressionInterval, String expressionText, Express } @Override - public Object evaluate(EvaluationRuntime runtime, Object left, Object right, ExceptionThrower exceptions) { - return Boolean.logicalXor( - castToBoolean(runtime, exceptions, left), - castToBoolean(runtime, exceptions, right) - ); + public EvaluationResult evaluate(EvaluationRuntime runtime, CloudEvent event, ExceptionFactory exceptions) { + EvaluationResult left = this.getLeftOperand().evaluate(runtime, event, exceptions); + EvaluationResult right = this.getRightOperand().evaluate(runtime, event, exceptions); + + EvaluationResult x = castToBoolean(exceptions, left); + EvaluationResult y = castToBoolean(exceptions, right); + return new EvaluationResult(Boolean.logicalXor( + (Boolean)x.value(), + (Boolean)y.value() + )).wrapExceptions(x).wrapExceptions(y); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/AbsFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/AbsFunction.java new file mode 100644 index 000000000..52d404918 --- /dev/null +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/AbsFunction.java @@ -0,0 +1,20 @@ +package io.cloudevents.sql.impl.functions; + +import io.cloudevents.CloudEvent; +import io.cloudevents.sql.EvaluationContext; +import io.cloudevents.sql.EvaluationRuntime; +import io.cloudevents.sql.impl.runtime.EvaluationResult; + +public class AbsFunction extends BaseOneArgumentFunction { + public AbsFunction() { + super("ABS", Integer.class, Integer.class); + } + + @Override + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, Integer argument) { + if (argument == Integer.MIN_VALUE) { + return new EvaluationResult(Integer.MAX_VALUE, ctx.exceptionFactory().mathError(ctx.expressionInterval(), ctx.expressionText(), "integer overflow while computing absolute value of " + Integer.MIN_VALUE)); + } + return new EvaluationResult(Math.abs(argument)); + } +} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseOneArgumentFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseOneArgumentFunction.java index 0f18eb41e..5d4b1287a 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseOneArgumentFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseOneArgumentFunction.java @@ -4,26 +4,35 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import java.util.List; -public abstract class BaseOneArgumentFunction extends BaseFunction { +public abstract class BaseOneArgumentFunction extends BaseFunction { private final Type argumentClass; - public BaseOneArgumentFunction(String name, Class argumentClass) { + private final Type returnClass; + + public BaseOneArgumentFunction(String name, Class argumentClass, Class returnClass) { super(name); this.argumentClass = Type.fromClass(argumentClass); + this.returnClass = Type.fromClass(returnClass); } - abstract Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, T argument); + abstract EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, T argument); @SuppressWarnings("unchecked") @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { return this.invoke(ctx, evaluationRuntime, event, (T) arguments.get(0)); } + @Override + public Type returnType() { + return this.returnClass; + } + @Override public Type typeOfParameter(int i) { requireValidParameterIndex(i); diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseThreeArgumentFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseThreeArgumentFunction.java index 05f211072..1b40f1de3 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseThreeArgumentFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseThreeArgumentFunction.java @@ -4,28 +4,31 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import java.util.List; -public abstract class BaseThreeArgumentFunction extends BaseFunction { +public abstract class BaseThreeArgumentFunction extends BaseFunction { private final Type firstArg; private final Type secondArg; private final Type thirdArg; + private final Type returnType; - public BaseThreeArgumentFunction(String name, Class firstArg, Class secondArg, Class thirdArg) { + public BaseThreeArgumentFunction(String name, Class firstArg, Class secondArg, Class thirdArg, Class returnClass) { super(name); this.firstArg = Type.fromClass(firstArg); this.secondArg = Type.fromClass(secondArg); this.thirdArg = Type.fromClass(thirdArg); + this.returnType = Type.fromClass(returnClass); } - abstract Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, X x, Y y, Z z); + abstract EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, W w, X x, Y y); @SuppressWarnings("unchecked") @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { - return this.invoke(ctx, evaluationRuntime, event, (X) arguments.get(0), (Y) arguments.get(1), (Z) arguments.get(2)); + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { + return this.invoke(ctx, evaluationRuntime, event, (W) arguments.get(0), (X) arguments.get(1), (Y) arguments.get(2)); } @Override @@ -47,6 +50,11 @@ public int arity() { return 3; } + @Override + public Type returnType() { + return this.returnType; + } + @Override public boolean isVariadic() { return false; diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseTwoArgumentFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseTwoArgumentFunction.java index 23030ea1b..944f20c3d 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseTwoArgumentFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/BaseTwoArgumentFunction.java @@ -4,26 +4,28 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import java.util.List; -public abstract class BaseTwoArgumentFunction extends BaseFunction { +public abstract class BaseTwoArgumentFunction extends BaseFunction { private final Type firstArg; private final Type secondArg; + private final Type returnType; - public BaseTwoArgumentFunction(String name, Class firstArg, Class secondArg) { + public BaseTwoArgumentFunction(String name, Class firstArg, Class secondArg, Class returnClass) { super(name); this.firstArg = Type.fromClass(firstArg); this.secondArg = Type.fromClass(secondArg); - + this.returnType = Type.fromClass(returnClass); } - abstract Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, X x, Y y); + abstract EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, X x, Y y); @SuppressWarnings("unchecked") @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { return this.invoke(ctx, evaluationRuntime, event, (X) arguments.get(0), (Y) arguments.get(1)); } @@ -39,6 +41,11 @@ public Type typeOfParameter(int i) { throw new IllegalArgumentException(); // This should be already checked by requireValidParameterIndex } + @Override + public Type returnType() { + return this.returnType; + } + @Override public int arity() { return 2; diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/BoolFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/BoolFunction.java index aee15c763..898f0bdb9 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/BoolFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/BoolFunction.java @@ -4,15 +4,17 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; -public class BoolFunction extends BaseOneArgumentFunction { +public class BoolFunction extends BaseOneArgumentFunction { public BoolFunction() { - super("BOOL", String.class); + super("BOOL", Object.class, Boolean.class); } @Override - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String argument) { - return evaluationRuntime.cast(ctx, argument, Type.BOOLEAN); + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, Object argument) { + return TypeCastingProvider.cast(ctx, new EvaluationResult(argument), Type.BOOLEAN); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatFunction.java index 0070c57db..da180f7f9 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatFunction.java @@ -4,6 +4,7 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import java.util.List; import java.util.stream.Collectors; @@ -15,10 +16,10 @@ public ConcatFunction() { } @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { - return arguments.stream() + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { + return new EvaluationResult(arguments.stream() .map(o -> (String) o) - .collect(Collectors.joining()); + .collect(Collectors.joining())); } @Override @@ -35,4 +36,9 @@ public int arity() { public boolean isVariadic() { return true; } + + @Override + public Type returnType() { + return Type.STRING; + } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatWSFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatWSFunction.java index e1cc14435..f8768c0bc 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatWSFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/ConcatWSFunction.java @@ -4,6 +4,7 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import java.util.List; import java.util.stream.Collectors; @@ -15,11 +16,11 @@ public ConcatWSFunction() { } @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { - return arguments.stream() + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { + return new EvaluationResult(arguments.stream() .skip(1) .map(o -> (String) o) - .collect(Collectors.joining((String) arguments.get(0))); + .collect(Collectors.joining((String) arguments.get(0)))); } @Override @@ -36,4 +37,9 @@ public int arity() { public boolean isVariadic() { return true; } + + @Override + public Type returnType() { + return Type.STRING; + } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/InfallibleOneArgumentFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/InfallibleOneArgumentFunction.java index b5b6367b4..566e27a02 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/InfallibleOneArgumentFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/InfallibleOneArgumentFunction.java @@ -3,20 +3,21 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import java.util.function.Function; -public class InfallibleOneArgumentFunction extends BaseOneArgumentFunction { +public class InfallibleOneArgumentFunction extends BaseOneArgumentFunction { - private final Function functionImplementation; + private final Function functionImplementation; - public InfallibleOneArgumentFunction(String name, Class argumentClass, Function functionImplementation) { - super(name, argumentClass); + public InfallibleOneArgumentFunction(String name, Class argumentClass, Class returnClass, Function functionImplementation) { + super(name, argumentClass, returnClass); this.functionImplementation = functionImplementation; } @Override - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, T argument) { - return this.functionImplementation.apply(argument); + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, T argument) { + return new EvaluationResult(this.functionImplementation.apply(argument)); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/IntFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/IntFunction.java index 82028e69d..7b5e3e289 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/IntFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/IntFunction.java @@ -4,15 +4,17 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; -public class IntFunction extends BaseOneArgumentFunction { +public class IntFunction extends BaseOneArgumentFunction { public IntFunction() { - super("INT", String.class); + super("INT", Object.class, Integer.class); } @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String argument) { - return evaluationRuntime.cast(ctx, argument, Type.INTEGER); + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, Object argument) { + return TypeCastingProvider.cast(ctx, new EvaluationResult(argument), Type.INTEGER); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/IsBoolFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/IsBoolFunction.java index c6a3f1375..7761143b1 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/IsBoolFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/IsBoolFunction.java @@ -4,15 +4,17 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; -public class IsBoolFunction extends BaseOneArgumentFunction { +public class IsBoolFunction extends BaseOneArgumentFunction { public IsBoolFunction() { - super("IS_BOOL", String.class); + super("IS_BOOL", String.class, Boolean.class); } @Override - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String argument) { - return evaluationRuntime.canCast(argument, Type.BOOLEAN); + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String argument) { + return new EvaluationResult(TypeCastingProvider.canCast(argument, Type.BOOLEAN)); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/IsIntFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/IsIntFunction.java index f9c78597a..95785b2d8 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/IsIntFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/IsIntFunction.java @@ -4,15 +4,17 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; -public class IsIntFunction extends BaseOneArgumentFunction { +public class IsIntFunction extends BaseOneArgumentFunction { public IsIntFunction() { - super("IS_INT", String.class); + super("IS_INT", String.class, Boolean.class); } @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String argument) { - return evaluationRuntime.canCast(argument, Type.INTEGER); + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String argument) { + return new EvaluationResult(TypeCastingProvider.canCast(argument, Type.INTEGER)); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/LeftFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/LeftFunction.java index 563c6124b..d83dc8da2 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/LeftFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/LeftFunction.java @@ -3,24 +3,22 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionFactory; +import io.cloudevents.sql.impl.runtime.EvaluationResult; -public class LeftFunction extends BaseTwoArgumentFunction { +public class LeftFunction extends BaseTwoArgumentFunction { public LeftFunction() { - super("LEFT", String.class, Integer.class); + super("LEFT", String.class, Integer.class, String.class); } @Override - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String s, Integer length) { + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String s, Integer length) { if (length > s.length()) { - return s; + return new EvaluationResult(s); } if (length < 0) { - ctx.appendException( - ExceptionFactory.functionExecutionError(name(), new IllegalArgumentException("The length of the LEFT substring is lower than 0: " + length)) - ); - return s; + return new EvaluationResult(s, ctx.exceptionFactory().functionExecutionError(name(), new IllegalArgumentException("The length of the LEFT substring is lower than 0: " + length)).create(ctx.expressionInterval(), ctx.expressionText())); } - return s.substring(0, length); + + return new EvaluationResult(s.substring(0, length)); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/RightFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/RightFunction.java index b5817901c..47875ed70 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/RightFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/RightFunction.java @@ -3,24 +3,21 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionFactory; +import io.cloudevents.sql.impl.runtime.EvaluationResult; -public class RightFunction extends BaseTwoArgumentFunction { +public class RightFunction extends BaseTwoArgumentFunction { public RightFunction() { - super("RIGHT", String.class, Integer.class); + super("RIGHT", String.class, Integer.class, String.class); } @Override - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String s, Integer length) { + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String s, Integer length) { if (length > s.length()) { - return s; + return new EvaluationResult(s); } if (length < 0) { - ctx.appendException( - ExceptionFactory.functionExecutionError(name(), new IllegalArgumentException("The length of the RIGHT substring is lower than 0: " + length)) - ); - return s; + return new EvaluationResult(s, ctx.exceptionFactory().functionExecutionError(name(), new IllegalArgumentException("The length of the RIGHT substring is lower than 0: " + length)).create(ctx.expressionInterval(), ctx.expressionText())); } - return s.substring(s.length() - length); + return new EvaluationResult(s.substring(s.length() - length)); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/StringFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/StringFunction.java index a1d53db82..0bdc93607 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/StringFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/StringFunction.java @@ -4,15 +4,17 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Type; +import io.cloudevents.sql.impl.runtime.EvaluationResult; +import io.cloudevents.sql.impl.runtime.TypeCastingProvider; -public class StringFunction extends BaseOneArgumentFunction { +public class StringFunction extends BaseOneArgumentFunction { public StringFunction() { - super("STRING", Object.class); + super("STRING", Object.class, String.class); } @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, Object argument) { - return evaluationRuntime.cast(ctx, argument, Type.STRING); + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, Object argument) { + return TypeCastingProvider.cast(ctx, new EvaluationResult(argument), Type.STRING); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringFunction.java index 7123e1286..cb5db1568 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringFunction.java @@ -3,23 +3,19 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionFactory; +import io.cloudevents.sql.impl.runtime.EvaluationResult; -public class SubstringFunction extends BaseTwoArgumentFunction { +public class SubstringFunction extends BaseTwoArgumentFunction { public SubstringFunction() { - super("SUBSTRING", String.class, Integer.class); + super("SUBSTRING", String.class, Integer.class, String.class); } @Override - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String x, Integer pos) { + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String x, Integer pos) { try { - return SubstringWithLengthFunction.substring(x, pos, null); + return new EvaluationResult(SubstringWithLengthFunction.substring(x, pos, null)); } catch (Exception e) { - ctx.appendException(ExceptionFactory.functionExecutionError( - name(), - e - )); - return ""; + return new EvaluationResult("", ctx.exceptionFactory().functionExecutionError(name(), e).create(ctx.expressionInterval(), ctx.expressionText())); } } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringWithLengthFunction.java b/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringWithLengthFunction.java index 50767507a..b81ba1f8c 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringWithLengthFunction.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/functions/SubstringWithLengthFunction.java @@ -3,23 +3,19 @@ import io.cloudevents.CloudEvent; import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.impl.ExceptionFactory; +import io.cloudevents.sql.impl.runtime.EvaluationResult; -public class SubstringWithLengthFunction extends BaseThreeArgumentFunction { +public class SubstringWithLengthFunction extends BaseThreeArgumentFunction { public SubstringWithLengthFunction() { - super("SUBSTRING", String.class, Integer.class, Integer.class); + super("SUBSTRING", String.class, Integer.class, Integer.class, String.class); } @Override - Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String x, Integer pos, Integer len) { + EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, String x, Integer pos, Integer len) { try { - return substring(x, pos, len); + return new EvaluationResult(substring(x, pos, len)); } catch (Exception e) { - ctx.appendException(ExceptionFactory.functionExecutionError( - name(), - e - )); - return ""; + return new EvaluationResult("", ctx.exceptionFactory().functionExecutionError(name(), e).create(ctx.expressionInterval(), ctx.expressionText())); } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/parser/ConstantFoldingExpressionVisitor.java b/sql/src/main/java/io/cloudevents/sql/impl/parser/ConstantFoldingExpressionVisitor.java index 8c4f76de3..6cc1ae39f 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/parser/ConstantFoldingExpressionVisitor.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/parser/ConstantFoldingExpressionVisitor.java @@ -1,14 +1,12 @@ package io.cloudevents.sql.impl.parser; import io.cloudevents.SpecVersion; -import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.ExpressionInternalVisitor; import io.cloudevents.sql.impl.expressions.BaseBinaryExpression; import io.cloudevents.sql.impl.expressions.BaseUnaryExpression; import io.cloudevents.sql.impl.expressions.ExistsExpression; import io.cloudevents.sql.impl.expressions.ValueExpression; -import io.cloudevents.sql.impl.runtime.FailFastExceptionThrower; public class ConstantFoldingExpressionVisitor implements ExpressionInternalVisitor { @@ -22,20 +20,6 @@ public ExpressionInternal visitBaseBinaryExpression(BaseBinaryExpression baseBin ExpressionInternal left = baseBinaryExpression.getLeftOperand().visit(this); ExpressionInternal right = baseBinaryExpression.getRightOperand().visit(this); - if (left instanceof ValueExpression && right instanceof ValueExpression) { - // I can do constant folding! - return new ValueExpression( - baseBinaryExpression.expressionInterval(), - baseBinaryExpression.expressionText(), - baseBinaryExpression.evaluate( - EvaluationRuntime.getDefault(), - ((ValueExpression) left).getValue(), - ((ValueExpression) right).getValue(), - FailFastExceptionThrower.getInstance() - ) - ); - } - baseBinaryExpression.setLeftOperand(left); baseBinaryExpression.setRightOperand(right); return baseBinaryExpression; @@ -54,14 +38,6 @@ public ExpressionInternal visitExistsExpression(ExistsExpression existsExpressio public ExpressionInternal visitBaseUnaryExpression(BaseUnaryExpression baseUnaryExpression) { ExpressionInternal inner = baseUnaryExpression.getOperand().visit(this); - if (inner instanceof ValueExpression) { - return new ValueExpression( - baseUnaryExpression.expressionInterval(), - baseUnaryExpression.expressionText(), - baseUnaryExpression.evaluate(EvaluationRuntime.getDefault(), ((ValueExpression) inner).getValue(), FailFastExceptionThrower.getInstance()) - ); - } - baseUnaryExpression.setOperand(inner); return baseUnaryExpression; } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/parser/ExpressionTranslatorVisitor.java b/sql/src/main/java/io/cloudevents/sql/impl/parser/ExpressionTranslatorVisitor.java index 8ce88259f..86cb4f7c7 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/parser/ExpressionTranslatorVisitor.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/parser/ExpressionTranslatorVisitor.java @@ -1,9 +1,9 @@ package io.cloudevents.sql.impl.parser; +import io.cloudevents.sql.ExceptionFactory; import io.cloudevents.sql.Type; import io.cloudevents.sql.generated.CESQLParserBaseVisitor; import io.cloudevents.sql.generated.CESQLParserParser; -import io.cloudevents.sql.impl.ExceptionFactory; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.expressions.*; @@ -121,11 +121,11 @@ public ExpressionInternal visitBinaryComparisonExpression(CESQLParserParser.Bina if (ctx.EQUAL() != null) { // Equality operation is ambiguous, we have a specific implementation for it - return new EqualExpression(ctx.getSourceInterval(), ctx.getText(), leftExpression, rightExpression); + return new ComparisonExpression(ctx.getSourceInterval(), ctx.getText(), leftExpression, rightExpression, ComparisonExpression.Comparison.EQUALS); } if (ctx.NOT_EQUAL() != null || ctx.LESS_GREATER() != null) { // Equality operation is ambiguous, we have a specific implementation for it - return new NotExpression(ctx.getSourceInterval(), ctx.getText(), new EqualExpression(ctx.getSourceInterval(), ctx.getText(), leftExpression, rightExpression)); + return new ComparisonExpression(ctx.getSourceInterval(), ctx.getText(), leftExpression, rightExpression, ComparisonExpression.Comparison.NOT_EQUALS); } // From this onward, just operators defined on integers diff --git a/sql/src/main/java/io/cloudevents/sql/impl/parser/ParserImpl.java b/sql/src/main/java/io/cloudevents/sql/impl/parser/ParserImpl.java index c0f042358..08d4a67be 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/parser/ParserImpl.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/parser/ParserImpl.java @@ -1,12 +1,9 @@ package io.cloudevents.sql.impl.parser; -import io.cloudevents.sql.EvaluationException; -import io.cloudevents.sql.Expression; -import io.cloudevents.sql.ParseException; +import io.cloudevents.sql.*; import io.cloudevents.sql.Parser; import io.cloudevents.sql.generated.CESQLParserLexer; import io.cloudevents.sql.generated.CESQLParserParser; -import io.cloudevents.sql.impl.ExceptionFactory; import io.cloudevents.sql.impl.ExpressionInternal; import io.cloudevents.sql.impl.runtime.ExpressionImpl; import org.antlr.v4.runtime.*; diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationContextImpl.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationContextImpl.java index 1125079a5..fa60dce79 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationContextImpl.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationContextImpl.java @@ -2,19 +2,20 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.EvaluationException; -import io.cloudevents.sql.impl.ExceptionThrower; +import io.cloudevents.sql.ExceptionFactory; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import org.antlr.v4.runtime.misc.Interval; public class EvaluationContextImpl implements EvaluationContext { private final Interval expressionInterval; private final String expressionText; - private final ExceptionThrower exceptionThrower; + private final ExceptionFactory exceptionFactory; - public EvaluationContextImpl(Interval expressionInterval, String expressionText, ExceptionThrower exceptionThrower) { + public EvaluationContextImpl(Interval expressionInterval, String expressionText, ExceptionFactory exceptionFactory) { this.expressionInterval = expressionInterval; this.expressionText = expressionText; - this.exceptionThrower = exceptionThrower; + this.exceptionFactory = exceptionFactory; } @Override @@ -28,12 +29,7 @@ public String expressionText() { } @Override - public void appendException(EvaluationException exception) { - this.exceptionThrower.throwException(exception); - } - - @Override - public void appendException(EvaluationException.EvaluationExceptionFactory exceptionFactory) { - this.exceptionThrower.throwException(exceptionFactory.create(expressionInterval(), expressionText())); + public ExceptionFactory exceptionFactory() { + return this.exceptionFactory; } } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationResult.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationResult.java index d67ac1e18..d5392dc23 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationResult.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationResult.java @@ -2,20 +2,79 @@ import io.cloudevents.sql.EvaluationException; import io.cloudevents.sql.Result; +import io.cloudevents.sql.Type; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class EvaluationResult implements Result { private final Object value; private final List exceptions; + private final EvaluationException latestException; public EvaluationResult(Object value, List exceptions) { this.value = value; - this.exceptions = exceptions == null ? Collections.emptyList() : Collections.unmodifiableList(exceptions); + this.exceptions = exceptions == null ? new ArrayList<>() : exceptions; + this.latestException = null; + } + + public EvaluationResult(Object value, EvaluationException exception, EvaluationResult left, EvaluationResult right) { + this.exceptions = Stream.concat(Stream.of(left, right).filter(Objects::nonNull).map(r -> r.exceptions).flatMap(Collection::stream), Stream.of(exception)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + this.latestException = exception; + this.value = value; + } + + public EvaluationResult(Object value, EvaluationException exception) { + this(value, exception, null, null); + } + + public EvaluationResult(Object value) { + this.value = value; + this.exceptions = new ArrayList<>(); + this.latestException = null; + } + + public EvaluationResult wrapExceptions(EvaluationResult other) { + if (other != null && other.exceptions != null) { + return this.wrapExceptions(other.exceptions); + } + return this; + } + + public EvaluationResult wrapExceptions(List exceptions) { + if (!exceptions.isEmpty()) { + this.exceptions.addAll(exceptions); + } + return this; + } + + public EvaluationResult copyWithValue(Object value) { + return new EvaluationResult(value, this.exceptions); + } + + public EvaluationResult copyWithDefaultValueForType(Type type) { + Object value; + switch (type) { + case STRING: + value = ""; + break; + case INTEGER: + value = 0; + break; + default: + value = false; + break; + } + return new EvaluationResult(value, this.exceptions); + } + + // returns true is the most recent exception was a MISSING attribute exception + public boolean isMissingAttributeException() { + return (this.latestException != null && this.latestException.getKind() == EvaluationException.ErrorKind.MISSING_ATTRIBUTE); } /** diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeBuilder.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeBuilder.java index e12ed6ce1..bdfd8e37c 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeBuilder.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeBuilder.java @@ -18,7 +18,6 @@ public EvaluationRuntimeBuilder addFunction(Function function) throws IllegalArg public EvaluationRuntime build() { return new EvaluationRuntimeImpl( - new TypeCastingProvider(), functionTable ); } diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImpl.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImpl.java index 67538f732..4f2d951a5 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImpl.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImpl.java @@ -5,7 +5,7 @@ public class EvaluationRuntimeImpl implements EvaluationRuntime { private static class SingletonContainer { - private final static EvaluationRuntimeImpl INSTANCE = new EvaluationRuntimeImpl(new TypeCastingProvider(), FunctionTable.getDefaultInstance()); + private final static EvaluationRuntimeImpl INSTANCE = new EvaluationRuntimeImpl(FunctionTable.getDefaultInstance()); } /** @@ -15,29 +15,12 @@ public static EvaluationRuntime getInstance() { return EvaluationRuntimeImpl.SingletonContainer.INSTANCE; } - private final TypeCastingProvider typeCastingProvider; private final FunctionTable functionTable; - public EvaluationRuntimeImpl(TypeCastingProvider typeCastingProvider, FunctionTable functionTable) { - this.typeCastingProvider = typeCastingProvider; + public EvaluationRuntimeImpl(FunctionTable functionTable) { this.functionTable = functionTable; } - @Override - public boolean canCast(Object value, Type target) { - return this.typeCastingProvider.canCast(value, target); - } - - @Override - public Object cast(EvaluationContext ctx, Object value, Type target) { - return this.typeCastingProvider.cast(ctx, value, target); - } - - @Override - public Object cast(Object value, Type target) throws EvaluationException { - return this.typeCastingProvider.cast(FailFastExceptionThrower.getInstance(), value, target); - } - @Override public Function resolveFunction(String name, int args) throws IllegalStateException { return functionTable.resolve(name, args); diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/ExceptionStore.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/ExceptionStore.java deleted file mode 100644 index 9bb969531..000000000 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/ExceptionStore.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.cloudevents.sql.impl.runtime; - -import io.cloudevents.sql.EvaluationException; -import io.cloudevents.sql.impl.ExceptionThrower; - -import java.util.ArrayList; -import java.util.List; - -class ExceptionStore implements ExceptionThrower { - - private List exceptions; - - ExceptionStore() { - } - - @Override - public void throwException(EvaluationException exception) { - if (this.exceptions == null) { - this.exceptions = new ArrayList<>(); - } - this.exceptions.add(exception); - } - - List getExceptions() { - return exceptions; - } -} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/ExpressionImpl.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/ExpressionImpl.java index 0206617da..67b8fb373 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/ExpressionImpl.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/runtime/ExpressionImpl.java @@ -5,6 +5,7 @@ import io.cloudevents.sql.EvaluationRuntime; import io.cloudevents.sql.Expression; import io.cloudevents.sql.Result; +import io.cloudevents.sql.impl.ExceptionFactoryImpl; import io.cloudevents.sql.impl.ExpressionInternal; public class ExpressionImpl implements Expression { @@ -17,14 +18,15 @@ public ExpressionImpl(ExpressionInternal expressionInternal) { @Override public Result evaluate(EvaluationRuntime evaluationRuntime, CloudEvent event) { - ExceptionStore exceptions = new ExceptionStore(); - Object value = this.expressionInternal.evaluate(evaluationRuntime, event, exceptions); - return new EvaluationResult(value, exceptions.getExceptions()); + ExceptionFactoryImpl exceptionFactory = new ExceptionFactoryImpl(false); + return this.expressionInternal.evaluate(evaluationRuntime, event, exceptionFactory); + } @Override public Object tryEvaluate(EvaluationRuntime evaluationRuntime, CloudEvent event) throws EvaluationException { - return this.expressionInternal.evaluate(evaluationRuntime, event, FailFastExceptionThrower.getInstance()); + ExceptionFactoryImpl exceptionFactory = new ExceptionFactoryImpl(true); + return this.expressionInternal.evaluate(evaluationRuntime, event, exceptionFactory).value(); } public ExpressionInternal getExpressionInternal() { diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/FailFastExceptionThrower.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/FailFastExceptionThrower.java deleted file mode 100644 index fdc2e1517..000000000 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/FailFastExceptionThrower.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.cloudevents.sql.impl.runtime; - -import io.cloudevents.sql.EvaluationContext; -import io.cloudevents.sql.EvaluationException; -import io.cloudevents.sql.impl.ExceptionThrower; -import org.antlr.v4.runtime.misc.Interval; - -public class FailFastExceptionThrower implements ExceptionThrower, EvaluationContext { - - private static class SingletonContainer { - private final static FailFastExceptionThrower INSTANCE = new FailFastExceptionThrower(); - } - - public static FailFastExceptionThrower getInstance() { - return FailFastExceptionThrower.SingletonContainer.INSTANCE; - } - - @Override - public void throwException(EvaluationException exception) { - throw exception; - } - - @Override - public Interval expressionInterval() { - return Interval.INVALID; - } - - @Override - public String expressionText() { - return ""; - } - - @Override - public void appendException(EvaluationException exception) { - throwException(exception); - } - - @Override - public void appendException(EvaluationException.EvaluationExceptionFactory exceptionFactory) { - throwException(exceptionFactory.create(expressionInterval(), expressionText())); - } -} diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/FunctionTable.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/FunctionTable.java index b74de42f7..b456f6c81 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/FunctionTable.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/runtime/FunctionTable.java @@ -12,18 +12,18 @@ public class FunctionTable { private static class SingletonContainer { private final static FunctionTable INSTANCE = new FunctionTable( Stream.of( - new InfallibleOneArgumentFunction<>("ABS", Integer.class, Math::abs), + new AbsFunction(), new IntFunction(), new BoolFunction(), new StringFunction(), new IsBoolFunction(), new IsIntFunction(), - new InfallibleOneArgumentFunction<>("LENGTH", String.class, String::length), + new InfallibleOneArgumentFunction<>("LENGTH", String.class, Integer.class, String::length), new ConcatFunction(), new ConcatWSFunction(), - new InfallibleOneArgumentFunction<>("LOWER", String.class, String::toLowerCase), - new InfallibleOneArgumentFunction<>("UPPER", String.class, String::toUpperCase), - new InfallibleOneArgumentFunction<>("TRIM", String.class, String::trim), + new InfallibleOneArgumentFunction<>("LOWER", String.class, String.class, String::toLowerCase), + new InfallibleOneArgumentFunction<>("UPPER", String.class, String.class, String::toUpperCase), + new InfallibleOneArgumentFunction<>("TRIM", String.class, String.class, String::trim), new LeftFunction(), new RightFunction(), new SubstringFunction(), diff --git a/sql/src/main/java/io/cloudevents/sql/impl/runtime/TypeCastingProvider.java b/sql/src/main/java/io/cloudevents/sql/impl/runtime/TypeCastingProvider.java index ddfc6c468..418517cee 100644 --- a/sql/src/main/java/io/cloudevents/sql/impl/runtime/TypeCastingProvider.java +++ b/sql/src/main/java/io/cloudevents/sql/impl/runtime/TypeCastingProvider.java @@ -2,13 +2,12 @@ import io.cloudevents.sql.EvaluationContext; import io.cloudevents.sql.Type; -import io.cloudevents.sql.impl.ExceptionFactory; import java.util.Objects; public class TypeCastingProvider { - boolean canCast(Object value, Type target) { + public static boolean canCast(Object value, Type target) { if (target.valueClass().equals(value.getClass())) { return true; } @@ -22,7 +21,7 @@ boolean canCast(Object value, Type target) { return false; } } - return false; + return value instanceof Boolean; case BOOLEAN: if (value instanceof String) { try { @@ -31,58 +30,55 @@ boolean canCast(Object value, Type target) { } catch (IllegalArgumentException e) { return false; } - } - return false; + } else return value instanceof Integer; } return true; } - Object cast(EvaluationContext ctx, Object value, Type target) { - Objects.requireNonNull(value); - if (target.valueClass().equals(value.getClass())) { - return value; + public static EvaluationResult cast(EvaluationContext ctx, EvaluationResult result, Type target) { + Objects.requireNonNull(result); + Objects.requireNonNull(result.value()); + if (target.valueClass().equals(result.value().getClass())) { + return result; } switch (target) { case ANY: - return value; + return result; case STRING: - return Objects.toString(value); + return result.copyWithValue(Objects.toString(result.value())); case INTEGER: - if (value instanceof String) { + if (result.value() instanceof String) { try { - return Integer.parseInt((String) value); + return result.copyWithValue(Integer.parseInt((String) result.value())); } catch (NumberFormatException e) { - ctx.appendException( - ExceptionFactory.castError(String.class, Integer.class, e) - ); + return new EvaluationResult(0, ctx.exceptionFactory().castError(String.class, Integer.class, e).create(ctx.expressionInterval(), ctx.expressionText())); + } + } else if (result.value() instanceof Boolean) { + if ((Boolean) result.value()) { + return result.copyWithValue(1); } + return result.copyWithValue(0); } else { - ctx.appendException( - ExceptionFactory.invalidCastTarget(value.getClass(), target.valueClass()) - ); + return new EvaluationResult(0, ctx.exceptionFactory().invalidCastTarget(result.getClass(), target.valueClass()).create(ctx.expressionInterval(), ctx.expressionText())); } - return 0; case BOOLEAN: - if (value instanceof String) { + if (result.value() instanceof String) { try { - return parseBool((String) value); + return result.copyWithValue(parseBool((String) result.value())); } catch (IllegalArgumentException e) { - ctx.appendException( - ExceptionFactory.castError(String.class, Boolean.class, e) - ); + return new EvaluationResult(false, ctx.exceptionFactory().castError(String.class, Boolean.class, e).create(ctx.expressionInterval(), ctx.expressionText())); } + } else if (result.value() instanceof Integer) { + return result.copyWithValue(((Integer) result.value()) != 0); } else { - ctx.appendException( - ExceptionFactory.invalidCastTarget(value.getClass(), target.valueClass()) - ); + return new EvaluationResult(false, ctx.exceptionFactory().invalidCastTarget(result.getClass(), target.getClass()).create(ctx.expressionInterval(), ctx.expressionText())); } - return false; } // This should never happen throw new IllegalArgumentException("target type doesn't correspond to a known type"); } - private boolean parseBool(String val) { + private static boolean parseBool(String val) { switch (val.toLowerCase()) { case "true": return true; diff --git a/sql/src/test/java/io/cloudevents/sql/CustomFunctionsTest.java b/sql/src/test/java/io/cloudevents/sql/CustomFunctionsTest.java index 9e91b4890..e77caad01 100644 --- a/sql/src/test/java/io/cloudevents/sql/CustomFunctionsTest.java +++ b/sql/src/test/java/io/cloudevents/sql/CustomFunctionsTest.java @@ -5,6 +5,7 @@ import io.cloudevents.core.test.Data; import io.cloudevents.sql.impl.functions.BaseFunction; import io.cloudevents.sql.impl.functions.InfallibleOneArgumentFunction; +import io.cloudevents.sql.impl.runtime.EvaluationResult; import io.cloudevents.sql.impl.runtime.EvaluationRuntimeBuilder; import org.junit.jupiter.api.Test; @@ -21,6 +22,7 @@ void addSimpleFunction() { .addFunction(new InfallibleOneArgumentFunction<>( "MY_STRING_PREDICATE", String.class, + Boolean.class, s -> s.length() % 2 == 0 )) .build(); @@ -37,12 +39,12 @@ void addSimpleFunction() { Parser.parseDefault("MY_STRING_PREDICATE('abc', 'xyz')") .evaluate(runtime, Data.V1_MIN) ) - .hasFailure(EvaluationException.ErrorKind.FUNCTION_DISPATCH); + .hasFailure(EvaluationException.ErrorKind.MISSING_FUNCTION); assertThat( Parser.parseDefault("MY_STRING_PR('abc', 'xyz')") .evaluate(runtime, Data.V1_MIN) ) - .hasFailure(EvaluationException.ErrorKind.FUNCTION_DISPATCH); + .hasFailure(EvaluationException.ErrorKind.MISSING_FUNCTION); } @Test @@ -55,7 +57,7 @@ void addVariadicFunction() { Parser.parseDefault("MY_STRING_FN('abc')") .evaluate(runtime, Data.V1_MIN) ) - .hasFailure(EvaluationException.ErrorKind.FUNCTION_DISPATCH); + .hasFailure(EvaluationException.ErrorKind.MISSING_FUNCTION); assertThat( Parser.parseDefault("MY_STRING_FN('abc', 'b')") .evaluate(runtime, Data.V1_MIN) @@ -85,6 +87,7 @@ void addSimpleFunctionAndVariadicFunction() { .addFunction(new InfallibleOneArgumentFunction<>( "MY_STRING_FN", String.class, + Boolean.class, s -> s.length() % 2 == 0 )) .addFunction(new VariadicMockFunction("MY_STRING_FN", 2, Type.STRING)) @@ -126,6 +129,7 @@ void cannotAddVariadicWithFixedArgsLowerThanMaxArgsOverload() { .addFunction(new InfallibleOneArgumentFunction<>( "MY_STRING_FN", String.class, + Boolean.class, s -> s.length() % 2 == 0 )); @@ -153,6 +157,7 @@ void addSimpleFunctionFails() { .addFunction(new InfallibleOneArgumentFunction<>( "MY_STRING_FN", String.class, + Boolean.class, s -> s.length() % 2 == 0 )); @@ -160,6 +165,7 @@ void addSimpleFunctionFails() { new InfallibleOneArgumentFunction<>( "MY_STRING_FN", String.class, + Boolean.class, s -> s.length() % 2 == 0 ) )).isInstanceOf(IllegalArgumentException.class); @@ -167,6 +173,7 @@ void addSimpleFunctionFails() { new InfallibleOneArgumentFunction<>( "MY_STRING_FN", Integer.class, + Boolean.class, s -> s % 2 == 0 ) )).isInstanceOf(IllegalArgumentException.class); @@ -178,6 +185,7 @@ void customFunctionSpecTest() { .addFunction(new InfallibleOneArgumentFunction<>( "MY_STRING_PREDICATE", String.class, + Boolean.class, s -> s.length() % 2 == 0 )) .build(); @@ -211,8 +219,8 @@ private VariadicMockFunction(String name, int fixedArgs, Type argsType) { } @Override - public Object invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { - return arguments.size(); + public EvaluationResult invoke(EvaluationContext ctx, EvaluationRuntime evaluationRuntime, CloudEvent event, List arguments) { + return new EvaluationResult(arguments.size()); } @Override @@ -229,6 +237,11 @@ public int arity() { public boolean isVariadic() { return true; } + + @Override + public Type returnType() { + return Type.INTEGER; + } } } diff --git a/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java b/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java index d48b7a3c4..3a5a16b89 100644 --- a/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java +++ b/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java @@ -78,15 +78,15 @@ public CloudEvent getTestInputEvent() { public EvaluationException.ErrorKind getEvaluationExceptionErrorKind() { switch (this.error) { case CAST: - return EvaluationException.ErrorKind.INVALID_CAST; + return EvaluationException.ErrorKind.CAST; case MATH: return EvaluationException.ErrorKind.MATH; case MISSING_FUNCTION: - return EvaluationException.ErrorKind.FUNCTION_DISPATCH; + return EvaluationException.ErrorKind.MISSING_FUNCTION; case MISSING_ATTRIBUTE: return EvaluationException.ErrorKind.MISSING_ATTRIBUTE; case FUNCTION_EVALUATION: - return EvaluationException.ErrorKind.FUNCTION_EXECUTION; + return EvaluationException.ErrorKind.FUNCTION_EVALUATION; } return null; } @@ -124,8 +124,7 @@ public Stream> tckTestCases() { try { return mapper.readValue(this.getClass().getResource(fileName), TestSuiteModel.class); } catch (IOException e) { - e.printStackTrace(); - return null; + throw new RuntimeException(fileName, e); } }) .filter(Objects::nonNull) diff --git a/sql/src/test/java/io/cloudevents/sql/impl/parser/ConstantFoldingTest.java b/sql/src/test/java/io/cloudevents/sql/impl/parser/ConstantFoldingTest.java index f219ce5b6..730f5189b 100644 --- a/sql/src/test/java/io/cloudevents/sql/impl/parser/ConstantFoldingTest.java +++ b/sql/src/test/java/io/cloudevents/sql/impl/parser/ConstantFoldingTest.java @@ -12,20 +12,6 @@ import static org.assertj.core.api.Assertions.assertThat; public class ConstantFoldingTest { - - @Test - void withBinaryExpression() { - Expression expression = Parser.getDefault().parse("1 + 2"); - assertThat(expression) - .isInstanceOf(ExpressionImpl.class); - - ExpressionInternal internal = ((ExpressionImpl) expression).getExpressionInternal(); - assertThat(internal) - .isInstanceOf(ValueExpression.class) - .extracting(v -> ((ValueExpression) v).getValue()) - .isEqualTo(3); - } - @Test void withUnaryExpression() { Expression expression = Parser.getDefault().parse("-1"); diff --git a/sql/src/test/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImplTest.java b/sql/src/test/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImplTest.java deleted file mode 100644 index b258c87a4..000000000 --- a/sql/src/test/java/io/cloudevents/sql/impl/runtime/EvaluationRuntimeImplTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.cloudevents.sql.impl.runtime; - -import io.cloudevents.sql.EvaluationException; -import io.cloudevents.sql.EvaluationRuntime; -import io.cloudevents.sql.Type; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -public class EvaluationRuntimeImplTest { - - @Test - void castingFails() { - assertThatCode(() -> EvaluationRuntime.getDefault().cast("123", Type.BOOLEAN)) - .isInstanceOf(EvaluationException.class); - } - - @Test - void castingSucceeds() { - assertThat(EvaluationRuntime.getDefault().cast("TRUE", Type.BOOLEAN)) - .isEqualTo(true); - } - -} diff --git a/sql/src/test/resources/tck/README.md b/sql/src/test/resources/tck/README.md index 5852abcfe..64b0cafa5 100644 --- a/sql/src/test/resources/tck/README.md +++ b/sql/src/test/resources/tck/README.md @@ -23,6 +23,7 @@ The `error` values could be any of the following: * `parse`: Error while parsing the expression * `math`: Math error while evaluating a math operator * `cast`: Casting error -* `missingAttribute`: Addressed a missing attribute * `missingFunction`: Addressed a missing function * `functionEvaluation`: Error while evaluating a function +* `missingAttribute`: Error due to a missing attribute +* `generic`: A generic error diff --git a/sql/src/test/resources/tck/binary_comparison_operators.yaml b/sql/src/test/resources/tck/binary_comparison_operators.yaml index 94a3071bb..f1f70f842 100644 --- a/sql/src/test/resources/tck/binary_comparison_operators.yaml +++ b/sql/src/test/resources/tck/binary_comparison_operators.yaml @@ -18,6 +18,10 @@ tests: - name: abc is equal to abc expression: "'abc' = 'abc'" result: true + - name: Equals operator returns false when encountering a missing attribute + expression: missing = 2 + result: false + error: missingAttribute - name: True is not equal to false expression: TRUE != FALSE @@ -37,6 +41,10 @@ tests: - name: abc is not equal to abc expression: "'abc' != 'abc'" result: false + - name: Not equal operator returns false when encountering a missing attribute + expression: missing != 2 + result: false + error: missingAttribute - name: True is not equal to false (diamond operator) expression: TRUE <> FALSE @@ -56,6 +64,10 @@ tests: - name: abc is not equal to abc (diamond operator) expression: "'abc' <> 'abc'" result: false + - name: Diamond operator returns false when encountering a missing attribute + expression: missing <> 2 + result: false + error: missingAttribute - name: 1 is less or equal than 2 expression: 2 <= 2 @@ -81,6 +93,10 @@ tests: - name: 2 is greater than 2 expression: 2 > 2 result: false + - name: Less than or equal operator returns false when encountering a missing attribute + expression: missing <= 2 + result: false + error: missingAttribute - name: implicit casting with string as right type expression: "true = 'TRUE'" diff --git a/sql/src/test/resources/tck/binary_logical_operators.yaml b/sql/src/test/resources/tck/binary_logical_operators.yaml index 9b5d4ec96..bafc8e498 100644 --- a/sql/src/test/resources/tck/binary_logical_operators.yaml +++ b/sql/src/test/resources/tck/binary_logical_operators.yaml @@ -12,6 +12,13 @@ tests: - name: True and true expression: TRUE AND TRUE result: true + - name: AND operator is short circuit evaluated + expression: "false and (1 != 1 / 0)" + result: false + - name: AND operator is NOT short circuit evaluated when the first operand evaluates to true + expression: "true and (1 = 1 / 0)" + error: math + result: false - name: False or false expression: FALSE OR FALSE @@ -25,6 +32,13 @@ tests: - name: True or true expression: TRUE OR TRUE result: true + - name: OR operator is short circuit evaluated + expression: "true or (1 != 1 / 0)" + result: true + - name: OR operator is NOT short circuit evaluated when the first operand evaluates to false + expression: "false or (1 = 1 / 0)" + error: math + result: false - name: False xor false expression: FALSE XOR FALSE diff --git a/sql/src/test/resources/tck/binary_math_operators.yaml b/sql/src/test/resources/tck/binary_math_operators.yaml index 57753288c..d1d0417ee 100644 --- a/sql/src/test/resources/tck/binary_math_operators.yaml +++ b/sql/src/test/resources/tck/binary_math_operators.yaml @@ -21,6 +21,14 @@ tests: expression: 5 % 0 result: 0 error: math + - name: Missing attribute in division results in missing attribute error, not divide by 0 error + expression: missing / 0 + result: 0 + error: missingAttribute + - name: Missing attribute in modulo results in missing attribute error, not divide by 0 error + expression: missing % 0 + result: 0 + error: missingAttribute - name: Positive plus positive number expression: 4 + 1 @@ -50,11 +58,6 @@ tests: - name: Implicit casting, with both values string expression: "'5' + '3'" result: 8 - - name: Implicit casting, with invalid boolean value + - name: Implicit casting, with boolean value expression: "5 + TRUE" - result: 5 - error: cast - - name: Implicit casting, with invalid string value - expression: "'5avc4' + 10" - result: 10 - error: cast + result: 6 diff --git a/sql/src/test/resources/tck/casting_functions.yaml b/sql/src/test/resources/tck/casting_functions.yaml index f5be8ac66..59349b89f 100644 --- a/sql/src/test/resources/tck/casting_functions.yaml +++ b/sql/src/test/resources/tck/casting_functions.yaml @@ -12,10 +12,12 @@ tests: - name: Cast identity -1 expression: INT(-1) result: -1 - - name: Invalid cast from boolean to int + - name: Cast from TRUE to int expression: INT(TRUE) + result: 1 + - name: Cast from FALSE to int + expression: INT(FALSE) result: 0 - error: cast - name: Invalid cast from string to int expression: INT('ABC') result: 0 @@ -37,10 +39,18 @@ tests: expression: BOOL('ABC') result: false error: cast - - name: Invalid cast from int to boolean + - name: Cast from 1 to boolean expression: BOOL(1) + result: true + - name: Cast from 0 to boolean + expression: BOOL(0) result: false - error: cast + - name: Cast from 100 to boolean + expression: BOOL(100) + result: true + - name: Cast from -50 to boolean + expression: BOOL(-50) + result: true - name: Cast TRUE to string expression: STRING(TRUE) @@ -57,33 +67,3 @@ tests: - name: Cast identity "abc" expression: STRING("abc") result: "abc" - - - name: "'true' is a boolean" - expression: IS_BOOL('true') - result: true - - name: "'FALSE' is a boolean" - expression: IS_BOOL('FALSE') - result: true - - name: 1 is not a boolean - expression: IS_BOOL(1) - result: false - - name: "'abc' is not a boolean" - expression: IS_BOOL('abc') - result: false - - - name: "'-1' is an int" - expression: IS_INT('-1') - result: true - - name: "'1' is an int" - expression: IS_INT('1') - result: true - - name: true is not an int - expression: IS_INT(TRUE) - result: false - - name: "'abc' is not an int" - expression: IS_INT('abc') - result: false - - - name: IS_STRING does not exists - expression: IS_STRING('ABC') - error: missingFunction diff --git a/sql/src/test/resources/tck/context_attributes_access.yaml b/sql/src/test/resources/tck/context_attributes_access.yaml index 990a771e0..f3e46622d 100644 --- a/sql/src/test/resources/tck/context_attributes_access.yaml +++ b/sql/src/test/resources/tck/context_attributes_access.yaml @@ -17,7 +17,7 @@ tests: id: myId source: localhost.localdomain type: myType - result: "" + result: false error: missingAttribute - name: Access to optional boolean extension expression: mybool diff --git a/sql/src/test/resources/tck/integer_builtin_functions.yaml b/sql/src/test/resources/tck/integer_builtin_functions.yaml index 988a201f6..3da97c562 100644 --- a/sql/src/test/resources/tck/integer_builtin_functions.yaml +++ b/sql/src/test/resources/tck/integer_builtin_functions.yaml @@ -9,3 +9,8 @@ tests: - name: ABS (3) expression: ABS(0) result: 0 + - name: ABS overflow + expression: ABS(-2147483648) + result: 2147483647 + error: math + diff --git a/sql/src/test/resources/tck/like_expression.yaml b/sql/src/test/resources/tck/like_expression.yaml index b6bc5a18b..d44d46705 100644 --- a/sql/src/test/resources/tck/like_expression.yaml +++ b/sql/src/test/resources/tck/like_expression.yaml @@ -116,3 +116,14 @@ tests: - name: With type coercion from bool (4) expression: "FALSE LIKE 'fal%'" result: true + + - name: Invalid string literal in comparison causes parse error + expression: "x LIKE 123" + result: false + error: parse + eventOverrides: + x: "123" + - name: Missing attribute returns empty string + expression: "missing LIKE 'missing'" + result: false + error: missingAttribute diff --git a/sql/src/test/resources/tck/negate_operator.yaml b/sql/src/test/resources/tck/negate_operator.yaml index c8721b1f4..c4ab30748 100644 --- a/sql/src/test/resources/tck/negate_operator.yaml +++ b/sql/src/test/resources/tck/negate_operator.yaml @@ -14,7 +14,11 @@ tests: expression: --'10' result: 10 - - name: Invalid boolean cast + - name: Minus with boolean cast expression: -TRUE + result: -1 + + - name: Minus with missing attribute + expression: -missing result: 0 - error: cast + error: missingAttribute diff --git a/sql/src/test/resources/tck/not_operator.yaml b/sql/src/test/resources/tck/not_operator.yaml index 519af4141..0c7e157c0 100644 --- a/sql/src/test/resources/tck/not_operator.yaml +++ b/sql/src/test/resources/tck/not_operator.yaml @@ -16,5 +16,9 @@ tests: - name: Invalid int cast expression: NOT 10 - result: true - error: cast + result: false + + - name: Not missing attribute + expression: NOT missing + result: false + error: missingAttribute diff --git a/sql/src/test/resources/tck/spec_examples.yaml b/sql/src/test/resources/tck/spec_examples.yaml index 92ce148be..9aada2259 100644 --- a/sql/src/test/resources/tck/spec_examples.yaml +++ b/sql/src/test/resources/tck/spec_examples.yaml @@ -62,3 +62,16 @@ tests: eventOverrides: subject: Francesco Guardiani result: true + + - name: Missing attribute (1) + expression: true AND (missing = "") + result: false + error: missingAttribute + - name: Missing attribute (2) + expression: missing * 5 + result: 0 + error: missingAttribute + - name: Missing attribute (3) + expression: 1 / missing + result: 0 + error: missingAttribute diff --git a/sql/src/test/resources/tck/string_builtin_functions.yaml b/sql/src/test/resources/tck/string_builtin_functions.yaml index a7fa2a47c..c7200d95f 100644 --- a/sql/src/test/resources/tck/string_builtin_functions.yaml +++ b/sql/src/test/resources/tck/string_builtin_functions.yaml @@ -35,6 +35,7 @@ tests: - name: CONCAT_WS without arguments doesn't exist expression: CONCAT_WS() error: missingFunction + result: false - name: LOWER (1) expression: "LOWER('ABC')" diff --git a/sql/src/test/resources/tck/subscriptions_api_recreations.yaml b/sql/src/test/resources/tck/subscriptions_api_recreations.yaml index 9bd8659d3..d513e67c1 100644 --- a/sql/src/test/resources/tck/subscriptions_api_recreations.yaml +++ b/sql/src/test/resources/tck/subscriptions_api_recreations.yaml @@ -17,6 +17,7 @@ tests: myext: "customext" - name: Prefix filter on missing string extension expression: "myext LIKE 'custom%'" + result: false error: missingAttribute - name: Suffix filter (1) @@ -36,6 +37,7 @@ tests: myext: "customext" - name: Suffix filter on missing string extension expression: "myext LIKE '%ext'" + result: false error: missingAttribute - name: Exact filter (1) @@ -55,6 +57,7 @@ tests: myext: "customext" - name: Exact filter on missing string extension expression: "myext = 'customext'" + result: false error: missingAttribute - name: Prefix filter AND Suffix filter (1) @@ -77,9 +80,10 @@ tests: type: "com.github.error" - name: Prefix AND Suffix filter (4) expression: "type LIKE 'example.%' AND myext LIKE 'custom%'" - error: missingAttribute + result: false eventOverrides: type: "example.event.type" + error: missingAttribute - name: Prefix OR Suffix filter (1) expression: "id LIKE 'my%' OR source LIKE '%.ca'" @@ -107,20 +111,20 @@ tests: source: "http://www.some-website.com" - name: Disjunctive Normal Form (1) - expresion: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + expression: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" result: true eventOverrides: id: "myId" type: "example.event.success" - name: Disjunctive Normal Form (2) - expresion: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + expression: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" result: true eventOverrides: id: "notmyId" type: "example.event.warning" source: "http://localhost.localdomain" - name: Disjunctive Normal Form (3) - expresion: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + expression: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" result: false eventOverrides: id: "notmyId" @@ -150,18 +154,19 @@ tests: source: "http://localhost.localdomain" - name: Conjunctive Normal Form (4) expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" - result: true + result: false eventOverrides: id: "myId" type: "example.event.success" source: "http://localhost.localdomain" - name: Conjunctive Normal Form (5) expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning') AND (myext = 'customext')" - error: missingAttribute + result: false eventOverrides: id: "myId" - type: "example.event.success" + type: "example.event.warning" source: "http://localhost.localdomain" + error: missingAttribute