Skip to content

[feature](iceberg) Implements iceberg update delete merge into functionality. #60482

Open
kaka11chen wants to merge 2 commits intoapache:masterfrom
kaka11chen:iceberg_update_delete_merge_into
Open

[feature](iceberg) Implements iceberg update delete merge into functionality. #60482
kaka11chen wants to merge 2 commits intoapache:masterfrom
kaka11chen:iceberg_update_delete_merge_into

Conversation

@kaka11chen
Copy link
Contributor

@kaka11chen kaka11chen commented Feb 4, 2026

What problem does this PR solve?

Release note

[feature] (iceberg) Implements iceberg update & delete & merge into.

  1. Nereids Planner:
    • Introduced parser and planner support for DELETE, UPDATE, and MERGE INTO targeting Iceberg tables.
    • Added specific plan nodes and rules (IcebergDeleteCommand, IcebergMergeCommand, IcebergUpdateCommand, Logical/PhysicalIcebergDeleteSink, Logical/PhysicalIcebergMergeSink).
  2. Frontend & Transaction:
    • Handled Iceberg DML transactional context and snapshot commits via IcebergTransaction.
    • Implemented conflict detection and resolution utilities for concurrent modifications (IcebergConflictDetectionFilterUtils).
    • Added support for Iceberg metadata columns (e.g., _file_path, _pos) via IcebergMetadataColumn to accurately locate deleting records.
  3. Backend Execution Sinks:
    • Introduced VIcebergDeleteSink and VIcebergMergeSink sink operators.
    • Added VIcebergDeleteFileWriter mechanism to support writing Position Delete files natively inside Doris BE.
    • Implemented routing and data shuffling logic via MergePartitioner and IcebergPartitionFunction to properly align input rows with Iceberg's partitioning constraints during writes.
  4. Scanner & Execution Decoupling:
    • Adjusted FileScanner and GenericReader to natively support row-level operations (like generating $row_id), feeding precise coordinates for DML modifications.
  5. Quality Assurance:
    • Added comprehensive logic/physical plan tests (e.g. IcebergDDLAndDMLPlanTest).
    • Extensive coverage of the sink components in BE Unit Tests (viceberg_merge_sink_test.cpp, viceberg_delete_sink_test.cpp, etc.).
    • Introduced standard regression test suites covering basic/advanced usages and edge cases for merge/update/delete (regression-test/suites/external_table_p0/iceberg/dml/*).

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

@Thearas
Copy link
Contributor

Thearas commented Feb 4, 2026

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch 6 times, most recently from 96b12f9 to a6ce27b Compare February 5, 2026 09:17
@kaka11chen
Copy link
Contributor Author

run buildall

@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch from a6ce27b to 66648d2 Compare February 5, 2026 09:38
@kaka11chen
Copy link
Contributor Author

run buildall

@doris-robot
Copy link

TPC-H: Total hot run time: 32029 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 66648d21add96989c8dd2d4f47a6e7831d4b6ec5, data reload: false

------ Round 1 ----------------------------------
q1	17676	4624	4315	4315
q2	2054	359	234	234
q3	10397	1305	748	748
q4	10343	868	317	317
q5	9312	2226	1956	1956
q6	220	180	146	146
q7	904	750	597	597
q8	9280	1398	1132	1132
q9	5597	4849	4781	4781
q10	6873	1970	1572	1572
q11	516	292	295	292
q12	393	387	225	225
q13	17815	4065	3254	3254
q14	234	231	223	223
q15	892	809	811	809
q16	695	697	624	624
q17	651	844	512	512
q18	7278	6790	7237	6790
q19	2172	1043	706	706
q20	428	405	279	279
q21	3098	2274	2219	2219
q22	384	358	298	298
Total cold run time: 107212 ms
Total hot run time: 32029 ms

----- Round 2, with runtime_filter_mode=off -----
q1	4695	4663	4498	4498
q2	273	344	264	264
q3	2370	2827	2311	2311
q4	1465	1981	1421	1421
q5	4815	4731	4690	4690
q6	227	177	138	138
q7	2053	2014	1763	1763
q8	2581	2449	2391	2391
q9	7550	7394	7721	7394
q10	2861	3064	2538	2538
q11	548	453	428	428
q12	616	670	547	547
q13	3569	3990	3235	3235
q14	265	277	258	258
q15	818	792	769	769
q16	648	687	623	623
q17	1095	1284	1360	1284
q18	7361	7466	7267	7267
q19	912	878	843	843
q20	1985	2056	1861	1861
q21	4632	4305	4154	4154
q22	569	540	497	497
Total cold run time: 51908 ms
Total hot run time: 49174 ms

@doris-robot
Copy link

ClickBench: Total hot run time: 28.21 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 66648d21add96989c8dd2d4f47a6e7831d4b6ec5, data reload: false

query1	0.05	0.05	0.05
query2	0.13	0.08	0.08
query3	0.30	0.08	0.08
query4	1.62	0.10	0.10
query5	0.26	0.25	0.25
query6	1.14	0.64	0.65
query7	0.03	0.03	0.03
query8	0.08	0.06	0.06
query9	0.59	0.50	0.50
query10	0.54	0.55	0.54
query11	0.26	0.14	0.13
query12	0.26	0.14	0.17
query13	0.62	0.62	0.60
query14	0.98	0.98	0.97
query15	0.91	0.82	0.83
query16	0.39	0.38	0.38
query17	0.99	1.03	1.03
query18	0.24	0.23	0.23
query19	1.92	1.87	1.85
query20	0.02	0.01	0.02
query21	15.42	0.33	0.29
query22	4.96	0.12	0.12
query23	15.38	0.46	0.28
query24	2.27	0.54	0.37
query25	0.11	0.11	0.11
query26	0.19	0.19	0.19
query27	0.11	0.11	0.11
query28	3.62	1.16	0.99
query29	12.55	4.09	3.31
query30	0.32	0.13	0.14
query31	2.81	0.69	0.44
query32	3.26	0.63	0.50
query33	3.06	2.96	3.04
query34	16.14	5.07	4.49
query35	4.49	4.52	4.43
query36	0.60	0.48	0.50
query37	0.30	0.09	0.09
query38	0.26	0.05	0.06
query39	0.08	0.05	0.05
query40	0.20	0.18	0.17
query41	0.13	0.06	0.07
query42	0.08	0.04	0.04
query43	0.06	0.06	0.06
Total cold run time: 97.73 s
Total hot run time: 28.21 s

@doris-robot
Copy link

BE UT Coverage Report

Increment line coverage 54.22% (1034/1907) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 52.62% (19460/36980)
Line Coverage 36.13% (181071/501105)
Region Coverage 32.55% (140712/432235)
Branch Coverage 33.50% (60926/181885)

@hello-stephen
Copy link
Contributor

BE Regression && UT Coverage Report

Increment line coverage 54.47% (1035/1900) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.24% (21468/36242)
Line Coverage 42.31% (211530/499918)
Region Coverage 39.31% (171649/436654)
Branch Coverage 39.85% (72782/182619)

@hello-stephen
Copy link
Contributor

FE Regression Coverage Report

Increment line coverage 3.75% (74/1971) 🎉
Increment coverage report
Complete coverage report

@kaka11chen
Copy link
Contributor Author

run buildall

@hello-stephen
Copy link
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 79.29% (1792/2260)
Line Coverage 64.74% (31823/49158)
Region Coverage 65.41% (15882/24280)
Branch Coverage 55.93% (8433/15078)

@doris-robot
Copy link

BE UT Coverage Report

Increment line coverage 54.22% (1034/1907) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 52.63% (19464/36980)
Line Coverage 36.16% (181194/501105)
Region Coverage 32.57% (140764/432235)
Branch Coverage 33.52% (60966/181885)

@kaka11chen
Copy link
Contributor Author

run buildall

@doris-robot
Copy link

TPC-H: Total hot run time: 30511 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit a486d4dcc2286a8c2587c4317397aa861e2c8011, data reload: false

------ Round 1 ----------------------------------
q1	17607	4500	4388	4388
q2	2074	372	234	234
q3	10408	1353	746	746
q4	10333	803	309	309
q5	9505	2216	1948	1948
q6	242	180	148	148
q7	893	748	588	588
q8	9281	1442	1258	1258
q9	5078	4679	4600	4600
q10	6923	1950	1542	1542
q11	527	306	286	286
q12	406	389	224	224
q13	17812	4054	3267	3267
q14	226	242	219	219
q15	936	824	808	808
q16	685	673	628	628
q17	755	825	541	541
q18	6841	5924	5668	5668
q19	1354	998	607	607
q20	522	499	390	390
q21	2622	1828	1874	1828
q22	376	334	284	284
Total cold run time: 105406 ms
Total hot run time: 30511 ms

----- Round 2, with runtime_filter_mode=off -----
q1	4428	4349	4364	4349
q2	261	344	250	250
q3	2136	2682	2217	2217
q4	1384	1734	1275	1275
q5	4274	4188	4355	4188
q6	218	174	133	133
q7	1858	1787	2103	1787
q8	2636	2566	2445	2445
q9	7562	7538	7617	7538
q10	2901	2983	2724	2724
q11	593	497	462	462
q12	719	735	631	631
q13	3892	4381	3746	3746
q14	314	310	273	273
q15	861	797	782	782
q16	688	739	687	687
q17	1158	1626	1555	1555
q18	8084	7954	8051	7954
q19	903	959	924	924
q20	2057	2093	2041	2041
q21	4948	4560	4409	4409
q22	609	549	519	519
Total cold run time: 52484 ms
Total hot run time: 50889 ms

@doris-robot
Copy link

ClickBench: Total hot run time: 28.21 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit a486d4dcc2286a8c2587c4317397aa861e2c8011, data reload: false

query1	0.06	0.05	0.05
query2	0.14	0.07	0.07
query3	0.33	0.08	0.08
query4	1.61	0.10	0.10
query5	0.26	0.24	0.24
query6	1.14	0.65	0.65
query7	0.04	0.03	0.03
query8	0.08	0.06	0.06
query9	0.59	0.50	0.49
query10	0.54	0.55	0.56
query11	0.26	0.14	0.14
query12	0.26	0.14	0.14
query13	0.63	0.63	0.63
query14	0.97	0.98	0.97
query15	0.90	0.82	0.83
query16	0.40	0.37	0.39
query17	1.03	1.04	1.04
query18	0.25	0.24	0.24
query19	1.99	1.88	1.89
query20	0.02	0.02	0.02
query21	15.39	0.35	0.29
query22	4.97	0.13	0.12
query23	15.33	0.45	0.28
query24	2.30	0.55	0.37
query25	0.12	0.11	0.10
query26	0.21	0.19	0.18
query27	0.11	0.10	0.11
query28	3.61	1.18	0.98
query29	12.53	4.06	3.24
query30	0.32	0.12	0.11
query31	2.80	0.70	0.45
query32	3.23	0.63	0.50
query33	2.98	3.04	3.04
query34	16.35	5.09	4.42
query35	4.53	4.46	4.48
query36	0.61	0.51	0.49
query37	0.30	0.08	0.08
query38	0.27	0.06	0.05
query39	0.07	0.05	0.05
query40	0.20	0.16	0.17
query41	0.14	0.07	0.07
query42	0.08	0.05	0.05
query43	0.07	0.06	0.05
Total cold run time: 98.02 s
Total hot run time: 28.21 s

@hello-stephen
Copy link
Contributor

BE UT Coverage Report

Increment line coverage 54.22% (1034/1907) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 52.68% (19497/37013)
Line Coverage 36.21% (181685/501796)
Region Coverage 32.55% (140954/433033)
Branch Coverage 33.56% (61121/182140)

@hello-stephen
Copy link
Contributor

BE Regression && UT Coverage Report

Increment line coverage 54.47% (1035/1900) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.18% (21465/36273)
Line Coverage 42.26% (211541/500573)
Region Coverage 39.26% (171751/437418)
Branch Coverage 39.86% (72893/182850)

@hello-stephen
Copy link
Contributor

FE Regression Coverage Report

Increment line coverage 37.76% (773/2047) 🎉
Increment coverage report
Complete coverage report

@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch from 3e61bcd to 300a7c8 Compare February 6, 2026 15:02
@kaka11chen
Copy link
Contributor Author

run buildall

@hello-stephen
Copy link
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 79.29% (1792/2260)
Line Coverage 64.76% (31836/49158)
Region Coverage 65.44% (15889/24280)
Branch Coverage 55.97% (8439/15078)

@doris-robot
Copy link

BE UT Coverage Report

Increment line coverage 54.17% (1026/1894) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 52.68% (19499/37012)
Line Coverage 36.22% (181721/501776)
Region Coverage 32.60% (141157/433022)
Branch Coverage 33.56% (61128/182120)

@hello-stephen
Copy link
Contributor

BE Regression && UT Coverage Report

Increment line coverage 54.64% (1031/1887) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.42% (21553/36272)
Line Coverage 42.49% (212688/500553)
Region Coverage 39.58% (173134/437407)
Branch Coverage 40.15% (73399/182830)

@hello-stephen
Copy link
Contributor

FE Regression Coverage Report

Increment line coverage 52.74% (1087/2061) 🎉
Increment coverage report
Complete coverage report

@doris-robot
Copy link

TPC-DS: Total hot run time: 169222 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 77792669a793090cf2836eea29c40a623e12f99a, data reload: false

query5	4342	624	518	518
query6	350	253	218	218
query7	4234	490	276	276
query8	352	248	234	234
query9	8710	2758	2744	2744
query10	543	396	388	388
query11	7001	5111	4868	4868
query12	184	130	127	127
query13	1306	459	349	349
query14	5801	3792	3489	3489
query14_1	2949	2893	2895	2893
query15	206	197	180	180
query16	976	466	453	453
query17	902	732	640	640
query18	2443	448	349	349
query19	252	219	195	195
query20	146	131	130	130
query21	216	138	111	111
query22	13212	14150	15098	14150
query23	16601	16076	15614	15614
query23_1	15653	15688	15968	15688
query24	7650	1633	1239	1239
query24_1	1255	1213	1243	1213
query25	635	468	409	409
query26	1243	260	152	152
query27	2769	472	299	299
query28	4487	1828	1854	1828
query29	812	557	477	477
query30	299	224	191	191
query31	1052	950	861	861
query32	80	72	71	71
query33	499	340	286	286
query34	917	868	528	528
query35	662	694	590	590
query36	1061	1079	993	993
query37	136	99	87	87
query38	2957	2973	2993	2973
query39	853	821	819	819
query39_1	806	798	790	790
query40	242	154	140	140
query41	66	59	58	58
query42	259	260	257	257
query43	245	242	223	223
query44	
query45	200	191	185	185
query46	873	984	653	653
query47	2126	2129	2028	2028
query48	306	311	231	231
query49	638	473	384	384
query50	675	271	213	213
query51	4090	4047	4091	4047
query52	260	265	256	256
query53	291	340	292	292
query54	306	280	268	268
query55	100	90	86	86
query56	327	328	324	324
query57	1913	1742	1726	1726
query58	288	271	275	271
query59	2841	2969	2729	2729
query60	335	345	329	329
query61	155	151	153	151
query62	649	596	544	544
query63	324	288	279	279
query64	5016	1268	1005	1005
query65	
query66	1455	470	364	364
query67	24268	24364	24253	24253
query68	
query69	405	317	297	297
query70	1000	978	958	958
query71	348	314	331	314
query72	2854	2700	2412	2412
query73	555	544	322	322
query74	9659	9590	9435	9435
query75	2865	2799	2506	2506
query76	2295	1066	688	688
query77	370	390	320	320
query78	11040	11157	10480	10480
query79	1968	768	580	580
query80	1449	630	553	553
query81	534	264	225	225
query82	972	157	118	118
query83	353	260	246	246
query84	298	117	98	98
query85	920	519	458	458
query86	405	309	321	309
query87	3120	3224	3008	3008
query88	3613	2675	2651	2651
query89	447	371	356	356
query90	2050	198	188	188
query91	169	163	138	138
query92	77	73	69	69
query93	1011	859	504	504
query94	650	333	299	299
query95	591	347	327	327
query96	643	520	232	232
query97	2504	2471	2394	2394
query98	241	221	223	221
query99	1025	985	912	912
Total cold run time: 251545 ms
Total hot run time: 169222 ms

@doris-robot
Copy link

BE UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 52.77% (19868/37649)
Line Coverage 36.34% (185954/511718)
Region Coverage 32.60% (144127/442041)
Branch Coverage 33.75% (63062/186826)

@hello-stephen
Copy link
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100% (0/0) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 71.57% (26377/36853)
Line Coverage 54.51% (277997/510010)
Region Coverage 51.80% (231077/446090)
Branch Coverage 53.13% (99526/187310)

@kaka11chen kaka11chen marked this pull request as ready for review March 23, 2026 02:17
@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch from 7779266 to cf68d86 Compare March 23, 2026 06:32
@kaka11chen
Copy link
Contributor Author

run buildall

@hello-stephen
Copy link
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.63% (1796/2284)
Line Coverage 64.35% (32261/50130)
Region Coverage 65.26% (16159/24760)
Branch Coverage 55.69% (8607/15456)

@doris-robot
Copy link

TPC-H: Total hot run time: 26972 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit cf68d869765fb27561f267e3c388c8ff3fce278e, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17660	4411	4288	4288
q2	q3	10650	813	538	538
q4	4678	367	261	261
q5	7561	1214	1029	1029
q6	181	174	149	149
q7	784	861	673	673
q8	9305	1551	1373	1373
q9	4909	4796	4725	4725
q10	6258	1943	1619	1619
q11	455	266	247	247
q12	721	583	462	462
q13	18053	2959	2163	2163
q14	229	235	209	209
q15	q16	719	759	685	685
q17	722	858	430	430
q18	6087	5501	5326	5326
q19	1108	989	635	635
q20	553	510	374	374
q21	4393	1867	1471	1471
q22	537	353	315	315
Total cold run time: 95563 ms
Total hot run time: 26972 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4855	4608	4597	4597
q2	q3	3882	4369	3800	3800
q4	907	1190	763	763
q5	4098	4426	4419	4419
q6	193	183	139	139
q7	1779	1716	1546	1546
q8	2510	2768	2641	2641
q9	7758	7618	7367	7367
q10	3808	4141	3655	3655
q11	513	444	414	414
q12	508	576	456	456
q13	2832	3142	2421	2421
q14	284	299	268	268
q15	q16	742	762	715	715
q17	1179	1377	1315	1315
q18	7129	6905	6526	6526
q19	887	964	935	935
q20	2141	2143	2027	2027
q21	3905	3499	3469	3469
q22	449	449	398	398
Total cold run time: 50359 ms
Total hot run time: 47871 ms

@doris-robot
Copy link

TPC-DS: Total hot run time: 168566 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit cf68d869765fb27561f267e3c388c8ff3fce278e, data reload: false

query5	4356	650	516	516
query6	336	238	219	219
query7	4240	490	276	276
query8	358	247	235	235
query9	8729	2715	2732	2715
query10	490	388	359	359
query11	7007	5099	4898	4898
query12	182	128	127	127
query13	1275	457	350	350
query14	5738	3730	3433	3433
query14_1	2819	2824	2785	2785
query15	205	199	181	181
query16	1003	476	394	394
query17	1136	752	651	651
query18	2489	471	362	362
query19	225	214	190	190
query20	135	128	130	128
query21	214	136	113	113
query22	13191	14084	14740	14084
query23	16419	15865	15600	15600
query23_1	15686	15380	15407	15380
query24	7213	1621	1235	1235
query24_1	1234	1230	1212	1212
query25	535	472	462	462
query26	1244	261	141	141
query27	2801	479	294	294
query28	4483	1843	1822	1822
query29	823	557	481	481
query30	295	225	186	186
query31	1009	953	865	865
query32	78	76	75	75
query33	500	341	298	298
query34	899	863	508	508
query35	640	679	598	598
query36	1100	1155	1017	1017
query37	133	93	83	83
query38	2919	2916	2812	2812
query39	871	825	815	815
query39_1	796	774	784	774
query40	267	156	137	137
query41	63	61	58	58
query42	260	257	252	252
query43	234	245	226	226
query44	
query45	193	189	182	182
query46	879	989	610	610
query47	2129	2115	2068	2068
query48	304	309	226	226
query49	630	451	390	390
query50	690	270	206	206
query51	4151	4096	4033	4033
query52	261	269	259	259
query53	285	330	292	292
query54	311	273	272	272
query55	100	92	84	84
query56	317	328	315	315
query57	1928	1755	1671	1671
query58	279	284	274	274
query59	2833	2927	2743	2743
query60	342	344	330	330
query61	158	154	160	154
query62	633	600	527	527
query63	309	284	285	284
query64	5010	1290	1006	1006
query65	
query66	1442	453	391	391
query67	24249	24552	24153	24153
query68	
query69	404	315	292	292
query70	888	962	976	962
query71	339	315	304	304
query72	2935	2940	2756	2756
query73	542	538	324	324
query74	9697	9546	9444	9444
query75	2896	2762	2458	2458
query76	2258	1025	674	674
query77	370	371	314	314
query78	10965	11099	10428	10428
query79	1111	775	580	580
query80	724	618	560	560
query81	483	273	224	224
query82	1355	154	120	120
query83	375	273	252	252
query84	251	114	98	98
query85	871	505	458	458
query86	376	313	293	293
query87	3190	3077	3019	3019
query88	3522	2635	2642	2635
query89	424	362	339	339
query90	1980	180	180	180
query91	178	165	145	145
query92	83	76	73	73
query93	927	857	496	496
query94	460	307	299	299
query95	590	344	319	319
query96	655	531	230	230
query97	2480	2462	2412	2412
query98	246	220	214	214
query99	1022	993	943	943
Total cold run time: 249212 ms
Total hot run time: 168566 ms

@morningman
Copy link
Contributor

/review

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: feature Implements iceberg update delete merge into

This is a large feature PR adding Iceberg UPDATE/DELETE/MERGE INTO support across FE, BE, and Thrift layers. Below is a checkpoint-by-checkpoint review per the code-review skill.


Critical Checkpoint Conclusions

Goal & Test Coverage: The feature adds position-delete-based UPDATE/DELETE/MERGE INTO for Iceberg tables. The architecture is sound (merge partitioner for dual insert+delete routing, position delete file writer, RowDelta commit). However, regression test coverage is minimal — only a basic MERGE INTO scenario is tested. Standalone DELETE, UPDATE, MERGE edge cases (empty source, all-matched, all-unmatched, WHEN MATCHED THEN DELETE) are missing.

Minimal & Focused: Reasonable for a feature of this scope. Some thrift field duplication (TIcebergMergeSink copies 12 fields from TIcebergTableSink instead of composing).

Concurrency: IcebergTransaction has inconsistent synchronization — commitDataList is synchronized when adding but unsynchronized when reading in getUpdateCnt(), updateManifestAfterDelete(), etc. ConnectContext.icebergRowIdTargetTableId relies on single-threaded planning assumption — should be documented.

Lifecycle: No circular references or SIOF issues found.

Configuration: icebergWriteTargetFileSizeBytes session variable missing description annotation. enableIcebergMergePartitioning defaults to true — verify this is intentional as default-on.

Incompatible Changes: New thrift enum values (ICEBERG_DELETE_SINK, ICEBERG_MERGE_SINK, MERGE_PARTITIONED) require BE upgrade before FE during rolling upgrades.

Parallel Code Paths: SessionVariable.needIcebergRowId is dead code — never set by any production path. Should be removed.

Data Writes: Merge sink approach (position delete + data insert via RowDelta) is architecturally correct. Transaction rollback for DELETE/MERGE is a no-op — written but uncommitted files are not cleaned up.

FE-BE Variable Passing: setIcebergRowIdTargetTableId is called in all command paths (DELETE, UPDATE, MERGE, EXPLAIN). Save/restore pattern is consistent.

Observability: Adequate logging and profile counters in BE sinks.


Issues Found (ordered by severity)

# Severity Location Description
1 CRITICAL merge_partitioner.cpp:240 Self-referential insert_from causes use-after-free for string columns
2 CRITICAL merge_partitioner.cpp:245 Out-of-bounds access on insert_hashes when _insert_random is true
3 HIGH IcebergTransaction.java:161 NPE when table has no snapshot (empty table) in beginRewrite()
4 HIGH IcebergTransaction.java:261-267 Format version v2 check is commented out — operating on v1 tables will fail
5 HIGH IcebergRowId.java:42-45 createHiddenColumn() returns shared mutable singleton — concurrent mutation risk
6 MEDIUM iceberg_partition_function.cpp:209 Const columns skipped in hash computation — different const values across blocks route incorrectly
7 MEDIUM viceberg_delete_sink.cpp:478-480 UUID truncated to 8 chars — collision risk in concurrent environments
8 MEDIUM IcebergTransaction.java:523-534 getUpdateCnt() returns misleading counts for mixed INSERT+DELETE operations
9 LOW Multiple BE files Missing compile_check_begin.h/compile_check_end.h in new source files
10 LOW viceberg_delete_sink.h:112 Unused member _writers — dead code

See inline comments for details on the critical and high severity issues.

op_values[row] = kUpdateDeleteOperation;
for (size_t col_idx = 0; col_idx < mutable_columns.size(); ++col_idx) {
mutable_columns[col_idx]->insert_from(*mutable_columns[col_idx], row);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Use-after-free (self-referential insert_from)

When op == kUpdateOperation, this line duplicates a row from mutable_columns[col_idx] back into the same column:

mutable_columns[col_idx]->insert_from(*mutable_columns[col_idx], row);

For ColumnString, insert_from calls chars.resize(new_size) which can reallocate the internal buffer, then reads from src.chars[offset] — but src IS *this, so the source pointer is now dangling. This is a classic self-referential container insertion bug that causes use-after-free / data corruption.

Fix: Clone each row's data into a temporary Block or row-holder first, then append from the temporary. Alternatively, collect all update rows' indices first, then bulk-duplicate them from a snapshot of the original columns.

op_values[new_row_idx] = kUpdateInsertOperation;
const uint32_t insert_channel =
_insert_random ? _next_rr_channel() : insert_hashes[row];
_channel_ids.push_back(insert_channel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Out-of-bounds vector access when _insert_random is true

insert_hashes is only populated when has_insert && !_insert_random (line 171). However, when op == kUpdateOperation, this line accesses insert_hashes[row] unconditionally via the ternary's false branch. If _insert_random is true, insert_hashes is empty, causing undefined behavior (out-of-bounds read).

While the ternary condition _insert_random ? _next_rr_channel() : insert_hashes[row] appears to short-circuit, C++ does NOT short-circuit ternary operands — both branches may be evaluated depending on compiler optimization. Actually, the ternary DOES short-circuit (only the selected branch is evaluated), but this relies on the compiler not speculatively evaluating the false branch. The real concern is: if _insert_random can ever be false for some rows but true for the function as a whole, or if future refactoring changes the branching logic.

Correction: On re-analysis, the C++ ternary operator does guarantee only one branch is evaluated. The condition _insert_random is a member variable that doesn't change during the loop. So if _insert_random is true, insert_hashes[row] is never evaluated. This is technically safe but fragile — consider guarding with a DCHECK(!_insert_random || insert_hashes.empty()) or populating insert_hashes unconditionally.

@@ -226,6 +249,210 @@
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH: NullPointerException on empty Iceberg table

table.currentSnapshot() returns null for a newly created table with no data. This line will throw NPE:

this.startingSnapshotId = table.currentSnapshot().snapshotId();

Note that getSnapshotIdIfPresent() (line 587) already handles this case correctly. Consider using:

this.startingSnapshotId = getSnapshotIdIfPresent(table);

this.table = IcebergUtils.getIcebergTable(dorisTable);
this.baseSnapshotId = getSnapshotIdIfPresent(table);
// // Verify table format version (must be v2+ for delete support)
// String formatVersionStr = table.properties().get("format-version");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH: Format version v2 check is commented out

Position delete files require Iceberg table format version >= 2. This validation is commented out. Operating against a v1 table will lead to runtime errors from the Iceberg library when attempting to create position delete files, or potentially silent data corruption.

This check should be active, or at minimum there should be a clear comment explaining why it's deferred (e.g., if the Iceberg library itself validates this).

}
}
return dataRows > 0 ? dataRows : deleteRows;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM: getUpdateCnt() returns misleading row counts

For a MERGE/UPDATE that produces both data files (inserts) and delete files:

return dataRows > 0 ? dataRows : deleteRows;

This reports only insert rows for mixed operations. For an UPDATE of 100 rows (which produces 100 position deletes + 100 new inserts), the user sees "100 rows affected" but the 100 deletes are invisible. For a pure DELETE operation, only delete rows are reported. The semantics should be documented, or consider returning Math.max(dataRows, deleteRows) or a separate count for each operation type.

_partition_fields[i].transformer->apply(*block, results[i]);
const auto& [column, is_const] = unpack_if_const(transformed.column);
if (is_const) {
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM: Const columns skipped in hash computation

When a partition column is const (same value for all rows in a block), its hash contribution is skipped entirely (continue). If different blocks have different const values for the same partition column, rows that should go to different partitions will hash identically, causing incorrect data routing.

For example: block A has partition_col = 'a' (const), block B has partition_col = 'b' (const). Both blocks will produce the same hash values since the partition column's contribution is skipped.

Consider materializing const columns before hashing, or computing the const value's hash contribution once and applying it to all rows.

}

std::string VIcebergDeleteSink::_generate_delete_file_path(
const std::string& referenced_data_file) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM: UUID truncated to 8 characters — collision risk

The generated delete file name uses uuid.substr(0, 8) which is only 8 hex characters (32 bits of entropy). Combined with std::hash % 10000000, there's a non-trivial collision risk in high-throughput concurrent environments where many delete files are written simultaneously.

Consider using the full UUID or at least a significantly longer substring (e.g., 16+ characters).

Copy link
Contributor

@morningman morningman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following test scenarios should be added:

  • Error handling when executing DELETE/UPDATE on Iceberg v1 tables
  • Conflict detection for concurrent DELETE/UPDATE operations
  • DELETE/UPDATE with subqueries
  • UPDATE SET clause with complex expressions
  • MERGE INTO edge cases: matched-only, not-matched-only, multiple WHEN clauses
  • DELETE/UPDATE after Schema Evolution

+ "to exclude the impact of dangling delete files."})
public boolean ignoreIcebergDanglingDelete = false;

@VariableMgr.VarAttr(name = ENABLE_ICEBERG_MERGE_PARTITIONING,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove chinese description

)
public boolean showHiddenColumns = false;

// 内部变量,保留兼容性;实际由 ConnectContext.needIcebergRowId 控制
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove


if (_delete_type == TFileContent::POSITION_DELETES && !_file_deletions.empty()) {
SCOPED_TIMER(_write_delete_files_timer);
RETURN_IF_ERROR(_write_position_delete_files(_file_deletions));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All file deletions will be hold in memory and write to disk until close().
There will be potential memory issue if there are lots for delete files.

if (targetAlias.isPresent()) {
targetPlan = new LogicalSubQueryAlias<>(targetAlias.get(), targetPlan);
}
return new LogicalJoin<>(JoinType.LEFT_OUTER_JOIN,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using INNER JOIN when there is no WHEN NOT MATCHED clause, for better performance.

return icebergRowIdTargetTableId;
}

/** @deprecated Use setIcebergRowIdTargetTableId instead. Kept for backward compat. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a newly added method and marked as @deprecated?

@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch from cf68d86 to ea86acb Compare March 24, 2026 14:05
@kaka11chen
Copy link
Contributor Author

run buildall

@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch from ea86acb to e5b7339 Compare March 24, 2026 14:36
@doris-robot
Copy link

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.70% (1796/2282)
Line Coverage 64.49% (32316/50110)
Region Coverage 65.36% (16180/24754)
Branch Coverage 55.81% (8623/15450)

namespace doris {

namespace {
constexpr int8_t kInsertOperation = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both viceberg_merge_sink and merge_partitioner have these definitions; consider whether to combine them. kInsertOperation, _is_delete_op ...


if (_need_row_id_column) {
if (auto* parquet_reader = dynamic_cast<ParquetReader*>(_file_format_reader.get())) {
parquet_reader->set_iceberg_rowid_params(_current_file_path, _partition_spec_id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_iceberg_rowid_params can called at IcebergParquetReader / IcebergOrcReader init_reader.

partition_data_json = table_desc.partition_data_json;
}

set_current_file_info(file_path, partition_spec_id, partition_data_json);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The set_current_file_info method seems somewhat unnecessary.

/// the value is backfilled at runtime (e.g. Iceberg V3 _row_id).
/// - INTERNAL: Read from the data file but consumed internally by the TableReader
/// and removed from the output block (e.g. Hive ACID system columns).
enum class ColumnCategory {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this.

if (_need_iceberg_rowid_column && _current_range.__isset.table_format_params &&
_current_range.table_format_params.table_format_type == "iceberg") {
if (auto* iceberg_reader = dynamic_cast<IcebergTableReader*>(_cur_reader.get())) {
iceberg_reader->set_row_id_column_position(_iceberg_rowid_column_pos);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to _get_next_reader or _init_parquet_reader

if (_t_sink.iceberg_merge_sink.__isset.schema_json) {
try {
std::unique_ptr<iceberg::Schema> schema =
iceberg::SchemaParser::from_json(_t_sink.iceberg_merge_sink.schema_json);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that VIcebergTableWriter::open will perform a SchemaParser::from_json check again.

partition_data_col.reserve(num_rows);

for (size_t i = 0; i < num_rows; ++i) {
file_path_col.insert_data(file_path.data(), file_path.size());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can ColumnConst be used?

if (_position_delete_ordered_rowids == nullptr) {
return;
}
auto start = _row_reader->getRowNumber();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for performance or correctness reasons?

@Override
public List<Column> getBaseSchema(boolean full) {
return getFullSchema();
List<Column> schema = getFullSchema();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand this logic. I guess you want to express:
fe/fe-core/src/main/java/org/apache/doris/catalog/Table.java
public List<Column> getBaseSchema(boolean full)

@kaka11chen
Copy link
Contributor Author

run buildall

@doris-robot
Copy link

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.67% (1796/2283)
Line Coverage 64.42% (32293/50128)
Region Coverage 65.30% (16170/24761)
Branch Coverage 55.76% (8617/15454)

@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch from fd3d9dd to 1d993ac Compare March 25, 2026 13:35
@kaka11chen
Copy link
Contributor Author

run buildall

1 similar comment
@kaka11chen
Copy link
Contributor Author

run buildall

@doris-robot
Copy link

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.67% (1796/2283)
Line Coverage 64.35% (32259/50128)
Region Coverage 65.26% (16160/24761)
Branch Coverage 55.67% (8603/15454)

@doris-robot
Copy link

TPC-H: Total hot run time: 26802 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 1d993acaf4c5204fa641236167ffc6c8d8cdc9b6, data reload: false

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17629	4479	4287	4287
q2	q3	10725	812	522	522
q4	4718	359	256	256
q5	8215	1229	1038	1038
q6	252	169	145	145
q7	809	865	670	670
q8	10965	1504	1343	1343
q9	6794	4835	4757	4757
q10	6329	1941	1630	1630
q11	454	262	242	242
q12	743	592	472	472
q13	18054	2755	1961	1961
q14	235	242	218	218
q15	q16	725	754	676	676
q17	726	860	438	438
q18	6085	5312	5312	5312
q19	1133	994	622	622
q20	537	496	379	379
q21	4688	2015	1550	1550
q22	379	320	284	284
Total cold run time: 100195 ms
Total hot run time: 26802 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4740	4745	4659	4659
q2	q3	3986	4503	3829	3829
q4	861	1207	806	806
q5	4081	4433	4285	4285
q6	184	187	141	141
q7	1787	1687	1576	1576
q8	2532	2753	2612	2612
q9	7694	7418	7418	7418
q10	3804	4055	3591	3591
q11	509	435	451	435
q12	582	591	464	464
q13	2912	3180	2146	2146
q14	304	305	284	284
q15	q16	751	795	721	721
q17	1244	1388	1399	1388
q18	7227	6789	6613	6613
q19	878	908	912	908
q20	2071	2151	2122	2122
q21	4066	3629	3338	3338
q22	469	466	395	395
Total cold run time: 50682 ms
Total hot run time: 47731 ms

@doris-robot
Copy link

TPC-DS: Total hot run time: 169529 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 1d993acaf4c5204fa641236167ffc6c8d8cdc9b6, data reload: false

query5	4348	618	512	512
query6	341	235	214	214
query7	4223	469	268	268
query8	383	251	238	238
query9	8744	2724	2698	2698
query10	504	388	345	345
query11	7005	5116	4939	4939
query12	182	127	125	125
query13	1286	453	334	334
query14	5751	3703	3391	3391
query14_1	2810	2793	2764	2764
query15	203	191	175	175
query16	974	467	466	466
query17	868	703	609	609
query18	2434	442	339	339
query19	233	208	179	179
query20	130	126	128	126
query21	214	133	110	110
query22	13272	13951	15137	13951
query23	16615	16266	16441	16266
query23_1	16282	16068	16023	16023
query24	7132	1611	1226	1226
query24_1	1220	1215	1237	1215
query25	538	482	409	409
query26	1250	260	148	148
query27	2781	474	293	293
query28	4520	1841	1826	1826
query29	851	563	474	474
query30	298	221	196	196
query31	1016	956	865	865
query32	80	70	70	70
query33	502	333	280	280
query34	902	870	522	522
query35	641	679	582	582
query36	1071	1151	984	984
query37	128	92	82	82
query38	2961	2955	2928	2928
query39	858	834	826	826
query39_1	791	789	793	789
query40	232	151	134	134
query41	62	58	57	57
query42	263	254	257	254
query43	242	251	218	218
query44	
query45	198	199	187	187
query46	882	988	612	612
query47	2134	2137	2045	2045
query48	313	327	236	236
query49	653	467	394	394
query50	679	298	223	223
query51	4062	4046	4008	4008
query52	266	267	255	255
query53	293	345	292	292
query54	314	287	280	280
query55	90	90	84	84
query56	349	334	334	334
query57	1951	1700	1698	1698
query58	317	282	273	273
query59	2795	2955	2762	2762
query60	350	361	346	346
query61	190	180	183	180
query62	628	601	549	549
query63	316	283	282	282
query64	5226	1392	1112	1112
query65	
query66	1465	439	375	375
query67	24231	24239	24214	24214
query68	
query69	403	311	289	289
query70	989	987	960	960
query71	329	306	300	300
query72	2872	2719	2479	2479
query73	541	543	317	317
query74	9632	9549	9393	9393
query75	2851	2731	2442	2442
query76	2310	1029	696	696
query77	365	416	305	305
query78	11054	11114	10445	10445
query79	1182	755	559	559
query80	1292	641	555	555
query81	546	259	221	221
query82	1027	153	113	113
query83	331	269	245	245
query84	300	117	99	99
query85	904	490	455	455
query86	417	308	335	308
query87	3149	3132	2991	2991
query88	3533	2663	2642	2642
query89	424	372	343	343
query90	2017	185	178	178
query91	175	173	138	138
query92	78	81	69	69
query93	986	828	493	493
query94	636	302	295	295
query95	585	349	321	321
query96	634	510	224	224
query97	2454	2497	2410	2410
query98	231	227	219	219
query99	985	994	927	927
Total cold run time: 250090 ms
Total hot run time: 169529 ms

@kaka11chen kaka11chen force-pushed the iceberg_update_delete_merge_into branch from 1d993ac to 561d1fd Compare March 25, 2026 16:00
@kaka11chen
Copy link
Contributor Author

run buildall

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants