Repository: ClickHouse/ClickHouse
File: src/Analyzer/Passes/FunctionToSubcolumnsPass.cpp (C++)

Code before:
    if (!subcolumn)
        return;

    NameAndTypePair column{ctx.column.name + "." + subcolumn->name, subcolumn->type};
    if (sourceHasColumn(ctx.column_source, column.name) || !canOptimizeToSubcolumn(ctx.column_source, column.name))
        return;
    node = std::make_shared<ColumnNode>(column, ctx.column_source);
}

/// Rewrites JSONExtract*(json_col, 'a', 'b', ...) to read the literal subcolumn (json_col.a.b)
/// directly from storage, providing I/O optimization by avoiding a full JSON column read.
/// The second pass adds a CAST to the function's result type (e.g. Int64).
///
/// We only read the literal subcolumn here (not the subobject subcolumn) because the typed
/// extraction functions (JSONExtractInt/String/Float/Bool) return the default value (0, "", etc.)
/// for paths that contain nested objects: which is exactly what happens when the literal
/// subcolumn is NULL and gets cast to the target type. JSONExtractRaw is NOT registered
/// for this optimization because it needs the subobject subcolumn for nested object serialization;
/// its full literal+subobject merge happens at runtime in runForObjectColumn(..).
///
/// @tparam has_trailing_type_arg TRUE for JSONExtract(col, path..., 'TypeName') where the last
///                               argument is a type name and must be excluded from the path.
///                               FALSE for JSONExtractInt[UInt|Float|Bool|String] where all
///                               arguments after the column are path keys.
template <bool has_trailing_type_arg = false>
void optimizeJSONExtractToSubcolumn(QueryTreeNodePtr & node, FunctionNode & function_node, ColumnContext & ctx)
{
    auto & args = function_node.getArguments().getNodes();

    constexpr size_t min_args = has_trailing_type_arg ? 3 : 2;
    if (args.size() < min_args)
        return;

    /// Build a dotted path from consecutive constant string arguments.
    const size_t path_end = has_trailing_type_arg ? args.size() - 1 : args.size();
    String path;
    for (size_t i = 1; i < path_end; ++i)
    {
        const auto * const_node = args[i]->as<ConstantNode>();
        if (!const_node || const_node->getValue().getType() != Field::Types::String)
            return;
        const auto & key = const_node->getValue().safeGet<String>();
        if (!path.empty())
            path += ".";
        path += key;
    }
    if (path.empty())
        return;

    const auto & data_type_object = assert_cast<const DataTypeObject &>(*ctx.column.type);


Review comment (refactor):
We still need to change this by replacing `JSONExtract` to something like`accurateCastOrDefault(multiIf(json.requested.path is not Null, json.requested.path, notEmpty(json.^requested.path), json.^requested.path, NULL), 'RequestedType')` or remove this optimization for now and add it later when we add new type of subcolumns for JSON.

Code after:
    if (!subcolumn)
        return;

    NameAndTypePair column{ctx.column.name + "." + subcolumn->name, subcolumn->type};
    if (sourceHasColumn(ctx.column_source, column.name) || !canOptimizeToSubcolumn(ctx.column_source, column.name))
        return;
    node = std::make_shared<ColumnNode>(column, ctx.column_source);
}

void optimizeDistinctJSONPaths(QueryTreeNodePtr & node, FunctionNode &, ColumnContext & ctx)
{
    /// Replace distinctJSONPaths(json) to arraySort(groupArrayDistinct(arrayJoin(json.__special_subcolumn_name_for_distinct_paths_calculation)))
    NameAndTypePair column{ctx.column.name + "." + DataTypeObject::SPECIAL_SUBCOLUMN_NAME_FOR_DISTINCT_PATHS_CALCULATION, std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>())};

    auto new_column_node = std::make_shared<ColumnNode>(column, ctx.column_source);
    auto function_array_join_node = std::make_shared<FunctionNode>("arrayJoin");
    function_array_join_node->getArguments().getNodes().push_back(std::move(new_column_node));
    resolveOrdinaryFunctionNodeByName(*function_array_join_node, "arrayJoin", ctx.context);

    auto function_group_array_distinct_node = std::make_shared<FunctionNode>("groupArrayDistinct");
    function_group_array_distinct_node->getArguments().getNodes().push_back(std::move(function_array_join_node));
    resolveAggregateFunctionNodeByName(*function_group_array_distinct_node, "groupArrayDistinct");

    auto function_array_sort_node = std::make_shared<FunctionNode>("arraySort");
    function_array_sort_node->getArguments().getNodes().push_back(std::move(function_group_array_distinct_node));
    resolveOrdinaryFunctionNodeByName(*function_array_sort_node, "arraySort", ctx.context);

    node = std::move(function_array_sort_node);
}

std::map<std::pair<TypeIndex, String>, NodeToSubcolumnTransformer> node_transformers =
{
    {
        {TypeIndex::String, "length"}, optimizeFunctionStringLength,
    },
    {
        {TypeIndex::String, "empty"}, optimizeFunctionStringEmpty<true>,
    },
    {
        {TypeIndex::String, "notEmpty"}, optimizeFunctionStringEmpty<false>,
    },
    {
        {TypeIndex::Array, "length"}, optimizeFunctionLength,
    },
    {
        {TypeIndex::Array, "empty"}, optimizeFunctionEmpty<true>,
    },
    {
        {TypeIndex::Array, "notEmpty"}, optimizeFunctionEmpty<false>,
    },
    {