Coverage Report

Created: 2026-07-06 09:46

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