Coverage Report

Created: 2026-07-08 14:26

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 <ranges>
26
#include <set>
27
#include <sstream>
28
#include <utility>
29
#include <vector>
30
31
#include "common/cast_set.h"
32
#include "common/status.h"
33
#include "core/assert_cast.h"
34
#include "core/data_type/data_type_array.h"
35
#include "core/data_type/data_type_map.h"
36
#include "core/data_type/data_type_struct.h"
37
#include "exprs/vexpr_context.h"
38
#include "exprs/vslot_ref.h"
39
#include "format/table/deletion_vector_reader.h"
40
#include "format/table/iceberg_delete_file_reader_helper.h"
41
#include "format/table/paimon_reader.h"
42
#include "format_v2/column_mapper.h"
43
#include "format_v2/delimited_text/csv_reader.h"
44
#include "format_v2/delimited_text/text_reader.h"
45
#include "format_v2/json/json_reader.h"
46
#include "format_v2/native/native_reader.h"
47
#include "format_v2/parquet/parquet_reader.h"
48
#include "storage/segment/condition_cache.h"
49
#include "util/string_util.h"
50
51
namespace doris::format {
52
namespace {
53
54
template <typename T, typename Formatter>
55
61
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
56
61
    std::ostringstream out;
57
61
    out << "[";
58
70
    for (size_t i = 0; i < values.size(); ++i) {
59
9
        if (i > 0) {
60
3
            out << ", ";
61
3
        }
62
9
        out << formatter(values[i]);
63
9
    }
64
61
    out << "]";
65
61
    return out.str();
66
61
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_0EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
55
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
56
12
    std::ostringstream out;
57
12
    out << "[";
58
14
    for (size_t i = 0; i < values.size(); ++i) {
59
2
        if (i > 0) {
60
1
            out << ", ";
61
1
        }
62
2
        out << formatter(values[i]);
63
2
    }
64
12
    out << "]";
65
12
    return out.str();
66
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableFilterEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_1EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
55
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
56
12
    std::ostringstream out;
57
12
    out << "[";
58
13
    for (size_t i = 0; i < values.size(); ++i) {
59
1
        if (i > 0) {
60
0
            out << ", ";
61
0
        }
62
1
        out << formatter(values[i]);
63
1
    }
64
12
    out << "]";
65
12
    return out.str();
66
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
55
1
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
56
1
    std::ostringstream out;
57
1
    out << "[";
58
2
    for (size_t i = 0; i < values.size(); ++i) {
59
1
        if (i > 0) {
60
0
            out << ", ";
61
0
        }
62
1
        out << formatter(values[i]);
63
1
    }
64
1
    out << "]";
65
1
    return out.str();
66
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
55
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
56
12
    std::ostringstream out;
57
12
    out << "[";
58
13
    for (size_t i = 0; i < values.size(); ++i) {
59
1
        if (i > 0) {
60
0
            out << ", ";
61
0
        }
62
1
        out << formatter(values[i]);
63
1
    }
64
12
    out << "]";
65
12
    return out.str();
66
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_16ColumnDefinitionEZNKS0_11TableReader12debug_stringB5cxx11EvE3$_3EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
55
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
56
12
    std::ostringstream out;
57
12
    out << "[";
58
14
    for (size_t i = 0; i < values.size(); ++i) {
59
2
        if (i > 0) {
60
1
            out << ", ";
61
1
        }
62
2
        out << formatter(values[i]);
63
2
    }
64
12
    out << "]";
65
12
    return out.str();
66
12
}
table_reader.cpp:_ZN5doris6format12_GLOBAL__N_131join_table_reader_debug_stringsINS0_11TableReader15FileBlockColumnEZNKS3_12debug_stringB5cxx11EvE3$_4EENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIT_SaISD_EET0_
Line
Count
Source
55
12
std::string join_table_reader_debug_strings(const std::vector<T>& values, Formatter formatter) {
56
12
    std::ostringstream out;
57
12
    out << "[";
58
14
    for (size_t i = 0; i < values.size(); ++i) {
59
2
        if (i > 0) {
60
1
            out << ", ";
61
1
        }
62
2
        out << formatter(values[i]);
63
2
    }
64
12
    out << "]";
65
12
    return out.str();
66
12
}
67
68
12
std::string file_format_to_string(FileFormat format) {
69
12
    switch (format) {
70
5
    case FileFormat::PARQUET:
71
5
        return "PARQUET";
72
1
    case FileFormat::ORC:
73
1
        return "ORC";
74
1
    case FileFormat::CSV:
75
1
        return "CSV";
76
1
    case FileFormat::JSON:
77
1
        return "JSON";
78
1
    case FileFormat::TEXT:
79
1
        return "TEXT";
80
1
    case FileFormat::JNI:
81
1
        return "JNI";
82
1
    case FileFormat::NATIVE:
83
1
        return "NATIVE";
84
1
    case FileFormat::ARROW:
85
1
        return "ARROW";
86
12
    }
87
0
    return "UNKNOWN";
88
12
}
89
90
12
std::string push_down_agg_to_string(TPushAggOp::type op) {
91
12
    switch (op) {
92
8
    case TPushAggOp::NONE:
93
8
        return "NONE";
94
1
    case TPushAggOp::COUNT:
95
1
        return "COUNT";
96
1
    case TPushAggOp::MINMAX:
97
1
        return "MINMAX";
98
1
    case TPushAggOp::MIX:
99
1
        return "MIX";
100
1
    case TPushAggOp::COUNT_ON_INDEX:
101
1
        return "COUNT_ON_INDEX";
102
12
    }
103
0
    return "UNKNOWN";
104
12
}
105
106
12
std::string current_file_debug_string(const std::unique_ptr<ScanTask>& task) {
107
12
    if (task == nullptr || task->data_file == nullptr) {
108
11
        return "null";
109
11
    }
110
1
    const auto& file = *task->data_file;
111
1
    std::ostringstream out;
112
1
    out << "FileDescription{path=" << file.path << ", file_size=" << file.file_size
113
1
        << ", range_start_offset=" << file.range_start_offset << ", range_size=" << file.range_size
114
1
        << ", mtime=" << file.mtime << ", fs_name=" << file.fs_name
115
1
        << ", file_cache_admission=" << file.file_cache_admission << "}";
116
1
    return out.str();
117
12
}
118
119
12
std::string partition_values_debug_string(const std::map<std::string, Field>& partition_values) {
120
12
    std::ostringstream out;
121
12
    out << "{";
122
12
    size_t idx = 0;
123
12
    for (const auto& [key, _] : partition_values) {
124
1
        if (idx++ > 0) {
125
0
            out << ", ";
126
0
        }
127
1
        out << key;
128
1
    }
129
12
    out << "}";
130
12
    return out.str();
131
12
}
132
133
6
const schema::external::TField* get_field_ptr(const schema::external::TFieldPtr& field_ptr) {
134
6
    if (!field_ptr.__isset.field_ptr || field_ptr.field_ptr == nullptr) {
135
0
        return nullptr;
136
0
    }
137
6
    return field_ptr.field_ptr.get();
138
6
}
139
140
4
bool external_field_matches_name(const schema::external::TField& field, const std::string& name) {
141
4
    if (field.__isset.name && to_lower(field.name) == to_lower(name)) {
142
0
        return true;
143
0
    }
144
4
    return field.__isset.name_mapping &&
145
4
           std::ranges::any_of(field.name_mapping, [&](const std::string& alias) {
146
4
               return to_lower(alias) == to_lower(name);
147
4
           });
148
4
}
149
150
DataTypePtr find_struct_child_type_by_external_field(const DataTypeStruct& struct_type,
151
2
                                                     const schema::external::TField& field) {
152
3
    for (size_t field_idx = 0; field_idx < struct_type.get_elements().size(); ++field_idx) {
153
3
        if (external_field_matches_name(field, struct_type.get_element_name(field_idx))) {
154
2
            return struct_type.get_element(field_idx);
155
2
        }
156
3
    }
157
0
    return nullptr;
158
2
}
159
160
ColumnDefinition build_schema_column_from_external_field(const schema::external::TField& field,
161
6
                                                         DataTypePtr type) {
162
6
    ColumnDefinition column {
163
6
            .identifier = field.__isset.id ? Field::create_field<TYPE_INT>(field.id) : Field {},
164
6
            .name = field.__isset.name ? field.name : "",
165
6
            .name_mapping =
166
6
                    field.__isset.name_mapping ? field.name_mapping : std::vector<std::string> {},
167
6
            .type = std::move(type),
168
6
            .children = {},
169
6
            .default_expr = nullptr,
170
6
            .is_partition_key = false,
171
6
    };
172
6
    if (column.type == nullptr || !field.__isset.nestedField) {
173
3
        return column;
174
3
    }
175
176
3
    const auto nested_type = remove_nullable(column.type);
177
3
    switch (nested_type->get_primitive_type()) {
178
1
    case TYPE_STRUCT: {
179
1
        if (!field.nestedField.__isset.struct_field ||
180
1
            !field.nestedField.struct_field.__isset.fields) {
181
0
            return column;
182
0
        }
183
1
        const auto& struct_type = assert_cast<const DataTypeStruct&>(*nested_type);
184
2
        for (const auto& child_ptr : field.nestedField.struct_field.fields) {
185
2
            const auto* child_field = get_field_ptr(child_ptr);
186
2
            if (child_field == nullptr || !child_field->__isset.name) {
187
0
                continue;
188
0
            }
189
2
            auto child_type = find_struct_child_type_by_external_field(struct_type, *child_field);
190
2
            if (child_type == nullptr) {
191
0
                continue;
192
0
            }
193
2
            column.children.push_back(
194
2
                    build_schema_column_from_external_field(*child_field, child_type));
195
2
        }
196
1
        break;
197
1
    }
198
1
    case TYPE_ARRAY: {
199
1
        if (!field.nestedField.__isset.array_field ||
200
1
            !field.nestedField.array_field.__isset.item_field) {
201
0
            return column;
202
0
        }
203
1
        const auto* item_field = get_field_ptr(field.nestedField.array_field.item_field);
204
1
        if (item_field == nullptr) {
205
0
            return column;
206
0
        }
207
1
        const auto& array_type = assert_cast<const DataTypeArray&>(*nested_type);
208
1
        auto child =
209
1
                build_schema_column_from_external_field(*item_field, array_type.get_nested_type());
210
1
        child.name = "element";
211
1
        if (child.has_identifier_name()) {
212
0
            child.identifier = Field::create_field<TYPE_STRING>(child.name);
213
0
        }
214
1
        column.children.push_back(std::move(child));
215
1
        break;
216
1
    }
217
1
    case TYPE_MAP: {
218
1
        if (!field.nestedField.__isset.map_field ||
219
1
            !field.nestedField.map_field.__isset.key_field ||
220
1
            !field.nestedField.map_field.__isset.value_field) {
221
0
            return column;
222
0
        }
223
1
        const auto& map_type = assert_cast<const DataTypeMap&>(*nested_type);
224
1
        const auto* key_field = get_field_ptr(field.nestedField.map_field.key_field);
225
1
        if (key_field != nullptr) {
226
1
            auto child =
227
1
                    build_schema_column_from_external_field(*key_field, map_type.get_key_type());
228
1
            child.name = "key";
229
1
            if (child.has_identifier_name()) {
230
0
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
231
0
            }
232
1
            column.children.push_back(std::move(child));
233
1
        }
234
1
        const auto* value_field = get_field_ptr(field.nestedField.map_field.value_field);
235
1
        if (value_field != nullptr) {
236
1
            auto child = build_schema_column_from_external_field(*value_field,
237
1
                                                                 map_type.get_value_type());
238
1
            child.name = "value";
239
1
            if (child.has_identifier_name()) {
240
0
                child.identifier = Field::create_field<TYPE_STRING>(child.name);
241
0
            }
242
1
            column.children.push_back(std::move(child));
243
1
        }
244
1
        break;
245
1
    }
246
0
    default:
247
0
        break;
248
3
    }
249
3
    return column;
250
3
}
251
252
const schema::external::TField* find_external_root_field(const TFileScanRangeParams* params,
253
3.05k
                                                         const ColumnDefinition& column) {
254
3.05k
    if (params == nullptr || !params->__isset.history_schema_info ||
255
3.05k
        params->history_schema_info.empty()) {
256
3.05k
        return nullptr;
257
3.05k
    }
258
1
    const auto* schema = &params->history_schema_info.front();
259
1
    if (params->__isset.current_schema_id) {
260
2
        for (const auto& candidate_schema : params->history_schema_info) {
261
2
            if (candidate_schema.__isset.schema_id &&
262
2
                candidate_schema.schema_id == params->current_schema_id) {
263
1
                schema = &candidate_schema;
264
1
                break;
265
1
            }
266
2
        }
267
1
    }
268
1
    if (!schema->__isset.root_field || !schema->root_field.__isset.fields) {
269
0
        return nullptr;
270
0
    }
271
1
    for (const auto& field_ptr : schema->root_field.fields) {
272
1
        const auto* field = get_field_ptr(field_ptr);
273
1
        if (field == nullptr) {
274
0
            continue;
275
0
        }
276
1
        if (external_field_matches_name(*field, column.name)) {
277
1
            return field;
278
1
        }
279
1
    }
280
0
    return nullptr;
281
1
}
282
283
2
std::string expr_context_debug_string(const VExprContextSPtr& context) {
284
2
    if (context == nullptr) {
285
0
        return "null";
286
0
    }
287
2
    const auto root = context->root();
288
2
    if (root == nullptr) {
289
0
        return "VExprContext{root=null}";
290
0
    }
291
2
    std::ostringstream out;
292
2
    out << "VExprContext{root_name=" << root->expr_name() << ", root_debug=" << root->debug_string()
293
2
        << "}";
294
2
    return out.str();
295
2
}
296
297
1
std::string table_filter_debug_string(const TableFilter& filter) {
298
1
    std::ostringstream out;
299
1
    out << "TableFilter{conjunct=" << expr_context_debug_string(filter.conjunct)
300
1
        << ", global_indices="
301
1
        << join_table_reader_debug_strings(
302
1
                   filter.global_indices,
303
1
                   [](GlobalIndex global_index) { return std::to_string(global_index.value()); })
304
1
        << "}";
305
1
    return out.str();
306
1
}
307
308
12
bool contains_runtime_filter(const VExprContextSPtrs& conjuncts) {
309
12
    return std::ranges::any_of(conjuncts, [](const auto& conjunct) {
310
12
        return conjunct != nullptr && conjunct->root() != nullptr &&
311
12
               conjunct->root()->is_rf_wrapper();
312
12
    });
313
12
}
314
315
101
void collect_global_indices(const VExprSPtr& expr, std::set<GlobalIndex>* global_indices) {
316
101
    if (expr == nullptr) {
317
0
        return;
318
0
    }
319
101
    if (expr->is_rf_wrapper()) {
320
        // RuntimeFilterExpr wraps a real predicate expression but its own thrift node can still
321
        // look like SLOT_REF. Collect indices from the wrapped predicate; do not cast the wrapper
322
        // itself to VSlotRef.
323
2
        collect_global_indices(expr->get_impl(), global_indices);
324
2
        return;
325
2
    }
326
99
    if (expr->is_slot_ref()) {
327
35
        const auto* slot_ref = assert_cast<const VSlotRef*>(expr.get());
328
35
        DORIS_CHECK(slot_ref->column_id() >= 0);
329
35
        global_indices->insert(GlobalIndex(cast_set<size_t>(slot_ref->column_id())));
330
35
    }
331
99
    for (const auto& child : expr->children()) {
332
65
        collect_global_indices(child, global_indices);
333
65
    }
334
99
}
335
336
Status build_table_filters_from_conjunct(const VExprContextSPtr& conjunct, RuntimeState* state,
337
34
                                         std::vector<TableFilter>* table_filters) {
338
34
    if (conjunct == nullptr) {
339
0
        return Status::OK();
340
0
    }
341
34
    std::set<GlobalIndex> global_indices;
342
34
    collect_global_indices(conjunct->root(), &global_indices);
343
34
    if (!global_indices.empty()) {
344
34
        TableFilter table_filter;
345
34
        VExprSPtr filter_root;
346
34
        RETURN_IF_ERROR(clone_table_expr_tree(conjunct->root(), &filter_root));
347
34
        table_filter.conjunct = VExprContext::create_shared(std::move(filter_root));
348
35
        for (const auto global_index : global_indices) {
349
35
            table_filter.global_indices.push_back(global_index);
350
35
        }
351
34
        table_filters->push_back(std::move(table_filter));
352
34
    }
353
34
    return Status::OK();
354
34
}
355
356
Status parse_deletion_vector(const char* buf, size_t buffer_size, DeleteFileDesc::Format format,
357
4
                             DeleteRows* delete_rows) {
358
4
    DORIS_CHECK(buf != nullptr);
359
4
    DORIS_CHECK(delete_rows != nullptr);
360
4
    DORIS_CHECK(format == DeleteFileDesc::Format::PAIMON ||
361
4
                format == DeleteFileDesc::Format::ICEBERG);
362
363
4
    if (format == DeleteFileDesc::Format::PAIMON) {
364
1
        RETURN_IF_ERROR(decode_paimon_deletion_vector_buffer(buf, buffer_size, delete_rows));
365
1
        return Status::OK();
366
1
    }
367
368
3
    roaring::Roaring64Map bitmap;
369
3
    RETURN_IF_ERROR(decode_iceberg_deletion_vector_buffer(buf, buffer_size, &bitmap));
370
3
    delete_rows->reserve(bitmap.cardinality());
371
7
    for (auto it = bitmap.begin(); it != bitmap.end(); it++) {
372
4
        delete_rows->push_back(cast_set<int64_t>(*it));
373
4
    }
374
3
    return Status::OK();
375
3
}
376
377
} // namespace
378
379
std::shared_ptr<io::FileSystemProperties> create_system_properties(
380
552
        const TFileScanRangeParams* scan_params) {
381
552
    auto system_properties = std::make_shared<io::FileSystemProperties>();
382
552
    if (scan_params == nullptr || !scan_params->__isset.file_type) {
383
97
        system_properties->system_type = TFileType::FILE_LOCAL;
384
97
        return system_properties;
385
97
    }
386
455
    system_properties->system_type = scan_params->file_type;
387
455
    system_properties->properties = scan_params->properties;
388
455
    system_properties->hdfs_params = scan_params->hdfs_params;
389
455
    if (scan_params->__isset.broker_addresses) {
390
0
        system_properties->broker_addresses.assign(scan_params->broker_addresses.begin(),
391
0
                                                   scan_params->broker_addresses.end());
392
0
    }
393
455
    return system_properties;
394
552
}
395
396
12
std::string TableReader::debug_string() const {
397
12
    std::ostringstream out;
398
12
    out << "TableReader{format=" << file_format_to_string(_format)
399
12
        << ", push_down_agg_type=" << push_down_agg_to_string(_push_down_agg_type)
400
12
        << ", aggregate_pushdown_tried=" << _aggregate_pushdown_tried
401
12
        << ", has_current_reader=" << (_data_reader.reader != nullptr)
402
12
        << ", has_current_task=" << (_current_task != nullptr)
403
12
        << ", current_file=" << current_file_debug_string(_current_task)
404
12
        << ", has_delete_rows=" << (_delete_rows != nullptr)
405
12
        << ", delete_row_count=" << (_delete_rows == nullptr ? 0 : _delete_rows->size())
406
12
        << ", has_system_properties=" << (_system_properties != nullptr) << ", system_type="
407
12
        << (_system_properties == nullptr ? static_cast<int>(TFileType::FILE_LOCAL)
408
12
                                          : static_cast<int>(_system_properties->system_type))
409
12
        << ", has_scan_params=" << (_scan_params != nullptr)
410
12
        << ", has_io_ctx=" << (_io_ctx != nullptr)
411
12
        << ", has_runtime_state=" << (_runtime_state != nullptr)
412
12
        << ", has_scanner_profile=" << (_scanner_profile != nullptr)
413
12
        << ", mapper_options=" << _mapper_options.debug_string() << ", projected_columns="
414
12
        << join_table_reader_debug_strings(
415
12
                   _projected_columns,
416
12
                   [](const ColumnDefinition& column) { return column.debug_string(); })
417
12
        << ", partition_values=" << partition_values_debug_string(_partition_values)
418
12
        << ", table_filters="
419
12
        << join_table_reader_debug_strings(
420
12
                   _table_filters,
421
12
                   [](const TableFilter& filter) { return table_filter_debug_string(filter); })
422
12
        << ", conjunct_count=" << _conjuncts.size() << ", conjuncts="
423
12
        << join_table_reader_debug_strings(_conjuncts,
424
12
                                           [](const VExprContextSPtr& conjunct) {
425
1
                                               return expr_context_debug_string(conjunct);
426
1
                                           })
427
12
        << ", file_schema="
428
12
        << join_table_reader_debug_strings(
429
12
                   _data_reader.file_schema,
430
12
                   [](const ColumnDefinition& field) { return field.debug_string(); })
431
12
        << ", file_block_layout="
432
12
        << join_table_reader_debug_strings(
433
12
                   _data_reader.file_block_layout,
434
12
                   [](const FileBlockColumn& column) {
435
2
                       std::ostringstream column_out;
436
2
                       column_out << "FileBlockColumn{file_column_id=" << column.file_column_id
437
2
                                  << ", name=" << column.name << ", type="
438
2
                                  << (column.type == nullptr ? "null" : column.type->get_name())
439
2
                                  << "}";
440
2
                       return column_out.str();
441
2
                   })
442
12
        << ", block_template_columns=" << _data_reader.block_template.columns()
443
12
        << ", column_mapper="
444
12
        << (_data_reader.column_mapper == nullptr ? "null"
445
12
                                                  : _data_reader.column_mapper->debug_string())
446
12
        << "}";
447
12
    return out.str();
448
12
}
449
450
Status TableReader::annotate_projected_column(const TFileScanSlotInfo& slot_info,
451
                                              ProjectedColumnBuildContext* context,
452
3.05k
                                              ColumnDefinition* column) const {
453
3.05k
    (void)slot_info;
454
3.05k
    DORIS_CHECK(context != nullptr);
455
3.05k
    DORIS_CHECK(column != nullptr);
456
3.05k
    context->schema_column.reset();
457
3.05k
    const auto* schema_field = find_external_root_field(context->scan_params, *column);
458
3.05k
    if (schema_field == nullptr) {
459
3.05k
        return Status::OK();
460
3.05k
    }
461
1
    context->schema_column = build_schema_column_from_external_field(*schema_field, column->type);
462
1
    column->identifier = context->schema_column->identifier;
463
1
    column->name_mapping = context->schema_column->name_mapping;
464
1
    return Status::OK();
465
3.05k
}
466
467
552
Status TableReader::init(TableReadOptions&& options) {
468
552
    _scan_params = options.scan_params;
469
552
    _format = options.format;
470
552
    _io_ctx = options.io_ctx;
471
552
    _runtime_state = options.runtime_state;
472
552
    _scanner_profile = options.scanner_profile;
473
552
    _file_slot_descs = options.file_slot_descs;
474
552
    _push_down_agg_type = options.push_down_agg_type;
475
552
    _condition_cache_digest = options.condition_cache_digest;
476
552
    _projected_columns = std::move(options.projected_columns);
477
552
    _system_properties = create_system_properties(_scan_params);
478
552
    _mapper_options.mode = TableColumnMappingMode::BY_NAME;
479
552
    _conjuncts = std::move(options.conjuncts);
480
481
552
    if (_scanner_profile != nullptr) {
482
481
        static const char* table_profile = "TableReader";
483
481
        ADD_TIMER_WITH_LEVEL(_scanner_profile, table_profile, 1);
484
481
        _profile.num_delete_files = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteFiles",
485
481
                                                                 TUnit::UNIT, table_profile, 1);
486
481
        _profile.num_delete_rows = ADD_CHILD_COUNTER_WITH_LEVEL(_scanner_profile, "NumDeleteRows",
487
481
                                                                TUnit::UNIT, table_profile, 1);
488
481
        _profile.parse_delete_file_time = ADD_CHILD_TIMER_WITH_LEVEL(
489
481
                _scanner_profile, "ParseDeleteFileTime", table_profile, 1);
490
481
        _profile.exec_timer =
491
481
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "GetBlockTime", table_profile, 1);
492
481
        _profile.prepare_split_timer =
493
481
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PrepareSplitTime", table_profile, 1);
494
481
        _profile.finalize_timer =
495
481
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "FinalizeBlockTime", table_profile, 1);
496
481
        _profile.create_reader_timer =
497
481
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "CreateReaderTime", table_profile, 1);
498
481
        _profile.pushdown_agg_timer =
499
481
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "PushDownAggTime", table_profile, 1);
500
481
        _profile.open_reader_timer =
501
481
                ADD_CHILD_TIMER_WITH_LEVEL(_scanner_profile, "OpenReaderTime", table_profile, 1);
502
481
    }
503
552
    return Status::OK();
504
552
}
505
506
534
Status TableReader::_build_table_filters_from_conjuncts() {
507
534
    _table_filters.clear();
508
534
    for (const auto& conjunct : _conjuncts) {
509
34
        RETURN_IF_ERROR(
510
34
                build_table_filters_from_conjunct(conjunct, _runtime_state, &_table_filters));
511
34
    }
512
534
    return Status::OK();
513
534
}
514
515
533
Status TableReader::_open_local_filter_exprs(const FileScanRequest& file_request) {
516
533
    RowDescriptor row_desc;
517
533
    for (const auto& conjunct : file_request.conjuncts) {
518
29
        RETURN_IF_ERROR(conjunct->prepare(_runtime_state, row_desc));
519
29
        RETURN_IF_ERROR(conjunct->open(_runtime_state));
520
29
    }
521
533
    for (const auto& delete_conjunct : file_request.delete_conjuncts) {
522
12
        RETURN_IF_ERROR(delete_conjunct->prepare(_runtime_state, row_desc));
523
12
        RETURN_IF_ERROR(delete_conjunct->open(_runtime_state));
524
12
    }
525
533
    return Status::OK();
526
533
}
527
528
533
bool TableReader::_should_enable_condition_cache(const FileScanRequest& file_request) const {
529
533
    if (_condition_cache_digest == 0 || _push_down_agg_type == TPushAggOp::type::COUNT ||
530
533
        _current_file_description == std::nullopt || _data_reader.reader == nullptr) {
531
61
        return false;
532
61
    }
533
    // Condition cache is populated by file readers after evaluating file-local row-level
534
    // conjuncts. Metadata pruning can skip row groups/pages, but it does not produce a per-row
535
    // survivor bitmap that can safely populate the cache.
536
472
    if (file_request.conjuncts.empty()) {
537
459
        return false;
538
459
    }
539
    // Delete files/deletion vectors are table-format state. They may change independently of the
540
    // data file path/mtime/size used by the external cache key, so caching their result can become
541
    // stale. Keep delete filtering enabled, but do not read or write condition cache.
542
13
    if (_delete_rows != nullptr || !file_request.delete_conjuncts.empty()) {
543
1
        return false;
544
1
    }
545
    // Runtime filters can arrive late and their payload is not guaranteed to be represented by the
546
    // scan-local digest. Without a read-only mode, a MISS could insert a bitmap for P AND RF under
547
    // the digest for only P. This mirrors the old FileScanner guard.
548
12
    return !contains_runtime_filter(file_request.conjuncts);
549
13
}
550
551
533
Status TableReader::_init_reader_condition_cache(const FileScanRequest& file_request) {
552
533
    _condition_cache = nullptr;
553
533
    _condition_cache_ctx = nullptr;
554
533
    if (!_should_enable_condition_cache(file_request)) {
555
522
        return Status::OK();
556
522
    }
557
558
11
    auto* cache = segment_v2::ConditionCache::instance();
559
11
    if (cache == nullptr) {
560
0
        return Status::OK();
561
0
    }
562
11
    const auto& file = *_current_file_description;
563
11
    _condition_cache_key = segment_v2::ConditionCache::ExternalCacheKey(
564
11
            file.path, file.mtime, file.file_size, _condition_cache_digest, file.range_start_offset,
565
11
            file.range_size);
566
567
11
    segment_v2::ConditionCacheHandle handle;
568
11
    const bool condition_cache_hit = cache->lookup(_condition_cache_key, &handle);
569
11
    if (condition_cache_hit) {
570
0
        _condition_cache = handle.get_filter_result();
571
0
        ++_condition_cache_hit_count;
572
11
    } else {
573
11
        const int64_t total_rows = _data_reader.reader->get_total_rows();
574
11
        if (total_rows <= 0) {
575
5
            return Status::OK();
576
5
        }
577
        // Add one guard granule for split ranges that start in the middle of a granule. A guard
578
        // false bit beyond the real range never overlaps real rows, but avoids boundary overflow
579
        // when a reader marks the last partial granule.
580
6
        const size_t num_granules = (total_rows + ConditionCacheContext::GRANULE_SIZE - 1) /
581
6
                                    ConditionCacheContext::GRANULE_SIZE;
582
6
        _condition_cache = std::make_shared<std::vector<bool>>(num_granules + 1, false);
583
6
    }
584
585
6
    if (_condition_cache != nullptr) {
586
6
        _condition_cache_ctx = std::make_shared<ConditionCacheContext>();
587
6
        _condition_cache_ctx->is_hit = condition_cache_hit;
588
6
        _condition_cache_ctx->filter_result = _condition_cache;
589
6
        _data_reader.reader->set_condition_cache_context(_condition_cache_ctx);
590
6
    }
591
6
    return Status::OK();
592
11
}
593
594
533
void TableReader::_finalize_reader_condition_cache() {
595
533
    if (_condition_cache_ctx == nullptr || _condition_cache_ctx->is_hit) {
596
527
        _condition_cache = nullptr;
597
527
        _condition_cache_ctx = nullptr;
598
527
        return;
599
527
    }
600
    // LIMIT or scanner cancellation may close a reader before all selected row ranges are visited.
601
    // Unvisited granules remain false in a MISS bitmap, so inserting a partial bitmap would make a
602
    // later HIT skip valid rows. Only publish cache entries after the physical reader reaches EOF.
603
6
    if (!_current_reader_reached_eof) {
604
1
        _condition_cache = nullptr;
605
1
        _condition_cache_ctx = nullptr;
606
1
        return;
607
1
    }
608
5
    segment_v2::ConditionCache::instance()->insert(_condition_cache_key,
609
5
                                                   std::move(_condition_cache));
610
5
    _condition_cache = nullptr;
611
5
    _condition_cache_ctx = nullptr;
612
5
}
613
614
1.01k
Status TableReader::create_next_reader(bool* eos) {
615
1.01k
    SCOPED_TIMER(_profile.create_reader_timer);
616
1.01k
    DCHECK(_data_reader.reader == nullptr);
617
1.01k
    if (_current_task == nullptr) {
618
478
        *eos = true;
619
478
        return Status::OK();
620
478
    }
621
622
534
    RETURN_IF_ERROR(create_file_reader(&_data_reader.reader));
623
534
    DORIS_CHECK(_data_reader.reader != nullptr);
624
534
    if (_batch_size > 0) {
625
465
        _data_reader.reader->set_batch_size(_batch_size);
626
465
    }
627
534
    Status st = _data_reader.reader->init(_runtime_state);
628
534
    if (!st.ok()) {
629
0
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
630
0
            *eos = true;
631
0
            _data_reader.reader.reset();
632
0
            return Status::OK();
633
0
        }
634
0
        return st;
635
0
    }
636
534
    st = open_reader();
637
534
    if (!st.ok()) {
638
0
        if (_io_ctx != nullptr && _io_ctx->should_stop && st.is<ErrorCode::END_OF_FILE>()) {
639
0
            *eos = true;
640
0
            _data_reader.reader.reset();
641
0
            return Status::OK();
642
0
        }
643
0
        return st;
644
0
    }
645
534
    if (_data_reader.reader == nullptr) {
646
1
        *eos = _current_task == nullptr;
647
1
        return Status::OK();
648
1
    }
649
533
    *eos = false;
650
533
    return Status::OK();
651
534
}
652
653
524
Status TableReader::create_file_reader(std::unique_ptr<FileReader>* reader) {
654
524
    DORIS_CHECK(reader != nullptr);
655
524
    if (_format == FileFormat::PARQUET) {
656
167
        const bool enable_mapping_timestamp_tz =
657
167
                _scan_params != nullptr && _scan_params->__isset.enable_mapping_timestamp_tz &&
658
167
                _scan_params->enable_mapping_timestamp_tz;
659
167
        *reader = std::make_unique<format::parquet::ParquetReader>(
660
167
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
661
167
                _global_rowid_context, enable_mapping_timestamp_tz);
662
167
        return Status::OK();
663
167
    }
664
357
    if (_format == FileFormat::CSV) {
665
337
        if (_file_slot_descs == nullptr) {
666
0
            return Status::InvalidArgument("CSV reader requires file slot descriptors");
667
0
        }
668
        // CSV has no embedded schema. TableReader owns table-level mapping, while CsvReader needs
669
        // only the physical file slots plus scan text parameters to build a file-local schema.
670
        // Non-file columns such as partitions/defaults/virtual row ids are intentionally excluded
671
        // from `_file_slot_descs` and are materialized during finalize_chunk().
672
337
        *reader = std::make_unique<format::csv::CsvReader>(
673
337
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
674
337
                _scan_params, *_file_slot_descs, _current_range_compress_type,
675
337
                _current_range_load_id);
676
337
        return Status::OK();
677
337
    }
678
20
    if (_format == FileFormat::TEXT) {
679
0
        if (_file_slot_descs == nullptr) {
680
0
            return Status::InvalidArgument("Text reader requires file slot descriptors");
681
0
        }
682
        // Text files have no embedded schema. As with CSV, TableReader handles table-level mapping
683
        // and only passes physical file slots to the v2 TextReader.
684
0
        *reader = std::make_unique<format::text::TextReader>(
685
0
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
686
0
                _scan_params, *_file_slot_descs, _current_range_compress_type,
687
0
                _current_range_load_id);
688
0
        return Status::OK();
689
0
    }
690
20
    if (_format == FileFormat::JSON) {
691
18
        if (_file_slot_descs == nullptr) {
692
0
            return Status::InvalidArgument("JSON reader requires file slot descriptors");
693
0
        }
694
18
        *reader = std::make_unique<format::json::JsonReader>(
695
18
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile,
696
18
                _scan_params, _current_file_range_desc, *_file_slot_descs,
697
18
                _current_range_compress_type, _current_range_load_id);
698
18
        return Status::OK();
699
18
    }
700
2
    if (_format == FileFormat::NATIVE) {
701
2
        *reader = std::make_unique<format::native::NativeReader>(
702
2
                _system_properties, _current_task->data_file, _io_ctx, _scanner_profile);
703
2
        return Status::OK();
704
2
    }
705
0
    return Status::NotSupported("TableReader does not support file format {}",
706
0
                                file_format_to_string(_format));
707
2
}
708
709
549
std::unique_ptr<io::FileDescription> create_file_description(const TFileRangeDesc& range) {
710
549
    auto file_description = std::make_unique<io::FileDescription>();
711
549
    file_description->path = range.path;
712
549
    file_description->file_size = range.__isset.file_size ? range.file_size : -1;
713
549
    file_description->mtime = range.__isset.modification_time ? range.modification_time : 0;
714
549
    file_description->range_start_offset = range.__isset.start_offset ? range.start_offset : 0;
715
549
    file_description->range_size = range.__isset.size ? range.size : -1;
716
549
    if (range.__isset.fs_name) {
717
1
        file_description->fs_name = range.fs_name;
718
1
    }
719
549
    if (range.__isset.file_cache_admission) {
720
355
        file_description->file_cache_admission = range.file_cache_admission;
721
355
    }
722
549
    return file_description;
723
549
}
724
725
549
Status TableReader::prepare_split(const SplitReadOptions& options) {
726
549
    SCOPED_TIMER(_profile.prepare_split_timer);
727
    // Update to current split format to handle ORC/PARQUET files in one table.
728
549
    _format = options.current_split_format;
729
549
    _partition_values = std::move(options.partition_values);
730
549
    _current_task = std::make_unique<ScanTask>();
731
549
    _current_task->data_file = create_file_description(options.current_range);
732
549
    _current_file_description = *_current_task->data_file;
733
549
    _current_file_range_desc = options.current_range;
734
549
    _current_range_compress_type = options.current_range.__isset.compress_type
735
549
                                           ? options.current_range.compress_type
736
549
                                           : TFileCompressType::UNKNOWN;
737
549
    _current_range_load_id = options.current_range.__isset.load_id
738
549
                                     ? std::make_optional(options.current_range.load_id)
739
549
                                     : std::nullopt;
740
549
    _global_rowid_context = options.global_rowid_context;
741
549
    _delete_rows = nullptr;
742
549
    _aggregate_pushdown_tried = false;
743
549
    _remaining_table_level_count = -1;
744
549
    _current_reader_reached_eof = false;
745
549
    if (_push_down_agg_type == TPushAggOp::type::COUNT &&
746
549
        options.current_range.__isset.table_format_params &&
747
549
        options.current_range.table_format_params.__isset.table_level_row_count) {
748
6
        DORIS_CHECK(options.current_range.table_format_params.table_level_row_count >= -1);
749
6
        _remaining_table_level_count =
750
6
                options.current_range.table_format_params.table_level_row_count;
751
6
    }
752
549
    if (_is_table_level_count_active()) {
753
2
        return Status::OK();
754
2
    }
755
547
    return _parse_delete_predicates(options);
756
549
}
757
758
547
Status TableReader::_parse_delete_predicates(const SplitReadOptions& options) {
759
547
    DeleteFileDesc desc {.fs_name = options.current_range.fs_name};
760
547
    bool has_delete_file = false;
761
547
    RETURN_IF_ERROR(_parse_deletion_vector_file(options.current_range.table_format_params, &desc,
762
547
                                                &has_delete_file));
763
547
    if (has_delete_file) {
764
4
        DORIS_CHECK(options.cache != nullptr);
765
4
        Status create_status = Status::OK();
766
767
4
        _delete_rows = options.cache->get<DeleteRows>(desc.key, [&]() -> DeleteRows* {
768
4
            auto* delete_rows = new DeleteRows;
769
770
4
            DeletionVectorReader dv_reader(_runtime_state, _scanner_profile, *_scan_params, desc,
771
4
                                           _io_ctx.get());
772
4
            create_status = dv_reader.open();
773
4
            if (!create_status.ok()) [[unlikely]] {
774
0
                return nullptr;
775
0
            }
776
777
4
            size_t bytes_read = desc.size;
778
4
            std::vector<char> buffer(bytes_read);
779
4
            create_status = dv_reader.read_at(desc.start_offset, {buffer.data(), bytes_read});
780
4
            if (!create_status.ok()) [[unlikely]] {
781
0
                return nullptr;
782
0
            }
783
784
4
            const char* buf = buffer.data();
785
4
            SCOPED_TIMER(_profile.parse_delete_file_time);
786
4
            create_status = parse_deletion_vector(buf, bytes_read, desc.format, delete_rows);
787
4
            if (!create_status.ok()) [[unlikely]] {
788
0
                return nullptr;
789
0
            }
790
4
            COUNTER_UPDATE(_profile.num_delete_rows, delete_rows->size());
791
4
            return delete_rows;
792
4
        });
793
4
        RETURN_IF_ERROR(create_status);
794
4
    }
795
796
547
    return Status::OK();
797
547
}
798
} // namespace doris::format