From 29971abed073f38e8d5b43eac6ac50b367643ff2 Mon Sep 17 00:00:00 2001 From: "Daniel Q. Kim" Date: Wed, 1 Apr 2026 15:47:36 +0200 Subject: [PATCH] Fix Nullable(X)->Y cross-type ALTER MODIFY COLUMN PR #84770 generates _CAST(ifNull(col, default), TargetType). When source and target base types differ (e.g. Nullable(UInt8) -> String), ifNull throws NO_COMMON_TYPE because getLeastSupertype({UInt8, String}) fails. Fix: swap to ifNull(_CAST(col, Nullable(TargetType)), _CAST(default, TargetType)). Ref: ClickHouse/ClickHouse#84770, ClickHouse/ClickHouse#5985 Signed-off-by: Daniel Q. Kim --- src/Interpreters/inplaceBlockConversions.cpp | 14 +++++++++++--- ...ify_column_null_to_default_cross_type.reference | 6 ++++++ ...75_modify_column_null_to_default_cross_type.sql | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.reference create mode 100644 tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.sql diff --git a/src/Interpreters/inplaceBlockConversions.cpp b/src/Interpreters/inplaceBlockConversions.cpp index f90eb52cd632..cba6d145bb5b 100644 --- a/src/Interpreters/inplaceBlockConversions.cpp +++ b/src/Interpreters/inplaceBlockConversions.cpp @@ -167,10 +167,18 @@ ASTPtr convertRequiredExpressions(Block & block, const NamesAndTypesList & requi "Please specify `DEFAULT` expression in ALTER MODIFY COLUMN statement", required_column.name, column_in_block.type->getName(), required_column.type->getName()); - auto convert_func = makeASTFunction("_CAST", - makeASTFunction("ifNull", std::make_shared(required_column.name), default_value), + /// Cast the nullable column to Nullable(TargetType) first, then strip NULLs with ifNull. + /// This handles cross-type conversion (e.g. Nullable(UInt8) -> String) correctly, + /// because _CAST(Nullable(UInt8), 'Nullable(String)') -> Nullable(String), + /// and ifNull(Nullable(String), String) trivially resolves to String. + auto nullable_target_type_name = "Nullable(" + required_column.type->getName() + ")"; + auto cast_col = makeASTFunction("_CAST", + std::make_shared(required_column.name), + std::make_shared(nullable_target_type_name)); + auto cast_default = makeASTFunction("_CAST", + default_value, std::make_shared(required_column.type->getName())); - + auto convert_func = makeASTFunction("ifNull", std::move(cast_col), std::move(cast_default)); conversion_expr_list->children.emplace_back(setAlias(convert_func, required_column.name)); continue; } diff --git a/tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.reference b/tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.reference new file mode 100644 index 000000000000..3d59c6f1a4c9 --- /dev/null +++ b/tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.reference @@ -0,0 +1,6 @@ +\N a +42 b + a +42 b + a +42 b diff --git a/tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.sql b/tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.sql new file mode 100644 index 000000000000..647fd572b904 --- /dev/null +++ b/tests/queries/0_stateless/03575_modify_column_null_to_default_cross_type.sql @@ -0,0 +1,14 @@ +-- Tags: no-random-settings, no-random-merge-tree-settings + +-- Test cross-type Nullable(X) -> Y conversion (e.g. Nullable(UInt8) -> String) +-- Follow-up to PR #84770 + +DROP TABLE IF EXISTS nullable_cross_type_test; +CREATE TABLE nullable_cross_type_test (x Nullable(UInt8), y String) ORDER BY tuple(); +INSERT INTO nullable_cross_type_test VALUES (NULL, 'a'), (42, 'b'); +SELECT * FROM nullable_cross_type_test ORDER BY y; +ALTER TABLE nullable_cross_type_test MODIFY COLUMN x String DEFAULT ''; +SELECT * FROM nullable_cross_type_test ORDER BY y; +OPTIMIZE TABLE nullable_cross_type_test FINAL; +SELECT * FROM nullable_cross_type_test ORDER BY y; +DROP TABLE nullable_cross_type_test;