Coverage Report

Created: 2026-07-12 17:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format_v2/table/iceberg_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/iceberg_reader.h"
19
20
#include <algorithm>
21
#include <memory>
22
#include <sstream>
23
#include <utility>
24
25
#include "common/cast_set.h"
26
#include "common/consts.h"
27
#include "core/assert_cast.h"
28
#include "core/block/block.h"
29
#include "core/column/column_const.h"
30
#include "core/column/column_nullable.h"
31
#include "core/column/column_string.h"
32
#include "core/column/column_struct.h"
33
#include "core/column/column_vector.h"
34
#include "core/data_type/data_type_number.h"
35
#include "core/data_type/define_primitive_type.h"
36
#include "core/field.h"
37
#include "exprs/vslot_ref.h"
38
#include "format/table/deletion_vector_reader.h"
39
#include "format_v2/expr/cast.h"
40
#include "format_v2/expr/equality_delete_predicate.h"
41
#include "format_v2/orc/orc_reader.h"
42
#include "format_v2/parquet/parquet_reader.h"
43
#include "format_v2/parquet/reader/column_reader.h"
44
#include "format_v2/table_reader.h"
45
#include "io/file_factory.h"
46
47
namespace doris::format::iceberg {
48
49
static constexpr const char* ROW_LINEAGE_ROW_ID = "_row_id";
50
static constexpr int32_t ROW_LINEAGE_ROW_ID_FIELD_ID = 2147483540;
51
52
template <typename T>
53
0
static std::string join_values_for_debug(const std::vector<T>& values) {
54
0
    std::ostringstream out;
55
0
    out << "[";
56
0
    for (size_t idx = 0; idx < values.size(); ++idx) {
57
0
        if (idx > 0) {
58
0
            out << ", ";
59
0
        }
60
0
        out << values[idx];
61
0
    }
62
0
    out << "]";
63
0
    return out.str();
64
0
}
65
66
1
static bool is_projected_row_lineage_row_id(const format::ColumnDefinition& column) {
67
    // Iceberg row lineage columns can be bound by field id when a mapper has already been built,
68
    // but customize_file_scan_request() is also exercised directly by scan-request tests before the
69
    // mapper exists. In that path, inspect the projected table schema so row-position dependencies
70
    // are still added for `_row_id`.
71
1
    return column.name == ROW_LINEAGE_ROW_ID ||
72
1
           (column.has_identifier_field_id() &&
73
0
            column.get_identifier_field_id() == ROW_LINEAGE_ROW_ID_FIELD_ID);
74
1
}
75
76
16
static bool is_projected_iceberg_rowid(const format::ColumnDefinition& column) {
77
16
    return column.name == BeConsts::ICEBERG_ROWID_COL;
78
16
}
79
80
0
static std::string iceberg_delete_file_debug_string(const TIcebergDeleteFileDesc& delete_file) {
81
0
    std::ostringstream out;
82
0
    out << "TIcebergDeleteFileDesc{path=" << (delete_file.__isset.path ? delete_file.path : "null")
83
0
        << ", content=" << (delete_file.__isset.content ? delete_file.content : -1)
84
0
        << ", file_format="
85
0
        << (delete_file.__isset.file_format ? static_cast<int>(delete_file.file_format) : -1)
86
0
        << ", position_lower_bound="
87
0
        << (delete_file.__isset.position_lower_bound ? delete_file.position_lower_bound : -1)
88
0
        << ", position_upper_bound="
89
0
        << (delete_file.__isset.position_upper_bound ? delete_file.position_upper_bound : -1)
90
0
        << ", field_ids="
91
0
        << (delete_file.__isset.field_ids ? join_values_for_debug(delete_file.field_ids) : "[]")
92
0
        << ", content_offset="
93
0
        << (delete_file.__isset.content_offset ? delete_file.content_offset : -1)
94
0
        << ", content_size_in_bytes="
95
0
        << (delete_file.__isset.content_size_in_bytes ? delete_file.content_size_in_bytes : -1)
96
0
        << "}";
97
0
    return out.str();
98
0
}
99
100
static std::string iceberg_delete_files_debug_string(
101
0
        const std::vector<TIcebergDeleteFileDesc>& delete_files) {
102
0
    std::ostringstream out;
103
0
    out << "[";
104
0
    for (size_t idx = 0; idx < delete_files.size(); ++idx) {
105
0
        if (idx > 0) {
106
0
            out << ", ";
107
0
        }
108
0
        out << iceberg_delete_file_debug_string(delete_files[idx]);
109
0
    }
110
0
    out << "]";
111
0
    return out.str();
112
0
}
113
114
0
static std::string iceberg_params_debug_string(const std::optional<TIcebergFileDesc>& params) {
115
0
    if (!params.has_value()) {
116
0
        return "null";
117
0
    }
118
0
    const auto& iceberg_params = *params;
119
0
    std::ostringstream out;
120
0
    out << "TIcebergFileDesc{format_version="
121
0
        << (iceberg_params.__isset.format_version ? iceberg_params.format_version : -1)
122
0
        << ", content=" << (iceberg_params.__isset.content ? iceberg_params.content : -1)
123
0
        << ", original_file_path="
124
0
        << (iceberg_params.__isset.original_file_path ? iceberg_params.original_file_path : "null")
125
0
        << ", row_count=" << (iceberg_params.__isset.row_count ? iceberg_params.row_count : -1)
126
0
        << ", partition_spec_id="
127
0
        << (iceberg_params.__isset.partition_spec_id ? iceberg_params.partition_spec_id : 0)
128
0
        << ", has_partition_data_json=" << iceberg_params.__isset.partition_data_json
129
0
        << ", first_row_id="
130
0
        << (iceberg_params.__isset.first_row_id ? iceberg_params.first_row_id : -1)
131
0
        << ", last_updated_sequence_number="
132
0
        << (iceberg_params.__isset.last_updated_sequence_number
133
0
                    ? iceberg_params.last_updated_sequence_number
134
0
                    : -1)
135
0
        << ", delete_file_count="
136
0
        << (iceberg_params.__isset.delete_files ? iceberg_params.delete_files.size() : 0)
137
0
        << ", delete_files="
138
0
        << (iceberg_params.__isset.delete_files
139
0
                    ? iceberg_delete_files_debug_string(iceberg_params.delete_files)
140
0
                    : "[]")
141
0
        << ", has_serialized_split=" << iceberg_params.__isset.serialized_split << "}";
142
0
    return out.str();
143
0
}
144
145
IcebergTableReader::PositionDeleteRowsCollector::PositionDeleteRowsCollector(
146
        std::string data_file_path, format::DeleteRows* rows)
147
8
        : _data_file_path(std::move(data_file_path)), _rows(rows) {}
148
149
Status IcebergTableReader::PositionDeleteRowsCollector::collect(const Block& block,
150
14
                                                                size_t read_rows) {
151
14
    if (read_rows == 0) {
152
6
        return Status::OK();
153
6
    }
154
8
    const auto& file_path_column_ptr =
155
8
            block.get_by_position(ICEBERG_FILE_PATH_BLOCK_POSITION).column;
156
8
    const auto& pos_column_ptr = block.get_by_position(ICEBERG_ROW_POS_BLOCK_POSITION).column;
157
8
    if (const auto* nullable_column = check_and_get_column<ColumnNullable>(*file_path_column_ptr);
158
8
        nullable_column != nullptr && nullable_column->has_null(0, read_rows)) {
159
1
        return Status::Corruption("Iceberg position delete column file_path contains null values");
160
1
    }
161
7
    if (const auto* nullable_column = check_and_get_column<ColumnNullable>(*pos_column_ptr);
162
7
        nullable_column != nullptr && nullable_column->has_null(0, read_rows)) {
163
1
        return Status::Corruption("Iceberg position delete column pos contains null values");
164
1
    }
165
6
    const auto& file_path_column =
166
6
            assert_cast<const ColumnString&>(*remove_nullable(file_path_column_ptr));
167
6
    const auto& pos_column = assert_cast<const ColumnInt64&>(*remove_nullable(pos_column_ptr));
168
15
    for (size_t row = 0; row < read_rows; ++row) {
169
9
        const auto file_path = file_path_column.get_data_at(row).to_string();
170
9
        if (file_path == _data_file_path) {
171
8
            _rows->push_back(pos_column.get_element(row));
172
8
        }
173
9
    }
174
6
    return Status::OK();
175
7
}
176
177
27
Status IcebergTableReader::prepare_split(const format::SplitReadOptions& options) {
178
27
    _row_lineage_columns = {};
179
27
    _iceberg_params.reset();
180
27
    _delete_predicates_initialized = false;
181
27
    _position_delete_rows_storage.clear();
182
27
    _equality_delete_filters.clear();
183
27
    if (options.current_range.__isset.table_format_params &&
184
27
        options.current_range.table_format_params.__isset.iceberg_params) {
185
25
        const auto& iceberg_params = options.current_range.table_format_params.iceberg_params;
186
25
        _iceberg_params = iceberg_params;
187
25
        if (iceberg_params.__isset.first_row_id) {
188
8
            _row_lineage_columns.first_row_id = iceberg_params.first_row_id;
189
8
        }
190
25
        if (iceberg_params.__isset.last_updated_sequence_number) {
191
6
            _row_lineage_columns.last_updated_sequence_number =
192
6
                    iceberg_params.last_updated_sequence_number;
193
6
        }
194
25
    }
195
27
    RETURN_IF_ERROR(TableReader::prepare_split(options));
196
24
    if (current_split_pruned()) {
197
0
        return Status::OK();
198
0
    }
199
24
    if (_is_table_level_count_active()) {
200
1
        return Status::OK();
201
1
    }
202
23
    RETURN_IF_ERROR(_init_delete_predicates(options.current_range.table_format_params));
203
21
    return Status::OK();
204
23
}
205
206
0
std::string IcebergTableReader::debug_string() const {
207
0
    size_t position_delete_file_count = 0;
208
0
    size_t equality_delete_file_count = 0;
209
0
    size_t deletion_vector_file_count = 0;
210
0
    if (_iceberg_params.has_value() && _iceberg_params->__isset.delete_files) {
211
0
        for (const auto& delete_file : _iceberg_params->delete_files) {
212
0
            if (!delete_file.__isset.content) {
213
0
                continue;
214
0
            }
215
0
            if (delete_file.content == POSITION_DELETE) {
216
0
                ++position_delete_file_count;
217
0
            } else if (delete_file.content == EQUALITY_DELETE) {
218
0
                ++equality_delete_file_count;
219
0
            } else if (delete_file.content == DELETION_VECTOR) {
220
0
                ++deletion_vector_file_count;
221
0
            }
222
0
        }
223
0
    }
224
225
0
    std::ostringstream equality_filters;
226
0
    equality_filters << "[";
227
0
    for (size_t idx = 0; idx < _equality_delete_filters.size(); ++idx) {
228
0
        if (idx > 0) {
229
0
            equality_filters << ", ";
230
0
        }
231
0
        const auto& filter = _equality_delete_filters[idx];
232
0
        equality_filters << "EqualityDeleteFilter{field_ids="
233
0
                         << join_values_for_debug(filter.field_ids) << ", key_types=[";
234
0
        for (size_t type_idx = 0; type_idx < filter.key_types.size(); ++type_idx) {
235
0
            if (type_idx > 0) {
236
0
                equality_filters << ", ";
237
0
            }
238
0
            equality_filters << (filter.key_types[type_idx] == nullptr
239
0
                                         ? "null"
240
0
                                         : filter.key_types[type_idx]->get_name());
241
0
        }
242
0
        equality_filters << "], delete_block_rows=" << filter.delete_block.rows()
243
0
                         << ", delete_block_columns=" << filter.delete_block.columns() << "}";
244
0
    }
245
0
    equality_filters << "]";
246
247
0
    std::ostringstream out;
248
0
    out << "IcebergTableReader{base=" << format::TableReader::debug_string()
249
0
        << ", iceberg_params=" << iceberg_params_debug_string(_iceberg_params)
250
0
        << ", row_lineage_first_row_id=" << _row_lineage_columns.first_row_id
251
0
        << ", row_lineage_last_updated_sequence_number="
252
0
        << _row_lineage_columns.last_updated_sequence_number
253
0
        << ", need_row_lineage_row_id=" << _need_row_lineage_row_id()
254
0
        << ", need_iceberg_rowid=" << _need_iceberg_rowid()
255
0
        << ", row_position_block_position=" << _row_position_block_position
256
0
        << ", delete_predicates_initialized=" << _delete_predicates_initialized
257
0
        << ", position_delete_file_count=" << position_delete_file_count
258
0
        << ", equality_delete_file_count=" << equality_delete_file_count
259
0
        << ", deletion_vector_file_count=" << deletion_vector_file_count
260
0
        << ", position_delete_rows_storage_count=" << _position_delete_rows_storage.size()
261
0
        << ", equality_delete_filter_count=" << _equality_delete_filters.size()
262
0
        << ", equality_delete_filters=" << equality_filters.str() << "}";
263
0
    return out.str();
264
0
}
265
266
20
Status IcebergTableReader::materialize_virtual_columns(Block* table_block) {
267
58
    for (size_t column_idx = 0; column_idx < _data_reader.column_mapper->mappings().size();
268
38
         ++column_idx) {
269
38
        const auto& mapping = _data_reader.column_mapper->mappings()[column_idx];
270
38
        switch (mapping.virtual_column_type) {
271
9
        case format::TableVirtualColumnType::ROW_ID:
272
9
            RETURN_IF_ERROR(_materialize_row_lineage_row_id(table_block, column_idx));
273
9
            break;
274
9
        case format::TableVirtualColumnType::LAST_UPDATED_SEQUENCE_NUMBER:
275
8
            RETURN_IF_ERROR(
276
8
                    _materialize_row_lineage_last_updated_sequence_number(table_block, column_idx));
277
8
            break;
278
8
        case format::TableVirtualColumnType::ICEBERG_ROWID:
279
1
            RETURN_IF_ERROR(_materialize_iceberg_rowid(table_block, column_idx));
280
1
            break;
281
20
        case format::TableVirtualColumnType::INVALID:
282
20
            break;
283
38
        }
284
38
    }
285
20
    return Status::OK();
286
20
}
287
288
21
Status IcebergTableReader::customize_file_scan_request(format::FileScanRequest* file_request) {
289
21
    RETURN_IF_ERROR(TableReader::customize_file_scan_request(file_request));
290
21
    if ((_row_lineage_columns.first_row_id >= 0 && _need_row_lineage_row_id()) ||
291
21
        _need_iceberg_rowid()) {
292
9
        RETURN_IF_ERROR(_append_row_position_output_column(file_request));
293
9
    }
294
21
    RETURN_IF_ERROR(_append_equality_delete_predicates(file_request));
295
21
    return Status::OK();
296
21
}
297
298
20
bool IcebergTableReader::_supports_aggregate_pushdown(TPushAggOp::type agg_type) const {
299
20
    if (!TableReader::_supports_aggregate_pushdown(agg_type)) {
300
19
        return false;
301
19
    }
302
1
    return _equality_delete_filters.empty();
303
20
}
304
305
Status IcebergTableReader::_parse_deletion_vector_file(const TTableFormatFileDesc& t_desc,
306
                                                       DeleteFileDesc* desc,
307
33
                                                       bool* has_delete_file) {
308
33
    DORIS_CHECK(desc != nullptr);
309
33
    DORIS_CHECK(has_delete_file != nullptr);
310
33
    *has_delete_file = false;
311
33
    if (!t_desc.__isset.iceberg_params) {
312
2
        return Status::OK();
313
2
    }
314
31
    const auto& iceberg_params = t_desc.iceberg_params;
315
31
    if (!iceberg_params.__isset.format_version ||
316
31
        iceberg_params.format_version < MIN_SUPPORT_DELETE_FILES_VERSION ||
317
31
        !iceberg_params.__isset.delete_files || iceberg_params.delete_files.empty()) {
318
8
        return Status::OK();
319
8
    }
320
321
23
    const TIcebergDeleteFileDesc* deletion_vector = nullptr;
322
25
    for (const auto& delete_file : iceberg_params.delete_files) {
323
25
        if (!delete_file.__isset.content || delete_file.content != DELETION_VECTOR) {
324
11
            continue;
325
11
        }
326
14
        if (deletion_vector != nullptr) {
327
1
            return Status::DataQualityError("This iceberg data file has multiple DVs.");
328
1
        }
329
13
        deletion_vector = &delete_file;
330
13
    }
331
22
    if (deletion_vector == nullptr) {
332
10
        return Status::OK();
333
10
    }
334
12
    if (!deletion_vector->__isset.content_offset ||
335
12
        !deletion_vector->__isset.content_size_in_bytes) {
336
1
        return Status::InternalError("Deletion vector is missing content offset or length");
337
1
    }
338
339
11
    const std::string data_file_path = iceberg_params.__isset.original_file_path
340
11
                                               ? iceberg_params.original_file_path
341
11
                                               : _data_file_path();
342
11
    desc->key = build_iceberg_deletion_vector_cache_key(data_file_path, *deletion_vector);
343
11
    desc->path = deletion_vector->path;
344
11
    desc->start_offset = deletion_vector->content_offset;
345
11
    desc->size = deletion_vector->content_size_in_bytes;
346
11
    desc->file_size = -1;
347
11
    desc->format = DeleteFileDesc::Format::ICEBERG;
348
11
    *has_delete_file = true;
349
11
    return Status::OK();
350
12
}
351
352
23
Status IcebergTableReader::_init_delete_predicates(const TTableFormatFileDesc& t_desc) {
353
23
    if (!t_desc.__isset.iceberg_params || _delete_predicates_initialized) {
354
2
        _delete_predicates_initialized = true;
355
2
        return Status::OK();
356
2
    }
357
21
    const auto& iceberg_params = t_desc.iceberg_params;
358
21
    if (!iceberg_params.__isset.format_version ||
359
21
        iceberg_params.format_version < MIN_SUPPORT_DELETE_FILES_VERSION ||
360
21
        !iceberg_params.__isset.delete_files || iceberg_params.delete_files.empty()) {
361
8
        _delete_predicates_initialized = true;
362
8
        return Status::OK();
363
8
    }
364
365
13
    std::vector<TIcebergDeleteFileDesc> position_delete_files;
366
13
    std::vector<TIcebergDeleteFileDesc> equality_delete_files;
367
14
    for (const auto& delete_file : iceberg_params.delete_files) {
368
14
        if (!delete_file.__isset.content) {
369
0
            continue;
370
0
        }
371
14
        if (delete_file.content == POSITION_DELETE) {
372
9
            position_delete_files.push_back(delete_file);
373
9
        } else if (delete_file.content == EQUALITY_DELETE) {
374
2
            equality_delete_files.push_back(delete_file);
375
2
        }
376
14
    }
377
    // Per Iceberg scan planning, position delete files apply only when there is no deletion vector
378
    // for the data file. DVs and position deletes now intentionally use different in-memory
379
    // representations, so use the Roaring pointer as the DV sentinel.
380
13
    if (_deletion_vector != nullptr) {
381
3
        position_delete_files.clear();
382
3
    }
383
    // Initialize position and equality delete predicates. Position delete files contain row
384
    // positions of deleted rows, which can be directly added to `_delete_rows`. Equality delete
385
    // files contain values of deleted rows, which require reading the files and building
386
    // predicates for later filtering.
387
13
    if (!position_delete_files.empty()) {
388
8
        RETURN_IF_ERROR(_init_position_delete_rows(position_delete_files));
389
8
    }
390
11
    if (!equality_delete_files.empty()) {
391
2
        RETURN_IF_ERROR(_init_equality_delete_predicates(equality_delete_files));
392
2
    }
393
394
11
    _delete_predicates_initialized = true;
395
11
    return Status::OK();
396
11
}
397
398
std::shared_ptr<io::FileSystemProperties> IcebergTableReader::_delete_file_system_properties(
399
10
        const TFileScanRangeParams& scan_params) {
400
10
    auto system_properties = std::make_shared<io::FileSystemProperties>();
401
10
    system_properties->system_type =
402
10
            scan_params.__isset.file_type ? scan_params.file_type : TFileType::FILE_LOCAL;
403
10
    system_properties->properties = scan_params.properties;
404
10
    system_properties->hdfs_params = scan_params.hdfs_params;
405
10
    if (scan_params.__isset.broker_addresses) {
406
0
        system_properties->broker_addresses.assign(scan_params.broker_addresses.begin(),
407
0
                                                   scan_params.broker_addresses.end());
408
0
    }
409
10
    return system_properties;
410
10
}
411
412
std::unique_ptr<io::FileDescription> IcebergTableReader::_delete_file_description(
413
10
        const TFileRangeDesc& range) {
414
10
    auto file_description = std::make_unique<io::FileDescription>();
415
10
    file_description->path = range.path;
416
10
    file_description->file_size = range.__isset.file_size ? range.file_size : -1;
417
10
    file_description->range_start_offset = range.__isset.start_offset ? range.start_offset : 0;
418
10
    file_description->range_size = range.__isset.size ? range.size : -1;
419
10
    if (range.__isset.fs_name) {
420
0
        file_description->fs_name = range.fs_name;
421
0
    }
422
10
    return file_description;
423
10
}
424
425
9
std::string IcebergTableReader::_data_file_path() const {
426
9
    if (_iceberg_params.has_value() && _iceberg_params->__isset.original_file_path) {
427
8
        return _iceberg_params->original_file_path;
428
8
    }
429
1
    DORIS_CHECK(_current_task != nullptr);
430
1
    DORIS_CHECK(_current_task->data_file != nullptr);
431
1
    return _current_task->data_file->path;
432
9
}
433
434
9
Status IcebergTableReader::_append_row_position_output_column(format::FileScanRequest* request) {
435
9
    const auto row_position_column_id = format::LocalColumnId(format::ROW_POSITION_COLUMN_ID);
436
9
    _append_file_scan_column(request, row_position_column_id, &request->non_predicate_columns);
437
9
    _row_position_block_position = request->local_positions.at(row_position_column_id).value();
438
9
    return Status::OK();
439
9
}
440
441
21
Status IcebergTableReader::_append_equality_delete_predicates(format::FileScanRequest* request) {
442
21
    DORIS_CHECK(request != nullptr);
443
21
    for (const auto& filter : _equality_delete_filters) {
444
2
        auto delete_predicate =
445
2
                std::make_shared<EqualityDeletePredicate>(filter.delete_block, filter.field_ids);
446
2
        DCHECK_EQ(filter.field_ids.size(), filter.key_types.size());
447
4
        for (size_t idx = 0; idx < filter.field_ids.size(); ++idx) {
448
2
            const int field_id = filter.field_ids[idx];
449
2
            auto field_it = std::ranges::find_if(
450
2
                    _data_reader.file_schema, [field_id](const format::ColumnDefinition& field) {
451
2
                        return field.has_identifier_field_id() &&
452
2
                               field.get_identifier_field_id() == field_id;
453
2
                    });
454
2
            if (field_it == _data_reader.file_schema.end()) {
455
0
                return Status::InternalError(
456
0
                        "Can not find equality delete column field id {} in data file schema",
457
0
                        field_id);
458
0
            }
459
2
            const auto field_column_id = format::LocalColumnId(field_it->file_local_id());
460
2
            _append_file_scan_column(request, field_column_id, &request->predicate_columns);
461
2
            const auto block_position = request->local_positions.at(field_column_id).value();
462
2
            auto slot = VSlotRef::create_shared(cast_set<int>(block_position),
463
2
                                                cast_set<int>(block_position), -1, field_it->type,
464
2
                                                field_it->name);
465
2
            if (field_it->type->equals(*filter.key_types[idx])) {
466
1
                delete_predicate->add_child(std::move(slot));
467
1
            } else {
468
1
                auto cast_expr = Cast::create_shared(filter.key_types[idx]);
469
1
                cast_expr->add_child(std::move(slot));
470
1
                delete_predicate->add_child(std::move(cast_expr));
471
1
            }
472
2
        }
473
2
        request->delete_conjuncts.push_back(
474
2
                VExprContext::create_shared(std::move(delete_predicate)));
475
2
    }
476
21
    return Status::OK();
477
21
}
478
479
Status IcebergTableReader::_create_delete_file_reader(const TIcebergDeleteFileDesc& delete_file,
480
                                                      const TFileScanRangeParams& scan_params,
481
                                                      IcebergDeleteFileIOContext* delete_io_ctx,
482
10
                                                      std::unique_ptr<format::FileReader>* reader) {
483
10
    DORIS_CHECK(delete_io_ctx != nullptr);
484
10
    DORIS_CHECK(reader != nullptr);
485
10
    if (!delete_file.__isset.file_format) {
486
0
        return Status::InternalError("Iceberg delete file is missing file format");
487
0
    }
488
10
    if (delete_file.file_format != TFileFormatType::FORMAT_PARQUET &&
489
10
        delete_file.file_format != TFileFormatType::FORMAT_ORC) {
490
0
        return Status::NotSupported("Unsupported Iceberg delete file format {}",
491
0
                                    delete_file.file_format);
492
0
    }
493
10
    auto delete_range = build_iceberg_delete_file_range(delete_file.path);
494
10
    if (_current_task != nullptr && _current_task->data_file != nullptr &&
495
10
        !_current_task->data_file->fs_name.empty()) {
496
0
        delete_range.__set_fs_name(_current_task->data_file->fs_name);
497
0
    }
498
10
    auto system_properties = _delete_file_system_properties(scan_params);
499
10
    auto file_description = _delete_file_description(delete_range);
500
10
    std::shared_ptr<io::IOContext> io_ctx(&delete_io_ctx->io_ctx, [](io::IOContext*) {});
501
10
    if (delete_file.file_format == TFileFormatType::FORMAT_PARQUET) {
502
10
        *reader = std::make_unique<format::parquet::ParquetReader>(
503
10
                system_properties, file_description, io_ctx, _scanner_profile);
504
10
    } else {
505
0
        *reader = std::make_unique<format::orc::OrcReader>(system_properties, file_description,
506
0
                                                           io_ctx, _scanner_profile);
507
0
    }
508
10
    RETURN_IF_ERROR((*reader)->init(_runtime_state));
509
10
    return Status::OK();
510
10
}
511
512
Status IcebergTableReader::_read_position_delete_file(const TIcebergDeleteFileDesc& delete_file,
513
                                                      const TFileScanRangeParams& scan_params,
514
                                                      IcebergDeleteFileIOContext* delete_io_ctx,
515
8
                                                      PositionDeleteRowsCollector* collector) {
516
8
    DORIS_CHECK(collector != nullptr);
517
8
    std::unique_ptr<format::FileReader> reader;
518
8
    RETURN_IF_ERROR(_create_delete_file_reader(delete_file, scan_params, delete_io_ctx, &reader));
519
8
    DORIS_CHECK(reader != nullptr);
520
521
8
    std::vector<format::ColumnDefinition> schema;
522
8
    RETURN_IF_ERROR(reader->get_schema(&schema));
523
8
    format::ColumnDefinition* file_path_field = nullptr;
524
8
    format::ColumnDefinition* pos_field = nullptr;
525
16
    for (auto& field : schema) {
526
16
        if (field.name == ICEBERG_FILE_PATH) {
527
8
            file_path_field = &field;
528
8
        } else if (field.name == ICEBERG_ROW_POS) {
529
8
            pos_field = &field;
530
8
        }
531
16
    }
532
8
    if (file_path_field == nullptr || pos_field == nullptr) {
533
0
        return Status::InternalError("Position delete file is missing required columns");
534
0
    }
535
536
8
    auto request = std::make_shared<format::FileScanRequest>();
537
8
    request->non_predicate_columns = {
538
8
            format::LocalColumnIndex::top_level(
539
8
                    format::LocalColumnId(file_path_field->file_local_id())),
540
8
            format::LocalColumnIndex::top_level(format::LocalColumnId(pos_field->file_local_id()))};
541
8
    request->local_positions = {
542
8
            {format::LocalColumnId(file_path_field->file_local_id()),
543
8
             format::LocalIndex(ICEBERG_FILE_PATH_BLOCK_POSITION)},
544
8
            {format::LocalColumnId(pos_field->file_local_id()),
545
8
             format::LocalIndex(ICEBERG_ROW_POS_BLOCK_POSITION)},
546
8
    };
547
8
    RETURN_IF_ERROR(reader->open(request));
548
549
8
    bool eof = false;
550
8
    auto build_position_delete_block = [](const format::ColumnDefinition& file_path_field,
551
14
                                          const format::ColumnDefinition& pos_field) -> Block {
552
14
        Block block;
553
14
        block.insert(
554
14
                {file_path_field.type->create_column(), file_path_field.type, ICEBERG_FILE_PATH});
555
14
        block.insert({pos_field.type->create_column(), pos_field.type, ICEBERG_ROW_POS});
556
14
        return block;
557
14
    };
558
20
    while (!eof) {
559
14
        Block block = build_position_delete_block(*file_path_field, *pos_field);
560
14
        size_t read_rows = 0;
561
14
        RETURN_IF_ERROR(reader->get_block(&block, &read_rows, &eof));
562
14
        RETURN_IF_ERROR(collector->collect(block, read_rows));
563
14
    }
564
6
    return reader->close();
565
8
}
566
567
Status IcebergTableReader::_init_position_delete_rows(
568
8
        const std::vector<TIcebergDeleteFileDesc>& delete_files) {
569
8
    TFileScanRangeParams delete_scan_params =
570
8
            _scan_params == nullptr ? TFileScanRangeParams() : *_scan_params;
571
8
    format::DeleteRows position_delete_rows;
572
8
    IcebergDeleteFileIOContext delete_io_ctx(_runtime_state);
573
8
    PositionDeleteRowsCollector collector(_data_file_path(), &position_delete_rows);
574
8
    for (const auto& delete_file : delete_files) {
575
8
        RETURN_IF_ERROR(_read_position_delete_file(delete_file, delete_scan_params, &delete_io_ctx,
576
8
                                                   &collector));
577
8
    }
578
6
    if (position_delete_rows.empty()) {
579
0
        return Status::OK();
580
0
    }
581
    // Position delete files and deletion vectors both become row-position deletes for the
582
    // common TableReader DeletePredicate path. Keep the merged rows in a member vector because
583
    // DeletePredicate stores a reference to the vector used by _delete_rows.
584
6
    _position_delete_rows_storage.insert(_position_delete_rows_storage.end(),
585
6
                                         position_delete_rows.begin(), position_delete_rows.end());
586
6
    std::sort(_position_delete_rows_storage.begin(), _position_delete_rows_storage.end());
587
6
    _position_delete_rows_storage.erase(
588
6
            std::unique(_position_delete_rows_storage.begin(), _position_delete_rows_storage.end()),
589
6
            _position_delete_rows_storage.end());
590
6
    _delete_rows = &_position_delete_rows_storage;
591
6
    return Status::OK();
592
6
}
593
594
Status IcebergTableReader::_init_equality_delete_predicates(
595
2
        const std::vector<TIcebergDeleteFileDesc>& delete_files) {
596
2
    TFileScanRangeParams delete_scan_params =
597
2
            _scan_params == nullptr ? TFileScanRangeParams() : *_scan_params;
598
2
    IcebergDeleteFileIOContext delete_io_ctx(_runtime_state);
599
2
    for (const auto& delete_file : delete_files) {
600
2
        RETURN_IF_ERROR(
601
2
                _read_equality_delete_file(delete_file, delete_scan_params, &delete_io_ctx));
602
2
    }
603
2
    return Status::OK();
604
2
}
605
606
Status IcebergTableReader::_read_equality_delete_file(const TIcebergDeleteFileDesc& delete_file,
607
                                                      const TFileScanRangeParams& scan_params,
608
2
                                                      IcebergDeleteFileIOContext* delete_io_ctx) {
609
2
    if (!delete_file.__isset.field_ids || delete_file.field_ids.empty()) {
610
0
        return Status::InternalError("Iceberg equality delete file is missing field ids");
611
0
    }
612
2
    std::unique_ptr<format::FileReader> reader;
613
2
    RETURN_IF_ERROR(_create_delete_file_reader(delete_file, scan_params, delete_io_ctx, &reader));
614
2
    DORIS_CHECK(reader != nullptr);
615
616
2
    std::vector<format::ColumnDefinition> schema;
617
2
    RETURN_IF_ERROR(reader->get_schema(&schema));
618
2
    std::vector<format::ColumnDefinition> delete_fields;
619
2
    std::vector<int> delete_field_ids;
620
2
    std::vector<DataTypePtr> delete_key_types;
621
2
    for (const auto field_id : delete_file.field_ids) {
622
2
        auto field_it = std::find_if(schema.begin(), schema.end(),
623
2
                                     [field_id](const format::ColumnDefinition& field) {
624
2
                                         return field.has_identifier_field_id() &&
625
2
                                                field_id == field.get_identifier_field_id();
626
2
                                     });
627
2
        if (field_it == schema.end()) {
628
0
            return Status::InternalError("Can not find field id {} in equality delete file {}",
629
0
                                         field_id, delete_file.path);
630
0
        }
631
2
        if (!field_it->children.empty()) {
632
0
            return Status::NotSupported(
633
0
                    "Iceberg equality delete does not support complex column {}", field_it->name);
634
0
        }
635
2
        delete_fields.push_back(*field_it);
636
2
        delete_field_ids.push_back(field_id);
637
2
        delete_key_types.push_back(field_it->type);
638
2
    }
639
640
2
    auto request = std::make_shared<format::FileScanRequest>();
641
4
    for (size_t idx = 0; idx < delete_fields.size(); ++idx) {
642
2
        const auto local_column_id = format::LocalColumnId(delete_fields[idx].file_local_id());
643
2
        request->non_predicate_columns.push_back(
644
2
                format::LocalColumnIndex::top_level(local_column_id));
645
2
        request->local_positions.emplace(local_column_id, format::LocalIndex(idx));
646
2
    }
647
2
    RETURN_IF_ERROR(reader->open(request));
648
649
2
    auto build_equality_delete_block =
650
6
            [](const std::vector<format::ColumnDefinition> fields) -> Block {
651
6
        Block block;
652
6
        for (const auto& field : fields) {
653
6
            block.insert({field.type->create_column(), field.type, field.name});
654
6
        }
655
6
        return block;
656
6
    };
657
2
    Block delete_block = build_equality_delete_block(delete_fields);
658
2
    MutableBlock mutable_delete_block(std::move(delete_block));
659
2
    bool eof = false;
660
6
    while (!eof) {
661
4
        Block block = build_equality_delete_block(delete_fields);
662
4
        size_t read_rows = 0;
663
4
        RETURN_IF_ERROR(reader->get_block(&block, &read_rows, &eof));
664
4
        if (read_rows > 0) {
665
2
            RETURN_IF_ERROR(mutable_delete_block.merge(block));
666
2
        }
667
4
    }
668
2
    RETURN_IF_ERROR(reader->close());
669
2
    delete_block = mutable_delete_block.to_block();
670
2
    _equality_delete_filters.push_back(
671
2
            EqualityDeleteFilter {.field_ids = std::move(delete_field_ids),
672
2
                                  .key_types = std::move(delete_key_types),
673
2
                                  .delete_block = std::move(delete_block)});
674
2
    return Status::OK();
675
2
}
676
677
9
Status IcebergTableReader::_materialize_row_lineage_row_id(Block* table_block, size_t column_idx) {
678
9
    if (_row_lineage_columns.first_row_id < 0) {
679
2
        return Status::OK();
680
2
    }
681
7
    DORIS_CHECK(_row_position_block_position < _data_reader.block_template.columns());
682
7
    const auto& row_position_column = assert_cast<const ColumnInt64&>(
683
7
            *_data_reader.block_template.get_by_position(_row_position_block_position).column);
684
7
    DORIS_CHECK(row_position_column.size() == table_block->rows());
685
7
    auto column = IColumn::mutate(
686
7
            table_block->get_by_position(column_idx).column->convert_to_full_column_if_const());
687
7
    auto* nullable_column = assert_cast<ColumnNullable*>(column.get());
688
7
    auto& null_map = nullable_column->get_null_map_data();
689
7
    auto& data = assert_cast<ColumnInt64&>(*nullable_column->get_nested_column_ptr()).get_data();
690
7
    DORIS_CHECK(null_map.size() == row_position_column.size());
691
7
    DORIS_CHECK(data.size() == row_position_column.size());
692
23
    for (size_t row = 0; row < row_position_column.size(); ++row) {
693
16
        if (null_map[row]) {
694
10
            null_map[row] = 0;
695
10
            data[row] = _row_lineage_columns.first_row_id + row_position_column.get_element(row);
696
10
        }
697
16
    }
698
7
    table_block->replace_by_position(column_idx, std::move(column));
699
7
    return Status::OK();
700
9
}
701
702
1
Status IcebergTableReader::_materialize_iceberg_rowid(Block* table_block, size_t column_idx) {
703
1
    DORIS_CHECK(_row_position_block_position < _data_reader.block_template.columns());
704
1
    const auto& row_position_column = assert_cast<const ColumnInt64&>(
705
1
            *_data_reader.block_template.get_by_position(_row_position_block_position).column);
706
1
    DORIS_CHECK(row_position_column.size() == table_block->rows());
707
708
1
    const auto& type = table_block->get_by_position(column_idx).type;
709
1
    auto column = type->create_column();
710
1
    auto* nullable_column = check_and_get_column<ColumnNullable>(column.get());
711
1
    auto* struct_column = nullable_column != nullptr
712
1
                                  ? check_and_get_column<ColumnStruct>(
713
1
                                            nullable_column->get_nested_column_ptr().get())
714
1
                                  : check_and_get_column<ColumnStruct>(column.get());
715
1
    DORIS_CHECK(struct_column != nullptr);
716
1
    DORIS_CHECK(struct_column->tuple_size() >= 4);
717
718
1
    const auto rows = row_position_column.size();
719
1
    const auto file_path = _data_file_path();
720
1
    const int32_t partition_spec_id =
721
1
            _iceberg_params.has_value() && _iceberg_params->__isset.partition_spec_id
722
1
                    ? _iceberg_params->partition_spec_id
723
1
                    : 0;
724
1
    const std::string partition_data_json =
725
1
            _iceberg_params.has_value() && _iceberg_params->__isset.partition_data_json
726
1
                    ? _iceberg_params->partition_data_json
727
1
                    : "";
728
729
1
    auto& file_path_column = struct_column->get_column(0);
730
1
    auto& row_pos_column = struct_column->get_column(1);
731
1
    auto& spec_id_column = struct_column->get_column(2);
732
1
    auto& partition_data_column = struct_column->get_column(3);
733
1
    file_path_column.reserve(rows);
734
1
    row_pos_column.reserve(rows);
735
1
    spec_id_column.reserve(rows);
736
1
    partition_data_column.reserve(rows);
737
3
    for (size_t row = 0; row < rows; ++row) {
738
2
        file_path_column.insert_data(file_path.data(), file_path.size());
739
2
        const int64_t row_pos = row_position_column.get_element(row);
740
2
        row_pos_column.insert_data(reinterpret_cast<const char*>(&row_pos), sizeof(row_pos));
741
2
        spec_id_column.insert_data(reinterpret_cast<const char*>(&partition_spec_id),
742
2
                                   sizeof(partition_spec_id));
743
2
        partition_data_column.insert_data(partition_data_json.data(), partition_data_json.size());
744
2
    }
745
1
    if (nullable_column != nullptr) {
746
1
        nullable_column->get_null_map_data().resize_fill(rows, 0);
747
1
    }
748
1
    table_block->replace_by_position(column_idx, std::move(column));
749
1
    return Status::OK();
750
1
}
751
752
Status IcebergTableReader::_materialize_row_lineage_last_updated_sequence_number(
753
8
        Block* table_block, size_t column_idx) {
754
8
    if (_row_lineage_columns.last_updated_sequence_number < 0) {
755
2
        return Status::OK();
756
2
    }
757
6
    auto column = IColumn::mutate(
758
6
            table_block->get_by_position(column_idx).column->convert_to_full_column_if_const());
759
6
    auto* nullable_column = assert_cast<ColumnNullable*>(column.get());
760
6
    auto& null_map = nullable_column->get_null_map_data();
761
6
    auto& data = assert_cast<ColumnInt64&>(*nullable_column->get_nested_column_ptr()).get_data();
762
6
    DORIS_CHECK(null_map.size() == table_block->rows());
763
6
    DORIS_CHECK(data.size() == table_block->rows());
764
20
    for (size_t row = 0; row < table_block->rows(); ++row) {
765
14
        if (null_map[row]) {
766
8
            null_map[row] = 0;
767
8
            data[row] = _row_lineage_columns.last_updated_sequence_number;
768
8
        }
769
14
    }
770
6
    table_block->replace_by_position(column_idx, std::move(column));
771
6
    return Status::OK();
772
8
}
773
774
8
bool IcebergTableReader::_need_row_lineage_row_id() const {
775
8
    if (_data_reader.column_mapper != nullptr) {
776
7
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
777
7
            if (mapping.virtual_column_type == format::TableVirtualColumnType::ROW_ID) {
778
7
                return true;
779
7
            }
780
7
        }
781
7
    }
782
1
    return std::ranges::any_of(_projected_columns, is_projected_row_lineage_row_id);
783
8
}
784
785
13
bool IcebergTableReader::_need_iceberg_rowid() const {
786
13
    if (_data_reader.column_mapper != nullptr) {
787
17
        for (const auto& mapping : _data_reader.column_mapper->mappings()) {
788
17
            if (mapping.virtual_column_type == format::TableVirtualColumnType::ICEBERG_ROWID) {
789
1
                return true;
790
1
            }
791
17
        }
792
13
    }
793
12
    return std::ranges::any_of(_projected_columns, is_projected_iceberg_rowid);
794
13
}
795
796
} // namespace doris::format::iceberg