Coverage Report

Created: 2026-07-13 14:42

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