Skip to content

[fix](iceberg) Enforce external write correctness - #66112

Merged
Gabriel39 merged 8 commits into
apache:masterfrom
Gabriel39:agent/fix-external-write-correctness
Jul 29, 2026
Merged

[fix](iceberg) Enforce external write correctness#66112
Gabriel39 merged 8 commits into
apache:masterfrom
Gabriel39:agent/fix-external-write-correctness

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

  • Validate unsupported CTAS sinks before publishing table metadata, so setup failures cannot leave an unusable table or remove a concurrent replacement.
  • Preserve nullable string wrappers in truncate partition transforms.
  • Enforce Iceberg MERGE cardinality routing independently of enable_strict_consistency_dml.
  • Detect duplicate target matches with file-path-interned Roaring bitmaps and expose retained validation-state bytes in the sink profile.
  • Activate the negative regressions for the three retained Jira fixes and align the duplicate-match oracle with the emitted diagnostic.

Test

  • Full build: ./build.sh --fe --be -j 48
  • FE: CreateTableCommandTest, IcebergDDLAndDMLPlanTest (19 tests)
  • BE: VIcebergMergeSinkTest (8 tests, including 100,000 matched rows)
  • Regression: test_paimon_ctas_atomicity_negative (1 suite, passed)
  • Iceberg end-to-end SQL: duplicate-source MERGE rejected atomically; nullable/merge truncate writes and logical rows verified; physical partition values verified from the same Iceberg tables through Spark metadata.

The Doris-side $partitions/$snapshots checks in the Iceberg suites currently hit an unrelated JNI scanner initialization abort on the current master test binary; the equivalent table state and metadata assertions above passed.

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

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?

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Aug 2nd, 2026 1:27 AM.
Workflow run: https://github.com/apache/doris/actions/runs/30257698169

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17590	4133	4392	4133
q2	2044	325	206	206
q3	10300	1436	820	820
q4	4716	485	341	341
q5	7636	853	572	572
q6	209	171	139	139
q7	756	835	620	620
q8	10258	1642	1675	1642
q9	6028	4352	4386	4352
q10	6842	1776	1471	1471
q11	526	359	335	335
q12	788	574	459	459
q13	18083	3289	2790	2790
q14	264	265	248	248
q15	q16	786	777	713	713
q17	1090	1020	1096	1020
q18	6951	5922	5505	5505
q19	1413	1343	1129	1129
q20	822	708	663	663
q21	6301	2792	2784	2784
q22	463	379	315	315
Total cold run time: 103866 ms
Total hot run time: 30257 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5132	4911	4949	4911
q2	302	336	227	227
q3	4932	5233	4695	4695
q4	2133	2181	1378	1378
q5	5023	4629	4814	4629
q6	238	179	129	129
q7	1916	1757	1613	1613
q8	2438	2151	2092	2092
q9	7332	7155	7184	7155
q10	4594	4558	4129	4129
q11	595	390	361	361
q12	740	740	544	544
q13	3166	3364	2769	2769
q14	281	293	264	264
q15	q16	685	710	628	628
q17	1316	1285	1262	1262
q18	7324	7055	7054	7054
q19	1207	1163	1103	1103
q20	2234	2221	1940	1940
q21	5279	4637	4413	4413
q22	524	458	395	395
Total cold run time: 57391 ms
Total hot run time: 51691 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 177986 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 6560153b697d245b78470346bff86baa58e00639, data reload: false

query5	4367	630	494	494
query6	463	221	216	216
query7	4856	609	341	341
query8	338	188	176	176
query9	8786	4211	4181	4181
query10	454	379	302	302
query11	5967	2347	2100	2100
query12	161	105	102	102
query13	1296	636	427	427
query14	6254	5261	4872	4872
query14_1	4260	4264	4242	4242
query15	204	206	177	177
query16	1052	488	510	488
query17	1144	741	617	617
query18	2741	478	363	363
query19	221	193	157	157
query20	119	108	110	108
query21	245	168	140	140
query22	13524	13622	13378	13378
query23	17407	16398	16075	16075
query23_1	16258	16247	16215	16215
query24	7508	1763	1298	1298
query24_1	1344	1309	1312	1309
query25	570	504	404	404
query26	1320	370	214	214
query27	2535	633	389	389
query28	4422	2059	2031	2031
query29	1097	627	510	510
query30	346	266	230	230
query31	1118	1091	979	979
query32	117	67	62	62
query33	534	337	272	272
query34	1177	1188	680	680
query35	795	792	686	686
query36	1169	1174	1073	1073
query37	161	109	96	96
query38	1956	1754	1642	1642
query39	883	873	864	864
query39_1	858	837	842	837
query40	243	171	153	153
query41	65	66	68	66
query42	93	94	92	92
query43	322	324	278	278
query44	1493	794	758	758
query45	194	183	176	176
query46	1114	1188	748	748
query47	2098	2113	2031	2031
query48	418	425	304	304
query49	583	425	338	338
query50	1102	430	342	342
query51	10861	10516	10173	10173
query52	91	105	83	83
query53	276	281	207	207
query54	290	255	237	237
query55	75	69	65	65
query56	311	287	297	287
query57	1361	1305	1211	1211
query58	286	263	277	263
query59	1610	1722	1485	1485
query60	310	276	258	258
query61	162	154	148	148
query62	537	492	430	430
query63	243	204	207	204
query64	2808	1089	899	899
query65	4577	4512	4585	4512
query66	1785	573	409	409
query67	29246	29213	29007	29007
query68	3094	1687	1088	1088
query69	430	306	259	259
query70	1078	1079	978	978
query71	372	354	325	325
query72	3082	2748	2483	2483
query73	920	883	460	460
query74	5050	4880	4656	4656
query75	2538	2507	2156	2156
query76	2371	1183	772	772
query77	355	384	283	283
query78	11957	11947	11279	11279
query79	1429	1230	738	738
query80	1281	577	488	488
query81	540	347	290	290
query82	635	158	121	121
query83	371	323	299	299
query84	271	168	133	133
query85	999	654	526	526
query86	425	310	296	296
query87	1827	1829	1767	1767
query88	3838	2826	2850	2826
query89	438	378	335	335
query90	2067	212	199	199
query91	202	195	166	166
query92	63	60	55	55
query93	1783	1557	985	985
query94	765	359	315	315
query95	784	492	569	492
query96	1072	876	371	371
query97	2622	2618	2496	2496
query98	224	213	220	213
query99	1096	1112	937	937
Total cold run time: 264457 ms
Total hot run time: 177986 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 25.23 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 6560153b697d245b78470346bff86baa58e00639, data reload: false

query1	0.01	0.01	0.00
query2	0.10	0.04	0.04
query3	0.25	0.14	0.14
query4	1.61	0.14	0.14
query5	0.24	0.22	0.24
query6	1.27	1.11	1.09
query7	0.04	0.00	0.00
query8	0.06	0.03	0.04
query9	0.38	0.33	0.33
query10	0.54	0.55	0.55
query11	0.21	0.14	0.13
query12	0.18	0.15	0.14
query13	0.49	0.46	0.47
query14	1.01	1.01	1.00
query15	0.63	0.59	0.60
query16	0.31	0.31	0.33
query17	1.11	1.14	1.17
query18	0.25	0.22	0.24
query19	2.19	1.96	1.99
query20	0.01	0.01	0.01
query21	15.35	0.18	0.13
query22	5.00	0.05	0.04
query23	16.09	0.30	0.12
query24	2.93	0.42	0.33
query25	0.10	0.05	0.05
query26	0.74	0.20	0.14
query27	0.04	0.04	0.03
query28	3.51	0.91	0.53
query29	12.54	4.13	3.33
query30	0.29	0.15	0.15
query31	2.77	0.60	0.31
query32	3.23	0.59	0.49
query33	3.17	3.26	3.19
query34	15.62	4.24	3.57
query35	3.55	3.55	3.52
query36	0.55	0.43	0.44
query37	0.09	0.07	0.07
query38	0.04	0.04	0.03
query39	0.04	0.02	0.02
query40	0.18	0.16	0.17
query41	0.08	0.03	0.03
query42	0.05	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.89 s
Total hot run time: 25.23 s

@Gabriel39
Gabriel39 marked this pull request as ready for review July 27, 2026 11:25
@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 100.00% (4/4) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 78.08% (57/73) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 58.14% (24642/42385)
Line Coverage 42.23% (246461/583619)
Region Coverage 38.12% (195827/513677)
Branch Coverage 39.23% (88351/225240)

Keep generic external columns nullable until the Iceberg-specific read and write schema handling from PR apache#65851 is available.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review context preparation failed before Codex ran; inspect the 'Prepare authoritative PR context and required AGENTS guides' step.
Workflow run: https://github.com/apache/doris/actions/runs/30262896835

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Aug 2nd, 2026 1:27 AM.
Workflow run: https://github.com/apache/doris/actions/runs/30265315356

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17927	4205	4058	4058
q2	2269	326	201	201
q3	10308	1390	833	833
q4	4687	467	337	337
q5	7707	839	575	575
q6	192	176	144	144
q7	771	815	604	604
q8	10102	1489	1498	1489
q9	6523	4291	4296	4291
q10	7705	1703	1440	1440
q11	519	359	319	319
q12	774	595	466	466
q13	18250	3336	2753	2753
q14	268	257	241	241
q15	q16	779	773	706	706
q17	1045	897	1135	897
q18	6984	5674	5549	5549
q19	1405	1258	1117	1117
q20	846	734	571	571
q21	5674	2613	2304	2304
q22	440	352	301	301
Total cold run time: 105175 ms
Total hot run time: 29196 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4489	4464	4409	4409
q2	300	322	213	213
q3	4589	4990	4433	4433
q4	2063	2168	1362	1362
q5	4386	4241	4321	4241
q6	354	204	148	148
q7	2043	1781	1642	1642
q8	2444	2104	2082	2082
q9	7880	7714	7704	7704
q10	4703	4673	4416	4416
q11	576	409	400	400
q12	774	759	565	565
q13	3253	3732	2942	2942
q14	319	304	288	288
q15	q16	711	740	682	682
q17	1344	1327	1322	1322
q18	7986	7528	6928	6928
q19	1136	1069	1072	1069
q20	2208	2205	1926	1926
q21	5209	4533	4387	4387
q22	498	458	397	397
Total cold run time: 57265 ms
Total hot run time: 51556 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 177718 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 72b2ba671b37fdf5eb15dab93e4c9ce1149508b5, data reload: false

query5	4323	637	493	493
query6	455	217	196	196
query7	4886	593	361	361
query8	335	190	170	170
query9	8803	4063	4018	4018
query10	469	367	323	323
query11	5844	2349	2115	2115
query12	160	130	99	99
query13	1240	541	438	438
query14	6163	5171	4855	4855
query14_1	4248	4221	4220	4220
query15	217	203	175	175
query16	997	457	434	434
query17	1100	684	566	566
query18	2431	463	336	336
query19	204	184	145	145
query20	109	109	106	106
query21	230	161	141	141
query22	13650	13616	13425	13425
query23	17424	16406	16039	16039
query23_1	16213	16326	16356	16326
query24	7384	1752	1289	1289
query24_1	1316	1317	1267	1267
query25	560	469	384	384
query26	870	352	224	224
query27	2547	615	388	388
query28	4491	1998	1989	1989
query29	1067	614	509	509
query30	348	262	229	229
query31	1115	1091	968	968
query32	124	65	63	63
query33	530	322	255	255
query34	1164	1142	664	664
query35	767	793	673	673
query36	1190	1163	1068	1068
query37	148	116	97	97
query38	1888	1695	1683	1683
query39	887	889	857	857
query39_1	838	836	833	833
query40	241	178	147	147
query41	71	70	69	69
query42	97	94	93	93
query43	327	327	282	282
query44	1420	786	780	780
query45	194	193	175	175
query46	1031	1196	731	731
query47	2184	2126	2079	2079
query48	417	407	296	296
query49	558	411	302	302
query50	1121	424	322	322
query51	10525	10866	10593	10593
query52	88	87	74	74
query53	264	275	199	199
query54	274	233	230	230
query55	73	70	65	65
query56	287	309	290	290
query57	1297	1277	1179	1179
query58	282	258	266	258
query59	1572	1633	1385	1385
query60	301	272	250	250
query61	147	144	140	140
query62	555	501	428	428
query63	245	202	206	202
query64	2267	1046	847	847
query65	4705	4656	4671	4656
query66	1790	501	379	379
query67	29213	29233	29075	29075
query68	3157	1555	1000	1000
query69	408	292	257	257
query70	1039	947	958	947
query71	384	333	321	321
query72	3069	2672	2330	2330
query73	850	734	441	441
query74	5083	4933	4695	4695
query75	2539	2490	2120	2120
query76	2319	1158	752	752
query77	357	369	279	279
query78	11861	11898	11297	11297
query79	1401	1185	737	737
query80	1281	547	470	470
query81	521	351	292	292
query82	631	156	122	122
query83	405	339	296	296
query84	286	163	133	133
query85	960	644	511	511
query86	430	290	270	270
query87	1830	1830	1742	1742
query88	3742	2799	2800	2799
query89	442	376	331	331
query90	1875	199	194	194
query91	201	193	162	162
query92	61	58	57	57
query93	1642	1450	951	951
query94	720	349	315	315
query95	791	542	544	542
query96	1050	850	349	349
query97	2680	2621	2493	2493
query98	216	202	201	201
query99	1075	1116	972	972
Total cold run time: 261847 ms
Total hot run time: 177718 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 25.04 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 72b2ba671b37fdf5eb15dab93e4c9ce1149508b5, data reload: false

query1	0.01	0.01	0.01
query2	0.10	0.05	0.05
query3	0.25	0.13	0.14
query4	1.62	0.14	0.14
query5	0.24	0.23	0.22
query6	1.27	1.11	1.08
query7	0.04	0.01	0.01
query8	0.05	0.04	0.03
query9	0.39	0.31	0.31
query10	0.59	0.55	0.53
query11	0.19	0.13	0.13
query12	0.18	0.15	0.14
query13	0.48	0.46	0.48
query14	1.02	1.01	1.00
query15	0.61	0.61	0.59
query16	0.31	0.33	0.32
query17	1.13	1.10	1.10
query18	0.22	0.21	0.21
query19	2.07	1.93	1.99
query20	0.01	0.01	0.01
query21	15.44	0.21	0.13
query22	4.87	0.06	0.06
query23	16.13	0.32	0.13
query24	2.96	0.41	0.32
query25	0.11	0.04	0.04
query26	0.73	0.21	0.16
query27	0.04	0.03	0.04
query28	3.53	0.93	0.58
query29	12.47	4.13	3.30
query30	0.28	0.15	0.15
query31	2.76	0.59	0.30
query32	3.24	0.59	0.50
query33	3.16	3.17	3.20
query34	15.47	4.24	3.50
query35	3.55	3.50	3.54
query36	0.55	0.45	0.42
query37	0.09	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.17	0.15
query41	0.09	0.03	0.02
query42	0.04	0.02	0.02
query43	0.05	0.04	0.03
Total cold run time: 96.61 s
Total hot run time: 25.04 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 78.08% (57/73) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 58.13% (24638/42385)
Line Coverage 42.19% (246251/583619)
Region Coverage 38.08% (195614/513677)
Branch Coverage 39.20% (88303/225240)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 83.56% (61/73) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 75.40% (31166/41332)
Line Coverage 59.95% (347570/579789)
Region Coverage 56.65% (292026/515468)
Branch Coverage 57.95% (130573/225308)

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

924060929
924060929 previously approved these changes Jul 28, 2026
@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Jul 28, 2026
@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 82.52% (118/143) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 75.39% (31180/41359)
Line Coverage 59.91% (347599/580180)
Region Coverage 56.78% (292937/515902)
Branch Coverage 58.04% (130960/225621)

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.62% (1908/2458)
Line Coverage 64.58% (34205/52968)
Region Coverage 64.99% (17596/27076)
Branch Coverage 54.14% (9433/17424)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Request changes: this revision still has one data-file ownership gap and two coverage/diagnostic regressions. I found three new, non-duplicate issues and left the already-open review threads un-repeated.

Critical checkpoint conclusions:

  • Goal and proof: The PR substantially improves Iceberg MERGE cardinality enforcement, failed-write cleanup, nullable truncate handling, and Paimon/CTAS behavior. It does not fully prove failed-write atomicity: a dynamic writer can lose cleanup ownership after creating its object, and the new late-rollover regression does not deterministically reach the lifecycle it claims to test. Explicit-engine CTAS also gains an unknown-catalog null dereference.
  • Scope and focus: The patch is broad but its Iceberg, CTAS/Paimon, protocol, and test changes are traceable to the stated follow-up fixes. No additional user focus was supplied, so I reviewed the whole changed-file set.
  • Concurrency and thread safety: No new thread or lock primitive is introduced. SQL MERGE rows with the same target row ID are routed to the same destination fragment instance and sink-local validator. The CTAS concurrent-ownership race is already covered by an existing thread; the new unknown-catalog issue is a distinct single-command validation path.
  • Error handling: Status propagation across the revised merge close paths is generally explicit. The dynamic partition writer still has a post-create open() error path with no cleanup owner, and eager CTAS sink construction turns a normal catalog lookup error into a NullPointerException.
  • Memory safety and ownership: The compact per-file Roaring cardinality state avoids the former per-row string retention, and no new unsafe buffer lifetime was found. Physical file ownership is incomplete only on the reported dynamic writer open path.
  • Data correctness: Row-ID colocation makes duplicate detection exact across blocks for SQL MERGE, while plain UPDATE carries require_cardinality_check=false. No new snapshot, row, or partition-transform correctness issue was found beyond the reported failed-object ownership gap and existing threads.
  • BE null/nullable handling: INSERT, MERGE, and UPDATE projections materialize const wrappers before string truncate runs; the direct nullable nested column and null map remain row-aligned and the original null map is restored. I found no reachable ColumnConst(ColumnNullable(String)) failure after tracing those boundaries.
  • Lifecycle and static initialization: Deferred data/delete cleanup now survives sibling close failures and successful rollover. Static/nonpartitioned writers, sort-roll replacement, position-delete, and Puffin paths retain owners; dynamic partition creation before owner-map insertion remains the reported exception. No cross-translation-unit static initialization dependency was added.
  • Configuration: No new runtime-tunable configuration item was added. The BE execution-version maximum moves to 11 and is used as the compatibility gate; existing session settings in regressions do not require dynamic process reconfiguration.
  • Compatibility: The new thrift boolean is optional, UPDATE explicitly sends false, old FE requests omit it, and version 10 uniformly disables enforcement on new BEs. The operational mixed-version configuration concern is already raised in an existing thread and was not duplicated.
  • Parallel paths and conditions: MERGE versus UPDATE, strict-consistency modes, partitioned versus unpartitioned/static writers, position-delete versus Puffin, CTAS with and without IF NOT EXISTS, and stable/concurrent Paimon targets were traced. The two missed parallel/conditional cases are the reported dynamic open failure and explicit-engine unknown catalog.
  • Tests and expected results: The patch adds BE/FE unit coverage and activates Iceberg/Paimon regressions; the inspected expected-result changes are consistent with the SQL assertions. Coverage is still missing for post-create/pre-registration failure and explicit-engine unknown catalog, and the late-rollover object-count case can pass without reaching a closed-file state.
  • Test execution: Per the review-only instruction, I did not build Doris or run tests. Formatting/checkstyle checks were green when inspected; compile and FE/BE/Cloud unit-test checks were still pending, so this review does not claim runtime validation.
  • Observability: Existing status messages, warning logs for deletion failures, runtime profiles, and cardinality diagnostics cover the new normal paths. The reported unowned object is not visible to cleanup logging because its owner is lost before registration.
  • Transactions and persistence: No EditLog schema or replay path is changed. External catalog create/no-op semantics and failed Iceberg publication were traced; the pre-existing rollback-by-name race remains covered by an existing thread.
  • Data writes and atomicity: Outer MERGE close now retains data and delete handles until both sides succeed, but post-create dynamic writer failure can still orphan an unpublished HDFS object. FE or BE failure semantics otherwise revealed no additional new issue beyond existing threads.
  • FE/BE variable propagation: require_cardinality_check, merge partition information, and execution version are carried through logical/physical sinks, translation, thrift, runtime state, and BE construction. Missing/false/true cases were checked.
  • Performance: Operation-aware distribution avoids the prior unmatched-insert hotspot, and the cardinality structure is compact. No additional CPU, lock, or asymptotic regression was substantiated; performance concerns already raised in existing threads were not duplicated.
  • Other issues: The unknown-catalog CTAS diagnostic regression is reported inline. No other distinct issue survived final deduplication.

// to update a target row. Tiny blocks and files force a valid row to roll before
// the late duplicate, and the entire statement must still leave no data object behind.
sql """set batch_size = 1"""
// A single pipeline instance preserves the ordered source sequence needed to prove late cleanup.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Make the late-rollover sequence deterministic

parallel_pipeline_task_num=1 does not make this a single global sink. The new MERGE_PARTITIONED path sends the unmatched INSERT by its Iceberg partition while both matched UPDATE rows use the row-id channel, so the duplicate can fail in one VIcebergMergeSink before the insert channel has closed any data file. In that execution the object count still stays unchanged and this passes without exercising _closed_files cleanup. This is a distinct follow-up to the existing rollover thread: add a barrier/failpoint that confirms a data file has closed and been recorded before releasing the duplicate row, then keep the object-store assertion.

io::FileDescription file_description = {
.path = fmt::format("{}/{}", _write_info.write_path, _get_target_file_name()),
.fs_name {}};
_path = fmt::format("{}/{}", _write_info.write_path, _get_target_file_name());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Register the data file before open() can fail

_path becomes a physical file before compression validation and transformer initialization, but the dynamic-partition caller adds this writer to _partitions_to_writers only after open() succeeds. For example, an Iceberg Parquet table with write.parquet.compression-codec=gzip reaches create_file() with TFileCompressType::GZ and then returns Unsupported compress type; the local writer is destroyed before either the active-writer cleanup or this new closed-file callback owns the path. HdfsFileWriter only closes that empty file in its destructor, so a failed MERGE/UPDATE leaves it orphaned. Transfer cleanup ownership immediately after creation, or register/close the dynamic writer on open failure, and cover this post-create failure path with an object-store assertion.

}
// Reject unsupported destinations before publishing metadata; rollback by table name
// cannot distinguish this CTAS table from a concurrent replacement with the same name.
sinkQuery = UnboundTableSinkCreator.createUnboundTableSink(createTableInfo.getTableNameParts(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Validate the catalog before eager sink construction

With an explicit recognized engine, a missing catalog reaches this line: paddingEngineName() only checks catalog existence when the engine is omitted, checkEngineWithCatalog() falls through on a null catalog, and targetTableExists() returns false. createUnboundTableSink() then evaluates curCatalog.getClass() and throws a NullPointerException, whereas the former Env.createTable() ordering returned Unknown catalog. This is distinct from the existing Paimon-target diagnostic thread because neither the catalog nor the table exists here. Resolve the catalog before sink prevalidation (or make the factory report the standard error) and cover explicit-engine CTAS against an unknown catalog.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17699	4094	4063	4063
q2	2031	321	216	216
q3	10917	1493	829	829
q4	4730	469	335	335
q5	8262	851	591	591
q6	314	167	134	134
q7	840	825	615	615
q8	10307	1587	1643	1587
q9	5935	4418	4408	4408
q10	6739	1750	1451	1451
q11	515	356	321	321
q12	743	600	456	456
q13	18063	3314	2728	2728
q14	264	251	238	238
q15	q16	777	791	712	712
q17	1056	969	1025	969
q18	6772	5683	5471	5471
q19	1265	1256	1116	1116
q20	788	699	568	568
q21	5788	2707	2659	2659
q22	426	352	290	290
Total cold run time: 104231 ms
Total hot run time: 29757 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4402	4300	4311	4300
q2	291	311	212	212
q3	4546	4937	4421	4421
q4	2076	2167	1357	1357
q5	4455	4239	4270	4239
q6	232	175	125	125
q7	2349	1846	1627	1627
q8	2458	2123	2128	2123
q9	7736	7818	7818	7818
q10	4731	4638	4200	4200
q11	696	418	368	368
q12	797	755	530	530
q13	3182	3575	2905	2905
q14	300	305	288	288
q15	q16	701	735	654	654
q17	1367	1356	1351	1351
q18	7998	7371	6736	6736
q19	1082	1099	1074	1074
q20	2215	2205	1948	1948
q21	5255	4551	4368	4368
q22	511	441	400	400
Total cold run time: 57380 ms
Total hot run time: 51044 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 176889 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 8b4efe19232d1ac52712c99ae3dc22d9b713112f, data reload: false

query5	4334	633	480	480
query6	464	228	217	217
query7	4857	595	346	346
query8	358	194	173	173
query9	8763	4018	4007	4007
query10	504	369	307	307
query11	5924	2326	2132	2132
query12	160	110	101	101
query13	1266	603	423	423
query14	6248	5184	4846	4846
query14_1	4219	4211	4209	4209
query15	221	205	181	181
query16	1061	479	474	474
query17	1142	725	577	577
query18	2517	484	353	353
query19	218	192	156	156
query20	112	110	107	107
query21	236	158	141	141
query22	13534	13419	13375	13375
query23	17298	16404	16123	16123
query23_1	16304	16263	16132	16132
query24	7544	1767	1280	1280
query24_1	1303	1306	1283	1283
query25	561	462	382	382
query26	1335	353	221	221
query27	2591	606	387	387
query28	4409	1957	1953	1953
query29	1089	641	506	506
query30	332	273	232	232
query31	1118	1094	973	973
query32	109	66	59	59
query33	522	323	259	259
query34	1230	1122	635	635
query35	787	784	676	676
query36	1025	1018	883	883
query37	160	104	94	94
query38	1877	1704	1605	1605
query39	873	883	855	855
query39_1	835	837	884	837
query40	250	161	142	142
query41	62	62	62	62
query42	91	93	92	92
query43	325	328	279	279
query44	1424	758	743	743
query45	195	186	177	177
query46	1076	1177	727	727
query47	2203	2087	2040	2040
query48	421	403	290	290
query49	598	426	303	303
query50	1080	443	321	321
query51	11235	11023	10924	10924
query52	88	87	76	76
query53	264	281	201	201
query54	302	240	211	211
query55	74	68	68	68
query56	287	300	285	285
query57	1323	1293	1200	1200
query58	278	293	287	287
query59	1572	1621	1450	1450
query60	312	273	250	250
query61	148	153	141	141
query62	550	486	436	436
query63	236	196	202	196
query64	2824	1032	863	863
query65	4718	4631	4625	4625
query66	1804	504	378	378
query67	29246	28601	29016	28601
query68	3021	1562	1023	1023
query69	417	314	264	264
query70	898	813	812	812
query71	370	325	340	325
query72	3050	2699	2376	2376
query73	842	805	400	400
query74	5051	4893	4715	4715
query75	2531	2502	2125	2125
query76	2353	1170	760	760
query77	335	383	282	282
query78	11963	11876	11420	11420
query79	1400	1177	769	769
query80	1315	543	468	468
query81	533	331	290	290
query82	602	152	120	120
query83	372	323	308	308
query84	287	162	133	133
query85	985	599	512	512
query86	411	253	239	239
query87	1821	1821	1731	1731
query88	3750	2778	2767	2767
query89	440	378	338	338
query90	1902	201	195	195
query91	198	206	170	170
query92	62	60	52	52
query93	1737	1519	988	988
query94	736	346	314	314
query95	813	596	466	466
query96	1024	819	335	335
query97	2640	2659	2518	2518
query98	211	210	203	203
query99	1094	1108	978	978
Total cold run time: 264038 ms
Total hot run time: 176889 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.15	0.08	0.08
query3	0.38	0.25	0.24
query4	1.61	0.24	0.24
query5	0.32	0.31	0.31
query6	1.16	0.67	0.67
query7	0.04	0.01	0.01
query8	0.09	0.08	0.06
query9	0.52	0.38	0.38
query10	0.58	0.57	0.57
query11	0.30	0.19	0.18
query12	0.30	0.18	0.18
query13	0.53	0.52	0.52
query14	0.93	0.92	0.93
query15	0.69	0.59	0.60
query16	0.38	0.39	0.39
query17	1.02	0.99	0.99
query18	0.30	0.29	0.29
query19	1.94	1.82	1.79
query20	0.02	0.02	0.01
query21	15.40	0.37	0.32
query22	4.89	0.15	0.14
query23	15.80	0.48	0.31
query24	2.31	0.63	0.43
query25	0.15	0.10	0.10
query26	0.75	0.26	0.21
query27	0.10	0.09	0.10
query28	3.33	0.89	0.53
query29	12.44	4.26	3.34
query30	0.38	0.26	0.25
query31	2.76	0.61	0.32
query32	3.23	0.61	0.48
query33	2.91	3.03	3.00
query34	15.77	4.04	3.33
query35	3.25	3.22	3.28
query36	0.63	0.52	0.51
query37	0.12	0.09	0.10
query38	0.09	0.07	0.07
query39	0.07	0.06	0.06
query40	0.21	0.18	0.17
query41	0.13	0.09	0.09
query42	0.09	0.06	0.06
query43	0.07	0.06	0.07
Total cold run time: 96.15 s
Total hot run time: 25.6 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 86.27% (176/204) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 75.65% (31842/42089)
Line Coverage 60.31% (354907/588493)
Region Coverage 56.88% (297468/523005)
Branch Coverage 58.25% (133570/229302)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 20.90% (14/67) 🎉
Increment coverage report
Complete coverage report

@Gabriel39
Gabriel39 merged commit 8305fe7 into apache:master Jul 29, 2026
31 checks passed
morningman added a commit that referenced this pull request Jul 29, 2026
…t onto the connector SPI

Upstream #66112 (8305fe7) makes an iceberg `MERGE INTO` reject a target row that more
than one source row matches, independently of `enable_strict_consistency_dml`. It did that
in fe-core classes this branch no longer has (`LogicalIcebergMergeSink`,
`PhysicalIcebergMergeSink`, `planner.IcebergMergeSink`, `IcebergMergeCommand`,
`IcebergUpdateCommand`), so the rebase brought in the BE half and the thrift field
(`TIcebergMergeSink.require_merge_cardinality_check`) with no FE producer: BE resolved the
unset field to false and its duplicate-match validation was dead code.

`UPDATE` and `MERGE INTO` synthesize the same sink here, so the statement kind is carried
the way upstream carries it - one boolean stamped at the plan builder:

  ExternalRowLevel{Merge,Update}PlanBuilder (true / false)
    -> Logical/PhysicalExternalRowLevelMergeSink
    -> RequestPropertyDeriver  (MERGE keeps the merge distribution even with
                                enable_strict_consistency_dml off - BE can only see the
                                duplicates when both matches reach one instance)
    -> PhysicalPlanTranslator -> PluginDrivenTableSink
    -> ConnectorWriteHandle#isRequireMergeCardinalityCheck (new default-false SPI method)
    -> IcebergWritePlanProvider stamps TIcebergMergeSink.

The flag is kept off the existing `WriteOperation` axis on purpose: it is a statement-level
SQL requirement, not a sink dialect, and the engine also reads it for the distribution
decision. Every other connector keeps the default false, so no non-iceberg write changes.

Also updates PhysicalExternalRowLevelMergeSinkTest for the behaviour #66112 changed in a
hunk that merged cleanly: with `enable_iceberg_merge_partitioning` off the sink now asks for
a DistributionSpecMerge that routes only the DELETE images by row id, instead of hashing
every row by a row id that is NULL on the insert side (which serialized all inserts onto one
exchange channel). The test still encoded the old DistributionSpecHash and was failing.

Tests: unit coverage for each hop - origin (both polarities), deriver, translator threading,
and the thrift stamp; 4-way mutation check confirms each hop's test goes red when its link
is broken. FE 173 tests green in the touched classes; checkstyle 0 violations.
E2E: regression-test/.../iceberg/write/test_iceberg_write_merge_duplicate_source_negative
(un-gated by #66112) is the end-to-end gate and needs a docker run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…resh onto the plugin bridge

Upstream #66112 (8305fe7) fixed three CREATE TABLE defects in fe-core's legacy paimon
subsystem, which this branch has migrated into the generic plugin bridge. The upstream code
merged cleanly but is inert here, so each is re-implemented where it now belongs. All three
are connector-agnostic on this branch, not paimon-only.

1. Reject an unsupported CTAS destination BEFORE publishing metadata.
   Upstream moved the sink construction ahead of `Env.createTable`; that hunk is on this
   branch, but `UnboundTableSinkCreator` admits ANY PluginDrivenExternalCatalog, so a CTAS
   into a connector with no write path (paimon / es / hudi / trino) still created the remote
   table and only failed later in PhysicalPlanTranslator. The fallback then dropped the
   table BY NAME - which cannot tell this statement's table from a concurrent creator's
   table of the same name, i.e. it can drop someone else's. The CTAS overload of
   createUnboundTableSink now asks the connector-level write provider first. A gateway
   connector answers it (hive/iceberg/jdbc/maxcompute return a provider), so only the
   genuinely write-less connectors are refused, with the same wording the translator used.

2. Refresh the FE table-name cache on an IF NOT EXISTS no-op.
   `PluginDrivenExternalCatalog#createTable` returned true BEFORE the post-create cache
   invalidation, so a table created out-of-band (another engine / another FE) stayed
   invisible to SHOW TABLES and to the following SELECT. A neighbouring probe happened to
   self-heal it, which made the guarantee accidental; it is now explicit.

3. Report a concurrent creator instead of swallowing it.
   The existence probe and the remote create are not atomic. The paimon connector forwarded
   `isIfNotExists()` as paimon's `ignoreIfExists`, so a creator that won the race in between
   was silently no-op'd and this statement looked like the creator - a CTAS would then INSERT
   into, and on failure roll back, the winner's table. Paimon now always creates with
   `ignoreIfExists=false` (as upstream's fe-core arm now does) and the bridge turns the
   resulting failure back into an IF NOT EXISTS no-op returning true, after re-probing that
   the table really is there. Without IF NOT EXISTS the failure still surfaces.

Tests: bridge tests for both no-op arms and both race polarities, a new
UnboundTableSinkCreatorTest for the CTAS admission gate, and the paimon DDL tests updated to
the new contract (they encoded the pre-#66112 passthrough). Mutation check: removing the
cache refresh or the admission gate turns the respective tests red. 76 tests green in the
touched classes; checkstyle 0 violations.
E2E: regression-test/.../paimon/test_paimon_ctas_atomicity_negative (un-gated by #66112)
covers all three and needs a docker run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…66112)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…t onto the connector SPI

Upstream #66112 (8305fe7) makes an iceberg `MERGE INTO` reject a target row that more
than one source row matches, independently of `enable_strict_consistency_dml`. It did that
in fe-core classes this branch no longer has (`LogicalIcebergMergeSink`,
`PhysicalIcebergMergeSink`, `planner.IcebergMergeSink`, `IcebergMergeCommand`,
`IcebergUpdateCommand`), so the rebase brought in the BE half and the thrift field
(`TIcebergMergeSink.require_merge_cardinality_check`) with no FE producer: BE resolved the
unset field to false and its duplicate-match validation was dead code.

`UPDATE` and `MERGE INTO` synthesize the same sink here, so the statement kind is carried
the way upstream carries it - one boolean stamped at the plan builder:

  ExternalRowLevel{Merge,Update}PlanBuilder (true / false)
    -> Logical/PhysicalExternalRowLevelMergeSink
    -> RequestPropertyDeriver  (MERGE keeps the merge distribution even with
                                enable_strict_consistency_dml off - BE can only see the
                                duplicates when both matches reach one instance)
    -> PhysicalPlanTranslator -> PluginDrivenTableSink
    -> ConnectorWriteHandle#isRequireMergeCardinalityCheck (new default-false SPI method)
    -> IcebergWritePlanProvider stamps TIcebergMergeSink.

The flag is kept off the existing `WriteOperation` axis on purpose: it is a statement-level
SQL requirement, not a sink dialect, and the engine also reads it for the distribution
decision. Every other connector keeps the default false, so no non-iceberg write changes.

Also updates PhysicalExternalRowLevelMergeSinkTest for the behaviour #66112 changed in a
hunk that merged cleanly: with `enable_iceberg_merge_partitioning` off the sink now asks for
a DistributionSpecMerge that routes only the DELETE images by row id, instead of hashing
every row by a row id that is NULL on the insert side (which serialized all inserts onto one
exchange channel). The test still encoded the old DistributionSpecHash and was failing.

Tests: unit coverage for each hop - origin (both polarities), deriver, translator threading,
and the thrift stamp; 4-way mutation check confirms each hop's test goes red when its link
is broken. FE 173 tests green in the touched classes; checkstyle 0 violations.
E2E: regression-test/.../iceberg/write/test_iceberg_write_merge_duplicate_source_negative
(un-gated by #66112) is the end-to-end gate and needs a docker run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…resh onto the plugin bridge

Upstream #66112 (8305fe7) fixed three CREATE TABLE defects in fe-core's legacy paimon
subsystem, which this branch has migrated into the generic plugin bridge. The upstream code
merged cleanly but is inert here, so each is re-implemented where it now belongs. All three
are connector-agnostic on this branch, not paimon-only.

1. Reject an unsupported CTAS destination BEFORE publishing metadata.
   Upstream moved the sink construction ahead of `Env.createTable`; that hunk is on this
   branch, but `UnboundTableSinkCreator` admits ANY PluginDrivenExternalCatalog, so a CTAS
   into a connector with no write path (paimon / es / hudi / trino) still created the remote
   table and only failed later in PhysicalPlanTranslator. The fallback then dropped the
   table BY NAME - which cannot tell this statement's table from a concurrent creator's
   table of the same name, i.e. it can drop someone else's. The CTAS overload of
   createUnboundTableSink now asks the connector-level write provider first. A gateway
   connector answers it (hive/iceberg/jdbc/maxcompute return a provider), so only the
   genuinely write-less connectors are refused, with the same wording the translator used.

2. Refresh the FE table-name cache on an IF NOT EXISTS no-op.
   `PluginDrivenExternalCatalog#createTable` returned true BEFORE the post-create cache
   invalidation, so a table created out-of-band (another engine / another FE) stayed
   invisible to SHOW TABLES and to the following SELECT. A neighbouring probe happened to
   self-heal it, which made the guarantee accidental; it is now explicit.

3. Report a concurrent creator instead of swallowing it.
   The existence probe and the remote create are not atomic. The paimon connector forwarded
   `isIfNotExists()` as paimon's `ignoreIfExists`, so a creator that won the race in between
   was silently no-op'd and this statement looked like the creator - a CTAS would then INSERT
   into, and on failure roll back, the winner's table. Paimon now always creates with
   `ignoreIfExists=false` (as upstream's fe-core arm now does) and the bridge turns the
   resulting failure back into an IF NOT EXISTS no-op returning true, after re-probing that
   the table really is there. Without IF NOT EXISTS the failure still surfaces.

Tests: bridge tests for both no-op arms and both race polarities, a new
UnboundTableSinkCreatorTest for the CTAS admission gate, and the paimon DDL tests updated to
the new contract (they encoded the pre-#66112 passthrough). Mutation check: removing the
cache refresh or the admission gate turns the respective tests red. 76 tests green in the
touched classes; checkstyle 0 violations.
E2E: regression-test/.../paimon/test_paimon_ctas_atomicity_negative (un-gated by #66112)
covers all three and needs a docker run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…66112)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Jul 30, 2026
- Validate unsupported CTAS sinks before publishing table metadata, so
setup failures cannot leave an unusable table or remove a concurrent
replacement.
- Preserve nullable string wrappers in truncate partition transforms.
- Enforce Iceberg MERGE cardinality routing independently of
`enable_strict_consistency_dml`.
- Detect duplicate target matches with file-path-interned Roaring
bitmaps and expose retained validation-state bytes in the sink profile.
- Activate the negative regressions for the three retained Jira fixes
and align the duplicate-match oracle with the emitted diagnostic.

- Full build: `./build.sh --fe --be -j 48`
- FE: `CreateTableCommandTest`, `IcebergDDLAndDMLPlanTest` (19 tests)
- BE: `VIcebergMergeSinkTest` (8 tests, including 100,000 matched rows)
- Regression: `test_paimon_ctas_atomicity_negative` (1 suite, passed)
- Iceberg end-to-end SQL: duplicate-source MERGE rejected atomically;
nullable/merge truncate writes and logical rows verified; physical
partition values verified from the same Iceberg tables through Spark
metadata.

The Doris-side `$partitions`/`$snapshots` checks in the Iceberg suites
currently hit an unrelated JNI scanner initialization abort on the
current master test binary; the equivalent table state and metadata
assertions above passed.

(cherry picked from commit 8305fe7)
Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Jul 30, 2026
- Validate unsupported CTAS sinks before publishing table metadata, so
setup failures cannot leave an unusable table or remove a concurrent
replacement.
- Preserve nullable string wrappers in truncate partition transforms.
- Enforce Iceberg MERGE cardinality routing independently of
`enable_strict_consistency_dml`.
- Detect duplicate target matches with file-path-interned Roaring
bitmaps and expose retained validation-state bytes in the sink profile.
- Activate the negative regressions for the three retained Jira fixes
and align the duplicate-match oracle with the emitted diagnostic.

- Full build: `./build.sh --fe --be -j 48`
- FE: `CreateTableCommandTest`, `IcebergDDLAndDMLPlanTest` (19 tests)
- BE: `VIcebergMergeSinkTest` (8 tests, including 100,000 matched rows)
- Regression: `test_paimon_ctas_atomicity_negative` (1 suite, passed)
- Iceberg end-to-end SQL: duplicate-source MERGE rejected atomically;
nullable/merge truncate writes and logical rows verified; physical
partition values verified from the same Iceberg tables through Spark
metadata.

The Doris-side `$partitions`/`$snapshots` checks in the Iceberg suites
currently hit an unrelated JNI scanner initialization abort on the
current master test binary; the equivalent table state and metadata
assertions above passed.

(cherry picked from commit 8305fe7)
Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Jul 30, 2026
- Validate unsupported CTAS sinks before publishing table metadata, so
setup failures cannot leave an unusable table or remove a concurrent
replacement.
- Preserve nullable string wrappers in truncate partition transforms.
- Enforce Iceberg MERGE cardinality routing independently of
`enable_strict_consistency_dml`.
- Detect duplicate target matches with file-path-interned Roaring
bitmaps and expose retained validation-state bytes in the sink profile.
- Activate the negative regressions for the three retained Jira fixes
and align the duplicate-match oracle with the emitted diagnostic.

- Full build: `./build.sh --fe --be -j 48`
- FE: `CreateTableCommandTest`, `IcebergDDLAndDMLPlanTest` (19 tests)
- BE: `VIcebergMergeSinkTest` (8 tests, including 100,000 matched rows)
- Regression: `test_paimon_ctas_atomicity_negative` (1 suite, passed)
- Iceberg end-to-end SQL: duplicate-source MERGE rejected atomically;
nullable/merge truncate writes and logical rows verified; physical
partition values verified from the same Iceberg tables through Spark
metadata.

The Doris-side `$partitions`/`$snapshots` checks in the Iceberg suites
currently hit an unrelated JNI scanner initialization abort on the
current master test binary; the equivalent table state and metadata
assertions above passed.

(cherry picked from commit 8305fe7)
Gabriel39 added a commit that referenced this pull request Jul 31, 2026
…sistency fixes (#66246)

### What problem does this PR solve?

This backports the following lakehouse fixes to `branch-4.1`:

- #66112: enforce external write correctness for Iceberg operations.
- #66223: preserve pruned Paimon struct field reads.
- #66007: preserve metadata generations and nested schema semantics
across Iceberg, Paimon, Hudi, ORC, and complex-type materialization.

The #66007 changes were adapted to the `branch-4.1` APIs while
preserving the source PR's behavior. A minimal nullable-cast helper
required by the backport was included instead of pulling the unrelated
master refactor that originally introduced it.

### Check List

- FE focused unit tests: 202 successful test executions across the
initial suite and the Paimon follow-up run.
- FE Maven reactor: `BUILD SUCCESS`.
- BE affected production and test translation units: compiled
successfully with ASAN settings.
- BE #66007 ORC and complex-rematerialization tests: 18 tests passed on
the exact #66007 head.
- `git diff --check`: passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants