Coverage Report

Created: 2026-06-02 10:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/tablet_info.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 "storage/tablet_info.h"
19
20
#include <butil/logging.h>
21
#include <gen_cpp/Descriptors_types.h>
22
#include <gen_cpp/Exprs_types.h>
23
#include <gen_cpp/Partitions_types.h>
24
#include <gen_cpp/Types_types.h>
25
#include <gen_cpp/descriptors.pb.h>
26
#include <gen_cpp/olap_file.pb.h>
27
#include <glog/logging.h>
28
29
#include <algorithm>
30
#include <cstddef>
31
#include <cstdint>
32
#include <memory>
33
#include <ostream>
34
#include <string>
35
#include <tuple>
36
37
#include "common/exception.h"
38
#include "common/logging.h"
39
#include "common/status.h"
40
#include "core/column/column.h"
41
#include "core/data_type/data_type.h"
42
#include "core/data_type/data_type_factory.hpp"
43
#include "core/data_type/define_primitive_type.h"
44
#include "core/data_type/primitive_type.h"
45
#include "core/value/large_int_value.h"
46
#include "runtime/descriptors.h"
47
#include "runtime/memory/mem_tracker.h"
48
#include "storage/tablet/tablet_schema.h"
49
#include "util/raw_value.h"
50
#include "util/string_parser.hpp"
51
#include "util/string_util.h"
52
// NOLINTNEXTLINE(unused-includes)
53
#include "core/value/vdatetime_value.h"
54
#include "exprs/function/cast/cast_to_date_or_datetime_impl.hpp"
55
#include "exprs/function/cast/cast_to_datetimev2_impl.hpp"
56
#include "exprs/function/cast/cast_to_datev2_impl.hpp"
57
#include "exprs/function/cast/cast_to_timestamptz.h"
58
#include "exprs/vexpr_context.h" // IWYU pragma: keep
59
#include "exprs/vliteral.h"
60
61
namespace doris {
62
63
32
void OlapTableIndexSchema::to_protobuf(POlapTableIndexSchema* pindex) const {
64
32
    pindex->set_id(index_id);
65
32
    pindex->set_schema_hash(schema_hash);
66
32
    if (row_binlog_id > 0) {
67
0
        pindex->set_row_binlog_id(row_binlog_id);
68
0
    }
69
32
    for (auto* slot : slots) {
70
0
        pindex->add_columns(slot->col_name());
71
0
    }
72
32
    for (auto* column : columns) {
73
0
        column->to_schema_pb(pindex->add_columns_desc());
74
0
    }
75
32
    for (auto* index : indexes) {
76
0
        index->to_schema_pb(pindex->add_indexes_desc());
77
0
    }
78
32
}
79
80
bool VOlapTablePartKeyComparator::operator()(const BlockRowWithIndicator& lhs,
81
109
                                             const BlockRowWithIndicator& rhs) const {
82
109
    Block* l_block = std::get<0>(lhs);
83
109
    Block* r_block = std::get<0>(rhs);
84
109
    int32_t l_row = std::get<1>(lhs);
85
109
    int32_t r_row = std::get<1>(rhs);
86
109
    bool l_use_new = std::get<2>(lhs);
87
109
    bool r_use_new = std::get<2>(rhs);
88
89
109
    VLOG_TRACE << '\n' << l_block->dump_data() << '\n' << r_block->dump_data();
90
91
109
    if (l_row == -1) {
92
0
        return false;
93
109
    } else if (r_row == -1) {
94
0
        return true;
95
0
    }
96
97
109
    if (_param_locs.empty()) { // no transform, use origin column
98
84
        for (auto slot_loc : _slot_locs) {
99
84
            auto res = l_block->get_by_position(slot_loc).column->compare_at(
100
84
                    l_row, r_row, *r_block->get_by_position(slot_loc).column, -1);
101
84
            if (res != 0) {
102
80
                return res < 0;
103
80
            }
104
84
        }
105
84
    } else { // use transformed column to compare
106
25
        DCHECK(_slot_locs.size() == _param_locs.size())
107
0
                << _slot_locs.size() << ' ' << _param_locs.size();
108
109
25
        const std::vector<uint16_t>* l_index = l_use_new ? &_param_locs : &_slot_locs;
110
25
        const std::vector<uint16_t>* r_index = r_use_new ? &_param_locs : &_slot_locs;
111
112
25
        for (int i = 0; i < _slot_locs.size(); i++) {
113
25
            ColumnPtr l_col = l_block->get_by_position((*l_index)[i]).column;
114
25
            ColumnPtr r_col = r_block->get_by_position((*r_index)[i]).column;
115
116
25
            auto res = l_col->compare_at(l_row, r_row, *r_col, -1);
117
25
            if (res != 0) {
118
25
                return res < 0;
119
25
            }
120
25
        }
121
25
    }
122
123
    // equal, return false
124
4
    return false;
125
109
}
126
127
14
Status OlapTableSchemaParam::init(const POlapTableSchemaParam& pschema) {
128
14
    _db_id = pschema.db_id();
129
14
    _table_id = pschema.table_id();
130
14
    _version = pschema.version();
131
14
    if (pschema.has_unique_key_update_mode()) {
132
14
        _unique_key_update_mode = pschema.unique_key_update_mode();
133
14
        if (pschema.has_sequence_map_col_unique_id()) {
134
14
            _sequence_map_col_uid = pschema.sequence_map_col_unique_id();
135
14
        }
136
14
    } else {
137
        // for backward compatibility
138
0
        if (pschema.has_partial_update() && pschema.partial_update()) {
139
0
            _unique_key_update_mode = UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS;
140
0
        } else {
141
0
            _unique_key_update_mode = UniqueKeyUpdateModePB::UPSERT;
142
0
        }
143
0
    }
144
14
    _is_strict_mode = pschema.is_strict_mode();
145
14
    _is_insert = pschema.is_insert();
146
14
    if (_unique_key_update_mode == UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS) {
147
0
        _auto_increment_column = pschema.auto_increment_column();
148
0
        if (!_auto_increment_column.empty() && pschema.auto_increment_column_unique_id() == -1) {
149
0
            return Status::InternalError(
150
0
                    "Auto increment column id is not set in FE. Maybe FE is an older version "
151
0
                    "different from BE.");
152
0
        }
153
0
        _auto_increment_column_unique_id = pschema.auto_increment_column_unique_id();
154
0
    }
155
14
    if (_unique_key_update_mode != UniqueKeyUpdateModePB::UPSERT) {
156
0
        if (pschema.has_partial_update_new_key_policy()) {
157
0
            _partial_update_new_row_policy = pschema.partial_update_new_key_policy();
158
0
        }
159
0
    }
160
14
    _timestamp_ms = pschema.timestamp_ms();
161
14
    if (pschema.has_nano_seconds()) {
162
14
        _nano_seconds = pschema.nano_seconds();
163
14
    }
164
14
    _timezone = pschema.timezone();
165
166
14
    for (const auto& col : pschema.partial_update_input_columns()) {
167
0
        _partial_update_input_columns.insert(col);
168
0
    }
169
14
    std::unordered_map<std::string, SlotDescriptor*> slots_map;
170
171
14
    _tuple_desc = _obj_pool.add(new TupleDescriptor(pschema.tuple_desc()));
172
173
84
    for (const auto& p_slot_desc : pschema.slot_descs()) {
174
84
        auto* slot_desc = _obj_pool.add(new SlotDescriptor(p_slot_desc));
175
84
        _tuple_desc->add_slot(slot_desc);
176
84
        std::string data_type;
177
84
        EnumToString(TPrimitiveType, to_thrift(slot_desc->col_type()), data_type);
178
84
        std::string is_null_str = slot_desc->is_nullable() ? "true" : "false";
179
84
        std::string data_type_str =
180
84
                std::to_string(int64_t(TabletColumn::get_field_type_by_string(data_type)));
181
84
        slots_map.emplace(to_lower(slot_desc->col_name()) + "+" + data_type_str + is_null_str,
182
84
                          slot_desc);
183
84
    }
184
185
28
    for (const auto& p_index : pschema.indexes()) {
186
28
        auto* index = _obj_pool.add(new OlapTableIndexSchema());
187
28
        index->index_id = p_index.id();
188
28
        index->schema_hash = p_index.schema_hash();
189
28
        if (p_index.has_row_binlog_id()) {
190
0
            index->row_binlog_id = p_index.row_binlog_id();
191
0
        }
192
28
        for (const auto& pcolumn_desc : p_index.columns_desc()) {
193
0
            if (_unique_key_update_mode != UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS ||
194
0
                _partial_update_input_columns.contains(pcolumn_desc.name())) {
195
0
                std::string is_null_str = pcolumn_desc.is_nullable() ? "true" : "false";
196
0
                std::string data_type_str = std::to_string(
197
0
                        int64_t(TabletColumn::get_field_type_by_string(pcolumn_desc.type())));
198
0
                auto it = slots_map.find(to_lower(pcolumn_desc.name()) + "+" + data_type_str +
199
0
                                         is_null_str);
200
0
                if (it == std::end(slots_map)) {
201
0
                    std::string keys {};
202
0
                    for (const auto& [key, _] : slots_map) {
203
0
                        keys += fmt::format("{},", key);
204
0
                    }
205
0
                    LOG_EVERY_SECOND(WARNING) << fmt::format(
206
0
                            "[OlapTableSchemaParam::init(const POlapTableSchemaParam& pschema)]: "
207
0
                            "unknown index column, column={}, type={}, data_type_str={}, "
208
0
                            "is_null_str={}, slots_map.keys()=[{}], {}\npschema={}",
209
0
                            pcolumn_desc.name(), pcolumn_desc.type(), data_type_str, is_null_str,
210
0
                            keys, debug_string(), pschema.ShortDebugString());
211
212
0
                    return Status::InternalError("unknown index column, column={}, type={}",
213
0
                                                 pcolumn_desc.name(), pcolumn_desc.type());
214
0
                }
215
0
                index->slots.emplace_back(it->second);
216
0
            }
217
0
            TabletColumn* tc = _obj_pool.add(new TabletColumn());
218
0
            tc->init_from_pb(pcolumn_desc);
219
0
            index->columns.emplace_back(tc);
220
0
        }
221
28
        for (const auto& pindex_desc : p_index.indexes_desc()) {
222
0
            TabletIndex* ti = _obj_pool.add(new TabletIndex());
223
0
            ti->init_from_pb(pindex_desc);
224
0
            index->indexes.emplace_back(ti);
225
0
        }
226
28
        _indexes.emplace_back(index);
227
28
    }
228
229
14
    if (pschema.has_row_binlog_index_schema()) {
230
0
        const auto& p_index = pschema.row_binlog_index_schema();
231
0
        auto* index = _obj_pool.add(new OlapTableIndexSchema());
232
0
        index->index_id = p_index.id();
233
0
        index->schema_hash = p_index.schema_hash();
234
0
        if (p_index.has_row_binlog_id()) {
235
0
            index->row_binlog_id = p_index.row_binlog_id();
236
0
        }
237
0
        for (const auto& pcolumn_desc : p_index.columns_desc()) {
238
0
            TabletColumn* tc = _obj_pool.add(new TabletColumn());
239
0
            tc->init_from_pb(pcolumn_desc);
240
0
            index->columns.emplace_back(tc);
241
0
        }
242
0
        for (const auto& pindex_desc : p_index.indexes_desc()) {
243
0
            TabletIndex* ti = _obj_pool.add(new TabletIndex());
244
0
            ti->init_from_pb(pindex_desc);
245
0
            index->indexes.emplace_back(ti);
246
0
        }
247
0
        _row_binlog_index_schema = index;
248
0
    }
249
250
14
    std::sort(_indexes.begin(), _indexes.end(),
251
28
              [](const OlapTableIndexSchema* lhs, const OlapTableIndexSchema* rhs) {
252
28
                  return lhs->index_id < rhs->index_id;
253
28
              });
254
14
    return Status::OK();
255
14
}
256
257
29
Status OlapTableSchemaParam::init_unique_key_update_mode(const TOlapTableSchemaParam& tschema) {
258
29
    if (tschema.__isset.unique_key_update_mode) {
259
0
        switch (tschema.unique_key_update_mode) {
260
0
        case doris::TUniqueKeyUpdateMode::UPSERT: {
261
0
            _unique_key_update_mode = UniqueKeyUpdateModePB::UPSERT;
262
0
            break;
263
0
        }
264
0
        case doris::TUniqueKeyUpdateMode::UPDATE_FIXED_COLUMNS: {
265
0
            _unique_key_update_mode = UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS;
266
0
            break;
267
0
        }
268
0
        case doris::TUniqueKeyUpdateMode::UPDATE_FLEXIBLE_COLUMNS: {
269
0
            _unique_key_update_mode = UniqueKeyUpdateModePB::UPDATE_FLEXIBLE_COLUMNS;
270
0
            break;
271
0
        }
272
0
        default: {
273
0
            return Status::InternalError(
274
0
                    "Unknown unique_key_update_mode: {}, should be one of "
275
0
                    "UPSERT/UPDATE_FIXED_COLUMNS/UPDATE_FLEXIBLE_COLUMNS",
276
0
                    tschema.unique_key_update_mode);
277
0
        }
278
0
        }
279
0
        if (tschema.__isset.sequence_map_col_unique_id) {
280
0
            _sequence_map_col_uid = tschema.sequence_map_col_unique_id;
281
0
        }
282
29
    } else {
283
        // for backward compatibility
284
29
        if (tschema.__isset.is_partial_update && tschema.is_partial_update) {
285
0
            _unique_key_update_mode = UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS;
286
29
        } else {
287
29
            _unique_key_update_mode = UniqueKeyUpdateModePB::UPSERT;
288
29
        }
289
29
    }
290
29
    return Status::OK();
291
29
}
292
293
29
Status OlapTableSchemaParam::init(const TOlapTableSchemaParam& tschema) {
294
29
    _db_id = tschema.db_id;
295
29
    _table_id = tschema.table_id;
296
29
    _version = tschema.version;
297
29
    RETURN_IF_ERROR(init_unique_key_update_mode(tschema));
298
29
    if (tschema.__isset.is_strict_mode) {
299
29
        _is_strict_mode = tschema.is_strict_mode;
300
29
    }
301
29
    if (tschema.__isset.is_insert) {
302
29
        _is_insert = tschema.is_insert;
303
29
    }
304
29
    if (_unique_key_update_mode == UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS) {
305
0
        _auto_increment_column = tschema.auto_increment_column;
306
0
        if (!_auto_increment_column.empty() && tschema.auto_increment_column_unique_id == -1) {
307
0
            return Status::InternalError(
308
0
                    "Auto increment column id is not set in FE. Maybe FE is an older version "
309
0
                    "different from BE.");
310
0
        }
311
0
        _auto_increment_column_unique_id = tschema.auto_increment_column_unique_id;
312
0
    }
313
314
29
    if (_unique_key_update_mode != UniqueKeyUpdateModePB::UPSERT) {
315
0
        if (tschema.__isset.partial_update_new_key_policy) {
316
0
            switch (tschema.partial_update_new_key_policy) {
317
0
            case doris::TPartialUpdateNewRowPolicy::APPEND: {
318
0
                _partial_update_new_row_policy = PartialUpdateNewRowPolicyPB::APPEND;
319
0
                break;
320
0
            }
321
0
            case doris::TPartialUpdateNewRowPolicy::ERROR: {
322
0
                _partial_update_new_row_policy = PartialUpdateNewRowPolicyPB::ERROR;
323
0
                break;
324
0
            }
325
0
            default: {
326
0
                return Status::InvalidArgument(
327
0
                        "Unknown partial_update_new_key_behavior: {}, should be one of "
328
0
                        "'APPEND' or 'ERROR'",
329
0
                        tschema.partial_update_new_key_policy);
330
0
            }
331
0
            }
332
0
        }
333
0
    }
334
335
29
    for (const auto& tcolumn : tschema.partial_update_input_columns) {
336
0
        _partial_update_input_columns.insert(tcolumn);
337
0
    }
338
29
    std::unordered_map<std::string, SlotDescriptor*> slots_map;
339
29
    _tuple_desc = _obj_pool.add(new TupleDescriptor(tschema.tuple_desc));
340
116
    for (const auto& t_slot_desc : tschema.slot_descs) {
341
116
        auto* slot_desc = _obj_pool.add(new SlotDescriptor(t_slot_desc));
342
116
        _tuple_desc->add_slot(slot_desc);
343
116
        std::string is_null_str = slot_desc->is_nullable() ? "true" : "false";
344
116
        std::string data_type_str = std::to_string(int64_t(slot_desc->col_type()));
345
116
        slots_map.emplace(to_lower(slot_desc->col_name()) + "+" + data_type_str + is_null_str,
346
116
                          slot_desc);
347
116
    }
348
349
45
    for (const auto& t_index : tschema.indexes) {
350
45
        std::unordered_map<std::string, int32_t> index_slots_map;
351
45
        auto* index = _obj_pool.add(new OlapTableIndexSchema());
352
45
        index->index_id = t_index.id;
353
45
        index->schema_hash = t_index.schema_hash;
354
45
        if (t_index.__isset.row_binlog_id) {
355
4
            index->row_binlog_id = t_index.row_binlog_id;
356
4
        }
357
45
        for (const auto& tcolumn_desc : t_index.columns_desc) {
358
11
            if (_unique_key_update_mode != UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS ||
359
11
                _partial_update_input_columns.contains(tcolumn_desc.column_name)) {
360
11
                std::string is_null_str = tcolumn_desc.is_allow_null ? "true" : "false";
361
11
                std::string data_type_str =
362
11
                        std::to_string(int64_t(thrift_to_type(tcolumn_desc.column_type.type)));
363
11
                auto it = slots_map.find(to_lower(tcolumn_desc.column_name) + "+" + data_type_str +
364
11
                                         is_null_str);
365
11
                if (it == slots_map.end()) {
366
0
                    std::stringstream ss;
367
0
                    ss << tschema;
368
0
                    std::string keys {};
369
0
                    for (const auto& [key, _] : slots_map) {
370
0
                        keys += fmt::format("{},", key);
371
0
                    }
372
0
                    LOG_EVERY_SECOND(WARNING) << fmt::format(
373
0
                            "[OlapTableSchemaParam::init(const TOlapTableSchemaParam& tschema)]: "
374
0
                            "unknown index column, column={}, type={}, data_type_str={}, "
375
0
                            "is_null_str={}, slots_map.keys()=[{}], {}\ntschema={}",
376
0
                            tcolumn_desc.column_name, tcolumn_desc.column_type.type, data_type_str,
377
0
                            is_null_str, keys, debug_string(), ss.str());
378
0
                    return Status::InternalError("unknown index column, column={}, type={}",
379
0
                                                 tcolumn_desc.column_name,
380
0
                                                 tcolumn_desc.column_type.type);
381
0
                }
382
11
                index->slots.emplace_back(it->second);
383
11
            }
384
11
            index_slots_map.emplace(to_lower(tcolumn_desc.column_name), tcolumn_desc.col_unique_id);
385
11
            TabletColumn* tc = _obj_pool.add(new TabletColumn());
386
11
            tc->init_from_thrift(tcolumn_desc);
387
11
            index->columns.emplace_back(tc);
388
11
        }
389
45
        if (t_index.__isset.indexes_desc) {
390
0
            for (const auto& tindex_desc : t_index.indexes_desc) {
391
0
                std::vector<int32_t> column_unique_ids(tindex_desc.columns.size());
392
0
                for (size_t i = 0; i < tindex_desc.columns.size(); i++) {
393
0
                    auto it = index_slots_map.find(to_lower(tindex_desc.columns[i]));
394
0
                    if (it != index_slots_map.end()) {
395
0
                        column_unique_ids[i] = it->second;
396
0
                    }
397
0
                }
398
0
                TabletIndex* ti = _obj_pool.add(new TabletIndex());
399
0
                ti->init_from_thrift(tindex_desc, column_unique_ids);
400
0
                index->indexes.emplace_back(ti);
401
0
            }
402
0
        }
403
45
        if (t_index.__isset.where_clause) {
404
0
            RETURN_IF_ERROR(VExpr::create_expr_tree(t_index.where_clause, index->where_clause));
405
0
        }
406
45
        _indexes.emplace_back(index);
407
45
    }
408
409
29
    if (tschema.__isset.row_binlog_index_schema) {
410
4
        const auto& t_index = tschema.row_binlog_index_schema;
411
4
        auto* index = _obj_pool.add(new OlapTableIndexSchema());
412
4
        index->index_id = t_index.id;
413
4
        index->schema_hash = t_index.schema_hash;
414
4
        if (t_index.__isset.row_binlog_id) {
415
0
            index->row_binlog_id = t_index.row_binlog_id;
416
0
        }
417
14
        for (const auto& tcolumn_desc : t_index.columns_desc) {
418
14
            TabletColumn* tc = _obj_pool.add(new TabletColumn());
419
14
            tc->init_from_thrift(tcolumn_desc);
420
14
            index->columns.emplace_back(tc);
421
14
        }
422
4
        _row_binlog_index_schema = index;
423
4
    }
424
425
29
    std::sort(_indexes.begin(), _indexes.end(),
426
32
              [](const OlapTableIndexSchema* lhs, const OlapTableIndexSchema* rhs) {
427
32
                  return lhs->index_id < rhs->index_id;
428
32
              });
429
29
    return Status::OK();
430
29
}
431
432
16
void OlapTableSchemaParam::to_protobuf(POlapTableSchemaParam* pschema) const {
433
16
    pschema->set_db_id(_db_id);
434
16
    pschema->set_table_id(_table_id);
435
16
    pschema->set_version(_version);
436
16
    pschema->set_unique_key_update_mode(_unique_key_update_mode);
437
16
    if (_unique_key_update_mode == UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS) {
438
        // for backward compatibility
439
0
        pschema->set_partial_update(true);
440
0
    }
441
16
    pschema->set_partial_update_new_key_policy(_partial_update_new_row_policy);
442
16
    pschema->set_is_strict_mode(_is_strict_mode);
443
16
    pschema->set_is_insert(_is_insert);
444
16
    pschema->set_auto_increment_column(_auto_increment_column);
445
16
    pschema->set_auto_increment_column_unique_id(_auto_increment_column_unique_id);
446
16
    pschema->set_timestamp_ms(_timestamp_ms);
447
16
    pschema->set_timezone(_timezone);
448
16
    pschema->set_nano_seconds(_nano_seconds);
449
16
    pschema->set_sequence_map_col_unique_id(_sequence_map_col_uid);
450
16
    for (auto col : _partial_update_input_columns) {
451
0
        *pschema->add_partial_update_input_columns() = col;
452
0
    }
453
16
    _tuple_desc->to_protobuf(pschema->mutable_tuple_desc());
454
96
    for (auto* slot : _tuple_desc->slots()) {
455
96
        slot->to_protobuf(pschema->add_slot_descs());
456
96
    }
457
32
    for (auto* index : _indexes) {
458
32
        index->to_protobuf(pschema->add_indexes());
459
32
    }
460
16
    if (_row_binlog_index_schema != nullptr) {
461
0
        _row_binlog_index_schema->to_protobuf(pschema->mutable_row_binlog_index_schema());
462
0
    }
463
16
}
464
465
0
std::string OlapTableSchemaParam::debug_string() const {
466
0
    std::stringstream ss;
467
0
    ss << "tuple_desc=" << _tuple_desc->debug_string();
468
0
    return ss.str();
469
0
}
470
471
VOlapTablePartitionParam::VOlapTablePartitionParam(std::shared_ptr<OlapTableSchemaParam>& schema,
472
                                                   const TOlapTablePartitionParam& t_param)
473
9
        : _schema(schema),
474
9
          _t_param(t_param),
475
9
          _slots(_schema->tuple_desc()->slots()),
476
9
          _mem_tracker(std::make_unique<MemTracker>("OlapTablePartitionParam")),
477
9
          _part_type(t_param.partition_type) {
478
9
    if (t_param.__isset.enable_automatic_partition && t_param.enable_automatic_partition) {
479
2
        _is_auto_partition = true;
480
2
        auto size = t_param.partition_function_exprs.size();
481
2
        _part_func_ctx.resize(size);
482
2
        _partition_function.resize(size);
483
2
        DCHECK((t_param.partition_type == TPartitionType::RANGE_PARTITIONED && size == 1) ||
484
0
               (t_param.partition_type == TPartitionType::LIST_PARTITIONED && size >= 1))
485
0
                << "now support only 1 partition column for auto range partitions. "
486
0
                << t_param.partition_type << " " << size;
487
4
        for (int i = 0; i < size; ++i) {
488
2
            Status st =
489
2
                    VExpr::create_expr_tree(t_param.partition_function_exprs[i], _part_func_ctx[i]);
490
2
            if (!st.ok()) {
491
0
                throw Exception(Status::InternalError("Partition function expr is not valid"),
492
0
                                "Partition function expr is not valid");
493
0
            }
494
2
            _partition_function[i] = _part_func_ctx[i]->root();
495
2
        }
496
2
    }
497
498
9
    if (t_param.__isset.enable_auto_detect_overwrite && t_param.enable_auto_detect_overwrite) {
499
1
        _is_auto_detect_overwrite = true;
500
1
        DCHECK(t_param.__isset.overwrite_group_id);
501
1
        _overwrite_group_id = t_param.overwrite_group_id;
502
1
    }
503
504
9
    if (t_param.__isset.master_address) {
505
0
        _master_address = std::make_shared<TNetworkAddress>(t_param.master_address);
506
0
    }
507
508
9
    if (_is_auto_partition) {
509
        // the nullable mode depends on partition_exprs. not column slots. so use them.
510
2
        DCHECK(_partition_function.size() <= _slots.size())
511
0
                << _partition_function.size() << ", " << _slots.size();
512
513
        // suppose (k0, [k1], [k2]), so get [k1, 0], [k2, 1]
514
2
        std::map<std::string, int> partition_slots_map; // name to idx in part_exprs
515
4
        for (size_t i = 0; i < t_param.partition_columns.size(); i++) {
516
2
            partition_slots_map.emplace(t_param.partition_columns[i], i);
517
2
        }
518
519
        // here we rely on the same order and number of the _part_funcs and _slots in the prefix
520
        // _part_block contains all slots of table.
521
2
        for (auto* slot : _slots) {
522
            // try to replace with partition expr.
523
2
            if (auto it = partition_slots_map.find(slot->col_name());
524
2
                it != partition_slots_map.end()) { // it's a partition column slot
525
2
                auto& expr_type = _partition_function[it->second]->data_type();
526
2
                _partition_block.insert({expr_type->create_column(), expr_type, slot->col_name()});
527
2
            } else {
528
0
                _partition_block.insert({slot->get_empty_mutable_column(),
529
0
                                         slot->get_data_type_ptr(), slot->col_name()});
530
0
            }
531
2
        }
532
2
        VLOG_TRACE << _partition_block.dump_structure();
533
7
    } else {
534
        // we insert all. but not all will be used. it will controlled by _partition_slot_locs
535
7
        for (auto* slot : _slots) {
536
7
            _partition_block.insert({slot->get_empty_mutable_column(), slot->get_data_type_ptr(),
537
7
                                     slot->col_name()});
538
7
        }
539
7
    }
540
9
}
541
542
9
VOlapTablePartitionParam::~VOlapTablePartitionParam() {
543
9
    _mem_tracker->release(_mem_usage);
544
9
}
545
546
9
Status VOlapTablePartitionParam::init() {
547
9
    std::vector<std::string> slot_column_names;
548
9
    for (auto* slot_desc : _schema->tuple_desc()->slots()) {
549
9
        slot_column_names.emplace_back(slot_desc->col_name());
550
9
    }
551
552
9
    auto find_slot_locs = [&slot_column_names](const std::string& slot_name,
553
9
                                               std::vector<uint16_t>& locs,
554
17
                                               const std::string& column_type) {
555
17
        auto it = std::find(slot_column_names.begin(), slot_column_names.end(), slot_name);
556
17
        if (it == slot_column_names.end()) {
557
0
            return Status::InternalError("{} column not found, column ={}", column_type, slot_name);
558
0
        }
559
17
        locs.emplace_back(it - slot_column_names.begin());
560
17
        return Status::OK();
561
17
    };
562
563
    // here we find the partition columns. others maybe non-partition columns/special columns.
564
9
    if (_t_param.__isset.partition_columns) {
565
9
        for (auto& part_col : _t_param.partition_columns) {
566
9
            RETURN_IF_ERROR(find_slot_locs(part_col, _partition_slot_locs, "partition"));
567
9
        }
568
9
    }
569
570
9
    _partitions_map = std::make_unique<
571
9
            std::map<BlockRowWithIndicator, VOlapTablePartition*, VOlapTablePartKeyComparator>>(
572
9
            VOlapTablePartKeyComparator(_partition_slot_locs, _transformed_slot_locs));
573
9
    if (_t_param.__isset.distributed_columns) {
574
8
        for (auto& col : _t_param.distributed_columns) {
575
8
            RETURN_IF_ERROR(find_slot_locs(col, _distributed_slot_locs, "distributed"));
576
8
        }
577
8
    }
578
579
    // for both auto/non-auto partition table.
580
9
    _is_in_partition = _part_type == TPartitionType::type::LIST_PARTITIONED;
581
582
    // initial partitions. if meet dummy partitions only for open BE nodes, not generate key of them for finding
583
17
    for (const auto& t_part : _t_param.partitions) {
584
17
        VOlapTablePartition* part = nullptr;
585
17
        RETURN_IF_ERROR(generate_partition_from(t_part, part));
586
17
        _partitions.emplace_back(part);
587
588
17
        if (!_t_param.partitions_is_fake) {
589
17
            if (_is_in_partition) {
590
0
                for (auto& in_key : part->in_keys) {
591
0
                    _partitions_map->emplace(std::tuple {in_key.first, in_key.second, false}, part);
592
0
                }
593
17
            } else {
594
17
                _partitions_map->emplace(
595
17
                        std::tuple {part->end_key.first, part->end_key.second, false}, part);
596
17
            }
597
17
        }
598
17
    }
599
600
9
    _mem_usage = _partition_block.allocated_bytes();
601
9
    _mem_tracker->consume(_mem_usage);
602
9
    return Status::OK();
603
9
}
604
605
bool VOlapTablePartitionParam::_part_contains(VOlapTablePartition* part,
606
29
                                              BlockRowWithIndicator key) const {
607
29
    VOlapTablePartKeyComparator comparator(_partition_slot_locs, _transformed_slot_locs);
608
    // we have used upper_bound to find to ensure key < part.right and this part is closest(right - key is min)
609
    // now we only have to check (key >= part.left). the comparator(a,b) means a < b, so we use anti
610
29
    return part->start_key.second == -1 /* spj: start_key.second == -1 means only single partition*/
611
29
           || !comparator(key, std::tuple {part->start_key.first, part->start_key.second, false});
612
29
}
613
614
// insert value into _partition_block's column
615
// NOLINTBEGIN(readability-function-size)
616
38
static Status _create_partition_key(const TExprNode& t_expr, BlockRow* part_key, uint16_t pos) {
617
38
    auto column = std::move(*part_key->first->get_by_position(pos).column).mutate();
618
38
    switch (t_expr.node_type) {
619
0
    case TExprNodeType::DATE_LITERAL: {
620
0
        auto primitive_type =
621
0
                DataTypeFactory::instance().create_data_type(t_expr.type)->get_primitive_type();
622
0
        if (primitive_type == TYPE_DATEV2) {
623
0
            DateV2Value<DateV2ValueType> dt;
624
0
            CastParameters params;
625
0
            if (!CastToDateV2::from_string_strict_mode<DatelikeParseMode::STRICT>(
626
0
                        {t_expr.date_literal.value.c_str(), t_expr.date_literal.value.size()}, dt,
627
0
                        nullptr, params)) {
628
0
                std::stringstream ss;
629
0
                ss << "invalid date literal in partition column, date=" << t_expr.date_literal;
630
0
                return Status::InternalError(ss.str());
631
0
            }
632
0
            column->insert_data(reinterpret_cast<const char*>(&dt), 0);
633
0
        } else if (primitive_type == TYPE_DATETIMEV2) {
634
0
            DateV2Value<DateTimeV2ValueType> dt;
635
0
            const int32_t scale =
636
0
                    t_expr.type.types.empty() ? -1 : t_expr.type.types.front().scalar_type.scale;
637
0
            CastParameters params;
638
0
            if (!CastToDatetimeV2::from_string_strict_mode<DatelikeParseMode::STRICT>(
639
0
                        {t_expr.date_literal.value.c_str(), t_expr.date_literal.value.size()}, dt,
640
0
                        nullptr, scale, params)) {
641
0
                std::stringstream ss;
642
0
                ss << "invalid date literal in partition column, date=" << t_expr.date_literal;
643
0
                return Status::InternalError(ss.str());
644
0
            }
645
0
            column->insert_data(reinterpret_cast<const char*>(&dt), 0);
646
0
        } else if (primitive_type == TYPE_TIMESTAMPTZ) {
647
0
            TimestampTzValue res;
648
0
            CastParameters params {.status = Status::OK(), .is_strict = true};
649
0
            const int32_t scale =
650
0
                    t_expr.type.types.empty() ? -1 : t_expr.type.types.front().scalar_type.scale;
651
0
            if (!CastToTimestampTz::from_string(
652
0
                        {t_expr.date_literal.value.c_str(), t_expr.date_literal.value.size()}, res,
653
0
                        params, nullptr, scale)) [[unlikely]] {
654
0
                std::stringstream ss;
655
0
                ss << "invalid timestamptz literal in partition column, value="
656
0
                   << t_expr.date_literal;
657
0
                return Status::InternalError(ss.str());
658
0
            }
659
0
            column->insert_data(reinterpret_cast<const char*>(&res), 0);
660
0
        } else {
661
0
            VecDateTimeValue dt;
662
0
            CastParameters params;
663
0
            if (!CastToDateOrDatetime::from_string_strict_mode<DatelikeParseMode::STRICT,
664
0
                                                               DatelikeTargetType::DATE_TIME>(
665
0
                        {t_expr.date_literal.value.c_str(), t_expr.date_literal.value.size()}, dt,
666
0
                        nullptr, params)) {
667
0
                std::stringstream ss;
668
0
                ss << "invalid date literal in partition column, date=" << t_expr.date_literal;
669
0
                return Status::InternalError(ss.str());
670
0
            }
671
0
            if (primitive_type == TYPE_DATE) {
672
0
                dt.cast_to_date();
673
0
            }
674
0
            column->insert_data(reinterpret_cast<const char*>(&dt), 0);
675
0
        }
676
0
        break;
677
0
    }
678
38
    case TExprNodeType::INT_LITERAL: {
679
38
        switch (t_expr.type.types[0].scalar_type.type) {
680
0
        case TPrimitiveType::TINYINT: {
681
0
            auto value = cast_set<int8_t>(t_expr.int_literal.value);
682
0
            column->insert_data(reinterpret_cast<const char*>(&value), 0);
683
0
            break;
684
0
        }
685
0
        case TPrimitiveType::SMALLINT: {
686
0
            auto value = cast_set<int16_t>(t_expr.int_literal.value);
687
0
            column->insert_data(reinterpret_cast<const char*>(&value), 0);
688
0
            break;
689
0
        }
690
38
        case TPrimitiveType::INT: {
691
38
            auto value = cast_set<int32_t>(t_expr.int_literal.value);
692
38
            column->insert_data(reinterpret_cast<const char*>(&value), 0);
693
38
            break;
694
0
        }
695
0
        default:
696
0
            int64_t value = t_expr.int_literal.value;
697
0
            column->insert_data(reinterpret_cast<const char*>(&value), 0);
698
38
        }
699
38
        break;
700
38
    }
701
38
    case TExprNodeType::LARGE_INT_LITERAL: {
702
0
        StringParser::ParseResult parse_result = StringParser::PARSE_SUCCESS;
703
0
        auto value = StringParser::string_to_int<__int128>(t_expr.large_int_literal.value.c_str(),
704
0
                                                           t_expr.large_int_literal.value.size(),
705
0
                                                           &parse_result);
706
0
        if (parse_result != StringParser::PARSE_SUCCESS) {
707
0
            value = MAX_INT128;
708
0
        }
709
0
        column->insert_data(reinterpret_cast<const char*>(&value), 0);
710
0
        break;
711
38
    }
712
0
    case TExprNodeType::STRING_LITERAL: {
713
0
        size_t len = t_expr.string_literal.value.size();
714
0
        const char* str_val = t_expr.string_literal.value.c_str();
715
0
        column->insert_data(str_val, len);
716
0
        break;
717
38
    }
718
0
    case TExprNodeType::BOOL_LITERAL: {
719
0
        column->insert_data(reinterpret_cast<const char*>(&t_expr.bool_literal.value), 0);
720
0
        break;
721
38
    }
722
0
    case TExprNodeType::NULL_LITERAL: {
723
        // insert a null literal
724
0
        if (!column->is_nullable()) {
725
            // https://github.com/apache/doris/pull/39449 have forbid this cause. always add this check as protective measures
726
0
            return Status::InternalError("The column {} is not null, can't insert into NULL value.",
727
0
                                         part_key->first->get_by_position(pos).name);
728
0
        }
729
0
        column->insert_data(nullptr, 0);
730
0
        break;
731
0
    }
732
0
    default: {
733
0
        return Status::InternalError("unsupported partition column node type, type={}",
734
0
                                     t_expr.node_type);
735
0
    }
736
38
    }
737
38
    part_key->second = cast_set<int32_t>(column->size() - 1);
738
38
    return Status::OK();
739
38
}
740
// NOLINTEND(readability-function-size)
741
742
Status VOlapTablePartitionParam::_create_partition_keys(const std::vector<TExprNode>& t_exprs,
743
38
                                                        BlockRow* part_key) {
744
76
    for (int i = 0; i < t_exprs.size(); i++) {
745
38
        RETURN_IF_ERROR(_create_partition_key(t_exprs[i], part_key, _partition_slot_locs[i]));
746
38
    }
747
38
    return Status::OK();
748
38
}
749
750
Status VOlapTablePartitionParam::generate_partition_from(const TOlapTablePartition& t_part,
751
17
                                                         VOlapTablePartition*& part_result) {
752
17
    DCHECK(part_result == nullptr);
753
    // here we set the default value of partition bounds first! if it doesn't have some key, it will be -1.
754
17
    part_result = _obj_pool.add(new VOlapTablePartition(&_partition_block));
755
17
    part_result->id = t_part.id;
756
17
    part_result->is_mutable = t_part.is_mutable;
757
    // only load_to_single_tablet = true will set load_tablet_idx
758
17
    if (t_part.__isset.load_tablet_idx) {
759
1
        part_result->load_tablet_idx = t_part.load_tablet_idx;
760
1
    }
761
762
17
    if (_is_in_partition) {
763
0
        for (const auto& keys : t_part.in_keys) {
764
0
            RETURN_IF_ERROR(_create_partition_keys(
765
0
                    keys, &part_result->in_keys.emplace_back(&_partition_block, -1)));
766
0
        }
767
0
        if (t_part.__isset.is_default_partition && t_part.is_default_partition &&
768
0
            _default_partition == nullptr) {
769
0
            _default_partition = part_result;
770
0
        }
771
17
    } else { // range
772
17
        if (t_part.__isset.start_keys) {
773
17
            RETURN_IF_ERROR(_create_partition_keys(t_part.start_keys, &part_result->start_key));
774
17
        }
775
        // we generate the right bound but not insert into partition map
776
17
        if (t_part.__isset.end_keys) {
777
17
            RETURN_IF_ERROR(_create_partition_keys(t_part.end_keys, &part_result->end_key));
778
17
        }
779
17
    }
780
781
17
    part_result->num_buckets = t_part.num_buckets;
782
17
    auto num_indexes = _schema->indexes().size();
783
17
    if (t_part.indexes.size() != num_indexes) {
784
0
        return Status::InternalError(
785
0
                "number of partition's index is not equal with schema's"
786
0
                ", num_part_indexes={}, num_schema_indexes={}",
787
0
                t_part.indexes.size(), num_indexes);
788
0
    }
789
17
    part_result->indexes = t_part.indexes;
790
17
    std::sort(part_result->indexes.begin(), part_result->indexes.end(),
791
17
              [](const OlapTableIndexTablets& lhs, const OlapTableIndexTablets& rhs) {
792
0
                  return lhs.index_id < rhs.index_id;
793
0
              });
794
    // check index
795
34
    for (int j = 0; j < num_indexes; ++j) {
796
17
        if (part_result->indexes[j].index_id != _schema->indexes()[j]->index_id) {
797
0
            return Status::InternalError(
798
0
                    "partition's index is not equal with schema's"
799
0
                    ", part_index={}, schema_index={}",
800
0
                    part_result->indexes[j].index_id, _schema->indexes()[j]->index_id);
801
0
        }
802
17
    }
803
17
    if (t_part.__isset.total_replica_num) {
804
0
        part_result->total_replica_num = t_part.total_replica_num;
805
0
    }
806
17
    if (t_part.__isset.load_required_replica_num) {
807
0
        part_result->load_required_replica_num = t_part.load_required_replica_num;
808
0
    }
809
17
    if (t_part.__isset.tablet_version_gap_backends) {
810
0
        for (const auto& [tablet_id, backend_ids] : t_part.tablet_version_gap_backends) {
811
0
            auto& gap_set = part_result->tablet_version_gap_backends[tablet_id];
812
0
            for (auto backend_id : backend_ids) {
813
0
                gap_set.insert(backend_id);
814
0
            }
815
0
        }
816
0
    }
817
17
    return Status::OK();
818
17
}
819
820
Status VOlapTablePartitionParam::add_partitions(
821
2
        const std::vector<TOlapTablePartition>& partitions) {
822
2
    for (const auto& t_part : partitions) {
823
2
        auto* part = _obj_pool.add(new VOlapTablePartition(&_partition_block));
824
2
        part->id = t_part.id;
825
2
        part->is_mutable = t_part.is_mutable;
826
827
        // we dont pass right keys when it's MAX_VALUE. so there's possibility we only have start_key but not end_key
828
        // range partition
829
2
        if (t_part.__isset.start_keys) {
830
2
            RETURN_IF_ERROR(_create_partition_keys(t_part.start_keys, &part->start_key));
831
2
        }
832
2
        if (t_part.__isset.end_keys) {
833
2
            RETURN_IF_ERROR(_create_partition_keys(t_part.end_keys, &part->end_key));
834
2
        }
835
        // list partition - we only set 1 value in 1 partition for new created ones
836
2
        if (t_part.__isset.in_keys) {
837
0
            for (const auto& keys : t_part.in_keys) {
838
0
                RETURN_IF_ERROR(_create_partition_keys(
839
0
                        keys, &part->in_keys.emplace_back(&_partition_block, -1)));
840
0
            }
841
0
            if (t_part.__isset.is_default_partition && t_part.is_default_partition) {
842
0
                _default_partition = part;
843
0
            }
844
0
        }
845
846
2
        part->num_buckets = t_part.num_buckets;
847
2
        auto num_indexes = _schema->indexes().size();
848
2
        if (t_part.indexes.size() != num_indexes) {
849
0
            return Status::InternalError(
850
0
                    "number of partition's index is not equal with schema's"
851
0
                    ", num_part_indexes={}, num_schema_indexes={}",
852
0
                    t_part.indexes.size(), num_indexes);
853
0
        }
854
2
        part->indexes = t_part.indexes;
855
2
        std::sort(part->indexes.begin(), part->indexes.end(),
856
2
                  [](const OlapTableIndexTablets& lhs, const OlapTableIndexTablets& rhs) {
857
0
                      return lhs.index_id < rhs.index_id;
858
0
                  });
859
        // check index
860
4
        for (int j = 0; j < num_indexes; ++j) {
861
2
            if (part->indexes[j].index_id != _schema->indexes()[j]->index_id) {
862
0
                return Status::InternalError(
863
0
                        "partition's index is not equal with schema's"
864
0
                        ", part_index={}, schema_index={}",
865
0
                        part->indexes[j].index_id, _schema->indexes()[j]->index_id);
866
0
            }
867
2
        }
868
2
        _partitions.emplace_back(part);
869
        // after _creating_partiton_keys
870
2
        if (_is_in_partition) {
871
0
            for (auto& in_key : part->in_keys) {
872
0
                _partitions_map->emplace(std::tuple {in_key.first, in_key.second, false}, part);
873
0
            }
874
2
        } else {
875
2
            _partitions_map->emplace(std::tuple {part->end_key.first, part->end_key.second, false},
876
2
                                     part);
877
2
        }
878
2
    }
879
880
2
    return Status::OK();
881
2
}
882
883
Status VOlapTablePartitionParam::replace_partitions(
884
        std::vector<int64_t>& old_partition_ids,
885
1
        const std::vector<TOlapTablePartition>& new_partitions) {
886
    // remove old replaced partitions
887
1
    DCHECK(old_partition_ids.size() == new_partitions.size());
888
889
    // init and add new partitions. insert into _partitions
890
3
    for (int i = 0; i < new_partitions.size(); i++) {
891
2
        const auto& t_part = new_partitions[i];
892
        // pair old_partition_ids and new_partitions one by one. TODO: sort to opt performance
893
2
        VOlapTablePartition* old_part = nullptr;
894
2
        auto old_part_id = old_partition_ids[i];
895
2
        if (auto it = std::find_if(
896
2
                    _partitions.begin(), _partitions.end(),
897
3
                    [=](const VOlapTablePartition* lhs) { return lhs->id == old_part_id; });
898
2
            it != _partitions.end()) {
899
2
            old_part = *it;
900
2
        } else {
901
0
            return Status::InternalError("Cannot find old tablet {} in replacing", old_part_id);
902
0
        }
903
904
2
        auto* part = _obj_pool.add(new VOlapTablePartition(&_partition_block));
905
2
        part->id = t_part.id;
906
2
        part->is_mutable = t_part.is_mutable;
907
908
        /// just substitute directly. no need to remove and reinsert keys.
909
        // range partition
910
2
        part->start_key = std::move(old_part->start_key);
911
2
        part->end_key = std::move(old_part->end_key);
912
        // list partition
913
2
        part->in_keys = std::move(old_part->in_keys);
914
2
        if (t_part.__isset.is_default_partition && t_part.is_default_partition) {
915
0
            _default_partition = part;
916
0
        }
917
918
2
        part->num_buckets = t_part.num_buckets;
919
2
        auto num_indexes = _schema->indexes().size();
920
2
        if (t_part.indexes.size() != num_indexes) {
921
0
            return Status::InternalError(
922
0
                    "number of partition's index is not equal with schema's"
923
0
                    ", num_part_indexes={}, num_schema_indexes={}",
924
0
                    t_part.indexes.size(), num_indexes);
925
0
        }
926
2
        part->indexes = t_part.indexes;
927
2
        std::sort(part->indexes.begin(), part->indexes.end(),
928
2
                  [](const OlapTableIndexTablets& lhs, const OlapTableIndexTablets& rhs) {
929
0
                      return lhs.index_id < rhs.index_id;
930
0
                  });
931
        // check index
932
4
        for (int j = 0; j < num_indexes; ++j) {
933
2
            if (part->indexes[j].index_id != _schema->indexes()[j]->index_id) {
934
0
                return Status::InternalError(
935
0
                        "partition's index is not equal with schema's"
936
0
                        ", part_index={}, schema_index={}",
937
0
                        part->indexes[j].index_id, _schema->indexes()[j]->index_id);
938
0
            }
939
2
        }
940
941
        // add new partitions with new id.
942
2
        _partitions.emplace_back(part);
943
2
        VLOG_NOTICE << "params add new partition " << part->id;
944
945
        // replace items in _partition_maps
946
2
        if (_is_in_partition) {
947
0
            for (auto& in_key : part->in_keys) {
948
0
                (*_partitions_map)[std::tuple {in_key.first, in_key.second, false}] = part;
949
0
            }
950
2
        } else {
951
2
            (*_partitions_map)[std::tuple {part->end_key.first, part->end_key.second, false}] =
952
2
                    part;
953
2
        }
954
2
    }
955
    // remove old partitions by id
956
1
    std::ranges::sort(old_partition_ids);
957
5
    for (auto it = _partitions.begin(); it != _partitions.end();) {
958
4
        if (std::ranges::binary_search(old_partition_ids, (*it)->id)) {
959
2
            it = _partitions.erase(it);
960
2
        } else {
961
2
            it++;
962
2
        }
963
4
    }
964
965
1
    return Status::OK();
966
1
}
967
968
} // namespace doris