Coverage Report

Created: 2026-07-12 10:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/parquet/vparquet_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 <gen_cpp/parquet_types.h>
21
#include <stddef.h>
22
#include <stdint.h>
23
24
#include <list>
25
#include <memory>
26
#include <string>
27
#include <tuple>
28
#include <unordered_map>
29
#include <unordered_set>
30
#include <vector>
31
32
#include "common/status.h"
33
#include "format/parquet/parquet_common.h"
34
#include "format/parquet/parquet_predicate.h"
35
#include "format/parquet/vparquet_column_reader.h"
36
#include "format/parquet/vparquet_group_reader.h"
37
#include "format/table/table_format_reader.h"
38
#include "format/table/table_schema_change_helper.h"
39
#include "io/file_factory.h"
40
#include "io/fs/file_meta_cache.h"
41
#include "io/fs/file_reader.h"
42
#include "io/fs/file_reader_writer_fwd.h"
43
#include "roaring/roaring64map.hh"
44
#include "runtime/runtime_profile.h"
45
#include "storage/olap_scan_common.h"
46
#include "util/obj_lru_cache.h"
47
48
namespace cctz {
49
class time_zone;
50
} // namespace cctz
51
namespace doris {
52
class RowDescriptor;
53
class RuntimeState;
54
class SlotDescriptor;
55
class TFileRangeDesc;
56
class TFileScanRangeParams;
57
class TupleDescriptor;
58
59
namespace io {
60
class FileSystem;
61
struct IOContext;
62
} // namespace io
63
class Block;
64
class FileMetaData;
65
class PageIndex;
66
class ShardedKVCache;
67
class VExprContext;
68
struct RowLineageColumns;
69
} // namespace doris
70
71
namespace doris {
72
73
/// Parquet-specific initialization context.
74
/// Extends ReaderInitContext with predicate pushdown fields.
75
struct ParquetInitContext final : public ReaderInitContext {
76
    // Safe defaults for standalone readers (delete file readers, push handler)
77
    // that don't have conjuncts/predicates. Dereferenced by _do_init_reader.
78
    static inline const VExprContextSPtrs EMPTY_CONJUNCTS {};
79
    static inline phmap::flat_hash_map<int, std::vector<std::shared_ptr<ColumnPredicate>>>
80
            EMPTY_SLOT_PREDICATES {};
81
82
    const VExprContextSPtrs* conjuncts = &EMPTY_CONJUNCTS;
83
    phmap::flat_hash_map<int, std::vector<std::shared_ptr<ColumnPredicate>>>*
84
            slot_id_to_predicates = &EMPTY_SLOT_PREDICATES;
85
    const std::unordered_map<std::string, int>* colname_to_slot_id = nullptr;
86
    const VExprContextSPtrs* not_single_slot_filter_conjuncts = nullptr;
87
    const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts = nullptr;
88
    bool filter_groups = true;
89
};
90
91
class ParquetReader : public TableFormatReader {
92
    ENABLE_FACTORY_CREATOR(ParquetReader);
93
94
public:
95
    struct ReaderStatistics {
96
        int32_t filtered_row_groups = 0;
97
        int32_t filtered_row_groups_by_min_max = 0;
98
        int32_t filtered_row_groups_by_expr_zonemap = 0;
99
        int32_t filtered_row_groups_by_bloom_filter = 0;
100
        int32_t read_row_groups = 0;
101
        int64_t filtered_group_rows = 0;
102
        int64_t filtered_page_rows = 0;
103
        int64_t lazy_read_filtered_rows = 0;
104
        int64_t read_rows = 0;
105
        int64_t filtered_bytes = 0;
106
        int64_t column_read_time = 0;
107
        int64_t parse_meta_time = 0;
108
        int64_t parse_footer_time = 0;
109
        int64_t file_footer_read_calls = 0;
110
        int64_t file_footer_hit_cache = 0;
111
        int64_t file_reader_create_time = 0;
112
        int64_t open_file_num = 0;
113
        int64_t row_group_filter_time = 0;
114
        int64_t page_index_filter_time = 0;
115
        int64_t read_page_index_time = 0;
116
        int64_t parse_page_index_time = 0;
117
        int64_t predicate_filter_time = 0;
118
        int64_t dict_filter_rewrite_time = 0;
119
        int64_t bloom_filter_read_time = 0;
120
        int64_t expr_zonemap_unusable_evals = 0;
121
        int64_t in_zonemap_point_check_count = 0;
122
        int64_t in_zonemap_range_only_count = 0;
123
    };
124
125
    ParquetReader(RuntimeProfile* profile, const TFileScanRangeParams& params,
126
                  const TFileRangeDesc& range, size_t batch_size, const cctz::time_zone* ctz,
127
                  io::IOContext* io_ctx, RuntimeState* state, FileMetaCache* meta_cache = nullptr,
128
                  bool enable_lazy_mat = true);
129
130
    ParquetReader(RuntimeProfile* profile, const TFileScanRangeParams& params,
131
                  const TFileRangeDesc& range, size_t batch_size, const cctz::time_zone* ctz,
132
                  std::shared_ptr<io::IOContext> io_ctx_holder, RuntimeState* state,
133
                  FileMetaCache* meta_cache = nullptr, bool enable_lazy_mat = true);
134
135
    ParquetReader(const TFileScanRangeParams& params, const TFileRangeDesc& range,
136
                  io::IOContext* io_ctx, RuntimeState* state, FileMetaCache* meta_cache = nullptr,
137
                  bool enable_lazy_mat = true);
138
139
    ParquetReader(const TFileScanRangeParams& params, const TFileRangeDesc& range,
140
                  std::shared_ptr<io::IOContext> io_ctx_holder, RuntimeState* state,
141
                  FileMetaCache* meta_cache = nullptr, bool enable_lazy_mat = true);
142
143
    ~ParquetReader() override;
144
#ifdef BE_TEST
145
    // for unit test
146
    void set_file_reader(io::FileReaderSPtr file_reader);
147
    void set_conjuncts_for_test(const VExprContextSPtrs& conjuncts) {
148
        _lazy_read_ctx.conjuncts = conjuncts;
149
    }
150
#endif
151
152
    // Override to build table_info_node from Parquet file metadata using by_parquet_name.
153
    // Subclasses (HiveParquetReader, etc.) call GenericReader::on_before_init_reader directly,
154
    // so this override only applies to plain ParquetReader (TVF, load).
155
    Status on_before_init_reader(ReaderInitContext* ctx) override;
156
157
    void set_batch_size(size_t batch_size) override;
158
159
    Status close() override;
160
161
    // set the delete rows in current parquet file
162
2
    void set_delete_rows(const std::vector<int64_t>* delete_rows) { _delete_rows = delete_rows; }
163
164
    // DVs stay compressed in the query cache. Only row ids belonging to the row group being opened
165
    // are materialized for the legacy RowGroupReader position-delete interface.
166
0
    void set_deletion_vector(const roaring::Roaring64Map* deletion_vector) {
167
0
        _deletion_vector = deletion_vector;
168
0
    }
169
170
0
    int64_t size() const { return _file_reader->size(); }
171
172
    Status _get_columns_impl(std::unordered_map<std::string, DataTypePtr>* name_to_type) override;
173
174
    Status init_schema_reader() override;
175
176
    Status get_parsed_schema(std::vector<std::string>* col_names,
177
                             std::vector<DataTypePtr>* col_types) override;
178
179
0
    ReaderStatistics& reader_statistics() { return _reader_statistics; }
180
181
4
    const tparquet::FileMetaData* get_meta_data() const { return _t_metadata; }
182
183
    Status get_file_metadata_schema(const FieldDescriptor** ptr);
184
185
    void set_create_row_id_column_iterator_func(
186
1.29k
            std::function<std::shared_ptr<segment_v2::RowIdColumnIteratorV2>()> create_func) {
187
1.29k
        _create_topn_row_id_column_iterator = create_func;
188
1.29k
    }
189
190
    /// Access current batch row positions (delegates to RowGroupReader).
191
    /// Used by IcebergReaderMixin to build $row_id column.
192
5
    const std::vector<segment_v2::rowid_t>& current_batch_row_positions() const {
193
5
        return _current_group_reader->current_batch_row_positions();
194
5
    }
195
196
    Status fill_topn_row_id(
197
            std::shared_ptr<segment_v2::RowIdColumnIteratorV2> _row_id_column_iterator,
198
5
            std::string col_name, Block* block, size_t rows) {
199
5
        int col_pos = block->get_position_by_name(col_name);
200
5
        DCHECK(col_pos >= 0);
201
5
        if (col_pos < 0) {
202
0
            return Status::InternalError("Column {} not found in block", col_name);
203
0
        }
204
5
        auto column_guard = block->mutate_column_scoped(col_pos);
205
5
        auto& col = column_guard.mutable_column();
206
5
        const auto& row_ids = this->current_batch_row_positions();
207
5
        RETURN_IF_ERROR(
208
5
                _row_id_column_iterator->read_by_rowids(row_ids.data(), row_ids.size(), col));
209
210
5
        return Status::OK();
211
5
    }
212
213
2.47k
    bool count_read_rows() override { return true; }
214
215
    void set_condition_cache_context(std::shared_ptr<ConditionCacheContext> ctx) override;
216
217
4
    bool supports_count_pushdown() const override { return true; }
218
219
    int64_t get_total_rows() const override;
220
221
4
    bool has_delete_operations() const override {
222
4
        return (_delete_rows != nullptr && !_delete_rows->empty()) ||
223
4
               (_deletion_vector != nullptr && !_deletion_vector->isEmpty());
224
4
    }
225
226
    /// Disable row-group range filtering (needed when reading delete files
227
    /// whose TFileRangeDesc has size=-1).
228
0
    void set_filter_groups(bool v) { _filter_groups = v; }
229
230
protected:
231
    // ---- Unified init_reader(ReaderInitContext*) overrides ----
232
    Status _open_file_reader(ReaderInitContext* ctx) override;
233
    Status _do_init_reader(ReaderInitContext* ctx) override;
234
235
    void _collect_profile_before_close() override;
236
237
    // Core block reading implementation
238
    Status _do_get_next_block(Block* block, size_t* read_rows, bool* eof) override;
239
240
    // Parquet fills partition/missing columns per-batch internally via RowGroupReader,
241
    // so suppress TableFormatReader's default on_after_read_block fill.
242
3.38k
    Status on_after_read_block(Block* /*block*/, size_t* /*read_rows*/) override {
243
3.38k
        return Status::OK();
244
3.38k
    }
245
246
    // Protected accessors so CRTP mixin subclasses can reach private members
247
13
    io::IOContext* get_io_ctx() const { return _io_ctx; }
248
0
    std::unordered_map<std::string, uint32_t>*& col_name_to_block_idx_ref() {
249
0
        return _col_name_to_block_idx;
250
0
    }
251
1.45k
    RuntimeProfile* get_profile() const { return _profile; }
252
2.46k
    RuntimeState* get_state() const { return _state; }
253
532
    const TFileScanRangeParams& get_scan_params() const { return _scan_params; }
254
174
    const TFileRangeDesc& get_scan_range() const { return _scan_range; }
255
0
    const TupleDescriptor* get_tuple_descriptor() const { return _tuple_descriptor; }
256
0
    const RowDescriptor* get_row_descriptor() const { return _row_descriptor; }
257
0
    const FileMetaData* get_file_metadata() const { return _file_metadata; }
258
259
private:
260
    struct ParquetProfile {
261
        RuntimeProfile::Counter* filtered_row_groups = nullptr;
262
        RuntimeProfile::Counter* filtered_row_groups_by_min_max = nullptr;
263
        RuntimeProfile::Counter* filtered_row_groups_by_expr_zonemap = nullptr;
264
        RuntimeProfile::Counter* filtered_row_groups_by_bloom_filter = nullptr;
265
        RuntimeProfile::Counter* to_read_row_groups = nullptr;
266
        RuntimeProfile::Counter* total_row_groups = nullptr;
267
        RuntimeProfile::Counter* filtered_group_rows = nullptr;
268
        RuntimeProfile::Counter* filtered_page_rows = nullptr;
269
        RuntimeProfile::Counter* lazy_read_filtered_rows = nullptr;
270
        RuntimeProfile::Counter* filtered_bytes = nullptr;
271
        RuntimeProfile::Counter* raw_rows_read = nullptr;
272
        RuntimeProfile::Counter* column_read_time = nullptr;
273
        RuntimeProfile::Counter* parse_meta_time = nullptr;
274
        RuntimeProfile::Counter* parse_footer_time = nullptr;
275
        RuntimeProfile::Counter* file_reader_create_time = nullptr;
276
        RuntimeProfile::Counter* open_file_num = nullptr;
277
        RuntimeProfile::Counter* row_group_filter_time = nullptr;
278
        RuntimeProfile::Counter* page_index_read_calls = nullptr;
279
        RuntimeProfile::Counter* page_index_filter_time = nullptr;
280
        RuntimeProfile::Counter* read_page_index_time = nullptr;
281
        RuntimeProfile::Counter* parse_page_index_time = nullptr;
282
        RuntimeProfile::Counter* file_footer_read_calls = nullptr;
283
        RuntimeProfile::Counter* file_footer_hit_cache = nullptr;
284
        RuntimeProfile::Counter* decompress_time = nullptr;
285
        RuntimeProfile::Counter* decompress_cnt = nullptr;
286
        RuntimeProfile::Counter* page_read_counter = nullptr;
287
        RuntimeProfile::Counter* page_cache_write_counter = nullptr;
288
        RuntimeProfile::Counter* page_cache_compressed_write_counter = nullptr;
289
        RuntimeProfile::Counter* page_cache_decompressed_write_counter = nullptr;
290
        RuntimeProfile::Counter* page_cache_hit_counter = nullptr;
291
        RuntimeProfile::Counter* page_cache_missing_counter = nullptr;
292
        RuntimeProfile::Counter* page_cache_compressed_hit_counter = nullptr;
293
        RuntimeProfile::Counter* page_cache_decompressed_hit_counter = nullptr;
294
        RuntimeProfile::Counter* decode_header_time = nullptr;
295
        RuntimeProfile::Counter* read_page_header_time = nullptr;
296
        RuntimeProfile::Counter* decode_value_time = nullptr;
297
        RuntimeProfile::Counter* decode_dict_time = nullptr;
298
        RuntimeProfile::Counter* decode_level_time = nullptr;
299
        RuntimeProfile::Counter* decode_null_map_time = nullptr;
300
        RuntimeProfile::Counter* skip_page_header_num = nullptr;
301
        RuntimeProfile::Counter* parse_page_header_num = nullptr;
302
        RuntimeProfile::Counter* predicate_filter_time = nullptr;
303
        RuntimeProfile::Counter* dict_filter_rewrite_time = nullptr;
304
        RuntimeProfile::Counter* convert_time = nullptr;
305
        RuntimeProfile::Counter* bloom_filter_read_time = nullptr;
306
        RuntimeProfile::Counter* expr_zonemap_unusable = nullptr;
307
        RuntimeProfile::Counter* in_zonemap_point_check = nullptr;
308
        RuntimeProfile::Counter* in_zonemap_range_only = nullptr;
309
    };
310
311
    // ---- set_fill_columns sub-functions ----
312
    void _collect_predicate_columns_from_conjuncts(
313
            std::unordered_map<std::string, std::pair<uint32_t, int>>& predicate_columns);
314
    void _classify_columns_for_lazy_read(
315
            const std::unordered_map<std::string, std::pair<uint32_t, int>>& predicate_columns,
316
            const std::unordered_map<std::string, std::tuple<std::string, const SlotDescriptor*>>&
317
                    partition_columns,
318
            const std::unordered_map<std::string, VExprContextSPtr>& missing_columns);
319
320
    Status _open_file();
321
    void _init_profile();
322
    void _close_internal();
323
    Status _next_row_group_reader();
324
    RowGroupReader::PositionDeleteContext _get_position_delete_ctx(
325
            const tparquet::RowGroup& row_group,
326
            const RowGroupReader::RowGroupIndex& row_group_index);
327
    void _init_system_properties();
328
    void _init_file_description();
329
330
    // At the beginning of reading next row group, index should be loaded and used to filter data efficiently.
331
    Status _process_page_index_filter(
332
            const tparquet::RowGroup& row_group,
333
            const RowGroupReader::RowGroupIndex& row_group_index,
334
            const std::vector<std::unique_ptr<MutilColumnBlockPredicate>>& push_down_pred,
335
            RowRanges* candidate_row_ranges);
336
    Status _process_expr_zonemap_page_filter(
337
            ParquetPredicate::CachedPageIndexStat* cached_page_index,
338
            RowRanges* candidate_row_ranges, bool* filtered_row_group_by_expr_zonemap);
339
    bool _expr_zonemap_page_slot_index(const VExprContextSPtr& conjunct, int* cid) const;
340
    bool _has_expr_zonemap_page_filter() const;
341
342
    // check this range contain this row group.
343
    bool _is_misaligned_range_group(const tparquet::RowGroup& row_group) const;
344
345
    // Row Group min-max Filter
346
    Status _process_column_stat_filter(
347
            const tparquet::RowGroup& row_group,
348
            const std::vector<std::unique_ptr<MutilColumnBlockPredicate>>& push_down_pred,
349
            bool* filter_group, bool* filtered_by_min_max, bool* filtered_by_bloom_filter);
350
    Status _process_expr_zonemap_filter(const tparquet::RowGroup& row_group, bool* filter_group);
351
352
    /*
353
     * 1. row group min-max filter
354
     * 2. row group bloom filter
355
     * 3. page index min-max filter
356
     *
357
     * return Status && row_ranges (lines to be read)
358
     */
359
    Status _process_min_max_bloom_filter(
360
            const RowGroupReader::RowGroupIndex& row_group_index,
361
            const tparquet::RowGroup& row_group,
362
            const std::vector<std::unique_ptr<MutilColumnBlockPredicate>>& push_down_pred,
363
            RowRanges* row_ranges);
364
365
    int64_t _get_column_start_offset(
366
            const tparquet::ColumnMetaData& column_init_column_readers) const;
367
0
    std::string _meta_cache_key(const std::string& path) { return "meta_" + path; }
368
    std::vector<io::PrefetchRange> _generate_random_access_ranges(
369
            const RowGroupReader::RowGroupIndex& group, size_t* avg_io_size);
370
    void _collect_profile();
371
372
1.34k
    Status _set_read_one_line_impl() override { return Status::OK(); }
373
374
    bool _exists_in_file(const std::string& expr_name) const;
375
    bool _type_matches(const int cid) const;
376
    void _init_read_columns(const std::vector<std::string>& column_names);
377
378
    io::FileSystemProperties _system_properties;
379
    io::FileDescription _file_description;
380
381
    // the following fields are for parquet meta data cache.
382
    // if _meta_cache is not null, the _file_metadata will be got from _meta_cache,
383
    // and it is owned by _meta_cache_handle.
384
    // if _meta_cache is null, _file_metadata will be managed by _file_metadata_ptr,
385
    // which will be released when deconstructing.
386
    // ATTN: these fields must be before _file_reader, to make sure they will be released
387
    // after _file_reader. Otherwise, there may be heap-use-after-free bug.
388
    ObjLRUCache::CacheHandle _meta_cache_handle;
389
    std::unique_ptr<FileMetaData> _file_metadata_ptr;
390
    const tparquet::FileMetaData* _t_metadata = nullptr;
391
392
    // _tracing_file_reader wraps _file_reader.
393
    // _file_reader is original file reader.
394
    // _tracing_file_reader is tracing file reader with io context.
395
    // If io_ctx is null, _tracing_file_reader will be the same as file_reader.
396
    io::FileReaderSPtr _file_reader = nullptr;
397
    io::FileReaderSPtr _tracing_file_reader = nullptr;
398
    std::unique_ptr<RowGroupReader> _current_group_reader;
399
400
    RowGroupReader::RowGroupIndex _current_row_group_index {-1, 0, 0};
401
    // read to the end of current reader
402
    bool _row_group_eof = true;
403
    size_t _total_groups = 0; // num of groups(stripes) of a parquet(orc) file
404
405
    std::shared_ptr<ConditionCacheContext> _condition_cache_ctx;
406
407
    // Through this node, you can find the file column based on the table column.
408
    std::shared_ptr<TableSchemaChangeHelper::Node> _table_info_node_ptr =
409
            TableSchemaChangeHelper::ConstNode::get_instance();
410
411
    //sequence in file, need to read
412
    std::vector<std::string> _read_table_columns;
413
    std::vector<std::string> _read_file_columns;
414
    // The set of file columns to be read; only columns within this set will be filtered using the min-max predicate.
415
    std::set<std::string> _read_table_columns_set;
416
    // Deleted rows will be marked by Iceberg/Paimon. So we should filter deleted rows when reading it.
417
    const std::vector<int64_t>* _delete_rows = nullptr;
418
    int64_t _delete_rows_index = 0;
419
    const roaring::Roaring64Map* _deletion_vector = nullptr;
420
    std::vector<int64_t> _row_group_deletion_vector_rows;
421
422
    // parquet file reader object
423
    RuntimeProfile* _profile = nullptr;
424
    const TFileScanRangeParams& _scan_params;
425
    const TFileRangeDesc& _scan_range;
426
    size_t _batch_size;
427
    // Bytes-per-row estimate from the previous batch, used to pre-shrink _batch_size
428
    // before reading so that oversized blocks are prevented from the current call onward.
429
    // Zero means no prior data (first batch).
430
    size_t _load_bytes_per_row = 0;
431
    int64_t _range_start_offset;
432
    int64_t _range_size;
433
    const cctz::time_zone* _ctz = nullptr;
434
435
    std::unordered_map<int, tparquet::OffsetIndex> _col_offsets;
436
437
    ReaderStatistics _reader_statistics;
438
    ParquetColumnReader::ColumnStatistics _column_statistics;
439
    ParquetProfile _parquet_profile;
440
    bool _closed = false;
441
    io::IOContext* _io_ctx = nullptr;
442
    std::shared_ptr<io::IOContext> _io_ctx_holder;
443
    RuntimeState* _state = nullptr;
444
    const TupleDescriptor* _tuple_descriptor = nullptr;
445
    const RowDescriptor* _row_descriptor = nullptr;
446
    const FileMetaData* _file_metadata = nullptr;
447
    // Pointer to external column name to block index mapping (from FileScanner)
448
    std::unordered_map<std::string, uint32_t>* _col_name_to_block_idx = nullptr;
449
    bool _enable_lazy_mat = true;
450
    bool _enable_filter_by_min_max = true;
451
    bool _enable_filter_by_bloom_filter = true;
452
    const std::unordered_map<std::string, int>* _colname_to_slot_id = nullptr;
453
    const VExprContextSPtrs* _not_single_slot_filter_conjuncts = nullptr;
454
    const std::unordered_map<int, VExprContextSPtrs>* _slot_id_to_filter_conjuncts = nullptr;
455
    std::unordered_map<tparquet::Type::type, bool> _ignored_stats;
456
0
    size_t get_batch_size() const override { return _batch_size; }
457
458
protected:
459
    // Used for column lazy read. Protected so Iceberg/Paimon subclasses can
460
    // register synthesized columns in on_before_init_reader.
461
    RowGroupReader::LazyReadContext _lazy_read_ctx;
462
    bool _filter_groups = true;
463
464
    std::function<std::shared_ptr<segment_v2::RowIdColumnIteratorV2>()>
465
            _create_topn_row_id_column_iterator;
466
467
private:
468
    std::set<uint64_t> _column_ids;
469
    std::set<uint64_t> _filter_column_ids;
470
471
    std::vector<std::unique_ptr<MutilColumnBlockPredicate>> _push_down_predicates;
472
    Arena _arena;
473
};
474
475
} // namespace doris