Coverage Report

Created: 2026-07-10 10:18

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
123k
    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
54.7k
    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
243k
    void set_batch_size(size_t batch_size) {
160
243k
        _batch_size = std::max<size_t>(1, batch_size);
161
243k
        if (_data_reader.reader != nullptr) {
162
112k
            _data_reader.reader->set_batch_size(_batch_size);
163
112k
        }
164
243k
    }
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
237k
    virtual Status get_block(Block* block, bool* eos) {
175
237k
        SCOPED_TIMER(_profile.exec_timer);
176
237k
        DORIS_CHECK(block->columns() == _projected_columns.size());
177
237k
        block->clear_column_data(_projected_columns.size());
178
179
363k
        while (true) {
180
363k
            if (*eos) {
181
0
                return Status::OK();
182
0
            }
183
363k
            if (_io_ctx != nullptr && _io_ctx->should_stop) {
184
6
                *eos = true;
185
6
                return Status::OK();
186
6
            }
187
363k
            if (!_data_reader.reader) {
188
234k
                if (_is_table_level_count_active()) {
189
298
                    RETURN_IF_ERROR(_read_table_level_count(block, eos));
190
298
                    return Status::OK();
191
298
                }
192
234k
                RETURN_IF_ERROR(create_next_reader(eos));
193
234k
                if (!_data_reader.reader) {
194
117k
                    DCHECK(*eos);
195
117k
                    return Status::OK();
196
117k
                }
197
234k
            }
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
245k
            if (!_aggregate_pushdown_tried) {
204
116k
                SCOPED_TIMER(_profile.pushdown_agg_timer);
205
116k
                bool pushed_down = false;
206
116k
                const auto status = _try_materialize_aggregate_pushdown_rows(block, &pushed_down);
207
116k
                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
116k
                if (pushed_down) {
216
1.36k
                    return Status::OK();
217
1.36k
                }
218
116k
            }
219
220
244k
            bool current_eof = false;
221
244k
            _data_reader.block_template.clear_column_data(
222
244k
                    cast_set<int64_t>(_data_reader.file_block_layout.size()));
223
244k
            size_t current_rows = 0;
224
244k
            RETURN_IF_ERROR(_data_reader.reader->get_block(&_data_reader.block_template,
225
244k
                                                           &current_rows, &current_eof));
226
244k
            if (current_rows == 0) {
227
126k
                if (current_eof) {
228
114k
                    _current_reader_reached_eof = true;
229
114k
                    RETURN_IF_ERROR(close_current_reader());
230
114k
                }
231
126k
                continue;
232
126k
            }
233
244k
            DCHECK_EQ(_data_reader.block_template.columns(), _data_reader.file_block_layout.size())
234
0
                    << _data_reader.block_template.dump_structure();
235
118k
#ifndef NDEBUG
236
118k
            RETURN_IF_ERROR(_check_file_block_columns("after file reader get_block", current_rows));
237
118k
#endif
238
118k
            DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
239
118k
            RETURN_IF_ERROR(finalize_chunk(block, current_rows));
240
118k
#ifndef NDEBUG
241
118k
            RETURN_IF_ERROR(
242
118k
                    _check_table_block_columns("after finalize_chunk", block, current_rows));
243
118k
#endif
244
118k
            if (current_eof) {
245
6
                _current_reader_reached_eof = true;
246
6
                RETURN_IF_ERROR(close_current_reader());
247
6
            }
248
118k
            return Status::OK();
249
118k
        }
250
237k
    }
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
48.6k
    virtual Status close() {
255
48.6k
        if (_data_reader.reader) {
256
634
            RETURN_IF_ERROR(close_current_reader());
257
634
        }
258
48.6k
        _current_task.reset();
259
48.6k
        _current_file_description.reset();
260
48.6k
        _remaining_table_level_count = -1;
261
48.6k
        return Status::OK();
262
48.6k
    }
263
264
97.0k
    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
28.6k
    virtual Status validate_projected_columns(const ProjectedColumnBuildContext& context) const {
273
28.6k
        (void)context;
274
28.6k
        return Status::OK();
275
28.6k
    }
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
39.6k
                                               DeleteFileDesc* desc, bool* has_delete_file) {
281
39.6k
        *has_delete_file = false;
282
39.6k
        return Status::OK();
283
39.6k
    }
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
6.66k
    virtual TableColumnMappingMode mapping_mode() const { return TableColumnMappingMode::BY_NAME; }
290
32.4k
    virtual Status annotate_file_schema(std::vector<ColumnDefinition>* file_schema) {
291
32.4k
        DORIS_CHECK(file_schema != nullptr);
292
32.4k
        return Status::OK();
293
32.4k
    }
294
295
    // Open the concrete reader for the current split/task and build the file-local scan request.
296
118k
    virtual Status open_reader() {
297
118k
        SCOPED_TIMER(_profile.open_reader_timer);
298
        // 1. Get file schema and create column mapping.
299
118k
        std::vector<ColumnDefinition> file_schema;
300
118k
        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
118k
        RETURN_IF_ERROR(annotate_file_schema(&file_schema));
304
118k
        _data_reader.file_schema = file_schema;
305
118k
        _mapper_options.mode = mapping_mode();
306
307
118k
        _data_reader.column_mapper = _data_reader.reader->create_column_mapper(_mapper_options);
308
118k
        DORIS_CHECK(_data_reader.column_mapper != nullptr);
309
118k
        RETURN_IF_ERROR(_data_reader.column_mapper->create_mapping(_projected_columns,
310
118k
                                                                   _partition_values, file_schema));
311
118k
        DORIS_CHECK(_data_reader.column_mapper->mappings().size() == _projected_columns.size());
312
313
        // 2. Build table filters based on conjuncts and column predicates.
314
118k
        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
118k
        auto file_request = std::make_shared<FileScanRequest>();
320
118k
        RETURN_IF_ERROR(_data_reader.column_mapper->create_scan_request(
321
118k
                _table_filters, _projected_columns, file_request.get(), _runtime_state));
322
118k
        bool constant_filter_pruned_split = false;
323
118k
        RETURN_IF_ERROR(_evaluate_constant_filters(&constant_filter_pruned_split));
324
118k
        if (constant_filter_pruned_split) {
325
1.44k
            RETURN_IF_ERROR(close_current_reader());
326
1.44k
            return Status::OK();
327
1.44k
        }
328
116k
        RETURN_IF_ERROR(customize_file_scan_request(file_request.get()));
329
116k
        RETURN_IF_ERROR(_open_local_filter_exprs(*file_request));
330
116k
        _data_reader.file_block_layout.clear();
331
116k
        _data_reader.block_template.clear();
332
116k
        _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
510k
        for (const auto& [file_column_id, block_position] : file_request->local_positions) {
337
510k
            DORIS_CHECK(block_position.value() < _data_reader.file_block_layout.size());
338
510k
            const auto* field = _find_column_definition(_data_reader.file_schema, file_column_id);
339
510k
            DORIS_CHECK(field != nullptr);
340
341
510k
            ColumnDefinition projected_field;
342
510k
            {
343
510k
                auto it = std::find_if(
344
510k
                        file_request->non_predicate_columns.begin(),
345
510k
                        file_request->non_predicate_columns.end(),
346
8.53M
                        [&](const LocalColumnIndex& p) { return p.column_id() == file_column_id; });
347
510k
                if (it != file_request->non_predicate_columns.end()) {
348
418k
                    RETURN_IF_ERROR(project_column_definition(*field, *it, &projected_field));
349
418k
                }
350
510k
            }
351
510k
            {
352
510k
                auto it = std::find_if(
353
510k
                        file_request->predicate_columns.begin(),
354
510k
                        file_request->predicate_columns.end(),
355
510k
                        [&](const LocalColumnIndex& p) { return p.column_id() == file_column_id; });
356
510k
                if (it != file_request->predicate_columns.end()) {
357
92.0k
                    RETURN_IF_ERROR(project_column_definition(*field, *it, &projected_field));
358
92.0k
                }
359
510k
            }
360
510k
            _data_reader.file_block_layout[block_position.value()] = {
361
510k
                    .file_column_id = file_column_id,
362
510k
                    .name = projected_field.name,
363
510k
                    .type = projected_field.type,
364
510k
            };
365
510k
            DORIS_CHECK(_data_reader.file_block_layout[block_position.value()].type != nullptr);
366
510k
        }
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
116k
        _data_reader.block_template.reserve(_data_reader.file_block_layout.size());
371
510k
        for (const auto& column : _data_reader.file_block_layout) {
372
510k
            _data_reader.block_template.insert(
373
510k
                    {column.type->create_column(), column.type, column.name});
374
510k
        }
375
116k
        if (VLOG_DEBUG_IS_ON) {
376
0
            VLOG_DEBUG << "TableReader debug: " << debug_string();
377
0
        }
378
116k
        RETURN_IF_ERROR(_open_mapping_exprs());
379
116k
        RETURN_IF_ERROR(_data_reader.reader->open(file_request));
380
116k
        RETURN_IF_ERROR(_init_reader_condition_cache(*file_request));
381
116k
        return Status::OK();
382
116k
    }
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
118k
    Status _evaluate_constant_filters(bool* can_filter_all) {
391
118k
        DORIS_CHECK(can_filter_all != nullptr);
392
118k
        *can_filter_all = false;
393
118k
        for (const auto& table_filter : _table_filters) {
394
81.6k
            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
81.7k
                table_filter.conjunct->root()->is_rf_wrapper() ||
400
81.6k
                !_table_filter_has_only_constant_entries(table_filter)) {
401
77.5k
                continue;
402
77.5k
            }
403
4.17k
            Block eval_block;
404
4.17k
            RETURN_IF_ERROR(_build_constant_filter_block(table_filter, &eval_block));
405
4.17k
            RowDescriptor row_desc;
406
4.17k
            RETURN_IF_ERROR(table_filter.conjunct->prepare(_runtime_state, row_desc));
407
4.17k
            RETURN_IF_ERROR(table_filter.conjunct->open(_runtime_state));
408
4.17k
            int result_column_id = -1;
409
4.17k
            RETURN_IF_ERROR(table_filter.conjunct->execute(&eval_block, &result_column_id));
410
4.17k
            DORIS_CHECK(result_column_id >= 0);
411
4.17k
            if (_filter_result_filters_all(eval_block.get_by_position(result_column_id).column)) {
412
1.44k
                *can_filter_all = true;
413
1.44k
                return Status::OK();
414
1.44k
            }
415
4.17k
        }
416
116k
        return Status::OK();
417
118k
    }
418
419
73.9k
    bool _table_filter_has_only_constant_entries(const TableFilter& table_filter) const {
420
73.9k
        const auto& filter_entries = _data_reader.column_mapper->filter_entries();
421
74.1k
        for (const auto global_index : table_filter.global_indices) {
422
74.1k
            const auto entry_it = filter_entries.find(global_index);
423
74.1k
            if (entry_it == filter_entries.end() || !entry_it->second.is_constant()) {
424
69.7k
                return false;
425
69.7k
            }
426
74.1k
        }
427
4.22k
        return !table_filter.global_indices.empty();
428
73.9k
    }
429
430
4.19k
    Status _build_constant_filter_block(const TableFilter& table_filter, Block* eval_block) {
431
4.19k
        DORIS_CHECK(eval_block != nullptr);
432
4.19k
        eval_block->clear();
433
4.19k
        const auto& mappings = _data_reader.column_mapper->mappings();
434
4.19k
        const auto& filter_entries = _data_reader.column_mapper->filter_entries();
435
4.19k
        DORIS_CHECK(mappings.size() == _projected_columns.size());
436
16.8k
        for (size_t column_idx = 0; column_idx < mappings.size(); ++column_idx) {
437
12.6k
            const auto global_index = GlobalIndex(column_idx);
438
12.6k
            const auto& mapping = mappings[column_idx];
439
12.6k
            const auto entry_it = filter_entries.find(global_index);
440
12.6k
            const bool referenced_by_filter =
441
12.6k
                    std::find(table_filter.global_indices.begin(),
442
12.6k
                              table_filter.global_indices.end(),
443
12.6k
                              global_index) != table_filter.global_indices.end();
444
12.6k
            if (referenced_by_filter && entry_it != filter_entries.end() &&
445
12.6k
                entry_it->second.is_constant()) {
446
4.43k
                ColumnPtr constant_column;
447
4.43k
                RETURN_IF_ERROR(_materialize_constant_filter_column(
448
4.43k
                        entry_it->second.constant_index(), &constant_column));
449
4.43k
                eval_block->insert({std::move(constant_column), mapping.table_type,
450
4.43k
                                    mapping.table_column_name});
451
8.21k
            } else {
452
8.21k
                eval_block->insert({mapping.table_type->create_column_const_with_default_value(1),
453
8.21k
                                    mapping.table_type, mapping.table_column_name});
454
8.21k
            }
455
12.6k
        }
456
4.19k
        return Status::OK();
457
4.19k
    }
458
459
4.43k
    Status _materialize_constant_filter_column(ConstantIndex constant_index, ColumnPtr* column) {
460
4.43k
        DORIS_CHECK(column != nullptr);
461
4.43k
        const auto& constant_entry = _data_reader.column_mapper->constant_map().get(constant_index);
462
4.43k
        DORIS_CHECK(constant_entry.expr != nullptr);
463
4.43k
        DORIS_CHECK(constant_entry.type != nullptr);
464
4.43k
        RowDescriptor row_desc;
465
4.43k
        RETURN_IF_ERROR(constant_entry.expr->prepare(_runtime_state, row_desc));
466
4.43k
        RETURN_IF_ERROR(constant_entry.expr->open(_runtime_state));
467
4.43k
        Block eval_block;
468
4.43k
        eval_block.insert({constant_entry.type->create_column_const_with_default_value(1),
469
4.43k
                           constant_entry.type, "__table_reader_constant_filter"});
470
4.43k
        int result_column_id = -1;
471
4.43k
        RETURN_IF_ERROR(constant_entry.expr->execute(&eval_block, &result_column_id));
472
4.43k
        DORIS_CHECK(result_column_id >= 0);
473
4.43k
        *column = eval_block.get_by_position(result_column_id).column;
474
4.43k
        DORIS_CHECK((*column)->size() == 1);
475
4.43k
        return Status::OK();
476
4.43k
    }
477
478
4.20k
    static bool _filter_result_filters_all(const ColumnPtr& filter_column) {
479
4.20k
        DORIS_CHECK(filter_column.get() != nullptr);
480
4.20k
        DORIS_CHECK(filter_column->size() == 1);
481
4.20k
        return !filter_column->get_bool(0);
482
4.20k
    }
483
484
116k
    virtual Status customize_file_scan_request(FileScanRequest* file_request) {
485
116k
        return _append_delete_predicate(file_request);
486
116k
    }
487
488
401k
    bool _is_table_level_count_active() const { return _remaining_table_level_count >= 0; }
489
490
1.42k
    Status _materialize_count_rows(size_t rows, Block* block) const {
491
1.42k
        DORIS_CHECK(block != nullptr);
492
1.42k
        DORIS_CHECK(block->columns() > 0 || rows == 0);
493
2.84k
        for (size_t column_idx = 0; column_idx < block->columns(); ++column_idx) {
494
1.42k
            auto column = block->get_by_position(column_idx).type->create_column();
495
1.42k
            column->resize(rows);
496
1.42k
            block->replace_by_position(column_idx, std::move(column));
497
1.42k
        }
498
1.42k
        return Status::OK();
499
1.42k
    }
500
501
430
    Status _read_table_level_count(Block* block, bool* eos) {
502
430
        DORIS_CHECK(block != nullptr);
503
430
        DORIS_CHECK(eos != nullptr);
504
430
        DORIS_CHECK(_push_down_agg_type == TPushAggOp::type::COUNT);
505
430
        DORIS_CHECK(_remaining_table_level_count >= 0);
506
430
        if (_remaining_table_level_count == 0) {
507
214
            _remaining_table_level_count = -1;
508
214
            _current_task.reset();
509
214
            *eos = true;
510
214
            return Status::OK();
511
214
        }
512
513
216
        const int64_t batch_size = _runtime_state == nullptr
514
216
                                           ? _remaining_table_level_count
515
216
                                           : static_cast<int64_t>(_runtime_state->batch_size());
516
216
        const auto rows = std::min(_remaining_table_level_count, batch_size);
517
216
        RETURN_IF_ERROR(_materialize_count_rows(cast_set<size_t>(rows), block));
518
216
        _remaining_table_level_count -= rows;
519
216
        *eos = false;
520
216
        return Status::OK();
521
216
    }
522
523
    void _append_file_scan_column(FileScanRequest* request, LocalColumnId column_id,
524
31.7k
                                  std::vector<LocalColumnIndex>* scan_columns) {
525
31.7k
        DORIS_CHECK(request != nullptr);
526
31.7k
        DORIS_CHECK(scan_columns != nullptr);
527
31.7k
        FileScanRequestBuilder builder(request);
528
31.7k
        Status status;
529
31.7k
        if (scan_columns == &request->predicate_columns) {
530
31.2k
            status = builder.add_predicate_column(column_id);
531
31.2k
        } else {
532
561
            DORIS_CHECK(scan_columns == &request->non_predicate_columns);
533
561
            status = builder.add_non_predicate_column(column_id);
534
561
        }
535
31.7k
        DORIS_CHECK(status.ok()) << status.to_string();
536
31.7k
        if (column_id == LocalColumnId(ROW_POSITION_COLUMN_ID) &&
537
31.7k
            _find_column_definition(_data_reader.file_schema, column_id) == nullptr) {
538
27.1k
            _data_reader.file_schema.push_back(row_position_column_definition());
539
27.1k
        }
540
31.7k
    }
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
116k
    Status _append_delete_predicate(FileScanRequest* request) {
544
116k
        DORIS_CHECK(request != nullptr);
545
116k
        if (_delete_rows == nullptr || _delete_rows->empty()) {
546
89.9k
            return Status::OK();
547
89.9k
        }
548
26.5k
        const auto row_position_column_id = LocalColumnId(ROW_POSITION_COLUMN_ID);
549
26.5k
        _append_file_scan_column(request, row_position_column_id, &request->predicate_columns);
550
551
26.5k
        auto delete_predicate = std::make_shared<DeletePredicate>(*_delete_rows);
552
26.5k
        const auto block_position = request->local_positions.at(row_position_column_id);
553
26.5k
        delete_predicate->add_child(VSlotRef::create_shared(
554
26.5k
                cast_set<int>(block_position.value()), cast_set<int>(block_position.value()), -1,
555
26.5k
                std::make_shared<DataTypeInt64>(), ROW_POSITION_COLUMN_NAME));
556
557
26.5k
        request->delete_conjuncts.push_back(
558
26.5k
                VExprContext::create_shared(std::move(delete_predicate)));
559
26.5k
        return Status::OK();
560
116k
    }
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
118k
    virtual Status close_current_reader() {
565
118k
        _finalize_reader_condition_cache();
566
118k
        RETURN_IF_ERROR(_data_reader.reader->close());
567
118k
        _data_reader.reader.reset();
568
118k
        if (_data_reader.column_mapper != nullptr) {
569
118k
            _data_reader.column_mapper->clear();
570
118k
            _data_reader.column_mapper.reset();
571
118k
        }
572
118k
        _table_filters.clear();
573
118k
        _data_reader.file_schema.clear();
574
118k
        _data_reader.file_block_layout.clear();
575
118k
        _data_reader.block_template.clear();
576
118k
        _current_task.reset();
577
118k
        _current_file_description.reset();
578
118k
        _current_reader_reached_eof = false;
579
118k
        return Status::OK();
580
118k
    }
581
582
6.08k
    void _record_scan_rows(size_t rows) {
583
6.08k
        if (_io_ctx != nullptr && _io_ctx->file_reader_stats != nullptr) {
584
6.08k
            _io_ctx->file_reader_stats->read_rows += rows;
585
6.08k
        }
586
6.08k
    }
587
588
    // Finalize file-local block to table/global schema block.
589
118k
    Status finalize_chunk(Block* block, const size_t rows) {
590
118k
        SCOPED_TIMER(_profile.finalize_timer);
591
118k
        size_t idx = 0;
592
638k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
593
638k
            ColumnPtr column;
594
638k
            RETURN_IF_ERROR(_materialize_mapping_column(mapping, &_data_reader.block_template, rows,
595
638k
                                                        &column));
596
638k
            block->replace_by_position(idx, IColumn::mutate(std::move(column)));
597
638k
            idx++;
598
638k
        }
599
118k
        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
118k
        RETURN_IF_ERROR(_truncate_char_or_varchar_columns(block));
603
118k
        return Status::OK();
604
118k
    }
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
93.9k
    virtual Status materialize_virtual_columns(Block* table_block) { return Status::OK(); }
610
611
#ifndef NDEBUG
612
118k
    Status _check_file_block_columns(std::string_view stage, size_t rows) {
613
118k
        DORIS_CHECK(_data_reader.block_template.columns() == _data_reader.file_block_layout.size());
614
743k
        for (size_t idx = 0; idx < _data_reader.block_template.columns(); ++idx) {
615
625k
            const auto& file_block_column = _data_reader.file_block_layout[idx];
616
625k
            const auto& column_with_type = _data_reader.block_template.get_by_position(idx);
617
625k
            const auto* column = column_with_type.column.get();
618
625k
            try {
619
625k
                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
625k
                column->sanity_check();
632
625k
                auto st = column_with_type.check_type_and_column_match();
633
625k
                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
625k
            } 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
625k
        }
676
118k
        return Status::OK();
677
118k
    }
678
679
118k
    Status _check_table_block_columns(std::string_view stage, const Block* block, size_t rows) {
680
118k
        DORIS_CHECK(block != nullptr);
681
118k
        DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
682
756k
        for (size_t idx = 0; idx < block->columns(); ++idx) {
683
638k
            const auto& mapping = _data_reader.column_mapper->mappings()[idx];
684
638k
            const auto& column_with_type = block->get_by_position(idx);
685
638k
            const auto* column = column_with_type.column.get();
686
638k
            try {
687
638k
                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
638k
                column->sanity_check();
698
638k
                auto st = column_with_type.check_type_and_column_match();
699
638k
                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
638k
            } 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
638k
        }
737
118k
        return Status::OK();
738
118k
    }
739
#endif
740
741
118k
    Status _truncate_char_or_varchar_columns(Block* block) {
742
118k
        DORIS_CHECK(block != nullptr);
743
118k
        if (_runtime_state == nullptr ||
744
118k
            !_runtime_state->query_options().truncate_char_or_varchar_columns) {
745
118k
            return Status::OK();
746
118k
        }
747
8
        DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
748
44
        for (size_t idx = 0; idx < _data_reader.column_mapper->mappings().size(); ++idx) {
749
36
            const auto& mapping = _data_reader.column_mapper->mappings()[idx];
750
36
            if (!_should_truncate_char_or_varchar_column(mapping)) {
751
12
                continue;
752
12
            }
753
24
            const auto target_len =
754
24
                    assert_cast<const DataTypeString*>(remove_nullable(mapping.table_type).get())
755
24
                            ->len();
756
24
            _truncate_char_or_varchar_column(block, idx, target_len);
757
24
        }
758
8
        return Status::OK();
759
118k
    }
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
41
    static bool _should_truncate_char_or_varchar_column(const ColumnMapping& mapping) {
767
41
        if (mapping.table_type == nullptr) {
768
0
            return false;
769
0
        }
770
41
        const auto table_type = remove_nullable(mapping.table_type);
771
41
        const auto primitive_type = table_type->get_primitive_type();
772
41
        if (primitive_type != TYPE_VARCHAR && primitive_type != TYPE_CHAR) {
773
13
            return false;
774
13
        }
775
28
        const auto target_len = assert_cast<const DataTypeString*>(table_type.get())->len();
776
28
        if (target_len <= 0) {
777
0
            return false;
778
0
        }
779
28
        if (mapping.file_type == nullptr) {
780
0
            return true;
781
0
        }
782
28
        const auto file_type = remove_nullable(mapping.file_type);
783
28
        DORIS_CHECK(file_type != nullptr);
784
28
        int file_len = -1;
785
28
        if (file_type->get_primitive_type() == TYPE_VARCHAR ||
786
28
            file_type->get_primitive_type() == TYPE_CHAR ||
787
28
            file_type->get_primitive_type() == TYPE_STRING) {
788
27
            file_len = assert_cast<const DataTypeString*>(file_type.get())->len();
789
27
        }
790
791
28
        return file_len < 0 || target_len < file_len;
792
28
    }
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
25
    static void _truncate_char_or_varchar_column(Block* block, size_t idx, int len) {
799
25
        DORIS_CHECK(block != nullptr);
800
25
        auto int_type = std::make_shared<DataTypeInt32>();
801
25
        const auto num_columns_without_result = cast_set<uint32_t>(block->columns());
802
25
        auto& target = block->get_by_position(idx);
803
25
        const bool is_nullable = target.type->is_nullable();
804
25
        ColumnPtr input_column = target.column;
805
25
        ColumnPtr null_map_column;
806
25
        if (is_nullable) {
807
25
            const auto* nullable_column = assert_cast<const ColumnNullable*>(target.column.get());
808
25
            input_column = nullable_column->get_nested_column_ptr();
809
25
            null_map_column = nullable_column->get_null_map_column_ptr();
810
25
        }
811
25
        block->replace_by_position(idx, std::move(input_column));
812
25
        block->insert({int_type->create_column_const(block->rows(), to_field<TYPE_INT>(1)),
813
25
                       int_type, "const 1"});
814
25
        block->insert({int_type->create_column_const(block->rows(), to_field<TYPE_INT>(len)),
815
25
                       int_type, "const len"});
816
25
        block->insert({nullptr, std::make_shared<DataTypeString>(), "result"});
817
818
25
        ColumnNumbers temp_arguments(3);
819
25
        temp_arguments[0] = cast_set<uint32_t>(idx);
820
25
        temp_arguments[1] = num_columns_without_result;
821
25
        temp_arguments[2] = num_columns_without_result + 1;
822
25
        const uint32_t result_column_id = num_columns_without_result + 2;
823
25
        SubstringUtil::substring_execute(*block, temp_arguments, result_column_id, block->rows());
824
825
25
        ColumnPtr result_column = block->get_by_position(result_column_id).column;
826
25
        if (is_nullable) {
827
25
            result_column = ColumnNullable::create(std::move(result_column), null_map_column);
828
25
        }
829
25
        block->replace_by_position(idx, std::move(result_column));
830
25
        block->erase_tail(num_columns_without_result);
831
25
    }
832
833
116k
    Status _try_materialize_aggregate_pushdown_rows(Block* block, bool* pushed_down) {
834
116k
        DORIS_CHECK(block != nullptr);
835
116k
        DORIS_CHECK(pushed_down != nullptr);
836
116k
        *pushed_down = false;
837
116k
        block->clear_column_data(_projected_columns.size());
838
116k
        _aggregate_pushdown_tried = true;
839
116k
        if (!_supports_aggregate_pushdown(_push_down_agg_type)) {
840
115k
            return Status::OK();
841
115k
        }
842
843
1.40k
        FileAggregateRequest file_request;
844
1.40k
        RETURN_IF_ERROR(_build_file_aggregate_request(_push_down_agg_type, &file_request));
845
1.40k
        FileAggregateResult file_result;
846
1.40k
        const auto status = _data_reader.reader->get_aggregate_result(file_request, &file_result);
847
1.40k
        if (status.is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) {
848
89
            return Status::OK();
849
89
        }
850
1.31k
        RETURN_IF_ERROR(status);
851
1.31k
        RETURN_IF_ERROR(
852
1.31k
                _materialize_aggregate_pushdown_rows(_push_down_agg_type, file_result, block));
853
1.31k
        *pushed_down = true;
854
1.31k
        RETURN_IF_ERROR(close_current_reader());
855
1.31k
        return Status::OK();
856
1.31k
    }
857
858
118k
    virtual bool _supports_aggregate_pushdown(TPushAggOp::type agg_type) const {
859
        // Only COUNT and MIN/MAX can be push down.
860
118k
        if (agg_type != TPushAggOp::type::COUNT && agg_type != TPushAggOp::type::MINMAX) {
861
114k
            return false;
862
114k
        }
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
3.62k
        if (_delete_rows != nullptr && !_delete_rows->empty()) {
867
605
            return false;
868
605
        }
869
3.02k
        if (!_table_filters.empty()) {
870
3
            return false;
871
3
        }
872
3.02k
        if (agg_type == TPushAggOp::type::COUNT) {
873
2.54k
            return true;
874
2.54k
        }
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
2.74k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
879
2.74k
            if (!mapping.file_local_id.has_value() ||
880
2.74k
                mapping.virtual_column_type != TableVirtualColumnType::INVALID ||
881
2.74k
                mapping.default_expr != nullptr || mapping.file_type == nullptr ||
882
2.74k
                mapping.table_type == nullptr) {
883
9
                return false;
884
9
            }
885
2.73k
            if (!_can_push_down_minmax_for_mapping(mapping)) {
886
1
                return false;
887
1
            }
888
2.73k
        }
889
466
        return true;
890
476
    }
891
892
628k
    static ColumnPtr _detach_column(ColumnPtr column) {
893
628k
        DORIS_CHECK(column.get() != nullptr);
894
628k
        return IColumn::mutate(std::move(column));
895
628k
    }
896
897
105k
    static Status _align_column_nullability(ColumnPtr* column, const DataTypePtr& table_type) {
898
105k
        DORIS_CHECK(column != nullptr);
899
105k
        DORIS_CHECK(column->get() != nullptr);
900
105k
        DORIS_CHECK(table_type != nullptr);
901
        // Must return non-const column
902
105k
        *column = (*column)->convert_to_full_column_if_const();
903
105k
        if (table_type->is_nullable()) {
904
52.9k
            const auto& nested_type =
905
52.9k
                    assert_cast<const DataTypeNullable&>(*table_type).get_nested_type();
906
52.9k
            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
52.9k
            const auto& nullable_column = assert_cast<const ColumnNullable&>(**column);
912
52.9k
            ColumnPtr nested_column = nullable_column.get_nested_column_ptr();
913
52.9k
            RETURN_IF_ERROR(_align_column_nullability(&nested_column, nested_type));
914
52.9k
            *column = ColumnNullable::create(nested_column,
915
52.9k
                                             nullable_column.get_null_map_column_ptr());
916
52.9k
            return Status::OK();
917
52.9k
        }
918
53.0k
        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
53.0k
        if (const auto* array_type = typeid_cast<const DataTypeArray*>(table_type.get())) {
930
145
            const auto& array_column = assert_cast<const ColumnArray&>(**column);
931
145
            ColumnPtr nested_column = array_column.get_data_ptr();
932
145
            RETURN_IF_ERROR(
933
145
                    _align_column_nullability(&nested_column, array_type->get_nested_type()));
934
145
            *column = ColumnArray::create(nested_column, array_column.get_offsets_ptr());
935
145
            return Status::OK();
936
145
        }
937
52.8k
        if (const auto* map_type = typeid_cast<const DataTypeMap*>(table_type.get())) {
938
120
            const auto& map_column = assert_cast<const ColumnMap&>(**column);
939
120
            ColumnPtr key_column = map_column.get_keys_ptr();
940
120
            ColumnPtr value_column = map_column.get_values_ptr();
941
120
            RETURN_IF_ERROR(_align_column_nullability(&key_column, map_type->get_key_type()));
942
120
            RETURN_IF_ERROR(_align_column_nullability(&value_column, map_type->get_value_type()));
943
120
            *column = ColumnMap::create(key_column, value_column, map_column.get_offsets_ptr());
944
120
            return Status::OK();
945
120
        }
946
52.7k
        if (const auto* struct_type = typeid_cast<const DataTypeStruct*>(table_type.get())) {
947
6.21k
            const auto& struct_column = assert_cast<const ColumnStruct&>(**column);
948
6.21k
            Columns columns = struct_column.get_columns_copy();
949
6.21k
            DORIS_CHECK(columns.size() == struct_type->get_elements().size());
950
20.9k
            for (size_t i = 0; i < columns.size(); ++i) {
951
14.7k
                RETURN_IF_ERROR(
952
14.7k
                        _align_column_nullability(&columns[i], struct_type->get_element(i)));
953
14.7k
            }
954
6.21k
            *column = ColumnStruct::create(columns);
955
6.21k
            return Status::OK();
956
6.21k
        }
957
46.5k
        return Status::OK();
958
52.7k
    }
959
960
    static Status _execute_default_expr_without_root_type_check(
961
            const VExprContextSPtr& default_expr, const Block* block,
962
18.6k
            ColumnWithTypeAndName* result_data) {
963
18.6k
        DORIS_CHECK(default_expr != nullptr);
964
18.6k
        DORIS_CHECK(block != nullptr);
965
18.6k
        DORIS_CHECK(result_data != nullptr);
966
18.6k
        ColumnPtr result_column;
967
18.6k
        Status st;
968
18.6k
        RETURN_IF_CATCH_EXCEPTION({
969
18.6k
            st = default_expr->root()->execute_column_impl(default_expr.get(), block, nullptr,
970
18.6k
                                                           block->rows(), result_column);
971
18.6k
        });
972
18.6k
        RETURN_IF_ERROR(st);
973
18.6k
        DORIS_CHECK(result_column.get() != nullptr);
974
18.6k
        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
18.6k
        result_data->column = result_column;
980
18.6k
        result_data->type = default_expr->execute_type(block);
981
18.6k
        result_data->name = default_expr->expr_name();
982
18.6k
        return Status::OK();
983
18.6k
    }
984
985
    Status _cast_column_to_type(ColumnPtr* column, const DataTypePtr& file_type,
986
                                const DataTypePtr& table_type,
987
9.41k
                                const std::string& column_name) const {
988
9.41k
        DORIS_CHECK(column != nullptr);
989
9.41k
        DORIS_CHECK(column->get() != nullptr);
990
9.41k
        DORIS_CHECK(file_type != nullptr);
991
9.41k
        DORIS_CHECK(table_type != nullptr);
992
9.41k
        if (file_type->equals(*table_type)) {
993
0
            return Status::OK();
994
0
        }
995
996
9.41k
        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
9.41k
        if ((*column)->is_nullable() && !input_type->is_nullable()) {
1000
0
            input_type = make_nullable(input_type);
1001
9.41k
        } else if (!(*column)->is_nullable() && input_type->is_nullable()) {
1002
1
            input_type = remove_nullable(input_type);
1003
1
        }
1004
9.41k
        Block cast_block;
1005
9.41k
        cast_block.insert({*column, input_type, column_name});
1006
9.41k
        auto slot_ref = VSlotRef::create_shared(0, 0, -1, input_type, column_name);
1007
9.41k
        auto cast_expr = Cast::create_shared(table_type);
1008
9.41k
        cast_expr->add_child(std::move(slot_ref));
1009
9.41k
        auto cast_ctx = VExprContext::create_shared(std::move(cast_expr));
1010
9.41k
        RowDescriptor row_desc;
1011
9.41k
        RETURN_IF_ERROR(cast_ctx->prepare(_runtime_state, row_desc));
1012
9.41k
        RETURN_IF_ERROR(cast_ctx->open(_runtime_state));
1013
9.41k
        ColumnPtr cast_column;
1014
9.41k
        RETURN_IF_ERROR(cast_ctx->execute(&cast_block, cast_column));
1015
9.41k
        *column = std::move(cast_column);
1016
9.41k
        return Status::OK();
1017
9.41k
    }
1018
1019
    Status _materialize_present_child_mapping_column(const ColumnMapping& mapping,
1020
                                                     const ColumnPtr& file_column,
1021
19.2k
                                                     const size_t rows, ColumnPtr* column) {
1022
19.2k
        DORIS_CHECK(column != nullptr);
1023
19.2k
        DORIS_CHECK(mapping.file_type != nullptr);
1024
19.2k
        DORIS_CHECK(mapping.table_type != nullptr);
1025
19.2k
        *column = file_column;
1026
19.2k
        if (!mapping.is_trivial) {
1027
11.5k
            if (!mapping.child_mappings.empty()) {
1028
2.13k
                RETURN_IF_ERROR(
1029
2.13k
                        _materialize_complex_mapping_column(mapping, *column, rows, column));
1030
9.41k
            } else {
1031
9.41k
                RETURN_IF_ERROR(_cast_column_to_type(column, mapping.file_type, mapping.table_type,
1032
9.41k
                                                     mapping.file_column_name));
1033
9.41k
            }
1034
11.5k
        }
1035
19.2k
        RETURN_IF_ERROR(_align_column_nullability(column, mapping.table_type));
1036
19.2k
        return Status::OK();
1037
19.2k
    }
1038
1039
    Status _materialize_mapping_column(const ColumnMapping& mapping, Block* current_block,
1040
639k
                                       const size_t rows, ColumnPtr* column) {
1041
639k
        if (!mapping.is_trivial && mapping.file_local_id.has_value() &&
1042
639k
            !mapping.child_mappings.empty()) {
1043
10.5k
            DCHECK(mapping.projection != nullptr);
1044
10.5k
            int res_id;
1045
10.5k
            auto st = mapping.projection->execute(current_block, &res_id);
1046
10.5k
            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
10.5k
            ColumnPtr result_column = current_block->get_by_position(res_id).column;
1054
10.5k
            RETURN_IF_ERROR(
1055
10.5k
                    _materialize_complex_mapping_column(mapping, result_column, rows, column));
1056
10.5k
            return Status::OK();
1057
10.5k
        }
1058
628k
        if (mapping.projection != nullptr) {
1059
609k
            int res_id;
1060
609k
            auto st = mapping.projection->execute(current_block, &res_id);
1061
609k
            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
609k
            ColumnPtr result_column = current_block->get_by_position(res_id).column;
1073
609k
            *column = _detach_column(std::move(result_column));
1074
609k
            return Status::OK();
1075
609k
        }
1076
19.3k
        if (mapping.default_expr != nullptr) {
1077
18.6k
            if (current_block->rows() == rows) {
1078
15.1k
                ColumnWithTypeAndName result;
1079
15.1k
                RETURN_IF_ERROR(_execute_default_expr_without_root_type_check(
1080
15.1k
                        mapping.default_expr, current_block, &result));
1081
15.1k
                ColumnPtr result_column = result.column;
1082
15.1k
                RETURN_IF_ERROR(_align_column_nullability(&result_column, mapping.table_type));
1083
15.1k
                *column = _detach_column(std::move(result_column));
1084
15.1k
            } else {
1085
3.46k
                DORIS_CHECK(mapping.constant_index.has_value());
1086
3.46k
                Block eval_block;
1087
3.46k
                eval_block.insert({mapping.table_type->create_column_const_with_default_value(rows),
1088
3.46k
                                   mapping.table_type, "__table_reader_const_rows"});
1089
3.46k
                ColumnWithTypeAndName result;
1090
3.46k
                RETURN_IF_ERROR(_execute_default_expr_without_root_type_check(
1091
3.46k
                        mapping.default_expr, &eval_block, &result));
1092
3.46k
                ColumnPtr result_column = result.column;
1093
3.46k
                RETURN_IF_ERROR(_align_column_nullability(&result_column, mapping.table_type));
1094
3.46k
                *column = _detach_column(std::move(result_column));
1095
3.46k
            }
1096
18.6k
            return Status::OK();
1097
18.6k
        }
1098
705
        ColumnPtr result_column = mapping.table_type->create_column_const_with_default_value(rows);
1099
705
        *column = _detach_column(std::move(result_column));
1100
705
        return Status::OK();
1101
19.3k
    }
1102
1103
    Status _materialize_complex_mapping_column(const ColumnMapping& mapping,
1104
                                               const ColumnPtr& file_column, const size_t rows,
1105
12.6k
                                               ColumnPtr* column) {
1106
12.6k
        DORIS_CHECK(mapping.table_type != nullptr);
1107
12.6k
        DORIS_CHECK(file_column.get() != nullptr);
1108
12.6k
        const auto table_type = remove_nullable(mapping.table_type);
1109
12.6k
        switch (table_type->get_primitive_type()) {
1110
3.94k
        case TYPE_STRUCT:
1111
3.94k
            RETURN_IF_ERROR(_materialize_struct_mapping_column(mapping, file_column, rows, column));
1112
3.94k
            break;
1113
4.58k
        case TYPE_ARRAY:
1114
4.58k
            RETURN_IF_ERROR(_materialize_array_mapping_column(mapping, file_column, rows, column));
1115
4.58k
            break;
1116
4.58k
        case TYPE_MAP:
1117
4.16k
            RETURN_IF_ERROR(_materialize_map_mapping_column(mapping, file_column, rows, column));
1118
4.16k
            break;
1119
4.16k
        default:
1120
0
            *column = _detach_column(file_column);
1121
0
            break;
1122
12.6k
        }
1123
12.6k
        return Status::OK();
1124
12.6k
    }
1125
1126
    static std::vector<const ColumnMapping*> _present_child_mappings_in_file_order(
1127
3.94k
            const std::vector<ColumnMapping>& child_mappings) {
1128
3.94k
        std::vector<const ColumnMapping*> result;
1129
3.94k
        result.reserve(child_mappings.size());
1130
10.1k
        for (const auto& child_mapping : child_mappings) {
1131
10.1k
            if (child_mapping.file_local_id.has_value()) {
1132
6.28k
                result.push_back(&child_mapping);
1133
6.28k
            }
1134
10.1k
        }
1135
5.10k
        std::ranges::sort(result, [](const ColumnMapping* lhs, const ColumnMapping* rhs) {
1136
5.10k
            DORIS_CHECK(lhs->file_local_id.has_value());
1137
5.10k
            DORIS_CHECK(rhs->file_local_id.has_value());
1138
5.10k
            return *lhs->file_local_id < *rhs->file_local_id;
1139
5.10k
        });
1140
3.94k
        return result;
1141
3.94k
    }
1142
1143
    static size_t _file_child_ordinal_for_mapping(
1144
            const ColumnMapping& mapping, const ColumnMapping& child_mapping,
1145
6.28k
            const std::vector<const ColumnMapping*>& file_ordered_children) {
1146
6.28k
        DORIS_CHECK(child_mapping.file_local_id.has_value());
1147
6.28k
        if (!mapping.projected_file_children.empty()) {
1148
6.28k
            const auto child_it = std::ranges::find_if(
1149
11.4k
                    mapping.projected_file_children, [&](const ColumnDefinition& file_child) {
1150
11.4k
                        return file_child.file_local_id() == *child_mapping.file_local_id;
1151
11.4k
                    });
1152
6.28k
            DORIS_CHECK(child_it != mapping.projected_file_children.end());
1153
6.28k
            return static_cast<size_t>(
1154
6.28k
                    std::distance(mapping.projected_file_children.begin(), child_it));
1155
6.28k
        }
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
6.28k
    }
1160
1161
    static std::vector<const ColumnMapping*> _child_mappings_in_table_type_order(
1162
3.94k
            const ColumnMapping& mapping, const DataTypeStruct& table_type) {
1163
3.94k
        std::vector<const ColumnMapping*> result;
1164
3.94k
        result.reserve(mapping.child_mappings.size());
1165
14.1k
        for (size_t child_idx = 0; child_idx < table_type.get_elements().size(); ++child_idx) {
1166
10.1k
            const auto& child_name = table_type.get_element_name(child_idx);
1167
10.1k
            const auto child_it = std::ranges::find_if(
1168
21.2k
                    mapping.child_mappings, [&](const ColumnMapping& child_mapping) {
1169
21.2k
                        return child_mapping.table_column_name == child_name;
1170
21.2k
                    });
1171
10.1k
            DORIS_CHECK(child_it != mapping.child_mappings.end())
1172
0
                    << mapping.debug_string() << ", table_child_name=" << child_name;
1173
10.1k
            result.push_back(&*child_it);
1174
10.1k
        }
1175
3.94k
        return result;
1176
3.94k
    }
1177
1178
    static const IColumn* _nested_column_if_nullable(const ColumnPtr& column,
1179
12.6k
                                                     const NullMap** null_map) {
1180
12.6k
        DORIS_CHECK(column.get() != nullptr);
1181
12.6k
        if (const auto* nullable_column = check_and_get_column<ColumnNullable>(*column)) {
1182
12.6k
            if (null_map != nullptr) {
1183
12.6k
                *null_map = &nullable_column->get_null_map_data();
1184
12.6k
            }
1185
12.6k
            return &nullable_column->get_nested_column();
1186
12.6k
        }
1187
4
        return column.get();
1188
12.6k
    }
1189
1190
    Status _materialize_struct_mapping_column(const ColumnMapping& mapping,
1191
                                              const ColumnPtr& file_column, const size_t rows,
1192
3.94k
                                              ColumnPtr* column) {
1193
3.94k
        DORIS_CHECK(mapping.table_type != nullptr);
1194
3.94k
        const auto* table_type =
1195
3.94k
                assert_cast<const DataTypeStruct*>(remove_nullable(mapping.table_type).get());
1196
3.94k
        const auto full_file_column = file_column->convert_to_full_column_if_const();
1197
3.94k
        const NullMap* parent_null_map = nullptr;
1198
3.94k
        const auto* nested_file_column =
1199
3.94k
                _nested_column_if_nullable(full_file_column, &parent_null_map);
1200
3.94k
        const auto* file_struct = assert_cast<const ColumnStruct*>(nested_file_column);
1201
3.94k
        DORIS_CHECK(table_type->get_elements().size() == mapping.child_mappings.size());
1202
1203
3.94k
        Columns child_columns;
1204
3.94k
        child_columns.reserve(mapping.child_mappings.size());
1205
3.94k
        const auto file_ordered_children =
1206
3.94k
                _present_child_mappings_in_file_order(mapping.child_mappings);
1207
3.94k
        const auto table_ordered_children =
1208
3.94k
                _child_mappings_in_table_type_order(mapping, *table_type);
1209
10.1k
        for (const auto* child_mapping : table_ordered_children) {
1210
10.1k
            DORIS_CHECK(child_mapping != nullptr);
1211
10.1k
            if (!child_mapping->file_local_id.has_value()) {
1212
3.91k
                child_columns.push_back(
1213
3.91k
                        child_mapping->table_type->create_column_const_with_default_value(rows)
1214
3.91k
                                ->convert_to_full_column_if_const());
1215
3.91k
                continue;
1216
3.91k
            }
1217
6.28k
            const auto file_child_idx =
1218
6.28k
                    _file_child_ordinal_for_mapping(mapping, *child_mapping, file_ordered_children);
1219
6.28k
            DORIS_CHECK(file_child_idx < file_struct->get_columns().size());
1220
6.28k
            ColumnPtr child_column = file_struct->get_column_ptr(file_child_idx);
1221
6.28k
            RETURN_IF_ERROR(_materialize_present_child_mapping_column(*child_mapping, child_column,
1222
6.28k
                                                                      rows, &child_column));
1223
6.28k
            child_columns.push_back(std::move(child_column));
1224
6.28k
        }
1225
3.94k
        MutableColumns mutable_child_columns;
1226
3.94k
        mutable_child_columns.reserve(child_columns.size());
1227
10.1k
        for (auto& child_column : child_columns) {
1228
10.1k
            mutable_child_columns.push_back(IColumn::mutate(std::move(child_column)));
1229
10.1k
        }
1230
3.94k
        auto result = ColumnStruct::create(std::move(mutable_child_columns));
1231
3.94k
        if (mapping.table_type->is_nullable()) {
1232
3.94k
            auto null_map = ColumnUInt8::create();
1233
3.94k
            auto& null_map_data = null_map->get_data();
1234
3.94k
            null_map_data.resize(rows);
1235
3.94k
            if (parent_null_map != nullptr) {
1236
3.94k
                DORIS_CHECK(parent_null_map->size() == rows);
1237
3.94k
                null_map_data.assign(parent_null_map->begin(), parent_null_map->end());
1238
3.94k
            } else {
1239
0
                std::fill(null_map_data.begin(), null_map_data.end(), 0);
1240
0
            }
1241
3.94k
            *column = ColumnNullable::create(std::move(result), std::move(null_map));
1242
3.94k
        } else {
1243
2
            *column = std::move(result);
1244
2
        }
1245
3.94k
        return Status::OK();
1246
3.94k
    }
1247
1248
    Status _materialize_array_mapping_column(const ColumnMapping& mapping,
1249
                                             const ColumnPtr& file_column, const size_t rows,
1250
4.58k
                                             ColumnPtr* column) {
1251
4.58k
        DORIS_CHECK(mapping.child_mappings.size() == 1);
1252
4.58k
        const auto full_file_column = file_column->convert_to_full_column_if_const();
1253
4.58k
        const NullMap* parent_null_map = nullptr;
1254
4.58k
        const auto* nested_file_column =
1255
4.58k
                _nested_column_if_nullable(full_file_column, &parent_null_map);
1256
4.58k
        const auto* file_array = assert_cast<const ColumnArray*>(nested_file_column);
1257
4.58k
        ColumnPtr nested_column = file_array->get_data_ptr();
1258
4.58k
        const auto& element_mapping = mapping.child_mappings[0];
1259
4.58k
        RETURN_IF_ERROR(_materialize_present_child_mapping_column(
1260
4.58k
                element_mapping, nested_column, nested_column->size(), &nested_column));
1261
4.58k
        auto offsets_column = file_array->get_offsets_ptr()->convert_to_full_column_if_const();
1262
4.58k
        auto result = ColumnArray::create(IColumn::mutate(std::move(nested_column)),
1263
4.58k
                                          IColumn::mutate(std::move(offsets_column)));
1264
4.58k
        if (mapping.table_type->is_nullable()) {
1265
4.58k
            auto null_map = ColumnUInt8::create();
1266
4.58k
            auto& null_map_data = null_map->get_data();
1267
4.58k
            null_map_data.resize(rows);
1268
4.58k
            if (parent_null_map != nullptr) {
1269
4.58k
                DORIS_CHECK(parent_null_map->size() == rows);
1270
4.58k
                null_map_data.assign(parent_null_map->begin(), parent_null_map->end());
1271
4.58k
            } else {
1272
0
                std::fill(null_map_data.begin(), null_map_data.end(), 0);
1273
0
            }
1274
4.58k
            *column = ColumnNullable::create(std::move(result), std::move(null_map));
1275
18.4E
        } else {
1276
18.4E
            *column = std::move(result);
1277
18.4E
        }
1278
4.58k
        return Status::OK();
1279
4.58k
    }
1280
1281
    Status _materialize_map_mapping_column(const ColumnMapping& mapping,
1282
                                           const ColumnPtr& file_column, const size_t rows,
1283
4.16k
                                           ColumnPtr* column) {
1284
4.16k
        const auto full_file_column = file_column->convert_to_full_column_if_const();
1285
4.16k
        const NullMap* parent_null_map = nullptr;
1286
4.16k
        const auto* nested_file_column =
1287
4.16k
                _nested_column_if_nullable(full_file_column, &parent_null_map);
1288
4.16k
        const auto* file_map = assert_cast<const ColumnMap*>(nested_file_column);
1289
4.16k
        ColumnPtr key_column = file_map->get_keys_ptr();
1290
4.16k
        ColumnPtr value_column = file_map->get_values_ptr();
1291
1292
4.16k
        const ColumnMapping* key_mapping = nullptr;
1293
4.16k
        const ColumnMapping* value_mapping = nullptr;
1294
8.33k
        for (const auto& child_mapping : mapping.child_mappings) {
1295
8.33k
            if (!child_mapping.file_local_id.has_value()) {
1296
0
                continue;
1297
0
            }
1298
8.33k
            if (*child_mapping.file_local_id == 0) {
1299
4.16k
                key_mapping = &child_mapping;
1300
4.16k
            } else if (*child_mapping.file_local_id == 1) {
1301
4.16k
                value_mapping = &child_mapping;
1302
4.16k
            }
1303
8.33k
        }
1304
1305
4.16k
        if (key_mapping != nullptr) {
1306
4.16k
            RETURN_IF_ERROR(_materialize_present_child_mapping_column(
1307
4.16k
                    *key_mapping, key_column, key_column->size(), &key_column));
1308
4.16k
        }
1309
4.16k
        if (value_mapping != nullptr) {
1310
4.16k
            RETURN_IF_ERROR(_materialize_present_child_mapping_column(
1311
4.16k
                    *value_mapping, value_column, value_column->size(), &value_column));
1312
4.16k
        }
1313
4.16k
        auto offsets_column = file_map->get_offsets_ptr()->convert_to_full_column_if_const();
1314
4.16k
        auto result = ColumnMap::create(IColumn::mutate(std::move(key_column)),
1315
4.16k
                                        IColumn::mutate(std::move(value_column)),
1316
4.16k
                                        IColumn::mutate(std::move(offsets_column)));
1317
4.16k
        if (mapping.table_type->is_nullable()) {
1318
4.16k
            auto null_map = ColumnUInt8::create();
1319
4.16k
            auto& null_map_data = null_map->get_data();
1320
4.16k
            null_map_data.resize(rows);
1321
4.16k
            if (parent_null_map != nullptr) {
1322
4.16k
                DORIS_CHECK(parent_null_map->size() == rows);
1323
4.16k
                null_map_data.assign(parent_null_map->begin(), parent_null_map->end());
1324
4.16k
            } else {
1325
0
                std::fill(null_map_data.begin(), null_map_data.end(), 0);
1326
0
            }
1327
4.16k
            *column = ColumnNullable::create(std::move(result), std::move(null_map));
1328
4.16k
        } else {
1329
0
            *column = std::move(result);
1330
0
        }
1331
4.16k
        return Status::OK();
1332
4.16k
    }
1333
1334
116k
    Status _open_mapping_exprs() {
1335
116k
        RowDescriptor row_desc;
1336
502k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
1337
502k
            if (mapping.projection != nullptr) {
1338
482k
                RETURN_IF_ERROR(mapping.projection->prepare(_runtime_state, row_desc));
1339
482k
                RETURN_IF_ERROR(mapping.projection->open(_runtime_state));
1340
482k
            }
1341
502k
            if (mapping.default_expr != nullptr) {
1342
19.2k
                RETURN_IF_ERROR(mapping.default_expr->prepare(_runtime_state, row_desc));
1343
19.2k
                RETURN_IF_ERROR(mapping.default_expr->open(_runtime_state));
1344
19.2k
            }
1345
502k
        }
1346
116k
        return Status::OK();
1347
116k
    }
1348
1349
    Status _build_file_aggregate_request(TPushAggOp::type agg_type,
1350
1.45k
                                         FileAggregateRequest* request) const {
1351
1.45k
        DORIS_CHECK(request != nullptr);
1352
1.45k
        DORIS_CHECK(_supports_aggregate_pushdown(agg_type));
1353
1.45k
        request->agg_type = agg_type;
1354
1.45k
        request->columns.clear();
1355
1.45k
        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
1.20k
            if (_data_reader.column_mapper->mappings().size() == 1) {
1364
1.20k
                const auto& mapping = _data_reader.column_mapper->mappings()[0];
1365
1.20k
                if (mapping.file_local_id.has_value() && mapping.file_type != nullptr &&
1366
1.20k
                    is_complex_type(remove_nullable(mapping.file_type)->get_primitive_type()) &&
1367
1.20k
                    mapping.virtual_column_type == TableVirtualColumnType::INVALID &&
1368
1.20k
                    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
1.20k
            }
1375
1.20k
            return Status::OK();
1376
1.20k
        }
1377
247
        request->columns.reserve(_data_reader.column_mapper->mappings().size());
1378
1.36k
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
1379
1.36k
            DORIS_CHECK(mapping.file_local_id.has_value());
1380
1.36k
            FileAggregateRequest::Column column;
1381
1.36k
            column.projection = LocalColumnIndex::top_level(LocalColumnId(*mapping.file_local_id));
1382
1.36k
            if (!mapping.child_mappings.empty()) {
1383
1
                RETURN_IF_ERROR(build_aggregate_projection(mapping, &column.projection));
1384
1
            }
1385
1.36k
            request->columns.push_back(std::move(column));
1386
1.36k
        }
1387
247
        return Status::OK();
1388
247
    }
1389
1390
    Status _materialize_aggregate_pushdown_rows(TPushAggOp::type agg_type,
1391
                                                const FileAggregateResult& file_result,
1392
1.36k
                                                Block* block) {
1393
1.36k
        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
1.20k
            DORIS_CHECK(file_result.count >= 0);
1398
1.20k
            return _materialize_count_rows(cast_set<size_t>(file_result.count), block);
1399
1.20k
        }
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
158
        DORIS_CHECK(file_result.columns.size() == _data_reader.column_mapper->mappings().size());
1403
158
        DORIS_CHECK(block->columns() == _data_reader.column_mapper->mappings().size());
1404
158
        Block file_block;
1405
158
        file_block.reserve(_data_reader.file_block_layout.size());
1406
839
        for (const auto& column : _data_reader.file_block_layout) {
1407
839
            file_block.insert({column.type->create_column(), column.type, column.name});
1408
839
        }
1409
997
        for (size_t column_idx = 0; column_idx < file_result.columns.size(); ++column_idx) {
1410
839
            const auto& result_column = file_result.columns[column_idx];
1411
839
            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
839
            bool found_file_column = false;
1416
2.86k
            for (size_t block_position = 0; block_position < _data_reader.file_block_layout.size();
1417
2.86k
                 ++block_position) {
1418
2.86k
                if (_data_reader.file_block_layout[block_position].file_column_id ==
1419
2.86k
                    file_result.columns[column_idx].projection.column_id()) {
1420
839
                    found_file_column = true;
1421
839
                    auto column = file_block.get_by_position(block_position)
1422
839
                                          .type->create_column()
1423
839
                                          ->assert_mutable();
1424
839
                    RETURN_IF_ERROR(_insert_aggregate_projection_value(
1425
839
                            file_result.columns[column_idx].projection, result_column.min_value,
1426
839
                            column.get()));
1427
839
                    RETURN_IF_ERROR(_insert_aggregate_projection_value(
1428
839
                            file_result.columns[column_idx].projection, result_column.max_value,
1429
839
                            column.get()));
1430
839
                    file_block.replace_by_position(block_position, std::move(column));
1431
839
                    break;
1432
839
                }
1433
2.86k
            }
1434
839
            DORIS_CHECK(found_file_column);
1435
839
        }
1436
997
        for (size_t column_idx = 0; column_idx < _data_reader.column_mapper->mappings().size();
1437
839
             ++column_idx) {
1438
839
            ColumnPtr table_column;
1439
839
            RETURN_IF_ERROR(
1440
839
                    _materialize_mapping_column(_data_reader.column_mapper->mappings()[column_idx],
1441
839
                                                &file_block, 2, &table_column));
1442
839
            block->replace_by_position(column_idx, std::move(table_column));
1443
839
        }
1444
158
        return Status::OK();
1445
158
    }
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
537k
            const std::vector<ColumnDefinition>& schema, LocalColumnId column_id) {
1504
10.6M
        for (const auto& field : schema) {
1505
10.6M
            if (field.file_local_id() == column_id.value()) {
1506
510k
                return &field;
1507
510k
            }
1508
10.6M
        }
1509
27.5k
        return nullptr;
1510
537k
    }
1511
1512
2.73k
    static bool _can_push_down_minmax_for_mapping(const ColumnMapping& mapping) {
1513
2.73k
        if (mapping.child_mappings.empty()) {
1514
2.73k
            return true;
1515
2.73k
        }
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
3.36k
                                                     const Field& value, IColumn* column) {
1558
3.36k
        DORIS_CHECK(column != nullptr);
1559
3.36k
        if (auto* nullable_column = check_and_get_column<ColumnNullable>(*column)) {
1560
1.68k
            RETURN_IF_ERROR(_insert_aggregate_projection_value(
1561
1.68k
                    projection, value, &nullable_column->get_nested_column()));
1562
1.68k
            nullable_column->get_null_map_data().push_back(0);
1563
1.68k
            return Status::OK();
1564
1.68k
        }
1565
1.68k
        if (projection.project_all_children || projection.children.empty()) {
1566
1.67k
            column->insert(value);
1567
1.67k
            return Status::OK();
1568
1.67k
        }
1569
2
        auto* struct_column = assert_cast<ColumnStruct*>(column);
1570
2
        DORIS_CHECK(projection.children.size() == 1);
1571
2
        const auto& child_projection = projection.children[0];
1572
2
        DORIS_CHECK(struct_column->get_columns().size() == 1);
1573
2
        RETURN_IF_ERROR(_insert_aggregate_projection_value(child_projection, value,
1574
2
                                                           &struct_column->get_column(0)));
1575
2
        return Status::OK();
1576
2
    }
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