Skip to content

[fix](iceberg) Fix MVCC and nested schema evolution edge cases - #66345

Open
Gabriel39 wants to merge 4 commits into
apache:masterfrom
Gabriel39:fix/pr-66007-review-followups
Open

[fix](iceberg) Fix MVCC and nested schema evolution edge cases#66345
Gabriel39 wants to merge 4 commits into
apache:masterfrom
Gabriel39:fix/pr-66007-review-followups

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

This PR fixes correctness and memory-usage edge cases in Iceberg MVCC writes and nested schema evolution:

  • An explicitly empty read could be reinterpreted as the latest snapshot after a concurrent first append. A MERGE could then miss the concurrent row and silently insert a duplicate.
  • Write-schema validation used the INSERT column subset instead of the complete bind-time target schema, rejecting valid partial and static-partition INSERTs while weakening ordinal drift checks.
  • Iceberg v3 row-lineage metadata columns could be mistaken for user-schema drift.
  • Parent null-mask handling for nested ARRAY/MAP values could scan descendant entry maps and allocate entry-sized scratch even when the inherited mask was absent or all-clear.
  • Strict cast failure semantics were incorrectly used to mark a struct field physically non-nullable even though BE narrowing casts still return ColumnNullable.
  • Nested DESCRIBE comments were not emitted as round-trippable SQL literals.

The fix preserves the explicit empty-snapshot marker through scan planning and keeps RowDelta conflict validation anchored at table creation. In the concurrent empty-table MERGE/INSERT window, MERGE keeps reading the original empty view and its commit conflicts with the concurrent first append instead of producing duplicate data.

Write planning now carries an immutable complete target-schema snapshot separately from the write subset, validates column order, type, and Iceberg field id, and excludes engine-generated row-lineage metadata from the user-schema comparison. Nested collection alignment gates descendant scans on an inherited mask that can actually hide a row, and struct constructors preserve the physical nullability of cast results.

Release note

Fix Iceberg empty-snapshot concurrency, partial-write schema validation, nested collection memory usage, cast-result nullability, and nested DESCRIBE comment rendering.

Check List (For Author)

  • Test
    • Regression tests
    • Unit tests
  • Behavior changed:
    • Yes. Correctness and memory-usage fixes for the affected edge cases.
  • Does this need documentation?
    • No.

Tests

  • Iceberg write-plan tests: 50 passed
  • Iceberg concurrent empty-snapshot barrier test: passed
  • BE TableReaderTest: 95 passed
  • FE connector sink, binding, executor, and struct tests: 38 passed
  • FE Checkstyle: 0 violations
  • Clang Formatter 16: passed
  • git diff --check: passed

### What problem does this PR solve?

Issue Number: close #xxx

Related PR: apache#66007

Problem Summary:

Several review follow-ups from apache#66007 remained open: empty Iceberg reads could lose their MVCC boundary before a concurrent first append; nested DESCRIBE comments were not emitted as valid SQL literals; strict pre-casts widened required struct fields to nullable; trivial collection children lost inherited parent masks; and connector sink binding repeatedly loaded the latest target schema.

This change preserves the empty-read marker through Iceberg write planning, centralizes SQL string literal quoting, makes cast nullability strict-mode aware, projects inherited collection masks through ARRAY/MAP offsets, and captures one case-insensitive connector target-schema snapshot per bind.

### Release note

Fix Iceberg MVCC and nested schema-evolution edge cases in connector reads, writes, DESCRIBE output, casts, and sink binding.

### Check List (For Author)

- Test
  - [x] Regression tests
  - [x] Unit tests
- Behavior changed:
  - [x] Yes. Correctness fixes for the affected edge cases.
- Does this need documentation?
  - [x] No.
@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

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17815	4019	3955	3955
q2	2052	341	214	214
q3	10253	1422	804	804
q4	4677	466	334	334
q5	7495	820	581	581
q6	181	167	134	134
q7	740	775	608	608
q8	9327	1604	1522	1522
q9	5335	4054	4043	4043
q10	6712	1635	1352	1352
q11	506	366	332	332
q12	732	565	464	464
q13	18072	3314	2755	2755
q14	272	263	241	241
q15	q16	738	727	670	670
q17	962	998	1073	998
q18	6899	5789	5460	5460
q19	1303	1186	1078	1078
q20	824	657	610	610
q21	6513	2737	2738	2737
q22	471	377	320	320
Total cold run time: 101879 ms
Total hot run time: 29212 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4970	4789	4703	4703
q2	280	323	204	204
q3	4928	5265	4703	4703
q4	2247	2240	1421	1421
q5	4768	4439	4607	4439
q6	230	170	125	125
q7	1823	1690	1511	1511
q8	2357	1997	1978	1978
q9	7094	6724	6700	6700
q10	4234	4212	3829	3829
q11	502	368	341	341
q12	685	714	489	489
q13	2998	3268	2751	2751
q14	271	277	254	254
q15	q16	670	681	608	608
q17	1265	1227	1232	1227
q18	7066	6777	6708	6708
q19	1049	1061	1059	1059
q20	2209	2198	1916	1916
q21	5268	4572	4426	4426
q22	519	463	409	409
Total cold run time: 55433 ms
Total hot run time: 49801 ms

@hello-stephen

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

query5	4358	607	475	475
query6	470	229	219	219
query7	4846	575	352	352
query8	341	188	163	163
query9	8777	4057	4096	4057
query10	452	351	295	295
query11	5797	2223	2022	2022
query12	152	98	97	97
query13	1301	591	412	412
query14	6147	4668	4361	4361
query14_1	3807	3798	3756	3756
query15	221	199	173	173
query16	1008	479	436	436
query17	1099	687	530	530
query18	2443	451	353	353
query19	201	182	144	144
query20	105	99	98	98
query21	232	155	139	139
query22	13057	13048	12898	12898
query23	17236	16398	15968	15968
query23_1	16102	16144	16137	16137
query24	7526	1696	1247	1247
query24_1	1249	1237	1203	1203
query25	527	440	377	377
query26	1324	351	215	215
query27	2658	605	404	404
query28	4536	1995	2053	1995
query29	1059	596	455	455
query30	341	265	226	226
query31	1124	1082	951	951
query32	108	64	62	62
query33	532	325	256	256
query34	1155	1114	636	636
query35	739	756	654	654
query36	787	799	717	717
query37	152	105	92	92
query38	1840	1651	1604	1604
query39	816	840	800	800
query39_1	785	780	788	780
query40	262	169	157	157
query41	71	68	72	68
query42	95	96	97	96
query43	319	317	275	275
query44	1393	790	771	771
query45	187	182	172	172
query46	1067	1207	724	724
query47	1526	1527	1429	1429
query48	407	406	323	323
query49	585	432	309	309
query50	1100	442	346	346
query51	10540	10657	10629	10629
query52	89	89	76	76
query53	273	277	207	207
query54	307	249	232	232
query55	77	77	68	68
query56	325	309	307	307
query57	1043	994	922	922
query58	296	284	263	263
query59	1574	1594	1367	1367
query60	322	278	266	266
query61	179	178	177	177
query62	402	326	269	269
query63	242	205	199	199
query64	2955	1203	860	860
query65	3930	3845	3833	3833
query66	1840	468	368	368
query67	28397	28180	28060	28060
query68	3224	1527	946	946
query69	400	287	253	253
query70	905	811	787	787
query71	379	353	318	318
query72	3027	2661	2226	2226
query73	837	764	435	435
query74	4641	4626	4350	4350
query75	2371	2362	1979	1979
query76	2306	1153	755	755
query77	338	368	270	270
query78	11187	11095	10582	10582
query79	1378	1159	744	744
query80	1256	559	471	471
query81	544	328	291	291
query82	594	153	118	118
query83	365	321	291	291
query84	271	165	125	125
query85	971	599	516	516
query86	407	233	218	218
query87	1799	1793	1693	1693
query88	3740	2816	2806	2806
query89	394	311	283	283
query90	1934	211	196	196
query91	213	192	164	164
query92	62	58	53	53
query93	1685	1556	1012	1012
query94	713	354	311	311
query95	807	590	487	487
query96	1052	812	351	351
query97	2494	2472	2333	2333
query98	212	193	186	186
query99	718	730	615	615
Total cold run time: 256718 ms
Total hot run time: 169738 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.10	0.05	0.05
query3	0.26	0.13	0.14
query4	1.62	0.14	0.14
query5	0.24	0.22	0.22
query6	1.16	0.82	0.82
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.38	0.31	0.31
query10	0.55	0.55	0.53
query11	0.19	0.14	0.14
query12	0.18	0.15	0.14
query13	0.46	0.46	0.47
query14	1.01	0.99	0.99
query15	0.61	0.58	0.58
query16	0.31	0.32	0.31
query17	1.06	1.10	1.10
query18	0.21	0.20	0.20
query19	2.03	1.89	1.93
query20	0.02	0.01	0.01
query21	15.44	0.19	0.13
query22	4.98	0.05	0.05
query23	16.13	0.31	0.12
query24	2.95	0.41	0.33
query25	0.10	0.06	0.04
query26	0.73	0.22	0.15
query27	0.05	0.04	0.03
query28	3.60	0.78	0.37
query29	12.47	4.01	3.18
query30	0.26	0.15	0.15
query31	2.77	0.58	0.31
query32	3.22	0.58	0.48
query33	3.18	3.30	3.16
query34	15.59	3.96	3.29
query35	3.24	3.23	3.25
query36	0.55	0.46	0.42
query37	0.09	0.06	0.06
query38	0.05	0.04	0.03
query39	0.03	0.03	0.03
query40	0.17	0.15	0.14
query41	0.09	0.04	0.04
query42	0.03	0.02	0.02
query43	0.04	0.03	0.03
Total cold run time: 96.25 s
Total hot run time: 23.86 s

Keep an explicitly empty MVCC read empty across a concurrent first append and validate RowDelta conflicts from table creation. Add a barrier test for the MERGE/INSERT race.

Also fail writes when the bound schema changed, avoid unnecessary nested collection null-mask allocation, and keep Cast nullability independent of thread-local strict mode.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex completed, but no new pull request review was submitted for the current head SHA.
Workflow run: https://github.com/apache/doris/actions/runs/30683578831

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

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17634	3894	3896	3894
q2	1990	311	201	201
q3	10367	1334	818	818
q4	4683	464	341	341
q5	7557	830	548	548
q6	194	167	133	133
q7	736	793	594	594
q8	10100	1490	1521	1490
q9	5856	4076	3995	3995
q10	6854	1633	1366	1366
q11	516	359	320	320
q12	737	573	445	445
q13	18086	3236	2714	2714
q14	252	251	241	241
q15	q16	728	738	654	654
q17	992	887	937	887
q18	6816	5734	5560	5560
q19	1361	1203	1070	1070
q20	781	669	579	579
q21	5797	2590	2439	2439
q22	442	355	308	308
Total cold run time: 102479 ms
Total hot run time: 28597 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4232	4132	4135	4132
q2	282	311	208	208
q3	4487	4900	4417	4417
q4	2126	2236	1395	1395
q5	4211	4122	4054	4054
q6	223	173	124	124
q7	1666	1560	1407	1407
q8	2745	2051	2020	2020
q9	7219	7185	7211	7185
q10	4290	4248	3843	3843
q11	547	406	356	356
q12	703	715	500	500
q13	3228	3442	3005	3005
q14	294	303	265	265
q15	q16	696	711	614	614
q17	1306	1300	1271	1271
q18	7951	7191	7133	7133
q19	1080	1054	1006	1006
q20	2165	2169	1891	1891
q21	5206	4532	4389	4389
q22	530	432	405	405
Total cold run time: 55187 ms
Total hot run time: 49620 ms

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex completed, but no new pull request review was submitted for the current head SHA.
Workflow run: https://github.com/apache/doris/actions/runs/30684910352

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

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 168981 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 4576ae63e6f1b751ff9f3af374ff1caf38bd8237, data reload: false

query5	4312	604	466	466
query6	472	223	206	206
query7	4885	554	343	343
query8	337	186	179	179
query9	8785	4039	4011	4011
query10	507	370	317	317
query11	5817	2205	2068	2068
query12	162	101	98	98
query13	1269	571	433	433
query14	6164	4692	4369	4369
query14_1	3828	3808	3778	3778
query15	206	192	173	173
query16	1002	451	439	439
query17	910	671	522	522
query18	2428	469	328	328
query19	199	185	145	145
query20	103	98	99	98
query21	243	158	136	136
query22	13060	13000	12757	12757
query23	17295	16339	16053	16053
query23_1	16166	16293	16335	16293
query24	7625	1672	1245	1245
query24_1	1247	1270	1185	1185
query25	522	425	348	348
query26	1341	338	212	212
query27	2596	605	373	373
query28	4490	2044	2007	2007
query29	1047	571	458	458
query30	345	260	229	229
query31	1113	1058	949	949
query32	106	61	58	58
query33	516	315	244	244
query34	1176	1156	644	644
query35	722	742	647	647
query36	771	775	681	681
query37	149	103	89	89
query38	1833	1645	1573	1573
query39	814	825	787	787
query39_1	804	778	785	778
query40	264	165	142	142
query41	64	65	64	64
query42	96	92	88	88
query43	310	316	272	272
query44	1428	774	763	763
query45	190	181	168	168
query46	1074	1147	718	718
query47	1512	1471	1394	1394
query48	401	425	295	295
query49	576	410	284	284
query50	1091	424	341	341
query51	10428	10372	10469	10372
query52	83	87	73	73
query53	257	269	202	202
query54	281	254	225	225
query55	75	69	67	67
query56	314	292	301	292
query57	1043	996	925	925
query58	295	259	250	250
query59	1536	1609	1417	1417
query60	304	266	254	254
query61	148	151	148	148
query62	402	314	266	266
query63	235	202	196	196
query64	2886	1015	826	826
query65	3878	3814	3835	3814
query66	1821	477	352	352
query67	28159	28302	27946	27946
query68	3093	1691	970	970
query69	416	298	264	264
query70	906	803	771	771
query71	362	347	311	311
query72	2981	2731	2275	2275
query73	846	784	453	453
query74	4644	4526	4282	4282
query75	2365	2352	1994	1994
query76	2340	1144	766	766
query77	344	378	276	276
query78	11052	11089	10602	10602
query79	1236	1182	717	717
query80	611	549	450	450
query81	443	333	280	280
query82	229	154	118	118
query83	320	325	302	302
query84	306	155	130	130
query85	905	605	572	572
query86	277	227	230	227
query87	1780	1787	1687	1687
query88	3785	2826	2796	2796
query89	371	321	284	284
query90	2105	204	203	203
query91	203	188	163	163
query92	61	58	53	53
query93	1547	1477	950	950
query94	515	354	310	310
query95	797	602	481	481
query96	1053	825	378	378
query97	2467	2451	2349	2349
query98	203	194	191	191
query99	726	716	608	608
Total cold run time: 253733 ms
Total hot run time: 168981 ms

@Gabriel39
Gabriel39 marked this pull request as ready for review August 1, 2026 05:48
@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.10	0.05	0.05
query3	0.25	0.14	0.13
query4	1.62	0.14	0.14
query5	0.24	0.24	0.23
query6	1.16	0.80	0.80
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.37	0.32	0.32
query10	0.55	0.58	0.55
query11	0.19	0.13	0.13
query12	0.17	0.14	0.15
query13	0.47	0.48	0.46
query14	1.01	1.00	1.00
query15	0.60	0.60	0.61
query16	0.32	0.32	0.31
query17	1.11	1.16	1.13
query18	0.22	0.19	0.20
query19	1.98	1.97	1.93
query20	0.02	0.01	0.01
query21	15.44	0.21	0.13
query22	4.80	0.05	0.05
query23	16.11	0.31	0.12
query24	2.71	0.41	0.34
query25	0.11	0.05	0.04
query26	0.74	0.20	0.14
query27	0.06	0.04	0.04
query28	3.58	0.79	0.36
query29	12.48	4.05	3.20
query30	0.29	0.15	0.15
query31	2.77	0.55	0.31
query32	3.24	0.58	0.48
query33	3.17	3.20	3.20
query34	15.64	3.92	3.29
query35	3.21	3.20	3.22
query36	0.54	0.43	0.42
query37	0.09	0.07	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.17	0.15	0.14
query41	0.08	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 95.89 s
Total hot run time: 23.99 s

@Gabriel39 Gabriel39 changed the title [fix](lakehouse) Address schema evolution review follow-ups [fix](iceberg) Fix MVCC and nested schema evolution edge cases Aug 1, 2026

@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: three issues remain.

Checkpoint conclusions:

  • Goal and proof: the PR addresses nested schema evolution, Iceberg MVCC/write binding, strict-cast struct nullability, and SQL comment quoting, but it does not fully achieve that goal because valid Iceberg write shapes and strict narrowing casts still fail, and the BE collection path adds a redundant scan. The added tests do not exercise those three cases.
  • Scope and clarity: the changes are generally localized and the full 23-file diff was reviewed; no unrelated source change was identified.
  • Concurrency and lifecycle: the resolved-empty snapshot marker, handle copies, scan initialization, synchronous/streaming/count paths, and RowDelta base validation were traced. Production pin ordering preserves the empty snapshot, and no lock-order, static-lifetime, or release issue was found.
  • Configuration, compatibility, and persistence: no new configuration, storage-format, journal, symbol, or rolling-upgrade compatibility issue was found.
  • Parallel paths and data writes: ARRAY/MAP, struct/named_struct, partial/static/full/rewrite/row-level writes, and both SQL backslash modes were checked. The Iceberg validator incorrectly treats a write subset as a full captured schema.
  • FE/BE contract: snapshot state propagation is complete, but strict-cast logical nullability disagrees with the BE physical column type for narrowing casts.
  • Performance: the new nested collection probe performs an avoidable O(entries) null-map pass for absent/all-clear inherited masks. No other material CPU, memory, or observability issue was substantiated.
  • Tests and results: changed unit tests and assertions were statically inspected; no generated result-file issue was found. Builds and tests were not run because the authoritative review instructions prohibit them.

No additional user review focus was provided; the whole PR was reviewed.

Comment thread be/src/format_v2/table_reader.h Outdated
// only when a required descendant can consume them. This avoids scratch proportional
// to all array entries for the common all-required schema.
const NullMap* descendant_parent_null_map_ptr = nullptr;
if (_requires_parent_null_map_for_alignment(nested_column,

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.

_requires_parent_null_map_for_alignment() runs before this code establishes that the inherited parent mask exists and hides at least one row. For a large ARRAY/MAP with nullable-to-required descendants and a null or all-clear parent mask, the probe scans descendant null maps via has_null(), projection returns no entry mask, and normal alignment scans those descendants again. Please gate the descendant probe on a parent mask that can actually hide rows so this schema-evolution path does not add an avoidable O(number of entries) pass per block.

List<ConnectorColumn> boundColumns = handle.getColumns();
boolean hasSyntheticRowId = boundColumns.size() == currentColumns.size() + 1
&& DORIS_ICEBERG_ROWID_COL.equals(boundColumns.get(boundColumns.size() - 1).getName());
if (boundColumns.size() != currentColumns.size() && !hasSyntheticRowId) {

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.

handle.getColumns() is the query's bound write list, not the full schema snapshot. BindSink intentionally omits unspecified columns and static partition columns, and PhysicalPlanTranslator copies that subset into the handle, so a valid INSERT INTO t (id) ... for a two-column table (or an INSERT with a static partition) reaches this branch with different sizes and is rejected as schema drift even when the table never changed. Please carry the complete bound target schema separately for this comparison, while retaining the subset for value-to-column mapping.


/** Nullability of this cast when invalid input fails instead of producing NULL. */
public boolean strictModeNullable() {
return child().nullable();

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.

Strict failure semantics do not make every successful cast physically non-nullable. BE's strict BIGINT-to-INT specialization still has type Nullable(Int32) (with an all-clear map for valid rows), while this reports a required field and FE serializes Struct<Int32>. The generic nested-type compatibility check admits that mismatch, then FunctionStruct tries to insert the ColumnNullable argument into a non-null ColumnInt32 field and hits the column-type assertion even for an in-range value. Please keep the field nullable until strict casts actually return non-null types, or normalize the strict cast's physical result as part of this change.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 78.79% (52/66) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 74.36% (31418/42252)
Line Coverage 58.57% (346062/590860)
Region Coverage 54.81% (288028/525545)
Branch Coverage 55.81% (128955/231076)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 14.05% (26/185) 🎉
Increment coverage report
Complete coverage report

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17645	4049	3980	3980
q2	1996	318	197	197
q3	10323	1380	784	784
q4	4682	469	334	334
q5	7498	840	560	560
q6	185	176	136	136
q7	742	792	594	594
q8	9309	1506	1593	1506
q9	5343	4083	4102	4083
q10	6757	1625	1343	1343
q11	517	359	327	327
q12	748	579	447	447
q13	18067	3381	2799	2799
q14	261	256	247	247
q15	q16	739	732	671	671
q17	937	1013	997	997
q18	6785	5838	5564	5564
q19	1161	1126	1074	1074
q20	802	682	586	586
q21	5597	2663	2325	2325
q22	435	354	306	306
Total cold run time: 100529 ms
Total hot run time: 28860 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4352	4221	4430	4221
q2	306	315	217	217
q3	4552	4941	4439	4439
q4	2146	2246	1392	1392
q5	4326	4109	4157	4109
q6	231	170	127	127
q7	1695	1604	1419	1419
q8	2876	2215	2149	2149
q9	7476	7515	7534	7515
q10	4314	4280	3904	3904
q11	539	398	380	380
q12	724	717	504	504
q13	3240	3454	3005	3005
q14	310	301	269	269
q15	q16	693	712	630	630
q17	1367	1336	1297	1297
q18	8309	7209	7354	7209
q19	1141	1127	1127	1127
q20	2223	2219	1940	1940
q21	5319	4603	4477	4477
q22	521	464	424	424
Total cold run time: 56660 ms
Total hot run time: 50754 ms

@hello-stephen

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

query5	4340	612	457	457
query6	462	217	204	204
query7	4977	553	350	350
query8	339	181	172	172
query9	8777	4073	4076	4073
query10	512	352	295	295
query11	5863	2201	2014	2014
query12	155	95	96	95
query13	1239	585	450	450
query14	6080	4758	4438	4438
query14_1	3925	3838	3923	3838
query15	217	215	179	179
query16	1022	482	430	430
query17	935	701	578	578
query18	2442	492	349	349
query19	220	199	169	169
query20	106	104	102	102
query21	245	163	145	145
query22	13165	13052	12870	12870
query23	17344	16511	16102	16102
query23_1	16164	16202	16114	16114
query24	7554	1704	1236	1236
query24_1	1262	1274	1253	1253
query25	565	506	362	362
query26	1320	342	211	211
query27	2686	577	404	404
query28	4549	2041	2032	2032
query29	1029	598	477	477
query30	343	260	226	226
query31	1134	1066	959	959
query32	113	66	56	56
query33	521	296	243	243
query34	1180	1121	652	652
query35	749	734	651	651
query36	792	785	734	734
query37	160	104	88	88
query38	1834	1667	1605	1605
query39	830	823	820	820
query39_1	803	813	790	790
query40	255	167	146	146
query41	65	63	63	63
query42	95	89	91	89
query43	326	329	288	288
query44	1461	795	769	769
query45	188	174	163	163
query46	1081	1160	724	724
query47	1485	1536	1384	1384
query48	425	440	289	289
query49	585	398	298	298
query50	1055	424	345	345
query51	11249	10317	10488	10317
query52	86	90	75	75
query53	263	275	202	202
query54	284	239	224	224
query55	73	73	66	66
query56	305	303	291	291
query57	1018	983	910	910
query58	309	258	255	255
query59	1557	1606	1396	1396
query60	304	276	248	248
query61	150	151	154	151
query62	395	315	267	267
query63	248	198	190	190
query64	2857	1028	839	839
query65	3926	3833	3859	3833
query66	1811	491	364	364
query67	28241	28212	27378	27378
query68	3161	1557	1035	1035
query69	402	313	263	263
query70	875	808	812	808
query71	387	340	310	310
query72	3176	2784	2443	2443
query73	840	824	427	427
query74	4633	4512	4304	4304
query75	2385	2337	2007	2007
query76	2357	1138	768	768
query77	351	394	301	301
query78	11270	11190	10640	10640
query79	1394	1094	774	774
query80	1190	531	479	479
query81	530	329	283	283
query82	569	148	112	112
query83	382	328	300	300
query84	325	161	127	127
query85	979	631	537	537
query86	366	237	236	236
query87	1813	1793	1739	1739
query88	3740	2813	2815	2813
query89	404	327	304	304
query90	1835	211	204	204
query91	201	187	169	169
query92	62	62	57	57
query93	1542	1508	969	969
query94	626	347	300	300
query95	814	596	468	468
query96	1014	817	320	320
query97	2452	2442	2327	2327
query98	219	203	189	189
query99	721	714	607	607
Total cold run time: 257169 ms
Total hot run time: 169312 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.09	0.05	0.05
query3	0.25	0.14	0.12
query4	1.61	0.13	0.12
query5	0.24	0.22	0.23
query6	1.16	0.80	0.80
query7	0.03	0.01	0.01
query8	0.06	0.04	0.04
query9	0.38	0.31	0.31
query10	0.60	0.58	0.55
query11	0.19	0.13	0.13
query12	0.18	0.14	0.14
query13	0.45	0.46	0.46
query14	1.00	0.98	1.01
query15	0.59	0.58	0.60
query16	0.32	0.32	0.31
query17	1.12	1.06	1.10
query18	0.22	0.19	0.20
query19	2.06	1.85	1.99
query20	0.02	0.02	0.01
query21	15.45	0.21	0.13
query22	4.82	0.06	0.06
query23	16.14	0.32	0.13
query24	2.93	0.42	0.35
query25	0.11	0.06	0.05
query26	0.73	0.20	0.14
query27	0.04	0.03	0.03
query28	3.55	0.73	0.34
query29	12.47	4.12	3.25
query30	0.27	0.17	0.15
query31	2.77	0.55	0.31
query32	3.23	0.59	0.48
query33	3.22	3.11	3.21
query34	15.69	3.92	3.30
query35	3.26	3.22	3.23
query36	0.55	0.43	0.42
query37	0.09	0.06	0.07
query38	0.06	0.04	0.03
query39	0.03	0.04	0.03
query40	0.18	0.15	0.15
query41	0.08	0.03	0.03
query42	0.04	0.02	0.02
query43	0.04	0.03	0.04
Total cold run time: 96.32 s
Total hot run time: 23.75 s

@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: four blocking write-correctness issues remain. Production type conversion rejects unchanged Iceberg schemas; row-level UPDATE/MERGE can move their validation baseline to a later schema; recursive Iceberg field identity is not fenced; and partition/write-sort planning still resolves live positions against older bound output.

Checkpoint conclusions:

  • Goal and proof: the head fixes the earlier partial/static write-list false positive, empty-snapshot lifecycle, strict-cast struct shape, and format-v2 descendant-scan cost, but the end-to-end schema-generation goal is incomplete because of the four inline findings. The added tests do not prove the real translator-to-provider path or the concurrent generation paths.
  • Scope and user focus: all 29 changed files and their upstream/downstream consumers were reviewed. The changes are generally localized and the new SPI method is defaulted. No additional user review focus was provided, so the whole PR was reviewed.
  • Concurrency: no new in-process shared-state, lock-order, or deadlock issue was found. The surviving races are external metadata updates between bind, row-level projection synthesis, physical partition/write-sort planning, and planWrite; supported no-cache/refresh behavior makes them reachable.
  • Lifecycle: snapshotResolved and the ordinary bound-schema list survive all traced immutable copies. Mandatory scan initialization pins before batch selection, so the provisional resolved-empty streaming concern is dismissed. No static-initialization, reference-cycle, cleanup, or ownership defect was found.
  • Configuration: no configuration item is added. Existing cache invalidation and Iceberg table-cache TTL 0 expose the mixed-generation cases; no separate dynamic-configuration issue was found.
  • Compatibility and protocol: the write-handle API addition is source-compatible, and no storage format, Thrift payload, EditLog, symbol, or rolling-upgrade carrier changes. The scalar ConnectorType encoding mismatch is nevertheless a direct behavioral compatibility break for ordinary writes.
  • Parallel paths and conditions: ordinary INSERT/OVERWRITE/REWRITE, explicit/static-partition writes, DELETE, COUNT/system/Top-N/rewrite-scope scans, RowDelta conflict validation, both SQL quoting modes, struct constructors, and ARRAY/MAP/STRUCT null-mask coordinates were checked. UPDATE/MERGE's compatibility constructor and the pre-validation position consumers are the missed parallel paths. The special conditions otherwise fail loudly and are documented; a default-only change is correctly dismissed because its old value is already an explicit legal expression.
  • Tests and results: changed tests and assertions were statically inspected and their expected behavior is consistent, but they bypass production type conversion and omit recursive field replacement, consecutive row-level schema reads, and live partition/write-sort positions. No build or test was run because the authoritative review instructions require static review only.
  • Observability: existing connector exceptions and scan/write profiles are sufficient once validation executes; no new logging, metric, or hot-INFO issue was found.
  • Persistence, transactions, data writes, and crashes: no FE persistence state is added and Iceberg atomic commit/abort behavior remains intact. However, recursive replacement and row-level generation skew can atomically commit payloads under the wrong field identity/order, while the positional paths can choose the wrong slot or fail before the intended retry exception. No separate crash-leak or recovery defect survived review.
  • FE/BE variables: no new variable needs FE-to-BE propagation and no scattered send path is missing.
  • Performance: the immutable schema copies and validation are O(schema) per write and outside row hot paths. The format-v2 all-clear/absent-mask gates remove the prior redundant descendant scan; no other material CPU or memory regression was substantiated.
  • Other issues and deduplication: the three existing threads cover only the earlier format-v2 scan cost, subset/full-schema comparison, and strict-cast nullability. The four submitted findings are distinct, and no other unresolved candidate remains after three converged review rounds.


private static ConnectorColumn toWriteConnectorColumn(Column column) {
ConnectorColumn result = new ConnectorColumn(column.getName(),
ConnectorColumnConverter.toConnectorType(column.getType()),

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] Normalize the bound type before comparing it. A real unchanged Iceberg INTEGER arrives as ConnectorType("INT", -1, -1), becomes ScalarType.INT with 0/0, and is converted here to ConnectorType("INT", 0, 0); ConnectorType.equals compares those parameters, so ordinary INSERT/UPDATE/MERGE/REWRITE is rejected as "schema changed". Timestamps and several other scalars diverge too. The added provider tests construct canonical ConnectorType.of("INT") directly and miss this path; please compare one canonical representation and add a translator-to-provider test.

ConnectorTableHandle tableHandle, List<ConnectorColumn> connectorColumns,
TSortInfo writeSortInfo, WriteOperation writeOperation, boolean requireMergeCardinalityCheck) {
this(targetTable, writePlanProvider, connectorSession, tableHandle, connectorColumns,
connectorColumns, writeSortInfo, writeOperation, requireMergeCardinalityCheck);

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] Keep the row-level projection and validation baseline on one schema generation. UPDATE and MERGE each call getBaseSchema(true) once while building [operation,row_id,data...] and again for sink.getCols(). With Iceberg table-cache TTL 0, a reorder can land between them; this overload then aliases the later list as boundTargetColumns, so S1 validates against S1 while BE writes the S0-ordered expressions positionally under S1. A same-shaped FIXED/CHAR reorder silently swaps values. Please capture the schema once and thread that exact list through projection, sink, and validation, with a consecutive-read drift test.

ConnectorType currentType = IcebergTypeMapping.fromIcebergType(
current.type(), enableVarbinary, enableTimestampTz);
if (!current.name().equalsIgnoreCase(bound.getName())
|| !currentType.equals(bound.getType())

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] Compare recursive Iceberg field identities. The bound conversion has already lost every nested Column.uniqueId, and ConnectorType.equals intentionally excludes child IDs and optionality. For example, a FIXED(n) struct child can be dropped and re-added with the same name/type/position, pass this check as the same CHAR(n) shape, and have the old bound child payload written under the replacement field ID from the live schema JSON. Please carry and compare recursive IDs/requiredness (or an authoritative schema generation) and add a nested recreate test.

*/
public class PhysicalConnectorTableSink<CHILD_TYPE extends Plan> extends PhysicalBaseExternalTableSink<CHILD_TYPE> {

private final List<Column> boundTargetSchema;

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] Resolve positional consumers from this bound schema. getRequirePhysicalProperties() still reloads Hive/MaxCompute partition names and full-schema positions, then indexes the older child output; the Iceberg write-sort path likewise returns live ordinals and indexes that output before schema validation. A refresh or reorder can therefore hash/sort the wrong expression or throw out of bounds. Please carry stable partition/sort identities with this generation and derive every output position from it.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review step was failure (possibly timeout or cancelled)
Workflow run: https://github.com/apache/doris/actions/runs/30705596458

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

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 80.00% (60/75) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.19% (25645/43325)
Line Coverage 43.29% (257504/594869)
Region Coverage 38.97% (204117/523794)
Branch Coverage 40.30% (93121/231048)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 54.05% (40/74) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 80.00% (60/75) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 74.39% (31433/42256)
Line Coverage 58.63% (346436/590893)
Region Coverage 54.89% (288471/525561)
Branch Coverage 55.87% (129107/231086)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 89.19% (66/74) 🎉
Increment coverage report
Complete coverage report

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.

2 participants