Repository: ClickHouse/ClickHouse
File: src/Functions/FunctionsJSON.cpp (C++)

Code before:
                return result_type->createColumnConstWithDefaultValue(input_rows_count)->convertToFullColumnIfConst();

            /// Extract literal subcolumn (json.path returns the scalar value at that path).
            auto literal_type = data_type_object.getSubcolumnType(path);
            auto literal_subcolumn = data_type_object.getSubcolumn(path, object_column);

            ColumnPtr merged;
            DataTypePtr merged_type;

            /// When the path has a type hint (literal_type is not Dynamic), the subobject may not be convertible to that type.
            /// For typed paths we skip the subobject merge and use only the literal subcolumn.
            if (literal_type && !isDynamic(literal_type))
            {
                merged = literal_subcolumn;
                merged_type = literal_type;
            }
            else
            {
                /// Extract subobject subcolumn (json.^`path` returns nested object at that path).
                String sub_object_name = "^`" + path + "`";
                auto sub_object_type = data_type_object.getSubcolumnType(sub_object_name);
                ColumnPtr sub_object_subcolumn = data_type_object.getSubcolumn(sub_object_name, object_column);

                /// If neither subcolumn exists, the path is absent thus return defaults.
                if (!literal_subcolumn && !sub_object_subcolumn)
                    return result_type->createColumnConstWithDefaultValue(input_rows_count)->convertToFullColumnIfConst();

                /// Merge literal and subobject subcolumns (same logic as `getObjectElement' in tupleElement.cpp):
                /// For each row: prefer literal (scalar) value, if absent, use subobject (nested JSON).
                if (sub_object_subcolumn->getNumberOfDefaultRows() == sub_object_subcolumn->size())
                {
                    /// No nested subobject at this path (only literal exists).
                    merged = literal_subcolumn;
                    merged_type = literal_type;
                }
                else
                {
                    /// Both exist: merge row-by-row.
                    auto casted_sub_object = castColumn({sub_object_subcolumn, sub_object_type, ""}, literal_type);
                    auto result_col = literal_type->createColumn();
                    for (size_t i = 0; i < input_rows_count; ++i)
                    {
                        if (!literal_subcolumn->isDefaultAt(i))
                            result_col->insertFrom(*literal_subcolumn, i);
                        else if (!sub_object_subcolumn->isDefaultAt(i))
                            result_col->insertFrom(*casted_sub_object, i);
                        else
                            result_col->insertDefault();
                    }
                    merged = std::move(result_col);
                    merged_type = literal_type;

Review comment (bug):
Method `getSubcolumn` will never return nullptr, it will throw an exception if there is no such subcolumn (in our case it should not happen because JSON always has such subcolumns).

Code after:
                return result_type->createColumnConstWithDefaultValue(input_rows_count)->convertToFullColumnIfConst();

            /// Extract literal subcolumn (json.path returns the scalar value at that path).
            auto literal_type = data_type_object.getSubcolumnType(path);
            auto literal_subcolumn = data_type_object.getSubcolumn(path, object_column);

            ColumnPtr merged;
            DataTypePtr merged_type;

            /// When the path has a type hint (literal_type is not Dynamic), the subobject may not be convertible to that type.
            /// For typed paths we skip the subobject merge and use only the literal subcolumn.
            if (literal_type && !isDynamic(literal_type))
            {
                merged = literal_subcolumn;
                merged_type = literal_type;
            }
            else
            {
                /// Extract subobject subcolumn (json.^`path` returns nested object at that path).
                String sub_object_name = "^`" + path + "`";
                auto sub_object_type = data_type_object.getSubcolumnType(sub_object_name);
                ColumnPtr sub_object_subcolumn = data_type_object.getSubcolumn(sub_object_name, object_column);

                /// Merge literal and subobject subcolumns (same logic as `getObjectElement' in tupleElement.cpp):
                /// For each row: prefer literal (scalar) value, if absent, use subobject (nested JSON).
                if (sub_object_subcolumn->getNumberOfDefaultRows() == sub_object_subcolumn->size())
                {
                    /// No nested subobject at this path (only literal exists).
                    merged = literal_subcolumn;
                    merged_type = literal_type;
                }
                else
                {
                    /// Both exist: merge row-by-row.
                    auto casted_sub_object = castColumn({sub_object_subcolumn, sub_object_type, ""}, literal_type);
                    auto result_col = literal_type->createColumn();
                    for (size_t i = 0; i < input_rows_count; ++i)
                    {
                        if (!literal_subcolumn->isDefaultAt(i))
                            result_col->insertFrom(*literal_subcolumn, i);
                        else if (!sub_object_subcolumn->isDefaultAt(i))
                            result_col->insertFrom(*casted_sub_object, i);
                        else
                            result_col->insertDefault();
                    }
                    merged = std::move(result_col);
                    merged_type = literal_type;
                }
            }

            /// For JSONExtractRaw: serialize each value as a JSON string