Coverage Report

Created: 2026-11-03 20:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/table/iceberg_default_value.h
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
#pragma once
19
20
#include <gen_cpp/ExternalTableSchema_types.h>
21
#include <rapidjson/document.h>
22
#include <rapidjson/stringbuffer.h>
23
#include <rapidjson/writer.h>
24
25
#include <cstddef>
26
#include <deque>
27
#include <limits>
28
#include <string>
29
#include <string_view>
30
#include <unordered_map>
31
#include <utility>
32
33
#include "common/status.h"
34
#include "core/assert_cast.h"
35
#include "core/column/column.h"
36
#include "core/data_type/data_type.h"
37
#include "core/data_type/data_type_array.h"
38
#include "core/data_type/data_type_map.h"
39
#include "core/data_type/data_type_nullable.h"
40
#include "core/data_type/data_type_struct.h"
41
#include "core/data_type/primitive_type.h"
42
#include "core/field.h"
43
#include "util/string_util.h"
44
#include "util/url_coding.h"
45
46
namespace doris::iceberg {
47
48
namespace detail {
49
50
inline bool parse_non_finite_default(doris::PrimitiveType type, std::string_view value,
51
101
                                     Field* result) {
52
101
    DORIS_CHECK(result != nullptr);
53
101
    if (type != TYPE_FLOAT && type != TYPE_DOUBLE) {
54
94
        return false;
55
94
    }
56
7
    double parsed;
57
7
    if (value == "NaN") {
58
2
        parsed = std::numeric_limits<double>::quiet_NaN();
59
5
    } else if (value == "Infinity") {
60
2
        parsed = std::numeric_limits<double>::infinity();
61
3
    } else if (value == "-Infinity") {
62
2
        parsed = -std::numeric_limits<double>::infinity();
63
2
    } else {
64
1
        return false;
65
1
    }
66
    // Iceberg serializes non-finite defaults as strings, which generic Doris numeric parsers reject.
67
6
    *result = type == TYPE_FLOAT ? Field::create_field<TYPE_FLOAT>(static_cast<float>(parsed))
68
6
                                 : Field::create_field<TYPE_DOUBLE>(parsed);
69
6
    return true;
70
7
}
71
72
0
inline const schema::external::TField* get_field_ptr(const schema::external::TFieldPtr& field_ptr) {
73
0
    if (!field_ptr.__isset.field_ptr || field_ptr.field_ptr == nullptr) {
74
0
        return nullptr;
75
0
    }
76
0
    return field_ptr.field_ptr.get();
77
0
}
78
79
inline const schema::external::TField* find_struct_child(
80
0
        const schema::external::TStructField& struct_field, const std::string& name) {
81
0
    if (!struct_field.__isset.fields) {
82
0
        return nullptr;
83
0
    }
84
0
    for (const auto& child_ptr : struct_field.fields) {
85
0
        const auto* child = get_field_ptr(child_ptr);
86
0
        if (child != nullptr && child->__isset.name && iequal(child->name, name)) {
87
0
            return child;
88
0
        }
89
0
    }
90
0
    for (const auto& child_ptr : struct_field.fields) {
91
0
        const auto* child = get_field_ptr(child_ptr);
92
0
        if (child == nullptr || !child->__isset.name_mapping) {
93
0
            continue;
94
0
        }
95
0
        for (const auto& alias : child->name_mapping) {
96
0
            if (iequal(alias, name)) {
97
0
                return child;
98
0
            }
99
0
        }
100
0
    }
101
0
    return nullptr;
102
0
}
103
104
0
inline int hex_value(char c) {
105
0
    if (c >= '0' && c <= '9') {
106
0
        return c - '0';
107
0
    }
108
0
    if (c >= 'a' && c <= 'f') {
109
0
        return c - 'a' + 10;
110
0
    }
111
0
    if (c >= 'A' && c <= 'F') {
112
0
        return c - 'A' + 10;
113
0
    }
114
0
    return -1;
115
0
}
116
117
0
inline Status decode_hex(std::string_view encoded, std::string* decoded) {
118
0
    DORIS_CHECK(decoded != nullptr);
119
0
    if ((encoded.size() & 1U) != 0) {
120
0
        return Status::InvalidArgument("Invalid odd-length Iceberg binary default");
121
0
    }
122
0
    decoded->resize(encoded.size() / 2);
123
0
    for (size_t index = 0; index < encoded.size(); index += 2) {
124
0
        const int high = hex_value(encoded[index]);
125
0
        const int low = hex_value(encoded[index + 1]);
126
0
        if (high < 0 || low < 0) {
127
0
            return Status::InvalidArgument("Invalid hexadecimal Iceberg binary default");
128
0
        }
129
0
        (*decoded)[index / 2] = static_cast<char>((high << 4) | low);
130
0
    }
131
0
    return Status::OK();
132
0
}
133
134
0
inline Status decode_json_binary(std::string_view encoded, std::string* decoded) {
135
0
    DORIS_CHECK(decoded != nullptr);
136
0
    const bool is_uuid = encoded.size() == 36 && encoded[8] == '-' && encoded[13] == '-' &&
137
0
                         encoded[18] == '-' && encoded[23] == '-';
138
0
    if (is_uuid) {
139
0
        std::string uuid_hex;
140
0
        uuid_hex.reserve(32);
141
0
        for (size_t index = 0; index < encoded.size(); ++index) {
142
0
            if (index != 8 && index != 13 && index != 18 && index != 23) {
143
0
                uuid_hex.push_back(encoded[index]);
144
0
            }
145
0
        }
146
0
        return decode_hex(uuid_hex, decoded);
147
0
    }
148
0
    return decode_hex(encoded, decoded);
149
0
}
150
151
0
inline std::string json_scalar_text(const rapidjson::Value& value) {
152
0
    if (value.IsString()) {
153
0
        return {value.GetString(), value.GetStringLength()};
154
0
    }
155
0
    rapidjson::StringBuffer buffer;
156
0
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
157
0
    value.Accept(writer);
158
0
    return {buffer.GetString(), buffer.GetSize()};
159
0
}
160
161
0
inline void normalize_timestamp_for_doris(doris::PrimitiveType primitive_type, std::string* value) {
162
0
    if (primitive_type != TYPE_DATETIME && primitive_type != TYPE_DATETIMEV2 &&
163
0
        primitive_type != TYPE_TIMESTAMPTZ) {
164
0
        return;
165
0
    }
166
0
    if (const size_t separator = value->find('T'); separator != std::string::npos) {
167
0
        (*value)[separator] = ' ';
168
0
    }
169
0
    if (primitive_type == TYPE_TIMESTAMPTZ) {
170
0
        return;
171
0
    }
172
0
    if (value->ends_with('Z')) {
173
0
        value->pop_back();
174
0
        return;
175
0
    }
176
0
    const size_t time_start = value->find(' ');
177
0
    if (time_start == std::string::npos) {
178
0
        return;
179
0
    }
180
0
    const size_t offset = value->find_first_of("+-", time_start + 1);
181
0
    if (offset != std::string::npos) {
182
0
        value->erase(offset);
183
0
    }
184
0
}
185
186
inline Status make_null_field(const schema::external::TField& field, const DataTypePtr& data_type,
187
0
                              Field* result) {
188
0
    DORIS_CHECK(data_type != nullptr);
189
0
    DORIS_CHECK(result != nullptr);
190
0
    if (field.__isset.is_optional && !field.is_optional) {
191
0
        return Status::InvalidArgument("Required Iceberg field '{}' has a null default",
192
0
                                       field.name);
193
0
    }
194
0
    if (!data_type->is_nullable()) {
195
0
        return Status::InternalError(
196
0
                "Optional Iceberg field '{}' has a null default, but its Doris type '{}' is not "
197
0
                "nullable",
198
0
                field.name, data_type->get_name());
199
0
    }
200
0
    *result = Field();
201
0
    return Status::OK();
202
0
}
203
204
inline Status build_initial_default_field(const schema::external::TField& field,
205
                                          const DataTypePtr& data_type,
206
                                          std::deque<std::string>* binary_storage, Field* result);
207
208
inline Status build_json_default_field(const schema::external::TField& field,
209
                                       const DataTypePtr& data_type,
210
                                       const rapidjson::Value& json_value,
211
                                       std::deque<std::string>* binary_storage, Field* result);
212
213
inline Status build_json_struct_default(const schema::external::TField& field,
214
                                        const DataTypePtr& value_type,
215
                                        const rapidjson::Value& json_value,
216
0
                                        std::deque<std::string>* binary_storage, Field* result) {
217
0
    if (!json_value.IsObject() || !field.__isset.nestedField ||
218
0
        !field.nestedField.__isset.struct_field || !field.nestedField.struct_field.__isset.fields) {
219
0
        return Status::InvalidArgument("Invalid Iceberg struct default for field '{}'", field.name);
220
0
    }
221
0
222
0
    const auto& struct_type = assert_cast<const DataTypeStruct&>(*value_type);
223
0
    Struct struct_value;
224
0
    struct_value.reserve(struct_type.get_elements().size());
225
0
    for (size_t index = 0; index < struct_type.get_elements().size(); ++index) {
226
0
        const auto& child_name = struct_type.get_element_name(index);
227
0
        const auto* child = find_struct_child(field.nestedField.struct_field, child_name);
228
0
        if (child == nullptr || !child->__isset.id) {
229
0
            return Status::InvalidArgument(
230
0
                    "Iceberg struct default for field '{}' is missing metadata for projected "
231
0
                    "child '{}'",
232
0
                    field.name, child_name);
233
0
        }
234
0
235
0
        const std::string child_id = std::to_string(child->id);
236
0
        const auto member = json_value.FindMember(child_id.c_str());
237
0
        Field child_value;
238
0
        if (member == json_value.MemberEnd()) {
239
0
            RETURN_IF_ERROR(build_initial_default_field(*child, struct_type.get_element(index),
240
0
                                                        binary_storage, &child_value));
241
0
        } else {
242
0
            RETURN_IF_ERROR(build_json_default_field(*child, struct_type.get_element(index),
243
0
                                                     member->value, binary_storage, &child_value));
244
0
        }
245
0
        struct_value.push_back(std::move(child_value));
246
0
    }
247
0
    *result = Field::create_field<TYPE_STRUCT>(std::move(struct_value));
248
0
    return Status::OK();
249
0
}
250
251
// The recursive item TField describes the element schema and its field-level default metadata. It
252
// cannot represent a particular list literal's length or per-position values, so the parent
253
// initial-default keeps those values in Iceberg's single-value JSON array.
254
inline Status build_json_array_default(const schema::external::TField& field,
255
                                       const DataTypePtr& value_type,
256
                                       const rapidjson::Value& json_value,
257
0
                                       std::deque<std::string>* binary_storage, Field* result) {
258
0
    if (!json_value.IsArray() || !field.__isset.nestedField ||
259
0
        !field.nestedField.__isset.array_field ||
260
0
        !field.nestedField.array_field.__isset.item_field) {
261
0
        return Status::InvalidArgument("Invalid Iceberg list default for field '{}'", field.name);
262
0
    }
263
0
    const auto* element = get_field_ptr(field.nestedField.array_field.item_field);
264
0
    if (element == nullptr) {
265
0
        return Status::InvalidArgument(
266
0
                "Iceberg list default for field '{}' has incomplete element metadata", field.name);
267
0
    }
268
0
269
0
    const auto& array_type = assert_cast<const DataTypeArray&>(*value_type);
270
0
    Array array_value;
271
0
    array_value.reserve(json_value.Size());
272
0
    for (const auto& json_element : json_value.GetArray()) {
273
0
        Field element_value;
274
0
        RETURN_IF_ERROR(build_json_default_field(*element, array_type.get_nested_type(),
275
0
                                                 json_element, binary_storage, &element_value));
276
0
        array_value.push_back(std::move(element_value));
277
0
    }
278
0
    *result = Field::create_field<TYPE_ARRAY>(std::move(array_value));
279
0
    return Status::OK();
280
0
}
281
282
// The recursive key/value TFields describe entry schemas and field-level default metadata. They
283
// cannot represent the number, order, or concrete values of map entries, so the parent
284
// initial-default keeps the entries in Iceberg's single-value JSON key/value arrays.
285
inline Status build_json_map_default(const schema::external::TField& field,
286
                                     const DataTypePtr& value_type,
287
                                     const rapidjson::Value& json_value,
288
0
                                     std::deque<std::string>* binary_storage, Field* result) {
289
0
    if (!json_value.IsObject() || !json_value.HasMember("keys") || !json_value["keys"].IsArray() ||
290
0
        !json_value.HasMember("values") || !json_value["values"].IsArray() ||
291
0
        !field.__isset.nestedField || !field.nestedField.__isset.map_field ||
292
0
        !field.nestedField.map_field.__isset.key_field ||
293
0
        !field.nestedField.map_field.__isset.value_field) {
294
0
        return Status::InvalidArgument("Invalid Iceberg map default for field '{}'", field.name);
295
0
    }
296
0
    const auto& keys = json_value["keys"];
297
0
    const auto& values = json_value["values"];
298
0
    if (keys.Size() != values.Size()) {
299
0
        return Status::InvalidArgument(
300
0
                "Iceberg map default for field '{}' has {} keys but {} values", field.name,
301
0
                keys.Size(), values.Size());
302
0
    }
303
0
304
0
    const auto* key = get_field_ptr(field.nestedField.map_field.key_field);
305
0
    const auto* value = get_field_ptr(field.nestedField.map_field.value_field);
306
0
    if (key == nullptr || value == nullptr) {
307
0
        return Status::InvalidArgument(
308
0
                "Iceberg map default for field '{}' has incomplete key/value metadata", field.name);
309
0
    }
310
0
311
0
    const auto& map_type = assert_cast<const DataTypeMap&>(*value_type);
312
0
    Array key_fields;
313
0
    Array value_fields;
314
0
    key_fields.reserve(keys.Size());
315
0
    value_fields.reserve(values.Size());
316
0
    for (rapidjson::SizeType index = 0; index < keys.Size(); ++index) {
317
0
        Field key_value;
318
0
        Field mapped_value;
319
0
        RETURN_IF_ERROR(build_json_default_field(*key, map_type.get_key_type(), keys[index],
320
0
                                                 binary_storage, &key_value));
321
0
        RETURN_IF_ERROR(build_json_default_field(*value, map_type.get_value_type(), values[index],
322
0
                                                 binary_storage, &mapped_value));
323
0
        key_fields.push_back(std::move(key_value));
324
0
        value_fields.push_back(std::move(mapped_value));
325
0
    }
326
0
    Map map_value;
327
0
    map_value.push_back(Field::create_field<TYPE_ARRAY>(std::move(key_fields)));
328
0
    map_value.push_back(Field::create_field<TYPE_ARRAY>(std::move(value_fields)));
329
0
    *result = Field::create_field<TYPE_MAP>(std::move(map_value));
330
0
    return Status::OK();
331
0
}
332
333
inline Status build_json_scalar_default(const schema::external::TField& field,
334
                                        const DataTypePtr& value_type,
335
                                        const rapidjson::Value& json_value,
336
0
                                        std::deque<std::string>* binary_storage, Field* result) {
337
0
    const auto primitive_type = value_type->get_primitive_type();
338
0
    std::string serialized_value = json_scalar_text(json_value);
339
0
    const bool binary_like = (field.__isset.initial_default_value_is_base64 &&
340
0
                              field.initial_default_value_is_base64) ||
341
0
                             primitive_type == TYPE_VARBINARY;
342
0
    if (binary_like) {
343
0
        if (!json_value.IsString()) {
344
0
            return Status::InvalidArgument(
345
0
                    "Iceberg binary default for field '{}' is not a JSON string", field.name);
346
0
        }
347
0
        binary_storage->emplace_back();
348
0
        RETURN_IF_ERROR(decode_json_binary(serialized_value, &binary_storage->back()));
349
0
        if (primitive_type == TYPE_VARBINARY) {
350
0
            *result = Field::create_field<TYPE_VARBINARY>(StringView(binary_storage->back()));
351
0
        } else if (is_string_type(primitive_type)) {
352
0
            *result = Field::create_field<TYPE_STRING>(binary_storage->back());
353
0
        } else {
354
0
            return Status::InvalidArgument(
355
0
                    "Iceberg binary default for field '{}' has incompatible Doris type '{}'",
356
0
                    field.name, value_type->get_name());
357
0
        }
358
0
        return Status::OK();
359
0
    }
360
0
361
0
    if (is_string_type(primitive_type)) {
362
0
        if (!json_value.IsString()) {
363
0
            return Status::InvalidArgument("Iceberg string default for field '{}' is not a string",
364
0
                                           field.name);
365
0
        }
366
0
        *result = Field::create_field<TYPE_STRING>(std::move(serialized_value));
367
0
        return Status::OK();
368
0
    }
369
0
    normalize_timestamp_for_doris(primitive_type, &serialized_value);
370
0
    if (parse_non_finite_default(primitive_type, serialized_value, result)) {
371
0
        return Status::OK();
372
0
    }
373
0
    RETURN_IF_ERROR(value_type->get_serde()->from_fe_string(serialized_value, *result));
374
0
    return Status::OK();
375
0
}
376
377
inline Status build_json_default_field(const schema::external::TField& field,
378
                                       const DataTypePtr& data_type,
379
                                       const rapidjson::Value& json_value,
380
0
                                       std::deque<std::string>* binary_storage, Field* result) {
381
0
    DORIS_CHECK(data_type != nullptr);
382
0
    DORIS_CHECK(binary_storage != nullptr);
383
0
    DORIS_CHECK(result != nullptr);
384
0
    if (json_value.IsNull()) {
385
0
        return make_null_field(field, data_type, result);
386
0
    }
387
0
388
0
    const auto value_type = remove_nullable(data_type);
389
0
    switch (value_type->get_primitive_type()) {
390
0
    case TYPE_STRUCT:
391
0
        return build_json_struct_default(field, value_type, json_value, binary_storage, result);
392
0
    case TYPE_ARRAY:
393
0
        return build_json_array_default(field, value_type, json_value, binary_storage, result);
394
0
    case TYPE_MAP:
395
0
        return build_json_map_default(field, value_type, json_value, binary_storage, result);
396
0
    default:
397
0
        return build_json_scalar_default(field, value_type, json_value, binary_storage, result);
398
0
    }
399
0
}
400
401
inline Status build_initial_default_field(const schema::external::TField& field,
402
                                          const DataTypePtr& data_type,
403
0
                                          std::deque<std::string>* binary_storage, Field* result) {
404
0
    DORIS_CHECK(data_type != nullptr);
405
0
    DORIS_CHECK(binary_storage != nullptr);
406
0
    DORIS_CHECK(result != nullptr);
407
0
    if (!field.__isset.initial_default_value) {
408
0
        if (field.__isset.is_optional && !field.is_optional) {
409
0
            return Status::InvalidArgument(
410
0
                    "Required Iceberg field '{}' is missing from the data file and has no initial "
411
0
                    "default",
412
0
                    field.name);
413
0
        }
414
0
        return make_null_field(field, data_type, result);
415
0
    }
416
0
417
0
    const auto value_type = remove_nullable(data_type);
418
0
    const auto primitive_type = value_type->get_primitive_type();
419
0
    if (is_complex_type(primitive_type)) {
420
0
        rapidjson::Document document;
421
0
        document.Parse<rapidjson::kParseFullPrecisionFlag>(field.initial_default_value.data(),
422
0
                                                           field.initial_default_value.size());
423
0
        if (document.HasParseError()) {
424
0
            return Status::InvalidArgument("Invalid Iceberg JSON initial default for field '{}'",
425
0
                                           field.name);
426
0
        }
427
0
        return build_json_default_field(field, data_type, document, binary_storage, result);
428
0
    }
429
0
430
0
    const bool default_is_base64 = (field.__isset.initial_default_value_is_base64 &&
431
0
                                    field.initial_default_value_is_base64) ||
432
0
                                   primitive_type == TYPE_VARBINARY;
433
0
    if (default_is_base64) {
434
0
        binary_storage->emplace_back();
435
0
        if (!base64_decode(field.initial_default_value, &binary_storage->back())) {
436
0
            return Status::InvalidArgument("Invalid Base64 Iceberg initial default for field '{}'",
437
0
                                           field.name);
438
0
        }
439
0
        if (primitive_type == TYPE_VARBINARY) {
440
0
            *result = Field::create_field<TYPE_VARBINARY>(StringView(binary_storage->back()));
441
0
        } else if (is_string_type(primitive_type)) {
442
0
            *result = Field::create_field<TYPE_STRING>(binary_storage->back());
443
0
        } else {
444
0
            return Status::InvalidArgument(
445
0
                    "Iceberg field '{}' marks its initial default as Base64, but Doris type '{}' "
446
0
                    "cannot contain binary data",
447
0
                    field.name, value_type->get_name());
448
0
        }
449
0
        return Status::OK();
450
0
    }
451
0
452
0
    if (parse_non_finite_default(primitive_type, field.initial_default_value, result)) {
453
0
        return Status::OK();
454
0
    }
455
0
    RETURN_IF_ERROR(value_type->get_serde()->from_fe_string(field.initial_default_value, *result));
456
0
    return Status::OK();
457
0
}
458
459
} // namespace detail
460
461
// Builds an owned one-row column for an Iceberg field that is absent from an old data file.
462
// Complex values follow Iceberg's JSON single-value encoding. Struct members omitted from the
463
// encoded value are recursively populated from the child field's own initial default.
464
inline Status create_initial_default_column(const schema::external::TField& field,
465
0
                                            const DataTypePtr& data_type, ColumnPtr* result) {
466
0
    DORIS_CHECK(data_type != nullptr);
467
0
    DORIS_CHECK(result != nullptr);
468
0
469
0
    auto column = data_type->create_column();
470
0
    std::deque<std::string> binary_storage;
471
0
    Field value;
472
0
    RETURN_IF_ERROR(detail::build_initial_default_field(field, data_type, &binary_storage, &value));
473
0
    // The column copies every String/StringView leaf before binary_storage is destroyed.
474
0
    column->insert(value);
475
0
476
0
    *result = std::move(column);
477
0
    return Status::OK();
478
0
}
479
480
0
inline ColumnPtr repeat_initial_default_column(const ColumnPtr& default_column, size_t rows) {
481
0
    DORIS_CHECK(default_column);
482
0
    DORIS_CHECK_EQ(default_column->size(), 1);
483
0
484
0
    auto repeated_column = default_column->clone_empty();
485
0
    repeated_column->insert_many_from(*default_column, 0, rows);
486
0
    return repeated_column;
487
0
}
488
489
inline Status append_initial_default(
490
        const schema::external::TField& field, const DataTypePtr& data_type, size_t rows,
491
        std::unordered_map<int32_t, std::pair<DataTypePtr, ColumnPtr>>* prepared_values,
492
0
        ColumnPtr* destination) {
493
0
    DORIS_CHECK(data_type != nullptr);
494
0
    DORIS_CHECK(prepared_values != nullptr);
495
0
    DORIS_CHECK(destination != nullptr);
496
0
    DORIS_CHECK(field.__isset.id);
497
0
498
0
    auto prepared_value = prepared_values->find(field.id);
499
0
    if (prepared_value == prepared_values->end()) {
500
0
        ColumnPtr default_column;
501
0
        RETURN_IF_ERROR(create_initial_default_column(field, data_type, &default_column));
502
0
        prepared_value =
503
0
                prepared_values
504
0
                        ->emplace(field.id, std::make_pair(data_type, std::move(default_column)))
505
0
                        .first;
506
0
    } else {
507
0
        // One Iceberg field ID resolves to one query type. Hold the first DataTypePtr so equivalent
508
0
        // complex types reconstructed for later Blocks reuse the same prepared value.
509
0
        DORIS_CHECK(prepared_value->second.first->equals(*data_type));
510
0
    }
511
0
512
0
    auto mutable_destination = IColumn::mutate(std::move(*destination));
513
0
    mutable_destination->insert_many_from(*prepared_value->second.second, 0, rows);
514
0
    *destination = std::move(mutable_destination);
515
0
    return Status::OK();
516
0
}
517
518
} // namespace doris::iceberg