Coverage Report

Created: 2026-07-29 00:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/parquet/vparquet_group_reader.cpp
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
#include "format/parquet/vparquet_group_reader.h"
19
20
#include <gen_cpp/Exprs_types.h>
21
#include <gen_cpp/Opcodes_types.h>
22
#include <gen_cpp/Types_types.h>
23
#include <gen_cpp/parquet_types.h>
24
#include <string.h>
25
26
#include <algorithm>
27
#include <boost/iterator/iterator_facade.hpp>
28
#include <memory>
29
#include <numeric>
30
#include <ostream>
31
32
#include "common/config.h"
33
#include "common/logging.h"
34
#include "common/object_pool.h"
35
#include "common/status.h"
36
#include "core/assert_cast.h"
37
#include "core/block/block.h"
38
#include "core/block/column_with_type_and_name.h"
39
#include "core/column/column.h"
40
#include "core/column/column_const.h"
41
#include "core/column/column_nullable.h"
42
#include "core/column/column_string.h"
43
#include "core/column/column_vector.h"
44
#include "core/custom_allocator.h"
45
#include "core/data_type/data_type.h"
46
#include "core/data_type/data_type_string.h"
47
#include "core/data_type/define_primitive_type.h"
48
#include "core/pod_array.h"
49
#include "core/types.h"
50
#include "exprs/create_predicate_function.h"
51
#include "exprs/hybrid_set.h"
52
#include "exprs/vdirect_in_predicate.h"
53
#include "exprs/vectorized_fn_call.h"
54
#include "exprs/vexpr.h"
55
#include "exprs/vexpr_context.h"
56
#include "exprs/vliteral.h"
57
#include "exprs/vslot_ref.h"
58
#include "format/parquet/schema_desc.h"
59
#include "format/parquet/vparquet_column_reader.h"
60
#include "format/table/iceberg_reader.h"
61
#include "runtime/descriptors.h"
62
#include "runtime/runtime_state.h"
63
#include "runtime/thread_context.h"
64
#include "storage/segment/column_reader.h"
65
66
namespace cctz {
67
class time_zone;
68
} // namespace cctz
69
namespace doris {
70
class RuntimeState;
71
72
namespace io {
73
struct IOContext;
74
} // namespace io
75
} // namespace doris
76
77
namespace doris {
78
79
const std::vector<int64_t> RowGroupReader::NO_DELETE = {};
80
// See MAX_DICT_CODE_PREDICATE_TO_REWRITE in vorc_reader.cpp; kept effectively unlimited
81
// because admission is gated on the dictionary's total size at eval time.
82
static constexpr uint32_t MAX_DICT_CODE_PREDICATE_TO_REWRITE = std::numeric_limits<uint32_t>::max();
83
84
RowGroupReader::RowGroupReader(io::FileReaderSPtr file_reader,
85
                               const std::vector<std::string>& read_columns,
86
                               const int32_t row_group_id, const tparquet::RowGroup& row_group,
87
                               const cctz::time_zone* ctz, io::IOContext* io_ctx,
88
                               const PositionDeleteContext& position_delete_ctx,
89
                               const LazyReadContext& lazy_read_ctx, RuntimeState* state,
90
                               const std::set<uint64_t>& column_ids,
91
                               const std::set<uint64_t>& filter_column_ids)
92
44
        : _file_reader(file_reader),
93
44
          _read_table_columns(read_columns),
94
44
          _row_group_id(row_group_id),
95
44
          _row_group_meta(row_group),
96
44
          _remaining_rows(row_group.num_rows),
97
44
          _ctz(ctz),
98
44
          _io_ctx(io_ctx),
99
44
          _position_delete_ctx(position_delete_ctx),
100
44
          _lazy_read_ctx(lazy_read_ctx),
101
44
          _state(state),
102
44
          _obj_pool(new ObjectPool()),
103
44
          _column_ids(column_ids),
104
44
          _filter_column_ids(filter_column_ids) {}
105
106
44
RowGroupReader::~RowGroupReader() {
107
44
    if (_obj_pool != nullptr) {
108
44
        _obj_pool->clear();
109
44
    }
110
44
}
111
112
Status RowGroupReader::init(
113
        const FieldDescriptor& schema, RowRanges& row_ranges,
114
        std::unordered_map<int, tparquet::OffsetIndex>& col_offsets,
115
        const TupleDescriptor* tuple_descriptor, const RowDescriptor* row_descriptor,
116
        const std::unordered_map<std::string, int>* colname_to_slot_id,
117
        const VExprContextSPtrs* not_single_slot_filter_conjuncts,
118
43
        const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts) {
119
43
    _tuple_descriptor = tuple_descriptor;
120
43
    _row_descriptor = row_descriptor;
121
43
    _col_name_to_slot_id = colname_to_slot_id;
122
43
    _slot_id_to_filter_conjuncts = slot_id_to_filter_conjuncts;
123
43
    _read_ranges = row_ranges;
124
43
    _filter_read_ranges_by_condition_cache();
125
43
    _remaining_rows = _read_ranges.count();
126
127
43
    if (_read_table_columns.empty()) {
128
        // Query task that only select columns in path.
129
1
        return Status::OK();
130
1
    }
131
42
    const size_t MAX_GROUP_BUF_SIZE = config::parquet_rowgroup_max_buffer_mb << 20;
132
42
    const size_t MAX_COLUMN_BUF_SIZE = config::parquet_column_max_buffer_mb << 20;
133
42
    size_t max_buf_size =
134
42
            std::min(MAX_COLUMN_BUF_SIZE, MAX_GROUP_BUF_SIZE / _read_table_columns.size());
135
114
    for (const auto& read_table_col : _read_table_columns) {
136
114
        auto read_file_col = _table_info_node_ptr->children_file_column_name(read_table_col);
137
114
        auto* field = schema.get_column(read_file_col);
138
114
        std::unique_ptr<ParquetColumnReader> reader;
139
114
        RETURN_IF_ERROR(ParquetColumnReader::create(
140
114
                _file_reader, field, _row_group_meta, _read_ranges, _ctz, _io_ctx, reader,
141
114
                max_buf_size, col_offsets, _state, false, _column_ids, _filter_column_ids));
142
114
        if (reader == nullptr) {
143
0
            VLOG_DEBUG << "Init row group(" << _row_group_id << ") reader failed";
144
0
            return Status::Corruption("Init row group reader failed");
145
0
        }
146
114
        _column_readers[read_table_col] = std::move(reader);
147
114
    }
148
149
42
    bool disable_dict_filter = false;
150
42
    if (not_single_slot_filter_conjuncts != nullptr && !not_single_slot_filter_conjuncts->empty()) {
151
0
        disable_dict_filter = true;
152
0
        _filter_conjuncts.insert(_filter_conjuncts.end(), not_single_slot_filter_conjuncts->begin(),
153
0
                                 not_single_slot_filter_conjuncts->end());
154
0
    }
155
156
    // Check if single slot can be filtered by dict.
157
42
    if (_slot_id_to_filter_conjuncts && !_slot_id_to_filter_conjuncts->empty()) {
158
2
        const std::vector<std::string>& predicate_col_names =
159
2
                _lazy_read_ctx.predicate_columns.first;
160
2
        const std::vector<int>& predicate_col_slot_ids = _lazy_read_ctx.predicate_columns.second;
161
4
        for (size_t i = 0; i < predicate_col_names.size(); ++i) {
162
2
            const std::string& predicate_col_name = predicate_col_names[i];
163
2
            int slot_id = predicate_col_slot_ids[i];
164
165
2
            if (!_table_format_reader->has_column_optimization(
166
2
                        predicate_col_name,
167
2
                        TableFormatReader::ColumnOptimizationTypes::DICT_FILTER)) {
168
                // Row-lineage style generated columns cannot participate in dict filtering.
169
0
                if (_slot_id_to_filter_conjuncts->find(slot_id) !=
170
0
                    _slot_id_to_filter_conjuncts->end()) {
171
0
                    for (auto& ctx : _slot_id_to_filter_conjuncts->at(slot_id)) {
172
0
                        _filter_conjuncts.push_back(ctx);
173
0
                    }
174
0
                }
175
0
                continue;
176
0
            }
177
178
2
            auto predicate_file_col_name =
179
2
                    _table_info_node_ptr->children_file_column_name(predicate_col_name);
180
2
            auto field = schema.get_column(predicate_file_col_name);
181
2
            if (!disable_dict_filter && !_lazy_read_ctx.has_complex_type &&
182
2
                _can_filter_by_dict(
183
2
                        slot_id, _row_group_meta.columns[field->physical_column_index].meta_data)) {
184
0
                _dict_filter_cols.emplace_back(std::make_pair(predicate_col_name, slot_id));
185
2
            } else {
186
2
                if (_slot_id_to_filter_conjuncts->find(slot_id) !=
187
2
                    _slot_id_to_filter_conjuncts->end()) {
188
2
                    for (auto& ctx : _slot_id_to_filter_conjuncts->at(slot_id)) {
189
2
                        _filter_conjuncts.push_back(ctx);
190
2
                    }
191
2
                }
192
2
            }
193
2
        }
194
        // Add predicate_partition_columns in _slot_id_to_filter_conjuncts(single slot conjuncts)
195
        // to _filter_conjuncts, others should be added from not_single_slot_filter_conjuncts.
196
2
        for (auto& kv : _lazy_read_ctx.predicate_partition_columns) {
197
2
            auto& [value, slot_desc] = kv.second;
198
2
            auto iter = _slot_id_to_filter_conjuncts->find(slot_desc->id());
199
2
            if (iter != _slot_id_to_filter_conjuncts->end()) {
200
2
                for (auto& ctx : iter->second) {
201
2
                    _filter_conjuncts.push_back(ctx);
202
2
                }
203
2
            }
204
2
        }
205
        //For check missing column :   missing column == xx, missing column is null,missing column is not null.
206
2
        _filter_conjuncts.insert(_filter_conjuncts.end(),
207
2
                                 _lazy_read_ctx.missing_columns_conjuncts.begin(),
208
2
                                 _lazy_read_ctx.missing_columns_conjuncts.end());
209
2
        RETURN_IF_ERROR(_rewrite_dict_predicates());
210
2
    }
211
    // _state is nullptr in some ut.
212
42
    if (_state && _state->enable_adjust_conjunct_order_by_cost()) {
213
4
        std::ranges::stable_sort(_filter_conjuncts, [](const auto& a, const auto& b) {
214
2
            return a->execute_cost() < b->execute_cost();
215
2
        });
216
4
    }
217
42
    return Status::OK();
218
42
}
219
220
bool RowGroupReader::_can_filter_by_dict(int slot_id,
221
2
                                         const tparquet::ColumnMetaData& column_metadata) {
222
2
    SlotDescriptor* slot = nullptr;
223
2
    const std::vector<SlotDescriptor*>& slots = _tuple_descriptor->slots();
224
4
    for (auto each : slots) {
225
4
        if (each->id() == slot_id) {
226
2
            slot = each;
227
2
            break;
228
2
        }
229
4
    }
230
2
    if (!is_string_type(slot->type()->get_primitive_type()) &&
231
2
        !is_var_len_object(slot->type()->get_primitive_type())) {
232
2
        return false;
233
2
    }
234
0
    if (column_metadata.type != tparquet::Type::BYTE_ARRAY) {
235
0
        return false;
236
0
    }
237
238
0
    if (!is_dictionary_encoded(column_metadata)) {
239
0
        return false;
240
0
    }
241
242
0
    if (_slot_id_to_filter_conjuncts->find(slot_id) == _slot_id_to_filter_conjuncts->end()) {
243
0
        return false;
244
0
    }
245
246
    // TODO: The current implementation of dictionary filtering does not take into account
247
    //  the implementation of NULL values because the dictionary itself does not contain
248
    //  NULL value encoding. As a result, many NULL-related functions or expressions
249
    //  cannot work properly, such as is null, is not null, coalesce, etc.
250
    //  can_push_down_to_dict_filter enforces this by rejecting NULL-sensitive and
251
    //  non-deterministic exprs, so a value-derived predicate like split_by_string(col,
252
    //  sep)[n] = 'x' can also be evaluated on the dictionary, not just a bare column ref.
253
    //  all_of is required: the column is physically rewritten into an int dict-code
254
    //  column, so every conjunct on this slot must be dict-evaluable. Cross-column
255
    //  contamination is already avoided upstream: any multi-slot conjunct disables dict
256
    //  filtering for the whole reader (see set_position_and_ctxs / disable_dict_filter).
257
0
    bool allow_expr = _state == nullptr || _state->query_options().enable_dict_filter_for_expr;
258
0
    return std::ranges::all_of(_slot_id_to_filter_conjuncts->at(slot_id), [&](const auto& ctx) {
259
0
        return VExpr::can_push_down_to_dict_filter(ctx->root(), slot_id, allow_expr);
260
0
    });
261
0
}
262
263
// This function is copied from
264
// https://github.com/apache/impala/blob/master/be/src/exec/parquet/hdfs-parquet-scanner.cc#L1717
265
1
bool RowGroupReader::is_dictionary_encoded(const tparquet::ColumnMetaData& column_metadata) {
266
    // The Parquet spec allows for column chunks to have mixed encodings
267
    // where some data pages are dictionary-encoded and others are plain
268
    // encoded. For example, a Parquet file writer might start writing
269
    // a column chunk as dictionary encoded, but it will switch to plain
270
    // encoding if the dictionary grows too large.
271
    //
272
    // In order for dictionary filters to skip the entire row group,
273
    // the conjuncts must be evaluated on column chunks that are entirely
274
    // encoded with the dictionary encoding. There are two checks
275
    // available to verify this:
276
    // 1. The encoding_stats field on the column chunk metadata provides
277
    //    information about the number of data pages written in each
278
    //    format. This allows for a specific check of whether all the
279
    //    data pages are dictionary encoded.
280
    // 2. The encodings field on the column chunk metadata lists the
281
    //    encodings used. If this list contains the dictionary encoding
282
    //    and does not include unexpected encodings (i.e. encodings not
283
    //    associated with definition/repetition levels), then it is entirely
284
    //    dictionary encoded.
285
1
    if (column_metadata.__isset.encoding_stats) {
286
        // Condition #1 above
287
2
        for (const tparquet::PageEncodingStats& enc_stat : column_metadata.encoding_stats) {
288
2
            if ((enc_stat.page_type == tparquet::PageType::DATA_PAGE ||
289
2
                 enc_stat.page_type == tparquet::PageType::DATA_PAGE_V2) &&
290
2
                (enc_stat.encoding != tparquet::Encoding::PLAIN_DICTIONARY &&
291
2
                 enc_stat.encoding != tparquet::Encoding::RLE_DICTIONARY) &&
292
2
                enc_stat.count > 0) {
293
1
                return false;
294
1
            }
295
2
        }
296
1
    } else {
297
        // Condition #2 above
298
0
        bool has_dict_encoding = false;
299
0
        bool has_nondict_encoding = false;
300
0
        for (const tparquet::Encoding::type& encoding : column_metadata.encodings) {
301
0
            if (encoding == tparquet::Encoding::PLAIN_DICTIONARY ||
302
0
                encoding == tparquet::Encoding::RLE_DICTIONARY) {
303
0
                has_dict_encoding = true;
304
0
            }
305
306
            // RLE and BIT_PACKED are used for repetition/definition levels
307
0
            if (encoding != tparquet::Encoding::PLAIN_DICTIONARY &&
308
0
                encoding != tparquet::Encoding::RLE_DICTIONARY &&
309
0
                encoding != tparquet::Encoding::RLE && encoding != tparquet::Encoding::BIT_PACKED) {
310
0
                has_nondict_encoding = true;
311
0
                break;
312
0
            }
313
0
        }
314
        // Not entirely dictionary encoded if:
315
        // 1. No dictionary encoding listed
316
        // OR
317
        // 2. Some non-dictionary encoding is listed
318
0
        if (!has_dict_encoding || has_nondict_encoding) {
319
0
            return false;
320
0
        }
321
0
    }
322
323
0
    return true;
324
1
}
325
326
Status RowGroupReader::next_batch(Block* block, size_t batch_size, size_t* read_rows,
327
93
                                  bool* batch_eof) {
328
93
    if (_is_row_group_filtered) {
329
0
        *read_rows = 0;
330
0
        *batch_eof = true;
331
0
        return Status::OK();
332
0
    }
333
334
    // Process external table query task that select columns are all from path.
335
93
    if (_read_table_columns.empty()) {
336
11
        int64_t batch_base_row = _total_read_rows;
337
11
        RETURN_IF_ERROR(_read_empty_batch(batch_size, read_rows, batch_eof));
338
339
11
        DCHECK(_table_format_reader);
340
11
        RETURN_IF_ERROR(_table_format_reader->on_fill_partition_columns(
341
11
                block, *read_rows, _lazy_read_ctx.partition_col_names));
342
11
        RETURN_IF_ERROR(_table_format_reader->on_fill_missing_columns(
343
11
                block, *read_rows, _lazy_read_ctx.missing_col_names));
344
11
        RETURN_IF_ERROR(_table_format_reader->fill_synthesized_columns(block, *read_rows));
345
11
        RETURN_IF_ERROR(_table_format_reader->fill_generated_columns(block, *read_rows));
346
11
        std::vector<uint32_t> columns_to_filter(block->columns());
347
22
        for (uint32_t i = 0; i < columns_to_filter.size(); ++i) {
348
11
            columns_to_filter[i] = i;
349
11
        }
350
11
        IColumn::Filter result_filter;
351
11
        RETURN_IF_ERROR(VExprContext::execute_conjuncts_and_filter_block(
352
11
                _lazy_read_ctx.conjuncts, block, columns_to_filter, block->columns(),
353
11
                result_filter));
354
11
        _mark_condition_cache_granules(result_filter.data(), *read_rows, batch_base_row);
355
11
        *read_rows = block->rows();
356
11
        return Status::OK();
357
11
    }
358
82
    if (_lazy_read_ctx.can_lazy_read) {
359
        // call _do_lazy_read recursively when current batch is skipped
360
11
        return _do_lazy_read(block, batch_size, read_rows, batch_eof);
361
71
    } else {
362
71
        FilterMap filter_map;
363
71
        int64_t batch_base_row = _total_read_rows;
364
71
        RETURN_IF_ERROR((_read_column_data(block, _lazy_read_ctx.all_read_columns, batch_size,
365
71
                                           read_rows, batch_eof, filter_map)));
366
71
        DCHECK(_table_format_reader);
367
71
        RETURN_IF_ERROR(_table_format_reader->on_fill_partition_columns(
368
71
                block, *read_rows, _lazy_read_ctx.partition_col_names));
369
71
        RETURN_IF_ERROR(_table_format_reader->on_fill_missing_columns(
370
71
                block, *read_rows, _lazy_read_ctx.missing_col_names));
371
372
71
        if (_need_current_batch_row_positions()) {
373
5
            RETURN_IF_ERROR(_get_current_batch_row_id(*read_rows));
374
5
        }
375
71
        RETURN_IF_ERROR(_table_format_reader->fill_synthesized_columns(block, *read_rows));
376
71
        RETURN_IF_ERROR(_table_format_reader->fill_generated_columns(block, *read_rows));
377
378
71
#ifndef NDEBUG
379
188
        for (auto col : *block) {
380
188
            col.column->sanity_check();
381
188
            DCHECK(block->rows() == col.column->size())
382
0
                    << absl::Substitute("block rows = $0 , column rows = $1, col name = $2",
383
0
                                        block->rows(), col.column->size(), col.name);
384
188
        }
385
71
#endif
386
387
71
        if (block->rows() == 0) {
388
0
            RETURN_IF_ERROR(_convert_dict_cols_to_string_cols(block));
389
0
            *read_rows = block->rows();
390
0
#ifndef NDEBUG
391
0
            for (auto col : *block) {
392
0
                col.column->sanity_check();
393
0
                DCHECK(block->rows() == col.column->size())
394
0
                        << absl::Substitute("block rows = $0 , column rows = $1, col name = $2",
395
0
                                            block->rows(), col.column->size(), col.name);
396
0
            }
397
0
#endif
398
0
            return Status::OK();
399
0
        }
400
71
        {
401
71
            SCOPED_RAW_TIMER(&_predicate_filter_time);
402
71
            RETURN_IF_ERROR(_build_pos_delete_filter(*read_rows));
403
404
71
            std::vector<uint32_t> columns_to_filter;
405
71
            int column_to_keep = block->columns();
406
71
            columns_to_filter.resize(column_to_keep);
407
259
            for (uint32_t i = 0; i < column_to_keep; ++i) {
408
188
                columns_to_filter[i] = i;
409
188
            }
410
71
            if (!_lazy_read_ctx.conjuncts.empty()) {
411
11
                std::vector<IColumn::Filter*> filters;
412
11
                if (_position_delete_ctx.has_filter) {
413
0
                    filters.push_back(_pos_delete_filter_ptr.get());
414
0
                }
415
11
                IColumn::Filter result_filter(block->rows(), 1);
416
11
                bool can_filter_all = false;
417
418
11
                {
419
11
                    RETURN_IF_ERROR_OR_CATCH_EXCEPTION(VExprContext::execute_conjuncts(
420
11
                            _filter_conjuncts, &filters, block, &result_filter, &can_filter_all));
421
11
                }
422
423
                // Condition cache MISS: mark granules with surviving rows (non-lazy path)
424
11
                if (!can_filter_all) {
425
11
                    _mark_condition_cache_granules(result_filter.data(), block->rows(),
426
11
                                                   batch_base_row);
427
11
                }
428
429
11
                if (can_filter_all) {
430
0
                    block->clear_column_data(columns_to_filter);
431
0
                    Block::erase_useless_column(block, column_to_keep);
432
0
                    RETURN_IF_ERROR(_convert_dict_cols_to_string_cols(block));
433
0
                    return Status::OK();
434
0
                }
435
436
11
                RETURN_IF_CATCH_EXCEPTION(
437
11
                        Block::filter_block_internal(block, columns_to_filter, result_filter));
438
11
                Block::erase_useless_column(block, column_to_keep);
439
60
            } else {
440
60
                RETURN_IF_CATCH_EXCEPTION(
441
60
                        RETURN_IF_ERROR(_filter_block(block, column_to_keep, columns_to_filter)));
442
60
            }
443
71
            RETURN_IF_ERROR(_convert_dict_cols_to_string_cols(block));
444
71
        }
445
71
#ifndef NDEBUG
446
188
        for (auto col : *block) {
447
188
            col.column->sanity_check();
448
188
            DCHECK(block->rows() == col.column->size())
449
0
                    << absl::Substitute("block rows = $0 , column rows = $1, col name = $2",
450
0
                                        block->rows(), col.column->size(), col.name);
451
188
        }
452
71
#endif
453
71
        *read_rows = block->rows();
454
71
        return Status::OK();
455
71
    }
456
82
}
457
458
// Maps each batch row to its global parquet file position via _read_ranges, then marks
459
// the corresponding condition cache granule as true if the filter indicates the row survived.
460
// batch_seq_start is the number of rows already read sequentially before this batch
461
// (i.e., _total_read_rows before the batch started).
462
void RowGroupReader::_mark_condition_cache_granules(const uint8_t* filter_data, size_t num_rows,
463
33
                                                    int64_t batch_seq_start) {
464
33
    if (!_condition_cache_ctx || _condition_cache_ctx->is_hit) {
465
33
        return;
466
33
    }
467
0
    auto& cache = *_condition_cache_ctx->filter_result;
468
0
    for (size_t i = 0; i < num_rows; i++) {
469
0
        if (filter_data[i]) {
470
            // row-group-relative position of this row
471
0
            int64_t rg_pos = _read_ranges.get_row_index_by_pos(batch_seq_start + i);
472
            // global row number in the parquet file
473
0
            size_t granule = (_current_row_group_idx.first_row + rg_pos) /
474
0
                             ConditionCacheContext::GRANULE_SIZE;
475
0
            size_t cache_idx = granule - _condition_cache_ctx->base_granule;
476
0
            if (cache_idx < cache.size()) {
477
0
                cache[cache_idx] = true;
478
0
            }
479
0
        }
480
0
    }
481
0
}
482
483
// On condition cache HIT, removes row ranges whose granules have no surviving rows from
484
// _read_ranges BEFORE column readers are created. This makes ParquetColumnReader skip I/O
485
// entirely for false-granule rows — both predicate and lazy columns — via its existing
486
// page/row-skipping infrastructure.
487
43
void RowGroupReader::_filter_read_ranges_by_condition_cache() {
488
43
    if (!_condition_cache_ctx || !_condition_cache_ctx->is_hit) {
489
43
        return;
490
43
    }
491
0
    auto& filter_result = *_condition_cache_ctx->filter_result;
492
0
    if (filter_result.empty()) {
493
0
        return;
494
0
    }
495
496
0
    auto old_row_count = _read_ranges.count();
497
0
    _read_ranges =
498
0
            filter_ranges_by_cache(_read_ranges, filter_result, _current_row_group_idx.first_row,
499
0
                                   _condition_cache_ctx->base_granule);
500
0
    _is_row_group_filtered = _read_ranges.is_empty();
501
0
    _condition_cache_filtered_rows += old_row_count - _read_ranges.count();
502
0
}
503
504
// Filters read_ranges by removing rows whose cache granule is false.
505
//
506
// Cache index i maps to global granule (base_granule + i), which covers global file
507
// rows [(base_granule+i)*GS, (base_granule+i+1)*GS). Since read_ranges uses
508
// row-group-relative indices and first_row is the global position of the row group's
509
// first row, global granule g maps to row-group-relative range:
510
//   [max(0, g*GS - first_row), max(0, (g+1)*GS - first_row))
511
//
512
// We build a RowRanges of all false-granule regions (in row-group-relative coordinates),
513
// then subtract from read_ranges via ranges_exception.
514
//
515
// Granules beyond cache.size() are kept conservatively (assumed true).
516
//
517
// When base_granule > 0, the cache only covers granules starting from base_granule.
518
// This happens when a Parquet file is split across multiple scan ranges and this reader
519
// only processes row groups starting at a non-zero offset in the file.
520
RowRanges RowGroupReader::filter_ranges_by_cache(const RowRanges& read_ranges,
521
                                                 const std::vector<bool>& cache, int64_t first_row,
522
21
                                                 int64_t base_granule) {
523
21
    constexpr int64_t GS = ConditionCacheContext::GRANULE_SIZE;
524
21
    RowRanges filtered_ranges;
525
526
138
    for (size_t i = 0; i < cache.size(); i++) {
527
117
        if (!cache[i]) {
528
64
            int64_t global_granule = base_granule + static_cast<int64_t>(i);
529
64
            int64_t rg_from = std::max(static_cast<int64_t>(0), global_granule * GS - first_row);
530
64
            int64_t rg_to =
531
64
                    std::max(static_cast<int64_t>(0), (global_granule + 1) * GS - first_row);
532
64
            if (rg_from < rg_to) {
533
16
                filtered_ranges.add(RowRange(rg_from, rg_to));
534
16
            }
535
64
        }
536
117
    }
537
538
21
    RowRanges result;
539
21
    RowRanges::ranges_exception(read_ranges, filtered_ranges, &result);
540
21
    return result;
541
21
}
542
543
Status RowGroupReader::_read_column_data(Block* block,
544
                                         const std::vector<std::string>& table_columns,
545
                                         size_t batch_size, size_t* read_rows, bool* batch_eof,
546
93
                                         FilterMap& filter_map) {
547
93
    size_t batch_read_rows = 0;
548
93
    bool has_eof = false;
549
194
    for (auto& read_col_name : table_columns) {
550
194
        uint32_t block_pos = 0;
551
194
        RETURN_IF_ERROR(_get_block_column_pos(*block, read_col_name, &block_pos));
552
194
        auto reader_iter = _column_readers.find(read_col_name);
553
194
        if (reader_iter == _column_readers.end() || reader_iter->second == nullptr) {
554
0
            return Status::InternalError("Column reader for '{}' not found in parquet row group",
555
0
                                         read_col_name);
556
0
        }
557
558
194
        auto& column_with_type_and_name = block->safe_get_by_position(block_pos);
559
194
        auto& column_ptr = column_with_type_and_name.column;
560
194
        auto& column_type = column_with_type_and_name.type;
561
194
        bool is_dict_filter = false;
562
194
        for (auto& _dict_filter_col : _dict_filter_cols) {
563
0
            if (_dict_filter_col.first == read_col_name) {
564
0
                MutableColumnPtr dict_column = ColumnInt32::create();
565
0
                if (column_type->is_nullable()) {
566
0
                    block->get_by_position(block_pos).type =
567
0
                            std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
568
0
                    block->replace_by_position(
569
0
                            block_pos,
570
0
                            ColumnNullable::create(std::move(dict_column),
571
0
                                                   ColumnUInt8::create(dict_column->size(), 0)));
572
0
                } else {
573
0
                    block->get_by_position(block_pos).type = std::make_shared<DataTypeInt32>();
574
0
                    block->replace_by_position(block_pos, std::move(dict_column));
575
0
                }
576
0
                is_dict_filter = true;
577
0
                break;
578
0
            }
579
0
        }
580
581
194
        size_t col_read_rows = 0;
582
194
        bool col_eof = false;
583
        // Should reset _filter_map_index to 0 when reading next column.
584
        //        select_vector.reset();
585
194
        reader_iter->second->reset_filter_map_index();
586
451
        while (!col_eof && col_read_rows < batch_size) {
587
257
            size_t loop_rows = 0;
588
257
            RETURN_IF_ERROR(reader_iter->second->read_column_data(
589
257
                    column_ptr, column_type, _table_info_node_ptr->get_children_node(read_col_name),
590
257
                    filter_map, batch_size - col_read_rows, &loop_rows, &col_eof, is_dict_filter));
591
257
            VLOG_DEBUG << "[RowGroupReader] column '" << read_col_name
592
0
                       << "' loop_rows=" << loop_rows << " col_read_rows_so_far=" << col_read_rows
593
0
                       << std::endl;
594
257
            col_read_rows += loop_rows;
595
257
        }
596
194
        VLOG_DEBUG << "[RowGroupReader] column '" << read_col_name
597
0
                   << "' read_rows=" << col_read_rows << std::endl;
598
194
        if (batch_read_rows > 0 && batch_read_rows != col_read_rows) {
599
0
            LOG(WARNING) << "[RowGroupReader] Mismatched read rows among parquet columns. "
600
0
                            "previous_batch_read_rows="
601
0
                         << batch_read_rows << ", current_column='" << read_col_name
602
0
                         << "', current_col_read_rows=" << col_read_rows;
603
0
            return Status::Corruption("Can't read the same number of rows among parquet columns");
604
0
        }
605
194
        batch_read_rows = col_read_rows;
606
607
194
#ifndef NDEBUG
608
194
        column_ptr->sanity_check();
609
194
#endif
610
194
        if (col_eof) {
611
114
            has_eof = true;
612
114
        }
613
194
    }
614
615
93
    *read_rows = batch_read_rows;
616
93
    *batch_eof = has_eof;
617
618
93
    return Status::OK();
619
93
}
620
621
Status RowGroupReader::_do_lazy_read(Block* block, size_t batch_size, size_t* read_rows,
622
11
                                     bool* batch_eof) {
623
11
    std::unique_ptr<FilterMap> filter_map_ptr = nullptr;
624
11
    size_t pre_read_rows;
625
11
    bool pre_eof;
626
11
    std::vector<uint32_t> columns_to_filter;
627
11
    uint32_t origin_column_num = block->columns();
628
11
    columns_to_filter.resize(origin_column_num);
629
44
    for (uint32_t i = 0; i < origin_column_num; ++i) {
630
33
        columns_to_filter[i] = i;
631
33
    }
632
11
    IColumn::Filter result_filter;
633
11
    size_t pre_raw_read_rows = 0;
634
11
    while (!_state->is_cancelled()) {
635
        // read predicate columns
636
11
        pre_read_rows = 0;
637
11
        pre_eof = false;
638
11
        FilterMap filter_map;
639
11
        int64_t batch_base_row = _total_read_rows;
640
11
        RETURN_IF_ERROR(_read_column_data(block, _lazy_read_ctx.predicate_columns.first, batch_size,
641
11
                                          &pre_read_rows, &pre_eof, filter_map));
642
11
        if (pre_read_rows == 0) {
643
0
            DCHECK_EQ(pre_eof, true);
644
0
            break;
645
0
        }
646
11
        pre_raw_read_rows += pre_read_rows;
647
648
11
        DCHECK(_table_format_reader);
649
11
        RETURN_IF_ERROR(_table_format_reader->on_fill_partition_columns(
650
11
                block, pre_read_rows, _lazy_read_ctx.predicate_partition_col_names));
651
11
        RETURN_IF_ERROR(_table_format_reader->on_fill_missing_columns(
652
11
                block, pre_read_rows, _lazy_read_ctx.predicate_missing_col_names));
653
11
        if (_need_current_batch_row_positions()) {
654
0
            RETURN_IF_ERROR(_get_current_batch_row_id(pre_read_rows));
655
0
        }
656
11
        RETURN_IF_ERROR(_table_format_reader->fill_synthesized_columns(block, pre_read_rows));
657
11
        RETURN_IF_ERROR(_table_format_reader->fill_generated_columns(block, pre_read_rows));
658
11
        RETURN_IF_ERROR(_build_pos_delete_filter(pre_read_rows));
659
660
11
#ifndef NDEBUG
661
33
        for (auto col : *block) {
662
33
            if (col.column->size() == 0) { // lazy read column.
663
11
                continue;
664
11
            }
665
22
            col.column->sanity_check();
666
22
            DCHECK(pre_read_rows == col.column->size())
667
0
                    << absl::Substitute("pre_read_rows = $0 , column rows = $1, col name = $2",
668
0
                                        pre_read_rows, col.column->size(), col.name);
669
22
        }
670
11
#endif
671
672
11
        bool can_filter_all = false;
673
11
        {
674
11
            SCOPED_RAW_TIMER(&_predicate_filter_time);
675
676
            // generate filter vector
677
11
            if (_lazy_read_ctx.resize_first_column) {
678
                // VExprContext.execute has an optimization, the filtering is executed when block->rows() > 0
679
                // The following process may be tricky and time-consuming, but we have no other way.
680
11
                auto column_guard = block->mutate_column_scoped(0);
681
11
                column_guard.mutable_column()->resize(pre_read_rows);
682
11
            }
683
11
            result_filter.assign(pre_read_rows, static_cast<unsigned char>(1));
684
11
            std::vector<IColumn::Filter*> filters;
685
11
            if (_position_delete_ctx.has_filter) {
686
0
                filters.push_back(_pos_delete_filter_ptr.get());
687
0
            }
688
689
11
            VExprContextSPtrs filter_contexts;
690
22
            for (auto& conjunct : _filter_conjuncts) {
691
22
                filter_contexts.emplace_back(conjunct);
692
22
            }
693
694
11
            {
695
11
                RETURN_IF_ERROR(VExprContext::execute_conjuncts(filter_contexts, &filters, block,
696
11
                                                                &result_filter, &can_filter_all));
697
11
            }
698
699
            // Condition cache MISS: mark granules with surviving rows
700
11
            if (!can_filter_all) {
701
11
                _mark_condition_cache_granules(result_filter.data(), pre_read_rows, batch_base_row);
702
11
            }
703
704
11
            if (_lazy_read_ctx.resize_first_column) {
705
                // We have to clean the first column to insert right data.
706
11
                block->clear_column_data(std::vector<uint32_t> {0});
707
11
            }
708
11
        }
709
710
0
        const uint8_t* __restrict filter_map_data = result_filter.data();
711
11
        filter_map_ptr = std::make_unique<FilterMap>();
712
11
        RETURN_IF_ERROR(filter_map_ptr->init(filter_map_data, pre_read_rows, can_filter_all));
713
11
        if (filter_map_ptr->filter_all()) {
714
0
            {
715
0
                SCOPED_RAW_TIMER(&_predicate_filter_time);
716
0
                std::vector<uint32_t> columns_to_clear;
717
0
                columns_to_clear.reserve(_lazy_read_ctx.predicate_columns.first.size() +
718
0
                                         _lazy_read_ctx.predicate_partition_columns.size() +
719
0
                                         _lazy_read_ctx.predicate_missing_columns.size());
720
0
                for (const auto& col : _lazy_read_ctx.predicate_columns.first) {
721
                    // clean block to read predicate columns
722
0
                    uint32_t block_pos = 0;
723
0
                    RETURN_IF_ERROR(_get_block_column_pos(*block, col, &block_pos));
724
0
                    columns_to_clear.emplace_back(block_pos);
725
0
                }
726
0
                for (const auto& col : _lazy_read_ctx.predicate_partition_columns) {
727
0
                    uint32_t block_pos = 0;
728
0
                    RETURN_IF_ERROR(_get_block_column_pos(*block, col.first, &block_pos));
729
0
                    columns_to_clear.emplace_back(block_pos);
730
0
                }
731
0
                for (const auto& col : _lazy_read_ctx.predicate_missing_columns) {
732
0
                    uint32_t block_pos = 0;
733
0
                    RETURN_IF_ERROR(_get_block_column_pos(*block, col.first, &block_pos));
734
0
                    columns_to_clear.emplace_back(block_pos);
735
0
                }
736
0
                block->clear_column_data(columns_to_clear);
737
0
                RETURN_IF_ERROR(_table_format_reader->clear_synthesized_columns(block));
738
0
                RETURN_IF_ERROR(_table_format_reader->clear_generated_columns(block));
739
0
                Block::erase_useless_column(block, origin_column_num);
740
0
            }
741
742
0
            if (!pre_eof) {
743
                // If continuous batches are skipped, we can cache them to skip a whole page
744
0
                _cached_filtered_rows += pre_read_rows;
745
0
                if (pre_raw_read_rows >= config::doris_scanner_row_num) {
746
0
                    *read_rows = 0;
747
0
                    RETURN_IF_ERROR(_convert_dict_cols_to_string_cols(block));
748
0
                    return Status::OK();
749
0
                }
750
0
            } else { // pre_eof
751
                // If filter_map_ptr->filter_all() and pre_eof, we can skip whole row group.
752
0
                *read_rows = 0;
753
0
                *batch_eof = true;
754
0
                _lazy_read_filtered_rows += (pre_read_rows + _cached_filtered_rows);
755
0
                RETURN_IF_ERROR(_convert_dict_cols_to_string_cols(block));
756
0
                return Status::OK();
757
0
            }
758
11
        } else {
759
11
            break;
760
11
        }
761
11
    }
762
11
    if (_state->is_cancelled()) {
763
0
        return Status::Cancelled("cancelled");
764
0
    }
765
766
11
    if (filter_map_ptr == nullptr) {
767
0
        DCHECK_EQ(pre_read_rows + _cached_filtered_rows, 0);
768
0
        *read_rows = 0;
769
0
        *batch_eof = true;
770
0
        RETURN_IF_ERROR(_convert_dict_cols_to_string_cols(block));
771
0
        return Status::OK();
772
0
    }
773
774
11
    FilterMap& filter_map = *filter_map_ptr;
775
11
    DorisUniqueBufferPtr<uint8_t> rebuild_filter_map = nullptr;
776
11
    if (_cached_filtered_rows != 0) {
777
0
        RETURN_IF_ERROR(_rebuild_filter_map(filter_map, rebuild_filter_map, pre_read_rows));
778
0
        pre_read_rows += _cached_filtered_rows;
779
0
        _cached_filtered_rows = 0;
780
0
    }
781
782
    // lazy read columns
783
11
    size_t lazy_read_rows;
784
11
    bool lazy_eof;
785
11
    RETURN_IF_ERROR(_read_column_data(block, _lazy_read_ctx.lazy_read_columns, pre_read_rows,
786
11
                                      &lazy_read_rows, &lazy_eof, filter_map));
787
788
11
    if (pre_read_rows != lazy_read_rows) {
789
0
        return Status::Corruption("Can't read the same number of rows when doing lazy read");
790
0
    }
791
    // pre_eof ^ lazy_eof
792
    // we set pre_read_rows as batch_size for lazy read columns, so pre_eof != lazy_eof
793
794
    // filter data in predicate columns, and remove filter column
795
11
    {
796
11
        SCOPED_RAW_TIMER(&_predicate_filter_time);
797
11
        if (filter_map.has_filter()) {
798
0
            RETURN_IF_CATCH_EXCEPTION(Block::filter_block_internal(
799
0
                    block, _lazy_read_ctx.all_predicate_col_ids, result_filter));
800
0
            Block::erase_useless_column(block, origin_column_num);
801
802
11
        } else {
803
11
            Block::erase_useless_column(block, origin_column_num);
804
11
        }
805
11
    }
806
807
11
    RETURN_IF_ERROR(_convert_dict_cols_to_string_cols(block));
808
809
11
    size_t column_num = block->columns();
810
11
    size_t column_size = 0;
811
44
    for (int i = 0; i < column_num; ++i) {
812
33
        size_t cz = block->get_by_position(i).column->size();
813
33
        if (column_size != 0 && cz != 0) {
814
22
            DCHECK_EQ(column_size, cz);
815
22
        }
816
33
        if (cz != 0) {
817
33
            column_size = cz;
818
33
        }
819
33
    }
820
11
    _lazy_read_filtered_rows += pre_read_rows - column_size;
821
11
    *read_rows = column_size;
822
823
11
    *batch_eof = pre_eof;
824
11
    DCHECK(_table_format_reader);
825
11
    RETURN_IF_ERROR(_table_format_reader->on_fill_partition_columns(
826
11
            block, column_size, _lazy_read_ctx.partition_col_names));
827
11
    RETURN_IF_ERROR(_table_format_reader->on_fill_missing_columns(
828
11
            block, column_size, _lazy_read_ctx.missing_col_names));
829
11
#ifndef NDEBUG
830
33
    for (auto col : *block) {
831
33
        col.column->sanity_check();
832
33
        DCHECK(block->rows() == col.column->size())
833
0
                << absl::Substitute("block rows = $0 , column rows = $1, col name = $2",
834
0
                                    block->rows(), col.column->size(), col.name);
835
33
    }
836
11
#endif
837
11
    return Status::OK();
838
11
}
839
840
Status RowGroupReader::_rebuild_filter_map(FilterMap& filter_map,
841
                                           DorisUniqueBufferPtr<uint8_t>& filter_map_data,
842
0
                                           size_t pre_read_rows) const {
843
0
    if (_cached_filtered_rows == 0) {
844
0
        return Status::OK();
845
0
    }
846
0
    size_t total_rows = _cached_filtered_rows + pre_read_rows;
847
0
    if (filter_map.filter_all()) {
848
0
        RETURN_IF_ERROR(filter_map.init(nullptr, total_rows, true));
849
0
        return Status::OK();
850
0
    }
851
852
0
    filter_map_data = make_unique_buffer<uint8_t>(total_rows);
853
0
    auto* map = filter_map_data.get();
854
0
    for (size_t i = 0; i < _cached_filtered_rows; ++i) {
855
0
        map[i] = 0;
856
0
    }
857
0
    const uint8_t* old_map = filter_map.filter_map_data();
858
0
    if (old_map == nullptr) {
859
        // select_vector.filter_all() == true is already built.
860
0
        for (size_t i = _cached_filtered_rows; i < total_rows; ++i) {
861
0
            map[i] = 1;
862
0
        }
863
0
    } else {
864
0
        memcpy(map + _cached_filtered_rows, old_map, pre_read_rows);
865
0
    }
866
0
    RETURN_IF_ERROR(filter_map.init(map, total_rows, false));
867
0
    return Status::OK();
868
0
}
869
870
Status RowGroupReader::_fill_partition_columns(
871
        Block* block, size_t rows,
872
        const std::unordered_map<std::string, std::tuple<std::string, const SlotDescriptor*>>&
873
0
                partition_columns) {
874
0
    DataTypeSerDe::FormatOptions _text_formatOptions;
875
0
    for (const auto& kv : partition_columns) {
876
0
        uint32_t block_pos = 0;
877
0
        RETURN_IF_ERROR(_get_block_column_pos(*block, kv.first, &block_pos));
878
0
        auto doris_column = block->get_by_position(block_pos).column;
879
        // obtained from block*, it is a mutable object.
880
0
        auto* col_ptr = const_cast<IColumn*>(doris_column.get());
881
0
        const auto& [value, slot_desc] = kv.second;
882
0
        auto _text_serde = slot_desc->get_data_type_ptr()->get_serde();
883
0
        Slice slice(value.data(), value.size());
884
0
        uint64_t num_deserialized = 0;
885
        // Be careful when reading empty rows from parquet row groups.
886
0
        if (_text_serde->deserialize_column_from_fixed_json(*col_ptr, slice, rows,
887
0
                                                            &num_deserialized,
888
0
                                                            _text_formatOptions) != Status::OK()) {
889
0
            return Status::InternalError("Failed to fill partition column: {}={}",
890
0
                                         slot_desc->col_name(), value);
891
0
        }
892
0
        if (num_deserialized != rows) {
893
0
            return Status::InternalError(
894
0
                    "Failed to fill partition column: {}={} ."
895
0
                    "Number of rows expected to be written : {}, number of rows actually written : "
896
0
                    "{}",
897
0
                    slot_desc->col_name(), value, num_deserialized, rows);
898
0
        }
899
0
    }
900
0
    return Status::OK();
901
0
}
902
903
Status RowGroupReader::_fill_missing_columns(
904
        Block* block, size_t rows,
905
0
        const std::unordered_map<std::string, VExprContextSPtr>& missing_columns) {
906
0
    for (const auto& kv : missing_columns) {
907
0
        uint32_t block_pos = 0;
908
0
        RETURN_IF_ERROR(_get_block_column_pos(*block, kv.first, &block_pos));
909
0
        if (kv.second == nullptr) {
910
            // no default column, fill with null
911
0
            auto column_guard = block->mutate_column_scoped(block_pos);
912
0
            auto& mutable_column = column_guard.mutable_column();
913
0
            auto* nullable_column = assert_cast<ColumnNullable*>(mutable_column.get());
914
0
            nullable_column->insert_many_defaults(rows);
915
0
        } else {
916
            // fill with default value
917
0
            const auto& ctx = kv.second;
918
0
            ColumnPtr result_column_ptr;
919
            // PT1 => dest primitive type
920
0
            RETURN_IF_ERROR(ctx->execute(block, result_column_ptr));
921
0
            if (result_column_ptr->use_count() == 1) {
922
                // call resize because the first column of _src_block_ptr may not be filled by reader,
923
                // so _src_block_ptr->rows() may return wrong result, cause the column created by `ctx->execute()`
924
                // has only one row.
925
0
                auto mutable_column = result_column_ptr->assert_mutable();
926
0
                mutable_column->resize(rows);
927
                // result_column_ptr maybe a ColumnConst, convert it to a normal column
928
0
                result_column_ptr = result_column_ptr->convert_to_full_column_if_const();
929
0
                auto origin_column_type = block->get_by_position(block_pos).type;
930
0
                bool is_nullable = origin_column_type->is_nullable();
931
0
                block->replace_by_position(block_pos, is_nullable ? make_nullable(result_column_ptr)
932
0
                                                                  : result_column_ptr);
933
0
            }
934
0
        }
935
0
    }
936
0
    return Status::OK();
937
0
}
938
939
Status RowGroupReader::_get_block_column_pos(const Block& block, const std::string& column_name,
940
194
                                             uint32_t* position) const {
941
194
    if (_col_name_to_block_idx == nullptr) {
942
0
        return Status::InternalError(
943
0
                "Column name to block index map is not set when reading parquet column '{}', "
944
0
                "block: "
945
0
                "{}",
946
0
                column_name, block.dump_structure());
947
0
    }
948
194
    auto iter = _col_name_to_block_idx->find(column_name);
949
194
    if (iter == _col_name_to_block_idx->end()) {
950
0
        return Status::InternalError("Column '{}' not found in block index map, block: {}",
951
0
                                     column_name, block.dump_structure());
952
0
    }
953
194
    if (iter->second >= block.columns()) {
954
0
        return Status::InternalError(
955
0
                "Column '{}' maps to invalid block position {}, block columns: {}, block: {}",
956
0
                column_name, iter->second, block.columns(), block.dump_structure());
957
0
    }
958
194
    *position = iter->second;
959
194
    return Status::OK();
960
194
}
961
962
93
bool RowGroupReader::_need_current_batch_row_positions() const {
963
93
    DCHECK(_table_format_reader);
964
93
    return _table_format_reader->has_synthesized_column_handlers() ||
965
93
           _table_format_reader->has_generated_column_handlers();
966
93
}
967
968
11
Status RowGroupReader::_read_empty_batch(size_t batch_size, size_t* read_rows, bool* batch_eof) {
969
11
    if (_position_delete_ctx.has_filter) {
970
0
        int64_t start_row_id = _position_delete_ctx.current_row_id;
971
0
        int64_t end_row_id = std::min(_position_delete_ctx.current_row_id + (int64_t)batch_size,
972
0
                                      _position_delete_ctx.last_row_id);
973
0
        int64_t num_delete_rows = 0;
974
0
        auto before_index = _position_delete_ctx.index;
975
0
        while (_position_delete_ctx.index < _position_delete_ctx.end_index) {
976
0
            const int64_t& delete_row_id =
977
0
                    _position_delete_ctx.delete_rows[_position_delete_ctx.index];
978
0
            if (delete_row_id < start_row_id) {
979
0
                _position_delete_ctx.index++;
980
0
                before_index = _position_delete_ctx.index;
981
0
            } else if (delete_row_id < end_row_id) {
982
0
                num_delete_rows++;
983
0
                _position_delete_ctx.index++;
984
0
            } else { // delete_row_id >= end_row_id
985
0
                break;
986
0
            }
987
0
        }
988
0
        *read_rows = end_row_id - start_row_id - num_delete_rows;
989
0
        _position_delete_ctx.current_row_id = end_row_id;
990
0
        *batch_eof = _position_delete_ctx.current_row_id == _position_delete_ctx.last_row_id;
991
992
0
        if (_need_current_batch_row_positions()) {
993
0
            _current_batch_row_ids.clear();
994
0
            _current_batch_row_ids.resize(*read_rows);
995
0
            size_t idx = 0;
996
0
            for (auto id = start_row_id; id < end_row_id; id++) {
997
0
                if (before_index < _position_delete_ctx.index &&
998
0
                    id == _position_delete_ctx.delete_rows[before_index]) {
999
0
                    before_index++;
1000
0
                    continue;
1001
0
                }
1002
0
                _current_batch_row_ids[idx++] = (rowid_t)id;
1003
0
            }
1004
0
        }
1005
11
    } else {
1006
11
        if (batch_size < _remaining_rows) {
1007
10
            *read_rows = batch_size;
1008
10
            _remaining_rows -= batch_size;
1009
10
            *batch_eof = false;
1010
10
        } else {
1011
1
            *read_rows = _remaining_rows;
1012
1
            _remaining_rows = 0;
1013
1
            *batch_eof = true;
1014
1
        }
1015
11
        if (_need_current_batch_row_positions()) {
1016
0
            RETURN_IF_ERROR(_get_current_batch_row_id(*read_rows));
1017
0
        }
1018
11
    }
1019
11
    _total_read_rows += *read_rows;
1020
11
    return Status::OK();
1021
11
}
1022
1023
5
Status RowGroupReader::_get_current_batch_row_id(size_t read_rows) {
1024
5
    _current_batch_row_ids.clear();
1025
5
    _current_batch_row_ids.resize(read_rows);
1026
1027
5
    int64_t idx = 0;
1028
5
    int64_t read_range_rows = 0;
1029
19
    for (size_t range_idx = 0; range_idx < _read_ranges.range_size(); range_idx++) {
1030
14
        auto range = _read_ranges.get_range(range_idx);
1031
14
        if (read_rows == 0) {
1032
0
            break;
1033
0
        }
1034
14
        if (read_range_rows + (range.to() - range.from()) > _total_read_rows) {
1035
14
            int64_t fi =
1036
14
                    std::max(_total_read_rows, read_range_rows) - read_range_rows + range.from();
1037
14
            size_t len = std::min(read_rows, (size_t)(std::max(range.to(), fi) - fi));
1038
1039
14
            read_rows -= len;
1040
1041
28
            for (auto i = 0; i < len; i++) {
1042
14
                _current_batch_row_ids[idx++] =
1043
14
                        (rowid_t)(fi + i + _current_row_group_idx.first_row);
1044
14
            }
1045
14
        }
1046
14
        read_range_rows += range.to() - range.from();
1047
14
    }
1048
5
    return Status::OK();
1049
5
}
1050
1051
82
Status RowGroupReader::_build_pos_delete_filter(size_t read_rows) {
1052
82
    if (!_position_delete_ctx.has_filter) {
1053
82
        _pos_delete_filter_ptr.reset(nullptr);
1054
82
        _total_read_rows += read_rows;
1055
82
        return Status::OK();
1056
82
    }
1057
0
    _pos_delete_filter_ptr.reset(new IColumn::Filter(read_rows, 1));
1058
0
    auto* __restrict _pos_delete_filter_data = _pos_delete_filter_ptr->data();
1059
0
    while (_position_delete_ctx.index < _position_delete_ctx.end_index) {
1060
0
        const int64_t delete_row_index_in_row_group =
1061
0
                _position_delete_ctx.delete_rows[_position_delete_ctx.index] -
1062
0
                _position_delete_ctx.first_row_id;
1063
0
        int64_t read_range_rows = 0;
1064
0
        size_t remaining_read_rows = _total_read_rows + read_rows;
1065
0
        for (size_t range_idx = 0; range_idx < _read_ranges.range_size(); range_idx++) {
1066
0
            auto range = _read_ranges.get_range(range_idx);
1067
0
            if (delete_row_index_in_row_group < range.from()) {
1068
0
                ++_position_delete_ctx.index;
1069
0
                break;
1070
0
            } else if (delete_row_index_in_row_group < range.to()) {
1071
0
                int64_t index = (delete_row_index_in_row_group - range.from()) + read_range_rows -
1072
0
                                _total_read_rows;
1073
0
                if (index > read_rows - 1) {
1074
0
                    _total_read_rows += read_rows;
1075
0
                    return Status::OK();
1076
0
                }
1077
0
                _pos_delete_filter_data[index] = 0;
1078
0
                ++_position_delete_ctx.index;
1079
0
                break;
1080
0
            } else { // delete_row >= range.last_row
1081
0
            }
1082
1083
0
            int64_t range_size = range.to() - range.from();
1084
            // Don't search next range when there is no remaining_read_rows.
1085
0
            if (remaining_read_rows <= range_size) {
1086
0
                _total_read_rows += read_rows;
1087
0
                return Status::OK();
1088
0
            } else {
1089
0
                remaining_read_rows -= range_size;
1090
0
                read_range_rows += range_size;
1091
0
            }
1092
0
        }
1093
0
    }
1094
0
    _total_read_rows += read_rows;
1095
0
    return Status::OK();
1096
0
}
1097
1098
// need exception safety
1099
Status RowGroupReader::_filter_block(Block* block, int column_to_keep,
1100
60
                                     const std::vector<uint32_t>& columns_to_filter) {
1101
60
    if (_pos_delete_filter_ptr) {
1102
0
        RETURN_IF_CATCH_EXCEPTION(
1103
0
                Block::filter_block_internal(block, columns_to_filter, (*_pos_delete_filter_ptr)));
1104
0
    }
1105
60
    Block::erase_useless_column(block, column_to_keep);
1106
1107
60
    return Status::OK();
1108
60
}
1109
1110
2
Status RowGroupReader::_rewrite_dict_predicates() {
1111
2
    SCOPED_RAW_TIMER(&_dict_filter_rewrite_time);
1112
2
    for (auto it = _dict_filter_cols.begin(); it != _dict_filter_cols.end();) {
1113
0
        std::string& dict_filter_col_name = it->first;
1114
0
        int slot_id = it->second;
1115
        // 1. Get dictionary values to a string column.
1116
0
        MutableColumnPtr dict_value_column = ColumnString::create();
1117
0
        bool has_dict = false;
1118
0
        RETURN_IF_ERROR(_column_readers[dict_filter_col_name]->read_dict_values_to_column(
1119
0
                dict_value_column, &has_dict));
1120
0
#ifndef NDEBUG
1121
0
        dict_value_column->sanity_check();
1122
0
#endif
1123
0
        size_t dict_value_column_size = dict_value_column->size();
1124
0
        DCHECK(has_dict);
1125
        // Skip dict evaluation when the dictionary itself is larger than one batch:
1126
        // evaluating a heavy predicate over that many distinct values (and the resulting
1127
        // large IN filter) costs more than per-row filtering. Mirrors StarRocks'
1128
        // `dictionaryOffset.size() > chunk_size()` gate; falls back to per-row filter.
1129
0
        int max_dict_for_eval = _state != nullptr ? std::max(_state->batch_size(), 4096) : 4096;
1130
0
        if (dict_value_column_size > (size_t)max_dict_for_eval) {
1131
0
            auto slot_iter = _slot_id_to_filter_conjuncts->find(slot_id);
1132
0
            if (slot_iter != _slot_id_to_filter_conjuncts->end()) {
1133
0
                for (auto& ctx : slot_iter->second) {
1134
0
                    _filter_conjuncts.push_back(ctx);
1135
0
                }
1136
0
            }
1137
0
            it = _dict_filter_cols.erase(it);
1138
0
            continue;
1139
0
        }
1140
        // 2. Build a temp block from the dict string column, then execute conjuncts and filter block.
1141
        // 2.1 Build a temp block from the dict string column to match the conjuncts executing.
1142
0
        Block temp_block;
1143
0
        int dict_pos = -1;
1144
0
        int index = 0;
1145
0
        for (const auto slot_desc : _tuple_descriptor->slots()) {
1146
0
            if (slot_desc->id() == slot_id) {
1147
0
                auto data_type = slot_desc->get_data_type_ptr();
1148
0
                if (data_type->is_nullable()) {
1149
0
                    temp_block.insert(
1150
0
                            {ColumnNullable::create(
1151
0
                                     std::move(
1152
0
                                             dict_value_column), // NOLINT(bugprone-use-after-move)
1153
0
                                     ColumnUInt8::create(dict_value_column_size, 0)),
1154
0
                             std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>()),
1155
0
                             ""});
1156
0
                } else {
1157
0
                    temp_block.insert(
1158
0
                            {std::move(dict_value_column), std::make_shared<DataTypeString>(), ""});
1159
0
                }
1160
0
                dict_pos = index;
1161
1162
0
            } else {
1163
0
                temp_block.insert(ColumnWithTypeAndName(slot_desc->get_empty_mutable_column(),
1164
0
                                                        slot_desc->get_data_type_ptr(),
1165
0
                                                        slot_desc->col_name()));
1166
0
            }
1167
0
            ++index;
1168
0
        }
1169
1170
        // 2.2 Execute conjuncts.
1171
0
        VExprContextSPtrs ctxs;
1172
0
        auto iter = _slot_id_to_filter_conjuncts->find(slot_id);
1173
0
        if (iter != _slot_id_to_filter_conjuncts->end()) {
1174
0
            for (auto& ctx : iter->second) {
1175
0
                ctxs.push_back(ctx);
1176
0
            }
1177
0
        } else {
1178
0
            std::stringstream msg;
1179
0
            msg << "_slot_id_to_filter_conjuncts: slot_id [" << slot_id << "] not found";
1180
0
            return Status::NotFound(msg.str());
1181
0
        }
1182
1183
0
        if (dict_pos != 0) {
1184
            // VExprContext.execute has an optimization, the filtering is executed when block->rows() > 0
1185
            // The following process may be tricky and time-consuming, but we have no other way.
1186
0
            temp_block.get_by_position(0).column->assert_mutable()->resize(dict_value_column_size);
1187
0
        }
1188
0
        IColumn::Filter result_filter(temp_block.rows(), 1);
1189
0
        bool can_filter_all;
1190
0
        {
1191
0
            RETURN_IF_ERROR(VExprContext::execute_conjuncts(ctxs, nullptr, &temp_block,
1192
0
                                                            &result_filter, &can_filter_all));
1193
0
        }
1194
0
        if (dict_pos != 0) {
1195
            // We have to clean the first column to insert right data.
1196
0
            temp_block.get_by_position(0).column->assert_mutable()->clear();
1197
0
        }
1198
1199
        // If can_filter_all = true, can filter this row group.
1200
0
        if (can_filter_all) {
1201
0
            _is_row_group_filtered = true;
1202
0
            return Status::OK();
1203
0
        }
1204
1205
        // 3. Get dict codes.
1206
0
        std::vector<int32_t> dict_codes;
1207
0
        for (size_t i = 0; i < result_filter.size(); ++i) {
1208
0
            if (result_filter[i]) {
1209
0
                dict_codes.emplace_back(i);
1210
0
            }
1211
0
        }
1212
1213
        // About Performance: if dict_column size is too large, it will generate a large IN filter.
1214
0
        if (dict_codes.size() > MAX_DICT_CODE_PREDICATE_TO_REWRITE) {
1215
0
            it = _dict_filter_cols.erase(it);
1216
0
            for (auto& ctx : ctxs) {
1217
0
                _filter_conjuncts.push_back(ctx);
1218
0
            }
1219
0
            continue;
1220
0
        }
1221
1222
        // 4. Rewrite conjuncts.
1223
0
        RETURN_IF_ERROR(_rewrite_dict_conjuncts(
1224
0
                dict_codes, slot_id, temp_block.get_by_position(dict_pos).column->is_nullable()));
1225
0
        ++it;
1226
0
    }
1227
2
    return Status::OK();
1228
2
}
1229
1230
Status RowGroupReader::_rewrite_dict_conjuncts(std::vector<int32_t>& dict_codes, int slot_id,
1231
0
                                               bool is_nullable) {
1232
0
    VExprSPtr root;
1233
0
    if (dict_codes.size() == 1) {
1234
0
        {
1235
0
            TFunction fn;
1236
0
            TFunctionName fn_name;
1237
0
            fn_name.__set_db_name("");
1238
0
            fn_name.__set_function_name("eq");
1239
0
            fn.__set_name(fn_name);
1240
0
            fn.__set_binary_type(TFunctionBinaryType::BUILTIN);
1241
0
            std::vector<TTypeDesc> arg_types;
1242
0
            arg_types.push_back(create_type_desc(PrimitiveType::TYPE_INT));
1243
0
            arg_types.push_back(create_type_desc(PrimitiveType::TYPE_INT));
1244
0
            fn.__set_arg_types(arg_types);
1245
0
            fn.__set_ret_type(create_type_desc(PrimitiveType::TYPE_BOOLEAN));
1246
0
            fn.__set_has_var_args(false);
1247
1248
0
            TExprNode texpr_node;
1249
0
            texpr_node.__set_type(create_type_desc(PrimitiveType::TYPE_BOOLEAN));
1250
0
            texpr_node.__set_node_type(TExprNodeType::BINARY_PRED);
1251
0
            texpr_node.__set_opcode(TExprOpcode::EQ);
1252
0
            texpr_node.__set_fn(fn);
1253
0
            texpr_node.__set_num_children(2);
1254
0
            texpr_node.__set_is_nullable(is_nullable);
1255
0
            root = VectorizedFnCall::create_shared(texpr_node);
1256
0
        }
1257
0
        {
1258
0
            SlotDescriptor* slot = nullptr;
1259
0
            const std::vector<SlotDescriptor*>& slots = _tuple_descriptor->slots();
1260
0
            for (auto each : slots) {
1261
0
                if (each->id() == slot_id) {
1262
0
                    slot = each;
1263
0
                    break;
1264
0
                }
1265
0
            }
1266
0
            root->add_child(VSlotRef::create_shared(slot));
1267
0
        }
1268
0
        {
1269
0
            TExprNode texpr_node;
1270
0
            texpr_node.__set_node_type(TExprNodeType::INT_LITERAL);
1271
0
            texpr_node.__set_type(create_type_desc(TYPE_INT));
1272
0
            TIntLiteral int_literal;
1273
0
            int_literal.__set_value(dict_codes[0]);
1274
0
            texpr_node.__set_int_literal(int_literal);
1275
0
            texpr_node.__set_is_nullable(is_nullable);
1276
0
            root->add_child(VLiteral::create_shared(texpr_node));
1277
0
        }
1278
0
    } else {
1279
0
        {
1280
0
            TTypeDesc type_desc = create_type_desc(PrimitiveType::TYPE_BOOLEAN);
1281
0
            TExprNode node;
1282
0
            node.__set_type(type_desc);
1283
0
            node.__set_node_type(TExprNodeType::IN_PRED);
1284
0
            node.in_predicate.__set_is_not_in(false);
1285
0
            node.__set_opcode(TExprOpcode::FILTER_IN);
1286
            // VdirectInPredicate assume is_nullable = false.
1287
0
            node.__set_is_nullable(false);
1288
1289
0
            std::shared_ptr<HybridSetBase> hybrid_set(
1290
0
                    create_set(PrimitiveType::TYPE_INT, dict_codes.size(), false));
1291
0
            for (int j = 0; j < dict_codes.size(); ++j) {
1292
0
                hybrid_set->insert(&dict_codes[j]);
1293
0
            }
1294
0
            root = VDirectInPredicate::create_shared(node, hybrid_set, false);
1295
0
        }
1296
0
        {
1297
0
            SlotDescriptor* slot = nullptr;
1298
0
            const std::vector<SlotDescriptor*>& slots = _tuple_descriptor->slots();
1299
0
            for (auto each : slots) {
1300
0
                if (each->id() == slot_id) {
1301
0
                    slot = each;
1302
0
                    break;
1303
0
                }
1304
0
            }
1305
0
            root->add_child(VSlotRef::create_shared(slot));
1306
0
        }
1307
0
    }
1308
0
    VExprContextSPtr rewritten_conjunct_ctx = VExprContext::create_shared(root);
1309
0
    RETURN_IF_ERROR(rewritten_conjunct_ctx->prepare(_state, *_row_descriptor));
1310
0
    RETURN_IF_ERROR(rewritten_conjunct_ctx->open(_state));
1311
0
    _dict_filter_conjuncts.push_back(rewritten_conjunct_ctx);
1312
0
    _filter_conjuncts.push_back(rewritten_conjunct_ctx);
1313
0
    return Status::OK();
1314
0
}
1315
1316
82
Status RowGroupReader::_convert_dict_cols_to_string_cols(Block* block) {
1317
82
    for (auto& dict_filter_cols : _dict_filter_cols) {
1318
0
        uint32_t block_pos = 0;
1319
0
        RETURN_IF_ERROR(_get_block_column_pos(*block, dict_filter_cols.first, &block_pos));
1320
0
        auto reader_iter = _column_readers.find(dict_filter_cols.first);
1321
0
        if (reader_iter == _column_readers.end() || reader_iter->second == nullptr) {
1322
0
            return Status::InternalError("Column reader for '{}' not found in parquet row group",
1323
0
                                         dict_filter_cols.first);
1324
0
        }
1325
0
        ColumnWithTypeAndName& column_with_type_and_name = block->get_by_position(block_pos);
1326
0
        const ColumnPtr& column = column_with_type_and_name.column;
1327
0
        if (const auto* nullable_column = check_and_get_column<ColumnNullable>(*column)) {
1328
0
            const ColumnPtr& nested_column = nullable_column->get_nested_column_ptr();
1329
0
            const auto* dict_column = assert_cast<const ColumnInt32*>(nested_column.get());
1330
0
            DCHECK(dict_column);
1331
1332
0
            auto string_column = DORIS_TRY(
1333
0
                    reader_iter->second->convert_dict_column_to_string_column(dict_column));
1334
1335
0
            column_with_type_and_name.type =
1336
0
                    std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
1337
0
            block->replace_by_position(
1338
0
                    block_pos, ColumnNullable::create(std::move(string_column),
1339
0
                                                      nullable_column->get_null_map_column_ptr()));
1340
0
        } else {
1341
0
            const auto* dict_column = assert_cast<const ColumnInt32*>(column.get());
1342
0
            auto string_column = DORIS_TRY(
1343
0
                    reader_iter->second->convert_dict_column_to_string_column(dict_column));
1344
1345
0
            column_with_type_and_name.type = std::make_shared<DataTypeString>();
1346
0
            block->replace_by_position(block_pos, std::move(string_column));
1347
0
        }
1348
0
    }
1349
82
    return Status::OK();
1350
82
}
1351
1352
43
ParquetColumnReader::ColumnStatistics RowGroupReader::merged_column_statistics() {
1353
43
    ParquetColumnReader::ColumnStatistics st;
1354
114
    for (auto& reader : _column_readers) {
1355
114
        auto ost = reader.second->column_statistics();
1356
114
        st.merge(ost);
1357
114
    }
1358
43
    return st;
1359
43
}
1360
1361
} // namespace doris