@@ -366,6 +366,18 @@ std::map<std::pair<TypeIndex, String>, NodeToSubcolumnTransformer> node_transfor
366366 NameAndTypePair column{ctx.column .name + " .null" , std::make_shared<DataTypeUInt8>()};
367367 if (sourceHasColumn (ctx.column_source , column.name ) || !canOptimizeToSubcolumn (ctx.column_source , column.name ))
368368 return ;
369+
370+ // / For nested Nullable types (e.g. Nullable(Tuple(... Nullable(T) ...))),
371+ // / the .null subcolumn in storage is Nullable(UInt8), not UInt8.
372+ // / Using it with a hardcoded UInt8 type causes a type mismatch at runtime.
373+ if (auto * table_node = ctx.column_source ->as <TableNode>())
374+ {
375+ auto actual = table_node->getStorageSnapshot ()->tryGetColumn (
376+ GetColumnsOptions (GetColumnsOptions::All).withRegularSubcolumns (), column.name );
377+ if (actual && actual->type ->isNullable ())
378+ return ;
379+ }
380+
369381 node = std::make_shared<ColumnNode>(column, ctx.column_source );
370382 },
371383 },
@@ -377,6 +389,16 @@ std::map<std::pair<TypeIndex, String>, NodeToSubcolumnTransformer> node_transfor
377389 NameAndTypePair column{ctx.column .name + " .null" , std::make_shared<DataTypeUInt8>()};
378390 if (sourceHasColumn (ctx.column_source , column.name ) || !canOptimizeToSubcolumn (ctx.column_source , column.name ))
379391 return ;
392+
393+ // / Same guard as isNull above: nested Nullable .null subcolumn may itself be Nullable.
394+ if (auto * table_node = ctx.column_source ->as <TableNode>())
395+ {
396+ auto actual = table_node->getStorageSnapshot ()->tryGetColumn (
397+ GetColumnsOptions (GetColumnsOptions::All).withRegularSubcolumns (), column.name );
398+ if (actual && actual->type ->isNullable ())
399+ return ;
400+ }
401+
380402 auto & function_arguments_nodes = function_node.getArguments ().getNodes ();
381403
382404 function_arguments_nodes = {std::make_shared<ColumnNode>(column, ctx.column_source )};
0 commit comments