Coverage Report

Created: 2026-11-04 23:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format_v2/json/json_reader.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <simdjson/simdjson.h> // IWYU pragma: keep
21
22
#include <memory>
23
#include <optional>
24
#include <string>
25
#include <string_view>
26
#include <unordered_map>
27
#include <vector>
28
29
#include "core/custom_allocator.h"
30
#include "core/data_type_serde/data_type_serde.h"
31
#include "exprs/json_functions.h"
32
#include "format_v2/file_reader.h"
33
#include "gen_cpp/PlanNodes_types.h"
34
#include "runtime/runtime_profile.h"
35
36
namespace doris {
37
class Decompressor;
38
class NewPlainTextLineReader;
39
class SlotDescriptor;
40
class IColumn;
41
} // namespace doris
42
43
namespace doris::format::json {
44
45
struct TransparentStringHash {
46
    using is_transparent = void;
47
2.36k
    size_t operator()(std::string_view value) const {
48
2.36k
        return std::hash<std::string_view> {}(value);
49
2.36k
    }
50
};
51
52
struct CaseInsensitiveStringHash {
53
    using is_transparent = void;
54
4
    size_t operator()(std::string_view value) const {
55
4
        size_t hash = 1469598103934665603ULL;
56
12
        for (const unsigned char c : value) {
57
12
            const unsigned char folded = c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
58
12
            hash = (hash ^ folded) * 1099511628211ULL;
59
12
        }
60
4
        return hash;
61
4
    }
62
};
63
64
struct CaseInsensitiveStringEqual {
65
    using is_transparent = void;
66
2
    bool operator()(std::string_view lhs, std::string_view rhs) const {
67
2
        if (lhs.size() != rhs.size()) return false;
68
8
        for (size_t i = 0; i < lhs.size(); ++i) {
69
6
            const unsigned char left =
70
6
                    lhs[i] >= 'A' && lhs[i] <= 'Z' ? lhs[i] + ('a' - 'A') : lhs[i];
71
6
            const unsigned char right =
72
6
                    rhs[i] >= 'A' && rhs[i] <= 'Z' ? rhs[i] + ('a' - 'A') : rhs[i];
73
6
            if (left != right) return false;
74
6
        }
75
2
        return true;
76
2
    }
77
};
78
79
// FileScannerV2 JSON reader.
80
//
81
// JSON files do not carry an embedded physical schema. The v2 table layer still needs a
82
// file-local schema and FileScanRequest contract, so this reader exposes FE-provided file slots as
83
// v2 file-local columns and performs JSON parsing/materialization directly in the v2 path.
84
class JsonReader final : public FileReader {
85
public:
86
    // `file_slot_descs` is the FE-planned file schema. JSON has no physical schema, so the reader
87
    // exposes these slots as synthetic file-local columns and materializes only the columns
88
    // requested by FileScanRequest.
89
    JsonReader(std::shared_ptr<io::FileSystemProperties>& system_properties,
90
               std::unique_ptr<io::FileDescription>& file_description,
91
               std::shared_ptr<io::IOContext> io_ctx, RuntimeProfile* profile,
92
               const TFileScanRangeParams* scan_params, const TFileRangeDesc& range,
93
               const std::vector<SlotDescriptor*>& file_slot_descs,
94
               TFileCompressType::type range_compress_type = TFileCompressType::UNKNOWN,
95
               std::optional<TUniqueId> stream_load_id = std::nullopt);
96
    ~JsonReader() override;
97
98
    // Initializes scan attributes and builds the synthetic schema from FE slots.
99
    Status init(RuntimeState* state) override;
100
    Status infer_schema(RuntimeState* state, std::vector<ColumnDefinition>* schema);
101
    Status get_schema(std::vector<ColumnDefinition>* file_schema) const override;
102
    std::unique_ptr<TableColumnMapper> create_column_mapper(
103
            TableColumnMapperOptions options) const override;
104
    // Opens the underlying file or stream and binds requested local column ids to output block
105
    // positions. After this call, `get_block` can be called until it returns eof.
106
    Status open(std::shared_ptr<FileScanRequest> request) override;
107
    // Appends rows into `file_block` according to the FileScanRequest order. The block must already
108
    // contain columns matching the requested positions.
109
    Status get_block(Block* file_block, size_t* rows, bool* eof) override;
110
    Status close() override;
111
112
#ifdef BE_TEST
113
27
    size_t TEST_document_buffer_size() const { return _document_buffer.size(); }
114
#endif
115
116
private:
117
    void _init_profile() override;
118
    // A requested column keeps both identities:
119
    // - `source_index`: index in FE file slots, used for jsonpaths and SerDe lookup.
120
    // - `block_position`: index in the caller's output block, used for materialization.
121
    struct RequestedColumn {
122
        LocalColumnId file_column_id = LocalColumnId::invalid();
123
        LocalIndex block_position;
124
        size_t source_index = 0;
125
        SlotDescriptor* slot_desc = nullptr;
126
        DataTypeSerDeSPtr serde;
127
    };
128
129
    Status _build_requested_columns(const FileScanRequest& request,
130
                                    std::vector<RequestedColumn>* columns) const;
131
    // Reconciles TableReader's split/range descriptor with FileReader's concrete file description.
132
    TFileRangeDesc _json_range() const;
133
    Status _open_file_reader(bool need_schema = false);
134
    Status _create_decompressor();
135
    Status _create_line_reader();
136
    Status _parse_jsonpath_and_json_root();
137
    // Reads one logical JSON document: one line, one file range, or one pipe message.
138
    // Chunked HTTP transfers concatenate their pipe buffers into one document.
139
    Status _read_one_document(size_t* size, bool* eof);
140
    Status _read_one_document_from_pipe(size_t* read_size);
141
    // Moves the logical document into a simdjson-padded buffer and creates an ondemand document.
142
    Status _parse_next_json(size_t* size, bool* eof);
143
    // Applies json_root and validates the object/array shape required by strip_outer_array.
144
    Status _extract_json_value(size_t size, bool* eof, bool* is_empty_row);
145
    Status _append_rows_from_current_value(Block* block, bool* is_empty_row, bool* eof);
146
    Status _append_simple_json_rows(Block* block, bool* is_empty_row, bool* eof);
147
    Status _append_flat_array_jsonpath_rows(Block* block, bool* is_empty_row, bool* eof);
148
    Status _append_nested_jsonpath_row(Block* block, bool* is_empty_row, bool* eof);
149
    Status _set_column_values_from_object(simdjson::ondemand::object* object_value, Block* block,
150
                                          bool* valid);
151
    Status _has_matching_jsonpath(simdjson::ondemand::object* object_value, bool* matches) const;
152
    Status _write_columns_by_jsonpath(simdjson::ondemand::object* object_value, Block* block,
153
                                      bool* valid);
154
    template <bool use_string_cache>
155
    Status _write_data_to_column(simdjson::ondemand::value& value, const DataTypePtr& type_desc,
156
                                 IColumn* column_ptr, const std::string& column_name,
157
                                 const DataTypeSerDeSPtr& serde, bool* valid);
158
    Status _fill_missing_column(const RequestedColumn& column, IColumn* column_ptr, bool* valid);
159
    Status _append_null_for_malformed_json(Block* block);
160
    Status _handle_json_error(const Status& status, Block* block, size_t original_rows,
161
                              bool* is_empty_row);
162
    Status _apply_filters(Block* file_block, size_t* rows);
163
    void _truncate_block_to_rows(Block* block, size_t num_rows);
164
    void _pop_back_last_inserted_value(Block* block, size_t column_index);
165
    size_t _column_index(std::string_view key, size_t key_index);
166
    bool _is_root_path_for_column(const RequestedColumn& column) const;
167
168
    const TFileScanRangeParams* _scan_params = nullptr;
169
    TFileRangeDesc _range;
170
    TFileRangeDesc _reader_range;
171
    std::vector<SlotDescriptor*> _source_file_slot_descs;
172
    DataTypeSerDeSPtrs _source_serdes;
173
    std::vector<ColumnDefinition> _file_schema;
174
    RuntimeState* _runtime_state = nullptr;
175
    TFileCompressType::type _range_compress_type = TFileCompressType::UNKNOWN;
176
    std::optional<TUniqueId> _stream_load_id;
177
    std::vector<RequestedColumn> _requested_columns;
178
    std::unordered_map<std::string, size_t, TransparentStringHash, std::equal_to<>>
179
            _slot_name_to_index;
180
    std::unordered_map<std::string, size_t, CaseInsensitiveStringHash, CaseInsensitiveStringEqual>
181
            _hive_slot_name_to_index;
182
    std::vector<size_t> _previous_positions;
183
184
    RuntimeProfile::Counter* _total_time = nullptr;
185
    RuntimeProfile::Counter* _open_time = nullptr;
186
    RuntimeProfile::Counter* _read_document_time = nullptr;
187
    RuntimeProfile::Counter* _parse_time = nullptr;
188
    RuntimeProfile::Counter* _materialize_time = nullptr;
189
    RuntimeProfile::Counter* _filter_time = nullptr;
190
191
    io::FileReaderSPtr _physical_file_reader;
192
    std::unique_ptr<Decompressor> _decompressor;
193
    std::unique_ptr<NewPlainTextLineReader> _line_reader;
194
    int64_t _current_offset = 0;
195
    // Partition-only scans have no file columns, so Block::rows() cannot count their records.
196
    size_t _logical_rows = 0;
197
    bool _reader_eof = false;
198
    bool _skip_first_line = false;
199
    bool _single_document_read = false;
200
201
    std::string _line_delimiter;
202
    size_t _line_delimiter_length = 0;
203
    std::string _jsonpaths;
204
    std::string _json_root;
205
    bool _read_json_by_line = false;
206
    bool _strip_outer_array = false;
207
    bool _num_as_string = false;
208
    bool _fuzzy_parse = false;
209
    bool _is_hive_table = false;
210
    bool _openx_json_ignore_malformed = false;
211
    TFileCompressType::type _file_compress_type = TFileCompressType::UNKNOWN;
212
213
    std::vector<std::vector<JsonPath>> _parsed_jsonpaths;
214
    std::vector<JsonPath> _parsed_json_root;
215
    bool _parsed_from_json_root = false;
216
    DataTypeSerDe::FormatOptions _serde_options;
217
218
    // simdjson ondemand values point into `_padding_buffer`, so the buffer must outlive all values
219
    // created from the current document.
220
    std::unique_ptr<simdjson::ondemand::parser> _json_parser;
221
    simdjson::ondemand::document _original_json_doc;
222
    simdjson::ondemand::value _json_value;
223
    simdjson::ondemand::array _array;
224
    simdjson::ondemand::array_iterator _array_iter;
225
    std::string _document_buffer;
226
    std::string_view _document_view;
227
    std::string _padding_buffer;
228
    size_t _original_doc_size = 0;
229
    size_t _padded_size = 1024 * 1024 * 8 + simdjson::SIMDJSON_PADDING;
230
    std::unordered_map<std::string_view, std::string_view> _cached_string_values;
231
};
232
233
} // namespace doris::format::json