diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 911496e676d..84a41a9c415 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -977,8 +977,6 @@ struct ConvertThroughParsing }; - - template struct ConvertImpl, DataTypeString>, ToDataType, Name, ConvertDefaultBehaviorTag> : ConvertThroughParsing {}; @@ -1044,6 +1042,9 @@ template <> struct ConvertImpl : ConvertImpl {}; +template <> +struct ConvertImpl + : ConvertImpl {}; /** If types are identical, just take reference to column. */ @@ -1187,6 +1188,7 @@ public: auto getter = [&] (const auto & args) { return getReturnTypeImplRemovedNullable(args); }; auto res = FunctionOverloadResolverAdaptor::getReturnTypeDefaultImplementationForNulls(arguments, getter); to_nullable = res->isNullable(); + checked_return_type = true; return res; } @@ -1268,17 +1270,16 @@ public: } } - bool useDefaultImplementationForNulls() const override { return to_nullable; } // FIXME + /// Function actually uses default implementation for nulls, + /// but we need to know if return type is Nullable or not, + /// so we use checked_return_type only to intercept the first call to getReturnTypeImpl(...). + bool useDefaultImplementationForNulls() const override { return checked_return_type; } + bool useDefaultImplementationForConstants() const override { return true; } ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1}; } bool canBeExecutedOnDefaultArguments() const override { return false; } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override - { - return executeImplRemovedNullable(arguments, result_type, input_rows_count); - } - - ColumnPtr executeImplRemovedNullable(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const { try { @@ -1323,6 +1324,7 @@ public: } private: + mutable bool checked_return_type = false; mutable bool to_nullable = false; ColumnPtr executeInternal(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const @@ -1359,12 +1361,12 @@ private: const ColumnWithTypeAndName & scale_column = arguments[1]; UInt32 scale = extractToDecimalScale(scale_column); - result_column = ConvertImpl::execute(arguments, result_type, input_rows_count, scale); + result_column = ConvertImpl::execute(arguments, result_type, input_rows_count, scale); } else if constexpr (IsDataTypeDateOrDateTime && std::is_same_v) { const auto * dt64 = assert_cast(arguments[0].type.get()); - result_column = ConvertImpl::execute(arguments, result_type, input_rows_count, dt64->getScale()); + result_column = ConvertImpl::execute(arguments, result_type, input_rows_count, dt64->getScale()); } else if constexpr (IsDataTypeDecimalOrNumber && IsDataTypeDecimalOrNumber) { @@ -1414,6 +1416,8 @@ private: } else { + /// We should use ConvertFromStringExceptionMode::Null mode when converting from String (or FixedString) + /// to Nullable type, to avoid 'value is too short' error on attempt to parse empty string from NULL values. if (to_nullable && WhichDataType(from_type).isStringOrFixedString()) done = callOnIndexAndDataType(from_type->getTypeId(), call, ConvertReturnNullOnErrorTag{}); else diff --git a/tests/queries/0_stateless/01186_conversion_to_nullable.reference b/tests/queries/0_stateless/01186_conversion_to_nullable.reference new file mode 100644 index 00000000000..04146644154 --- /dev/null +++ b/tests/queries/0_stateless/01186_conversion_to_nullable.reference @@ -0,0 +1,38 @@ +42 +\N +0 +\N +\N +42 +\N +0 +\N +256 +2020-12-24 +\N +1970-01-01 +\N +1970-01-01 +2020-12-24 01:02:03 +\N +1970-01-01 03:00:00 +\N +2020-12-24 01:02:03.00 +\N +1970-01-01 03:00:00.00 +1970-01-01 03:00:00.00 +946721532 +\N +\N +42.00 +\N +\N +42.00000000 +\N +3.14159000 +42 +\N +test +42\0\0\0\0\0\0 +\N +test\0\0\0\0 diff --git a/tests/queries/0_stateless/01186_conversion_to_nullable.sql b/tests/queries/0_stateless/01186_conversion_to_nullable.sql new file mode 100644 index 00000000000..bf7df6234d2 --- /dev/null +++ b/tests/queries/0_stateless/01186_conversion_to_nullable.sql @@ -0,0 +1,13 @@ +select toUInt8(x) from values('x Nullable(String)', '42', NULL, '0', '', '256'); +select toInt64(x) from values('x Nullable(String)', '42', NULL, '0', '', '256'); + +select toDate(x) from values('x Nullable(String)', '2020-12-24', NULL, '0000-00-00', '', '9999-01-01'); +select toDateTime(x) from values('x Nullable(String)', '2020-12-24 01:02:03', NULL, '0000-00-00 00:00:00', ''); +select toDateTime64(x, 2) from values('x Nullable(String)', '2020-12-24 01:02:03', NULL, '0000-00-00 00:00:00', ''); +select toUnixTimestamp(x) from values ('x Nullable(String)', '2000-01-01 13:12:12', NULL, ''); + +select toDecimal32(x, 2) from values ('x Nullable(String)', '42', NULL, '3.14159'); +select toDecimal64(x, 8) from values ('x Nullable(String)', '42', NULL, '3.14159'); + +select toString(x) from values ('x Nullable(String)', '42', NULL, 'test'); +select toFixedString(x, 8) from values ('x Nullable(String)', '42', NULL, 'test');