Fixed parseDateTime64BestEffort implementation

Fixed argument resolution issues.
Added tests and made sure -orNull and -orZero variants alwo work correctly.
This commit is contained in:
Vasily Nemkov 2020-05-16 15:11:17 +03:00
parent ee665d6837
commit 403aae9126
4 changed files with 132 additions and 68 deletions

View File

@ -1110,6 +1110,8 @@ public:
std::is_same_v<ToDataType, DataTypeDecimal<Decimal64>> ||
std::is_same_v<ToDataType, DataTypeDecimal<Decimal128>>;
static constexpr bool to_datetime64 = std::is_same_v<ToDataType, DataTypeDateTime64>;
static FunctionPtr create(const Context &) { return std::make_shared<FunctionConvertFromString>(); }
static FunctionPtr create() { return std::make_shared<FunctionConvertFromString>(); }
@ -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<ToDataType, DataTypeDateTime>)
{
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<ToDataType, DataTypeDateTime>)
res = std::make_shared<DataTypeDateTime>(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<ToDataType, DataTypeDecimal<Decimal32>>)
res = createDecimal<DataTypeDecimal>(9, scale);
else if constexpr (std::is_same_v<ToDataType, DataTypeDecimal<Decimal64>>)
res = createDecimal<DataTypeDecimal>(18, scale);
else if constexpr (std::is_same_v<ToDataType, DataTypeDecimal<Decimal128>>)
res = createDecimal<DataTypeDecimal>(38, scale);
if (!res)
throw Exception("Someting wrong with toDecimalNNOrZero() or toDecimalNNOrNull()", ErrorCodes::LOGICAL_ERROR);
}
else if constexpr (std::is_same_v<ToDataType, DataTypeDateTime64>)
{
UInt64 scale = DataTypeDateTime64::default_scale;
if (arguments.size() > 1)
scale = extractToDecimalScale(arguments[1]);
@ -1194,7 +1146,67 @@ public:
res = std::make_shared<DataTypeDateTime64>(scale, timezone);
}
else
res = std::make_shared<ToDataType>();
{
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<ToDataType, DataTypeDateTime>)
{
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<ToDataType, DataTypeDateTime>)
res = std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 1, 0));
else if constexpr (to_decimal)
{
UInt64 scale = extractToDecimalScale(arguments[1]);
if constexpr (std::is_same_v<ToDataType, DataTypeDecimal<Decimal32>>)
res = createDecimal<DataTypeDecimal>(9, scale);
else if constexpr (std::is_same_v<ToDataType, DataTypeDecimal<Decimal64>>)
res = createDecimal<DataTypeDecimal>(18, scale);
else if constexpr (std::is_same_v<ToDataType, DataTypeDecimal<Decimal128>>)
res = createDecimal<DataTypeDecimal>(38, scale);
if (!res)
throw Exception("Someting wrong with toDecimalNNOrZero() or toDecimalNNOrNull()", ErrorCodes::LOGICAL_ERROR);
}
else
res = std::make_shared<ToDataType>();
}
if constexpr (exception_mode == ConvertFromStringExceptionMode::Null)
res = std::make_shared<DataTypeNullable>(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<ToDataType, DataTypeDateTime64>)
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<const ToDataType &>(*removeNullable(block.getByPosition(result).type)).getScale();
if (checkAndGetDataType<DataTypeString>(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

View File

@ -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<bool>(whole, in, local_time_zone, utc_time_zone, &subsecond))
return ReturnType(false);
if constexpr (std::is_same_v<ReturnType, bool>)
{
if (!parseDateTimeBestEffortImpl<bool>(whole, in, local_time_zone, utc_time_zone, &subsecond))
return false;
}
else
{
parseDateTimeBestEffortImpl<ReturnType>(whole, in, local_time_zone, utc_time_zone, &subsecond);
}
DateTime64::NativeType fractional = subsecond.value;
if (scale < subsecond.digits)

View File

@ -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

View File

@ -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');