diff --git a/velox/core/SimpleFunctionMetadata.h b/velox/core/SimpleFunctionMetadata.h index ccf80e43c3c3..9fcb0ad60ac3 100644 --- a/velox/core/SimpleFunctionMetadata.h +++ b/velox/core/SimpleFunctionMetadata.h @@ -20,6 +20,7 @@ #include #include "velox/common/base/Exceptions.h" +#include "velox/common/base/Status.h" #include "velox/core/CoreTypeSystem.h" #include "velox/core/Metaprogramming.h" #include "velox/core/QueryConfig.h" @@ -687,17 +688,33 @@ class UDFHolder { bool, exec_return_type, const exec_arg_type&...>::value; + static constexpr bool udf_has_call_return_void = util::has_method< Fun, call_method_resolver, void, exec_return_type, const exec_arg_type&...>::value; - static constexpr bool udf_has_call = - udf_has_call_return_bool | udf_has_call_return_void; + + static constexpr bool udf_has_call_return_status = util::has_method< + Fun, + call_method_resolver, + Status, + exec_return_type, + const exec_arg_type&...>::value; + + static constexpr bool udf_has_call = udf_has_call_return_bool | + udf_has_call_return_void | udf_has_call_return_status; + static_assert( !(udf_has_call_return_bool && udf_has_call_return_void), - "Provided call() methods need to return either void OR bool."); + "Provided call() methods need to return either void OR bool OR status."); + static_assert( + !(udf_has_call_return_bool && udf_has_call_return_status), + "Provided call() methods need to return either void OR bool OR status."); + static_assert( + !(udf_has_call_return_void && udf_has_call_return_status), + "Provided call() methods need to return either void OR bool OR status."); // callNullable(): static constexpr bool udf_has_callNullable_return_bool = util::has_method< @@ -863,13 +880,14 @@ class UDFHolder { } } - FOLLY_ALWAYS_INLINE bool call( + FOLLY_ALWAYS_INLINE Status call( exec_return_type& out, + bool& notNull, const typename exec_resolver::in_type&... args) { if constexpr (udf_has_call) { - return callImpl(out, args...); + return callImpl(out, notNull, args...); } else if constexpr (udf_has_callNullable) { - return callNullableImpl(out, (&args)...); + return callNullableImpl(out, notNull, (&args)...); } else { VELOX_UNREACHABLE( "call should never be called if the UDF does not " @@ -877,18 +895,20 @@ class UDFHolder { } } - FOLLY_ALWAYS_INLINE bool callNullable( + FOLLY_ALWAYS_INLINE Status callNullable( exec_return_type& out, + bool& notNull, const typename exec_resolver::in_type*... args) { if constexpr (udf_has_callNullable) { - return callNullableImpl(out, args...); + return callNullableImpl(out, notNull, args...); } else if constexpr (udf_has_call) { // Default null behavior. const bool isAllSet = (args && ...); if (LIKELY(isAllSet)) { - return callImpl(out, (*args)...); + return callImpl(out, notNull, (*args)...); } else { - return false; + notNull = false; + return Status::OK(); } } else { VELOX_UNREACHABLE( @@ -897,21 +917,23 @@ class UDFHolder { } } - FOLLY_ALWAYS_INLINE bool callAscii( + FOLLY_ALWAYS_INLINE Status callAscii( exec_return_type& out, + bool& notNull, const typename exec_resolver::in_type&... args) { if constexpr (udf_has_callAscii) { - return callAsciiImpl(out, args...); + return callAsciiImpl(out, notNull, args...); } else { - return call(out, args...); + return call(out, notNull, args...); } } - FOLLY_ALWAYS_INLINE bool callNullFree( + FOLLY_ALWAYS_INLINE Status callNullFree( exec_return_type& out, + bool& notNull, const exec_no_nulls_arg_type&... args) { if constexpr (udf_has_callNullFree) { - return callNullFreeImpl(out, args...); + return callNullFreeImpl(out, notNull, args...); } else { VELOX_UNREACHABLE( "callNullFree should never be called if the UDF does not implement callNullFree."); @@ -920,52 +942,66 @@ class UDFHolder { // Helper functions to handle void vs bool return type. - FOLLY_ALWAYS_INLINE bool callImpl( + FOLLY_ALWAYS_INLINE Status callImpl( typename Exec::template resolver::out_type& out, + bool& notNull, const typename Exec::template resolver::in_type&... args) { static_assert(udf_has_call); - if constexpr (udf_has_call_return_bool) { + + if constexpr (udf_has_call_return_status) { + notNull = true; return instance_.call(out, args...); + } else if constexpr (udf_has_call_return_bool) { + notNull = instance_.call(out, args...); + return Status::OK(); } else { instance_.call(out, args...); - return true; + notNull = true; + return Status::OK(); } } - FOLLY_ALWAYS_INLINE bool callNullableImpl( + FOLLY_ALWAYS_INLINE Status callNullableImpl( exec_return_type& out, + bool& notNull, const typename Exec::template resolver::in_type*... args) { static_assert(udf_has_callNullable); if constexpr (udf_has_callNullable_return_bool) { - return instance_.callNullable(out, args...); + notNull = instance_.callNullable(out, args...); + return Status::OK(); } else { instance_.callNullable(out, args...); - return true; + notNull = true; + return Status::OK(); } } - FOLLY_ALWAYS_INLINE bool callAsciiImpl( + FOLLY_ALWAYS_INLINE Status callAsciiImpl( typename Exec::template resolver::out_type& out, + bool& notNull, const typename Exec::template resolver::in_type&... args) { static_assert(udf_has_callAscii); if constexpr (udf_has_callAscii_return_bool) { - return instance_.callAscii(out, args...); + notNull = instance_.callAscii(out, args...); } else { instance_.callAscii(out, args...); - return true; + notNull = true; } + return Status::OK(); } - FOLLY_ALWAYS_INLINE bool callNullFreeImpl( + FOLLY_ALWAYS_INLINE Status callNullFreeImpl( typename Exec::template resolver::out_type& out, + bool& notNull, const exec_no_nulls_arg_type&... args) { static_assert(udf_has_callNullFree); if constexpr (udf_has_callNullFree_return_bool) { - return instance_.callNullFree(out, args...); + notNull = instance_.callNullFree(out, args...); } else { instance_.callNullFree(out, args...); - return true; + notNull = true; } + return Status::OK(); } }; diff --git a/velox/expression/EvalCtx.cpp b/velox/expression/EvalCtx.cpp index 13c5aa520483..dfc4bf62fc07 100644 --- a/velox/expression/EvalCtx.cpp +++ b/velox/expression/EvalCtx.cpp @@ -172,6 +172,26 @@ auto throwError(const std::exception_ptr& exceptionPtr) { } } // namespace +void EvalCtx::setStatus(vector_size_t index, Status status) { + VELOX_CHECK(!status.ok(), "Status must be an error"); + + if (status.isUserError()) { + setVeloxExceptionError( + index, + std::make_exception_ptr(VeloxUserError( + __FILE__, + __LINE__, + __FUNCTION__, + "", + status.message(), + error_source::kErrorSourceUser, + error_code::kInvalidArgument, + false /*retriable*/))); + } else { + VELOX_FAIL(status.message()); + } +} + void EvalCtx::setError( vector_size_t index, const std::exception_ptr& exceptionPtr) { diff --git a/velox/expression/EvalCtx.h b/velox/expression/EvalCtx.h index 1f4d0de95cb4..0cc762bbcf4b 100644 --- a/velox/expression/EvalCtx.h +++ b/velox/expression/EvalCtx.h @@ -78,6 +78,9 @@ class EvalCtx { void restore(ContextSaver& saver); + // @param status Must indicate an error. Cannot be "ok". + void setStatus(vector_size_t index, Status status); + // If exceptionPtr is known to be a VeloxException use setVeloxExceptionError // instead. void setError(vector_size_t index, const std::exception_ptr& exceptionPtr); diff --git a/velox/expression/SimpleFunctionAdapter.h b/velox/expression/SimpleFunctionAdapter.h index b6b0aae5f59c..8c0bb54d5b12 100644 --- a/velox/expression/SimpleFunctionAdapter.h +++ b/velox/expression/SimpleFunctionAdapter.h @@ -16,11 +16,13 @@ #pragma once +#include #include #include #include #include "velox/common/base/Portability.h" +#include "velox/common/base/Status.h" #include "velox/expression/ComplexWriterTypes.h" #include "velox/expression/DecodedArgs.h" #include "velox/expression/Expr.h" @@ -195,6 +197,10 @@ class SimpleFunctionAdapter : public VectorFunction { context.template applyToSelectedNoThrow(*rows, func); } + void setError(vector_size_t row, Status status) { + context.setStatus(row, status); + } + const SelectivityVector* rows; result_vector_t* result; VectorWriter resultWriter; @@ -618,7 +624,11 @@ class SimpleFunctionAdapter : public VectorFunction { // Result is NULL because the input contains NULL. notNull = false; } else { - notNull = doApplyNullFree<0>(row, out, readers...); + auto status = doApplyNullFree<0>(row, out, notNull, readers...); + if UNLIKELY (!status.ok()) { + applyContext.setError(row, status); + return; + } } writeResult(row, notNull, out); @@ -626,7 +636,12 @@ class SimpleFunctionAdapter : public VectorFunction { } else { applyContext.applyToSelectedNoThrow([&](auto row) INLINE_LAMBDA { typename return_type_traits::NativeType out{}; - bool notNull = doApplyNullFree<0>(row, out, readers...); + bool notNull; + auto status = doApplyNullFree<0>(row, out, notNull, readers...); + if UNLIKELY (!status.ok()) { + applyContext.setError(row, status); + return; + } writeResult(row, notNull, out); }); @@ -636,7 +651,13 @@ class SimpleFunctionAdapter : public VectorFunction { if (applyContext.allAscii) { applyContext.applyToSelectedNoThrow([&](auto row) INLINE_LAMBDA { typename return_type_traits::NativeType out{}; - bool notNull = doApplyAsciiNotNull<0>(row, out, readers...); + bool notNull; + auto status = + doApplyAsciiNotNull<0>(row, out, notNull, readers...); + if UNLIKELY (!status.ok()) { + applyContext.setError(row, status); + return; + } writeResult(row, notNull, out); }); return; @@ -648,13 +669,23 @@ class SimpleFunctionAdapter : public VectorFunction { // optimization (eliminating the temp) is easier to do by the // compiler (assuming the function call is inlined). typename return_type_traits::NativeType out{}; - bool notNull = doApplyNotNull<0>(row, out, readers...); + bool notNull; + auto status = doApplyNotNull<0>(row, out, notNull, readers...); + if UNLIKELY (!status.ok()) { + applyContext.setError(row, status); + return; + } writeResult(row, notNull, out); }); } else { applyContext.applyToSelectedNoThrow([&](auto row) INLINE_LAMBDA { typename return_type_traits::NativeType out{}; - bool notNull = doApply<0>(row, out, readers...); + bool notNull; + auto status = doApply<0>(row, out, notNull, readers...); + if UNLIKELY (!status.ok()) { + applyContext.setError(row, status); + return; + } writeResult(row, notNull, out); }); } @@ -664,36 +695,47 @@ class SimpleFunctionAdapter : public VectorFunction { // once per batch instead of once per row shows a significant // performance improvement when there are no nulls. if (applyContext.mayHaveNullsRecursive) { - applyUdf(applyContext, [&](auto& out, auto row) INLINE_LAMBDA { - auto containsNull = (readers.containsNull(row) || ...); - if (containsNull) { - // Result is NULL because the input contains NULL. - return false; - } - - return doApplyNullFree<0>(row, out, readers...); - }); + applyUdf( + applyContext, + [&](auto& out, auto& notNull, auto row) INLINE_LAMBDA { + auto containsNull = (readers.containsNull(row) || ...); + if (containsNull) { + // Result is NULL because the input contains NULL. + notNull = false; + return Status::OK(); + } + + return doApplyNullFree<0>(row, out, notNull, readers...); + }); } else { - applyUdf(applyContext, [&](auto& out, auto row) INLINE_LAMBDA { - return doApplyNullFree<0>(row, out, readers...); - }); + applyUdf( + applyContext, + [&](auto& out, auto& notNull, auto row) INLINE_LAMBDA { + return doApplyNullFree<0>(row, out, notNull, readers...); + }); } } else if (allNotNull) { if constexpr (FUNC::has_ascii) { if (applyContext.allAscii) { - applyUdf(applyContext, [&](auto& out, auto row) INLINE_LAMBDA { - return doApplyAsciiNotNull<0>(row, out, readers...); - }); + applyUdf( + applyContext, + [&](auto& out, auto& notNull, auto row) INLINE_LAMBDA { + return doApplyAsciiNotNull<0>(row, out, notNull, readers...); + }); return; } } - applyUdf(applyContext, [&](auto& out, auto row) INLINE_LAMBDA { - return doApplyNotNull<0>(row, out, readers...); - }); + applyUdf( + applyContext, + [&](auto& out, auto& notNull, auto row) INLINE_LAMBDA { + return doApplyNotNull<0>(row, out, notNull, readers...); + }); } else { - applyUdf(applyContext, [&](auto& out, auto row) INLINE_LAMBDA { - return doApply<0>(row, out, readers...); - }); + applyUdf( + applyContext, + [&](auto& out, auto& notNull, auto row) INLINE_LAMBDA { + return doApply<0>(row, out, notNull, readers...); + }); } } } @@ -709,16 +751,26 @@ class SimpleFunctionAdapter : public VectorFunction { applyContext.resultWriter.setOffset(row); // Force local copy of proxy. auto localWriter = currentWriter; - auto notNull = func(localWriter, row); - currentWriter = localWriter; - applyContext.resultWriter.commit(notNull); + bool notNull; + auto status = func(localWriter, notNull, row); + if UNLIKELY (!status.ok()) { + applyContext.setError(row, status); + } else { + currentWriter = localWriter; + applyContext.resultWriter.commit(notNull); + } }); applyContext.resultWriter.finish(); } else { applyContext.applyToSelectedNoThrow([&](auto row) INLINE_LAMBDA { applyContext.resultWriter.setOffset(row); - applyContext.resultWriter.commit( - func(applyContext.resultWriter.current(), row)); + bool notNull; + auto status = func(applyContext.resultWriter.current(), notNull, row); + if UNLIKELY (!status.ok()) { + applyContext.setError(row, status); + } else { + applyContext.resultWriter.commit(notNull); + } }); } } @@ -734,10 +786,11 @@ class SimpleFunctionAdapter : public VectorFunction { typename... Values, std::enable_if_t< POSITION = - 0> FOLLY_ALWAYS_INLINE bool + 0> FOLLY_ALWAYS_INLINE Status doApply( size_t idx, T& target, + bool& notNull, R0& currentReader, const Values&... extra) const { if (LIKELY(currentReader.isSet(idx))) { @@ -745,9 +798,10 @@ class SimpleFunctionAdapter : public VectorFunction { decltype(currentReader[idx]) v0 = currentReader[idx]; // recurse through the readers to build the arg list at compile time. - return doApply(idx, target, extra..., v0); + return doApply(idx, target, notNull, extra..., v0); } else { - return false; + notNull = false; + return Status::OK(); } } @@ -758,10 +812,11 @@ class SimpleFunctionAdapter : public VectorFunction { typename... Values, std::enable_if_t< POSITION = - 0> FOLLY_ALWAYS_INLINE bool + 0> FOLLY_ALWAYS_INLINE Status doApply( size_t idx, T& target, + bool& notNull, R0& currentReader, const Values&... extra) const { // Recurse through all the arguments to build the arg list at compile @@ -770,16 +825,17 @@ class SimpleFunctionAdapter : public VectorFunction { return doApply( idx, target, + notNull, extra..., (currentReader.isSet(idx) ? ¤tReader[idx] : nullptr)); } else { using temp_type = std::remove_reference_t; if (currentReader.isSet(idx)) { temp_type temp = currentReader[idx]; - return doApply(idx, target, extra..., &temp); + return doApply(idx, target, notNull, extra..., &temp); } else { return doApply( - idx, target, extra..., (const temp_type*)nullptr); + idx, target, notNull, extra..., (const temp_type*)nullptr); } } } @@ -791,9 +847,12 @@ class SimpleFunctionAdapter : public VectorFunction { std::enable_if_t< POSITION == FUNC::num_args && FUNC::is_default_null_behavior, int32_t> = 0> - FOLLY_ALWAYS_INLINE bool - doApply(size_t /*idx*/, T& target, const Values&... values) const { - return (*fn_).call(target, values...); + FOLLY_ALWAYS_INLINE Status doApply( + size_t /*idx*/, + T& target, + bool& notNull, + const Values&... values) const { + return (*fn_).call(target, notNull, values...); } // For NOT default null behavior, terminate with UDFHolder::callNullable. @@ -803,9 +862,12 @@ class SimpleFunctionAdapter : public VectorFunction { std::enable_if_t< POSITION == FUNC::num_args && !FUNC::is_default_null_behavior, int32_t> = 0> - FOLLY_ALWAYS_INLINE bool - doApply(size_t /*idx*/, T& target, const Values*... values) const { - return (*fn_).callNullable(target, values...); + FOLLY_ALWAYS_INLINE Status doApply( + size_t /*idx*/, + T& target, + bool& notNull, + const Values*... values) const { + return (*fn_).callNullable(target, notNull, values...); } // == NOT-NULL VARIANT == @@ -822,13 +884,14 @@ class SimpleFunctionAdapter : public VectorFunction { typename R0, typename... TStuff, std::enable_if_t = 0> - FOLLY_ALWAYS_INLINE bool doApplyNotNull( + FOLLY_ALWAYS_INLINE Status doApplyNotNull( size_t idx, T& target, + bool& notNull, R0& currentReader, const TStuff&... extra) const { decltype(currentReader[idx]) v0 = currentReader[idx]; - return doApplyNotNull(idx, target, extra..., v0); + return doApplyNotNull(idx, target, notNull, extra..., v0); } // For default null behavior, Terminate by with UDFHolder::call. @@ -836,9 +899,12 @@ class SimpleFunctionAdapter : public VectorFunction { size_t POSITION, typename... Values, std::enable_if_t = 0> - FOLLY_ALWAYS_INLINE bool - doApplyNotNull(size_t /*idx*/, T& target, const Values&... values) const { - return (*fn_).call(target, values...); + FOLLY_ALWAYS_INLINE Status doApplyNotNull( + size_t /*idx*/, + T& target, + bool& notNull, + const Values&... values) const { + return (*fn_).call(target, notNull, values...); } template < @@ -846,24 +912,27 @@ class SimpleFunctionAdapter : public VectorFunction { typename R0, typename... TStuff, std::enable_if_t = 0> - FOLLY_ALWAYS_INLINE bool doApplyAsciiNotNull( + FOLLY_ALWAYS_INLINE Status doApplyAsciiNotNull( size_t idx, T& target, + bool& notNull, R0& currentReader, const TStuff&... extra) const { decltype(currentReader[idx]) v0 = currentReader[idx]; - return doApplyAsciiNotNull(idx, target, extra..., v0); + return doApplyAsciiNotNull( + idx, target, notNull, extra..., v0); } template < size_t POSITION, typename... Values, std::enable_if_t = 0> - FOLLY_ALWAYS_INLINE bool doApplyAsciiNotNull( + FOLLY_ALWAYS_INLINE Status doApplyAsciiNotNull( size_t /*idx*/, T& target, + bool& notNull, const Values&... values) const { - return (*fn_).callAscii(target, values...); + return (*fn_).callAscii(target, notNull, values...); } template < @@ -871,22 +940,26 @@ class SimpleFunctionAdapter : public VectorFunction { typename R0, typename... TStuff, std::enable_if_t = 0> - FOLLY_ALWAYS_INLINE bool doApplyNullFree( + FOLLY_ALWAYS_INLINE Status doApplyNullFree( size_t idx, T& target, + bool& notNull, R0& currentReader, const TStuff&... extra) const { auto v0 = currentReader.readNullFree(idx); - return doApplyNullFree(idx, target, extra..., v0); + return doApplyNullFree(idx, target, notNull, extra..., v0); } template < size_t POSITION, typename... Values, std::enable_if_t = 0> - FOLLY_ALWAYS_INLINE bool - doApplyNullFree(size_t /*idx*/, T& target, const Values&... values) const { - return (*fn_).callNullFree(target, values...); + FOLLY_ALWAYS_INLINE Status doApplyNullFree( + size_t /*idx*/, + T& target, + bool& notNull, + const Values&... values) const { + return (*fn_).callNullFree(target, notNull, values...); } }; diff --git a/velox/expression/tests/SimpleFunctionTest.cpp b/velox/expression/tests/SimpleFunctionTest.cpp index 63143e7f06a8..cd76de6bf3a6 100644 --- a/velox/expression/tests/SimpleFunctionTest.cpp +++ b/velox/expression/tests/SimpleFunctionTest.cpp @@ -19,8 +19,10 @@ #include #include +#include #include "folly/lang/Hint.h" -#include "gtest/gtest.h" + +#include "velox/common/base/tests/GTestUtils.h" #include "velox/expression/Expr.h" #include "velox/expression/SimpleFunctionAdapter.h" #include "velox/functions/Udf.h" @@ -1135,7 +1137,7 @@ struct StringInputIntOutputFunction { } }; -TEST_F(SimpleFunctionTest, TestcallAscii) { +TEST_F(SimpleFunctionTest, callAscii) { registerFunction( {"get_input_size"}); auto asciiInput = makeFlatVector({"abc123", "10% #\0"}); @@ -1478,4 +1480,57 @@ TEST_F(SimpleFunctionTest, decimalsWithConstraints) { } } +template +struct NoThrowFunction { + VELOX_DEFINE_FUNCTION_TYPES(TExec); + + Status call(out_type& out, const arg_type& in) { + if (in % 3 != 0) { + return Status::UserError("Input must be divisible by 3"); + } + + // Throwing exceptions is not recommended, but allowed. + VELOX_USER_CHECK(in % 2 == 0, "Input must be even"); + + if (in == 6) { + return Status::UnknownError("Input must not be 6"); + } + + out = in / 6; + return Status::OK(); + } +}; + +TEST_F(SimpleFunctionTest, noThrow) { + registerFunction({"no_throw"}); + + auto result = evaluateOnce("no_throw(c0)", 12); + EXPECT_EQ(2, result); + + // Errors reported via Status. + VELOX_ASSERT_THROW( + (evaluateOnce("no_throw(c0)", 10)), + "Input must be divisible by 3"); + + result = evaluateOnce("try(no_throw(c0))", 10); + EXPECT_EQ(std::nullopt, result); + + // Errors reported by throwing exceptions. + VELOX_ASSERT_THROW( + (evaluateOnce("no_throw(c0)", 15)), + "Input must be even"); + + result = evaluateOnce("try(no_throw(c0))", 15); + EXPECT_EQ(std::nullopt, result); + + // Non-user errors cannot be suppressed by TRY. + VELOX_ASSERT_THROW( + (evaluateOnce("no_throw(c0)", 6)), + "Input must not be 6"); + + VELOX_ASSERT_THROW( + (evaluateOnce("try(no_throw(c0))", 6)), + "Input must not be 6"); +} + } // namespace diff --git a/velox/functions/prestosql/DateTimeFunctions.h b/velox/functions/prestosql/DateTimeFunctions.h index 95fa3a8bd96d..4d0cda270c4d 100644 --- a/velox/functions/prestosql/DateTimeFunctions.h +++ b/velox/functions/prestosql/DateTimeFunctions.h @@ -1293,7 +1293,7 @@ struct DateParseFunction { } } - FOLLY_ALWAYS_INLINE void call( + FOLLY_ALWAYS_INLINE Status call( out_type& result, const arg_type& input, const arg_type& format) { @@ -1302,15 +1302,17 @@ struct DateParseFunction { std::string_view(format.data(), format.size())); } - auto dateTimeResult = - format_->parse(std::string_view(input.data(), input.size()), true) - .value(); + auto dateTimeResult = format_->parse((std::string_view)(input)); + if (!dateTimeResult.has_value()) { + return Status::UserError("Invalid date format: '{}'", input); + } // Since MySql format has no timezone specifier, simply check if session // timezone was provided. If not, fallback to 0 (GMT). int16_t timezoneId = sessionTzID_.value_or(0); - dateTimeResult.timestamp.toGMT(timezoneId); - result = dateTimeResult.timestamp; + dateTimeResult->timestamp.toGMT(timezoneId); + result = dateTimeResult->timestamp; + return Status::OK(); } }; diff --git a/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp b/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp index e6f4a8c13962..425cfd4879bb 100644 --- a/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp +++ b/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp @@ -3467,13 +3467,9 @@ TEST_F(DateTimeFunctionsTest, dateParse) { EXPECT_EQ( Timestamp(-66600, 0), dateParse("1969-12-31+11:00", "%Y-%m-%d+%H:%i")); - VELOX_ASSERT_THROW( - dateParse("", "%y+"), "Invalid date format: '' is malformed at ''"); - VELOX_ASSERT_THROW( - dateParse("1", "%y+"), "Invalid date format: '1' is malformed at '1'"); - VELOX_ASSERT_THROW( - dateParse("116", "%y+"), - "Invalid date format: '116' is malformed at '6'"); + VELOX_ASSERT_THROW(dateParse("", "%y+"), "Invalid date format: ''"); + VELOX_ASSERT_THROW(dateParse("1", "%y+"), "Invalid date format: '1'"); + VELOX_ASSERT_THROW(dateParse("116", "%y+"), "Invalid date format: '116'"); } TEST_F(DateTimeFunctionsTest, dateFunctionVarchar) {