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