Coverage Report

Created: 2026-07-17 10:41

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/paimon_reader.h"
45
#include "format_v2/column_mapper.h"
46
#include "format_v2/delimited_text/csv_reader.h"
47
#include "format_v2/delimited_text/text_reader.h"
48
#include "format_v2/json/json_reader.h"
49
#include "format_v2/native/native_reader.h"
50
#include "format_v2/orc/orc_reader.h"
51
#include "format_v2/parquet/parquet_reader.h"
52
#include "runtime/file_scan_profile.h"
53
#include "storage/segment/condition_cache.h"
54
#include "util/debug_points.h"
55
#include "util/string_util.h"
56
57
namespace doris::format {
58
namespace {
59
60
template <typename T, typename Formatter>
61
61
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
62
61
    std::ostringstream out;
63
61
    out << "[";
64
70
    for (size_t i = 0; i < values.size(); ++i) {
65
9
        if (i > 0) {
66
3
            out << ", ";
67
3
        }
68
9
        out << formatter(values[i]);
69
9
    }
70
61
    out << "]";
71
61
    return out.str();
72
61
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_0EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
61
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
62
12
    std::ostringstream out;
63
12
    out << "[";
64
14
    for (size_t i = 0; i < values.size(); ++i) {
65
2
        if (i > 0) {
66
1
            out << ", ";
67
1
        }
68
2
        out << formatter(values[i]);
69
2
    }
70
12
    out << "]";
71
12
    return out.str();
72
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableFilterEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_1EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
61
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
62
12
    std::ostringstream out;
63
12
    out << "[";
64
13
    for (size_t i = 0; i < values.size(); ++i) {
65
1
        if (i > 0) {
66
0
            out << ", ";
67
0
        }
68
1
        out << formatter(values[i]);
69
1
    }
70
12
    out << "]";
71
12
    return out.str();
72
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
61
1
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
62
1
    std::ostringstream out;
63
1
    out << "[";
64
2
    for (size_t i = 0; i < values.size(); ++i) {
65
1
        if (i > 0) {
66
0
            out << ", ";
67
0
        }
68
1
        out << formatter(values[i]);
69
1
    }
70
1
    out << "]";
71
1
    return out.str();
72
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
61
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
62
12
    std::ostringstream out;
63
12
    out << "[";
64
13
    for (size_t i = 0; i < values.size(); ++i) {
65
1
        if (i > 0) {
66
0
            out << ", ";
67
0
        }
68
1
        out << formatter(values[i]);
69
1
    }
70
12
    out << "]";
71
12
    return out.str();
72
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_3EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
61
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
62
12
    std::ostringstream out;
63
12
    out << "[";
64
14
    for (size_t i = 0; i < values.size(); ++i) {
65
2
        if (i > 0) {
66
1
            out << ", ";
67
1
        }
68
2
        out << formatter(values[i]);
69
2
    }
70
12
    out << "]";
71
12
    return out.str();
72
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableReader15FileBlockColumnEZNKS3_12debug_stringB5cxx11EvE3$_4EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
61
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
62
12
    std::ostringstream out;
63
12
    out << "[";
64
14
    for (size_t i = 0; i < values.size(); ++i) {
65
2
        if (i > 0) {
66
1
            out << ", ";
67
1
        }
68
2
        out << formatter(values[i]);
69
2
    }
70
12
    out << "]";
71
12
    return out.str();
72
12
}
73
74
12
std::string file_format_to_string(FileFormat format) {
75
12
    switch (format) {
76
5
    case FileFormat::PARQUET:
77
5
        return "PARQUET";
78
1
    case FileFormat::ORC:
79
1
        return "ORC";
80
1
    case FileFormat::CSV:
81
1
        return "CSV";
82
1
    case FileFormat::JSON:
83
1
        return "JSON";
84
1
    case FileFormat::TEXT:
85
1
        return "TEXT";
86
1
    case FileFormat::JNI:
87
1
        return "JNI";
88
1
    case FileFormat::NATIVE:
89
1
        return "NATIVE";
90
1
    case FileFormat::ARROW:
91
1
        return "ARROW";
92
12
    }
93
0
    return "UNKNOWN";
94
12
}
95
96
12
std::string push_down_agg_to_string(TPushAggOp::type op) {
97
12
    switch (op) {
98
8
    case TPushAggOp::NONE:
99
8
        return "NONE";
100
1
    case TPushAggOp::COUNT:
101
1
        return "COUNT";
102
1
    case TPushAggOp::MINMAX:
103
1
        return "MINMAX";
104
1
    case TPushAggOp::MIX:
105
1
        return "MIX";
106
1
    case TPushAggOp::COUNT_ON_INDEX:
107
1
        return "COUNT_ON_INDEX";
108
12
    }
109
0
    return "UNKNOWN";
110
12
}
111
112
12
std::string current_file_debug_string(const std::unique_ptr<ScanTask>& task) {
113
12
    if (task == nullptr || task->data_file == nullptr) {
114
11
        return "null";
115
11
    }
116
1
    const auto& file = *task->data_file;
117
1
    std::ostringstream out;
118
1
    out << "FileDescription{path=" << file.path << ", file_size=" << file.file_size
119
1
        << ", range_start_offset=" << file.range_start_offset << ", range_size=" << file.range_size
120
1
        << ", mtime=" << file.mtime << ", fs_name=" << file.fs_name
121
1
        << ", is_immutable=" << file.is_immutable
122
1
        << ", file_cache_admission=" << file.file_cache_admission << "}";
123
1
    return out.str();
124
12
}
125
126
12
std::string partition_values_debug_string(const std::map<std::string, Field>& partition_values) {
127
12
    std::ostringstream out;
128
12
    out << "{";
129
12
    size_t idx = 0;
130
12
    for (const auto& [key, _] : partition_values) {
131
1
        if (idx++ > 0) {
132
0
            out << ", ";
133
0
        }
134
1
        out << key;
135
1
    }
136
12
    out << "}";
137
12
    return out.str();
138
12
}
139
140
17
const schema::external::TField* get_field_ptr(const schema::external::TFieldPtr& field_ptr) {
141
17
    if (!field_ptr.__isset.field_ptr || field_ptr.field_ptr == nullptr) {
142
0
        return nullptr;
143
0
    }
144
17
    return field_ptr.field_ptr.get();
145
17
}
146
147
4
bool external_field_matches_name(const schema::external::TField& field, const std::string& name) {
148
4
    if (field.__isset.name && to_lower(field.name) == to_lower(name)) {
149
0
        return true;
150
0
    }
151
4
    return field.__isset.name_mapping &&
152
4
           std::ranges::any_of(field.name_mapping, [&](const std::string& alias) {
153
4
               return to_lower(alias) == to_lower(name);
154
4
           });
155
4
}
156
157
DataTypePtr find_struct_child_type_by_external_field(const DataTypeStruct& struct_type,
158
2
                                                     const schema::external::TField& field) {
159
3
    for (size_t field_idx = 0; field_idx < struct_type.get_elements().size(); ++field_idx) {
160
3
        if (external_field_matches_name(field, struct_type.get_element_name(field_idx))) {
161
2
            return struct_type.get_element(field_idx);
162
2
        }
163
3
    }
164
0
    return nullptr;
165
2
}
166
167
DataTypePtr restore_current_primitive_type(const schema::external::TField& field,
168
12
                                           DataTypePtr fallback_type) {
169
12
    if (!field.__isset.type) {
170
8
        return fallback_type;
171
8
    }
172
4
    const auto primitive_type = thrift_to_type(field.type.type);
173
4
    if (is_complex_type(primitive_type)) {
174
0
        return fallback_type;
175
0
    }
176
    // The delete file can expose an older physical type, but initial defaults belong to the
177
    // current table field. Restore that type from FE before parsing the default and let the table
178
    // reader apply the normal promotion cast to the delete-key type.
179
4
    return DataTypeFactory::instance().create_data_type(
180
4
            primitive_type, false, field.type.__isset.precision ? field.type.precision : 0,
181
4
            field.type.__isset.scale ? field.type.scale : 0,
182
4
            field.type.__isset.len ? field.type.len : -1);
183
4
}
184
185
ColumnDefinition build_schema_column_from_external_field(const schema::external::TField& field,
186
12
                                                         DataTypePtr type) {
187
12
    type = restore_current_primitive_type(field, std::move(type));
188
12
    ColumnDefinition column {
189
12
            .identifier = field.__isset.id ? Field::create_field<TYPE_INT>(field.id) : Field {},
190
12
            .name = field.__isset.name ? field.name : "",
191
12
            .name_mapping =
192
12
                    field.__isset.name_mapping ? field.name_mapping : std::vector<std::string> {},
193
12
            .type = std::move(type),
194
12
            .children = {},
195
12
            .default_expr = nullptr,
196
12
            .initial_default_value = field.__isset.initial_default_value
197
12
                                             ? std::make_optional(field.initial_default_value)
198
12
                                             : std::nullopt,
199
12
            .initial_default_value_is_base64 = field.__isset.initial_default_value_is_base64 &&
200
12
                                               field.initial_default_value_is_base64,
201
12
            .is_partition_key = false,
202
12
    };
203
12
    if (column.type == nullptr || !field.__isset.nestedField) {
204
9
        return column;
205
9
    }
206
207
3
    const auto nested_type = remove_nullable(column.type);
208
3
    switch (nested_type->get_primitive_type()) {
209
1
    case TYPE_STRUCT: {
210
1
        if (!field.nestedField.__isset.struct_field ||
211
1
            !field.nestedField.struct_field.__isset.fields) {
212
0
            return column;
213
0
        }
214
1
        const auto& struct_type = assert_cast<const DataTypeStruct&>(*nested_type);
215
2
        for (const auto& child_ptr : field.nestedField.struct_field.fields) {
216
2
            const auto* child_field = get_field_ptr(child_ptr);
217
2
            if (child_field == nullptr || !child_field->__isset.name) {
218
0
                continue;
219
0
            }
220
2
            auto child_type = find_struct_child_type_by_external_field(struct_type, *child_field);
221
2
            if (child_type == nullptr) {
222
0
                continue;
223
0
            }
224
2
            column.children.push_back(
225
2
                    build_schema_column_from_external_field(*child_field, child_type));
226
2
        }
227
1
        break;
228
1
    }
229
1
    case TYPE_ARRAY: {
230
1
        if (!field.nestedField.__isset.array_field ||
231
1
            !field.nestedField.array_field.__isset.item_field) {
232
0
            return column;
233
0
        }
234
1
        const auto* item_field = get_field_ptr(field.nestedField.array_field.item_field);
235
1
        if (item_field == nullptr) {
236
0
            return column;
237
0
        }
238
1
        const auto& array_type = assert_cast<const DataTypeArray&>(*nested_type);
239
1
        auto child =
240
1
                build_schema_column_from_external_field(*item_field, array_type.get_nested_type());
241
1
        child.name = "element";
242
1
        if (child.has_identifier_name()) {
243
0
            child.identifier = Field::create_field<TYPE_STRING>(child.name);
244
0
        }
245
1
        column.children.push_back(std::move(child));
246
1
        break;
247
1
    }
248
1
    case TYPE_MAP: {
249
1
        if (!field.nestedField.__isset.map_field ||
250
1
            !field.nestedField.map_field.__isset.key_field ||
251
1
            !field.nestedField.map_field.__isset.value_field) {
252
0
            return column;
253
0
        }
254
1
        const auto& map_type = assert_cast<const DataTypeMap&>(*nested_type);
255
1
        const auto* key_field = get_field_ptr(field.nestedField.map_field.key_field);
256
1
        if (key_field != nullptr) {
257
1
            auto child =
258
1
                    build_schema_column_from_external_field(*key_field, map_type.get_key_type());
259
1
            child.name = "key";
260
1
            if (child.has_identifier_name()) {
261
0
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
262
0
            }
263
1
            column.children.push_back(std::move(child));
264
1
        }
265
1
        const auto* value_field = get_field_ptr(field.nestedField.map_field.value_field);
266
1
        if (value_field != nullptr) {
267
1
            auto child = build_schema_column_from_external_field(*value_field,
268
1
                                                                 map_type.get_value_type());
269
1
            child.name = "value";
270
1
            if (child.has_identifier_name()) {
271
0
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
272
0
            }
273
1
            column.children.push_back(std::move(child));
274
1
        }
275
1
        break;
276
1
    }
277
0
    default:
278
0
        break;
279
3
    }
280
3
    return column;
281
3
}
282
283
const schema::external::TField* find_external_root_field(const TFileScanRangeParams* params,
284
8
                                                         const ColumnDefinition& column) {
285
8
    if (params == nullptr || !params->__isset.history_schema_info ||
286
8
        params->history_schema_info.empty()) {
287
7
        return nullptr;
288
7
    }
289
1
    const auto* schema = &params->history_schema_info.front();
290
1
    if (params->__isset.current_schema_id) {
291
2
        for (const auto& candidate_schema : params->history_schema_info) {
292
2
            if (candidate_schema.__isset.schema_id &&
293
2
                candidate_schema.schema_id == params->current_schema_id) {
294
1
                schema = &candidate_schema;
295
1
                break;
296
1
            }
297
2
        }
298
1
    }
299
1
    if (!schema->__isset.root_field || !schema->root_field.__isset.fields) {
300
0
        return nullptr;
301
0
    }
302
1
    for (const auto& field_ptr : schema->root_field.fields) {
303
1
        const auto* field = get_field_ptr(field_ptr);
304
1
        if (field == nullptr) {
305
0
            continue;
306
0
        }
307
1
        if (external_field_matches_name(*field, column.name)) {
308
1
            return field;
309
1
        }
310
1
    }
311
0
    return nullptr;
312
1
}
313
314
2
std::string expr_context_debug_string(const VExprContextSPtr& context) {
315
2
    if (context == nullptr) {
316
0
        return "null";
317
0
    }
318
2
    const auto root = context->root();
319
2
    if (root == nullptr) {
320
0
        return "VExprContext{root=null}";
321
0
    }
322
2
    std::ostringstream out;
323
2
    out << "VExprContext{root_name=" << root->expr_name() << ", root_debug=" << root->debug_string()
324
2
        << "}";
325
2
    return out.str();
326
2
}
327
328
1
std::string table_filter_debug_string(const TableFilter& filter) {
329
1
    std::ostringstream out;
330
1
    out << "TableFilter{conjunct=" << expr_context_debug_string(filter.conjunct)
331
1
        << ", global_indices="
332
1
        << join_table_reader_debug_strings(
333
1
                   filter.global_indices,
334
1
                   [](GlobalIndex global_index) { return std::to_string(global_index.value()); })
335
1
        << "}";
336
1
    return out.str();
337
1
}
338
339
4
bool contains_runtime_filter(const VExprContextSPtrs& conjuncts) {
340
4
    return std::ranges::any_of(conjuncts, [](const auto& conjunct) {
341
4
        return conjunct != nullptr && conjunct->root() != nullptr &&
342
4
               conjunct->root()->is_rf_wrapper();
343
4
    });
344
4
}
345
346
102
void collect_global_indices(const VExprSPtr& expr, std::set<GlobalIndex>* global_indices) {
347
102
    if (expr == nullptr) {
348
0
        return;
349
0
    }
350
102
    if (expr->is_rf_wrapper()) {
351
        // RuntimeFilterExpr wraps a real predicate expression but its own thrift node can still
352
        // look like SLOT_REF. Collect indices from the wrapped predicate; do not cast the wrapper
353
        // itself to VSlotRef.
354
7
        collect_global_indices(expr->get_impl(), global_indices);
355
7
        return;
356
7
    }
357
95
    if (expr->is_slot_ref()) {
358
32
        const auto* slot_ref = assert_cast<const VSlotRef*>(expr.get());
359
32
        DORIS_CHECK(slot_ref->column_id() >= 0);
360
32
        global_indices->insert(GlobalIndex(cast_set<size_t>(slot_ref->column_id())));
361
32
    }
362
95
    for (const auto& child : expr->children()) {
363
64
        collect_global_indices(child, global_indices);
364
64
    }
365
95
}
366
367
Status build_table_filters_from_conjunct(const VExprContextSPtr& conjunct, RuntimeState* state,
368
29
                                         std::vector<TableFilter>* table_filters) {
369
29
    if (conjunct == nullptr) {
370
0
        return Status::OK();
371
0
    }
372
29
    std::set<GlobalIndex> global_indices;
373
29
    collect_global_indices(conjunct->root(), &global_indices);
374
29
    if (!global_indices.empty()) {
375
29
        TableFilter table_filter;
376
29
        VExprSPtr filter_root;
377
29
        RETURN_IF_ERROR(clone_table_expr_tree(conjunct->root(), &filter_root));
378
29
        table_filter.conjunct = VExprContext::create_shared(std::move(filter_root));
379
30
        for (const auto global_index : global_indices) {
380
30
            table_filter.global_indices.push_back(global_index);
381
30
        }
382
29
        table_filters->push_back(std::move(table_filter));
383
29
    }
384
29
    return Status::OK();
385
29
}
386
387
Status parse_deletion_vector(const char* buf, size_t buffer_size, DeleteFileDesc::Format format,
388
5
                             DeletionVector* deletion_vector) {
389
5
    DORIS_CHECK(buf != nullptr);
390
5
    DORIS_CHECK(deletion_vector != nullptr);
391
5
    DORIS_CHECK(format == DeleteFileDesc::Format::PAIMON ||
392
5
                format == DeleteFileDesc::Format::ICEBERG);
393
394
5
    if (format == DeleteFileDesc::Format::PAIMON) {
395
1
        RETURN_IF_ERROR(decode_paimon_deletion_vector_buffer(buf, buffer_size, deletion_vector));
396
1
        return Status::OK();
397
1
    }
398
399
4
    return decode_iceberg_deletion_vector_buffer(buf, buffer_size, deletion_vector);
400
5
}
401
402
} // namespace
403
404
std::shared_ptr<io::FileSystemProperties> create_system_properties(
405
133
        const TFileScanRangeParams* scan_params) {
406
133
    auto system_properties = std::make_shared<io::FileSystemProperties>();
407
133
    if (scan_params == nullptr || !scan_params->__isset.file_type) {
408
98
        system_properties->system_type = TFileType::FILE_LOCAL;
409
98
        return system_properties;
410
98
    }
411
35
    system_properties->system_type = scan_params->file_type;
412
35
    system_properties->properties = scan_params->properties;
413
35
    system_properties->hdfs_params = scan_params->hdfs_params;
414
35
    if (scan_params->__isset.broker_addresses) {
415
0
        system_properties->broker_addresses.assign(scan_params->broker_addresses.begin(),
416
0
                                                   scan_params->broker_addresses.end());
417
0
    }
418
35
    return system_properties;
419
133
}
420
421
12
std::string TableReader::debug_string() const {
422
12
    std::ostringstream out;
423
12
    out << "TableReader{format=" << file_format_to_string(_format)
424
12
        << ", push_down_agg_type=" << push_down_agg_to_string(_push_down_agg_type)
425
12
        << ", aggregate_pushdown_tried=" << _aggregate_pushdown_tried
426
12
        << ", has_current_reader=" << (_data_reader.reader != nullptr)
427
12
        << ", has_current_task=" << (_current_task != nullptr)
428
12
        << ", current_file=" << current_file_debug_string(_current_task)
429
12
        << ", has_delete_rows=" << (_delete_rows != nullptr)
430
12
        << ", delete_row_count=" << (_delete_rows == nullptr ? 0 : _delete_rows->size())
431
12
        << ", has_deletion_vector=" << (_deletion_vector != nullptr)
432
12
        << ", deletion_vector_cardinality="
433
12
        << (_deletion_vector == nullptr ? 0 : _deletion_vector->cardinality())
434
12
        << ", has_system_properties=" << (_system_properties != nullptr) << ", system_type="
435
12
        << (_system_properties == nullptr ? static_cast<int>(TFileType::FILE_LOCAL)
436
12
                                          : static_cast<int>(_system_properties->system_type))
437
12
        << ", has_scan_params=" << (_scan_params != nullptr)
438
12
        << ", has_io_ctx=" << (_io_ctx != nullptr)
439
12
        << ", has_runtime_state=" << (_runtime_state != nullptr)
440
12
        << ", has_scanner_profile=" << (_scanner_profile != nullptr)
441
12
        << ", mapper_options=" << _mapper_options.debug_string() << ", projected_columns="
442
12
        << join_table_reader_debug_strings(
443
12
                   _projected_columns,
444
12
                   [](const ColumnDefinition& column) { return column.debug_string(); })
445
12
        << ", partition_values=" << partition_values_debug_string(_partition_values)
446
12
        << ", table_filters="
447
12
        << join_table_reader_debug_strings(
448
12
                   _table_filters,
449
12
                   [](const TableFilter& filter) { return table_filter_debug_string(filter); })
450
12
        << ", conjunct_count=" << _conjuncts.size() << ", conjuncts="
451
12
        << join_table_reader_debug_strings(_conjuncts,
452
12
                                           [](const VExprContextSPtr& conjunct) {
453
1
                                               return expr_context_debug_string(conjunct);
454
1
                                           })
455
12
        << ", file_schema="
456
12
        << join_table_reader_debug_strings(
457
12
                   _data_reader.file_schema,
458
12
                   [](const ColumnDefinition& field) { return field.debug_string(); })
459
12
        << ", file_block_layout="
460
12
        << join_table_reader_debug_strings(
461
12
                   _data_reader.file_block_layout,
462
12
                   [](const FileBlockColumn& column) {
463
2
                       std::ostringstream column_out;
464
2
                       column_out << "FileBlockColumn{file_column_id=" << column.file_column_id
465
2
                                  << ", name=" << column.name << ", type="
466
2
                                  << (column.type == nullptr ? "null" : column.type->get_name())
467
2
                                  << "}";
468
2
                       return column_out.str();
469
2
                   })
470
12
        << ", block_template_columns=" << _data_reader.block_template.columns()
471
12
        << ", column_mapper="
472
12
        << (_data_reader.column_mapper == nullptr ? "null"
473
12
                                                  : _data_reader.column_mapper->debug_string())
474
12
        << "}";
475
12
    return out.str();
476
12
}
477
478
Status TableReader::annotate_projected_column(const TFileScanSlotInfo& slot_info,
479
                                              ProjectedColumnBuildContext* context,
480
8
                                              ColumnDefinition* column) const {
481
8
    (void)slot_info;
482
8
    DORIS_CHECK(context != nullptr);
483
8
    DORIS_CHECK(column != nullptr);
484
8
    context->schema_column.reset();
485
8
    const auto* schema_field = find_external_root_field(context->scan_params, *column);
486
8
    if (schema_field == nullptr) {
487
7
        return Status::OK();
488
7
    }
489
1
    context->schema_column = build_schema_column_from_external_field(*schema_field, column->type);
490
1
    column->identifier = context->schema_column->identifier;
491
1
    column->name_mapping = context->schema_column->name_mapping;
492
1
    return Status::OK();
493
8
}
494
495
std::optional<ColumnDefinition> TableReader::_find_current_table_column_by_field_id(
496
9
        int32_t field_id, DataTypePtr type) const {
497
9
    if (_scan_params == nullptr || !_scan_params->__isset.history_schema_info ||
498
9
        _scan_params->history_schema_info.empty()) {
499
3
        return std::nullopt;
500
3
    }
501
6
    const auto* schema = &_scan_params->history_schema_info.front();
502
6
    if (_scan_params->__isset.current_schema_id) {
503
6
        for (const auto& candidate_schema : _scan_params->history_schema_info) {
504
6
            if (candidate_schema.__isset.schema_id &&
505
6
                candidate_schema.schema_id == _scan_params->current_schema_id) {
506
6
                schema = &candidate_schema;
507
6
                break;
508
6
            }
509
6
        }
510
6
    }
511
6
    if (!schema->__isset.root_field || !schema->root_field.__isset.fields) {
512
0
        return std::nullopt;
513
0
    }
514
11
    for (const auto& field_ptr : schema->root_field.fields) {
515
11
        const auto* field = get_field_ptr(field_ptr);
516
11
        if (field != nullptr && field->__isset.id && field->id == field_id) {
517
6
            return build_schema_column_from_external_field(*field, std::move(type));
518
6
        }
519
11
    }
520
0
    return std::nullopt;
521
6
}
522
523
133
Status TableReader::init(TableReadOptions&& options) {
524
133
    _scan_params = options.scan_params;
525
133
    _format = options.format;
526
133
    _io_ctx = options.io_ctx;
527
133
    _runtime_state = options.runtime_state;
528
133
    _scanner_profile = options.scanner_profile;
529
133
    _file_slot_descs = options.file_slot_descs;
530
133
    _push_down_agg_type = options.push_down_agg_type;
531
133
    _initial_condition_cache_digest = options.condition_cache_digest;
532
133
    _condition_cache_digest = _initial_condition_cache_digest;
533
133
    _projected_columns = std::move(options.projected_columns);
534
133
    _system_properties = create_system_properties(_scan_params);
535
133
    _mapper_options.mode = TableColumnMappingMode::BY_NAME;
536
133
    _conjuncts = std::move(options.conjuncts);
537
538
133
    if (_scanner_profile != nullptr) {
539
43
        const auto hierarchy = file_scan_profile::ensure_hierarchy(_scanner_profile);
540
43
        static const char* table_profile = file_scan_profile::TABLE_READER;
541
43
        static const char* file_reader_profile = file_scan_profile::FILE_READER;
542
43
        _profile.total_timer = hierarchy.table_reader;
543
43
        _profile.file_reader_total_timer = hierarchy.file_reader;
544
43
        _profile.init_timer =
545
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "InitTime", table_profile, 1);
546
43
        _profile.num_delete_files = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteFiles",
547
43
                                                                 TUnit::UNIT, table_profile, 1);
548
43
        _profile.num_delete_rows = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteRows",
549
43
                                                                TUnit::UNIT, table_profile, 1);
550
43
        _profile.parse_delete_file_time = ADD_CHILD_TIMER_WITH_LEVEL(
551
43
                _scanner_profile, "ParseDeleteFileTime", table_profile, 1);
552
43
        _profile.decoded_dv_cache_hit_count =
553
43
                ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "DeletionVectorDecodedCacheHitCount",
554
43
                                             TUnit::UNIT, table_profile, 1);
555
43
        _profile.decoded_dv_cache_miss_count = ADD_CHILD_COUNTER_WITH_LEVEL(
556
43
                _scanner_profile, "DeletionVectorDecodedCacheMissCount", TUnit::UNIT, table_profile,
557
43
                1);
558
43
        _profile.dv_file_cache_hit_count = ADD_CHILD_COUNTER_WITH_LEVEL(
559
43
                _scanner_profile, "DeletionVectorFileCacheHitCount", TUnit::UNIT, table_profile, 1);
560
43
        _profile.dv_file_cache_miss_count =
561
43
                ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "DeletionVectorFileCacheMissCount",
562
43
                                             TUnit::UNIT, table_profile, 1);
563
43
        _profile.dv_file_cache_peer_read_count = ADD_CHILD_COUNTER_WITH_LEVEL(
564
43
                _scanner_profile, "DeletionVectorFileCachePeerReadCount", TUnit::UNIT,
565
43
                table_profile, 1);
566
43
        _profile.exec_timer =
567
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "GetBlockTime", table_profile, 1);
568
43
        _profile.prepare_split_timer =
569
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PrepareSplitTime", table_profile, 1);
570
43
        _profile.finalize_timer =
571
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "FinalizeBlockTime", table_profile, 1);
572
43
        _profile.create_reader_timer =
573
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "CreateReaderTime", table_profile, 1);
574
43
        _profile.pushdown_agg_timer =
575
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PushDownAggTime", table_profile, 1);
576
43
        _profile.open_reader_timer =
577
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "OpenReaderTime", table_profile, 1);
578
43
        _profile.runtime_filter_partition_prune_timer = ADD_CHILD_TIMER_WITH_LEVEL(
579
43
                _scanner_profile, "FileScannerRuntimeFilterPartitionPruningTime", table_profile, 1);
580
43
        _profile.runtime_filter_partition_pruned_range_counter = ADD_CHILD_COUNTER_WITH_LEVEL(
581
43
                _scanner_profile, "RuntimeFilterPartitionPrunedRangeNum", TUnit::UNIT,
582
43
                table_profile, 1);
583
43
        _profile.close_timer =
584
43
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "CloseTime", table_profile, 1);
585
        // Lifecycle timer names remain globally unique because RuntimeProfile's visual hierarchy
586
        // does not namespace counters that share the same display parent.
587
43
        _profile.file_reader_init_timer = ADD_CHILD_TIMER_WITH_LEVEL(
588
43
                _scanner_profile, "FileReaderInitTime", file_reader_profile, 1);
589
43
        _profile.file_reader_schema_timer = ADD_CHILD_TIMER_WITH_LEVEL(
590
43
                _scanner_profile, "FileReaderGetSchemaTime", file_reader_profile, 1);
591
43
        _profile.file_reader_mapper_timer = ADD_CHILD_TIMER_WITH_LEVEL(
592
43
                _scanner_profile, "FileReaderCreateColumnMapperTime", file_reader_profile, 1);
593
43
        _profile.file_reader_open_timer = ADD_CHILD_TIMER_WITH_LEVEL(
594
43
                _scanner_profile, "FileReaderOpenTime", file_reader_profile, 1);
595
43
        _profile.file_reader_get_block_timer = ADD_CHILD_TIMER_WITH_LEVEL(
596
43
                _scanner_profile, "FileReaderGetBlockTime", file_reader_profile, 1);
597
43
        _profile.file_reader_aggregate_timer = ADD_CHILD_TIMER_WITH_LEVEL(
598
43
                _scanner_profile, "FileReaderAggregatePushDownTime", file_reader_profile, 1);
599
43
        _profile.file_reader_close_timer = ADD_CHILD_TIMER_WITH_LEVEL(
600
43
                _scanner_profile, "FileReaderCloseTime", file_reader_profile, 1);
601
43
    }
602
133
    SCOPED_TIMER(_profile.total_timer);
603
133
    SCOPED_TIMER(_profile.init_timer);
604
133
    return Status::OK();
605
133
}
606
607
98
Status TableReader::_build_table_filters_from_conjuncts() {
608
98
    _table_filters.clear();
609
98
    _constant_pruning_safe_filter_count = 0;
610
98
    bool in_safe_prefix = true;
611
98
    for (const auto& conjunct : _conjuncts) {
612
34
        DORIS_CHECK(conjunct != nullptr);
613
34
        DORIS_CHECK(conjunct->root() != nullptr);
614
        // `_table_filters` omits expressions without slot references, but such an expression still
615
        // occupies a position in the row-level conjunct order. Record how many localized filters
616
        // precede the first unsafe original conjunct so constant pruning cannot jump over a
617
        // slotless non-deterministic/error-preserving barrier.
618
34
        if (in_safe_prefix && !_is_safe_to_pre_execute(conjunct)) {
619
3
            in_safe_prefix = false;
620
3
        }
621
34
        if (!in_safe_prefix) {
622
5
            continue;
623
5
        }
624
29
        RETURN_IF_ERROR(
625
29
                build_table_filters_from_conjunct(conjunct, _runtime_state, &_table_filters));
626
29
        _constant_pruning_safe_filter_count = _table_filters.size();
627
29
    }
628
98
    return Status::OK();
629
98
}
630
631
97
Status TableReader::_open_local_filter_exprs(const FileScanRequest& file_request) {
632
97
    RowDescriptor row_desc;
633
97
    for (const auto& conjunct : file_request.conjuncts) {
634
24
        RETURN_IF_ERROR(conjunct->prepare(_runtime_state, row_desc));
635
24
        RETURN_IF_ERROR(conjunct->open(_runtime_state));
636
24
    }
637
97
    for (const auto& delete_conjunct : file_request.delete_conjuncts) {
638
32
        RETURN_IF_ERROR(delete_conjunct->prepare(_runtime_state, row_desc));
639
32
        RETURN_IF_ERROR(delete_conjunct->open(_runtime_state));
640
32
    }
641
97
    return Status::OK();
642
97
}
643
644
97
bool TableReader::_should_enable_condition_cache(const FileScanRequest& file_request) const {
645
97
    if (_condition_cache_digest == 0 || _push_down_agg_type == TPushAggOp::type::COUNT ||
646
97
        _current_file_description == std::nullopt || _data_reader.reader == nullptr) {
647
90
        return false;
648
90
    }
649
    // Condition cache is populated by file readers after evaluating file-local row-level
650
    // conjuncts. Metadata pruning can skip row groups/pages, but it does not produce a per-row
651
    // survivor bitmap that can safely populate the cache.
652
7
    if (file_request.conjuncts.empty()) {
653
1
        return false;
654
1
    }
655
    // Delete files/deletion vectors are table-format state. They may change independently of the
656
    // data file path/mtime/size used by the external cache key, so caching their result can become
657
    // stale. Keep delete filtering enabled, but do not read or write condition cache.
658
6
    if (_delete_rows != nullptr || _deletion_vector != nullptr ||
659
6
        !file_request.delete_conjuncts.empty()) {
660
1
        return false;
661
1
    }
662
    // Only scanner-driven splits provide a digest rebuilt from the exact RF snapshot. Keep the
663
    // conservative behavior for standalone TableReader callers: their initial digest may describe
664
    // only static predicate P and must not store P AND RF under that key.
665
5
    return _condition_cache_digest_covers_current_split ||
666
5
           !contains_runtime_filter(file_request.conjuncts);
667
6
}
668
669
97
Status TableReader::_init_reader_condition_cache(const FileScanRequest& file_request) {
670
97
    _condition_cache = nullptr;
671
97
    _condition_cache_ctx = nullptr;
672
97
    if (!_should_enable_condition_cache(file_request)) {
673
93
        return Status::OK();
674
93
    }
675
676
4
    auto* cache = segment_v2::ConditionCache::instance();
677
4
    if (cache == nullptr) {
678
0
        return Status::OK();
679
0
    }
680
4
    const auto& file = *_current_file_description;
681
4
    _condition_cache_key = segment_v2::ConditionCache::ExternalCacheKey(
682
4
            file.path, file.mtime, file.file_size, _condition_cache_digest, file.range_start_offset,
683
4
            file.range_size,
684
4
            segment_v2::ConditionCache::ExternalCacheKey::BASE_GRANULE_AWARE_VERSION);
685
686
4
    segment_v2::ConditionCacheHandle handle;
687
4
    const bool condition_cache_hit = cache->lookup(_condition_cache_key, &handle);
688
4
    if (condition_cache_hit) {
689
0
        _condition_cache = handle.get_filter_result();
690
0
        ++_condition_cache_hit_count;
691
4
    } else {
692
4
        const int64_t total_rows = _data_reader.reader->get_total_rows();
693
4
        if (total_rows <= 0) {
694
0
            return Status::OK();
695
0
        }
696
        // Add one guard granule for split ranges that start in the middle of a granule. A guard
697
        // false bit beyond the real range never overlaps real rows, but avoids boundary overflow
698
        // when a reader marks the last partial granule.
699
4
        const size_t num_granules = (total_rows + ConditionCacheContext::GRANULE_SIZE - 1) /
700
4
                                    ConditionCacheContext::GRANULE_SIZE;
701
4
        _condition_cache = std::make_shared<std::vector<bool>>(num_granules + 1, false);
702
4
    }
703
704
4
    if (_condition_cache != nullptr) {
705
4
        _condition_cache_ctx = std::make_shared<ConditionCacheContext>();
706
4
        _condition_cache_ctx->is_hit = condition_cache_hit;
707
4
        _condition_cache_ctx->filter_result = _condition_cache;
708
4
        _condition_cache_ctx->num_granules = _condition_cache->size();
709
4
        if (condition_cache_hit) {
710
0
            _condition_cache_ctx->base_granule = handle.get_base_granule();
711
0
        }
712
4
        _data_reader.reader->set_condition_cache_context(_condition_cache_ctx);
713
4
    }
714
4
    return Status::OK();
715
4
}
716
717
98
void TableReader::_finalize_reader_condition_cache() {
718
98
    if (_condition_cache_ctx == nullptr || _condition_cache_ctx->is_hit) {
719
94
        _condition_cache = nullptr;
720
94
        _condition_cache_ctx = nullptr;
721
94
        return;
722
94
    }
723
    // LIMIT or scanner cancellation may close a reader before all selected row ranges are visited.
724
    // Unvisited granules remain false in a MISS bitmap, so inserting a partial bitmap would make a
725
    // later HIT skip valid rows. Only publish cache entries after the physical reader reaches EOF.
726
4
    if (!_current_reader_reached_eof) {
727
2
        _condition_cache = nullptr;
728
2
        _condition_cache_ctx = nullptr;
729
2
        return;
730
2
    }
731
2
    DORIS_CHECK(_condition_cache_ctx->num_granules <= _condition_cache->size());
732
2
    _condition_cache->resize(_condition_cache_ctx->num_granules);
733
2
    segment_v2::ConditionCache::instance()->insert(
734
2
            _condition_cache_key, std::move(_condition_cache), _condition_cache_ctx->base_granule);
735
2
    _condition_cache = nullptr;
736
2
    _condition_cache_ctx = nullptr;
737
2
}
738
739
131
Status TableReader::create_next_reader(bool* eos) {
740
131
    SCOPED_TIMER(_profile.create_reader_timer);
741
131
    DCHECK(_data_reader.reader == nullptr);
742
131
    if (_current_task == nullptr) {
743
32
        *eos = true;
744
32
        return Status::OK();
745
32
    }
746
747
99
    RETURN_IF_ERROR(create_file_reader(&_data_reader.reader));
748
99
    DORIS_CHECK(_data_reader.reader != nullptr);
749
99
    if (_batch_size > 0) {
750
0
        _data_reader.reader->set_batch_size(_batch_size);
751
0
    }
752
99
    Status st;
753
99
    {
754
99
        SCOPED_TIMER(_profile.file_reader_total_timer);
755
99
        SCOPED_TIMER(_profile.file_reader_init_timer);
756
99
        st = _data_reader.reader->init(_runtime_state);
757
99
    }
758
99
    if (!st.ok()) {
759
1
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
760
0
            *eos = true;
761
0
            _data_reader.reader.reset();
762
0
            return Status::OK();
763
0
        }
764
1
        return st;
765
1
    }
766
98
    st = open_reader();
767
98
    if (!st.ok()) {
768
0
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
769
0
            *eos = true;
770
0
            _data_reader.reader.reset();
771
0
            return Status::OK();
772
0
        }
773
0
        return st;
774
0
    }
775
98
    if (_data_reader.reader == nullptr) {
776
1
        *eos = _current_task == nullptr;
777
1
        return Status::OK();
778
1
    }
779
97
    *eos = false;
780
97
    return Status::OK();
781
98
}
782
783
79
Status TableReader::create_file_reader(std::unique_ptr<FileReader>* reader) {
784
79
    DORIS_CHECK(reader != nullptr);
785
79
    const bool enable_mapping_timestamp_tz = _scan_params != nullptr &&
786
79
                                             _scan_params->__isset.enable_mapping_timestamp_tz &&
787
79
                                             _scan_params->enable_mapping_timestamp_tz;
788
79
    if (_format == FileFormat::PARQUET) {
789
79
        *reader = std::make_unique<format::parquet::ParquetReader>(
790
79
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
791
79
                _global_rowid_context, enable_mapping_timestamp_tz);
792
79
        return Status::OK();
793
79
    }
794
0
    if (_format == FileFormat::ORC) {
795
0
        *reader = std::make_unique<format::orc::OrcReader>(
796
0
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
797
0
                _global_rowid_context, enable_mapping_timestamp_tz);
798
0
        return Status::OK();
799
0
    }
800
0
    if (_format == FileFormat::CSV) {
801
0
        if (_file_slot_descs == nullptr) {
802
0
            return Status::InvalidArgument("CSV reader requires file slot descriptors");
803
0
        }
804
        // CSV has no embedded schema. TableReader owns table-level mapping, while CsvReader needs
805
        // only the physical file slots plus scan text parameters to build a file-local schema.
806
        // Non-file columns such as partitions/defaults/virtual row ids are intentionally excluded
807
        // from `_file_slot_descs` and are materialized during finalize_chunk().
808
0
        *reader = std::make_unique<format::csv::CsvReader>(
809
0
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
810
0
                _scan_params, *_file_slot_descs, _current_range_compress_type,
811
0
                _current_range_load_id);
812
0
        return Status::OK();
813
0
    }
814
0
    if (_format == FileFormat::TEXT) {
815
0
        if (_file_slot_descs == nullptr) {
816
0
            return Status::InvalidArgument("Text reader requires file slot descriptors");
817
0
        }
818
        // Text files have no embedded schema. As with CSV, TableReader handles table-level mapping
819
        // and only passes physical file slots to the v2 TextReader.
820
0
        *reader = std::make_unique<format::text::TextReader>(
821
0
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
822
0
                _scan_params, *_file_slot_descs, _current_range_compress_type,
823
0
                _current_range_load_id);
824
0
        return Status::OK();
825
0
    }
826
0
    if (_format == FileFormat::JSON) {
827
0
        if (_file_slot_descs == nullptr) {
828
0
            return Status::InvalidArgument("JSON reader requires file slot descriptors");
829
0
        }
830
0
        *reader = std::make_unique<format::json::JsonReader>(
831
0
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
832
0
                _scan_params, _current_file_range_desc, *_file_slot_descs,
833
0
                _current_range_compress_type, _current_range_load_id);
834
0
        return Status::OK();
835
0
    }
836
0
    if (_format == FileFormat::NATIVE) {
837
0
        *reader = std::make_unique<format::native::NativeReader>(
838
0
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile);
839
0
        return Status::OK();
840
0
    }
841
0
    return Status::NotSupported("TableReader does not support file format {}",
842
0
                                file_format_to_string(_format));
843
0
}
844
845
125
std::unique_ptr<io::FileDescription> create_file_description(const TFileRangeDesc& range) {
846
125
    auto file_description = std::make_unique<io::FileDescription>();
847
125
    file_description->path = range.path;
848
125
    file_description->file_size = range.__isset.file_size ? range.file_size : -1;
849
125
    file_description->mtime = range.__isset.modification_time ? range.modification_time : 0;
850
125
    file_description->range_start_offset = range.__isset.start_offset ? range.start_offset : 0;
851
125
    file_description->range_size = range.__isset.size ? range.size : -1;
852
125
    if (range.__isset.fs_name) {
853
5
        file_description->fs_name = range.fs_name;
854
5
    }
855
125
    if (range.__isset.file_cache_admission) {
856
0
        file_description->file_cache_admission = range.file_cache_admission;
857
0
    }
858
125
    return file_description;
859
125
}
860
861
126
Status TableReader::prepare_split(const SplitReadOptions& options) {
862
126
    SCOPED_TIMER(_profile.total_timer);
863
126
    SCOPED_TIMER(_profile.prepare_split_timer);
864
126
    _current_split_pruned = false;
865
126
    _all_runtime_filters_applied_for_split = options.all_runtime_filters_applied;
866
126
    _condition_cache_digest_covers_current_split = options.condition_cache_digest.has_value();
867
126
    if (options.condition_cache_digest.has_value()) {
868
        // The split snapshot may include RFs that arrived after TableReader::init(). Use the digest
869
        // computed from that exact snapshot. Example: an initial P digest must not be used to store
870
        // the bitmap for P AND late RF{7, 9}; the scanner supplies digest(P AND RF{7, 9}) here.
871
1
        _condition_cache_digest = *options.condition_cache_digest;
872
125
    } else {
873
        // An explicit scanner digest is split-scoped. Restore the init-time digest when a later
874
        // standalone split omits it instead of leaking the previous split's RF payload into its key.
875
125
        _condition_cache_digest = _initial_condition_cache_digest;
876
125
    }
877
126
    if (options.conjuncts.has_value()) {
878
2
        _conjuncts = *options.conjuncts;
879
2
    }
880
    // Update to current split format to handle ORC/PARQUET files in one table.
881
126
    _format = options.current_split_format;
882
126
    _partition_values = std::move(options.partition_values);
883
126
    _current_task.reset();
884
126
    _current_file_description.reset();
885
126
    _current_file_range_desc = options.current_range;
886
126
    _current_range_compress_type = options.current_range.__isset.compress_type
887
126
                                           ? options.current_range.compress_type
888
126
                                           : TFileCompressType::UNKNOWN;
889
126
    _current_range_load_id = options.current_range.__isset.load_id
890
126
                                     ? std::make_optional(options.current_range.load_id)
891
126
                                     : std::nullopt;
892
126
    _global_rowid_context = options.global_rowid_context;
893
126
    _delete_rows = nullptr;
894
126
    _deletion_vector = nullptr;
895
126
    _aggregate_pushdown_tried = false;
896
126
    _remaining_table_level_count = -1;
897
126
    _current_reader_reached_eof = false;
898
126
    RETURN_IF_ERROR(_evaluate_partition_prune_conjuncts(options.partition_prune_conjuncts,
899
126
                                                        &_current_split_pruned));
900
126
    if (_current_split_pruned) {
901
1
        COUNTER_UPDATE(_profile.runtime_filter_partition_pruned_range_counter, 1);
902
1
        return Status::OK();
903
1
    }
904
125
    _current_task = std::make_unique<ScanTask>();
905
125
    _current_task->data_file = create_file_description(options.current_range);
906
125
    _current_file_description = *_current_task->data_file;
907
    // A table-level row count is only equivalent to scanning the split when no row predicate is
908
    // active and no predicate can arrive later. The metadata path can return several batches for
909
    // one split; after its first synthetic batch there is no way to recover the real rows if a
910
    // runtime filter arrives before the next scheduler turn.
911
125
    if (_push_down_agg_type == TPushAggOp::type::COUNT && options.all_runtime_filters_applied &&
912
125
        _conjuncts.empty() && options.current_range.__isset.table_format_params &&
913
125
        options.current_range.table_format_params.__isset.table_level_row_count) {
914
6
        DORIS_CHECK(options.current_range.table_format_params.table_level_row_count >= -1);
915
6
        _remaining_table_level_count =
916
6
                options.current_range.table_format_params.table_level_row_count;
917
6
    }
918
125
    if (_is_table_level_count_active()) {
919
2
        return Status::OK();
920
2
    }
921
123
    return _parse_delete_predicates(options);
922
125
}
923
924
Status TableReader::_evaluate_partition_prune_conjuncts(const VExprContextSPtrs& conjuncts,
925
126
                                                        bool* can_filter_all) {
926
126
    DORIS_CHECK(can_filter_all != nullptr);
927
126
    SCOPED_TIMER(_profile.runtime_filter_partition_prune_timer);
928
126
    *can_filter_all = false;
929
126
    if (conjuncts.empty() || _partition_values.empty()) {
930
123
        return Status::OK();
931
123
    }
932
933
3
    VExprContextSPtrs partition_conjuncts;
934
3
    for (const auto& conjunct : conjuncts) {
935
3
        DORIS_CHECK(conjunct != nullptr);
936
3
        DORIS_CHECK(conjunct->root() != nullptr);
937
        // Keep only the safe prefix of the original conjunct order. If an unsafe conjunct is
938
        // skipped, a later predicate could prune the split before the unsafe one reaches its
939
        // normal row-level evaluation point.
940
3
        if (!_is_safe_to_pre_execute(conjunct)) {
941
1
            break;
942
1
        }
943
2
        std::set<GlobalIndex> global_indices;
944
2
        collect_global_indices(conjunct->root(), &global_indices);
945
2
        if (global_indices.empty()) {
946
0
            continue;
947
0
        }
948
2
        const bool partition_only = std::ranges::all_of(global_indices, [&](GlobalIndex index) {
949
2
            if (index.value() >= _projected_columns.size()) {
950
0
                return false;
951
0
            }
952
2
            const auto& column = _projected_columns[index.value()];
953
2
            return column.is_partition_key &&
954
2
                   find_partition_value(column, _partition_values) != nullptr;
955
2
        });
956
2
        if (partition_only) {
957
2
            partition_conjuncts.push_back(conjunct);
958
2
        }
959
2
    }
960
3
    if (partition_conjuncts.empty()) {
961
1
        return Status::OK();
962
1
    }
963
964
2
    Block block;
965
2
    RETURN_IF_ERROR(_build_partition_prune_block(&block));
966
2
    RowDescriptor row_desc;
967
2
    for (const auto& conjunct : partition_conjuncts) {
968
2
        RETURN_IF_ERROR(conjunct->prepare(_runtime_state, row_desc));
969
2
        RETURN_IF_ERROR(conjunct->open(_runtime_state));
970
2
    }
971
2
    IColumn::Filter result_filter(block.rows(), 1);
972
2
    return VExprContext::execute_conjuncts(partition_conjuncts, nullptr, &block, &result_filter,
973
2
                                           can_filter_all);
974
2
}
975
976
64
bool TableReader::_is_safe_to_pre_execute(const VExprContextSPtr& conjunct) {
977
64
    DORIS_CHECK(conjunct != nullptr);
978
64
    DORIS_CHECK(conjunct->root() != nullptr);
979
64
    const auto root = conjunct->root();
980
64
    const auto impl = root->get_impl();
981
64
    const auto predicate = impl != nullptr ? impl : root;
982
    // Split pruning evaluates a predicate once before any file rows are read. Reordering
983
    // non-deterministic or error-preserving expressions can change their row-level semantics,
984
    // even when every referenced slot is a partition column or maps to a constant entry.
985
64
    return predicate->is_safe_to_execute_on_selected_rows();
986
64
}
987
988
2
Status TableReader::_build_partition_prune_block(Block* block) const {
989
2
    DORIS_CHECK(block != nullptr);
990
2
    DORIS_CHECK(!_projected_columns.empty());
991
2
    block->clear();
992
2
    for (const auto& column : _projected_columns) {
993
2
        DORIS_CHECK(column.type != nullptr);
994
2
        ColumnPtr value_column = column.type->create_column_const_with_default_value(1);
995
2
        if (column.is_partition_key) {
996
2
            const auto* partition_value = find_partition_value(column, _partition_values);
997
2
            if (partition_value != nullptr) {
998
2
                value_column = column.type->create_column_const(1, *partition_value);
999
2
            }
1000
2
        }
1001
2
        block->insert({std::move(value_column), column.type, column.name});
1002
2
    }
1003
2
    return Status::OK();
1004
2
}
1005
1006
123
Status TableReader::_parse_delete_predicates(const SplitReadOptions& options) {
1007
123
    DeleteFileDesc desc {.fs_name = options.current_range.fs_name};
1008
123
    bool has_delete_file = false;
1009
123
    RETURN_IF_ERROR(_parse_deletion_vector_file(options.current_range.table_format_params, &desc,
1010
123
                                                &has_delete_file));
1011
123
    if (has_delete_file) {
1012
7
        DORIS_CHECK(options.cache != nullptr);
1013
7
        Status create_status = Status::OK();
1014
1015
7
        bool decoded_cache_hit = false;
1016
7
        _deletion_vector = options.cache->get<DeletionVector>(
1017
7
                desc.key,
1018
7
                [&]() -> DeletionVector* {
1019
7
                    auto deletion_vector = std::make_unique<DeletionVector>();
1020
1021
7
                    DeletionVectorReader dv_reader(_runtime_state, _scanner_profile, *_scan_params,
1022
7
                                                   desc, _io_ctx.get());
1023
7
                    create_status = dv_reader.open();
1024
7
                    if (!create_status.ok()) [[unlikely]] {
1025
0
                        return nullptr;
1026
0
                    }
1027
1028
7
                    size_t bytes_read = desc.size;
1029
7
                    std::vector<char> buffer(bytes_read);
1030
7
                    DBUG_EXECUTE_IF("TableReader.parse_deletion_vector.io_error", {
1031
7
                        create_status =
1032
7
                                Status::IOError("injected format v2 deletion vector read failure");
1033
7
                        return nullptr;
1034
7
                    });
1035
6
                    DBUG_EXECUTE_IF("TableReader.parse_deletion_vector.should_stop", {
1036
6
                        create_status = Status::EndOfFile("stop read.");
1037
6
                        return nullptr;
1038
6
                    });
1039
5
                    create_status =
1040
5
                            dv_reader.read_at(desc.start_offset, {buffer.data(), bytes_read});
1041
5
                    const auto& file_cache_stats = dv_reader.file_cache_statistics();
1042
5
                    COUNTER_UPDATE(_profile.dv_file_cache_hit_count,
1043
5
                                   file_cache_stats.num_local_io_total);
1044
5
                    COUNTER_UPDATE(_profile.dv_file_cache_miss_count,
1045
5
                                   file_cache_stats.num_remote_io_total);
1046
5
                    COUNTER_UPDATE(_profile.dv_file_cache_peer_read_count,
1047
5
                                   file_cache_stats.num_peer_io_total);
1048
5
                    if (!create_status.ok()) [[unlikely]] {
1049
0
                        return nullptr;
1050
0
                    }
1051
1052
5
                    const char* buf = buffer.data();
1053
5
                    SCOPED_TIMER(_profile.parse_delete_file_time);
1054
5
                    create_status = parse_deletion_vector(buf, bytes_read, desc.format,
1055
5
                                                          deletion_vector.get());
1056
5
                    if (!create_status.ok()) [[unlikely]] {
1057
1
                        return nullptr;
1058
1
                    }
1059
4
                    COUNTER_UPDATE(_profile.num_delete_rows, deletion_vector->cardinality());
1060
4
                    return deletion_vector.release();
1061
5
                },
1062
7
                &decoded_cache_hit);
1063
7
        RETURN_IF_ERROR(create_status);
1064
4
        COUNTER_UPDATE(decoded_cache_hit ? _profile.decoded_dv_cache_hit_count
1065
4
                                         : _profile.decoded_dv_cache_miss_count,
1066
4
                       1);
1067
4
    }
1068
1069
120
    return Status::OK();
1070
123
}
1071
} // namespace doris::format