Coverage Report

Created: 2026-08-17 18:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format_v2/table_reader.cpp
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
#include "format_v2/table_reader.h"
19
20
#include <gen_cpp/ExternalTableSchema_types.h>
21
#include <gen_cpp/PlanNodes_types.h>
22
#include <gen_cpp/Types_types.h>
23
24
#include <algorithm>
25
#include <memory>
26
#include <ranges>
27
#include <set>
28
#include <sstream>
29
#include <utility>
30
#include <vector>
31
32
#include "common/cast_set.h"
33
#include "common/status.h"
34
#include "core/assert_cast.h"
35
#include "core/data_type/data_type_array.h"
36
#include "core/data_type/data_type_factory.hpp"
37
#include "core/data_type/data_type_map.h"
38
#include "core/data_type/data_type_struct.h"
39
#include "core/data_type/primitive_type.h"
40
#include "exprs/vexpr_context.h"
41
#include "exprs/vslot_ref.h"
42
#include "format/table/deletion_vector_reader.h"
43
#include "format/table/iceberg_delete_file_reader_helper.h"
44
#include "format/table/iceberg_scan_semantics.h"
45
#include "format/table/paimon_reader.h"
46
#include "format_v2/column_mapper.h"
47
#include "format_v2/delimited_text/csv_reader.h"
48
#include "format_v2/delimited_text/text_reader.h"
49
#include "format_v2/json/json_reader.h"
50
#include "format_v2/native/native_reader.h"
51
#include "format_v2/orc/orc_reader.h"
52
#include "format_v2/parquet/parquet_reader.h"
53
#include "format_v2/table/schema_history_util.h" // get_field_ptr
54
#include "runtime/file_scan_profile.h"
55
#include "storage/segment/condition_cache.h"
56
#include "util/debug_points.h"
57
#include "util/string_util.h"
58
59
namespace doris::format {
60
namespace {
61
62
0
template <typename T, typename Formatter>
63
0
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
64
0
    std::ostringstream out;
65
0
    out << "[";
66
0
    for (size_t i = 0; i < values.size(); ++i) {
67
0
        if (i > 0) {
68
0
            out << ", ";
69
0
        }
70
0
        out << formatter(values[i]);
71
0
    }
72
0
    out << "]";
73
0
    return out.str();
Unexecuted instantiation: table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_0EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Unexecuted instantiation: table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableFilterEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_1EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Unexecuted instantiation: table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11GlobalIndexEZNS1_25table_filter_debug_stringB5cxx11ERKNS0_11TableFilterEE3$_0EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISF_EET0_
Unexecuted instantiation: table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsISt10shared_ptrINS_12VExprContextEEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_2EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISF_EET0_
Unexecuted instantiation: table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_3EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Unexecuted instantiation: table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableReader15FileBlockColumnEZNKS3_12debug_stringB5cxx11EvE3$_4EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
74
}
75
0
76
0
std::string file_format_to_string(FileFormat format) {
77
0
    switch (format) {
78
0
    case FileFormat::PARQUET:
79
0
        return "PARQUET";
80
0
    case FileFormat::ORC:
81
0
        return "ORC";
82
0
    case FileFormat::CSV:
83
0
        return "CSV";
84
0
    case FileFormat::JSON:
85
0
        return "JSON";
86
0
    case FileFormat::TEXT:
87
0
        return "TEXT";
88
0
    case FileFormat::JNI:
89
0
        return "JNI";
90
0
    case FileFormat::NATIVE:
91
0
        return "NATIVE";
92
0
    case FileFormat::ARROW:
93
0
        return "ARROW";
94
0
    case FileFormat::WAL:
95
0
        return "WAL";
96
0
    }
97
0
    return "UNKNOWN";
98
}
99
0
100
0
std::string push_down_agg_to_string(TPushAggOp::type op) {
101
0
    switch (op) {
102
0
    case TPushAggOp::NONE:
103
0
        return "NONE";
104
0
    case TPushAggOp::COUNT:
105
0
        return "COUNT";
106
0
    case TPushAggOp::MINMAX:
107
0
        return "MINMAX";
108
0
    case TPushAggOp::MIX:
109
0
        return "MIX";
110
0
    case TPushAggOp::COUNT_ON_INDEX:
111
0
        return "COUNT_ON_INDEX";
112
0
    }
113
0
    return "UNKNOWN";
114
}
115
0
116
0
std::string current_file_debug_string(const std::unique_ptr<ScanTask>& task) {
117
0
    if (task == nullptr || task->data_file == nullptr) {
118
0
        return "null";
119
0
    }
120
0
    const auto& file = *task->data_file;
121
0
    std::ostringstream out;
122
0
    out << "FileDescription{path=" << file.path << ", file_size=" << file.file_size
123
0
        << ", range_start_offset=" << file.range_start_offset << ", range_size=" << file.range_size
124
0
        << ", mtime=" << file.mtime << ", fs_name=" << file.fs_name
125
0
        << ", is_immutable=" << file.is_immutable
126
0
        << ", file_cache_admission=" << file.file_cache_admission << "}";
127
0
    return out.str();
128
}
129
0
130
0
std::string partition_values_debug_string(const std::map<std::string, Field>& partition_values) {
131
0
    std::ostringstream out;
132
0
    out << "{";
133
0
    size_t idx = 0;
134
0
    for (const auto& [key, _] : partition_values) {
135
0
        if (idx++ > 0) {
136
0
            out << ", ";
137
0
        }
138
0
        out << key;
139
0
    }
140
0
    out << "}";
141
0
    return out.str();
142
}
143
2.39M
144
2.39M
const schema::external::TField* find_external_field_by_id(
145
0
        const schema::external::TStructField* root, int32_t field_id) {
146
0
    if (root == nullptr || !root->__isset.fields) {
147
2.39M
        return nullptr;
148
2.39M
    }
149
    for (const auto& field_ptr : root->fields) {
150
        const auto* field = get_field_ptr(field_ptr);
151
0
        if (field == nullptr) {
152
0
            continue;
153
0
        }
154
0
        if (field->__isset.id && field->id == field_id) {
155
0
            return field;
156
0
        }
157
0
        if (!field->__isset.nestedField) {
158
0
            continue;
159
0
        }
160
0
        if (field->nestedField.__isset.struct_field) {
161
0
            if (const auto* result =
162
0
                        find_external_field_by_id(&field->nestedField.struct_field, field_id);
163
0
                result != nullptr) {
164
0
                return result;
165
0
            }
166
0
        } else if (field->nestedField.__isset.array_field &&
167
0
                   field->nestedField.array_field.__isset.item_field) {
168
0
            const auto* child = get_field_ptr(field->nestedField.array_field.item_field);
169
0
            if (child != nullptr) {
170
0
                schema::external::TStructField child_root;
171
0
                child_root.__set_fields({field->nestedField.array_field.item_field});
172
0
                if (const auto* result = find_external_field_by_id(&child_root, field_id);
173
0
                    result != nullptr) {
174
0
                    return result;
175
0
                }
176
0
            }
177
0
        } else if (field->nestedField.__isset.map_field) {
178
0
            schema::external::TStructField child_root;
179
0
            std::vector<schema::external::TFieldPtr> children;
180
0
            if (field->nestedField.map_field.__isset.key_field) {
181
0
                children.push_back(field->nestedField.map_field.key_field);
182
0
            }
183
0
            if (field->nestedField.map_field.__isset.value_field) {
184
0
                children.push_back(field->nestedField.map_field.value_field);
185
0
            }
186
0
            child_root.__set_fields(children);
187
0
            if (const auto* result = find_external_field_by_id(&child_root, field_id);
188
0
                result != nullptr) {
189
0
                return result;
190
0
            }
191
0
        }
192
0
    }
193
0
    return nullptr;
194
0
}
195
0
196
0
bool find_external_field_path_by_id(const schema::external::TField* field, int32_t field_id,
197
0
                                    std::vector<const schema::external::TField*>* const path) {
198
0
    DORIS_CHECK(path != nullptr);
199
0
    DORIS_CHECK(field != nullptr);
200
0
    path->push_back(field);
201
    if (field->__isset.id && field->id == field_id) {
202
        return true;
203
0
    }
204
0
    if (field->__isset.nestedField && field->nestedField.__isset.struct_field &&
205
0
        field->nestedField.struct_field.__isset.fields) {
206
0
        for (const auto& child_ptr : field->nestedField.struct_field.fields) {
207
0
            const auto* child = get_field_ptr(child_ptr);
208
0
            if (child != nullptr && find_external_field_path_by_id(child, field_id, path)) {
209
0
                return true;
210
0
            }
211
0
        }
212
0
    }
213
0
    path->pop_back();
214
0
    return false;
215
0
}
216
0
217
0
std::optional<std::vector<const schema::external::TField*>> find_external_struct_field_path_by_id(
218
0
        const schema::external::TSchema& schema, int32_t field_id) {
219
0
    if (!schema.__isset.root_field || !schema.root_field.__isset.fields) {
220
0
        return std::nullopt;
221
0
    }
222
    std::vector<const schema::external::TField*> path;
223
    for (const auto& field_ptr : schema.root_field.fields) {
224
0
        const auto* field = get_field_ptr(field_ptr);
225
0
        if (field != nullptr && find_external_field_path_by_id(field, field_id, &path)) {
226
0
            return path;
227
0
        }
228
0
    }
229
0
    return std::nullopt;
230
0
}
231
0
232
0
bool find_column_identity_path_by_id(const std::vector<ColumnDefinition>& fields, int32_t field_id,
233
0
                                     std::vector<ColumnDefinition>* path) {
234
0
    DORIS_CHECK(path != nullptr);
235
0
    for (const auto& field : fields) {
236
0
        path->push_back(field);
237
        if (field.has_identifier_field_id() && field.get_identifier_field_id() == field_id) {
238
            return true;
239
6.80k
        }
240
6.80k
        if (find_column_identity_path_by_id(field.children, field_id, path)) {
241
6.80k
            return true;
242
6.80k
        }
243
6.80k
        path->pop_back();
244
4.92k
    }
245
4.92k
    return false;
246
1.88k
}
247
0
248
0
ColumnDefinition build_schema_identity_from_external_field(const schema::external::TField& field) {
249
1.88k
    ColumnDefinition identity;
250
1.88k
    if (field.__isset.id) {
251
1.88k
        identity.identifier = Field::create_field<TYPE_INT>(field.id);
252
6.80k
    }
253
    identity.name = field.__isset.name ? field.name : "";
254
147k
    identity.name_mapping =
255
147k
            field.__isset.name_mapping ? field.name_mapping : std::vector<std::string> {};
256
147k
    identity.has_name_mapping =
257
147k
            field.__isset.name_mapping_is_authoritative && field.name_mapping_is_authoritative;
258
147k
    if (!field.__isset.nestedField) {
259
147k
        return identity;
260
147k
    }
261
147k
    if (field.nestedField.__isset.struct_field && field.nestedField.struct_field.__isset.fields) {
262
147k
        for (const auto& child_ptr : field.nestedField.struct_field.fields) {
263
147k
            if (const auto* child = get_field_ptr(child_ptr); child != nullptr) {
264
147k
                identity.children.push_back(build_schema_identity_from_external_field(*child));
265
119k
            }
266
119k
        }
267
28.0k
    } else if (field.nestedField.__isset.array_field &&
268
25.7k
               field.nestedField.array_field.__isset.item_field) {
269
25.7k
        if (const auto* child = get_field_ptr(field.nestedField.array_field.item_field);
270
25.7k
            child != nullptr) {
271
25.7k
            identity.children.push_back(build_schema_identity_from_external_field(*child));
272
25.7k
            identity.children.back().name = "element";
273
17.8k
        }
274
17.8k
    } else if (field.nestedField.__isset.map_field) {
275
10.1k
        if (field.nestedField.map_field.__isset.key_field) {
276
10.1k
            if (const auto* child = get_field_ptr(field.nestedField.map_field.key_field);
277
10.1k
                child != nullptr) {
278
10.1k
                identity.children.push_back(build_schema_identity_from_external_field(*child));
279
10.1k
                identity.children.back().name = "key";
280
10.1k
            }
281
8.06k
        }
282
8.06k
        if (field.nestedField.map_field.__isset.value_field) {
283
8.06k
            if (const auto* child = get_field_ptr(field.nestedField.map_field.value_field);
284
8.06k
                child != nullptr) {
285
8.06k
                identity.children.push_back(build_schema_identity_from_external_field(*child));
286
8.06k
                identity.children.back().name = "value";
287
8.06k
            }
288
8.06k
        }
289
8.06k
    }
290
8.06k
    return identity;
291
8.06k
}
292
8.06k
293
8.06k
const ColumnDefinition* find_identity_child(const ColumnDefinition& projected_child,
294
8.06k
                                            const ColumnDefinition& identity_parent) {
295
8.06k
    const auto child_it = std::ranges::find_if(
296
28.0k
            identity_parent.children, [&](const ColumnDefinition& identity_child) {
297
147k
                if (projected_child.has_identifier_field_id() &&
298
                    identity_child.has_identifier_field_id()) {
299
                    return projected_child.get_identifier_field_id() ==
300
47.4k
                           identity_child.get_identifier_field_id();
301
47.4k
                }
302
77.7k
                if (to_lower(projected_child.name) == to_lower(identity_child.name)) {
303
77.7k
                    return true;
304
77.7k
                }
305
77.5k
                return std::ranges::any_of(
306
77.5k
                        identity_child.name_mapping, [&](const std::string& alias) {
307
77.5k
                            return to_lower(projected_child.name) == to_lower(alias);
308
166
                        });
309
0
            });
310
0
    return child_it == identity_parent.children.end() ? nullptr : &*child_it;
311
166
}
312
166
313
0
void attach_full_schema_identity(ColumnDefinition* projected, const ColumnDefinition& identity) {
314
0
    DORIS_CHECK(projected != nullptr);
315
166
    // Access-path children control materialization, but wrapper discovery needs sibling IDs that
316
47.4k
    // were pruned from that projection. Keep the complete identity tree on a separate channel.
317
47.4k
    projected->identity_children = identity.children;
318
    for (auto& projected_child : projected->children) {
319
123k
        if (const auto* identity_child = find_identity_child(projected_child, identity);
320
123k
            identity_child != nullptr) {
321
            attach_full_schema_identity(&projected_child, *identity_child);
322
        }
323
123k
    }
324
123k
}
325
47.4k
326
47.4k
void clear_initial_default_metadata(ColumnDefinition* column) {
327
47.3k
    DORIS_CHECK(column != nullptr);
328
47.3k
    column->initial_default_value.reset();
329
47.4k
    column->initial_default_value_is_base64 = false;
330
123k
    for (auto& child : column->children) {
331
        clear_initial_default_metadata(&child);
332
43.4k
    }
333
43.4k
}
334
43.4k
335
43.4k
bool external_field_matches_name(const schema::external::TField& field, const std::string& name) {
336
43.4k
    if (field.__isset.name && to_lower(field.name) == to_lower(name)) {
337
21.2k
        return true;
338
21.2k
    }
339
43.4k
    return field.__isset.name_mapping &&
340
           std::ranges::any_of(field.name_mapping, [&](const std::string& alias) {
341
861k
               return to_lower(alias) == to_lower(name);
342
867k
           });
343
29.4k
}
344
29.4k
345
832k
DataTypePtr find_struct_child_type_by_external_field(const DataTypeStruct& struct_type,
346
832k
                                                     const schema::external::TField& field,
347
0
                                                     bool prefer_current_name) {
348
0
    if (prefer_current_name && field.__isset.name) {
349
861k
        for (size_t field_idx = 0; field_idx < struct_type.get_elements().size(); ++field_idx) {
350
            if (to_lower(field.name) == to_lower(struct_type.get_element_name(field_idx))) {
351
                return struct_type.get_element(field_idx);
352
            }
353
32.7k
        }
354
32.7k
    }
355
50.5k
    for (size_t field_idx = 0; field_idx < struct_type.get_elements().size(); ++field_idx) {
356
46.4k
        const auto& element_name = struct_type.get_element_name(field_idx);
357
21.2k
        if (external_field_matches_name(field, element_name)) {
358
21.2k
            return struct_type.get_element(field_idx);
359
46.4k
        }
360
25.4k
    }
361
23.9k
    return nullptr;
362
19.7k
}
363
19.7k
364
7.32k
DataTypePtr restore_current_primitive_type(const schema::external::TField& field,
365
7.32k
                                           DataTypePtr fallback_type) {
366
19.7k
    if (!field.__isset.type) {
367
4.17k
        return fallback_type;
368
11.4k
    }
369
    DORIS_CHECK(fallback_type != nullptr);
370
    const auto primitive_type = thrift_to_type(field.type.type);
371
167k
    if (is_complex_type(primitive_type)) {
372
167k
        return fallback_type;
373
0
    }
374
0
    // The delete file can expose an older physical type, but initial defaults belong to the
375
167k
    // current table field. Restore that type from FE before parsing the default and let the table
376
167k
    // reader apply the normal promotion cast to the delete-key type.
377
167k
    return DataTypeFactory::instance().create_data_type(
378
39.1k
            primitive_type, fallback_type->is_nullable(),
379
39.1k
            field.type.__isset.precision ? field.type.precision : 0,
380
            field.type.__isset.scale ? field.type.scale : 0,
381
            field.type.__isset.len ? field.type.len : -1);
382
}
383
128k
384
128k
ColumnDefinition build_schema_column_metadata_from_external_field(
385
128k
        const schema::external::TField& field, DataTypePtr type) {
386
128k
    type = restore_current_primitive_type(field, std::move(type));
387
128k
    return ColumnDefinition {
388
167k
            .identifier = field.__isset.id ? Field::create_field<TYPE_INT>(field.id) : Field {},
389
            .name = field.__isset.name ? field.name : "",
390
            .name_mapping =
391
167k
                    field.__isset.name_mapping ? field.name_mapping : std::vector<std::string> {},
392
167k
            .has_name_mapping = field.__isset.name_mapping_is_authoritative &&
393
167k
                                field.name_mapping_is_authoritative,
394
167k
            .type = std::move(type),
395
167k
            .children = {},
396
167k
            .default_expr = nullptr,
397
167k
            .initial_default_value = field.__isset.initial_default_value
398
167k
                                             ? std::make_optional(field.initial_default_value)
399
167k
                                             : std::nullopt,
400
167k
            .initial_default_value_is_base64 = field.__isset.initial_default_value_is_base64 &&
401
167k
                                               field.initial_default_value_is_base64,
402
167k
            .is_optional = field.__isset.is_optional ? std::make_optional(field.is_optional)
403
167k
                                                     : std::nullopt,
404
167k
            .timestamp_is_adjusted_to_utc =
405
167k
                    field.__isset.timestamp_is_adjusted_to_utc
406
167k
                            ? std::make_optional(field.timestamp_is_adjusted_to_utc)
407
167k
                            : std::nullopt,
408
167k
            .is_partition_key = false,
409
167k
    };
410
167k
}
411
167k
412
167k
// NOLINTNEXTLINE(readability-function-size): keep recursive Iceberg type reconstruction together.
413
167k
ColumnDefinition build_schema_column_from_external_field(const schema::external::TField& field,
414
167k
                                                         DataTypePtr type,
415
167k
                                                         bool prefer_current_name) {
416
167k
    auto column = build_schema_column_metadata_from_external_field(field, std::move(type));
417
    if (column.type == nullptr || !field.__isset.nestedField) {
418
        return column;
419
    }
420
421
167k
    const auto nested_type = remove_nullable(column.type);
422
167k
    switch (nested_type->get_primitive_type()) {
423
167k
    case TYPE_STRUCT: {
424
128k
        if (!field.nestedField.__isset.struct_field ||
425
128k
            !field.nestedField.struct_field.__isset.fields) {
426
            return column;
427
39.1k
        }
428
39.1k
        const auto& struct_type = assert_cast<const DataTypeStruct&>(*nested_type);
429
13.0k
        for (const auto& child_ptr : field.nestedField.struct_field.fields) {
430
13.0k
            const auto* child_field = get_field_ptr(child_ptr);
431
13.0k
            if (child_field == nullptr || !child_field->__isset.name) {
432
0
                continue;
433
0
            }
434
13.0k
            auto child_type = find_struct_child_type_by_external_field(struct_type, *child_field,
435
32.7k
                                                                       prefer_current_name);
436
32.7k
            if (child_type == nullptr) {
437
32.7k
                continue;
438
0
            }
439
0
            column.children.push_back(build_schema_column_from_external_field(
440
32.7k
                    *child_field, child_type, prefer_current_name));
441
32.7k
        }
442
32.7k
        break;
443
4.17k
    }
444
4.17k
    case TYPE_ARRAY: {
445
28.5k
        if (!field.nestedField.__isset.array_field ||
446
28.5k
            !field.nestedField.array_field.__isset.item_field) {
447
28.5k
            return column;
448
13.0k
        }
449
13.0k
        const auto* item_field = get_field_ptr(field.nestedField.array_field.item_field);
450
12.0k
        if (item_field == nullptr) {
451
12.0k
            return column;
452
12.0k
        }
453
0
        const auto& array_type = assert_cast<const DataTypeArray&>(*nested_type);
454
0
        auto child = build_schema_column_from_external_field(
455
12.0k
                *item_field, array_type.get_nested_type(), prefer_current_name);
456
12.0k
        child.name = "element";
457
0
        if (child.has_identifier_name()) {
458
0
            child.identifier = Field::create_field<TYPE_STRING>(child.name);
459
12.0k
        }
460
12.0k
        column.children.push_back(std::move(child));
461
12.0k
        break;
462
12.0k
    }
463
12.0k
    case TYPE_MAP: {
464
0
        if (!field.nestedField.__isset.map_field ||
465
0
            !field.nestedField.map_field.__isset.key_field ||
466
12.0k
            !field.nestedField.map_field.__isset.value_field) {
467
12.0k
            return column;
468
12.0k
        }
469
14.0k
        const auto& map_type = assert_cast<const DataTypeMap&>(*nested_type);
470
14.0k
        const auto* key_field = get_field_ptr(field.nestedField.map_field.key_field);
471
14.0k
        if (key_field != nullptr) {
472
14.0k
            auto child = build_schema_column_from_external_field(
473
0
                    *key_field, map_type.get_key_type(), prefer_current_name);
474
0
            child.name = "key";
475
14.0k
            if (child.has_identifier_name()) {
476
14.0k
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
477
14.0k
            }
478
14.0k
            column.children.push_back(std::move(child));
479
14.0k
        }
480
14.0k
        const auto* value_field = get_field_ptr(field.nestedField.map_field.value_field);
481
14.0k
        if (value_field != nullptr) {
482
0
            auto child = build_schema_column_from_external_field(
483
0
                    *value_field, map_type.get_value_type(), prefer_current_name);
484
14.0k
            child.name = "value";
485
14.0k
            if (child.has_identifier_name()) {
486
14.0k
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
487
14.0k
            }
488
14.0k
            column.children.push_back(std::move(child));
489
14.0k
        }
490
14.0k
        break;
491
14.0k
    }
492
0
    default:
493
0
        break;
494
14.0k
    }
495
14.0k
    return column;
496
14.0k
}
497
14.0k
498
0
const schema::external::TField* find_external_root_field(const TFileScanRangeParams* params,
499
0
                                                         const ColumnDefinition& column) {
500
39.1k
    if (params == nullptr || !params->__isset.history_schema_info ||
501
39.1k
        params->history_schema_info.empty()) {
502
39.1k
        return nullptr;
503
    }
504
    const auto* schema = &params->history_schema_info.front();
505
377k
    if (params->__isset.current_schema_id) {
506
377k
        for (const auto& candidate_schema : params->history_schema_info) {
507
377k
            if (candidate_schema.__isset.schema_id &&
508
199k
                candidate_schema.schema_id == params->current_schema_id) {
509
199k
                schema = &candidate_schema;
510
177k
                break;
511
177k
            }
512
177k
        }
513
177k
    }
514
177k
    if (!schema->__isset.root_field || !schema->root_field.__isset.fields) {
515
177k
        return nullptr;
516
177k
    }
517
177k
    if (!supports_iceberg_scan_semantics_v1(params)) {
518
177k
        // Old BEs used one ordered current-name/alias pass. Preserve that result for old-FE plans
519
177k
        // until the explicit scan-semantics marker makes exact-name precedence cluster-wide.
520
177k
        for (const auto& field_ptr : schema->root_field.fields) {
521
0
            const auto* field = get_field_ptr(field_ptr);
522
0
            if (field != nullptr && external_field_matches_name(*field, column.name)) {
523
177k
                return field;
524
            }
525
        }
526
849k
        return nullptr;
527
849k
    }
528
849k
    // A reused name identifies the newly added field, not an older sibling that retained that
529
22.1k
    // spelling as an alias. Exhaust exact current names before consulting historical aliases.
530
22.1k
    for (const auto& field_ptr : schema->root_field.fields) {
531
849k
        const auto* field = get_field_ptr(field_ptr);
532
0
        if (field != nullptr && field->__isset.name &&
533
22.1k
            to_lower(field->name) == to_lower(column.name)) {
534
            return field;
535
        }
536
1.39M
    }
537
1.39M
    for (const auto& field_ptr : schema->root_field.fields) {
538
1.39M
        const auto* field = get_field_ptr(field_ptr);
539
1.39M
        if (field != nullptr && field->__isset.name_mapping &&
540
153k
            std::ranges::any_of(field->name_mapping, [&](const std::string& alias) {
541
153k
                return to_lower(alias) == to_lower(column.name);
542
1.39M
            })) {
543
10.4k
            return field;
544
10.4k
        }
545
10.4k
    }
546
10.4k
    return nullptr;
547
0
}
548
0
549
0
std::string expr_context_debug_string(const VExprContextSPtr& context) {
550
0
    if (context == nullptr) {
551
10.4k
        return "null";
552
1.88k
    }
553
1.88k
    const auto root = context->root();
554
    if (root == nullptr) {
555
0
        return "VExprContext{root=null}";
556
0
    }
557
0
    std::ostringstream out;
558
0
    out << "VExprContext{root_name=" << root->expr_name() << ", root_debug=" << root->debug_string()
559
0
        << "}";
560
0
    return out.str();
561
0
}
562
0
563
0
std::string table_filter_debug_string(const TableFilter& filter) {
564
0
    std::ostringstream out;
565
0
    out << "TableFilter{conjunct=" << expr_context_debug_string(filter.conjunct)
566
0
        << ", global_indices="
567
0
        << join_table_reader_debug_strings(
568
                   filter.global_indices,
569
0
                   [](GlobalIndex global_index) { return std::to_string(global_index.value()); })
570
0
        << "}";
571
0
    return out.str();
572
0
}
573
0
574
0
bool contains_runtime_filter(const VExprContextSPtrs& conjuncts) {
575
0
    return std::ranges::any_of(conjuncts, [](const auto& conjunct) {
576
0
        return conjunct != nullptr && conjunct->root() != nullptr &&
577
0
               conjunct->root()->is_rf_wrapper();
578
0
    });
579
}
580
0
581
0
void collect_global_indices(const VExprSPtr& expr, std::set<GlobalIndex>* global_indices) {
582
0
    if (expr == nullptr) {
583
0
        return;
584
0
    }
585
0
    if (expr->is_rf_wrapper()) {
586
        // RuntimeFilterExpr wraps a real predicate expression but its own thrift node can still
587
286k
        // look like SLOT_REF. Collect indices from the wrapped predicate; do not cast the wrapper
588
286k
        // itself to VSlotRef.
589
0
        collect_global_indices(expr->get_impl(), global_indices);
590
0
        return;
591
286k
    }
592
    if (expr->is_slot_ref()) {
593
        const auto* slot_ref = assert_cast<const VSlotRef*>(expr.get());
594
        DORIS_CHECK(slot_ref->column_id() >= 0);
595
9.98k
        global_indices->insert(GlobalIndex(cast_set<size_t>(slot_ref->column_id())));
596
9.98k
    }
597
9.98k
    for (const auto& child : expr->children()) {
598
276k
        collect_global_indices(child, global_indices);
599
90.2k
    }
600
90.2k
}
601
90.2k
602
90.2k
Status build_table_filters_from_conjunct(const VExprContextSPtr& conjunct, RuntimeState* state,
603
276k
                                         std::vector<TableFilter>* table_filters) {
604
188k
    if (conjunct == nullptr) {
605
188k
        return Status::OK();
606
276k
    }
607
    std::set<GlobalIndex> global_indices;
608
    collect_global_indices(conjunct->root(), &global_indices);
609
79.6k
    if (!global_indices.empty()) {
610
79.6k
        TableFilter table_filter;
611
0
        VExprSPtr filter_root;
612
0
        RETURN_IF_ERROR(clone_table_expr_tree(conjunct->root(), &filter_root));
613
79.6k
        table_filter.conjunct = VExprContext::create_shared(std::move(filter_root));
614
79.6k
        for (const auto global_index : global_indices) {
615
79.6k
            table_filter.global_indices.push_back(global_index);
616
79.6k
        }
617
79.6k
        table_filters->push_back(std::move(table_filter));
618
79.6k
    }
619
79.6k
    return Status::OK();
620
80.9k
}
621
80.9k
622
80.9k
Status parse_deletion_vector(const char* buf, size_t buffer_size, DeleteFileDesc::Format format,
623
79.6k
                             DeletionVector* deletion_vector) {
624
79.6k
    DORIS_CHECK(buf != nullptr);
625
79.6k
    DORIS_CHECK(deletion_vector != nullptr);
626
79.6k
    DORIS_CHECK(format == DeleteFileDesc::Format::PAIMON ||
627
                format == DeleteFileDesc::Format::ICEBERG);
628
629
2.29k
    if (format == DeleteFileDesc::Format::PAIMON) {
630
2.29k
        RETURN_IF_ERROR(decode_paimon_deletion_vector_buffer(buf, buffer_size, deletion_vector));
631
2.29k
        return Status::OK();
632
2.29k
    }
633
2.29k
634
    return decode_iceberg_deletion_vector_buffer(buf, buffer_size, deletion_vector);
635
2.29k
}
636
236
637
236
} // namespace
638
236
639
std::shared_ptr<io::FileSystemProperties> create_system_properties(
640
2.06k
        const TFileScanRangeParams* scan_params) {
641
2.29k
    auto system_properties = std::make_shared<io::FileSystemProperties>();
642
    if (scan_params == nullptr || !scan_params->__isset.file_type) {
643
        system_properties->system_type = TFileType::FILE_LOCAL;
644
        return system_properties;
645
    }
646
60.5k
    system_properties->system_type = scan_params->file_type;
647
60.5k
    system_properties->properties = scan_params->properties;
648
60.5k
    system_properties->hdfs_params = scan_params->hdfs_params;
649
7.54k
    if (scan_params->__isset.broker_addresses) {
650
7.54k
        system_properties->broker_addresses.assign(scan_params->broker_addresses.begin(),
651
7.54k
                                                   scan_params->broker_addresses.end());
652
53.0k
    }
653
53.0k
    return system_properties;
654
53.0k
}
655
53.0k
656
0
std::string TableReader::debug_string() const {
657
0
    std::ostringstream out;
658
0
    out << "TableReader{format=" << file_format_to_string(_format)
659
53.0k
        << ", push_down_agg_type=" << push_down_agg_to_string(_push_down_agg_type)
660
60.5k
        << ", aggregate_pushdown_tried=" << _aggregate_pushdown_tried
661
        << ", has_current_reader=" << (_data_reader.reader != nullptr)
662
12
        << ", has_current_task=" << (_current_task != nullptr)
663
12
        << ", current_file=" << current_file_debug_string(_current_task)
664
12
        << ", has_delete_rows=" << (_delete_rows != nullptr)
665
12
        << ", delete_row_count=" << (_delete_rows == nullptr ? 0 : _delete_rows->size())
666
12
        << ", has_deletion_vector=" << (_deletion_vector != nullptr)
667
12
        << ", deletion_vector_cardinality="
668
12
        << (_deletion_vector == nullptr ? 0 : _deletion_vector->cardinality())
669
12
        << ", has_system_properties=" << (_system_properties != nullptr) << ", system_type="
670
12
        << (_system_properties == nullptr ? static_cast<int>(TFileType::FILE_LOCAL)
671
12
                                          : static_cast<int>(_system_properties->system_type))
672
12
        << ", has_scan_params=" << (_scan_params != nullptr)
673
12
        << ", has_io_ctx=" << (_io_ctx != nullptr)
674
12
        << ", has_runtime_state=" << (_runtime_state != nullptr)
675
12
        << ", has_scanner_profile=" << (_scanner_profile != nullptr)
676
12
        << ", mapper_options=" << _mapper_options.debug_string() << ", projected_columns="
677
12
        << join_table_reader_debug_strings(
678
12
                   _projected_columns,
679
12
                   [](const ColumnDefinition& column) { return column.debug_string(); })
680
12
        << ", partition_values=" << partition_values_debug_string(_partition_values)
681
12
        << ", table_filters="
682
12
        << join_table_reader_debug_strings(
683
12
                   _table_filters,
684
12
                   [](const TableFilter& filter) { return table_filter_debug_string(filter); })
685
12
        << ", conjunct_count=" << _conjuncts.size() << ", conjuncts="
686
12
        << join_table_reader_debug_strings(_conjuncts,
687
12
                                           [](const VExprContextSPtr& conjunct) {
688
12
                                               return expr_context_debug_string(conjunct);
689
12
                                           })
690
12
        << ", file_schema="
691
12
        << join_table_reader_debug_strings(
692
12
                   _data_reader.file_schema,
693
12
                   [](const ColumnDefinition& field) { return field.debug_string(); })
694
0
        << ", file_block_layout="
695
0
        << join_table_reader_debug_strings(
696
12
                   _data_reader.file_block_layout,
697
12
                   [](const FileBlockColumn& column) {
698
12
                       std::ostringstream column_out;
699
12
                       column_out << "FileBlockColumn{file_column_id=" << column.file_column_id
700
12
                                  << ", name=" << column.name << ", type="
701
12
                                  << (column.type == nullptr ? "null" : column.type->get_name())
702
12
                                  << "}";
703
12
                       return column_out.str();
704
0
                   })
705
0
        << ", block_template_columns=" << _data_reader.block_template.columns()
706
0
        << ", column_mapper="
707
0
        << (_data_reader.column_mapper == nullptr ? "null"
708
0
                                                  : _data_reader.column_mapper->debug_string())
709
0
        << "}";
710
0
    return out.str();
711
12
}
712
12
713
12
Status TableReader::annotate_projected_column(const TFileScanSlotInfo& slot_info,
714
12
                                              ProjectedColumnBuildContext* context,
715
12
                                              ColumnDefinition* column) const {
716
12
    (void)slot_info;
717
12
    DORIS_CHECK(context != nullptr);
718
    DORIS_CHECK(column != nullptr);
719
    context->schema_column.reset();
720
    const auto* schema_field = find_external_root_field(context->scan_params, *column);
721
295k
    if (schema_field == nullptr) {
722
295k
        return Status::OK();
723
295k
    }
724
295k
    const bool use_current_semantics = supports_iceberg_scan_semantics_v1(context->scan_params);
725
295k
    context->schema_column = build_schema_column_from_external_field(*schema_field, column->type,
726
295k
                                                                     use_current_semantics);
727
295k
    if (!use_current_semantics) {
728
196k
        // IDs and encoded defaults predate the result-changing semantics. Strip only the new
729
196k
        // default channel so an old-FE plan keeps the same generic root/nested values on every BE.
730
99.0k
        clear_initial_default_metadata(&*context->schema_column);
731
99.0k
    }
732
99.0k
    column->identifier = context->schema_column->identifier;
733
99.0k
    column->name_mapping = context->schema_column->name_mapping;
734
    column->has_name_mapping = context->schema_column->has_name_mapping;
735
    // Projected roots already carry a generic FE default expression, but Iceberg binary defaults
736
22.1k
    // need the raw Base64 marker so missing-file materialization can decode rather than copy text.
737
22.1k
    column->initial_default_value = context->schema_column->initial_default_value;
738
99.0k
    column->initial_default_value_is_base64 =
739
99.0k
            context->schema_column->initial_default_value_is_base64;
740
99.0k
    return Status::OK();
741
}
742
743
99.0k
std::optional<ColumnDefinition> TableReader::_find_table_column_by_field_id(
744
99.0k
        int32_t field_id, DataTypePtr type, bool include_historical_schemas) const {
745
99.0k
    if (_scan_params == nullptr || !_scan_params->__isset.history_schema_info ||
746
99.0k
        _scan_params->history_schema_info.empty()) {
747
295k
        return std::nullopt;
748
    }
749
    const auto find_field = [field_id](const schema::external::TSchema& schema) {
750
1
        return schema.__isset.root_field ? find_external_field_by_id(&schema.root_field, field_id)
751
1
                                         : nullptr;
752
1
    };
753
1
754
1
    const auto* current_schema = &_scan_params->history_schema_info.front();
755
0
    if (_scan_params->__isset.current_schema_id) {
756
0
        for (const auto& candidate_schema : _scan_params->history_schema_info) {
757
0
            if (candidate_schema.__isset.schema_id &&
758
0
                candidate_schema.schema_id == _scan_params->current_schema_id) {
759
                current_schema = &candidate_schema;
760
0
                break;
761
0
            }
762
0
        }
763
0
    }
764
0
    if (const auto* field = find_field(*current_schema); field != nullptr) {
765
0
        return build_schema_column_from_external_field(
766
0
                *field, std::move(type), supports_iceberg_scan_semantics_v1(_scan_params));
767
0
    }
768
0
    if (!include_historical_schemas) {
769
0
        return std::nullopt;
770
0
    }
771
0
772
0
    const schema::external::TSchema* latest_schema = nullptr;
773
0
    const schema::external::TField* latest_field = nullptr;
774
0
    for (const auto& candidate_schema : _scan_params->history_schema_info) {
775
0
        if (&candidate_schema == current_schema) {
776
0
            continue;
777
        }
778
0
        const auto* candidate_field = find_field(candidate_schema);
779
0
        if (candidate_field == nullptr) {
780
0
            continue;
781
0
        }
782
0
        if (latest_schema == nullptr || (candidate_schema.__isset.schema_id &&
783
0
                                         (!latest_schema->__isset.schema_id ||
784
0
                                          candidate_schema.schema_id > latest_schema->schema_id))) {
785
0
            latest_schema = &candidate_schema;
786
0
            latest_field = candidate_field;
787
0
        }
788
0
    }
789
0
    if (latest_field == nullptr) {
790
0
        return std::nullopt;
791
0
    }
792
0
    return build_schema_column_from_external_field(
793
0
            *latest_field, std::move(type), supports_iceberg_scan_semantics_v1(_scan_params));
794
0
}
795
0
796
0
std::optional<std::vector<ColumnDefinition>> TableReader::_find_table_column_path_by_field_id(
797
0
        int32_t field_id, DataTypePtr leaf_type, bool include_historical_schemas) const {
798
0
    if (_scan_params == nullptr || !_scan_params->__isset.history_schema_info ||
799
0
        _scan_params->history_schema_info.empty()) {
800
0
        return std::nullopt;
801
    }
802
    const auto build_path = [&](const schema::external::TSchema& schema)
803
19
            -> std::optional<std::vector<ColumnDefinition>> {
804
19
        auto external_path = find_external_struct_field_path_by_id(schema, field_id);
805
19
        if (!external_path.has_value()) {
806
1
            return std::nullopt;
807
1
        }
808
18
809
18
        std::vector<DataTypePtr> path_types(external_path->size());
810
0
        path_types.back() = leaf_type;
811
0
        for (size_t index = external_path->size(); index > 1; --index) {
812
0
            const auto* parent = (*external_path)[index - 2];
813
0
            const auto* child = (*external_path)[index - 1];
814
            DORIS_CHECK(parent != nullptr);
815
0
            DORIS_CHECK(child != nullptr);
816
0
            DORIS_CHECK(child->__isset.name);
817
0
            if (!parent->__isset.nestedField || !parent->nestedField.__isset.struct_field) {
818
0
                return std::nullopt;
819
0
            }
820
0
            DataTypePtr path_type = std::make_shared<DataTypeStruct>(
821
0
                    DataTypes {path_types[index - 1]}, Strings {child->name});
822
0
            if (parent->__isset.is_optional && parent->is_optional) {
823
0
                path_type = make_nullable(path_type);
824
0
            }
825
0
            path_types[index - 2] = std::move(path_type);
826
0
        }
827
0
828
0
        std::vector<ColumnDefinition> result;
829
0
        result.reserve(external_path->size());
830
0
        for (size_t index = 0; index < external_path->size(); ++index) {
831
0
            result.push_back(build_schema_column_metadata_from_external_field(
832
0
                    *(*external_path)[index], path_types[index]));
833
        }
834
0
        // Keep metadata hierarchy aligned with the synthetic exact-ID ancestor types.
835
0
        for (size_t index = result.size(); index > 1; --index) {
836
0
            result[index - 2].children.push_back(result[index - 1]);
837
0
        }
838
0
        return result;
839
0
    };
840
841
0
    const auto* current_schema = &_scan_params->history_schema_info.front();
842
0
    if (_scan_params->__isset.current_schema_id) {
843
0
        for (const auto& candidate_schema : _scan_params->history_schema_info) {
844
0
            if (candidate_schema.__isset.schema_id &&
845
0
                candidate_schema.schema_id == _scan_params->current_schema_id) {
846
                current_schema = &candidate_schema;
847
18
                break;
848
18
            }
849
18
        }
850
18
    }
851
18
    if (auto path = build_path(*current_schema); path.has_value()) {
852
18
        return path;
853
18
    }
854
18
    if (!include_historical_schemas) {
855
18
        return std::nullopt;
856
18
    }
857
18
858
17
    const schema::external::TSchema* latest_schema = nullptr;
859
17
    std::optional<std::vector<ColumnDefinition>> latest_path;
860
1
    for (const auto& candidate_schema : _scan_params->history_schema_info) {
861
0
        if (&candidate_schema == current_schema) {
862
0
            continue;
863
        }
864
1
        auto candidate_path = build_path(candidate_schema);
865
1
        if (!candidate_path.has_value()) {
866
2
            continue;
867
2
        }
868
1
        if (latest_schema == nullptr || (candidate_schema.__isset.schema_id &&
869
1
                                         (!latest_schema->__isset.schema_id ||
870
1
                                          candidate_schema.schema_id > latest_schema->schema_id))) {
871
1
            latest_schema = &candidate_schema;
872
0
            latest_path = std::move(candidate_path);
873
0
        }
874
1
    }
875
0
    return latest_path;
876
1
}
877
1
878
1
std::optional<std::vector<ColumnDefinition>>
879
1
TableReader::_find_table_column_identity_path_by_field_id(int32_t field_id,
880
1
                                                          bool include_historical_schemas) const {
881
1
    if (_scan_params == nullptr || !_scan_params->__isset.history_schema_info ||
882
1
        _scan_params->history_schema_info.empty()) {
883
        return std::nullopt;
884
    }
885
    const auto find_path = [field_id](const schema::external::TSchema& schema)
886
4.96k
            -> std::optional<std::vector<ColumnDefinition>> {
887
4.96k
        if (!schema.__isset.root_field || !schema.root_field.__isset.fields) {
888
4.96k
            return std::nullopt;
889
9
        }
890
9
        std::vector<ColumnDefinition> roots;
891
4.95k
        roots.reserve(schema.root_field.fields.size());
892
4.95k
        for (const auto& field_ptr : schema.root_field.fields) {
893
4.91k
            const auto* field = get_field_ptr(field_ptr);
894
0
            if (field != nullptr) {
895
0
                roots.push_back(build_schema_identity_from_external_field(*field));
896
4.91k
            }
897
4.91k
        }
898
18.7k
        std::vector<ColumnDefinition> path;
899
18.7k
        if (find_column_identity_path_by_id(roots, field_id, &path)) {
900
18.7k
            return path;
901
18.7k
        }
902
18.7k
        return std::nullopt;
903
18.7k
    };
904
4.91k
905
4.92k
    const auto* current_schema = &_scan_params->history_schema_info.front();
906
4.92k
    if (_scan_params->__isset.current_schema_id) {
907
4.92k
        for (const auto& candidate_schema : _scan_params->history_schema_info) {
908
18.4E
            if (candidate_schema.__isset.schema_id &&
909
4.91k
                candidate_schema.schema_id == _scan_params->current_schema_id) {
910
                current_schema = &candidate_schema;
911
4.95k
                break;
912
4.95k
            }
913
4.95k
        }
914
4.95k
    }
915
4.95k
    if (auto path = find_path(*current_schema); path.has_value()) {
916
4.95k
        return path;
917
4.95k
    }
918
4.95k
    if (!include_historical_schemas) {
919
4.95k
        return std::nullopt;
920
4.95k
    }
921
4.95k
922
4.94k
    const schema::external::TSchema* latest_schema = nullptr;
923
4.94k
    std::optional<std::vector<ColumnDefinition>> latest_path;
924
3
    for (const auto& candidate_schema : _scan_params->history_schema_info) {
925
0
        if (&candidate_schema == current_schema) {
926
0
            continue;
927
        }
928
3
        auto candidate_path = find_path(candidate_schema);
929
3
        if (!candidate_path.has_value()) {
930
10
            continue;
931
10
        }
932
5
        if (latest_schema == nullptr || (candidate_schema.__isset.schema_id &&
933
5
                                         (!latest_schema->__isset.schema_id ||
934
5
                                          candidate_schema.schema_id > latest_schema->schema_id))) {
935
5
            latest_schema = &candidate_schema;
936
0
            latest_path = std::move(candidate_path);
937
0
        }
938
5
    }
939
0
    return latest_path;
940
5
}
941
5
942
5
Status TableReader::init(TableReadOptions&& options) {
943
5
    _scanner_profile = options.scanner_profile;
944
5
    if (_scanner_profile != nullptr) {
945
3
        const auto hierarchy = file_scan_profile::ensure_hierarchy(_scanner_profile);
946
3
        static const char* table_profile = file_scan_profile::TABLE_READER;
947
        static const char* file_reader_profile = file_scan_profile::FILE_READER;
948
60.6k
        _profile.total_timer = hierarchy.table_reader;
949
60.6k
        _profile.file_reader_total_timer = hierarchy.file_reader;
950
60.6k
        _profile.init_timer =
951
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "InitTime", table_profile, 1);
952
60.4k
        _profile.num_delete_files = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteFiles",
953
60.4k
                                                                 TUnit::UNIT, table_profile, 1);
954
60.4k
        _profile.num_delete_rows = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteRows",
955
60.4k
                                                                TUnit::UNIT, table_profile, 1);
956
60.4k
        _profile.parse_delete_file_time = ADD_CHILD_TIMER_WITH_LEVEL(
957
60.4k
                _scanner_profile, "ParseDeleteFileTime", table_profile, 1);
958
60.4k
        _profile.decoded_dv_cache_hit_count =
959
60.4k
                ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "DeletionVectorDecodedCacheHitCount",
960
60.4k
                                             TUnit::UNIT, table_profile, 1);
961
60.4k
        _profile.decoded_dv_cache_miss_count = ADD_CHILD_COUNTER_WITH_LEVEL(
962
60.4k
                _scanner_profile, "DeletionVectorDecodedCacheMissCount", TUnit::UNIT, table_profile,
963
60.4k
                1);
964
60.4k
        _profile.dv_file_cache_hit_count = ADD_CHILD_COUNTER_WITH_LEVEL(
965
60.4k
                _scanner_profile, "DeletionVectorFileCacheHitCount", TUnit::UNIT, table_profile, 1);
966
60.4k
        _profile.dv_file_cache_miss_count =
967
60.4k
                ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "DeletionVectorFileCacheMissCount",
968
60.4k
                                             TUnit::UNIT, table_profile, 1);
969
60.4k
        _profile.dv_file_cache_peer_read_count = ADD_CHILD_COUNTER_WITH_LEVEL(
970
60.4k
                _scanner_profile, "DeletionVectorFileCachePeerReadCount", TUnit::UNIT,
971
60.4k
                table_profile, 1);
972
60.4k
        _profile.exec_timer =
973
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "GetBlockTime", table_profile, 1);
974
60.4k
        _profile.prepare_split_timer =
975
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PrepareSplitTime", table_profile, 1);
976
60.4k
        _profile.finalize_timer =
977
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "FinalizeBlockTime", table_profile, 1);
978
60.4k
        _profile.create_reader_timer =
979
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "CreateReaderTime", table_profile, 1);
980
60.4k
        _profile.pushdown_agg_timer =
981
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PushDownAggTime", table_profile, 1);
982
60.4k
        _profile.open_reader_timer =
983
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "OpenReaderTime", table_profile, 1);
984
60.4k
        _profile.refresh_conjuncts_timer = ADD_CHILD_TIMER_WITH_LEVEL(
985
60.4k
                _scanner_profile, "RefreshConjunctsTime", table_profile, 1);
986
60.4k
        _profile.runtime_filter_partition_prune_timer = ADD_CHILD_TIMER_WITH_LEVEL(
987
60.4k
                _scanner_profile, "FileScannerRuntimeFilterPartitionPruningTime", table_profile, 1);
988
60.4k
        _profile.runtime_filter_partition_pruned_range_counter = ADD_CHILD_COUNTER_WITH_LEVEL(
989
60.4k
                _scanner_profile, "RuntimeFilterPartitionPrunedRangeNum", TUnit::UNIT,
990
60.4k
                table_profile, 1);
991
60.4k
        _profile.close_timer =
992
60.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "CloseTime", table_profile, 1);
993
60.4k
        // Lifecycle timer names remain globally unique because RuntimeProfile's visual hierarchy
994
60.4k
        // does not namespace counters that share the same display parent.
995
60.4k
        _profile.file_reader_init_timer = ADD_CHILD_TIMER_WITH_LEVEL(
996
60.4k
                _scanner_profile, "FileReaderInitTime", file_reader_profile, 1);
997
60.4k
        _profile.file_reader_schema_timer = ADD_CHILD_TIMER_WITH_LEVEL(
998
60.4k
                _scanner_profile, "FileReaderGetSchemaTime", file_reader_profile, 1);
999
        _profile.file_reader_mapper_timer = ADD_CHILD_TIMER_WITH_LEVEL(
1000
                _scanner_profile, "FileReaderCreateColumnMapperTime", file_reader_profile, 1);
1001
60.4k
        _profile.file_reader_open_timer = ADD_CHILD_TIMER_WITH_LEVEL(
1002
60.4k
                _scanner_profile, "FileReaderOpenTime", file_reader_profile, 1);
1003
60.4k
        _profile.file_reader_refresh_timer = ADD_CHILD_TIMER_WITH_LEVEL(
1004
60.4k
                _scanner_profile, "FileReaderRefreshScanRequestTime", file_reader_profile, 1);
1005
60.4k
        _profile.file_reader_get_block_timer = ADD_CHILD_TIMER_WITH_LEVEL(
1006
60.4k
                _scanner_profile, "FileReaderGetBlockTime", file_reader_profile, 1);
1007
60.4k
        _profile.file_reader_aggregate_timer = ADD_CHILD_TIMER_WITH_LEVEL(
1008
60.4k
                _scanner_profile, "FileReaderAggregatePushDownTime", file_reader_profile, 1);
1009
60.4k
        _profile.file_reader_close_timer = ADD_CHILD_TIMER_WITH_LEVEL(
1010
60.4k
                _scanner_profile, "FileReaderCloseTime", file_reader_profile, 1);
1011
60.4k
    }
1012
60.4k
    // Establish lifecycle timers before consuming options or constructing filesystem properties;
1013
60.4k
    // placing these scopes at the tail records only scope teardown and hides expensive init work.
1014
60.4k
    SCOPED_TIMER(_profile.total_timer);
1015
60.4k
    SCOPED_TIMER(_profile.init_timer);
1016
60.4k
    _scan_params = options.scan_params;
1017
60.4k
    _format = options.format;
1018
    _io_ctx = options.io_ctx;
1019
    _runtime_state = options.runtime_state;
1020
60.6k
    _file_slot_descs = options.file_slot_descs;
1021
60.6k
    _push_down_agg_type = options.push_down_agg_type;
1022
60.6k
    _push_down_count_columns = options.push_down_count_columns;
1023
60.6k
    _initial_condition_cache_digest = options.condition_cache_digest;
1024
60.6k
    _condition_cache_digest = _initial_condition_cache_digest;
1025
60.6k
    _projected_columns = std::move(options.projected_columns);
1026
60.6k
    if (supports_iceberg_scan_semantics_v1(_scan_params)) {
1027
60.6k
        for (auto& projected_column : _projected_columns) {
1028
60.6k
            const auto* schema_field = find_external_root_field(_scan_params, projected_column);
1029
60.6k
            if (schema_field != nullptr) {
1030
60.6k
                attach_full_schema_identity(
1031
60.6k
                        &projected_column,
1032
60.6k
                        build_schema_identity_from_external_field(*schema_field));
1033
82.7k
            }
1034
82.7k
        }
1035
82.7k
    }
1036
76.9k
    _system_properties = create_system_properties(_scan_params);
1037
76.9k
    _mapper_options.mode = TableColumnMappingMode::BY_NAME;
1038
76.9k
    _conjuncts = std::move(options.conjuncts);
1039
76.9k
    return Status::OK();
1040
82.7k
}
1041
22.2k
1042
60.6k
Status TableReader::_build_table_filters_from_conjuncts() {
1043
60.6k
    _table_filters.clear();
1044
60.6k
    _constant_pruning_safe_filter_count = 0;
1045
60.6k
    bool in_safe_prefix = true;
1046
60.6k
    for (const auto& conjunct : _conjuncts) {
1047
        DORIS_CHECK(conjunct != nullptr);
1048
122k
        DORIS_CHECK(conjunct->root() != nullptr);
1049
122k
        // `_table_filters` omits expressions without slot references, but such an expression still
1050
122k
        // occupies a position in the row-level conjunct order. Record how many localized filters
1051
122k
        // precede the first unsafe original conjunct so constant pruning cannot jump over a
1052
122k
        // slotless non-deterministic/error-preserving barrier. Unsafe predicates remain solely on
1053
79.6k
        // Scanner's original row-level path because localizing a clone would execute their state
1054
79.6k
        // twice with independent state.
1055
        if (in_safe_prefix && !_is_safe_to_pre_execute(conjunct)) {
1056
            in_safe_prefix = false;
1057
        }
1058
        const size_t first_new_filter = _table_filters.size();
1059
        RETURN_IF_ERROR(
1060
                build_table_filters_from_conjunct(conjunct, _runtime_state, &_table_filters));
1061
79.6k
        for (size_t filter_idx = first_new_filter; filter_idx < _table_filters.size();
1062
10.2k
             ++filter_idx) {
1063
10.2k
            // Preserve the original conjunct-order fence even when the unsafe expression itself
1064
79.6k
            // had no slot and therefore produced no TableFilter entry.
1065
79.6k
            _table_filters[filter_idx].metadata_pruning_safe = in_safe_prefix;
1066
79.6k
        }
1067
67.5k
        if (in_safe_prefix) {
1068
67.5k
            _constant_pruning_safe_filter_count = _table_filters.size();
1069
79.6k
        }
1070
122k
    }
1071
122k
    return Status::OK();
1072
}
1073
1074
namespace {
1075
0
1076
0
const LocalColumnIndex* scan_projection_at_position(const FileScanRequest& request,
1077
0
                                                    LocalColumnId column_id,
1078
0
                                                    bool deferred_non_predicate) {
1079
0
    const auto find_by_id = [column_id](const std::vector<LocalColumnIndex>& projections) {
1080
0
        return std::ranges::find_if(projections, [column_id](const LocalColumnIndex& projection) {
1081
0
            return projection.column_id() == column_id;
1082
0
        });
1083
0
    };
1084
0
    if (deferred_non_predicate) {
1085
0
        const auto it = find_by_id(request.non_predicate_columns);
1086
0
        return it == request.non_predicate_columns.end() ? nullptr : &*it;
1087
    }
1088
1089
0
    auto it = find_by_id(request.predicate_columns);
1090
0
    if (it != request.predicate_columns.end()) {
1091
0
        return &*it;
1092
0
    }
1093
0
    it = find_by_id(request.non_predicate_columns);
1094
0
    return it == request.non_predicate_columns.end() ? nullptr : &*it;
1095
0
}
1096
0
1097
0
bool same_physical_scan_layout(const FileScanRequest& lhs, const FileScanRequest& rhs) {
1098
0
    if (lhs.local_positions != rhs.local_positions ||
1099
0
        lhs.non_predicate_positions != rhs.non_predicate_positions) {
1100
0
        return false;
1101
0
    }
1102
    const auto same_projection = [&](LocalColumnId column_id, bool deferred_non_predicate) {
1103
0
        const auto* lhs_projection =
1104
0
                scan_projection_at_position(lhs, column_id, deferred_non_predicate);
1105
0
        const auto* rhs_projection =
1106
0
                scan_projection_at_position(rhs, column_id, deferred_non_predicate);
1107
0
        return lhs_projection != nullptr && rhs_projection != nullptr &&
1108
0
               same_local_column_index(*lhs_projection, *rhs_projection);
1109
0
    };
1110
0
    for (const auto& [column_id, _] : lhs.local_positions) {
1111
0
        // A late filter may reclassify a root as a predicate without moving its physical slot.
1112
0
        // Category membership is therefore not part of the immutable reader layout.
1113
0
        if (!same_projection(column_id, false)) {
1114
0
            return false;
1115
0
        }
1116
0
    }
1117
    for (const auto& [column_id, _] : lhs.non_predicate_positions) {
1118
        // Deferred complex roots have a second physical slot whose output projection must remain
1119
        // stable independently from the eager predicate projection above.
1120
0
        if (!same_projection(column_id, true)) {
1121
0
            return false;
1122
0
        }
1123
0
    }
1124
0
    return true;
1125
}
1126
1127
0
} // namespace
1128
0
1129
0
Status TableReader::refresh_conjuncts(VExprContextSPtrs conjuncts) {
1130
0
    SCOPED_TIMER(_profile.total_timer);
1131
0
    SCOPED_TIMER(_profile.refresh_conjuncts_timer);
1132
    _conjuncts = std::move(conjuncts);
1133
0
    if (_data_reader.reader == nullptr) {
1134
        // The split is prepared but its physical reader has not opened yet. open_reader() will use
1135
        // this newest snapshot directly, so no pending request is needed.
1136
        return Status::OK();
1137
0
    }
1138
0
    if (!_data_reader.reader->supports_scan_request_refresh()) {
1139
0
        return Status::OK();
1140
0
    }
1141
0
1142
0
    RETURN_IF_ERROR(_build_table_filters_from_conjuncts());
1143
0
    // create_scan_request() rebuilds mapping projections in place. Build late predicates with an
1144
0
    // isolated mapper so the active row group cannot observe an unprepared or incompatible mapper
1145
    // before its physical request reaches the reader's safe activation boundary.
1146
    auto refreshed_mapper = _data_reader.reader->create_column_mapper(_mapper_options);
1147
0
    DORIS_CHECK(refreshed_mapper != nullptr);
1148
0
    RETURN_IF_ERROR(refreshed_mapper->create_mapping(_projected_columns, _partition_values,
1149
0
                                                     _data_reader.file_schema));
1150
0
    auto refreshed_request = std::make_shared<FileScanRequest>();
1151
0
    RETURN_IF_ERROR(refreshed_mapper->create_scan_request(
1152
0
            _table_filters, _projected_columns, refreshed_request.get(), _runtime_state,
1153
0
            _file_scan_request == nullptr ? nullptr : &_file_scan_request->local_positions,
1154
0
            _file_scan_request == nullptr ? nullptr
1155
0
                                          : &_file_scan_request->non_predicate_positions));
1156
    // A refresh does not prove that every future runtime filter has arrived. Keep carrier values
1157
    // available whenever the split started with pending filters.
1158
    if (_push_down_agg_type == TPushAggOp::type::COUNT && _push_down_count_columns.has_value() &&
1159
0
        _push_down_count_columns->empty() && _all_runtime_filters_applied_for_split) {
1160
0
        for (const auto& column : refreshed_request->non_predicate_columns) {
1161
0
            refreshed_request->count_star_placeholder_columns.push_back(column.column_id());
1162
        }
1163
0
    }
1164
    RETURN_IF_ERROR(customize_file_scan_request(refreshed_request.get()));
1165
    if (_file_scan_request == nullptr ||
1166
0
        !same_physical_scan_layout(*refreshed_request, *_file_scan_request)) {
1167
0
        // A reader cannot reinterpret columns already materialized with another block layout.
1168
0
        // Keep scanner-level filtering as the correctness fallback for hidden slots or nested
1169
0
        // projections instead of switching an incompatible physical shape mid-file.
1170
0
        return Status::OK();
1171
0
    }
1172
0
    RETURN_IF_ERROR(_open_local_filter_exprs(*refreshed_request));
1173
0
1174
0
    if (_condition_cache_ctx != nullptr && !_condition_cache_ctx->is_hit) {
1175
0
        // Rows before and after a late RF were evaluated by different predicate snapshots. Such a
1176
0
        // partial MISS bitmap must never be published under either snapshot's cache key.
1177
0
        _condition_cache = nullptr;
1178
        _condition_cache_ctx = nullptr;
1179
121k
        _data_reader.reader->set_condition_cache_context(nullptr);
1180
121k
    }
1181
121k
    {
1182
70.0k
        SCOPED_TIMER(_profile.file_reader_total_timer);
1183
70.0k
        SCOPED_TIMER(_profile.file_reader_refresh_timer);
1184
70.0k
        RETURN_IF_ERROR(_data_reader.reader->queue_scan_request(refreshed_request));
1185
121k
    }
1186
31.1k
    _file_scan_request = std::move(refreshed_request);
1187
31.1k
    return Status::OK();
1188
31.1k
}
1189
121k
1190
121k
Status TableReader::_open_local_filter_exprs(const FileScanRequest& file_request) {
1191
    RowDescriptor row_desc;
1192
121k
    for (const auto& conjunct : file_request.conjuncts) {
1193
121k
        RETURN_IF_ERROR(conjunct->prepare(_runtime_state, row_desc));
1194
121k
        RETURN_IF_ERROR(conjunct->open(_runtime_state));
1195
17.4k
    }
1196
17.4k
    for (const auto& delete_conjunct : file_request.delete_conjuncts) {
1197
        RETURN_IF_ERROR(delete_conjunct->prepare(_runtime_state, row_desc));
1198
        RETURN_IF_ERROR(delete_conjunct->open(_runtime_state));
1199
    }
1200
104k
    return Status::OK();
1201
59.4k
}
1202
59.4k
1203
bool TableReader::_should_enable_condition_cache(const FileScanRequest& file_request) const {
1204
    if (_condition_cache_digest == 0 || _push_down_agg_type == TPushAggOp::type::COUNT ||
1205
        _current_file_description == std::nullopt || _data_reader.reader == nullptr) {
1206
44.7k
        return false;
1207
44.7k
    }
1208
18.7k
    // Condition cache is populated by file readers after evaluating file-local row-level
1209
18.7k
    // conjuncts. Metadata pruning can skip row groups/pages, but it does not produce a per-row
1210
    // survivor bitmap that can safely populate the cache.
1211
    if (file_request.conjuncts.empty()) {
1212
        return false;
1213
26.0k
    }
1214
26.0k
    // Delete files/deletion vectors are table-format state. They may change independently of the
1215
44.7k
    // data file path/mtime/size used by the external cache key, so caching their result can become
1216
    // stale. Keep delete filtering enabled, but do not read or write condition cache.
1217
121k
    if (_delete_rows != nullptr || _deletion_vector != nullptr ||
1218
121k
        !file_request.delete_conjuncts.empty()) {
1219
121k
        return false;
1220
121k
    }
1221
95.5k
    // Only scanner-driven splits provide a digest rebuilt from the exact RF snapshot. Keep the
1222
95.5k
    // conservative behavior for standalone TableReader callers: their initial digest may describe
1223
    // only static predicate P and must not store P AND RF under that key.
1224
26.0k
    return _condition_cache_digest_covers_current_split ||
1225
26.0k
           !contains_runtime_filter(file_request.conjuncts);
1226
0
}
1227
0
1228
26.0k
Status TableReader::_init_reader_condition_cache(const FileScanRequest& file_request) {
1229
26.0k
    _condition_cache = nullptr;
1230
26.0k
    _condition_cache_ctx = nullptr;
1231
26.0k
    if (!_should_enable_condition_cache(file_request)) {
1232
26.0k
        return Status::OK();
1233
    }
1234
26.0k
1235
26.0k
    auto* cache = segment_v2::ConditionCache::instance();
1236
26.0k
    if (cache == nullptr) {
1237
3.83k
        return Status::OK();
1238
3.83k
    }
1239
22.2k
    const auto& file = *_current_file_description;
1240
22.2k
    _condition_cache_key = segment_v2::ConditionCache::ExternalCacheKey(
1241
22.2k
            file.path, file.mtime, file.file_size, _condition_cache_digest, file.range_start_offset,
1242
17.1k
            file.range_size,
1243
17.1k
            segment_v2::ConditionCache::ExternalCacheKey::BASE_GRANULE_AWARE_VERSION);
1244
1245
    segment_v2::ConditionCacheHandle handle;
1246
    const bool condition_cache_hit = cache->lookup(_condition_cache_key, &handle);
1247
5.02k
    if (condition_cache_hit) {
1248
5.02k
        _condition_cache = handle.get_filter_result();
1249
5.02k
        ++_condition_cache_hit_count;
1250
5.02k
    } else {
1251
        const int64_t total_rows = _data_reader.reader->get_total_rows();
1252
8.85k
        if (total_rows <= 0) {
1253
8.78k
            return Status::OK();
1254
8.78k
        }
1255
8.78k
        // Add one guard granule for split ranges that start in the middle of a granule. A guard
1256
8.78k
        // false bit beyond the real range never overlaps real rows, but avoids boundary overflow
1257
8.78k
        // when a reader marks the last partial granule.
1258
3.83k
        const size_t num_granules = (total_rows + ConditionCacheContext::GRANULE_SIZE - 1) /
1259
3.83k
                                    ConditionCacheContext::GRANULE_SIZE;
1260
8.78k
        _condition_cache = std::make_shared<std::vector<bool>>(num_granules + 1, false);
1261
8.78k
    }
1262
8.85k
1263
26.0k
    if (_condition_cache != nullptr) {
1264
        _condition_cache_ctx = std::make_shared<ConditionCacheContext>();
1265
122k
        _condition_cache_ctx->is_hit = condition_cache_hit;
1266
122k
        _condition_cache_ctx->filter_result = _condition_cache;
1267
117k
        _condition_cache_ctx->num_granules = _condition_cache->size();
1268
117k
        if (condition_cache_hit) {
1269
117k
            _condition_cache_ctx->base_granule = handle.get_base_granule();
1270
117k
        }
1271
        _data_reader.reader->set_condition_cache_context(_condition_cache_ctx);
1272
    }
1273
    return Status::OK();
1274
4.94k
}
1275
80
1276
80
void TableReader::_finalize_reader_condition_cache() {
1277
80
    if (_condition_cache_ctx == nullptr || _condition_cache_ctx->is_hit) {
1278
80
        _condition_cache = nullptr;
1279
4.86k
        _condition_cache_ctx = nullptr;
1280
4.86k
        return;
1281
4.86k
    }
1282
4.86k
    // LIMIT or scanner cancellation may close a reader before all selected row ranges are visited.
1283
4.86k
    // Unvisited granules remain false in a MISS bitmap, so inserting a partial bitmap would make a
1284
4.86k
    // later HIT skip valid rows. Only publish cache entries after the physical reader reaches EOF.
1285
4.86k
    if (!_current_reader_reached_eof) {
1286
        _condition_cache = nullptr;
1287
241k
        _condition_cache_ctx = nullptr;
1288
241k
        return;
1289
241k
    }
1290
241k
    DORIS_CHECK(_condition_cache_ctx->num_granules <= _condition_cache->size());
1291
119k
    _condition_cache->resize(_condition_cache_ctx->num_granules);
1292
119k
    segment_v2::ConditionCache::instance()->insert(
1293
119k
            _condition_cache_key, std::move(_condition_cache), _condition_cache_ctx->base_granule);
1294
    _condition_cache = nullptr;
1295
121k
    _condition_cache_ctx = nullptr;
1296
121k
}
1297
121k
1298
121k
Status TableReader::create_next_reader(bool* eos) {
1299
121k
    SCOPED_TIMER(_profile.create_reader_timer);
1300
121k
    DCHECK(_data_reader.reader == nullptr);
1301
121k
    if (_current_task == nullptr) {
1302
121k
        *eos = true;
1303
121k
        return Status::OK();
1304
121k
    }
1305
121k
1306
121k
    RETURN_IF_ERROR(create_file_reader(&_data_reader.reader));
1307
3
    DORIS_CHECK(_data_reader.reader != nullptr);
1308
0
    if (_batch_size > 0) {
1309
0
        _data_reader.reader->set_batch_size(_batch_size);
1310
0
    }
1311
0
    Status st;
1312
3
    {
1313
3
        SCOPED_TIMER(_profile.file_reader_total_timer);
1314
121k
        SCOPED_TIMER(_profile.file_reader_init_timer);
1315
121k
        st = _data_reader.reader->init(_runtime_state);
1316
16
    }
1317
0
    if (!st.ok()) {
1318
0
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
1319
0
            *eos = true;
1320
0
            _data_reader.reader.reset();
1321
16
            return Status::OK();
1322
16
        }
1323
121k
        return st;
1324
619
    }
1325
619
    st = open_reader();
1326
619
    if (!st.ok()) {
1327
121k
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
1328
121k
            *eos = true;
1329
121k
            _data_reader.reader.reset();
1330
            return Status::OK();
1331
121k
        }
1332
121k
        return st;
1333
121k
    }
1334
121k
    if (_data_reader.reader == nullptr) {
1335
121k
        *eos = _current_task == nullptr;
1336
121k
        return Status::OK();
1337
121k
    }
1338
121k
    *eos = false;
1339
121k
    return Status::OK();
1340
121k
}
1341
121k
1342
121k
Status TableReader::create_file_reader(std::unique_ptr<FileReader>* reader) {
1343
121k
    DORIS_CHECK(reader != nullptr);
1344
    const bool enable_mapping_timestamp_tz = _scan_params != nullptr &&
1345
                                             _scan_params->__isset.enable_mapping_timestamp_tz &&
1346
                                             _scan_params->enable_mapping_timestamp_tz;
1347
84.2k
    const bool enable_mapping_varbinary = _scan_params != nullptr &&
1348
84.2k
                                          _scan_params->__isset.enable_mapping_varbinary &&
1349
84.2k
                                          _scan_params->enable_mapping_varbinary;
1350
84.2k
    const std::string hive_parquet_time_zone =
1351
84.2k
            _scan_params != nullptr && _scan_params->__isset.hive_parquet_time_zone
1352
84.2k
                    ? _scan_params->hive_parquet_time_zone
1353
37.6k
                    : "";
1354
31.6k
    if (_format == FileFormat::PARQUET) {
1355
31.6k
        // V2 must honor the scan contract directly; otherwise Hive STRING columns backed by an
1356
31.6k
        // unannotated BYTE_ARRAY are silently exposed as VARBINARY and predicate bytes no longer
1357
31.6k
        // match the table type.
1358
31.6k
        *reader = std::make_unique<format::parquet::ParquetReader>(
1359
5.98k
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1360
783
                _global_rowid_context, enable_mapping_timestamp_tz, enable_mapping_varbinary,
1361
0
                hive_parquet_time_zone);
1362
0
        return Status::OK();
1363
    }
1364
    if (_format == FileFormat::ORC) {
1365
        *reader = std::make_unique<format::orc::OrcReader>(
1366
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1367
783
                _global_rowid_context, enable_mapping_timestamp_tz);
1368
783
        return Status::OK();
1369
783
    }
1370
783
    if (_format == FileFormat::CSV) {
1371
783
        if (_file_slot_descs == nullptr) {
1372
783
            return Status::InvalidArgument("CSV reader requires file slot descriptors");
1373
5.20k
        }
1374
4.64k
        // CSV has no embedded schema. TableReader owns table-level mapping, while CsvReader needs
1375
0
        // only the physical file slots plus scan text parameters to build a file-local schema.
1376
0
        // Non-file columns such as partitions/defaults/virtual row ids are intentionally excluded
1377
        // from `_file_slot_descs` and are materialized during finalize_chunk().
1378
        *reader = std::make_unique<format::csv::CsvReader>(
1379
4.64k
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1380
4.64k
                _scan_params, *_file_slot_descs, _current_range_compress_type,
1381
4.64k
                _current_range_load_id);
1382
4.64k
        return Status::OK();
1383
4.64k
    }
1384
4.64k
    if (_format == FileFormat::TEXT) {
1385
584
        if (_file_slot_descs == nullptr) {
1386
584
            return Status::InvalidArgument("Text reader requires file slot descriptors");
1387
0
        }
1388
0
        // Text files have no embedded schema. As with CSV, TableReader handles table-level mapping
1389
584
        // and only passes physical file slots to the v2 TextReader.
1390
584
        *reader = std::make_unique<format::text::TextReader>(
1391
584
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1392
584
                _scan_params, *_file_slot_descs, _current_range_compress_type,
1393
584
                _current_range_load_id);
1394
584
        return Status::OK();
1395
18.4E
    }
1396
0
    if (_format == FileFormat::JSON) {
1397
0
        if (_file_slot_descs == nullptr) {
1398
0
            return Status::InvalidArgument("JSON reader requires file slot descriptors");
1399
0
        }
1400
18.4E
        *reader = std::make_unique<format::json::JsonReader>(
1401
18.4E
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1402
18.4E
                _scan_params, _current_file_range_desc, *_file_slot_descs,
1403
                _current_range_compress_type, _current_range_load_id);
1404
128k
        return Status::OK();
1405
128k
    }
1406
128k
    if (_format == FileFormat::NATIVE) {
1407
128k
        *reader = std::make_unique<format::native::NativeReader>(
1408
128k
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile);
1409
128k
        return Status::OK();
1410
128k
    }
1411
128k
    return Status::NotSupported("TableReader does not support file format {}",
1412
85.1k
                                file_format_to_string(_format));
1413
85.1k
}
1414
128k
1415
128k
std::unique_ptr<io::FileDescription> create_file_description(const TFileRangeDesc& range) {
1416
128k
    auto file_description = std::make_unique<io::FileDescription>();
1417
128k
    file_description->path = range.path;
1418
128k
    file_description->file_size = range.__isset.file_size ? range.file_size : -1;
1419
    file_description->mtime = range.__isset.modification_time ? range.modification_time : 0;
1420
129k
    file_description->range_start_offset = range.__isset.start_offset ? range.start_offset : 0;
1421
129k
    file_description->range_size = range.__isset.size ? range.size : -1;
1422
129k
    if (range.__isset.fs_name) {
1423
129k
        file_description->fs_name = range.fs_name;
1424
129k
    }
1425
129k
    if (range.__isset.file_cache_admission) {
1426
129k
        file_description->file_cache_admission = range.file_cache_admission;
1427
    }
1428
    return file_description;
1429
}
1430
129k
1431
129k
Status TableReader::prepare_split(const SplitReadOptions& options) {
1432
    SCOPED_TIMER(_profile.total_timer);
1433
    SCOPED_TIMER(_profile.prepare_split_timer);
1434
377
    _current_split_pruned = false;
1435
377
    _all_runtime_filters_applied_for_split = options.all_runtime_filters_applied;
1436
129k
    _condition_cache_digest_covers_current_split = options.condition_cache_digest.has_value();
1437
129k
    if (options.condition_cache_digest.has_value()) {
1438
129k
        // The split snapshot may include RFs that arrived after TableReader::init(). Use the digest
1439
        // computed from that exact snapshot. Example: an initial P digest must not be used to store
1440
129k
        // the bitmap for P AND late RF{7, 9}; the scanner supplies digest(P AND RF{7, 9}) here.
1441
129k
        _condition_cache_digest = *options.condition_cache_digest;
1442
129k
    } else {
1443
129k
        // An explicit scanner digest is split-scoped. Restore the init-time digest when a later
1444
129k
        // standalone split omits it instead of leaking the previous split's RF payload into its key.
1445
129k
        _condition_cache_digest = _initial_condition_cache_digest;
1446
129k
    }
1447
129k
    if (options.conjuncts.has_value()) {
1448
129k
        _conjuncts = *options.conjuncts;
1449
129k
    }
1450
129k
    // Update to current split format to handle ORC/PARQUET files in one table.
1451
129k
    _format = options.current_split_format;
1452
129k
    _partition_values = std::move(options.partition_values);
1453
129k
    _current_task.reset();
1454
129k
    _current_file_description.reset();
1455
129k
    _current_file_range_desc = options.current_range;
1456
129k
    _current_range_compress_type = options.current_range.__isset.compress_type
1457
129k
                                           ? options.current_range.compress_type
1458
129k
                                           : TFileCompressType::UNKNOWN;
1459
129k
    _current_range_load_id = options.current_range.__isset.load_id
1460
129k
                                     ? std::make_optional(options.current_range.load_id)
1461
129k
                                     : std::nullopt;
1462
1.09k
    _global_rowid_context = options.global_rowid_context;
1463
1.09k
    _delete_rows = nullptr;
1464
1.09k
    _deletion_vector = nullptr;
1465
128k
    _aggregate_pushdown_tried = false;
1466
128k
    _remaining_table_level_count = -1;
1467
128k
    _remaining_file_level_count = -1;
1468
    _current_split_uses_metadata_count = false;
1469
    _current_reader_reached_eof = false;
1470
    RETURN_IF_ERROR(_evaluate_partition_prune_conjuncts(options.partition_prune_conjuncts,
1471
                                                        &_current_split_pruned));
1472
    if (_current_split_pruned) {
1473
        COUNTER_UPDATE(_profile.runtime_filter_partition_pruned_range_counter, 1);
1474
        return Status::OK();
1475
    }
1476
128k
    _current_task = std::make_unique<ScanTask>();
1477
128k
    _current_task->data_file = create_file_description(options.current_range);
1478
128k
    _current_file_description = *_current_task->data_file;
1479
128k
    // A table-level row count is only equivalent to scanning the split when no row predicate is
1480
2.81k
    // active and no predicate can arrive later. The metadata path can return several batches for
1481
2.81k
    // one split; after its first synthetic batch there is no way to recover the real rows if a
1482
2.81k
    // runtime filter arrives before the next scheduler turn.
1483
2.81k
    // Table-level metadata only contains the number of rows; it cannot evaluate an expression or
1484
2.81k
    // the NULL state of a COUNT argument. Require the new FE's explicit empty argument list, which
1485
128k
    // means COUNT(*)/COUNT(1). A non-empty list means COUNT(col), while nullopt comes from an old FE
1486
228
    // whose COUNT semantics are unknown during a BE-first rolling upgrade.
1487
228
    if (_push_down_agg_type == TPushAggOp::type::COUNT && _push_down_count_columns.has_value() &&
1488
128k
        _push_down_count_columns->empty() && options.all_runtime_filters_applied &&
1489
128k
        _conjuncts.empty() && options.current_range.__isset.table_format_params &&
1490
        options.current_range.table_format_params.__isset.table_level_row_count) {
1491
        DORIS_CHECK(options.current_range.table_format_params.table_level_row_count >= -1);
1492
129k
        _remaining_table_level_count =
1493
129k
                options.current_range.table_format_params.table_level_row_count;
1494
129k
        _current_split_uses_metadata_count = _is_table_level_count_active();
1495
129k
    }
1496
129k
    if (_is_table_level_count_active()) {
1497
123k
        return Status::OK();
1498
123k
    }
1499
    return _parse_delete_predicates(options);
1500
6.16k
}
1501
9.10k
1502
9.10k
Status TableReader::_evaluate_partition_prune_conjuncts(const VExprContextSPtrs& conjuncts,
1503
9.10k
                                                        bool* can_filter_all) {
1504
    DORIS_CHECK(can_filter_all != nullptr);
1505
    SCOPED_TIMER(_profile.runtime_filter_partition_prune_timer);
1506
    *can_filter_all = false;
1507
9.10k
    if (conjuncts.empty() || _partition_values.empty()) {
1508
271
        return Status::OK();
1509
271
    }
1510
8.83k
1511
8.83k
    VExprContextSPtrs partition_conjuncts;
1512
8.83k
    for (const auto& conjunct : conjuncts) {
1513
0
        DORIS_CHECK(conjunct != nullptr);
1514
0
        DORIS_CHECK(conjunct->root() != nullptr);
1515
8.91k
        // Keep only the safe prefix of the original conjunct order. If an unsafe conjunct is
1516
8.91k
        // skipped, a later predicate could prune the split before the unsafe one reaches its
1517
0
        // normal row-level evaluation point.
1518
0
        if (!_is_safe_to_pre_execute(conjunct)) {
1519
8.91k
            break;
1520
8.91k
        }
1521
8.91k
        std::set<GlobalIndex> global_indices;
1522
8.91k
        collect_global_indices(conjunct->root(), &global_indices);
1523
8.83k
        if (global_indices.empty()) {
1524
6.90k
            continue;
1525
6.90k
        }
1526
8.83k
        const bool partition_only = std::ranges::all_of(global_indices, [&](GlobalIndex index) {
1527
6.16k
            if (index.value() >= _projected_columns.size()) {
1528
1.72k
                return false;
1529
1.72k
            }
1530
            const auto& column = _projected_columns[index.value()];
1531
4.43k
            return column.is_partition_key &&
1532
4.43k
                   find_partition_value(column, _partition_values) != nullptr;
1533
4.43k
        });
1534
6.91k
        if (partition_only) {
1535
6.91k
            partition_conjuncts.push_back(conjunct);
1536
6.91k
        }
1537
6.91k
    }
1538
4.43k
    if (partition_conjuncts.empty()) {
1539
4.43k
        return Status::OK();
1540
4.43k
    }
1541
4.43k
1542
    Block block;
1543
154k
    RETURN_IF_ERROR(_build_partition_prune_block(&block));
1544
154k
    RowDescriptor row_desc;
1545
154k
    for (const auto& conjunct : partition_conjuncts) {
1546
154k
        RETURN_IF_ERROR(conjunct->prepare(_runtime_state, row_desc));
1547
154k
        RETURN_IF_ERROR(conjunct->open(_runtime_state));
1548
154k
    }
1549
    IColumn::Filter result_filter(block.rows(), 1);
1550
    return VExprContext::execute_conjuncts(partition_conjuncts, nullptr, &block, &result_filter,
1551
                                           can_filter_all);
1552
154k
}
1553
154k
1554
bool TableReader::_is_safe_to_pre_execute(const VExprContextSPtr& conjunct) {
1555
4.47k
    DORIS_CHECK(conjunct != nullptr);
1556
4.47k
    DORIS_CHECK(conjunct->root() != nullptr);
1557
4.47k
    const auto root = conjunct->root();
1558
4.47k
    const auto impl = root->get_impl();
1559
10.3k
    const auto predicate = impl != nullptr ? impl : root;
1560
10.3k
    // Split pruning evaluates a predicate once before any file rows are read. Reordering
1561
10.3k
    // non-deterministic or error-preserving expressions can change their row-level semantics,
1562
10.3k
    // even when every referenced slot is a partition column or maps to a constant entry.
1563
5.48k
    return predicate->is_safe_to_execute_on_selected_rows();
1564
5.48k
}
1565
5.48k
1566
5.48k
Status TableReader::_build_partition_prune_block(Block* block) const {
1567
5.48k
    DORIS_CHECK(block != nullptr);
1568
10.3k
    DORIS_CHECK(!_projected_columns.empty());
1569
10.3k
    block->clear();
1570
4.47k
    for (const auto& column : _projected_columns) {
1571
4.47k
        DORIS_CHECK(column.type != nullptr);
1572
        ColumnPtr value_column = column.type->create_column_const_with_default_value(1);
1573
128k
        if (column.is_partition_key) {
1574
128k
            const auto* partition_value = find_partition_value(column, _partition_values);
1575
128k
            if (partition_value != nullptr) {
1576
128k
                value_column = column.type->create_column_const(1, *partition_value);
1577
128k
            }
1578
128k
        }
1579
25.9k
        block->insert({std::move(value_column), column.type, column.name});
1580
25.9k
    }
1581
    return Status::OK();
1582
25.9k
}
1583
25.9k
1584
25.9k
Status TableReader::_parse_delete_predicates(const SplitReadOptions& options) {
1585
25.9k
    DeleteFileDesc desc {.fs_name = options.current_range.fs_name};
1586
2.30k
    bool has_delete_file = false;
1587
    RETURN_IF_ERROR(_parse_deletion_vector_file(options.current_range.table_format_params, &desc,
1588
2.30k
                                                &has_delete_file));
1589
2.30k
    if (has_delete_file) {
1590
2.30k
        DORIS_CHECK(options.cache != nullptr);
1591
2.30k
        Status create_status = Status::OK();
1592
0
1593
0
        bool decoded_cache_hit = false;
1594
        _deletion_vector = options.cache->get<DeletionVector>(
1595
2.30k
                desc.key,
1596
2.30k
                [&]() -> DeletionVector* {
1597
2.30k
                    auto deletion_vector = std::make_unique<DeletionVector>();
1598
2.30k
1599
2.30k
                    DeletionVectorReader dv_reader(_runtime_state, _scanner_profile, *_scan_params,
1600
2.30k
                                                   desc, _io_ctx.get());
1601
2.30k
                    create_status = dv_reader.open();
1602
2.30k
                    if (!create_status.ok()) [[unlikely]] {
1603
2.30k
                        return nullptr;
1604
2.30k
                    }
1605
2.30k
1606
2.30k
                    size_t bytes_read = desc.size;
1607
2.30k
                    std::vector<char> buffer(bytes_read);
1608
2.30k
                    DBUG_EXECUTE_IF("TableReader.parse_deletion_vector.io_error", {
1609
2.30k
                        create_status =
1610
2.30k
                                Status::IOError("injected format v2 deletion vector read failure");
1611
2.30k
                        return nullptr;
1612
2.30k
                    });
1613
2.30k
                    DBUG_EXECUTE_IF("TableReader.parse_deletion_vector.should_stop", {
1614
2.30k
                        create_status = Status::EndOfFile("stop read.");
1615
2.30k
                        return nullptr;
1616
0
                    });
1617
0
                    create_status =
1618
                            dv_reader.read_at(desc.start_offset, {buffer.data(), bytes_read});
1619
2.30k
                    const auto& file_cache_stats = dv_reader.file_cache_statistics();
1620
2.30k
                    COUNTER_UPDATE(_profile.dv_file_cache_hit_count,
1621
2.30k
                                   file_cache_stats.num_local_io_total);
1622
2.30k
                    COUNTER_UPDATE(_profile.dv_file_cache_miss_count,
1623
2.30k
                                   file_cache_stats.num_remote_io_total);
1624
0
                    COUNTER_UPDATE(_profile.dv_file_cache_peer_read_count,
1625
0
                                   file_cache_stats.num_peer_io_total);
1626
2.30k
                    if (!create_status.ok()) [[unlikely]] {
1627
2.30k
                        return nullptr;
1628
2.30k
                    }
1629
25.9k
1630
25.9k
                    const char* buf = buffer.data();
1631
25.9k
                    SCOPED_TIMER(_profile.parse_delete_file_time);
1632
25.9k
                    create_status = parse_deletion_vector(buf, bytes_read, desc.format,
1633
25.9k
                                                          deletion_vector.get());
1634
25.9k
                    if (!create_status.ok()) [[unlikely]] {
1635
                        return nullptr;
1636
128k
                    }
1637
128k
                    COUNTER_UPDATE(_profile.num_delete_rows, deletion_vector->cardinality());
1638
                    return deletion_vector.release();
1639
                },
1640
                &decoded_cache_hit);
1641
        RETURN_IF_ERROR(create_status);
1642
        COUNTER_UPDATE(decoded_cache_hit ? _profile.decoded_dv_cache_hit_count
1643
                                         : _profile.decoded_dv_cache_miss_count,
1644
                       1);
1645
    }
1646
1647
    return Status::OK();
1648
}
1649
} // namespace doris::format