diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 64708f45598..9d0b764e84b 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -1110,6 +1110,8 @@ public: std::is_same_v> || std::is_same_v>; + static constexpr bool to_datetime64 = std::is_same_v; + static FunctionPtr create(const Context &) { return std::make_shared(); } static FunctionPtr create() { return std::make_shared(); } @@ -1126,67 +1128,17 @@ public: DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override { - if ((arguments.size() != 1 && arguments.size() != 2) || (to_decimal && arguments.size() != 2)) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + - ", should be 1 or 2. Second argument only make sense for DateTime (time zone, optional) and Decimal (scale).", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - if (!isStringOrFixedString(arguments[0].type)) - { - if (this->getName().find("OrZero") != std::string::npos || - this->getName().find("OrNull") != std::string::npos) - throw Exception("Illegal type " + arguments[0].type->getName() + " of first argument of function " + getName() + - ". Conversion functions with postfix 'OrZero' or 'OrNull' should take String argument", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - else - throw Exception("Illegal type " + arguments[0].type->getName() + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - } - - if (arguments.size() == 2) - { - if constexpr (std::is_same_v) - { - if (!isString(arguments[1].type)) - throw Exception("Illegal type " + arguments[1].type->getName() + " of 2nd argument of function " + getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - } - else if constexpr (to_decimal) - { - if (!isInteger(arguments[1].type)) - throw Exception("Illegal type " + arguments[1].type->getName() + " of 2nd argument of function " + getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - if (!arguments[1].column) - throw Exception("Second argument for function " + getName() + " must be constant", ErrorCodes::ILLEGAL_COLUMN); - } - else - { - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 1. Second argument makes sense only for DateTime and Decimal.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - } - } - DataTypePtr res; - - if constexpr (std::is_same_v) - res = std::make_shared(extractTimeZoneNameFromFunctionArguments(arguments, 1, 0)); - else if constexpr (to_decimal) + if constexpr (to_datetime64) { - UInt64 scale = extractToDecimalScale(arguments[1]); + validateFunctionArgumentTypes(*this, arguments, + FunctionArgumentDescriptors{{"string", isStringOrFixedString, nullptr, "String or FixedString"}}, + // optional + FunctionArgumentDescriptors{ + {"precision", isUInt8, isColumnConst, "const UInt8"}, + {"timezone", isStringOrFixedString, isColumnConst, "const String or FixedString"}, + }); - if constexpr (std::is_same_v>) - res = createDecimal(9, scale); - else if constexpr (std::is_same_v>) - res = createDecimal(18, scale); - else if constexpr (std::is_same_v>) - res = createDecimal(38, scale); - - if (!res) - throw Exception("Someting wrong with toDecimalNNOrZero() or toDecimalNNOrNull()", ErrorCodes::LOGICAL_ERROR); - } - else if constexpr (std::is_same_v) - { UInt64 scale = DataTypeDateTime64::default_scale; if (arguments.size() > 1) scale = extractToDecimalScale(arguments[1]); @@ -1194,7 +1146,67 @@ public: res = std::make_shared(scale, timezone); } else - res = std::make_shared(); + { + if ((arguments.size() != 1 && arguments.size() != 2) || (to_decimal && arguments.size() != 2)) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + + ", should be 1 or 2. Second argument only make sense for DateTime (time zone, optional) and Decimal (scale).", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + if (!isStringOrFixedString(arguments[0].type)) + { + if (this->getName().find("OrZero") != std::string::npos || + this->getName().find("OrNull") != std::string::npos) + throw Exception("Illegal type " + arguments[0].type->getName() + " of first argument of function " + getName() + + ". Conversion functions with postfix 'OrZero' or 'OrNull' should take String argument", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + else + throw Exception("Illegal type " + arguments[0].type->getName() + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + + if (arguments.size() == 2) + { + if constexpr (std::is_same_v) + { + if (!isString(arguments[1].type)) + throw Exception("Illegal type " + arguments[1].type->getName() + " of 2nd argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + else if constexpr (to_decimal) + { + if (!isInteger(arguments[1].type)) + throw Exception("Illegal type " + arguments[1].type->getName() + " of 2nd argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + if (!arguments[1].column) + throw Exception("Second argument for function " + getName() + " must be constant", ErrorCodes::ILLEGAL_COLUMN); + } + else + { + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 1. Second argument makes sense only for DateTime and Decimal.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + } + } + + if constexpr (std::is_same_v) + res = std::make_shared(extractTimeZoneNameFromFunctionArguments(arguments, 1, 0)); + else if constexpr (to_decimal) + { + UInt64 scale = extractToDecimalScale(arguments[1]); + + if constexpr (std::is_same_v>) + res = createDecimal(9, scale); + else if constexpr (std::is_same_v>) + res = createDecimal(18, scale); + else if constexpr (std::is_same_v>) + res = createDecimal(38, scale); + + if (!res) + throw Exception("Someting wrong with toDecimalNNOrZero() or toDecimalNNOrNull()", ErrorCodes::LOGICAL_ERROR); + } + else + res = std::make_shared(); + } if constexpr (exception_mode == ConvertFromStringExceptionMode::Null) res = std::make_shared(res); @@ -1207,12 +1219,9 @@ public: const IDataType * from_type = block.getByPosition(arguments[0]).type.get(); bool ok = true; - if constexpr (to_decimal || std::is_same_v) + if constexpr (to_decimal || to_datetime64) { - if (arguments.size() != 2) - throw Exception{"Function " + getName() + " expects 2 arguments for Decimal.", ErrorCodes::TOO_FEW_ARGUMENTS_FOR_FUNCTION}; - - UInt32 scale = extractToDecimalScale(block.getByPosition(arguments[1])); + const UInt32 scale = assert_cast(*removeNullable(block.getByPosition(result).type)).getScale(); if (checkAndGetDataType(from_type)) { @@ -1241,7 +1250,6 @@ public: } else ok = false; - } if (!ok) @@ -1252,7 +1260,6 @@ public: } }; - /** Conversion to fixed string is implemented only for strings. */ class FunctionToFixedString : public IFunction diff --git a/src/IO/parseDateTimeBestEffort.cpp b/src/IO/parseDateTimeBestEffort.cpp index 7e40909226c..4b6183e9c0b 100644 --- a/src/IO/parseDateTimeBestEffort.cpp +++ b/src/IO/parseDateTimeBestEffort.cpp @@ -562,8 +562,17 @@ ReturnType parseDateTime64BestEffortImpl(DateTime64 & res, UInt32 scale, ReadBuf { time_t whole; DateTimeSubsecondPart subsecond = {0, 0}; // needs to be explicitly initialized sine it could be missing from input string - if (!parseDateTimeBestEffortImpl(whole, in, local_time_zone, utc_time_zone, &subsecond)) - return ReturnType(false); + + if constexpr (std::is_same_v) + { + if (!parseDateTimeBestEffortImpl(whole, in, local_time_zone, utc_time_zone, &subsecond)) + return false; + } + else + { + parseDateTimeBestEffortImpl(whole, in, local_time_zone, utc_time_zone, &subsecond); + } + DateTime64::NativeType fractional = subsecond.value; if (scale < subsecond.digits) diff --git a/tests/queries/0_stateless/01281_parseDateTime64BestEffort.reference b/tests/queries/0_stateless/01281_parseDateTime64BestEffort.reference new file mode 100644 index 00000000000..e55e50c15d5 --- /dev/null +++ b/tests/queries/0_stateless/01281_parseDateTime64BestEffort.reference @@ -0,0 +1,15 @@ +orNull +2020-05-14 03:37:03.253 +\N +orZero +2020-05-14 03:37:03.253 +0000-00-00 00:00:00.000 +non-const +2020-05-14 03:37:03.253 +Timezones +2020-05-14 03:37:03.253 +2020-05-14 06:37:03.253 +Formats +2020-05-14 03:37:03.253 +2020-05-14 03:37:03.000 +2020-05-14 03:37:03.000 diff --git a/tests/queries/0_stateless/01281_parseDateTime64BestEffort.sql b/tests/queries/0_stateless/01281_parseDateTime64BestEffort.sql new file mode 100644 index 00000000000..5c0bbe1b4c2 --- /dev/null +++ b/tests/queries/0_stateless/01281_parseDateTime64BestEffort.sql @@ -0,0 +1,33 @@ +-- Error cases +SELECT parseDateTime64BestEffort(); -- {serverError 42} +SELECT parseDateTime64BestEffort(123); -- {serverError 43} +SELECT parseDateTime64BestEffort('foo'); -- {serverError 41} + +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184Z', 'bar'); -- {serverError 43} -- invalid scale parameter +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184Z', 3, 4); -- {serverError 43} -- invalid timezone parameter +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184Z', 3, 'baz'); -- {serverError 1000} -- unknown timezone + +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184Z', materialize(3), 4); -- {serverError 44} -- non-const precision +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184Z', 3, materialize('UTC')); -- {serverError 44} -- non-const timezone + +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184012345678910111213141516171819Z', 3, 'UTC'); -- {serverError 6} + +SELECT 'orNull'; +SELECT parseDateTime64BestEffortOrNull('2020-05-14T03:37:03.253184Z', 3, 'UTC'); +SELECT parseDateTime64BestEffortOrNull('foo', 3, 'UTC'); + +SELECT 'orZero'; +SELECT parseDateTime64BestEffortOrZero('2020-05-14T03:37:03.253184Z', 3, 'UTC'); +SELECT parseDateTime64BestEffortOrZero('bar', 3, 'UTC'); + +SELECT 'non-const'; +SELECT parseDateTime64BestEffort(materialize('2020-05-14T03:37:03.253184Z'), 3, 'UTC'); + +SELECT 'Timezones'; +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184Z', 3, 'UTC'); +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184Z', 3, 'Europe/Minsk'); + +SELECT 'Formats'; +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03.253184', 3, 'UTC'); +SELECT parseDateTime64BestEffort('2020-05-14T03:37:03', 3, 'UTC'); +SELECT parseDateTime64BestEffort('2020-05-14 03:37:03', 3, 'UTC'); \ No newline at end of file