Coverage Report

Created: 2026-07-17 15:05

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