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