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

Code before:

            /// Extract last key segment for functions like JSONKey that need it.
            std::string_view last_key;
            if (all_const_keys && !moves.empty())
                last_key = moves.back().key;

            String error;
            for (size_t i = 0; i < input_rows_count; ++i)
            {
                Element document = col_const ? Element(*col_object, 0) : Element(*col_object, i);

                bool added_to_column = false;
                Element element;

                if (all_const_keys)
                {
                    if (flat_path.empty())
                        added_to_column = impl.insertResultToColumn(*to, document, last_key, format_settings, error);
                    else if (document.getObject().find(flat_path, element))
                        added_to_column = impl.insertResultToColumn(*to, element, last_key, format_settings, error);
                }
                else
                {
                    std::string_view row_last_key;
                    if (performMoves<ColumnObjectParser, case_insensitive>(arguments, i, document, moves, element, row_last_key))
                        added_to_column = impl.insertResultToColumn(*to, element, row_last_key, format_settings, error);
                }

                if (!added_to_column)
                    to->insertDefault();
            }

            return to;
        }
    };

private:
    /// Represents a move of a JSON iterator described by a single argument passed to a JSON function.
    /// For example, the call JSONExtractInt('{"a": "hello", "b": [-100, 200.0, 300]}', 'b', 1)
    /// contains two moves: {MoveType::ConstKey, "b"} and {MoveType::ConstIndex, 1}.
    /// Keys and indices can be nonconst, in this case they are calculated for each row.
    enum class MoveType : uint8_t
    {
        Key,
        Index,
        ConstKey,
        ConstIndex,
    };

    struct Move
    {

Review comment (refactor):
As we store flattened JSON in `ColumnObject`, it will not work with multiple non-const keys specified in the arguments. For example: `JSONExtract(materialize('{"a" : {"b" : "Hello"}}')::JSON, materialize('a'), materialize('b'), 'String')`.

Code after:
                }
                else if (input_rows_count <= 1 && !arg.column->empty())
                {
                    /// Constant folding: single-row column that isn't wrapped in ColumnConst.
                    key = (*arg.column)[0].safeGet<String>();
                }
                else
                {
                    throw Exception(
                        ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
                        "Function {} with JSON type input supports only constant string path arguments",
                        String(TName::name));
                }

                if (!path.empty())
                    path += '.';
                path += key;
            }

            /// Expand ColumnConst to full column if needed.
            ColumnPtr object_column = col_const ? col_const->convertToFullColumn() : first_column.column;

            /// Root case (no path specified) return defaults for most functions.
            if (path.empty())
                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())
                {