Coverage Report

Created: 2026-07-09 16:31

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