Coverage Report

Created: 2026-07-25 08:12

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 "runtime/file_scan_profile.h"
54
#include "storage/segment/condition_cache.h"
55
#include "util/debug_points.h"
56
#include "util/string_util.h"
57
58
namespace doris::format {
59
namespace {
60
61
template <typename T, typename Formatter>
62
61
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
63
61
    std::ostringstream out;
64
61
    out << "[";
65
70
    for (size_t i = 0; i < values.size(); ++i) {
66
9
        if (i > 0) {
67
3
            out << ", ";
68
3
        }
69
9
        out << formatter(values[i]);
70
9
    }
71
61
    out << "]";
72
61
    return out.str();
73
61
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_0EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
62
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
63
12
    std::ostringstream out;
64
12
    out << "[";
65
14
    for (size_t i = 0; i < values.size(); ++i) {
66
2
        if (i > 0) {
67
1
            out << ", ";
68
1
        }
69
2
        out << formatter(values[i]);
70
2
    }
71
12
    out << "]";
72
12
    return out.str();
73
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableFilterEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_1EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
62
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
63
12
    std::ostringstream out;
64
12
    out << "[";
65
13
    for (size_t i = 0; i < values.size(); ++i) {
66
1
        if (i > 0) {
67
0
            out << ", ";
68
0
        }
69
1
        out << formatter(values[i]);
70
1
    }
71
12
    out << "]";
72
12
    return out.str();
73
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11GlobalIndexEZNS1_25table_filter_debug_stringB5cxx11ERKNS0_11TableFilterEE3$_0EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISF_EET0_
Line
Count
Source
62
1
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
63
1
    std::ostringstream out;
64
1
    out << "[";
65
2
    for (size_t i = 0; i < values.size(); ++i) {
66
1
        if (i > 0) {
67
0
            out << ", ";
68
0
        }
69
1
        out << formatter(values[i]);
70
1
    }
71
1
    out << "]";
72
1
    return out.str();
73
1
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsISt10shared_ptrINS_12VExprContextEEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_2EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISF_EET0_
Line
Count
Source
62
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
63
12
    std::ostringstream out;
64
12
    out << "[";
65
13
    for (size_t i = 0; i < values.size(); ++i) {
66
1
        if (i > 0) {
67
0
            out << ", ";
68
0
        }
69
1
        out << formatter(values[i]);
70
1
    }
71
12
    out << "]";
72
12
    return out.str();
73
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_3EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
62
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
63
12
    std::ostringstream out;
64
12
    out << "[";
65
14
    for (size_t i = 0; i < values.size(); ++i) {
66
2
        if (i > 0) {
67
1
            out << ", ";
68
1
        }
69
2
        out << formatter(values[i]);
70
2
    }
71
12
    out << "]";
72
12
    return out.str();
73
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableReader15FileBlockColumnEZNKS3_12debug_stringB5cxx11EvE3$_4EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
62
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
63
12
    std::ostringstream out;
64
12
    out << "[";
65
14
    for (size_t i = 0; i < values.size(); ++i) {
66
2
        if (i > 0) {
67
1
            out << ", ";
68
1
        }
69
2
        out << formatter(values[i]);
70
2
    }
71
12
    out << "]";
72
12
    return out.str();
73
12
}
74
75
12
std::string file_format_to_string(FileFormat format) {
76
12
    switch (format) {
77
5
    case FileFormat::PARQUET:
78
5
        return "PARQUET";
79
1
    case FileFormat::ORC:
80
1
        return "ORC";
81
1
    case FileFormat::CSV:
82
1
        return "CSV";
83
1
    case FileFormat::JSON:
84
1
        return "JSON";
85
1
    case FileFormat::TEXT:
86
1
        return "TEXT";
87
1
    case FileFormat::JNI:
88
1
        return "JNI";
89
1
    case FileFormat::NATIVE:
90
1
        return "NATIVE";
91
1
    case FileFormat::ARROW:
92
1
        return "ARROW";
93
12
    }
94
0
    return "UNKNOWN";
95
12
}
96
97
12
std::string push_down_agg_to_string(TPushAggOp::type op) {
98
12
    switch (op) {
99
8
    case TPushAggOp::NONE:
100
8
        return "NONE";
101
1
    case TPushAggOp::COUNT:
102
1
        return "COUNT";
103
1
    case TPushAggOp::MINMAX:
104
1
        return "MINMAX";
105
1
    case TPushAggOp::MIX:
106
1
        return "MIX";
107
1
    case TPushAggOp::COUNT_ON_INDEX:
108
1
        return "COUNT_ON_INDEX";
109
12
    }
110
0
    return "UNKNOWN";
111
12
}
112
113
12
std::string current_file_debug_string(const std::unique_ptr<ScanTask>& task) {
114
12
    if (task == nullptr || task->data_file == nullptr) {
115
11
        return "null";
116
11
    }
117
1
    const auto& file = *task->data_file;
118
1
    std::ostringstream out;
119
1
    out << "FileDescription{path=" << file.path << ", file_size=" << file.file_size
120
1
        << ", range_start_offset=" << file.range_start_offset << ", range_size=" << file.range_size
121
1
        << ", mtime=" << file.mtime << ", fs_name=" << file.fs_name
122
1
        << ", is_immutable=" << file.is_immutable
123
1
        << ", file_cache_admission=" << file.file_cache_admission << "}";
124
1
    return out.str();
125
12
}
126
127
12
std::string partition_values_debug_string(const std::map<std::string, Field>& partition_values) {
128
12
    std::ostringstream out;
129
12
    out << "{";
130
12
    size_t idx = 0;
131
12
    for (const auto& [key, _] : partition_values) {
132
1
        if (idx++ > 0) {
133
0
            out << ", ";
134
0
        }
135
1
        out << key;
136
1
    }
137
12
    out << "}";
138
12
    return out.str();
139
12
}
140
141
1.41M
const schema::external::TField* get_field_ptr(const schema::external::TFieldPtr& field_ptr) {
142
1.41M
    if (!field_ptr.__isset.field_ptr || field_ptr.field_ptr == nullptr) {
143
0
        return nullptr;
144
0
    }
145
1.41M
    return field_ptr.field_ptr.get();
146
1.41M
}
147
148
118k
ColumnDefinition build_schema_identity_from_external_field(const schema::external::TField& field) {
149
118k
    ColumnDefinition identity;
150
118k
    if (field.__isset.id) {
151
118k
        identity.identifier = Field::create_field<TYPE_INT>(field.id);
152
118k
    }
153
118k
    identity.name = field.__isset.name ? field.name : "";
154
118k
    identity.name_mapping =
155
118k
            field.__isset.name_mapping ? field.name_mapping : std::vector<std::string> {};
156
118k
    identity.has_name_mapping =
157
118k
            field.__isset.name_mapping_is_authoritative && field.name_mapping_is_authoritative;
158
118k
    if (!field.__isset.nestedField) {
159
91.4k
        return identity;
160
91.4k
    }
161
27.0k
    if (field.nestedField.__isset.struct_field && field.nestedField.struct_field.__isset.fields) {
162
23.4k
        for (const auto& child_ptr : field.nestedField.struct_field.fields) {
163
23.4k
            if (const auto* child = get_field_ptr(child_ptr); child != nullptr) {
164
23.4k
                identity.children.push_back(build_schema_identity_from_external_field(*child));
165
23.4k
            }
166
23.4k
        }
167
17.3k
    } else if (field.nestedField.__isset.array_field &&
168
17.3k
               field.nestedField.array_field.__isset.item_field) {
169
9.81k
        if (const auto* child = get_field_ptr(field.nestedField.array_field.item_field);
170
9.81k
            child != nullptr) {
171
9.81k
            identity.children.push_back(build_schema_identity_from_external_field(*child));
172
9.81k
            identity.children.back().name = "element";
173
9.81k
        }
174
9.81k
    } else if (field.nestedField.__isset.map_field) {
175
7.75k
        if (field.nestedField.map_field.__isset.key_field) {
176
7.75k
            if (const auto* child = get_field_ptr(field.nestedField.map_field.key_field);
177
7.75k
                child != nullptr) {
178
7.75k
                identity.children.push_back(build_schema_identity_from_external_field(*child));
179
7.75k
                identity.children.back().name = "key";
180
7.75k
            }
181
7.75k
        }
182
7.75k
        if (field.nestedField.map_field.__isset.value_field) {
183
7.75k
            if (const auto* child = get_field_ptr(field.nestedField.map_field.value_field);
184
7.75k
                child != nullptr) {
185
7.75k
                identity.children.push_back(build_schema_identity_from_external_field(*child));
186
7.75k
                identity.children.back().name = "value";
187
7.75k
            }
188
7.75k
        }
189
7.74k
    }
190
27.0k
    return identity;
191
118k
}
192
193
const ColumnDefinition* find_identity_child(const ColumnDefinition& projected_child,
194
44.1k
                                            const ColumnDefinition& identity_parent) {
195
44.1k
    const auto child_it = std::ranges::find_if(
196
66.3k
            identity_parent.children, [&](const ColumnDefinition& identity_child) {
197
66.3k
                if (projected_child.has_identifier_field_id() &&
198
66.3k
                    identity_child.has_identifier_field_id()) {
199
66.2k
                    return projected_child.get_identifier_field_id() ==
200
66.2k
                           identity_child.get_identifier_field_id();
201
66.2k
                }
202
50
                if (to_lower(projected_child.name) == to_lower(identity_child.name)) {
203
0
                    return true;
204
0
                }
205
50
                return std::ranges::any_of(
206
50
                        identity_child.name_mapping, [&](const std::string& alias) {
207
0
                            return to_lower(projected_child.name) == to_lower(alias);
208
0
                        });
209
50
            });
210
44.1k
    return child_it == identity_parent.children.end() ? nullptr : &*child_it;
211
44.1k
}
212
213
113k
void attach_full_schema_identity(ColumnDefinition* projected, const ColumnDefinition& identity) {
214
113k
    DORIS_CHECK(projected != nullptr);
215
    // Access-path children control materialization, but wrapper discovery needs sibling IDs that
216
    // were pruned from that projection. Keep the complete identity tree on a separate channel.
217
113k
    projected->identity_children = identity.children;
218
113k
    for (auto& projected_child : projected->children) {
219
44.1k
        if (const auto* identity_child = find_identity_child(projected_child, identity);
220
44.1k
            identity_child != nullptr) {
221
43.9k
            attach_full_schema_identity(&projected_child, *identity_child);
222
43.9k
        }
223
44.1k
    }
224
113k
}
225
226
13
void clear_initial_default_metadata(ColumnDefinition* column) {
227
13
    DORIS_CHECK(column != nullptr);
228
13
    column->initial_default_value.reset();
229
13
    column->initial_default_value_is_base64 = false;
230
13
    for (auto& child : column->children) {
231
6
        clear_initial_default_metadata(&child);
232
6
    }
233
13
}
234
235
39.3k
bool external_field_matches_name(const schema::external::TField& field, const std::string& name) {
236
39.3k
    if (field.__isset.name && to_lower(field.name) == to_lower(name)) {
237
20.6k
        return true;
238
20.6k
    }
239
18.7k
    return field.__isset.name_mapping &&
240
18.7k
           std::ranges::any_of(field.name_mapping, [&](const std::string& alias) {
241
7
               return to_lower(alias) == to_lower(name);
242
7
           });
243
39.3k
}
244
245
DataTypePtr find_struct_child_type_by_external_field(const DataTypeStruct& struct_type,
246
23.2k
                                                     const schema::external::TField& field) {
247
42.0k
    for (size_t field_idx = 0; field_idx < struct_type.get_elements().size(); ++field_idx) {
248
39.4k
        if (external_field_matches_name(field, struct_type.get_element_name(field_idx))) {
249
20.6k
            return struct_type.get_element(field_idx);
250
20.6k
        }
251
39.4k
    }
252
2.63k
    return nullptr;
253
23.2k
}
254
255
DataTypePtr restore_current_primitive_type(const schema::external::TField& field,
256
115k
                                           DataTypePtr fallback_type) {
257
115k
    if (!field.__isset.type) {
258
16
        return fallback_type;
259
16
    }
260
115k
    const auto primitive_type = thrift_to_type(field.type.type);
261
115k
    if (is_complex_type(primitive_type)) {
262
27.1k
        return fallback_type;
263
27.1k
    }
264
    // The delete file can expose an older physical type, but initial defaults belong to the
265
    // current table field. Restore that type from FE before parsing the default and let the table
266
    // reader apply the normal promotion cast to the delete-key type.
267
88.7k
    return DataTypeFactory::instance().create_data_type(
268
88.7k
            primitive_type, false, field.type.__isset.precision ? field.type.precision : 0,
269
88.7k
            field.type.__isset.scale ? field.type.scale : 0,
270
88.7k
            field.type.__isset.len ? field.type.len : -1);
271
115k
}
272
273
ColumnDefinition build_schema_column_from_external_field(const schema::external::TField& field,
274
115k
                                                         DataTypePtr type) {
275
115k
    type = restore_current_primitive_type(field, std::move(type));
276
115k
    ColumnDefinition column {
277
115k
            .identifier = field.__isset.id ? Field::create_field<TYPE_INT>(field.id) : Field {},
278
115k
            .name = field.__isset.name ? field.name : "",
279
115k
            .name_mapping =
280
115k
                    field.__isset.name_mapping ? field.name_mapping : std::vector<std::string> {},
281
115k
            .has_name_mapping = field.__isset.name_mapping_is_authoritative &&
282
115k
                                field.name_mapping_is_authoritative,
283
115k
            .type = std::move(type),
284
115k
            .children = {},
285
115k
            .default_expr = nullptr,
286
115k
            .initial_default_value = field.__isset.initial_default_value
287
115k
                                             ? std::make_optional(field.initial_default_value)
288
115k
                                             : std::nullopt,
289
115k
            .initial_default_value_is_base64 = field.__isset.initial_default_value_is_base64 &&
290
115k
                                               field.initial_default_value_is_base64,
291
115k
            .is_partition_key = false,
292
115k
    };
293
115k
    if (column.type == nullptr || !field.__isset.nestedField) {
294
88.8k
        return column;
295
88.8k
    }
296
297
27.1k
    const auto nested_type = remove_nullable(column.type);
298
27.1k
    switch (nested_type->get_primitive_type()) {
299
9.62k
    case TYPE_STRUCT: {
300
9.62k
        if (!field.nestedField.__isset.struct_field ||
301
9.62k
            !field.nestedField.struct_field.__isset.fields) {
302
0
            return column;
303
0
        }
304
9.62k
        const auto& struct_type = assert_cast<const DataTypeStruct&>(*nested_type);
305
23.2k
        for (const auto& child_ptr : field.nestedField.struct_field.fields) {
306
23.2k
            const auto* child_field = get_field_ptr(child_ptr);
307
23.2k
            if (child_field == nullptr || !child_field->__isset.name) {
308
0
                continue;
309
0
            }
310
23.2k
            auto child_type = find_struct_child_type_by_external_field(struct_type, *child_field);
311
23.2k
            if (child_type == nullptr) {
312
2.63k
                continue;
313
2.63k
            }
314
20.6k
            column.children.push_back(
315
20.6k
                    build_schema_column_from_external_field(*child_field, child_type));
316
20.6k
        }
317
9.62k
        break;
318
9.62k
    }
319
9.81k
    case TYPE_ARRAY: {
320
9.81k
        if (!field.nestedField.__isset.array_field ||
321
9.81k
            !field.nestedField.array_field.__isset.item_field) {
322
0
            return column;
323
0
        }
324
9.81k
        const auto* item_field = get_field_ptr(field.nestedField.array_field.item_field);
325
9.81k
        if (item_field == nullptr) {
326
0
            return column;
327
0
        }
328
9.81k
        const auto& array_type = assert_cast<const DataTypeArray&>(*nested_type);
329
9.81k
        auto child =
330
9.81k
                build_schema_column_from_external_field(*item_field, array_type.get_nested_type());
331
9.81k
        child.name = "element";
332
9.81k
        if (child.has_identifier_name()) {
333
0
            child.identifier = Field::create_field<TYPE_STRING>(child.name);
334
0
        }
335
9.81k
        column.children.push_back(std::move(child));
336
9.81k
        break;
337
9.81k
    }
338
7.74k
    case TYPE_MAP: {
339
7.74k
        if (!field.nestedField.__isset.map_field ||
340
7.74k
            !field.nestedField.map_field.__isset.key_field ||
341
7.74k
            !field.nestedField.map_field.__isset.value_field) {
342
0
            return column;
343
0
        }
344
7.74k
        const auto& map_type = assert_cast<const DataTypeMap&>(*nested_type);
345
7.74k
        const auto* key_field = get_field_ptr(field.nestedField.map_field.key_field);
346
7.74k
        if (key_field != nullptr) {
347
7.74k
            auto child =
348
7.74k
                    build_schema_column_from_external_field(*key_field, map_type.get_key_type());
349
7.74k
            child.name = "key";
350
7.74k
            if (child.has_identifier_name()) {
351
0
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
352
0
            }
353
7.74k
            column.children.push_back(std::move(child));
354
7.74k
        }
355
7.74k
        const auto* value_field = get_field_ptr(field.nestedField.map_field.value_field);
356
7.74k
        if (value_field != nullptr) {
357
7.74k
            auto child = build_schema_column_from_external_field(*value_field,
358
7.74k
                                                                 map_type.get_value_type());
359
7.74k
            child.name = "value";
360
7.74k
            if (child.has_identifier_name()) {
361
0
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
362
0
            }
363
7.74k
            column.children.push_back(std::move(child));
364
7.74k
        }
365
7.74k
        break;
366
7.74k
    }
367
0
    default:
368
0
        break;
369
27.1k
    }
370
27.1k
    return column;
371
27.1k
}
372
373
const schema::external::TField* find_external_root_field(const TFileScanRangeParams* params,
374
311k
                                                         const ColumnDefinition& column) {
375
311k
    if (params == nullptr || !params->__isset.history_schema_info ||
376
311k
        params->history_schema_info.empty()) {
377
170k
        return nullptr;
378
170k
    }
379
140k
    const auto* schema = &params->history_schema_info.front();
380
140k
    if (params->__isset.current_schema_id) {
381
140k
        for (const auto& candidate_schema : params->history_schema_info) {
382
140k
            if (candidate_schema.__isset.schema_id &&
383
140k
                candidate_schema.schema_id == params->current_schema_id) {
384
140k
                schema = &candidate_schema;
385
140k
                break;
386
140k
            }
387
140k
        }
388
140k
    }
389
140k
    if (!schema->__isset.root_field || !schema->root_field.__isset.fields) {
390
0
        return nullptr;
391
0
    }
392
140k
    if (!supports_iceberg_scan_semantics_v1(params)) {
393
        // Old BEs used one ordered current-name/alias pass. Preserve that result for old-FE plans
394
        // until the explicit scan-semantics marker makes exact-name precedence cluster-wide.
395
7
        for (const auto& field_ptr : schema->root_field.fields) {
396
7
            const auto* field = get_field_ptr(field_ptr);
397
7
            if (field != nullptr && external_field_matches_name(*field, column.name)) {
398
7
                return field;
399
7
            }
400
7
        }
401
0
        return nullptr;
402
7
    }
403
    // A reused name identifies the newly added field, not an older sibling that retained that
404
    // spelling as an alias. Exhaust exact current names before consulting historical aliases.
405
1.31M
    for (const auto& field_ptr : schema->root_field.fields) {
406
1.31M
        const auto* field = get_field_ptr(field_ptr);
407
1.32M
        if (field != nullptr && field->__isset.name &&
408
1.32M
            to_lower(field->name) == to_lower(column.name)) {
409
140k
            return field;
410
140k
        }
411
1.31M
    }
412
3.51k
    for (const auto& field_ptr : schema->root_field.fields) {
413
3.51k
        const auto* field = get_field_ptr(field_ptr);
414
3.51k
        if (field != nullptr && field->__isset.name_mapping &&
415
3.51k
            std::ranges::any_of(field->name_mapping, [&](const std::string& alias) {
416
0
                return to_lower(alias) == to_lower(column.name);
417
0
            })) {
418
0
            return field;
419
0
        }
420
3.51k
    }
421
427
    return nullptr;
422
427
}
423
424
2
std::string expr_context_debug_string(const VExprContextSPtr& context) {
425
2
    if (context == nullptr) {
426
0
        return "null";
427
0
    }
428
2
    const auto root = context->root();
429
2
    if (root == nullptr) {
430
0
        return "VExprContext{root=null}";
431
0
    }
432
2
    std::ostringstream out;
433
2
    out << "VExprContext{root_name=" << root->expr_name() << ", root_debug=" << root->debug_string()
434
2
        << "}";
435
2
    return out.str();
436
2
}
437
438
1
std::string table_filter_debug_string(const TableFilter& filter) {
439
1
    std::ostringstream out;
440
1
    out << "TableFilter{conjunct=" << expr_context_debug_string(filter.conjunct)
441
1
        << ", global_indices="
442
1
        << join_table_reader_debug_strings(
443
1
                   filter.global_indices,
444
1
                   [](GlobalIndex global_index) { return std::to_string(global_index.value()); })
445
1
        << "}";
446
1
    return out.str();
447
1
}
448
449
4
bool contains_runtime_filter(const VExprContextSPtrs& conjuncts) {
450
4
    return std::ranges::any_of(conjuncts, [](const auto& conjunct) {
451
4
        return conjunct != nullptr && conjunct->root() != nullptr &&
452
4
               conjunct->root()->is_rf_wrapper();
453
4
    });
454
4
}
455
456
154k
void collect_global_indices(const VExprSPtr& expr, std::set<GlobalIndex>* global_indices) {
457
154k
    if (expr == nullptr) {
458
0
        return;
459
0
    }
460
154k
    if (expr->is_rf_wrapper()) {
461
        // RuntimeFilterExpr wraps a real predicate expression but its own thrift node can still
462
        // look like SLOT_REF. Collect indices from the wrapped predicate; do not cast the wrapper
463
        // itself to VSlotRef.
464
6.61k
        collect_global_indices(expr->get_impl(), global_indices);
465
6.61k
        return;
466
6.61k
    }
467
148k
    if (expr->is_slot_ref()) {
468
47.6k
        const auto* slot_ref = assert_cast<const VSlotRef*>(expr.get());
469
47.6k
        DORIS_CHECK(slot_ref->column_id() >= 0);
470
47.6k
        global_indices->insert(GlobalIndex(cast_set<size_t>(slot_ref->column_id())));
471
47.6k
    }
472
148k
    for (const auto& child : expr->children()) {
473
102k
        collect_global_indices(child, global_indices);
474
102k
    }
475
148k
}
476
477
Status build_table_filters_from_conjunct(const VExprContextSPtr& conjunct, RuntimeState* state,
478
41.6k
                                         std::vector<TableFilter>* table_filters) {
479
41.6k
    if (conjunct == nullptr) {
480
0
        return Status::OK();
481
0
    }
482
41.6k
    std::set<GlobalIndex> global_indices;
483
41.6k
    collect_global_indices(conjunct->root(), &global_indices);
484
41.7k
    if (!global_indices.empty()) {
485
41.7k
        TableFilter table_filter;
486
41.7k
        VExprSPtr filter_root;
487
41.7k
        RETURN_IF_ERROR(clone_table_expr_tree(conjunct->root(), &filter_root));
488
41.7k
        table_filter.conjunct = VExprContext::create_shared(std::move(filter_root));
489
42.8k
        for (const auto global_index : global_indices) {
490
42.8k
            table_filter.global_indices.push_back(global_index);
491
42.8k
        }
492
41.7k
        table_filters->push_back(std::move(table_filter));
493
41.7k
    }
494
41.6k
    return Status::OK();
495
41.6k
}
496
497
Status parse_deletion_vector(const char* buf, size_t buffer_size, DeleteFileDesc::Format format,
498
2.05k
                             DeletionVector* deletion_vector) {
499
2.05k
    DORIS_CHECK(buf != nullptr);
500
2.05k
    DORIS_CHECK(deletion_vector != nullptr);
501
2.05k
    DORIS_CHECK(format == DeleteFileDesc::Format::PAIMON ||
502
2.05k
                format == DeleteFileDesc::Format::ICEBERG);
503
504
2.05k
    if (format == DeleteFileDesc::Format::PAIMON) {
505
1
        RETURN_IF_ERROR(decode_paimon_deletion_vector_buffer(buf, buffer_size, deletion_vector));
506
1
        return Status::OK();
507
1
    }
508
509
2.05k
    return decode_iceberg_deletion_vector_buffer(buf, buffer_size, deletion_vector);
510
2.05k
}
511
512
} // namespace
513
514
std::shared_ptr<io::FileSystemProperties> create_system_properties(
515
41.4k
        const TFileScanRangeParams* scan_params) {
516
41.4k
    auto system_properties = std::make_shared<io::FileSystemProperties>();
517
41.4k
    if (scan_params == nullptr || !scan_params->__isset.file_type) {
518
644
        system_properties->system_type = TFileType::FILE_LOCAL;
519
644
        return system_properties;
520
644
    }
521
40.7k
    system_properties->system_type = scan_params->file_type;
522
40.7k
    system_properties->properties = scan_params->properties;
523
40.7k
    system_properties->hdfs_params = scan_params->hdfs_params;
524
40.7k
    if (scan_params->__isset.broker_addresses) {
525
0
        system_properties->broker_addresses.assign(scan_params->broker_addresses.begin(),
526
0
                                                   scan_params->broker_addresses.end());
527
0
    }
528
40.7k
    return system_properties;
529
41.4k
}
530
531
12
std::string TableReader::debug_string() const {
532
12
    std::ostringstream out;
533
12
    out << "TableReader{format=" << file_format_to_string(_format)
534
12
        << ", push_down_agg_type=" << push_down_agg_to_string(_push_down_agg_type)
535
12
        << ", aggregate_pushdown_tried=" << _aggregate_pushdown_tried
536
12
        << ", has_current_reader=" << (_data_reader.reader != nullptr)
537
12
        << ", has_current_task=" << (_current_task != nullptr)
538
12
        << ", current_file=" << current_file_debug_string(_current_task)
539
12
        << ", has_delete_rows=" << (_delete_rows != nullptr)
540
12
        << ", delete_row_count=" << (_delete_rows == nullptr ? 0 : _delete_rows->size())
541
12
        << ", has_deletion_vector=" << (_deletion_vector != nullptr)
542
12
        << ", deletion_vector_cardinality="
543
12
        << (_deletion_vector == nullptr ? 0 : _deletion_vector->cardinality())
544
12
        << ", has_system_properties=" << (_system_properties != nullptr) << ", system_type="
545
12
        << (_system_properties == nullptr ? static_cast<int>(TFileType::FILE_LOCAL)
546
12
                                          : static_cast<int>(_system_properties->system_type))
547
12
        << ", has_scan_params=" << (_scan_params != nullptr)
548
12
        << ", has_io_ctx=" << (_io_ctx != nullptr)
549
12
        << ", has_runtime_state=" << (_runtime_state != nullptr)
550
12
        << ", has_scanner_profile=" << (_scanner_profile != nullptr)
551
12
        << ", mapper_options=" << _mapper_options.debug_string() << ", projected_columns="
552
12
        << join_table_reader_debug_strings(
553
12
                   _projected_columns,
554
12
                   [](const ColumnDefinition& column) { return column.debug_string(); })
555
12
        << ", partition_values=" << partition_values_debug_string(_partition_values)
556
12
        << ", table_filters="
557
12
        << join_table_reader_debug_strings(
558
12
                   _table_filters,
559
12
                   [](const TableFilter& filter) { return table_filter_debug_string(filter); })
560
12
        << ", conjunct_count=" << _conjuncts.size() << ", conjuncts="
561
12
        << join_table_reader_debug_strings(_conjuncts,
562
12
                                           [](const VExprContextSPtr& conjunct) {
563
1
                                               return expr_context_debug_string(conjunct);
564
1
                                           })
565
12
        << ", file_schema="
566
12
        << join_table_reader_debug_strings(
567
12
                   _data_reader.file_schema,
568
12
                   [](const ColumnDefinition& field) { return field.debug_string(); })
569
12
        << ", file_block_layout="
570
12
        << join_table_reader_debug_strings(
571
12
                   _data_reader.file_block_layout,
572
12
                   [](const FileBlockColumn& column) {
573
2
                       std::ostringstream column_out;
574
2
                       column_out << "FileBlockColumn{file_column_id=" << column.file_column_id
575
2
                                  << ", name=" << column.name << ", type="
576
2
                                  << (column.type == nullptr ? "null" : column.type->get_name())
577
2
                                  << "}";
578
2
                       return column_out.str();
579
2
                   })
580
12
        << ", block_template_columns=" << _data_reader.block_template.columns()
581
12
        << ", column_mapper="
582
12
        << (_data_reader.column_mapper == nullptr ? "null"
583
12
                                                  : _data_reader.column_mapper->debug_string())
584
12
        << "}";
585
12
    return out.str();
586
12
}
587
588
Status TableReader::annotate_projected_column(const TFileScanSlotInfo& slot_info,
589
                                              ProjectedColumnBuildContext* context,
590
241k
                                              ColumnDefinition* column) const {
591
241k
    (void)slot_info;
592
241k
    DORIS_CHECK(context != nullptr);
593
241k
    DORIS_CHECK(column != nullptr);
594
241k
    context->schema_column.reset();
595
241k
    const auto* schema_field = find_external_root_field(context->scan_params, *column);
596
241k
    if (schema_field == nullptr) {
597
171k
        return Status::OK();
598
171k
    }
599
70.0k
    context->schema_column = build_schema_column_from_external_field(*schema_field, column->type);
600
70.0k
    const bool use_current_semantics = supports_iceberg_scan_semantics_v1(context->scan_params);
601
70.0k
    if (!use_current_semantics) {
602
        // IDs and encoded defaults predate the result-changing semantics. Strip only the new
603
        // default channel so an old-FE plan keeps the same generic root/nested values on every BE.
604
7
        clear_initial_default_metadata(&*context->schema_column);
605
7
    }
606
70.0k
    column->identifier = context->schema_column->identifier;
607
70.0k
    column->name_mapping = context->schema_column->name_mapping;
608
70.0k
    column->has_name_mapping = context->schema_column->has_name_mapping;
609
    // Projected roots already carry a generic FE default expression, but Iceberg binary defaults
610
    // need the raw Base64 marker so missing-file materialization can decode rather than copy text.
611
70.0k
    column->initial_default_value = context->schema_column->initial_default_value;
612
70.0k
    column->initial_default_value_is_base64 =
613
70.0k
            context->schema_column->initial_default_value_is_base64;
614
70.0k
    return Status::OK();
615
241k
}
616
617
std::optional<ColumnDefinition> TableReader::_find_current_table_column_by_field_id(
618
10
        int32_t field_id, DataTypePtr type) const {
619
10
    if (_scan_params == nullptr || !_scan_params->__isset.history_schema_info ||
620
10
        _scan_params->history_schema_info.empty()) {
621
4
        return std::nullopt;
622
4
    }
623
6
    const auto* schema = &_scan_params->history_schema_info.front();
624
6
    if (_scan_params->__isset.current_schema_id) {
625
6
        for (const auto& candidate_schema : _scan_params->history_schema_info) {
626
6
            if (candidate_schema.__isset.schema_id &&
627
6
                candidate_schema.schema_id == _scan_params->current_schema_id) {
628
6
                schema = &candidate_schema;
629
6
                break;
630
6
            }
631
6
        }
632
6
    }
633
6
    if (!schema->__isset.root_field || !schema->root_field.__isset.fields) {
634
0
        return std::nullopt;
635
0
    }
636
11
    for (const auto& field_ptr : schema->root_field.fields) {
637
11
        const auto* field = get_field_ptr(field_ptr);
638
11
        if (field != nullptr && field->__isset.id && field->id == field_id) {
639
6
            return build_schema_column_from_external_field(*field, std::move(type));
640
6
        }
641
11
    }
642
0
    return std::nullopt;
643
6
}
644
645
41.5k
Status TableReader::init(TableReadOptions&& options) {
646
41.5k
    _scanner_profile = options.scanner_profile;
647
41.5k
    if (_scanner_profile != nullptr) {
648
41.4k
        const auto hierarchy = file_scan_profile::ensure_hierarchy(_scanner_profile);
649
41.4k
        static const char* table_profile = file_scan_profile::TABLE_READER;
650
41.4k
        static const char* file_reader_profile = file_scan_profile::FILE_READER;
651
41.4k
        _profile.total_timer = hierarchy.table_reader;
652
41.4k
        _profile.file_reader_total_timer = hierarchy.file_reader;
653
41.4k
        _profile.init_timer =
654
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "InitTime", table_profile, 1);
655
41.4k
        _profile.num_delete_files = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteFiles",
656
41.4k
                                                                 TUnit::UNIT, table_profile, 1);
657
41.4k
        _profile.num_delete_rows = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteRows",
658
41.4k
                                                                TUnit::UNIT, table_profile, 1);
659
41.4k
        _profile.parse_delete_file_time = ADD_CHILD_TIMER_WITH_LEVEL(
660
41.4k
                _scanner_profile, "ParseDeleteFileTime", table_profile, 1);
661
41.4k
        _profile.decoded_dv_cache_hit_count =
662
41.4k
                ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "DeletionVectorDecodedCacheHitCount",
663
41.4k
                                             TUnit::UNIT, table_profile, 1);
664
41.4k
        _profile.decoded_dv_cache_miss_count = ADD_CHILD_COUNTER_WITH_LEVEL(
665
41.4k
                _scanner_profile, "DeletionVectorDecodedCacheMissCount", TUnit::UNIT, table_profile,
666
41.4k
                1);
667
41.4k
        _profile.dv_file_cache_hit_count = ADD_CHILD_COUNTER_WITH_LEVEL(
668
41.4k
                _scanner_profile, "DeletionVectorFileCacheHitCount", TUnit::UNIT, table_profile, 1);
669
41.4k
        _profile.dv_file_cache_miss_count =
670
41.4k
                ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "DeletionVectorFileCacheMissCount",
671
41.4k
                                             TUnit::UNIT, table_profile, 1);
672
41.4k
        _profile.dv_file_cache_peer_read_count = ADD_CHILD_COUNTER_WITH_LEVEL(
673
41.4k
                _scanner_profile, "DeletionVectorFileCachePeerReadCount", TUnit::UNIT,
674
41.4k
                table_profile, 1);
675
41.4k
        _profile.exec_timer =
676
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "GetBlockTime", table_profile, 1);
677
41.4k
        _profile.prepare_split_timer =
678
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PrepareSplitTime", table_profile, 1);
679
41.4k
        _profile.finalize_timer =
680
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "FinalizeBlockTime", table_profile, 1);
681
41.4k
        _profile.residual_filter_timer = ADD_CHILD_TIMER_WITH_LEVEL(
682
41.4k
                _scanner_profile, "ResidualFilterTime", table_profile, 1);
683
41.4k
        _profile.create_reader_timer =
684
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "CreateReaderTime", table_profile, 1);
685
41.4k
        _profile.pushdown_agg_timer =
686
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PushDownAggTime", table_profile, 1);
687
41.4k
        _profile.open_reader_timer =
688
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "OpenReaderTime", table_profile, 1);
689
41.4k
        _profile.runtime_filter_partition_prune_timer = ADD_CHILD_TIMER_WITH_LEVEL(
690
41.4k
                _scanner_profile, "FileScannerRuntimeFilterPartitionPruningTime", table_profile, 1);
691
41.4k
        _profile.runtime_filter_partition_pruned_range_counter = ADD_CHILD_COUNTER_WITH_LEVEL(
692
41.4k
                _scanner_profile, "RuntimeFilterPartitionPrunedRangeNum", TUnit::UNIT,
693
41.4k
                table_profile, 1);
694
41.4k
        _profile.close_timer =
695
41.4k
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "CloseTime", table_profile, 1);
696
        // Lifecycle timer names remain globally unique because RuntimeProfile's visual hierarchy
697
        // does not namespace counters that share the same display parent.
698
41.4k
        _profile.file_reader_init_timer = ADD_CHILD_TIMER_WITH_LEVEL(
699
41.4k
                _scanner_profile, "FileReaderInitTime", file_reader_profile, 1);
700
41.4k
        _profile.file_reader_schema_timer = ADD_CHILD_TIMER_WITH_LEVEL(
701
41.4k
                _scanner_profile, "FileReaderGetSchemaTime", file_reader_profile, 1);
702
41.4k
        _profile.file_reader_mapper_timer = ADD_CHILD_TIMER_WITH_LEVEL(
703
41.4k
                _scanner_profile, "FileReaderCreateColumnMapperTime", file_reader_profile, 1);
704
41.4k
        _profile.file_reader_open_timer = ADD_CHILD_TIMER_WITH_LEVEL(
705
41.4k
                _scanner_profile, "FileReaderOpenTime", file_reader_profile, 1);
706
41.4k
        _profile.file_reader_get_block_timer = ADD_CHILD_TIMER_WITH_LEVEL(
707
41.4k
                _scanner_profile, "FileReaderGetBlockTime", file_reader_profile, 1);
708
41.4k
        _profile.file_reader_aggregate_timer = ADD_CHILD_TIMER_WITH_LEVEL(
709
41.4k
                _scanner_profile, "FileReaderAggregatePushDownTime", file_reader_profile, 1);
710
41.4k
        _profile.file_reader_close_timer = ADD_CHILD_TIMER_WITH_LEVEL(
711
41.4k
                _scanner_profile, "FileReaderCloseTime", file_reader_profile, 1);
712
41.4k
    }
713
    // Establish lifecycle timers before consuming options or constructing filesystem properties;
714
    // placing these scopes at the tail records only scope teardown and hides expensive init work.
715
41.5k
    SCOPED_TIMER(_profile.total_timer);
716
41.5k
    SCOPED_TIMER(_profile.init_timer);
717
41.5k
    _scan_params = options.scan_params;
718
41.5k
    _format = options.format;
719
41.5k
    _io_ctx = options.io_ctx;
720
41.5k
    _runtime_state = options.runtime_state;
721
41.5k
    _file_slot_descs = options.file_slot_descs;
722
41.5k
    _push_down_agg_type = options.push_down_agg_type;
723
41.5k
    _push_down_count_columns = options.push_down_count_columns;
724
41.5k
    _initial_condition_cache_digest = options.condition_cache_digest;
725
41.5k
    _condition_cache_digest = _initial_condition_cache_digest;
726
41.5k
    _table_reader_owned_conjunct_count =
727
41.5k
            options.table_reader_owned_conjunct_count.value_or(options.conjuncts.size());
728
41.5k
    DORIS_CHECK_LE(_table_reader_owned_conjunct_count, options.conjuncts.size());
729
41.5k
    _projected_columns = std::move(options.projected_columns);
730
41.5k
    if (supports_iceberg_scan_semantics_v1(_scan_params)) {
731
70.3k
        for (auto& projected_column : _projected_columns) {
732
70.3k
            const auto* schema_field = find_external_root_field(_scan_params, projected_column);
733
70.3k
            if (schema_field != nullptr) {
734
69.9k
                attach_full_schema_identity(
735
69.9k
                        &projected_column,
736
69.9k
                        build_schema_identity_from_external_field(*schema_field));
737
69.9k
            }
738
70.3k
        }
739
18.6k
    }
740
41.5k
    _system_properties = create_system_properties(_scan_params);
741
41.5k
    _mapper_options.mode = TableColumnMappingMode::BY_NAME;
742
41.5k
    return _replace_conjuncts(options.conjuncts);
743
41.5k
}
744
745
28.6k
Status TableReader::_clone_conjunct(const VExprContextSPtr& source, VExprContextSPtr* cloned) {
746
28.6k
    DORIS_CHECK(source != nullptr);
747
28.6k
    DORIS_CHECK(source->root() != nullptr);
748
28.6k
    DORIS_CHECK(cloned != nullptr);
749
28.6k
    VExprSPtr root;
750
28.6k
    RETURN_IF_ERROR(clone_table_expr_tree(source->root(), &root));
751
28.6k
    *cloned = VExprContext::create_shared(std::move(root));
752
28.6k
    return Status::OK();
753
28.6k
}
754
755
23.2k
Status TableReader::_prepare_conjunct(const VExprContextSPtr& source, VExprContextSPtr* prepared) {
756
23.2k
    DORIS_CHECK(prepared != nullptr);
757
23.2k
    VExprContextSPtr conjunct;
758
23.2k
    RETURN_IF_ERROR(_clone_conjunct(source, &conjunct));
759
23.2k
    RETURN_IF_ERROR(conjunct->prepare(_runtime_state, RowDescriptor {}));
760
23.2k
    RETURN_IF_ERROR(conjunct->open(_runtime_state));
761
23.2k
    *prepared = std::move(conjunct);
762
23.2k
    return Status::OK();
763
23.2k
}
764
765
41.1k
Status TableReader::_replace_conjuncts(const VExprContextSPtrs& conjuncts) {
766
41.1k
    VExprContextSPtrs prepared;
767
41.1k
    prepared.reserve(conjuncts.size());
768
69.5k
    for (size_t conjunct_index = 0; conjunct_index < conjuncts.size(); ++conjunct_index) {
769
28.4k
        VExprContextSPtr conjunct;
770
28.4k
        if (conjunct_index < _table_reader_owned_conjunct_count) {
771
23.0k
            RETURN_IF_ERROR(_prepare_conjunct(conjuncts[conjunct_index], &conjunct));
772
23.0k
        } else {
773
            // Scanner-owned suffixes are cloned only for pruning analysis; preparing them here
774
            // would duplicate expression state that must remain exclusively in Scanner.
775
5.36k
            RETURN_IF_ERROR(_clone_conjunct(conjuncts[conjunct_index], &conjunct));
776
5.36k
        }
777
28.4k
        prepared.push_back(std::move(conjunct));
778
28.4k
    }
779
41.1k
    _conjuncts = std::move(prepared);
780
41.1k
    return Status::OK();
781
41.1k
}
782
783
Status TableReader::append_conjuncts_with_ownership(const VExprContextSPtrs& conjuncts,
784
1.89k
                                                    size_t table_reader_owned_conjunct_count) {
785
1.89k
    DORIS_CHECK(!_appended_table_reader_owned_conjunct_count.has_value());
786
1.89k
    _appended_table_reader_owned_conjunct_count = table_reader_owned_conjunct_count;
787
1.89k
    auto status = append_conjuncts(conjuncts);
788
1.89k
    _appended_table_reader_owned_conjunct_count.reset();
789
1.89k
    return status;
790
1.89k
}
791
792
1.89k
Status TableReader::append_conjuncts(const VExprContextSPtrs& conjuncts) {
793
1.89k
    const size_t owned_count =
794
1.89k
            _appended_table_reader_owned_conjunct_count.value_or(conjuncts.size());
795
1.89k
    DORIS_CHECK_LE(owned_count, conjuncts.size());
796
    // Once Scanner owns a suffix, later predicates cannot be inserted into the TableReader-owned
797
    // prefix without reordering them ahead of that stateful/error-preserving barrier.
798
1.89k
    DORIS_CHECK(owned_count == 0 || _table_reader_owned_conjunct_count == _conjuncts.size());
799
2.18k
    for (size_t conjunct_index = 0; conjunct_index < conjuncts.size(); ++conjunct_index) {
800
288
        const auto& source = conjuncts[conjunct_index];
801
288
        VExprContextSPtr conjunct;
802
288
        if (conjunct_index < owned_count) {
803
285
            RETURN_IF_ERROR(_prepare_conjunct(source, &conjunct));
804
285
        } else {
805
            // Preserve Scanner as the sole owner of runtime state for appended residuals.
806
3
            RETURN_IF_ERROR(_clone_conjunct(source, &conjunct));
807
3
        }
808
288
        _conjuncts.push_back(conjunct);
809
288
        if (conjunct_index < owned_count) {
810
285
            ++_table_reader_owned_conjunct_count;
811
285
        }
812
288
        if (_current_task != nullptr && conjunct_index < owned_count) {
813
            // The active reader has already fixed its localized predicate set. Appended runtime
814
            // filters must remain residual until the next split rebuilds its FileScanRequest.
815
285
            _remaining_conjuncts.push_back(std::move(conjunct));
816
285
        }
817
288
    }
818
1.89k
    return Status::OK();
819
1.89k
}
820
821
62.5k
Status TableReader::_build_table_filters_from_conjuncts() {
822
62.5k
    _table_filters.clear();
823
62.5k
    _constant_pruning_safe_filter_count = 0;
824
62.5k
    bool in_safe_prefix = true;
825
104k
    for (size_t conjunct_index = 0; conjunct_index < _conjuncts.size(); ++conjunct_index) {
826
41.6k
        const auto& conjunct = _conjuncts[conjunct_index];
827
41.6k
        DORIS_CHECK(conjunct != nullptr);
828
41.6k
        DORIS_CHECK(conjunct->root() != nullptr);
829
        // `_table_filters` omits expressions without slot references, but such an expression still
830
        // occupies a position in the row-level conjunct order. Record how many localized filters
831
        // precede the first unsafe original conjunct so constant pruning cannot jump over a
832
        // slotless non-deterministic/error-preserving barrier. An unsafe predicate is either kept
833
        // on TableReader's post-materialization path by a standalone caller or carried only for
834
        // analysis when FileScannerV2 owns the ordered suffix.
835
41.6k
        if (in_safe_prefix && !is_safe_to_pre_execute(conjunct)) {
836
9.07k
            in_safe_prefix = false;
837
9.07k
        }
838
41.6k
        const size_t filters_before = _table_filters.size();
839
41.6k
        RETURN_IF_ERROR(
840
41.6k
                build_table_filters_from_conjunct(conjunct, _runtime_state, &_table_filters));
841
83.3k
        for (size_t filter_index = filters_before; filter_index < _table_filters.size();
842
41.6k
             ++filter_index) {
843
41.6k
            _table_filters[filter_index].source_conjunct_index = conjunct_index;
844
41.6k
            _table_filters[filter_index].can_localize = in_safe_prefix;
845
41.6k
        }
846
41.6k
        if (in_safe_prefix) {
847
30.6k
            _constant_pruning_safe_filter_count = _table_filters.size();
848
30.6k
        }
849
41.6k
    }
850
62.5k
    return Status::OK();
851
62.5k
}
852
853
3
Status TableReader::_prepare_all_conjuncts_as_remaining() {
854
    // Expression contexts carry mutable state (for example sequence/stateful functions). Select
855
    // from the TableReader-owned contexts instead of reopening clones for every split.
856
3
    _remaining_conjuncts.assign(
857
3
            _conjuncts.begin(),
858
3
            _conjuncts.begin() + cast_set<ptrdiff_t>(_table_reader_owned_conjunct_count));
859
3
    return Status::OK();
860
3
}
861
862
Status TableReader::_prepare_remaining_conjuncts(
863
61.8k
        const FilterLocalizationResult& localization_result) {
864
61.8k
    DORIS_CHECK(localization_result.localized_filters.size() == _table_filters.size());
865
61.8k
    std::vector<bool> localized_conjuncts(_conjuncts.size(), false);
866
102k
    for (size_t filter_index = 0; filter_index < _table_filters.size(); ++filter_index) {
867
40.9k
        if (!localization_result.localized_filters[filter_index]) {
868
15.0k
            continue;
869
15.0k
        }
870
25.9k
        const size_t source_index = _table_filters[filter_index].source_conjunct_index;
871
25.9k
        DORIS_CHECK(source_index < localized_conjuncts.size());
872
25.9k
        localized_conjuncts[source_index] = true;
873
25.9k
    }
874
875
61.8k
    _remaining_conjuncts.clear();
876
91.8k
    for (size_t conjunct_index = 0; conjunct_index < _table_reader_owned_conjunct_count;
877
61.8k
         ++conjunct_index) {
878
30.0k
        if (localized_conjuncts[conjunct_index]) {
879
26.0k
            continue;
880
26.0k
        }
881
4.03k
        _remaining_conjuncts.push_back(_conjuncts[conjunct_index]);
882
4.03k
    }
883
61.8k
    return Status::OK();
884
61.8k
}
885
886
139k
Status TableReader::_filter_remaining_conjuncts(Block* block, size_t* rows) {
887
139k
    DORIS_CHECK(block != nullptr);
888
139k
    DORIS_CHECK(rows != nullptr);
889
139k
    if (*rows == 0 || _remaining_conjuncts.empty()) {
890
132k
        return Status::OK();
891
132k
    }
892
6.34k
    SCOPED_TIMER(_profile.residual_filter_timer);
893
6.34k
    const size_t rows_before_filter = *rows;
894
6.34k
    auto status = VExprContext::filter_block(_remaining_conjuncts, block, block->columns());
895
6.34k
    if (!status.ok() && _format == FileFormat::ORC) {
896
0
        status.prepend("Orc row reader nextBatch failed. reason = ");
897
0
    }
898
6.34k
    RETURN_IF_ERROR(status);
899
6.34k
    *rows = block->columns() == 0 ? rows_before_filter : block->rows();
900
6.34k
    if (_io_ctx != nullptr) {
901
6.33k
        _io_ctx->predicate_filtered_rows += rows_before_filter - *rows;
902
6.33k
    }
903
6.34k
    return Status::OK();
904
6.34k
}
905
906
61.7k
Status TableReader::_open_local_filter_exprs(const FileScanRequest& file_request) {
907
61.7k
    RowDescriptor row_desc;
908
61.7k
    for (const auto& conjunct : file_request.conjuncts) {
909
25.9k
        RETURN_IF_ERROR(conjunct->prepare(_runtime_state, row_desc));
910
25.9k
        RETURN_IF_ERROR(conjunct->open(_runtime_state));
911
25.9k
    }
912
61.7k
    for (const auto& delete_conjunct : file_request.delete_conjuncts) {
913
9.31k
        RETURN_IF_ERROR(delete_conjunct->prepare(_runtime_state, row_desc));
914
9.31k
        RETURN_IF_ERROR(delete_conjunct->open(_runtime_state));
915
9.31k
    }
916
61.7k
    return Status::OK();
917
61.7k
}
918
919
61.9k
bool TableReader::_should_enable_condition_cache(const FileScanRequest& file_request) const {
920
61.9k
    if (_condition_cache_digest == 0 || _push_down_agg_type == TPushAggOp::type::COUNT ||
921
61.9k
        _current_file_description == std::nullopt || _data_reader.reader == nullptr) {
922
14.4k
        return false;
923
14.4k
    }
924
    // Condition cache is populated by file readers after evaluating file-local row-level
925
    // conjuncts. Metadata pruning can skip row groups/pages, but it does not produce a per-row
926
    // survivor bitmap that can safely populate the cache.
927
47.5k
    if (file_request.conjuncts.empty()) {
928
38.5k
        return false;
929
38.5k
    }
930
    // Delete files/deletion vectors are table-format state. They may change independently of the
931
    // data file path/mtime/size used by the external cache key, so caching their result can become
932
    // stale. Keep delete filtering enabled, but do not read or write condition cache.
933
8.99k
    if (_delete_rows != nullptr || _deletion_vector != nullptr ||
934
8.99k
        !file_request.delete_conjuncts.empty()) {
935
1.31k
        return false;
936
1.31k
    }
937
    // Only scanner-driven splits provide a digest rebuilt from the exact RF snapshot. Keep the
938
    // conservative behavior for standalone TableReader callers: their initial digest may describe
939
    // only static predicate P and must not store P AND RF under that key.
940
7.68k
    return _condition_cache_digest_covers_current_split ||
941
7.68k
           !contains_runtime_filter(file_request.conjuncts);
942
8.99k
}
943
944
62.0k
Status TableReader::_init_reader_condition_cache(const FileScanRequest& file_request) {
945
62.0k
    _condition_cache = nullptr;
946
62.0k
    _condition_cache_ctx = nullptr;
947
62.0k
    if (!_should_enable_condition_cache(file_request)) {
948
54.1k
        return Status::OK();
949
54.1k
    }
950
951
7.85k
    auto* cache = segment_v2::ConditionCache::instance();
952
7.85k
    if (cache == nullptr) {
953
0
        return Status::OK();
954
0
    }
955
7.85k
    const auto& file = *_current_file_description;
956
7.85k
    _condition_cache_key = segment_v2::ConditionCache::ExternalCacheKey(
957
7.85k
            file.path, file.mtime, file.file_size, _condition_cache_digest, file.range_start_offset,
958
7.85k
            file.range_size,
959
7.85k
            segment_v2::ConditionCache::ExternalCacheKey::BASE_GRANULE_AWARE_VERSION);
960
961
7.85k
    segment_v2::ConditionCacheHandle handle;
962
7.85k
    const bool condition_cache_hit = cache->lookup(_condition_cache_key, &handle);
963
7.85k
    if (condition_cache_hit) {
964
2.58k
        _condition_cache = handle.get_filter_result();
965
2.58k
        ++_condition_cache_hit_count;
966
5.26k
    } else {
967
5.26k
        const int64_t total_rows = _data_reader.reader->get_total_rows();
968
5.26k
        if (total_rows <= 0) {
969
2.29k
            return Status::OK();
970
2.29k
        }
971
        // Add one guard granule for split ranges that start in the middle of a granule. A guard
972
        // false bit beyond the real range never overlaps real rows, but avoids boundary overflow
973
        // when a reader marks the last partial granule.
974
2.97k
        const size_t num_granules = (total_rows + ConditionCacheContext::GRANULE_SIZE - 1) /
975
2.97k
                                    ConditionCacheContext::GRANULE_SIZE;
976
2.97k
        _condition_cache = std::make_shared<std::vector<bool>>(num_granules + 1, false);
977
2.97k
    }
978
979
5.55k
    if (_condition_cache != nullptr) {
980
5.48k
        _condition_cache_ctx = std::make_shared<ConditionCacheContext>();
981
5.48k
        _condition_cache_ctx->is_hit = condition_cache_hit;
982
5.48k
        _condition_cache_ctx->filter_result = _condition_cache;
983
5.48k
        _condition_cache_ctx->num_granules = _condition_cache->size();
984
5.48k
        if (condition_cache_hit) {
985
2.58k
            _condition_cache_ctx->base_granule = handle.get_base_granule();
986
2.58k
        }
987
5.48k
        _data_reader.reader->set_condition_cache_context(_condition_cache_ctx);
988
5.48k
    }
989
5.55k
    return Status::OK();
990
7.85k
}
991
992
62.7k
void TableReader::_finalize_reader_condition_cache() {
993
62.7k
    if (_condition_cache_ctx == nullptr || _condition_cache_ctx->is_hit) {
994
59.8k
        _condition_cache = nullptr;
995
59.8k
        _condition_cache_ctx = nullptr;
996
59.8k
        return;
997
59.8k
    }
998
    // LIMIT or scanner cancellation may close a reader before all selected row ranges are visited.
999
    // Unvisited granules remain false in a MISS bitmap, so inserting a partial bitmap would make a
1000
    // later HIT skip valid rows. Only publish cache entries after the physical reader reaches EOF.
1001
2.91k
    if (!_current_reader_reached_eof) {
1002
44
        _condition_cache = nullptr;
1003
44
        _condition_cache_ctx = nullptr;
1004
44
        return;
1005
44
    }
1006
2.86k
    DORIS_CHECK(_condition_cache_ctx->num_granules <= _condition_cache->size());
1007
2.86k
    _condition_cache->resize(_condition_cache_ctx->num_granules);
1008
2.86k
    segment_v2::ConditionCache::instance()->insert(
1009
2.86k
            _condition_cache_key, std::move(_condition_cache), _condition_cache_ctx->base_granule);
1010
2.86k
    _condition_cache = nullptr;
1011
2.86k
    _condition_cache_ctx = nullptr;
1012
2.86k
}
1013
1014
122k
Status TableReader::create_next_reader(bool* eos) {
1015
122k
    SCOPED_TIMER(_profile.create_reader_timer);
1016
122k
    DCHECK(_data_reader.reader == nullptr);
1017
122k
    if (_current_task == nullptr) {
1018
59.9k
        *eos = true;
1019
59.9k
        return Status::OK();
1020
59.9k
    }
1021
1022
62.2k
    RETURN_IF_ERROR(create_file_reader(&_data_reader.reader));
1023
62.2k
    DORIS_CHECK(_data_reader.reader != nullptr);
1024
62.2k
    if (_batch_size > 0) {
1025
62.0k
        _data_reader.reader->set_batch_size(_batch_size);
1026
62.0k
    }
1027
62.2k
    Status st;
1028
62.2k
    {
1029
62.2k
        SCOPED_TIMER(_profile.file_reader_total_timer);
1030
62.2k
        SCOPED_TIMER(_profile.file_reader_init_timer);
1031
62.2k
        st = _data_reader.reader->init(_runtime_state);
1032
62.2k
    }
1033
62.2k
    if (!st.ok()) {
1034
3
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
1035
0
            *eos = true;
1036
0
            _data_reader.reader.reset();
1037
0
            return Status::OK();
1038
0
        }
1039
3
        return st;
1040
3
    }
1041
62.2k
    st = open_reader();
1042
62.2k
    if (!st.ok()) {
1043
14
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
1044
0
            *eos = true;
1045
0
            _data_reader.reader.reset();
1046
0
            return Status::OK();
1047
0
        }
1048
14
        return st;
1049
14
    }
1050
62.2k
    if (_data_reader.reader == nullptr) {
1051
611
        *eos = _current_task == nullptr;
1052
611
        return Status::OK();
1053
611
    }
1054
61.5k
    *eos = false;
1055
61.5k
    return Status::OK();
1056
62.2k
}
1057
1058
62.0k
Status TableReader::create_file_reader(std::unique_ptr<FileReader>* reader) {
1059
62.0k
    DORIS_CHECK(reader != nullptr);
1060
62.0k
    const bool enable_mapping_timestamp_tz = _scan_params != nullptr &&
1061
62.0k
                                             _scan_params->__isset.enable_mapping_timestamp_tz &&
1062
62.2k
                                             _scan_params->enable_mapping_timestamp_tz;
1063
62.0k
    const bool enable_mapping_varbinary = _scan_params != nullptr &&
1064
62.2k
                                          _scan_params->__isset.enable_mapping_varbinary &&
1065
62.3k
                                          _scan_params->enable_mapping_varbinary;
1066
62.0k
    if (_format == FileFormat::PARQUET) {
1067
        // V2 must honor the scan contract directly; otherwise Hive STRING columns backed by an
1068
        // unannotated BYTE_ARRAY are silently exposed as VARBINARY and predicate bytes no longer
1069
        // match the table type.
1070
32.1k
        *reader = std::make_unique<format::parquet::ParquetReader>(
1071
32.1k
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1072
32.1k
                _global_rowid_context, enable_mapping_timestamp_tz, enable_mapping_varbinary);
1073
32.1k
        return Status::OK();
1074
32.1k
    }
1075
29.9k
    if (_format == FileFormat::ORC) {
1076
23.8k
        *reader = std::make_unique<format::orc::OrcReader>(
1077
23.8k
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1078
23.8k
                _global_rowid_context, enable_mapping_timestamp_tz);
1079
23.8k
        return Status::OK();
1080
23.8k
    }
1081
6.01k
    if (_format == FileFormat::CSV) {
1082
804
        if (_file_slot_descs == nullptr) {
1083
0
            return Status::InvalidArgument("CSV reader requires file slot descriptors");
1084
0
        }
1085
        // CSV has no embedded schema. TableReader owns table-level mapping, while CsvReader needs
1086
        // only the physical file slots plus scan text parameters to build a file-local schema.
1087
        // Non-file columns such as partitions/defaults/virtual row ids are intentionally excluded
1088
        // from `_file_slot_descs` and are materialized during finalize_chunk().
1089
804
        *reader = std::make_unique<format::csv::CsvReader>(
1090
804
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1091
804
                _scan_params, *_file_slot_descs, _current_range_compress_type,
1092
804
                _current_range_load_id);
1093
804
        return Status::OK();
1094
804
    }
1095
5.21k
    if (_format == FileFormat::TEXT) {
1096
4.71k
        if (_file_slot_descs == nullptr) {
1097
0
            return Status::InvalidArgument("Text reader requires file slot descriptors");
1098
0
        }
1099
        // Text files have no embedded schema. As with CSV, TableReader handles table-level mapping
1100
        // and only passes physical file slots to the v2 TextReader.
1101
4.71k
        *reader = std::make_unique<format::text::TextReader>(
1102
4.71k
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1103
4.71k
                _scan_params, *_file_slot_descs, _current_range_compress_type,
1104
4.71k
                _current_range_load_id);
1105
4.71k
        return Status::OK();
1106
4.71k
    }
1107
588
    if (_format == FileFormat::JSON) {
1108
588
        if (_file_slot_descs == nullptr) {
1109
0
            return Status::InvalidArgument("JSON reader requires file slot descriptors");
1110
0
        }
1111
588
        *reader = std::make_unique<format::json::JsonReader>(
1112
588
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
1113
588
                _scan_params, _current_file_range_desc, *_file_slot_descs,
1114
588
                _current_range_compress_type, _current_range_load_id);
1115
588
        return Status::OK();
1116
588
    }
1117
18.4E
    if (_format == FileFormat::NATIVE) {
1118
0
        *reader = std::make_unique<format::native::NativeReader>(
1119
0
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile);
1120
0
        return Status::OK();
1121
0
    }
1122
18.4E
    return Status::NotSupported("TableReader does not support file format {}",
1123
18.4E
                                file_format_to_string(_format));
1124
18.4E
}
1125
1126
62.6k
std::unique_ptr<io::FileDescription> create_file_description(const TFileRangeDesc& range) {
1127
62.6k
    auto file_description = std::make_unique<io::FileDescription>();
1128
62.6k
    file_description->path = range.path;
1129
18.4E
    file_description->file_size = range.__isset.file_size ? range.file_size : -1;
1130
18.4E
    file_description->mtime = range.__isset.modification_time ? range.modification_time : 0;
1131
18.4E
    file_description->range_start_offset = range.__isset.start_offset ? range.start_offset : 0;
1132
18.4E
    file_description->range_size = range.__isset.size ? range.size : -1;
1133
62.6k
    if (range.__isset.fs_name) {
1134
31.9k
        file_description->fs_name = range.fs_name;
1135
31.9k
    }
1136
62.6k
    if (range.__isset.file_cache_admission) {
1137
62.6k
        file_description->file_cache_admission = range.file_cache_admission;
1138
62.6k
    }
1139
62.6k
    return file_description;
1140
62.6k
}
1141
1142
63.0k
Status TableReader::prepare_split(const SplitReadOptions& options) {
1143
63.0k
    SCOPED_TIMER(_profile.total_timer);
1144
63.0k
    SCOPED_TIMER(_profile.prepare_split_timer);
1145
63.0k
    _current_split_pruned = false;
1146
    // Predicate localization belongs to the physical schema of one split. Clear the previous
1147
    // ownership before any early return so a pruned or failed split cannot leak it to the next one.
1148
63.0k
    _remaining_conjuncts.clear();
1149
63.0k
    _all_runtime_filters_applied_for_split = options.all_runtime_filters_applied;
1150
63.0k
    _condition_cache_digest_covers_current_split = options.condition_cache_digest.has_value();
1151
63.0k
    if (options.condition_cache_digest.has_value()) {
1152
        // The split snapshot may include RFs that arrived after TableReader::init(). Use the digest
1153
        // computed from that exact snapshot. Example: an initial P digest must not be used to store
1154
        // the bitmap for P AND late RF{7, 9}; the scanner supplies digest(P AND RF{7, 9}) here.
1155
63.0k
        _condition_cache_digest = *options.condition_cache_digest;
1156
63.0k
    } else {
1157
        // An explicit scanner digest is split-scoped. Restore the init-time digest when a later
1158
        // standalone split omits it instead of leaking the previous split's RF payload into its key.
1159
44
        _condition_cache_digest = _initial_condition_cache_digest;
1160
44
    }
1161
63.0k
    if (options.conjuncts.has_value()) {
1162
2
        RETURN_IF_ERROR(_replace_conjuncts(*options.conjuncts));
1163
2
    }
1164
    // Update to current split format to handle ORC/PARQUET files in one table.
1165
63.0k
    _format = options.current_split_format;
1166
63.0k
    _partition_values = std::move(options.partition_values);
1167
63.0k
    _current_task.reset();
1168
63.0k
    _current_file_description.reset();
1169
63.0k
    _current_file_range_desc = options.current_range;
1170
63.0k
    _current_range_compress_type = options.current_range.__isset.compress_type
1171
63.0k
                                           ? options.current_range.compress_type
1172
63.0k
                                           : TFileCompressType::UNKNOWN;
1173
63.0k
    _current_range_load_id = options.current_range.__isset.load_id
1174
63.0k
                                     ? std::make_optional(options.current_range.load_id)
1175
63.0k
                                     : std::nullopt;
1176
63.0k
    _global_rowid_context = options.global_rowid_context;
1177
63.0k
    _delete_rows = nullptr;
1178
63.0k
    _deletion_vector = nullptr;
1179
63.0k
    _aggregate_pushdown_tried = false;
1180
63.0k
    _remaining_table_level_count = -1;
1181
63.0k
    _remaining_file_level_count = -1;
1182
63.0k
    _current_split_uses_metadata_count = false;
1183
63.0k
    _current_reader_reached_eof = false;
1184
63.0k
    RETURN_IF_ERROR(_evaluate_partition_prune_conjuncts(options.partition_prune_conjuncts,
1185
63.0k
                                                        &_current_split_pruned));
1186
63.0k
    if (_current_split_pruned) {
1187
283
        COUNTER_UPDATE(_profile.runtime_filter_partition_pruned_range_counter, 1);
1188
283
        return Status::OK();
1189
283
    }
1190
62.7k
    _current_task = std::make_unique<ScanTask>();
1191
62.7k
    _current_task->data_file = create_file_description(options.current_range);
1192
62.7k
    _current_file_description = *_current_task->data_file;
1193
    // A table-level row count is only equivalent to scanning the split when no row predicate is
1194
    // active and no predicate can arrive later. The metadata path can return several batches for
1195
    // one split; after its first synthetic batch there is no way to recover the real rows if a
1196
    // runtime filter arrives before the next scheduler turn.
1197
    // Table-level metadata only contains the number of rows; it cannot evaluate an expression or
1198
    // the NULL state of a COUNT argument. Require the new FE's explicit empty argument list, which
1199
    // means COUNT(*)/COUNT(1). A non-empty list means COUNT(col), while nullopt comes from an old FE
1200
    // whose COUNT semantics are unknown during a BE-first rolling upgrade.
1201
62.7k
    if (_push_down_agg_type == TPushAggOp::type::COUNT && _push_down_count_columns.has_value() &&
1202
62.7k
        _push_down_count_columns->empty() && options.all_runtime_filters_applied &&
1203
62.7k
        _conjuncts.empty() && options.current_range.__isset.table_format_params &&
1204
62.7k
        options.current_range.table_format_params.__isset.table_level_row_count) {
1205
2.10k
        DORIS_CHECK(options.current_range.table_format_params.table_level_row_count >= -1);
1206
2.10k
        _remaining_table_level_count =
1207
2.10k
                options.current_range.table_format_params.table_level_row_count;
1208
2.10k
        _current_split_uses_metadata_count = _is_table_level_count_active();
1209
2.10k
    }
1210
62.7k
    if (_is_table_level_count_active()) {
1211
150
        return Status::OK();
1212
150
    }
1213
62.6k
    return _parse_delete_predicates(options);
1214
62.7k
}
1215
1216
Status TableReader::_evaluate_partition_prune_conjuncts(const VExprContextSPtrs& conjuncts,
1217
62.9k
                                                        bool* can_filter_all) {
1218
62.9k
    DORIS_CHECK(can_filter_all != nullptr);
1219
62.9k
    SCOPED_TIMER(_profile.runtime_filter_partition_prune_timer);
1220
62.9k
    *can_filter_all = false;
1221
62.9k
    if (conjuncts.empty() || _partition_values.empty()) {
1222
59.6k
        return Status::OK();
1223
59.6k
    }
1224
1225
3.31k
    VExprContextSPtrs partition_conjuncts;
1226
4.52k
    for (const auto& conjunct : conjuncts) {
1227
4.52k
        DORIS_CHECK(conjunct != nullptr);
1228
4.52k
        DORIS_CHECK(conjunct->root() != nullptr);
1229
        // Keep only the safe prefix of the original conjunct order. If an unsafe conjunct is
1230
        // skipped, a later predicate could prune the split before the unsafe one reaches its
1231
        // normal row-level evaluation point.
1232
4.52k
        if (!is_safe_to_pre_execute(conjunct)) {
1233
59
            break;
1234
59
        }
1235
4.47k
        std::set<GlobalIndex> global_indices;
1236
4.47k
        collect_global_indices(conjunct->root(), &global_indices);
1237
4.47k
        if (global_indices.empty()) {
1238
0
            continue;
1239
0
        }
1240
4.53k
        const bool partition_only = std::ranges::all_of(global_indices, [&](GlobalIndex index) {
1241
4.53k
            if (index.value() >= _projected_columns.size()) {
1242
0
                return false;
1243
0
            }
1244
4.53k
            const auto& column = _projected_columns[index.value()];
1245
4.53k
            return column.is_partition_key &&
1246
4.53k
                   find_partition_value(column, _partition_values) != nullptr;
1247
4.53k
        });
1248
4.47k
        if (partition_only) {
1249
2.88k
            partition_conjuncts.push_back(conjunct);
1250
2.88k
        }
1251
4.47k
    }
1252
3.31k
    if (partition_conjuncts.empty()) {
1253
1.21k
        return Status::OK();
1254
1.21k
    }
1255
1256
2.09k
    Block block;
1257
2.09k
    RETURN_IF_ERROR(_build_partition_prune_block(&block));
1258
2.09k
    RowDescriptor row_desc;
1259
2.88k
    for (const auto& conjunct : partition_conjuncts) {
1260
2.88k
        RETURN_IF_ERROR(conjunct->prepare(_runtime_state, row_desc));
1261
2.88k
        RETURN_IF_ERROR(conjunct->open(_runtime_state));
1262
2.88k
    }
1263
2.09k
    IColumn::Filter result_filter(block.rows(), 1);
1264
2.09k
    return VExprContext::execute_conjuncts(partition_conjuncts, nullptr, &block, &result_filter,
1265
2.09k
                                           can_filter_all);
1266
2.09k
}
1267
1268
116k
bool TableReader::is_safe_to_pre_execute(const VExprContextSPtr& conjunct) {
1269
116k
    DORIS_CHECK(conjunct != nullptr);
1270
116k
    DORIS_CHECK(conjunct->root() != nullptr);
1271
116k
    const auto root = conjunct->root();
1272
116k
    const auto impl = root->get_impl();
1273
116k
    const auto predicate = impl != nullptr ? impl : root;
1274
    // Split pruning evaluates a predicate once before any file rows are read. Reordering
1275
    // non-deterministic or error-preserving expressions can change their row-level semantics,
1276
    // even when every referenced slot is a partition column or maps to a constant entry.
1277
116k
    return predicate->is_safe_to_execute_on_selected_rows();
1278
116k
}
1279
1280
2.18k
Status TableReader::_build_partition_prune_block(Block* block) const {
1281
2.18k
    DORIS_CHECK(block != nullptr);
1282
2.18k
    DORIS_CHECK(!_projected_columns.empty());
1283
2.18k
    block->clear();
1284
6.18k
    for (const auto& column : _projected_columns) {
1285
6.18k
        DORIS_CHECK(column.type != nullptr);
1286
6.18k
        ColumnPtr value_column = column.type->create_column_const_with_default_value(1);
1287
6.18k
        if (column.is_partition_key) {
1288
2.99k
            const auto* partition_value = find_partition_value(column, _partition_values);
1289
2.99k
            if (partition_value != nullptr) {
1290
2.99k
                value_column = column.type->create_column_const(1, *partition_value);
1291
2.99k
            }
1292
2.99k
        }
1293
6.18k
        block->insert({std::move(value_column), column.type, column.name});
1294
6.18k
    }
1295
2.18k
    return Status::OK();
1296
2.18k
}
1297
1298
62.4k
Status TableReader::_parse_delete_predicates(const SplitReadOptions& options) {
1299
62.4k
    DeleteFileDesc desc {.fs_name = options.current_range.fs_name};
1300
62.4k
    bool has_delete_file = false;
1301
62.4k
    RETURN_IF_ERROR(_parse_deletion_vector_file(options.current_range.table_format_params, &desc,
1302
62.4k
                                                &has_delete_file));
1303
62.4k
    if (has_delete_file) {
1304
4.24k
        DORIS_CHECK(options.cache != nullptr);
1305
4.24k
        Status create_status = Status::OK();
1306
1307
4.24k
        bool decoded_cache_hit = false;
1308
4.24k
        _deletion_vector = options.cache->get<DeletionVector>(
1309
4.24k
                desc.key,
1310
4.24k
                [&]() -> DeletionVector* {
1311
2.06k
                    auto deletion_vector = std::make_unique<DeletionVector>();
1312
1313
2.06k
                    DeletionVectorReader dv_reader(_runtime_state, _scanner_profile, *_scan_params,
1314
2.06k
                                                   desc, _io_ctx.get());
1315
2.06k
                    create_status = dv_reader.open();
1316
2.06k
                    if (!create_status.ok()) [[unlikely]] {
1317
0
                        return nullptr;
1318
0
                    }
1319
1320
2.06k
                    size_t bytes_read = desc.size;
1321
2.06k
                    std::vector<char> buffer(bytes_read);
1322
2.06k
                    DBUG_EXECUTE_IF("TableReader.parse_deletion_vector.io_error", {
1323
2.06k
                        create_status =
1324
2.06k
                                Status::IOError("injected format v2 deletion vector read failure");
1325
2.06k
                        return nullptr;
1326
2.06k
                    });
1327
2.05k
                    DBUG_EXECUTE_IF("TableReader.parse_deletion_vector.should_stop", {
1328
2.05k
                        create_status = Status::EndOfFile("stop read.");
1329
2.05k
                        return nullptr;
1330
2.05k
                    });
1331
2.05k
                    create_status =
1332
2.05k
                            dv_reader.read_at(desc.start_offset, {buffer.data(), bytes_read});
1333
2.05k
                    const auto& file_cache_stats = dv_reader.file_cache_statistics();
1334
2.05k
                    COUNTER_UPDATE(_profile.dv_file_cache_hit_count,
1335
2.05k
                                   file_cache_stats.num_local_io_total);
1336
2.05k
                    COUNTER_UPDATE(_profile.dv_file_cache_miss_count,
1337
2.05k
                                   file_cache_stats.num_remote_io_total);
1338
2.05k
                    COUNTER_UPDATE(_profile.dv_file_cache_peer_read_count,
1339
2.05k
                                   file_cache_stats.num_peer_io_total);
1340
2.05k
                    if (!create_status.ok()) [[unlikely]] {
1341
0
                        return nullptr;
1342
0
                    }
1343
1344
2.05k
                    const char* buf = buffer.data();
1345
2.05k
                    SCOPED_TIMER(_profile.parse_delete_file_time);
1346
2.05k
                    create_status = parse_deletion_vector(buf, bytes_read, desc.format,
1347
2.05k
                                                          deletion_vector.get());
1348
2.05k
                    if (!create_status.ok()) [[unlikely]] {
1349
1
                        return nullptr;
1350
1
                    }
1351
2.05k
                    COUNTER_UPDATE(_profile.num_delete_rows, deletion_vector->cardinality());
1352
2.05k
                    return deletion_vector.release();
1353
2.05k
                },
1354
4.24k
                &decoded_cache_hit);
1355
4.24k
        RETURN_IF_ERROR(create_status);
1356
4.23k
        COUNTER_UPDATE(decoded_cache_hit ? _profile.decoded_dv_cache_hit_count
1357
4.23k
                                         : _profile.decoded_dv_cache_miss_count,
1358
4.23k
                       1);
1359
4.23k
    }
1360
1361
62.4k
    return Status::OK();
1362
62.4k
}
1363
} // namespace doris::format