Coverage Report

Created: 2026-07-10 07:02

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