Coverage Report

Created: 2026-07-12 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format_v2/table_reader.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <bvar/status.h>
21
22
#include <algorithm>
23
#include <exception>
24
#include <map>
25
#include <memory>
26
#include <optional>
27
#include <string>
28
#include <string_view>
29
#include <utility>
30
#include <vector>
31
32
#include "common/cast_set.h"
33
#include "common/exception.h"
34
#include "common/logging.h"
35
#include "common/status.h"
36
#include "core/assert_cast.h"
37
#include "core/block/block.h"
38
#include "core/column/column_array.h"
39
#include "core/column/column_const.h"
40
#include "core/column/column_map.h"
41
#include "core/column/column_nullable.h"
42
#include "core/column/column_struct.h"
43
#include "core/column/column_vector.h"
44
#include "core/data_type/data_type.h"
45
#include "core/data_type/data_type_array.h"
46
#include "core/data_type/data_type_map.h"
47
#include "core/data_type/data_type_nullable.h"
48
#include "core/data_type/data_type_number.h"
49
#include "core/data_type/data_type_string.h"
50
#include "core/data_type/data_type_struct.h"
51
#include "core/field.h"
52
#include "exec/common/stringop_substring.h"
53
#include "exprs/vexpr.h"
54
#include "exprs/vexpr_context.h"
55
#include "exprs/vexpr_fwd.h"
56
#include "exprs/vslot_ref.h"
57
#include "format/table/deletion_vector.h"
58
#include "format_v2/column_data.h"
59
#include "format_v2/column_mapper.h"
60
#include "format_v2/expr/cast.h"
61
#include "format_v2/expr/delete_predicate.h"
62
#include "format_v2/file_reader.h"
63
#include "format_v2/parquet/reader/column_reader.h"
64
#include "format_v2/schema_projection.h"
65
#include "gen_cpp/PlanNodes_types.h"
66
#include "io/io_common.h"
67
#include "runtime/descriptors.h"
68
#include "storage/segment/condition_cache.h"
69
70
namespace doris {
71
class Block;
72
struct DeleteFileDesc;
73
class RuntimeState;
74
} // namespace doris
75
76
namespace doris::format {
77
78
using DeleteRows = std::vector<int64_t>;
79
80
// Row-level predicates on table/global schema. They are rewritten to file-local expressions when
81
// possible, and remain the source of row-level filtering after localization.
82
struct TableFilter {
83
    VExprContextSPtr conjunct;
84
    std::vector<GlobalIndex> global_indices;
85
};
86
87
struct ScanTask {
88
123k
    virtual ~ScanTask() = default;
89
90
    std::unique_ptr<io::FileDescription> data_file;
91
};
92
93
struct ProjectedColumnBuildContext {
94
    const TFileScanRangeParams* scan_params = nullptr;
95
    const TFileRangeDesc* range = nullptr;
96
    RuntimeState* runtime_state = nullptr;
97
    std::optional<ColumnDefinition> schema_column = std::nullopt;
98
    size_t next_file_column_idx = 0;
99
};
100
101
struct ReadProfile {
102
    RuntimeProfile::Counter* num_delete_files = nullptr;
103
    RuntimeProfile::Counter* num_delete_rows = nullptr;
104
    RuntimeProfile::Counter* parse_delete_file_time = nullptr;
105
    RuntimeProfile::Counter* decoded_dv_cache_hit_count = nullptr;
106
    RuntimeProfile::Counter* decoded_dv_cache_miss_count = nullptr;
107
    RuntimeProfile::Counter* dv_file_cache_hit_count = nullptr;
108
    RuntimeProfile::Counter* dv_file_cache_miss_count = nullptr;
109
    RuntimeProfile::Counter* dv_file_cache_peer_read_count = nullptr;
110
    RuntimeProfile::Counter* exec_timer = nullptr;
111
    RuntimeProfile::Counter* prepare_split_timer = nullptr;
112
    RuntimeProfile::Counter* finalize_timer = nullptr;
113
    RuntimeProfile::Counter* create_reader_timer = nullptr;
114
    RuntimeProfile::Counter* pushdown_agg_timer = nullptr;
115
    RuntimeProfile::Counter* open_reader_timer = nullptr;
116
    RuntimeProfile::Counter* runtime_filter_partition_prune_timer = nullptr;
117
    RuntimeProfile::Counter* runtime_filter_partition_pruned_range_counter = nullptr;
118
};
119
120
struct TableReadOptions {
121
    // Columns need to be read from file and output by table reader. They are all in table/global
122
    // schema semantics.
123
    const std::vector<ColumnDefinition> projected_columns;
124
    // All complex conjuncts from scan operator
125
    const VExprContextSPtrs conjuncts;
126
    // File format of the underlying data files, needed for reader initialization and reader-level
127
    // filter pushdown.
128
    const FileFormat format;
129
    TFileScanRangeParams* scan_params;
130
    std::shared_ptr<io::IOContext> io_ctx;
131
    RuntimeState* runtime_state;
132
    RuntimeProfile* scanner_profile;
133
    // File formats without complete self-describing metadata, such as CSV, Text, and JSON, need
134
    // the FE-planned physical file slots to build their file-local schema and deserialize values.
135
    const std::vector<SlotDescriptor*>* file_slot_descs = nullptr;
136
    // Push-down aggregate type.
137
    const TPushAggOp::type push_down_agg_type = TPushAggOp::type::NONE;
138
    // Digest of stable pushed-down predicates. A zero digest disables condition cache.
139
    uint64_t condition_cache_digest = 0;
140
};
141
142
struct SplitReadOptions {
143
    // Split-level information for reader initialization, which may include file path, partition values, delete file info, etc. The content is table format specific and opaque to table reader base class; it's the responsibility of the concrete table reader implementation to parse necessary information for reader initialization and filter pushdown.
144
    std::map<std::string, Field> partition_values;
145
    // Latest scanner conjuncts rewritten to table/global column indices. Runtime filters can
146
    // arrive after TableReader::init(), so split preparation must receive a fresh snapshot.
147
    VExprContextSPtrs partition_prune_conjuncts;
148
    ShardedKVCache* cache;
149
    TFileRangeDesc current_range;
150
    FileFormat current_split_format = FileFormat::PARQUET;
151
    std::optional<GlobalRowIdContext> global_rowid_context;
152
};
153
154
// Base class for table-level readers.
155
// This layer owns common table-level orchestration, such as split iteration, dynamic partition
156
// pruning, delete handling and conversion from file-local blocks to table-schema blocks. Concrete
157
// table-format readers only need to provide format-specific hooks for opening readers and parsing
158
// split metadata.
159
class TableReader {
160
public:
161
54.7k
    virtual ~TableReader() = default;
162
163
    // Initialize common runtime options for the table reader. Subclasses may call this from their
164
    // own init(options); table-format schema and split metadata are provided later per split.
165
    virtual Status init(TableReadOptions&& options);
166
167
    // FileScannerV2 adjusts this before each get_block() using an adaptive bytes-per-row estimate.
168
    // Store it here as well as forwarding to the current reader so newly opened split readers start
169
    // with the latest predicted batch size.
170
243k
    void set_batch_size(size_t batch_size) {
171
243k
        _batch_size = std::max<size_t>(1, batch_size);
172
243k
        if (_data_reader.reader != nullptr) {
173
112k
            _data_reader.reader->set_batch_size(_batch_size);
174
112k
        }
175
243k
    }
176
177
    // Prepare for reading a new split/task.
178
    // 1. Pass a new split/task to reader, which will be used in subsequent open_reader() to initialize the underlying file reader.
179
    // 2. Parse delete predicates from split/task information, which will be used for later dynamic filtering and delete handling.
180
    virtual Status prepare_split(const SplitReadOptions& options);
181
182
155k
    virtual bool current_split_pruned() const { return _current_split_pruned; }
183
184
    // Discard the active split after the caller decides an error is ignorable, for example a
185
    // stale external-table file listing that returns NOT_FOUND. The next prepare_split() must start
186
    // with no concrete reader or split-local state left from the failed split.
187
1
    virtual Status abort_split() {
188
1
        if (_data_reader.reader != nullptr) {
189
1
            RETURN_IF_ERROR(close_current_reader());
190
1
        } else {
191
0
            _current_task.reset();
192
0
            _current_file_description.reset();
193
0
        }
194
1
        _delete_rows = nullptr;
195
1
        _remaining_table_level_count = -1;
196
1
        _current_split_pruned = false;
197
1
        return Status::OK();
198
1
    }
199
200
    // Public entry point for reading a table-schema block. The base class opens the current reader,
201
    // advances across EOF, and closes exhausted readers. Subclasses provide protected hooks for
202
    // table-format-specific behavior.
203
237k
    virtual Status get_block(Block* block, bool* eos) {
204
237k
        SCOPED_TIMER(_profile.exec_timer);
205
237k
        DORIS_CHECK(block->columns() == _projected_columns.size());
206
237k
        block->clear_column_data(_projected_columns.size());
207
208
363k
        while (true) {
209
363k
            if (*eos) {
210
0
                return Status::OK();
211
0
            }
212
363k
            if (_io_ctx != nullptr && _io_ctx->should_stop) {
213
4
                *eos = true;
214
4
                return Status::OK();
215
4
            }
216
363k
            if (!_data_reader.reader) {
217
234k
                if (_is_table_level_count_active()) {
218
298
                    RETURN_IF_ERROR(_read_table_level_count(block, eos));
219
298
                    return Status::OK();
220
298
                }
221
234k
                RETURN_IF_ERROR(create_next_reader(eos));
222
234k
                if (!_data_reader.reader) {
223
117k
                    DCHECK(*eos);
224
117k
                    return Status::OK();
225
117k
                }
226
234k
            }
227
228
            // Materialize a reduced row set for upper aggregate operators when aggregate
229
            // pushdown can be applied. This is not the final aggregate result: COUNT emits
230
            // `count` default rows for the upper COUNT(*), and MIN/MAX emits two rows containing
231
            // file-level min/max values for the upper MIN/MAX.
232
245k
            if (!_aggregate_pushdown_tried) {
233
116k
                SCOPED_TIMER(_profile.pushdown_agg_timer);
234
116k
                bool pushed_down = false;
235
116k
                const auto status = _try_materialize_aggregate_pushdown_rows(block, &pushed_down);
236
116k
                if (!status.ok()) {
237
1
                    if (_io_ctx != nullptr && _io_ctx->should_stop &&
238
1
                        status.is<ErrorCode::END_OF_FILE>()) {
239
1
                        *eos = true;
240
1
                        return Status::OK();
241
1
                    }
242
0
                    return status;
243
1
                }
244
116k
                if (pushed_down) {
245
1.39k
                    return Status::OK();
246
1.39k
                }
247
116k
            }
248
249
243k
            bool current_eof = false;
250
243k
            _data_reader.block_template.clear_column_data(
251
243k
                    cast_set<int64_t>(_data_reader.file_block_layout.size()));
252
243k
            size_t current_rows = 0;
253
243k
            RETURN_IF_ERROR(_data_reader.reader->get_block(&_data_reader.block_template,
254
243k
                                                           &current_rows, &current_eof));
255
243k
            if (current_rows == 0) {
256
125k
                if (current_eof) {
257
114k
                    _current_reader_reached_eof = true;
258
114k
                    RETURN_IF_ERROR(close_current_reader());
259
114k
                }
260
125k
                continue;
261
125k
            }
262
243k
            DCHECK_EQ(_data_reader.block_template.columns(), _data_reader.file_block_layout.size())
263
0
                    << _data_reader.block_template.dump_structure();
264
117k
#ifndef NDEBUG
265
117k
            RETURN_IF_ERROR(_check_file_block_columns("after file reader get_block", current_rows));
266
117k
#endif
267
117k
            DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
268
117k
            RETURN_IF_ERROR(finalize_chunk(block, current_rows));
269
117k
#ifndef NDEBUG
270
117k
            RETURN_IF_ERROR(
271
117k
                    _check_table_block_columns("after finalize_chunk", block, current_rows));
272
117k
#endif
273
117k
            if (current_eof) {
274
7
                _current_reader_reached_eof = true;
275
7
                RETURN_IF_ERROR(close_current_reader());
276
7
            }
277
117k
            return Status::OK();
278
117k
        }
279
237k
    }
280
281
    // Close the table reader and the currently active file reader. Subclasses that hold additional
282
    // table-format resources should override this and call TableReader::close() first.
283
48.6k
    virtual Status close() {
284
48.6k
        if (_data_reader.reader) {
285
632
            RETURN_IF_ERROR(close_current_reader());
286
632
        }
287
48.6k
        _current_task.reset();
288
48.6k
        _current_file_description.reset();
289
48.6k
        _remaining_table_level_count = -1;
290
48.6k
        return Status::OK();
291
48.6k
    }
292
293
96.9k
    int64_t condition_cache_hit_count() const { return _condition_cache_hit_count; }
294
295
    virtual std::string debug_string() const;
296
297
    virtual Status annotate_projected_column(const TFileScanSlotInfo& slot_info,
298
                                             ProjectedColumnBuildContext* context,
299
                                             ColumnDefinition* column) const;
300
301
28.6k
    virtual Status validate_projected_columns(const ProjectedColumnBuildContext& context) const {
302
28.6k
        (void)context;
303
28.6k
        return Status::OK();
304
28.6k
    }
305
306
protected:
307
    // Parse deletion vector information from table format specific file description.
308
    virtual Status _parse_deletion_vector_file(const TTableFormatFileDesc& t_desc,
309
39.7k
                                               DeleteFileDesc* desc, bool* has_delete_file) {
310
39.7k
        *has_delete_file = false;
311
39.7k
        return Status::OK();
312
39.7k
    }
313
314
    // Advance to the next reader. This closes the current reader first and then opens the next
315
    // concrete reader. Subclasses should not duplicate this loop.
316
    Status create_next_reader(bool* eos);
317
    virtual Status create_file_reader(std::unique_ptr<FileReader>* reader);
318
6.66k
    virtual TableColumnMappingMode mapping_mode() const { return TableColumnMappingMode::BY_NAME; }
319
32.2k
    virtual Status annotate_file_schema(std::vector<ColumnDefinition>* file_schema) {
320
32.2k
        DORIS_CHECK(file_schema != nullptr);
321
32.2k
        return Status::OK();
322
32.2k
    }
323
324
    // Open the concrete reader for the current split/task and build the file-local scan request.
325
118k
    virtual Status open_reader() {
326
118k
        SCOPED_TIMER(_profile.open_reader_timer);
327
        // 1. Get file schema and create column mapping.
328
118k
        std::vector<ColumnDefinition> file_schema;
329
118k
        RETURN_IF_ERROR(_data_reader.reader->get_schema(&file_schema));
330
        // For Paimon/Hudi, FE can provide field ids through `history_schema_info`. Annotate the
331
        // file schema before column mapping when the table format maps columns by field id.
332
118k
        RETURN_IF_ERROR(annotate_file_schema(&file_schema));
333
118k
        _data_reader.file_schema = file_schema;
334
118k
        _mapper_options.mode = mapping_mode();
335
336
118k
        _data_reader.column_mapper = _data_reader.reader->create_column_mapper(_mapper_options);
337
118k
        DORIS_CHECK(_data_reader.column_mapper != nullptr);
338
118k
        RETURN_IF_ERROR(_data_reader.column_mapper->create_mapping(_projected_columns,
339
118k
                                                                   _partition_values, file_schema));
340
118k
        DORIS_CHECK(_data_reader.column_mapper->mappings().size() == _projected_columns.size());
341
342
        // 2. Build table filters based on conjuncts and column predicates.
343
118k
        RETURN_IF_ERROR(_build_table_filters_from_conjuncts());
344
345
        // 3. Create file scan request based on column mapping and table filters, then open file
346
        // reader with the request. File scan request carries row-level expression filters and
347
        // file-level pruning hints. Only expression filters decide returned rows.
348
118k
        auto file_request = std::make_shared<FileScanRequest>();
349
118k
        RETURN_IF_ERROR(_data_reader.column_mapper->create_scan_request(
350
118k
                _table_filters, _projected_columns, file_request.get(), _runtime_state));
351
118k
        bool constant_filter_pruned_split = false;
352
118k
        RETURN_IF_ERROR(_evaluate_constant_filters(&constant_filter_pruned_split));
353
118k
        if (constant_filter_pruned_split) {
354
1.37k
            RETURN_IF_ERROR(close_current_reader());
355
1.37k
            return Status::OK();
356
1.37k
        }
357
116k
        RETURN_IF_ERROR(customize_file_scan_request(file_request.get()));
358
116k
        RETURN_IF_ERROR(_open_local_filter_exprs(*file_request));
359
116k
        _data_reader.file_block_layout.clear();
360
116k
        _data_reader.block_template.clear();
361
116k
        _data_reader.file_block_layout.resize(file_request->local_positions.size());
362
363
        // 4. Build file block layout from file schema and column mapping. The layout describes
364
        // the block returned by file reader before table-column materialization.
365
520k
        for (const auto& [file_column_id, block_position] : file_request->local_positions) {
366
520k
            DORIS_CHECK(block_position.value() < _data_reader.file_block_layout.size());
367
520k
            const auto* field = _find_column_definition(_data_reader.file_schema, file_column_id);
368
520k
            DORIS_CHECK(field != nullptr);
369
370
520k
            ColumnDefinition projected_field;
371
520k
            {
372
520k
                auto it = std::find_if(
373
520k
                        file_request->non_predicate_columns.begin(),
374
520k
                        file_request->non_predicate_columns.end(),
375
8.85M
                        [&](const LocalColumnIndex& p) { return p.column_id() == file_column_id; });
376
520k
                if (it != file_request->non_predicate_columns.end()) {
377
428k
                    RETURN_IF_ERROR(project_column_definition(*field, *it, &projected_field));
378
428k
                }
379
520k
            }
380
520k
            {
381
520k
                auto it = std::find_if(
382
520k
                        file_request->predicate_columns.begin(),
383
520k
                        file_request->predicate_columns.end(),
384
520k
                        [&](const LocalColumnIndex& p) { return p.column_id() == file_column_id; });
385
520k
                if (it != file_request->predicate_columns.end()) {
386
92.0k
                    RETURN_IF_ERROR(project_column_definition(*field, *it, &projected_field));
387
92.0k
                }
388
520k
            }
389
520k
            _data_reader.file_block_layout[block_position.value()] = {
390
520k
                    .file_column_id = file_column_id,
391
520k
                    .name = projected_field.name,
392
520k
                    .type = projected_field.type,
393
520k
            };
394
520k
            DORIS_CHECK(_data_reader.file_block_layout[block_position.value()].type != nullptr);
395
520k
        }
396
397
        // 5. Prepare block template from file block layout. The block template stores the block
398
        // returned by file reader before table-column materialization.
399
116k
        _data_reader.block_template.reserve(_data_reader.file_block_layout.size());
400
520k
        for (const auto& column : _data_reader.file_block_layout) {
401
520k
            _data_reader.block_template.insert(
402
520k
                    {column.type->create_column(), column.type, column.name});
403
520k
        }
404
116k
        if (VLOG_DEBUG_IS_ON) {
405
0
            VLOG_DEBUG << "TableReader debug: " << debug_string();
406
0
        }
407
116k
        RETURN_IF_ERROR(_open_mapping_exprs());
408
116k
        RETURN_IF_ERROR(_data_reader.reader->open(file_request));
409
116k
        RETURN_IF_ERROR(_init_reader_condition_cache(*file_request));
410
116k
        return Status::OK();
411
116k
    }
412
413
    Status _build_table_filters_from_conjuncts();
414
    Status _evaluate_partition_prune_conjuncts(const VExprContextSPtrs& conjuncts,
415
                                               bool* can_filter_all);
416
    Status _build_partition_prune_block(Block* block) const;
417
    Status _open_local_filter_exprs(const FileScanRequest& file_request);
418
    Status _init_reader_condition_cache(const FileScanRequest& file_request);
419
    void _finalize_reader_condition_cache();
420
    bool _should_enable_condition_cache(const FileScanRequest& file_request) const;
421
422
117k
    Status _evaluate_constant_filters(bool* can_filter_all) {
423
117k
        DORIS_CHECK(can_filter_all != nullptr);
424
117k
        *can_filter_all = false;
425
117k
        for (const auto& table_filter : _table_filters) {
426
80.2k
            if (table_filter.conjunct == nullptr ||
427
                // RuntimeFilterExpr does not implement execute_column_impl(); it is evaluated by
428
                // the row-level filter path through execute_filter(). Constant split pruning uses
429
                // VExprContext::execute() on a one-row synthetic block, so runtime filters must not
430
                // be pre-executed here even when their referenced slot maps to a constant value.
431
80.2k
                table_filter.conjunct->root()->is_rf_wrapper() ||
432
80.2k
                !_table_filter_has_only_constant_entries(table_filter)) {
433
76.1k
                continue;
434
76.1k
            }
435
4.08k
            Block eval_block;
436
4.08k
            RETURN_IF_ERROR(_build_constant_filter_block(table_filter, &eval_block));
437
4.08k
            RowDescriptor row_desc;
438
4.08k
            RETURN_IF_ERROR(table_filter.conjunct->prepare(_runtime_state, row_desc));
439
4.08k
            RETURN_IF_ERROR(table_filter.conjunct->open(_runtime_state));
440
4.08k
            int result_column_id = -1;
441
4.08k
            RETURN_IF_ERROR(table_filter.conjunct->execute(&eval_block, &result_column_id));
442
4.08k
            DORIS_CHECK(result_column_id >= 0);
443
4.08k
            if (_filter_result_filters_all(eval_block.get_by_position(result_column_id).column)) {
444
1.37k
                *can_filter_all = true;
445
1.37k
                return Status::OK();
446
1.37k
            }
447
4.08k
        }
448
116k
        return Status::OK();
449
117k
    }
450
451
73.8k
    bool _table_filter_has_only_constant_entries(const TableFilter& table_filter) const {
452
73.8k
        const auto& filter_entries = _data_reader.column_mapper->filter_entries();
453
74.0k
        for (const auto global_index : table_filter.global_indices) {
454
74.0k
            const auto entry_it = filter_entries.find(global_index);
455
74.0k
            if (entry_it == filter_entries.end() || !entry_it->second.is_constant()) {
456
69.6k
                return false;
457
69.6k
            }
458
74.0k
        }
459
4.16k
        return !table_filter.global_indices.empty();
460
73.8k
    }
461
462
4.11k
    Status _build_constant_filter_block(const TableFilter& table_filter, Block* eval_block) {
463
4.11k
        DORIS_CHECK(eval_block != nullptr);
464
4.11k
        eval_block->clear();
465
4.11k
        const auto& mappings = _data_reader.column_mapper->mappings();
466
4.11k
        const auto& filter_entries = _data_reader.column_mapper->filter_entries();
467
4.11k
        DORIS_CHECK(mappings.size() == _projected_columns.size());
468
16.5k
        for (size_t column_idx = 0; column_idx < mappings.size(); ++column_idx) {
469
12.3k
            const auto global_index = GlobalIndex(column_idx);
470
12.3k
            const auto& mapping = mappings[column_idx];
471
12.3k
            const auto entry_it = filter_entries.find(global_index);
472
12.3k
            const bool referenced_by_filter =
473
12.3k
                    std::find(table_filter.global_indices.begin(),
474
12.3k
                              table_filter.global_indices.end(),
475
12.3k
                              global_index) != table_filter.global_indices.end();
476
12.3k
            if (referenced_by_filter && entry_it != filter_entries.end() &&
477
12.3k
                entry_it->second.is_constant()) {
478
4.36k
                ColumnPtr constant_column;
479
4.36k
                RETURN_IF_ERROR(_materialize_constant_filter_column(
480
4.36k
                        entry_it->second.constant_index(), &constant_column));
481
4.36k
                eval_block->insert({std::move(constant_column), mapping.table_type,
482
4.36k
                                    mapping.table_column_name});
483
8.03k
            } else {
484
8.03k
                eval_block->insert({mapping.table_type->create_column_const_with_default_value(1),
485
8.03k
                                    mapping.table_type, mapping.table_column_name});
486
8.03k
            }
487
12.3k
        }
488
4.11k
        return Status::OK();
489
4.11k
    }
490
491
4.36k
    Status _materialize_constant_filter_column(ConstantIndex constant_index, ColumnPtr* column) {
492
4.36k
        DORIS_CHECK(column != nullptr);
493
4.36k
        const auto& constant_entry = _data_reader.column_mapper->constant_map().get(constant_index);
494
4.36k
        DORIS_CHECK(constant_entry.expr != nullptr);
495
4.36k
        DORIS_CHECK(constant_entry.type != nullptr);
496
4.36k
        RowDescriptor row_desc;
497
4.36k
        RETURN_IF_ERROR(constant_entry.expr->prepare(_runtime_state, row_desc));
498
4.36k
        RETURN_IF_ERROR(constant_entry.expr->open(_runtime_state));
499
4.36k
        Block eval_block;
500
4.36k
        eval_block.insert({constant_entry.type->create_column_const_with_default_value(1),
501
4.36k
                           constant_entry.type, "__table_reader_constant_filter"});
502
4.36k
        int result_column_id = -1;
503
4.36k
        RETURN_IF_ERROR(constant_entry.expr->execute(&eval_block, &result_column_id));
504
4.36k
        DORIS_CHECK(result_column_id >= 0);
505
4.36k
        *column = eval_block.get_by_position(result_column_id).column;
506
4.36k
        DORIS_CHECK((*column)->size() == 1);
507
4.36k
        return Status::OK();
508
4.36k
    }
509
510
4.12k
    static bool _filter_result_filters_all(const ColumnPtr& filter_column) {
511
4.12k
        DORIS_CHECK(filter_column.get() != nullptr);
512
4.12k
        DORIS_CHECK(filter_column->size() == 1);
513
4.12k
        return !filter_column->get_bool(0);
514
4.12k
    }
515
516
116k
    virtual Status customize_file_scan_request(FileScanRequest* file_request) {
517
116k
        return _append_delete_predicate(file_request);
518
116k
    }
519
520
400k
    bool _is_table_level_count_active() const { return _remaining_table_level_count >= 0; }
521
522
1.45k
    Status _materialize_count_rows(size_t rows, Block* block) const {
523
1.45k
        DORIS_CHECK(block != nullptr);
524
1.45k
        DORIS_CHECK(block->columns() > 0 || rows == 0);
525
2.90k
        for (size_t column_idx = 0; column_idx < block->columns(); ++column_idx) {
526
1.45k
            auto column = block->get_by_position(column_idx).type->create_column();
527
1.45k
            column->resize(rows);
528
1.45k
            block->replace_by_position(column_idx, std::move(column));
529
1.45k
        }
530
1.45k
        return Status::OK();
531
1.45k
    }
532
533
430
    Status _read_table_level_count(Block* block, bool* eos) {
534
430
        DORIS_CHECK(block != nullptr);
535
430
        DORIS_CHECK(eos != nullptr);
536
430
        DORIS_CHECK(_push_down_agg_type == TPushAggOp::type::COUNT);
537
430
        DORIS_CHECK(_remaining_table_level_count >= 0);
538
430
        if (_remaining_table_level_count == 0) {
539
214
            _remaining_table_level_count = -1;
540
214
            _current_task.reset();
541
214
            *eos = true;
542
214
            return Status::OK();
543
214
        }
544
545
216
        const int64_t batch_size = _runtime_state == nullptr
546
216
                                           ? _remaining_table_level_count
547
216
                                           : static_cast<int64_t>(_runtime_state->batch_size());
548
216
        const auto rows = std::min(_remaining_table_level_count, batch_size);
549
216
        RETURN_IF_ERROR(_materialize_count_rows(cast_set<size_t>(rows), block));
550
216
        _remaining_table_level_count -= rows;
551
216
        *eos = false;
552
216
        return Status::OK();
553
216
    }
554
555
    void _append_file_scan_column(FileScanRequest* request, LocalColumnId column_id,
556
31.7k
                                  std::vector<LocalColumnIndex>* scan_columns) {
557
31.7k
        DORIS_CHECK(request != nullptr);
558
31.7k
        DORIS_CHECK(scan_columns != nullptr);
559
31.7k
        FileScanRequestBuilder builder(request);
560
31.7k
        Status status;
561
31.7k
        if (scan_columns == &request->predicate_columns) {
562
31.2k
            status = builder.add_predicate_column(column_id);
563
31.2k
        } else {
564
567
            DORIS_CHECK(scan_columns == &request->non_predicate_columns);
565
567
            status = builder.add_non_predicate_column(column_id);
566
567
        }
567
31.7k
        DORIS_CHECK(status.ok()) << status.to_string();
568
31.7k
        if (column_id == LocalColumnId(ROW_POSITION_COLUMN_ID) &&
569
31.7k
            _find_column_definition(_data_reader.file_schema, column_id) == nullptr) {
570
27.1k
            _data_reader.file_schema.push_back(row_position_column_definition());
571
27.1k
        }
572
31.7k
    }
573
574
    // Append DeletePredicate to file scan request if there are deletes. The predicate will be evaluated in file reader level and filter out deleted rows before returning data to table reader.
575
116k
    Status _append_delete_predicate(FileScanRequest* request) {
576
116k
        DORIS_CHECK(request != nullptr);
577
116k
        if ((_delete_rows == nullptr || _delete_rows->empty()) &&
578
116k
            (_deletion_vector == nullptr || _deletion_vector->isEmpty())) {
579
89.7k
            return Status::OK();
580
89.7k
        }
581
26.6k
        const auto row_position_column_id = LocalColumnId(ROW_POSITION_COLUMN_ID);
582
26.6k
        _append_file_scan_column(request, row_position_column_id, &request->predicate_columns);
583
584
26.6k
        const auto block_position = request->local_positions.at(row_position_column_id);
585
26.7k
        auto append_predicate = [&](auto& deleted_rows) {
586
26.7k
            auto delete_predicate = std::make_shared<DeletePredicate>(deleted_rows);
587
26.7k
            delete_predicate->add_child(VSlotRef::create_shared(
588
26.7k
                    cast_set<int>(block_position.value()), cast_set<int>(block_position.value()),
589
26.7k
                    -1, std::make_shared<DataTypeInt64>(), ROW_POSITION_COLUMN_NAME));
590
26.7k
            request->delete_conjuncts.push_back(
591
26.7k
                    VExprContext::create_shared(std::move(delete_predicate)));
592
26.7k
        };
_ZZN5doris6format11TableReader24_append_delete_predicateEPNS0_15FileScanRequestEENKUlRT_E_clISt6vectorIlSaIlEEEEDaS5_
Line
Count
Source
585
1.48k
        auto append_predicate = [&](auto& deleted_rows) {
586
1.48k
            auto delete_predicate = std::make_shared<DeletePredicate>(deleted_rows);
587
1.48k
            delete_predicate->add_child(VSlotRef::create_shared(
588
1.48k
                    cast_set<int>(block_position.value()), cast_set<int>(block_position.value()),
589
1.48k
                    -1, std::make_shared<DataTypeInt64>(), ROW_POSITION_COLUMN_NAME));
590
1.48k
            request->delete_conjuncts.push_back(
591
1.48k
                    VExprContext::create_shared(std::move(delete_predicate)));
592
1.48k
        };
_ZZN5doris6format11TableReader24_append_delete_predicateEPNS0_15FileScanRequestEENKUlRT_E_clIN7roaring12Roaring64MapEEEDaS5_
Line
Count
Source
585
25.2k
        auto append_predicate = [&](auto& deleted_rows) {
586
25.2k
            auto delete_predicate = std::make_shared<DeletePredicate>(deleted_rows);
587
25.2k
            delete_predicate->add_child(VSlotRef::create_shared(
588
25.2k
                    cast_set<int>(block_position.value()), cast_set<int>(block_position.value()),
589
25.2k
                    -1, std::make_shared<DataTypeInt64>(), ROW_POSITION_COLUMN_NAME));
590
25.2k
            request->delete_conjuncts.push_back(
591
25.2k
                    VExprContext::create_shared(std::move(delete_predicate)));
592
25.2k
        };
593
26.6k
        if (_delete_rows != nullptr && !_delete_rows->empty()) {
594
1.48k
            append_predicate(*_delete_rows);
595
1.48k
        }
596
26.6k
        if (_deletion_vector != nullptr && !_deletion_vector->isEmpty()) {
597
25.2k
            append_predicate(*_deletion_vector);
598
25.2k
        }
599
26.6k
        return Status::OK();
600
116k
    }
601
602
    // Close the current concrete reader. This hook is called by both create_next_reader() and
603
    // close(), so it should remain idempotent.
604
118k
    virtual Status close_current_reader() {
605
118k
        _finalize_reader_condition_cache();
606
118k
        RETURN_IF_ERROR(_data_reader.reader->close());
607
118k
        _data_reader.reader.reset();
608
118k
        if (_data_reader.column_mapper != nullptr) {
609
117k
            _data_reader.column_mapper->clear();
610
117k
            _data_reader.column_mapper.reset();
611
117k
        }
612
118k
        _table_filters.clear();
613
118k
        _data_reader.file_schema.clear();
614
118k
        _data_reader.file_block_layout.clear();
615
118k
        _data_reader.block_template.clear();
616
118k
        _current_task.reset();
617
118k
        _current_file_description.reset();
618
118k
        _current_reader_reached_eof = false;
619
118k
        return Status::OK();
620
118k
    }
621
622
6.09k
    void _record_scan_rows(size_t rows) {
623
6.09k
        if (_io_ctx != nullptr && _io_ctx->file_reader_stats != nullptr) {
624
6.09k
            _io_ctx->file_reader_stats->read_rows += rows;
625
6.09k
        }
626
6.09k
    }
627
628
    // Finalize file-local block to table/global schema block.
629
118k
    Status finalize_chunk(Block* block, const size_t rows) {
630
118k
        SCOPED_TIMER(_profile.finalize_timer);
631
118k
        size_t idx = 0;
632
647k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
633
647k
            ColumnPtr column;
634
647k
            RETURN_IF_ERROR(_materialize_mapping_column(mapping, &_data_reader.block_template, rows,
635
647k
                                                        &column));
636
647k
            block->replace_by_position(idx, IColumn::mutate(std::move(column)));
637
647k
            idx++;
638
647k
        }
639
118k
        RETURN_IF_ERROR(materialize_virtual_columns(block));
640
        // Enforce CHAR/VARCHAR length declared by the table schema after all file-to-table
641
        // materialization has finished.
642
118k
        RETURN_IF_ERROR(_truncate_char_or_varchar_columns(block));
643
118k
        return Status::OK();
644
118k
    }
645
646
    // Materialize virtual columns in the table block, such as Iceberg _row_id and
647
    // _last_updated_sequence_number. This runs after normal column materialization so finalize
648
    // expressions can reference those virtual columns.
649
93.8k
    virtual Status materialize_virtual_columns(Block* table_block) { return Status::OK(); }
650
651
#ifndef NDEBUG
652
118k
    Status _check_file_block_columns(std::string_view stage, size_t rows) {
653
118k
        DORIS_CHECK(_data_reader.block_template.columns() == _data_reader.file_block_layout.size());
654
752k
        for (size_t idx = 0; idx < _data_reader.block_template.columns(); ++idx) {
655
634k
            const auto& file_block_column = _data_reader.file_block_layout[idx];
656
634k
            const auto& column_with_type = _data_reader.block_template.get_by_position(idx);
657
634k
            const auto* column = column_with_type.column.get();
658
634k
            try {
659
634k
                if (column == nullptr) {
660
0
                    auto st = Status::InternalError(
661
0
                            "Invalid file block column {} at {}: file_column_id={}, name='{}', "
662
0
                            "type={}, column=null, expected_rows={}, reader={}",
663
0
                            idx, stage, file_block_column.file_column_id.value(),
664
0
                            file_block_column.name,
665
0
                            file_block_column.type == nullptr ? "null"
666
0
                                                              : file_block_column.type->get_name(),
667
0
                            rows, debug_string());
668
0
                    LOG(WARNING) << st;
669
0
                    return st;
670
0
                }
671
634k
                column->sanity_check();
672
634k
                auto st = column_with_type.check_type_and_column_match();
673
634k
                if (!st.ok()) {
674
0
                    auto contextual_status = Status::InternalError(
675
0
                            "Invalid file block column {} at {}: file_column_id={}, name='{}', "
676
0
                            "type={}, column={}, column_size={}, expected_rows={}, error={}, "
677
0
                            "reader={}",
678
0
                            idx, stage, file_block_column.file_column_id.value(),
679
0
                            file_block_column.name,
680
0
                            file_block_column.type == nullptr ? "null"
681
0
                                                              : file_block_column.type->get_name(),
682
0
                            column->get_name(), column->size(), rows, st.to_string(),
683
0
                            debug_string());
684
0
                    LOG(WARNING) << contextual_status;
685
0
                    return contextual_status;
686
0
                }
687
634k
            } catch (const Exception& e) {
688
0
                auto st = Status::InternalError(
689
0
                        "Invalid file block column {} at {}: file_column_id={}, name='{}', "
690
0
                        "type={}, column={}, column_size={}, expected_rows={}, error={}, "
691
0
                        "reader={}",
692
0
                        idx, stage, file_block_column.file_column_id.value(),
693
0
                        file_block_column.name,
694
0
                        file_block_column.type == nullptr ? "null"
695
0
                                                          : file_block_column.type->get_name(),
696
0
                        column == nullptr ? "null" : column->get_name(),
697
0
                        column == nullptr ? 0 : column->size(), rows, e.to_string(),
698
0
                        debug_string());
699
0
                LOG(WARNING) << st;
700
0
                return st;
701
0
            } catch (const std::exception& e) {
702
0
                auto st = Status::InternalError(
703
0
                        "Invalid file block column {} at {}: file_column_id={}, name='{}', "
704
0
                        "type={}, column={}, column_size={}, expected_rows={}, error={}, "
705
0
                        "reader={}",
706
0
                        idx, stage, file_block_column.file_column_id.value(),
707
0
                        file_block_column.name,
708
0
                        file_block_column.type == nullptr ? "null"
709
0
                                                          : file_block_column.type->get_name(),
710
0
                        column == nullptr ? "null" : column->get_name(),
711
0
                        column == nullptr ? 0 : column->size(), rows, e.what(), debug_string());
712
0
                LOG(WARNING) << st;
713
0
                return st;
714
0
            }
715
634k
        }
716
118k
        return Status::OK();
717
118k
    }
718
719
118k
    Status _check_table_block_columns(std::string_view stage, const Block* block, size_t rows) {
720
118k
        DORIS_CHECK(block != nullptr);
721
118k
        DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
722
765k
        for (size_t idx = 0; idx < block->columns(); ++idx) {
723
647k
            const auto& mapping = _data_reader.column_mapper->mappings()[idx];
724
647k
            const auto& column_with_type = block->get_by_position(idx);
725
647k
            const auto* column = column_with_type.column.get();
726
647k
            try {
727
647k
                if (column == nullptr) {
728
0
                    auto st = Status::InternalError(
729
0
                            "Invalid table block column {} at {}: table_column='{}', "
730
0
                            "global_index={}, type={}, column=null, expected_rows={}, mapping={}",
731
0
                            idx, stage, mapping.table_column_name, mapping.global_index.value(),
732
0
                            mapping.table_type == nullptr ? "null" : mapping.table_type->get_name(),
733
0
                            rows, mapping.debug_string());
734
0
                    LOG(WARNING) << st;
735
0
                    return st;
736
0
                }
737
647k
                column->sanity_check();
738
647k
                auto st = column_with_type.check_type_and_column_match();
739
647k
                if (!st.ok()) {
740
0
                    auto contextual_status = Status::InternalError(
741
0
                            "Invalid table block column {} at {}: table_column='{}', "
742
0
                            "global_index={}, type={}, column={}, column_size={}, "
743
0
                            "expected_rows={}, error={}, mapping={}",
744
0
                            idx, stage, mapping.table_column_name, mapping.global_index.value(),
745
0
                            mapping.table_type == nullptr ? "null" : mapping.table_type->get_name(),
746
0
                            column->get_name(), column->size(), rows, st.to_string(),
747
0
                            mapping.debug_string());
748
0
                    LOG(WARNING) << contextual_status;
749
0
                    return contextual_status;
750
0
                }
751
647k
            } catch (const Exception& e) {
752
0
                auto st = Status::InternalError(
753
0
                        "Invalid table block column {} at {}: table_column='{}', global_index={}, "
754
0
                        "type={}, column={}, column_size={}, expected_rows={}, error={}, "
755
0
                        "mapping={}",
756
0
                        idx, stage, mapping.table_column_name, mapping.global_index.value(),
757
0
                        mapping.table_type == nullptr ? "null" : mapping.table_type->get_name(),
758
0
                        column == nullptr ? "null" : column->get_name(),
759
0
                        column == nullptr ? 0 : column->size(), rows, e.to_string(),
760
0
                        mapping.debug_string());
761
0
                LOG(WARNING) << st;
762
0
                return st;
763
0
            } catch (const std::exception& e) {
764
0
                auto st = Status::InternalError(
765
0
                        "Invalid table block column {} at {}: table_column='{}', global_index={}, "
766
0
                        "type={}, column={}, column_size={}, expected_rows={}, error={}, "
767
0
                        "mapping={}",
768
0
                        idx, stage, mapping.table_column_name, mapping.global_index.value(),
769
0
                        mapping.table_type == nullptr ? "null" : mapping.table_type->get_name(),
770
0
                        column == nullptr ? "null" : column->get_name(),
771
0
                        column == nullptr ? 0 : column->size(), rows, e.what(),
772
0
                        mapping.debug_string());
773
0
                LOG(WARNING) << st;
774
0
                return st;
775
0
            }
776
647k
        }
777
118k
        return Status::OK();
778
118k
    }
779
#endif
780
781
118k
    Status _truncate_char_or_varchar_columns(Block* block) {
782
118k
        DORIS_CHECK(block != nullptr);
783
118k
        if (_runtime_state == nullptr ||
784
118k
            !_runtime_state->query_options().truncate_char_or_varchar_columns) {
785
118k
            return Status::OK();
786
118k
        }
787
12
        DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
788
48
        for (size_t idx = 0; idx < _data_reader.column_mapper->mappings().size(); ++idx) {
789
36
            const auto& mapping = _data_reader.column_mapper->mappings()[idx];
790
36
            if (!_should_truncate_char_or_varchar_column(mapping)) {
791
12
                continue;
792
12
            }
793
24
            const auto target_len =
794
24
                    assert_cast<const DataTypeString*>(remove_nullable(mapping.table_type).get())
795
24
                            ->len();
796
24
            _truncate_char_or_varchar_column(block, idx, target_len);
797
24
        }
798
12
        return Status::OK();
799
118k
    }
800
801
    // Return true when the table schema has a bounded CHAR/VARCHAR length that is stricter than
802
    // the file-side type. Examples:
803
    // - table VARCHAR(10), file VARCHAR(20): truncate to 10;
804
    // - table VARCHAR(10), file STRING: truncate to 10 because STRING has no declared bound;
805
    // - table STRING, any file type: no truncation because the target has no bound.
806
41
    static bool _should_truncate_char_or_varchar_column(const ColumnMapping& mapping) {
807
41
        if (mapping.table_type == nullptr) {
808
0
            return false;
809
0
        }
810
41
        const auto table_type = remove_nullable(mapping.table_type);
811
41
        const auto primitive_type = table_type->get_primitive_type();
812
41
        if (primitive_type != TYPE_VARCHAR && primitive_type != TYPE_CHAR) {
813
13
            return false;
814
13
        }
815
28
        const auto target_len = assert_cast<const DataTypeString*>(table_type.get())->len();
816
28
        if (target_len <= 0) {
817
0
            return false;
818
0
        }
819
28
        if (mapping.file_type == nullptr) {
820
0
            return true;
821
0
        }
822
28
        const auto file_type = remove_nullable(mapping.file_type);
823
28
        DORIS_CHECK(file_type != nullptr);
824
28
        int file_len = -1;
825
28
        if (file_type->get_primitive_type() == TYPE_VARCHAR ||
826
28
            file_type->get_primitive_type() == TYPE_CHAR ||
827
28
            file_type->get_primitive_type() == TYPE_STRING) {
828
27
            file_len = assert_cast<const DataTypeString*>(file_type.get())->len();
829
27
        }
830
831
28
        return file_len < 0 || target_len < file_len;
832
28
    }
833
834
    // Truncate a materialized CHAR/VARCHAR column in place by reusing the vectorized substring
835
    // implementation: substring(column, 1, len). Nullable columns are unwrapped before substring
836
    // execution and wrapped back with the original null map afterward, because substring operates
837
    // on the nested string payload only.
838
25
    static void _truncate_char_or_varchar_column(Block* block, size_t idx, int len) {
839
25
        DORIS_CHECK(block != nullptr);
840
25
        auto int_type = std::make_shared<DataTypeInt32>();
841
25
        const auto num_columns_without_result = cast_set<uint32_t>(block->columns());
842
25
        auto& target = block->get_by_position(idx);
843
25
        const bool is_nullable = target.type->is_nullable();
844
25
        ColumnPtr input_column = target.column;
845
25
        ColumnPtr null_map_column;
846
25
        if (is_nullable) {
847
25
            const auto* nullable_column = assert_cast<const ColumnNullable*>(target.column.get());
848
25
            input_column = nullable_column->get_nested_column_ptr();
849
25
            null_map_column = nullable_column->get_null_map_column_ptr();
850
25
        }
851
25
        block->replace_by_position(idx, std::move(input_column));
852
25
        block->insert({int_type->create_column_const(block->rows(), to_field<TYPE_INT>(1)),
853
25
                       int_type, "const 1"});
854
25
        block->insert({int_type->create_column_const(block->rows(), to_field<TYPE_INT>(len)),
855
25
                       int_type, "const len"});
856
25
        block->insert({nullptr, std::make_shared<DataTypeString>(), "result"});
857
858
25
        ColumnNumbers temp_arguments(3);
859
25
        temp_arguments[0] = cast_set<uint32_t>(idx);
860
25
        temp_arguments[1] = num_columns_without_result;
861
25
        temp_arguments[2] = num_columns_without_result + 1;
862
25
        const uint32_t result_column_id = num_columns_without_result + 2;
863
25
        SubstringUtil::substring_execute(*block, temp_arguments, result_column_id, block->rows());
864
865
25
        ColumnPtr result_column = block->get_by_position(result_column_id).column;
866
25
        if (is_nullable) {
867
25
            result_column = ColumnNullable::create(std::move(result_column), null_map_column);
868
25
        }
869
25
        block->replace_by_position(idx, std::move(result_column));
870
25
        block->erase_tail(num_columns_without_result);
871
25
    }
872
873
116k
    Status _try_materialize_aggregate_pushdown_rows(Block* block, bool* pushed_down) {
874
116k
        DORIS_CHECK(block != nullptr);
875
116k
        DORIS_CHECK(pushed_down != nullptr);
876
116k
        *pushed_down = false;
877
116k
        block->clear_column_data(_projected_columns.size());
878
116k
        _aggregate_pushdown_tried = true;
879
116k
        if (!_supports_aggregate_pushdown(_push_down_agg_type)) {
880
115k
            return Status::OK();
881
115k
        }
882
883
1.46k
        FileAggregateRequest file_request;
884
1.46k
        RETURN_IF_ERROR(_build_file_aggregate_request(_push_down_agg_type, &file_request));
885
1.46k
        FileAggregateResult file_result;
886
1.46k
        const auto status = _data_reader.reader->get_aggregate_result(file_request, &file_result);
887
1.46k
        if (status.is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) {
888
89
            return Status::OK();
889
89
        }
890
1.37k
        RETURN_IF_ERROR(status);
891
1.37k
        RETURN_IF_ERROR(
892
1.37k
                _materialize_aggregate_pushdown_rows(_push_down_agg_type, file_result, block));
893
1.37k
        *pushed_down = true;
894
1.37k
        RETURN_IF_ERROR(close_current_reader());
895
1.37k
        return Status::OK();
896
1.37k
    }
897
898
118k
    virtual bool _supports_aggregate_pushdown(TPushAggOp::type agg_type) const {
899
        // Only COUNT and MIN/MAX can be push down.
900
118k
        if (agg_type != TPushAggOp::type::COUNT && agg_type != TPushAggOp::type::MINMAX) {
901
114k
            return false;
902
114k
        }
903
        // Only support aggregate pushdown when there is no delete or filter, so
904
        // the reduced rows consumed by the upper aggregate remain semantically equivalent to a
905
        // normal scan.
906
3.71k
        if ((_delete_rows != nullptr && !_delete_rows->empty()) ||
907
3.71k
            (_deletion_vector != nullptr && !_deletion_vector->isEmpty())) {
908
607
            return false;
909
607
        }
910
3.10k
        if (!_table_filters.empty()) {
911
3
            return false;
912
3
        }
913
3.10k
        if (agg_type == TPushAggOp::type::COUNT) {
914
2.61k
            return true;
915
2.61k
        }
916
        // For MIN/MAX, only support direct file-to-table column mappings. The two emitted rows
917
        // must be enough for the upper MIN/MAX aggregate without evaluating default expressions or
918
        // virtual columns.
919
2.74k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
920
2.74k
            if (!mapping.file_local_id.has_value() ||
921
2.74k
                mapping.virtual_column_type != TableVirtualColumnType::INVALID ||
922
2.74k
                mapping.default_expr != nullptr || mapping.file_type == nullptr ||
923
2.74k
                mapping.table_type == nullptr) {
924
9
                return false;
925
9
            }
926
2.73k
            if (!_can_push_down_minmax_for_mapping(mapping)) {
927
1
                return false;
928
1
            }
929
2.73k
        }
930
484
        return true;
931
494
    }
932
933
637k
    static ColumnPtr _detach_column(ColumnPtr column) {
934
637k
        DORIS_CHECK(column.get() != nullptr);
935
637k
        return IColumn::mutate(std::move(column));
936
637k
    }
937
938
105k
    static Status _align_column_nullability(ColumnPtr* column, const DataTypePtr& table_type) {
939
105k
        DORIS_CHECK(column != nullptr);
940
105k
        DORIS_CHECK(column->get() != nullptr);
941
105k
        DORIS_CHECK(table_type != nullptr);
942
        // Must return non-const column
943
105k
        *column = (*column)->convert_to_full_column_if_const();
944
105k
        if (table_type->is_nullable()) {
945
52.5k
            const auto& nested_type =
946
52.5k
                    assert_cast<const DataTypeNullable&>(*table_type).get_nested_type();
947
52.5k
            if (!(*column)->is_nullable()) {
948
2
                RETURN_IF_ERROR(_align_column_nullability(column, nested_type));
949
2
                *column = make_nullable(*column);
950
2
                return Status::OK();
951
2
            }
952
52.5k
            const auto& nullable_column = assert_cast<const ColumnNullable&>(**column);
953
52.5k
            ColumnPtr nested_column = nullable_column.get_nested_column_ptr();
954
52.5k
            RETURN_IF_ERROR(_align_column_nullability(&nested_column, nested_type));
955
52.5k
            *column = ColumnNullable::create(nested_column,
956
52.5k
                                             nullable_column.get_null_map_column_ptr());
957
52.5k
            return Status::OK();
958
52.5k
        }
959
52.5k
        if ((*column)->is_nullable()) {
960
0
            const auto& nullable_column = assert_cast<const ColumnNullable&>(**column);
961
0
            if (nullable_column.has_null()) {
962
0
                return Status::InternalError(
963
0
                        "Default expression produced NULL for non-nullable table column");
964
0
            }
965
0
            ColumnPtr nested_column = nullable_column.get_nested_column_ptr();
966
0
            RETURN_IF_ERROR(_align_column_nullability(&nested_column, table_type));
967
0
            *column = nested_column;
968
0
            return Status::OK();
969
0
        }
970
52.5k
        if (const auto* array_type = typeid_cast<const DataTypeArray*>(table_type.get())) {
971
145
            const auto& array_column = assert_cast<const ColumnArray&>(**column);
972
145
            ColumnPtr nested_column = array_column.get_data_ptr();
973
145
            RETURN_IF_ERROR(
974
145
                    _align_column_nullability(&nested_column, array_type->get_nested_type()));
975
145
            *column = ColumnArray::create(nested_column, array_column.get_offsets_ptr());
976
145
            return Status::OK();
977
145
        }
978
52.4k
        if (const auto* map_type = typeid_cast<const DataTypeMap*>(table_type.get())) {
979
120
            const auto& map_column = assert_cast<const ColumnMap&>(**column);
980
120
            ColumnPtr key_column = map_column.get_keys_ptr();
981
120
            ColumnPtr value_column = map_column.get_values_ptr();
982
120
            RETURN_IF_ERROR(_align_column_nullability(&key_column, map_type->get_key_type()));
983
120
            RETURN_IF_ERROR(_align_column_nullability(&value_column, map_type->get_value_type()));
984
120
            *column = ColumnMap::create(key_column, value_column, map_column.get_offsets_ptr());
985
120
            return Status::OK();
986
120
        }
987
52.3k
        if (const auto* struct_type = typeid_cast<const DataTypeStruct*>(table_type.get())) {
988
6.21k
            const auto& struct_column = assert_cast<const ColumnStruct&>(**column);
989
6.21k
            Columns columns = struct_column.get_columns_copy();
990
6.21k
            DORIS_CHECK(columns.size() == struct_type->get_elements().size());
991
20.9k
            for (size_t i = 0; i < columns.size(); ++i) {
992
14.7k
                RETURN_IF_ERROR(
993
14.7k
                        _align_column_nullability(&columns[i], struct_type->get_element(i)));
994
14.7k
            }
995
6.21k
            *column = ColumnStruct::create(columns);
996
6.21k
            return Status::OK();
997
6.21k
        }
998
46.0k
        return Status::OK();
999
52.3k
    }
1000
1001
    static Status _execute_default_expr_without_root_type_check(
1002
            const VExprContextSPtr& default_expr, const Block* block,
1003
18.2k
            ColumnWithTypeAndName* result_data) {
1004
18.2k
        DORIS_CHECK(default_expr != nullptr);
1005
18.2k
        DORIS_CHECK(block != nullptr);
1006
18.2k
        DORIS_CHECK(result_data != nullptr);
1007
18.2k
        ColumnPtr result_column;
1008
18.2k
        Status st;
1009
18.2k
        RETURN_IF_CATCH_EXCEPTION({
1010
18.2k
            st = default_expr->root()->execute_column_impl(default_expr.get(), block, nullptr,
1011
18.2k
                                                           block->rows(), result_column);
1012
18.2k
        });
1013
18.2k
        RETURN_IF_ERROR(st);
1014
18.2k
        DORIS_CHECK(result_column.get() != nullptr);
1015
18.2k
        if (result_column->size() != block->rows()) {
1016
0
            return Status::InternalError(
1017
0
                    "Default expr {} return column size {} not equal to expected size {}",
1018
0
                    default_expr->expr_name(), result_column->size(), block->rows());
1019
0
        }
1020
18.2k
        result_data->column = result_column;
1021
18.2k
        result_data->type = default_expr->execute_type(block);
1022
18.2k
        result_data->name = default_expr->expr_name();
1023
18.2k
        return Status::OK();
1024
18.2k
    }
1025
1026
    Status _cast_column_to_type(ColumnPtr* column, const DataTypePtr& file_type,
1027
                                const DataTypePtr& table_type,
1028
9.41k
                                const std::string& column_name) const {
1029
9.41k
        DORIS_CHECK(column != nullptr);
1030
9.41k
        DORIS_CHECK(column->get() != nullptr);
1031
9.41k
        DORIS_CHECK(file_type != nullptr);
1032
9.41k
        DORIS_CHECK(table_type != nullptr);
1033
9.41k
        if (file_type->equals(*table_type)) {
1034
0
            return Status::OK();
1035
0
        }
1036
1037
9.41k
        DataTypePtr input_type = file_type;
1038
        // Cast wrappers unwrap nullable inputs according to the declared input type, so keep the
1039
        // root nullability of the declared type aligned with the actual column shape.
1040
9.41k
        if ((*column)->is_nullable() && !input_type->is_nullable()) {
1041
0
            input_type = make_nullable(input_type);
1042
9.41k
        } else if (!(*column)->is_nullable() && input_type->is_nullable()) {
1043
1
            input_type = remove_nullable(input_type);
1044
1
        }
1045
9.41k
        Block cast_block;
1046
9.41k
        cast_block.insert({*column, input_type, column_name});
1047
9.41k
        auto slot_ref = VSlotRef::create_shared(0, 0, -1, input_type, column_name);
1048
9.41k
        auto cast_expr = Cast::create_shared(table_type);
1049
9.41k
        cast_expr->add_child(std::move(slot_ref));
1050
9.41k
        auto cast_ctx = VExprContext::create_shared(std::move(cast_expr));
1051
9.41k
        RowDescriptor row_desc;
1052
9.41k
        RETURN_IF_ERROR(cast_ctx->prepare(_runtime_state, row_desc));
1053
9.41k
        RETURN_IF_ERROR(cast_ctx->open(_runtime_state));
1054
9.41k
        ColumnPtr cast_column;
1055
9.41k
        RETURN_IF_ERROR(cast_ctx->execute(&cast_block, cast_column));
1056
9.41k
        *column = std::move(cast_column);
1057
9.41k
        return Status::OK();
1058
9.41k
    }
1059
1060
    Status _materialize_present_child_mapping_column(const ColumnMapping& mapping,
1061
                                                     const ColumnPtr& file_column,
1062
19.2k
                                                     const size_t rows, ColumnPtr* column) {
1063
19.2k
        DORIS_CHECK(column != nullptr);
1064
19.2k
        DORIS_CHECK(mapping.file_type != nullptr);
1065
19.2k
        DORIS_CHECK(mapping.table_type != nullptr);
1066
19.2k
        *column = file_column;
1067
19.2k
        if (!mapping.is_trivial) {
1068
11.5k
            if (!mapping.child_mappings.empty()) {
1069
2.13k
                RETURN_IF_ERROR(
1070
2.13k
                        _materialize_complex_mapping_column(mapping, *column, rows, column));
1071
9.41k
            } else {
1072
9.41k
                RETURN_IF_ERROR(_cast_column_to_type(column, mapping.file_type, mapping.table_type,
1073
9.41k
                                                     mapping.file_column_name));
1074
9.41k
            }
1075
11.5k
        }
1076
19.2k
        RETURN_IF_ERROR(_align_column_nullability(column, mapping.table_type));
1077
19.2k
        return Status::OK();
1078
19.2k
    }
1079
1080
    Status _materialize_mapping_column(const ColumnMapping& mapping, Block* current_block,
1081
648k
                                       const size_t rows, ColumnPtr* column) {
1082
648k
        if (!mapping.is_trivial && mapping.file_local_id.has_value() &&
1083
648k
            !mapping.child_mappings.empty()) {
1084
10.5k
            DCHECK(mapping.projection != nullptr);
1085
10.5k
            int res_id;
1086
10.5k
            auto st = mapping.projection->execute(current_block, &res_id);
1087
10.5k
            if (!st.ok()) {
1088
0
                return Status::InternalError(
1089
0
                        "Failed to execute complex mapping projection for table column '{}' "
1090
0
                        "(global_index={}, file_local_id={}, rows={}): {}, mapping={}",
1091
0
                        mapping.table_column_name, mapping.global_index.value(),
1092
0
                        *mapping.file_local_id, rows, st.to_string(), mapping.debug_string());
1093
0
            }
1094
10.5k
            ColumnPtr result_column = current_block->get_by_position(res_id).column;
1095
10.5k
            RETURN_IF_ERROR(
1096
10.5k
                    _materialize_complex_mapping_column(mapping, result_column, rows, column));
1097
10.5k
            return Status::OK();
1098
10.5k
        }
1099
637k
        if (mapping.projection != nullptr) {
1100
618k
            int res_id;
1101
618k
            auto st = mapping.projection->execute(current_block, &res_id);
1102
618k
            if (!st.ok()) {
1103
0
                std::string file_local_id = "null";
1104
0
                if (mapping.file_local_id.has_value()) {
1105
0
                    file_local_id = std::to_string(*mapping.file_local_id);
1106
0
                }
1107
0
                return Status::InternalError(
1108
0
                        "Failed to execute mapping projection for table column '{}' "
1109
0
                        "(global_index={}, file_local_id={}, rows={}): {}, mapping={}",
1110
0
                        mapping.table_column_name, mapping.global_index.value(), file_local_id,
1111
0
                        rows, st.to_string(), mapping.debug_string());
1112
0
            }
1113
618k
            ColumnPtr result_column = current_block->get_by_position(res_id).column;
1114
618k
            *column = _detach_column(std::move(result_column));
1115
618k
            return Status::OK();
1116
618k
        }
1117
18.8k
        if (mapping.default_expr != nullptr) {
1118
18.1k
            if (current_block->rows() == rows) {
1119
15.1k
                ColumnWithTypeAndName result;
1120
15.1k
                RETURN_IF_ERROR(_execute_default_expr_without_root_type_check(
1121
15.1k
                        mapping.default_expr, current_block, &result));
1122
15.1k
                ColumnPtr result_column = result.column;
1123
15.1k
                RETURN_IF_ERROR(_align_column_nullability(&result_column, mapping.table_type));
1124
15.1k
                *column = _detach_column(std::move(result_column));
1125
15.1k
            } else {
1126
3.01k
                DORIS_CHECK(mapping.constant_index.has_value());
1127
3.01k
                Block eval_block;
1128
3.01k
                eval_block.insert({mapping.table_type->create_column_const_with_default_value(rows),
1129
3.01k
                                   mapping.table_type, "__table_reader_const_rows"});
1130
3.01k
                ColumnWithTypeAndName result;
1131
3.01k
                RETURN_IF_ERROR(_execute_default_expr_without_root_type_check(
1132
3.01k
                        mapping.default_expr, &eval_block, &result));
1133
3.01k
                ColumnPtr result_column = result.column;
1134
3.01k
                RETURN_IF_ERROR(_align_column_nullability(&result_column, mapping.table_type));
1135
3.01k
                *column = _detach_column(std::move(result_column));
1136
3.01k
            }
1137
18.1k
            return Status::OK();
1138
18.1k
        }
1139
699
        ColumnPtr result_column = mapping.table_type->create_column_const_with_default_value(rows);
1140
699
        *column = _detach_column(std::move(result_column));
1141
699
        return Status::OK();
1142
18.8k
    }
1143
1144
    Status _materialize_complex_mapping_column(const ColumnMapping& mapping,
1145
                                               const ColumnPtr& file_column, const size_t rows,
1146
12.6k
                                               ColumnPtr* column) {
1147
12.6k
        DORIS_CHECK(mapping.table_type != nullptr);
1148
12.6k
        DORIS_CHECK(file_column.get() != nullptr);
1149
12.6k
        const auto table_type = remove_nullable(mapping.table_type);
1150
12.6k
        switch (table_type->get_primitive_type()) {
1151
3.94k
        case TYPE_STRUCT:
1152
3.94k
            RETURN_IF_ERROR(_materialize_struct_mapping_column(mapping, file_column, rows, column));
1153
3.94k
            break;
1154
4.58k
        case TYPE_ARRAY:
1155
4.58k
            RETURN_IF_ERROR(_materialize_array_mapping_column(mapping, file_column, rows, column));
1156
4.58k
            break;
1157
4.58k
        case TYPE_MAP:
1158
4.16k
            RETURN_IF_ERROR(_materialize_map_mapping_column(mapping, file_column, rows, column));
1159
4.16k
            break;
1160
4.16k
        default:
1161
0
            *column = _detach_column(file_column);
1162
0
            break;
1163
12.6k
        }
1164
12.6k
        return Status::OK();
1165
12.6k
    }
1166
1167
    static std::vector<const ColumnMapping*> _present_child_mappings_in_file_order(
1168
3.94k
            const std::vector<ColumnMapping>& child_mappings) {
1169
3.94k
        std::vector<const ColumnMapping*> result;
1170
3.94k
        result.reserve(child_mappings.size());
1171
10.1k
        for (const auto& child_mapping : child_mappings) {
1172
10.1k
            if (child_mapping.file_local_id.has_value()) {
1173
6.28k
                result.push_back(&child_mapping);
1174
6.28k
            }
1175
10.1k
        }
1176
5.10k
        std::ranges::sort(result, [](const ColumnMapping* lhs, const ColumnMapping* rhs) {
1177
5.10k
            DORIS_CHECK(lhs->file_local_id.has_value());
1178
5.10k
            DORIS_CHECK(rhs->file_local_id.has_value());
1179
5.10k
            return *lhs->file_local_id < *rhs->file_local_id;
1180
5.10k
        });
1181
3.94k
        return result;
1182
3.94k
    }
1183
1184
    static size_t _file_child_ordinal_for_mapping(
1185
            const ColumnMapping& mapping, const ColumnMapping& child_mapping,
1186
6.28k
            const std::vector<const ColumnMapping*>& file_ordered_children) {
1187
6.28k
        DORIS_CHECK(child_mapping.file_local_id.has_value());
1188
6.28k
        if (!mapping.projected_file_children.empty()) {
1189
6.28k
            const auto child_it = std::ranges::find_if(
1190
11.4k
                    mapping.projected_file_children, [&](const ColumnDefinition& file_child) {
1191
11.4k
                        return file_child.file_local_id() == *child_mapping.file_local_id;
1192
11.4k
                    });
1193
6.28k
            DORIS_CHECK(child_it != mapping.projected_file_children.end());
1194
6.28k
            return static_cast<size_t>(
1195
6.28k
                    std::distance(mapping.projected_file_children.begin(), child_it));
1196
6.28k
        }
1197
4
        const auto child_it = std::ranges::find(file_ordered_children, &child_mapping);
1198
4
        DORIS_CHECK(child_it != file_ordered_children.end());
1199
4
        return static_cast<size_t>(std::distance(file_ordered_children.begin(), child_it));
1200
6.28k
    }
1201
1202
    static std::vector<const ColumnMapping*> _child_mappings_in_table_type_order(
1203
3.94k
            const ColumnMapping& mapping, const DataTypeStruct& table_type) {
1204
3.94k
        std::vector<const ColumnMapping*> result;
1205
3.94k
        result.reserve(mapping.child_mappings.size());
1206
14.1k
        for (size_t child_idx = 0; child_idx < table_type.get_elements().size(); ++child_idx) {
1207
10.1k
            const auto& child_name = table_type.get_element_name(child_idx);
1208
10.1k
            const auto child_it = std::ranges::find_if(
1209
21.2k
                    mapping.child_mappings, [&](const ColumnMapping& child_mapping) {
1210
21.2k
                        return child_mapping.table_column_name == child_name;
1211
21.2k
                    });
1212
10.1k
            DORIS_CHECK(child_it != mapping.child_mappings.end())
1213
0
                    << mapping.debug_string() << ", table_child_name=" << child_name;
1214
10.1k
            result.push_back(&*child_it);
1215
10.1k
        }
1216
3.94k
        return result;
1217
3.94k
    }
1218
1219
    static const IColumn* _nested_column_if_nullable(const ColumnPtr& column,
1220
12.6k
                                                     const NullMap** null_map) {
1221
12.6k
        DORIS_CHECK(column.get() != nullptr);
1222
12.6k
        if (const auto* nullable_column = check_and_get_column<ColumnNullable>(*column)) {
1223
12.6k
            if (null_map != nullptr) {
1224
12.6k
                *null_map = &nullable_column->get_null_map_data();
1225
12.6k
            }
1226
12.6k
            return &nullable_column->get_nested_column();
1227
12.6k
        }
1228
4
        return column.get();
1229
12.6k
    }
1230
1231
    Status _materialize_struct_mapping_column(const ColumnMapping& mapping,
1232
                                              const ColumnPtr& file_column, const size_t rows,
1233
3.94k
                                              ColumnPtr* column) {
1234
3.94k
        DORIS_CHECK(mapping.table_type != nullptr);
1235
3.94k
        const auto* table_type =
1236
3.94k
                assert_cast<const DataTypeStruct*>(remove_nullable(mapping.table_type).get());
1237
3.94k
        const auto full_file_column = file_column->convert_to_full_column_if_const();
1238
3.94k
        const NullMap* parent_null_map = nullptr;
1239
3.94k
        const auto* nested_file_column =
1240
3.94k
                _nested_column_if_nullable(full_file_column, &parent_null_map);
1241
3.94k
        const auto* file_struct = assert_cast<const ColumnStruct*>(nested_file_column);
1242
3.94k
        DORIS_CHECK(table_type->get_elements().size() == mapping.child_mappings.size());
1243
1244
3.94k
        Columns child_columns;
1245
3.94k
        child_columns.reserve(mapping.child_mappings.size());
1246
3.94k
        const auto file_ordered_children =
1247
3.94k
                _present_child_mappings_in_file_order(mapping.child_mappings);
1248
3.94k
        const auto table_ordered_children =
1249
3.94k
                _child_mappings_in_table_type_order(mapping, *table_type);
1250
10.1k
        for (const auto* child_mapping : table_ordered_children) {
1251
10.1k
            DORIS_CHECK(child_mapping != nullptr);
1252
10.1k
            if (!child_mapping->file_local_id.has_value()) {
1253
3.91k
                child_columns.push_back(
1254
3.91k
                        child_mapping->table_type->create_column_const_with_default_value(rows)
1255
3.91k
                                ->convert_to_full_column_if_const());
1256
3.91k
                continue;
1257
3.91k
            }
1258
6.28k
            const auto file_child_idx =
1259
6.28k
                    _file_child_ordinal_for_mapping(mapping, *child_mapping, file_ordered_children);
1260
6.28k
            DORIS_CHECK(file_child_idx < file_struct->get_columns().size());
1261
6.28k
            ColumnPtr child_column = file_struct->get_column_ptr(file_child_idx);
1262
6.28k
            RETURN_IF_ERROR(_materialize_present_child_mapping_column(*child_mapping, child_column,
1263
6.28k
                                                                      rows, &child_column));
1264
6.28k
            child_columns.push_back(std::move(child_column));
1265
6.28k
        }
1266
3.94k
        MutableColumns mutable_child_columns;
1267
3.94k
        mutable_child_columns.reserve(child_columns.size());
1268
10.1k
        for (auto& child_column : child_columns) {
1269
10.1k
            mutable_child_columns.push_back(IColumn::mutate(std::move(child_column)));
1270
10.1k
        }
1271
3.94k
        auto result = ColumnStruct::create(std::move(mutable_child_columns));
1272
3.94k
        if (mapping.table_type->is_nullable()) {
1273
3.94k
            auto null_map = ColumnUInt8::create();
1274
3.94k
            auto& null_map_data = null_map->get_data();
1275
3.94k
            null_map_data.resize(rows);
1276
3.94k
            if (parent_null_map != nullptr) {
1277
3.94k
                DORIS_CHECK(parent_null_map->size() == rows);
1278
3.94k
                null_map_data.assign(parent_null_map->begin(), parent_null_map->end());
1279
3.94k
            } else {
1280
0
                std::fill(null_map_data.begin(), null_map_data.end(), 0);
1281
0
            }
1282
3.94k
            *column = ColumnNullable::create(std::move(result), std::move(null_map));
1283
3.94k
        } else {
1284
2
            *column = std::move(result);
1285
2
        }
1286
3.94k
        return Status::OK();
1287
3.94k
    }
1288
1289
    Status _materialize_array_mapping_column(const ColumnMapping& mapping,
1290
                                             const ColumnPtr& file_column, const size_t rows,
1291
4.58k
                                             ColumnPtr* column) {
1292
4.58k
        DORIS_CHECK(mapping.child_mappings.size() == 1);
1293
4.58k
        const auto full_file_column = file_column->convert_to_full_column_if_const();
1294
4.58k
        const NullMap* parent_null_map = nullptr;
1295
4.58k
        const auto* nested_file_column =
1296
4.58k
                _nested_column_if_nullable(full_file_column, &parent_null_map);
1297
4.58k
        const auto* file_array = assert_cast<const ColumnArray*>(nested_file_column);
1298
4.58k
        ColumnPtr nested_column = file_array->get_data_ptr();
1299
4.58k
        const auto& element_mapping = mapping.child_mappings[0];
1300
4.58k
        RETURN_IF_ERROR(_materialize_present_child_mapping_column(
1301
4.58k
                element_mapping, nested_column, nested_column->size(), &nested_column));
1302
4.58k
        auto offsets_column = file_array->get_offsets_ptr()->convert_to_full_column_if_const();
1303
4.58k
        auto result = ColumnArray::create(IColumn::mutate(std::move(nested_column)),
1304
4.58k
                                          IColumn::mutate(std::move(offsets_column)));
1305
4.58k
        if (mapping.table_type->is_nullable()) {
1306
4.58k
            auto null_map = ColumnUInt8::create();
1307
4.58k
            auto& null_map_data = null_map->get_data();
1308
4.58k
            null_map_data.resize(rows);
1309
4.58k
            if (parent_null_map != nullptr) {
1310
4.58k
                DORIS_CHECK(parent_null_map->size() == rows);
1311
4.58k
                null_map_data.assign(parent_null_map->begin(), parent_null_map->end());
1312
4.58k
            } else {
1313
0
                std::fill(null_map_data.begin(), null_map_data.end(), 0);
1314
0
            }
1315
4.58k
            *column = ColumnNullable::create(std::move(result), std::move(null_map));
1316
4.58k
        } else {
1317
0
            *column = std::move(result);
1318
0
        }
1319
4.58k
        return Status::OK();
1320
4.58k
    }
1321
1322
    Status _materialize_map_mapping_column(const ColumnMapping& mapping,
1323
                                           const ColumnPtr& file_column, const size_t rows,
1324
4.16k
                                           ColumnPtr* column) {
1325
4.16k
        const auto full_file_column = file_column->convert_to_full_column_if_const();
1326
4.16k
        const NullMap* parent_null_map = nullptr;
1327
4.16k
        const auto* nested_file_column =
1328
4.16k
                _nested_column_if_nullable(full_file_column, &parent_null_map);
1329
4.16k
        const auto* file_map = assert_cast<const ColumnMap*>(nested_file_column);
1330
4.16k
        ColumnPtr key_column = file_map->get_keys_ptr();
1331
4.16k
        ColumnPtr value_column = file_map->get_values_ptr();
1332
1333
4.16k
        const ColumnMapping* key_mapping = nullptr;
1334
4.16k
        const ColumnMapping* value_mapping = nullptr;
1335
8.33k
        for (const auto& child_mapping : mapping.child_mappings) {
1336
8.33k
            if (!child_mapping.file_local_id.has_value()) {
1337
0
                continue;
1338
0
            }
1339
8.33k
            if (*child_mapping.file_local_id == 0) {
1340
4.16k
                key_mapping = &child_mapping;
1341
4.16k
            } else if (*child_mapping.file_local_id == 1) {
1342
4.16k
                value_mapping = &child_mapping;
1343
4.16k
            }
1344
8.33k
        }
1345
1346
4.16k
        if (key_mapping != nullptr) {
1347
4.16k
            RETURN_IF_ERROR(_materialize_present_child_mapping_column(
1348
4.16k
                    *key_mapping, key_column, key_column->size(), &key_column));
1349
4.16k
        }
1350
4.16k
        if (value_mapping != nullptr) {
1351
4.16k
            RETURN_IF_ERROR(_materialize_present_child_mapping_column(
1352
4.16k
                    *value_mapping, value_column, value_column->size(), &value_column));
1353
4.16k
        }
1354
4.16k
        auto offsets_column = file_map->get_offsets_ptr()->convert_to_full_column_if_const();
1355
4.16k
        auto result = ColumnMap::create(IColumn::mutate(std::move(key_column)),
1356
4.16k
                                        IColumn::mutate(std::move(value_column)),
1357
4.16k
                                        IColumn::mutate(std::move(offsets_column)));
1358
4.16k
        if (mapping.table_type->is_nullable()) {
1359
4.16k
            auto null_map = ColumnUInt8::create();
1360
4.16k
            auto& null_map_data = null_map->get_data();
1361
4.16k
            null_map_data.resize(rows);
1362
4.16k
            if (parent_null_map != nullptr) {
1363
4.16k
                DORIS_CHECK(parent_null_map->size() == rows);
1364
4.16k
                null_map_data.assign(parent_null_map->begin(), parent_null_map->end());
1365
4.16k
            } else {
1366
0
                std::fill(null_map_data.begin(), null_map_data.end(), 0);
1367
0
            }
1368
4.16k
            *column = ColumnNullable::create(std::move(result), std::move(null_map));
1369
4.16k
        } else {
1370
0
            *column = std::move(result);
1371
0
        }
1372
4.16k
        return Status::OK();
1373
4.16k
    }
1374
1375
116k
    Status _open_mapping_exprs() {
1376
116k
        RowDescriptor row_desc;
1377
511k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
1378
511k
            if (mapping.projection != nullptr) {
1379
492k
                RETURN_IF_ERROR(mapping.projection->prepare(_runtime_state, row_desc));
1380
492k
                RETURN_IF_ERROR(mapping.projection->open(_runtime_state));
1381
492k
            }
1382
511k
            if (mapping.default_expr != nullptr) {
1383
18.8k
                RETURN_IF_ERROR(mapping.default_expr->prepare(_runtime_state, row_desc));
1384
18.8k
                RETURN_IF_ERROR(mapping.default_expr->open(_runtime_state));
1385
18.8k
            }
1386
511k
        }
1387
116k
        return Status::OK();
1388
116k
    }
1389
1390
    Status _build_file_aggregate_request(TPushAggOp::type agg_type,
1391
1.48k
                                         FileAggregateRequest* request) const {
1392
1.48k
        DORIS_CHECK(request != nullptr);
1393
1.48k
        DORIS_CHECK(_supports_aggregate_pushdown(agg_type));
1394
1.48k
        request->agg_type = agg_type;
1395
1.48k
        request->columns.clear();
1396
1.48k
        if (agg_type == TPushAggOp::type::COUNT) {
1397
            // COUNT pushdown historically meant COUNT(*) and therefore carried no columns. For
1398
            // complex COUNT(col), materializing the full MAP/LIST/STRUCT value only to test the
1399
            // top-level NULL bit can be extremely expensive. When the scan projects exactly one
1400
            // directly-mapped complex column, pass that file column to the reader so formats such
1401
            // as Parquet can count the column shape from metadata/levels without decoding payload
1402
            // values like MAP value strings. Other COUNT cases stay on the existing row-count path
1403
            // to avoid changing count(*) semantics.
1404
1.23k
            if (_data_reader.column_mapper->mappings().size() == 1) {
1405
1.23k
                const auto& mapping = _data_reader.column_mapper->mappings()[0];
1406
1.23k
                if (mapping.file_local_id.has_value() && mapping.file_type != nullptr &&
1407
1.23k
                    is_complex_type(remove_nullable(mapping.file_type)->get_primitive_type()) &&
1408
1.23k
                    mapping.virtual_column_type == TableVirtualColumnType::INVALID &&
1409
1.23k
                    mapping.default_expr == nullptr) {
1410
0
                    FileAggregateRequest::Column column;
1411
0
                    column.projection =
1412
0
                            LocalColumnIndex::top_level(LocalColumnId(*mapping.file_local_id));
1413
0
                    request->columns.push_back(std::move(column));
1414
0
                }
1415
1.23k
            }
1416
1.23k
            return Status::OK();
1417
1.23k
        }
1418
247
        request->columns.reserve(_data_reader.column_mapper->mappings().size());
1419
1.36k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
1420
1.36k
            DORIS_CHECK(mapping.file_local_id.has_value());
1421
1.36k
            FileAggregateRequest::Column column;
1422
1.36k
            column.projection = LocalColumnIndex::top_level(LocalColumnId(*mapping.file_local_id));
1423
1.36k
            if (!mapping.child_mappings.empty()) {
1424
1
                RETURN_IF_ERROR(build_aggregate_projection(mapping, &column.projection));
1425
1
            }
1426
1.36k
            request->columns.push_back(std::move(column));
1427
1.36k
        }
1428
247
        return Status::OK();
1429
247
    }
1430
1431
    Status _materialize_aggregate_pushdown_rows(TPushAggOp::type agg_type,
1432
                                                const FileAggregateResult& file_result,
1433
1.39k
                                                Block* block) {
1434
1.39k
        if (agg_type == TPushAggOp::type::COUNT) {
1435
            // COUNT pushdown is not a final count value. It emits `count` default rows so the
1436
            // upper COUNT(*) aggregate can count them and produce the final result, including
1437
            // zero rows when count is 0.
1438
1.23k
            DORIS_CHECK(file_result.count >= 0);
1439
1.23k
            return _materialize_count_rows(cast_set<size_t>(file_result.count), block);
1440
1.23k
        }
1441
        // MIN/MAX pushdown emits two rows, min first and max second, for each projected column.
1442
        // The upper MIN/MAX aggregate consumes those two rows to produce the final aggregate value.
1443
158
        DORIS_CHECK(file_result.columns.size() == _data_reader.column_mapper->mappings().size());
1444
158
        DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
1445
158
        Block file_block;
1446
158
        file_block.reserve(_data_reader.file_block_layout.size());
1447
839
        for (const auto& column : _data_reader.file_block_layout) {
1448
839
            file_block.insert({column.type->create_column(), column.type, column.name});
1449
839
        }
1450
997
        for (size_t column_idx = 0; column_idx < file_result.columns.size(); ++column_idx) {
1451
839
            const auto& result_column = file_result.columns[column_idx];
1452
839
            if (!result_column.has_min || !result_column.has_max) {
1453
0
                return Status::NotSupported("Missing min/max aggregate result for column {}",
1454
0
                                            _projected_columns[column_idx].name);
1455
0
            }
1456
839
            bool found_file_column = false;
1457
2.86k
            for (size_t block_position = 0; block_position < _data_reader.file_block_layout.size();
1458
2.86k
                 ++block_position) {
1459
2.86k
                if (_data_reader.file_block_layout[block_position].file_column_id ==
1460
2.86k
                    file_result.columns[column_idx].projection.column_id()) {
1461
839
                    found_file_column = true;
1462
839
                    auto column = file_block.get_by_position(block_position)
1463
839
                                          .type->create_column()
1464
839
                                          ->assert_mutable();
1465
839
                    RETURN_IF_ERROR(_insert_aggregate_projection_value(
1466
839
                            file_result.columns[column_idx].projection, result_column.min_value,
1467
839
                            column.get()));
1468
839
                    RETURN_IF_ERROR(_insert_aggregate_projection_value(
1469
839
                            file_result.columns[column_idx].projection, result_column.max_value,
1470
839
                            column.get()));
1471
839
                    file_block.replace_by_position(block_position, std::move(column));
1472
839
                    break;
1473
839
                }
1474
2.86k
            }
1475
839
            DORIS_CHECK(found_file_column);
1476
839
        }
1477
997
        for (size_t column_idx = 0; column_idx < _data_reader.column_mapper->mappings().size();
1478
839
             ++column_idx) {
1479
839
            ColumnPtr table_column;
1480
839
            RETURN_IF_ERROR(
1481
839
                    _materialize_mapping_column(_data_reader.column_mapper->mappings()[column_idx],
1482
839
                                                &file_block, 2, &table_column));
1483
839
            block->replace_by_position(column_idx, std::move(table_column));
1484
839
        }
1485
158
        return Status::OK();
1486
158
    }
1487
1488
    struct FileBlockColumn {
1489
        LocalColumnId file_column_id = LocalColumnId::invalid();
1490
        std::string name;
1491
        DataTypePtr type;
1492
    };
1493
1494
    struct DataReader {
1495
        std::unique_ptr<FileReader> reader;
1496
        std::unique_ptr<TableColumnMapper> column_mapper;
1497
        // Schema of the data file, also including virtual column (row position).
1498
        std::vector<ColumnDefinition> file_schema;
1499
        // Layout of the block returned by file reader, determined by column mapping and file
1500
        // schema. It is used for file reader to materialize columns into correct type and position.
1501
        std::vector<FileBlockColumn> file_block_layout;
1502
        Block block_template;
1503
    };
1504
    DataReader _data_reader;
1505
    std::vector<ColumnDefinition> _projected_columns;
1506
    std::unique_ptr<ScanTask> _current_task;
1507
    std::optional<io::FileDescription> _current_file_description;
1508
    // Range-level compression has higher priority than scan-param compression. TVF/load can keep
1509
    // the logical format as CSV/TEXT while carrying the concrete compression such as GZ or LZO on
1510
    // each TFileRangeDesc, matching the old FileScanner reader contract.
1511
    TFileCompressType::type _current_range_compress_type = TFileCompressType::UNKNOWN;
1512
    std::optional<TUniqueId> _current_range_load_id;
1513
    TFileRangeDesc _current_file_range_desc;
1514
    std::shared_ptr<io::FileSystemProperties> _system_properties;
1515
    // partition key -> value
1516
    std::map<std::string, Field> _partition_values;
1517
    // Predicates built from scan conjuncts before file-level localization.
1518
    std::vector<TableFilter> _table_filters;
1519
    VExprContextSPtrs _conjuncts;
1520
    ReadProfile _profile;
1521
    // Parsed from row-position based delete files, including position delete and deletion vector.
1522
    DeleteRows* _delete_rows = nullptr;
1523
    DeletionVector* _deletion_vector = nullptr;
1524
    TFileScanRangeParams* _scan_params;
1525
    std::shared_ptr<io::IOContext> _io_ctx;
1526
    RuntimeState* _runtime_state;
1527
    RuntimeProfile* _scanner_profile;
1528
    const std::vector<SlotDescriptor*>* _file_slot_descs = nullptr;
1529
    FileFormat _format;
1530
    TPushAggOp::type _push_down_agg_type = TPushAggOp::type::NONE;
1531
    size_t _batch_size = 0;
1532
    uint64_t _condition_cache_digest = 0;
1533
    segment_v2::ConditionCache::ExternalCacheKey _condition_cache_key;
1534
    std::shared_ptr<std::vector<bool>> _condition_cache;
1535
    std::shared_ptr<ConditionCacheContext> _condition_cache_ctx;
1536
    int64_t _condition_cache_hit_count = 0;
1537
    bool _current_reader_reached_eof = false;
1538
    int64_t _remaining_table_level_count = -1;
1539
    std::optional<GlobalRowIdContext> _global_rowid_context;
1540
    bool _aggregate_pushdown_tried = false;
1541
    bool _current_split_pruned = false;
1542
    TableColumnMapperOptions _mapper_options;
1543
1544
private:
1545
    static const ColumnDefinition* _find_column_definition(
1546
547k
            const std::vector<ColumnDefinition>& schema, LocalColumnId column_id) {
1547
10.9M
        for (const auto& field : schema) {
1548
10.9M
            if (field.file_local_id() == column_id.value()) {
1549
520k
                return &field;
1550
520k
            }
1551
10.9M
        }
1552
27.2k
        return nullptr;
1553
547k
    }
1554
1555
2.73k
    static bool _can_push_down_minmax_for_mapping(const ColumnMapping& mapping) {
1556
2.73k
        if (mapping.child_mappings.empty()) {
1557
2.73k
            return true;
1558
2.73k
        }
1559
3
        const auto primitive_type = remove_nullable(mapping.file_type)->get_primitive_type();
1560
3
        if (primitive_type != TYPE_STRUCT) {
1561
1
            return false;
1562
1
        }
1563
2
        size_t mapped_children = 0;
1564
2
        const ColumnMapping* mapped_child = nullptr;
1565
2
        for (const auto& child_mapping : mapping.child_mappings) {
1566
2
            if (!child_mapping.file_local_id.has_value()) {
1567
0
                continue;
1568
0
            }
1569
2
            ++mapped_children;
1570
2
            mapped_child = &child_mapping;
1571
2
        }
1572
2
        return mapped_children == 1 && mapped_child != nullptr &&
1573
2
               _can_push_down_minmax_for_mapping(*mapped_child);
1574
3
    }
1575
1576
    static Status build_aggregate_projection(const ColumnMapping& mapping,
1577
2
                                             LocalColumnIndex* projection) {
1578
2
        DORIS_CHECK(projection != nullptr);
1579
2
        DORIS_CHECK(mapping.file_local_id.has_value());
1580
2
        *projection = LocalColumnIndex::local(*mapping.file_local_id);
1581
2
        projection->children.clear();
1582
2
        projection->project_all_children = true;
1583
2
        if (mapping.child_mappings.empty()) {
1584
1
            return Status::OK();
1585
1
        }
1586
1
        projection->project_all_children = false;
1587
1
        for (const auto& child_mapping : mapping.child_mappings) {
1588
1
            if (!child_mapping.file_local_id.has_value()) {
1589
0
                continue;
1590
0
            }
1591
1
            LocalColumnIndex child_projection;
1592
1
            RETURN_IF_ERROR(build_aggregate_projection(child_mapping, &child_projection));
1593
1
            projection->children.push_back(std::move(child_projection));
1594
1
        }
1595
1
        DORIS_CHECK(projection->children.size() == 1);
1596
1
        return Status::OK();
1597
1
    }
1598
1599
    static Status _insert_aggregate_projection_value(const LocalColumnIndex& projection,
1600
3.36k
                                                     const Field& value, IColumn* column) {
1601
3.36k
        DORIS_CHECK(column != nullptr);
1602
3.36k
        if (auto* nullable_column = check_and_get_column<ColumnNullable>(*column)) {
1603
1.68k
            RETURN_IF_ERROR(_insert_aggregate_projection_value(
1604
1.68k
                    projection, value, &nullable_column->get_nested_column()));
1605
1.68k
            nullable_column->get_null_map_data().push_back(0);
1606
1.68k
            return Status::OK();
1607
1.68k
        }
1608
1.68k
        if (projection.project_all_children || projection.children.empty()) {
1609
1.67k
            column->insert(value);
1610
1.67k
            return Status::OK();
1611
1.67k
        }
1612
2
        auto* struct_column = assert_cast<ColumnStruct*>(column);
1613
2
        DORIS_CHECK(projection.children.size() == 1);
1614
2
        const auto& child_projection = projection.children[0];
1615
2
        DORIS_CHECK(struct_column->get_columns().size() == 1);
1616
2
        RETURN_IF_ERROR(_insert_aggregate_projection_value(child_projection, value,
1617
2
                                                           &struct_column->get_column(0)));
1618
2
        return Status::OK();
1619
2
    }
1620
1621
    // Parse a DV into its compressed bitmap. Position delete files continue to use _delete_rows.
1622
    Status _parse_delete_predicates(const SplitReadOptions& options);
1623
};
1624
1625
} // namespace doris::format