Coverage Report

Created: 2026-07-29 15:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/sink/viceberg_merge_sink.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 "exec/sink/viceberg_merge_sink.h"
19
20
#include <fmt/format.h>
21
22
#include "agent/be_exec_version_manager.h"
23
#include "common/consts.h"
24
#include "common/exception.h"
25
#include "common/logging.h"
26
#include "core/block/block.h"
27
#include "core/column/column_nullable.h"
28
#include "core/column/column_string.h"
29
#include "core/column/column_struct.h"
30
#include "core/column/column_vector.h"
31
#include "core/data_type/data_type_nullable.h"
32
#include "core/data_type/data_type_struct.h"
33
#include "exec/sink/sink_common.h"
34
#include "exec/sink/viceberg_delete_sink.h"
35
#include "exec/sink/writer/iceberg/viceberg_table_writer.h"
36
#include "exprs/vexpr_context.h"
37
#include "format/table/iceberg/schema.h"
38
#include "format/table/iceberg/schema_parser.h"
39
#include "runtime/runtime_state.h"
40
#include "util/string_util.h"
41
42
namespace doris {
43
44
namespace {} // namespace
45
46
VIcebergMergeSink::VIcebergMergeSink(const TDataSink& t_sink, const VExprContextSPtrs& output_exprs,
47
                                     std::shared_ptr<Dependency> dep,
48
                                     std::shared_ptr<Dependency> fin_dep)
49
1.73k
        : AsyncResultWriter(output_exprs, dep, fin_dep), _t_sink(t_sink) {
50
1.73k
    DCHECK(_t_sink.__isset.iceberg_merge_sink);
51
1.73k
}
52
53
1.73k
VIcebergMergeSink::~VIcebergMergeSink() = default;
54
55
1.73k
Status VIcebergMergeSink::init_properties(ObjectPool* pool, const RowDescriptor& row_desc) {
56
1.73k
    RETURN_IF_ERROR(_build_inner_sinks());
57
58
1.73k
    _table_writer = std::make_unique<VIcebergTableWriter>(_table_sink, _table_output_expr_ctxs,
59
1.73k
                                                          nullptr, nullptr);
60
1.73k
    _table_writer->defer_file_cleanup_until_outer_close();
61
1.73k
    _delete_writer = std::make_unique<VIcebergDeleteSink>(_delete_sink, _delete_output_expr_ctxs,
62
1.73k
                                                          nullptr, nullptr);
63
1.73k
    _delete_writer->defer_file_cleanup_until_outer_close();
64
1.73k
    RETURN_IF_ERROR(_table_writer->init_properties(pool, row_desc));
65
1.73k
    RETURN_IF_ERROR(_delete_writer->init_properties(pool));
66
1.73k
    return Status::OK();
67
1.73k
}
68
69
1.72k
Status VIcebergMergeSink::open(RuntimeState* state, RuntimeProfile* profile) {
70
1.72k
    _state = state;
71
72
1.72k
    _written_rows_counter = ADD_COUNTER(profile, "RowsWritten", TUnit::UNIT);
73
1.72k
    _insert_rows_counter = ADD_COUNTER(profile, "InsertRows", TUnit::UNIT);
74
1.72k
    _delete_rows_counter = ADD_COUNTER(profile, "DeleteRows", TUnit::UNIT);
75
    // The query-wide version keeps validation all-or-nothing during a rolling BE upgrade.
76
1.72k
    _require_merge_cardinality_check =
77
1.72k
            _require_merge_cardinality_check &&
78
1.72k
            state->be_exec_version() >= SUPPORT_ICEBERG_MERGE_CARDINALITY_VERSION;
79
1.72k
    if (_require_merge_cardinality_check) {
80
829
        _matched_row_id_state_bytes_counter =
81
829
                ADD_COUNTER(profile, "MatchedRowIdStateBytes", TUnit::BYTES);
82
829
    }
83
1.72k
    _send_data_timer = ADD_TIMER(profile, "SendDataTime");
84
1.72k
    _open_timer = ADD_TIMER(profile, "OpenTime");
85
1.72k
    _close_timer = ADD_TIMER(profile, "CloseTime");
86
87
1.72k
    SCOPED_TIMER(_open_timer);
88
89
1.72k
    RETURN_IF_ERROR(_prepare_output_layout());
90
91
1.72k
    RuntimeProfile* table_profile = profile->create_child("IcebergMergeTableWriter", true, true);
92
1.72k
    RuntimeProfile* delete_profile = profile->create_child("IcebergMergeDeleteWriter", true, true);
93
94
1.72k
    RETURN_IF_ERROR(_table_writer->open(state, table_profile));
95
1.72k
    RETURN_IF_ERROR(_delete_writer->open(state, delete_profile));
96
97
1.72k
    return Status::OK();
98
1.72k
}
99
100
518
Status VIcebergMergeSink::write(RuntimeState* state, Block& block) {
101
518
    SCOPED_TIMER(_send_data_timer);
102
518
    if (block.rows() == 0) {
103
0
        return Status::OK();
104
0
    }
105
106
518
    Block output_block;
107
518
    RETURN_IF_ERROR(_projection_block(block, &output_block));
108
518
    if (output_block.rows() == 0) {
109
0
        return Status::OK();
110
0
    }
111
112
518
    if (_operation_idx < 0 || _row_id_idx < 0) {
113
0
        return Status::InternalError("Iceberg merge sink missing operation/row_id columns");
114
0
    }
115
116
518
    const auto& op_column = output_block.get_by_position(_operation_idx).column;
117
518
    const auto* op_data = remove_nullable(op_column).get();
118
119
518
    IColumn::Filter delete_filter(output_block.rows(), 0);
120
518
    IColumn::Filter insert_filter(output_block.rows(), 0);
121
518
    bool has_delete = false;
122
518
    bool has_insert = false;
123
518
    size_t delete_rows = 0;
124
518
    size_t insert_rows = 0;
125
126
103k
    for (size_t i = 0; i < output_block.rows(); ++i) {
127
102k
        int8_t op = static_cast<int8_t>(op_data->get_int(i));
128
102k
        bool delete_op = is_delete_op(op);
129
102k
        bool insert_op = is_insert_op(op);
130
102k
        if (!delete_op && !insert_op) {
131
1
            return Status::InternalError("Unknown Iceberg merge operation {}", op);
132
1
        }
133
102k
        if (delete_op) {
134
102k
            delete_filter[i] = 1;
135
102k
            has_delete = true;
136
102k
            ++delete_rows;
137
102k
        }
138
102k
        if (insert_op) {
139
102k
            insert_filter[i] = 1;
140
102k
            has_insert = true;
141
102k
            ++insert_rows;
142
102k
        }
143
102k
    }
144
145
517
    if (_require_merge_cardinality_check) {
146
        // The physical sink hashes matched rows by row_id, so exact state retained across blocks
147
        // enforces SQL MERGE cardinality for the whole query without changing UPDATE semantics.
148
299
        RETURN_IF_ERROR(_validate_matched_row_ids(output_block, delete_filter.data()));
149
296
        COUNTER_SET(_matched_row_id_state_bytes_counter,
150
296
                    static_cast<int64_t>(_matched_row_id_state_size));
151
296
    }
152
514
    _row_count += output_block.rows();
153
514
    _delete_row_count += delete_rows;
154
514
    _insert_row_count += insert_rows;
155
156
514
    bool skip_io = false;
157
#ifdef BE_TEST
158
    skip_io = _skip_io;
159
#endif
160
161
514
    if (has_delete && !skip_io) {
162
246
        Block delete_block = output_block;
163
246
        std::vector<int> delete_indices {_row_id_idx};
164
246
        delete_block.erase_not_in(delete_indices);
165
246
        Block::filter_block_internal(&delete_block, delete_filter);
166
246
        RETURN_IF_ERROR(_delete_writer->write(state, delete_block));
167
246
    }
168
169
514
    if (has_insert && !skip_io) {
170
246
        if (_data_column_indices.empty()) {
171
0
            return Status::InternalError("Iceberg merge sink has no data columns for insert");
172
0
        }
173
246
        Block insert_block = output_block;
174
246
        insert_block.erase_not_in(_data_column_indices);
175
246
        Block::filter_block_internal(&insert_block, insert_filter);
176
246
        RETURN_IF_ERROR(_table_writer->write_prepared_block(insert_block));
177
246
    }
178
179
514
    if (_written_rows_counter != nullptr) {
180
514
        COUNTER_UPDATE(_written_rows_counter, output_block.rows());
181
514
    }
182
514
    if (_insert_rows_counter != nullptr) {
183
514
        COUNTER_UPDATE(_insert_rows_counter, insert_rows);
184
514
    }
185
514
    if (_delete_rows_counter != nullptr) {
186
514
        COUNTER_UPDATE(_delete_rows_counter, delete_rows);
187
514
    }
188
189
514
    return Status::OK();
190
514
}
191
192
Status VIcebergMergeSink::_validate_matched_row_ids(const Block& block,
193
299
                                                    const uint8_t* delete_filter) {
194
299
    const auto& row_id = block.get_by_position(_row_id_idx);
195
299
    const IColumn* row_id_data = row_id.column.get();
196
299
    const IDataType* row_id_type = row_id.type.get();
197
299
    const auto* nullable_row_id = check_and_get_column<ColumnNullable>(row_id_data);
198
299
    if (nullable_row_id != nullptr) {
199
230
        row_id_data = nullable_row_id->get_nested_column_ptr().get();
200
230
    }
201
299
    if (const auto* nullable_type = check_and_get_data_type<DataTypeNullable>(row_id_type)) {
202
230
        row_id_type = nullable_type->get_nested_type().get();
203
230
    }
204
205
299
    const auto* struct_column = check_and_get_column<ColumnStruct>(row_id_data);
206
299
    const auto* struct_type = check_and_get_data_type<DataTypeStruct>(row_id_type);
207
299
    if (struct_column == nullptr || struct_type == nullptr) {
208
0
        return Status::InternalError("Iceberg merge row_id column is not a struct");
209
0
    }
210
211
299
    int file_path_idx = -1;
212
299
    int row_position_idx = -1;
213
299
    const auto& field_names = struct_type->get_element_names();
214
1.35k
    for (size_t i = 0; i < field_names.size(); ++i) {
215
1.05k
        std::string field_name = doris::to_lower(field_names[i]);
216
1.05k
        if (field_name == "file_path") {
217
299
            file_path_idx = static_cast<int>(i);
218
759
        } else if (field_name == "row_position") {
219
299
            row_position_idx = static_cast<int>(i);
220
299
        }
221
1.05k
    }
222
299
    if (file_path_idx < 0 || row_position_idx < 0) {
223
0
        return Status::InternalError(
224
0
                "Iceberg merge row_id must contain file_path and row_position fields");
225
0
    }
226
227
299
    const auto& file_path_column = struct_column->get_column_ptr(file_path_idx);
228
299
    const auto& row_position_column = struct_column->get_column_ptr(row_position_idx);
229
299
    const auto* nullable_file_path = check_and_get_column<ColumnNullable>(file_path_column.get());
230
299
    const auto* nullable_row_position =
231
299
            check_and_get_column<ColumnNullable>(row_position_column.get());
232
299
    const auto* file_paths =
233
299
            check_and_get_column<ColumnString>(remove_nullable(file_path_column).get());
234
299
    const auto* row_positions = check_and_get_column<ColumnVector<TYPE_BIGINT>>(
235
299
            remove_nullable(row_position_column).get());
236
299
    if (file_paths == nullptr || row_positions == nullptr) {
237
0
        return Status::InternalError("Iceberg merge row_id fields have incorrect types");
238
0
    }
239
240
299
    std::map<roaring::Roaring64Map*, size_t> touched_bitmap_sizes;
241
102k
    for (size_t i = 0; i < block.rows(); ++i) {
242
102k
        if (delete_filter[i] == 0) {
243
162
            continue;
244
162
        }
245
102k
        if ((nullable_row_id != nullptr && nullable_row_id->is_null_at(i)) ||
246
102k
            (nullable_file_path != nullptr && nullable_file_path->is_null_at(i)) ||
247
102k
            (nullable_row_position != nullptr && nullable_row_position->is_null_at(i))) {
248
0
            return Status::InternalError("Iceberg merge matched row_id cannot be null");
249
0
        }
250
251
102k
        int64_t row_position = row_positions->get_element(i);
252
102k
        if (row_position < 0) {
253
0
            return Status::InternalError("Invalid row_position {} in Iceberg merge row_id",
254
0
                                         row_position);
255
0
        }
256
        // Intern each file path once and keep exact positions in a compressed bitmap; retaining a
257
        // full path string per matched row makes MERGE memory grow with path_length * row_count.
258
102k
        auto [file_it, inserted] =
259
102k
                _matched_row_positions.try_emplace(file_paths->get_data_at(i).to_string());
260
102k
        auto* positions = &file_it->second;
261
102k
        auto touched_it = touched_bitmap_sizes.find(positions);
262
102k
        if (touched_it == touched_bitmap_sizes.end()) {
263
2.19k
            touched_it = touched_bitmap_sizes.emplace(positions, positions->getSizeInBytes()).first;
264
2.19k
        }
265
102k
        if (inserted) {
266
2.18k
            _matched_row_id_state_size +=
267
2.18k
                    sizeof(std::pair<const std::string, roaring::Roaring64Map>);
268
2.18k
            _matched_row_id_state_size += file_it->first.capacity();
269
2.18k
            _matched_row_id_state_size += touched_it->second;
270
2.18k
        }
271
102k
        if (!positions->addChecked(static_cast<uint64_t>(row_position))) {
272
3
            return Status::InvalidArgument(
273
3
                    "Iceberg MERGE failed because multiple source rows matched the same target "
274
3
                    "row");
275
3
        }
276
102k
    }
277
278
    // Measure only bitmaps touched by this block; rescanning all retained files on every write
279
    // makes a many-file MERGE quadratic in the number of input blocks.
280
2.18k
    for (const auto& [positions, previous_size] : touched_bitmap_sizes) {
281
2.18k
        size_t current_size = positions->getSizeInBytes();
282
2.18k
        if (current_size >= previous_size) {
283
2.18k
            _matched_row_id_state_size += current_size - previous_size;
284
2.18k
        } else {
285
0
            _matched_row_id_state_size -= previous_size - current_size;
286
0
        }
287
2.18k
    }
288
296
    return Status::OK();
289
299
}
290
291
1.72k
Status VIcebergMergeSink::close(Status close_status) {
292
1.72k
    SCOPED_TIMER(_close_timer);
293
294
1.72k
    if (!close_status.ok()) {
295
3
        LOG(WARNING) << fmt::format("VIcebergMergeSink close with error: {}",
296
3
                                    close_status.to_string());
297
3
        if (_table_writer) {
298
3
            static_cast<void>(_table_writer->close(close_status));
299
3
        }
300
3
        if (_delete_writer) {
301
3
            static_cast<void>(_delete_writer->close(close_status));
302
3
        }
303
3
        return close_status;
304
3
    }
305
306
1.71k
    Status table_status = Status::OK();
307
1.71k
    Status delete_status = Status::OK();
308
1.71k
    if (_table_writer) {
309
1.71k
        table_status = _table_writer->close(close_status);
310
1.71k
    }
311
1.71k
    if (_delete_writer) {
312
1.71k
        delete_status = _delete_writer->close(close_status);
313
1.71k
    }
314
315
1.71k
    if (_written_rows_counter != nullptr) {
316
1.71k
        COUNTER_SET(_written_rows_counter, static_cast<int64_t>(_row_count));
317
1.71k
    }
318
1.71k
    if (_insert_rows_counter != nullptr) {
319
1.71k
        COUNTER_SET(_insert_rows_counter, static_cast<int64_t>(_insert_row_count));
320
1.71k
    }
321
1.71k
    if (_delete_rows_counter != nullptr) {
322
1.71k
        COUNTER_SET(_delete_rows_counter, static_cast<int64_t>(_delete_row_count));
323
1.71k
    }
324
325
1.71k
    Status result_status = table_status.ok() ? delete_status : table_status;
326
1.71k
    if (_table_writer) {
327
1.71k
        _table_writer->finish_deferred_file_cleanup(result_status);
328
1.71k
    }
329
1.71k
    if (_delete_writer) {
330
1.71k
        _delete_writer->finish_deferred_file_cleanup(result_status);
331
1.71k
    }
332
1.71k
    return result_status;
333
1.72k
}
334
335
1.73k
Status VIcebergMergeSink::_build_inner_sinks() {
336
1.73k
    if (!_t_sink.__isset.iceberg_merge_sink) {
337
0
        return Status::InternalError("Missing iceberg merge sink config");
338
0
    }
339
340
1.73k
    const auto& merge_sink = _t_sink.iceberg_merge_sink;
341
    // Missing means an old FE plan, which predates SQL MERGE cardinality validation.
342
1.73k
    _require_merge_cardinality_check = merge_sink.__isset.require_merge_cardinality_check &&
343
1.73k
                                       merge_sink.require_merge_cardinality_check;
344
345
1.73k
    TIcebergTableSink table_sink;
346
1.73k
    if (merge_sink.__isset.db_name) {
347
1.73k
        table_sink.__set_db_name(merge_sink.db_name);
348
1.73k
    }
349
1.73k
    if (merge_sink.__isset.tb_name) {
350
1.73k
        table_sink.__set_tb_name(merge_sink.tb_name);
351
1.73k
    }
352
1.73k
    if (merge_sink.__isset.schema_json) {
353
1.73k
        table_sink.__set_schema_json(merge_sink.schema_json);
354
1.73k
    }
355
1.73k
    if (merge_sink.__isset.partition_specs_json) {
356
788
        table_sink.__set_partition_specs_json(merge_sink.partition_specs_json);
357
788
    }
358
1.73k
    if (merge_sink.__isset.partition_spec_id) {
359
802
        table_sink.__set_partition_spec_id(merge_sink.partition_spec_id);
360
802
    }
361
1.73k
    if (merge_sink.__isset.sort_fields) {
362
32
        table_sink.__set_sort_fields(merge_sink.sort_fields);
363
32
    }
364
1.73k
    if (merge_sink.__isset.file_format) {
365
1.73k
        table_sink.__set_file_format(merge_sink.file_format);
366
1.73k
    }
367
1.73k
    if (merge_sink.__isset.compression_type) {
368
1.73k
        table_sink.__set_compression_type(merge_sink.compression_type);
369
1.73k
    }
370
1.73k
    if (merge_sink.__isset.output_path) {
371
1.73k
        table_sink.__set_output_path(merge_sink.output_path);
372
1.73k
    }
373
1.73k
    if (merge_sink.__isset.original_output_path) {
374
1.73k
        table_sink.__set_original_output_path(merge_sink.original_output_path);
375
1.73k
    }
376
1.73k
    if (merge_sink.__isset.hadoop_config) {
377
1.71k
        table_sink.__set_hadoop_config(merge_sink.hadoop_config);
378
1.71k
    }
379
1.73k
    if (merge_sink.__isset.file_type) {
380
1.73k
        table_sink.__set_file_type(merge_sink.file_type);
381
1.73k
    }
382
1.73k
    if (merge_sink.__isset.broker_addresses) {
383
0
        table_sink.__set_broker_addresses(merge_sink.broker_addresses);
384
0
    }
385
1.73k
    if (merge_sink.__isset.collect_column_stats) {
386
1.71k
        table_sink.__set_collect_column_stats(merge_sink.collect_column_stats);
387
1.71k
    }
388
1.73k
    _table_sink.__set_type(TDataSinkType::ICEBERG_TABLE_SINK);
389
1.73k
    _table_sink.__set_iceberg_table_sink(table_sink);
390
391
1.73k
    TIcebergDeleteSink delete_sink;
392
1.73k
    if (merge_sink.__isset.db_name) {
393
1.73k
        delete_sink.__set_db_name(merge_sink.db_name);
394
1.73k
    }
395
1.73k
    if (merge_sink.__isset.tb_name) {
396
1.73k
        delete_sink.__set_tb_name(merge_sink.tb_name);
397
1.73k
    }
398
1.73k
    if (merge_sink.__isset.delete_type) {
399
1.73k
        delete_sink.__set_delete_type(merge_sink.delete_type);
400
1.73k
    }
401
1.73k
    if (merge_sink.__isset.file_format) {
402
1.73k
        delete_sink.__set_file_format(merge_sink.file_format);
403
1.73k
    }
404
1.73k
    if (merge_sink.__isset.compression_type) {
405
1.73k
        delete_sink.__set_compress_type(merge_sink.compression_type);
406
1.73k
    }
407
1.73k
    if (merge_sink.__isset.output_path) {
408
1.73k
        delete_sink.__set_output_path(merge_sink.output_path);
409
1.73k
    }
410
1.73k
    if (merge_sink.__isset.table_location) {
411
1.73k
        delete_sink.__set_table_location(merge_sink.table_location);
412
1.73k
    }
413
1.73k
    if (merge_sink.__isset.hadoop_config) {
414
1.71k
        delete_sink.__set_hadoop_config(merge_sink.hadoop_config);
415
1.71k
    }
416
1.73k
    if (merge_sink.__isset.file_type) {
417
1.73k
        delete_sink.__set_file_type(merge_sink.file_type);
418
1.73k
    }
419
1.73k
    if (merge_sink.__isset.partition_spec_id_for_delete) {
420
802
        delete_sink.__set_partition_spec_id(merge_sink.partition_spec_id_for_delete);
421
802
    }
422
1.73k
    if (merge_sink.__isset.partition_data_json_for_delete) {
423
0
        delete_sink.__set_partition_data_json(merge_sink.partition_data_json_for_delete);
424
0
    }
425
1.73k
    if (merge_sink.__isset.broker_addresses) {
426
0
        delete_sink.__set_broker_addresses(merge_sink.broker_addresses);
427
0
    }
428
1.73k
    if (merge_sink.__isset.format_version) {
429
1.71k
        delete_sink.__set_format_version(merge_sink.format_version);
430
1.71k
    }
431
1.73k
    if (merge_sink.__isset.rewritable_delete_file_sets) {
432
1.71k
        delete_sink.__set_rewritable_delete_file_sets(merge_sink.rewritable_delete_file_sets);
433
1.71k
    }
434
1.73k
    _delete_sink.__set_type(TDataSinkType::ICEBERG_DELETE_SINK);
435
1.73k
    _delete_sink.__set_iceberg_delete_sink(delete_sink);
436
437
1.73k
    return Status::OK();
438
1.73k
}
439
440
1.73k
Status VIcebergMergeSink::_prepare_output_layout() {
441
1.73k
    if (_vec_output_expr_ctxs.empty()) {
442
0
        return Status::InternalError("Iceberg merge sink has empty output expressions");
443
0
    }
444
445
1.73k
    std::string row_id_name = doris::to_lower(BeConsts::ICEBERG_ROWID_COL);
446
1.73k
    std::string op_name = doris::to_lower(kOperationColumnName);
447
448
1.73k
    _operation_idx = -1;
449
1.73k
    _row_id_idx = -1;
450
13.5k
    for (size_t i = 0; i < _vec_output_expr_ctxs.size(); ++i) {
451
11.8k
        std::string expr_name = doris::to_lower(_vec_output_expr_ctxs[i]->expr_name());
452
11.8k
        if (_operation_idx < 0 && expr_name == op_name) {
453
1.72k
            _operation_idx = static_cast<int>(i);
454
10.1k
        } else if (_row_id_idx < 0 && expr_name == row_id_name) {
455
1.72k
            _row_id_idx = static_cast<int>(i);
456
1.72k
        }
457
11.8k
    }
458
459
1.73k
    if (_operation_idx < 0) {
460
1
        return Status::InternalError("Iceberg merge sink missing operation column");
461
1
    }
462
1.72k
    if (_row_id_idx < 0) {
463
1
        return Status::InternalError("Iceberg merge sink missing row_id column");
464
1
    }
465
466
1.72k
    _data_column_indices.clear();
467
1.72k
    _table_output_expr_ctxs.clear();
468
13.5k
    for (size_t i = 0; i < _vec_output_expr_ctxs.size(); ++i) {
469
11.8k
        if (static_cast<int>(i) == _operation_idx || static_cast<int>(i) == _row_id_idx) {
470
3.45k
            continue;
471
3.45k
        }
472
8.38k
        _data_column_indices.push_back(static_cast<int>(i));
473
8.38k
        _table_output_expr_ctxs.emplace_back(_vec_output_expr_ctxs[i]);
474
8.38k
    }
475
476
1.72k
    return Status::OK();
477
1.72k
}
478
479
} // namespace doris