Coverage Report

Created: 2026-04-23 18:29

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