Skip to content

[fix](be) Correct FileScannerV2 Iceberg delete handling#65502

Open
Gabriel39 wants to merge 7 commits into
apache:masterfrom
Gabriel39:codex/fix-v2-iceberg-delete-handling
Open

[fix](be) Correct FileScannerV2 Iceberg delete handling#65502
Gabriel39 wants to merge 7 commits into
apache:masterfrom
Gabriel39:codex/fix-v2-iceberg-delete-handling

Conversation

@Gabriel39

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

FileScannerV2 had three related Iceberg delete correctness and efficiency gaps:

  • Materialized readers applied delete expressions as ordinary predicates, keeping rows for which a delete predicate returned true.
  • Equality-delete fields absent from older data files were rejected, although Iceberg schema evolution requires those values to be read as NULL. Name-mapped files without physical field ids also could not resolve the equality key.
  • Equality and position delete files shared by multiple data splits were parsed again for every split, and position bounds were ignored.

This change builds an explicit keep filter by inverting delete matches, resolves equality keys by field id or name mapping and supplies typed NULL literals for missing fields, honors position bounds, and caches parsed delete data in SplitReadOptions.cache for reuse across splits. The implementation is limited to FileScannerV2.

Release note

Fix Iceberg V2 row-level delete filtering across schema evolution and reuse parsed delete files across splits.

Check List (For Author)

  • Test: Unit Test
    • 26 focused EqualityDeletePredicate, JSON materialized-reader, and Iceberg V2 delete tests passed
    • Added V2 differential coverage to the existing Iceberg equality-delete schema-evolution regression suite; the remote run was skipped because external Iceberg tests are disabled in that environment
  • Behavior changed: Yes, FileScannerV2 now applies Iceberg equality and position deletes with correct semantics
  • Does this need documentation: No

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: FileScannerV2 kept rows matched by delete predicates because it applied delete expressions with ordinary predicate polarity. Iceberg equality deletes also rejected fields absent from older data files instead of treating them as NULL, and delete files shared by multiple splits were parsed repeatedly. Invert delete predicate results into a keep filter, resolve equality-delete keys through field ids or name mappings with typed NULL literals for absent fields, honor position bounds, and cache parsed equality and position delete data through the split cache.

### Release note

Fix Iceberg V2 row-level delete filtering across schema evolution and reuse parsed delete files across splits.

### Check List (For Author)

- Test: Unit Test
    - 26 focused EqualityDeletePredicate, JSON materialized-reader, and Iceberg V2 delete tests passed
    - Iceberg schema-evolution regression coverage was updated; execution was skipped because the validation environment disables external Iceberg tests
- Behavior changed: Yes, FileScannerV2 now applies Iceberg equality and position deletes with correct semantics
- Does this need documentation: 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 Gabriel39 marked this pull request as ready for review July 12, 2026 12:35
@Gabriel39 Gabriel39 requested a review from yiguolei as a code owner July 12, 2026 12:35
@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@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.

Automated review completed after two rounds: main risk scan, normal coverage subagents, risk-focused subagents, and a convergence round all ended with the final M1-M5 set and NO_NEW_VALUABLE_FINDINGS after that set stabilized. I found five issues that should be fixed before merge.

Checkpoint conclusions:

  • Scope/user focus: Reviewed the authoritative diff for the changed Iceberg V2 delete, reader, and test paths. No additional user-provided focus was supplied.
  • Correctness/schema compatibility: Issues found in equality-delete row sizing and BY_NAME schema-evolution binding.
  • Lifecycle/cache: Issue found in shared delete-file cache scoping across split filesystem identities.
  • Tests: Added tests cover useful positive paths but miss the accepted edge cases, and one regression leaks session state on failure.
  • Validation: Static review only; per the review prompt I did not build, run tests, or modify source. .worktree_initialized, thirdparty/installed, and thirdparty/installed/bin/protoc are absent in this checkout.

@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.

Automated review completed after two rounds: main risk scan, normal coverage subagents, risk-focused subagents, and a convergence round all ended with the final M1-M5 set and NO_NEW_VALUABLE_FINDINGS after that set stabilized. I found five issues that should be fixed before merge.

Checkpoint conclusions:

  • Scope/user focus: Reviewed the authoritative diff for the changed Iceberg V2 delete, reader, and test paths. No additional user-provided focus was supplied.
  • Correctness/schema compatibility: Issues found in equality-delete row sizing and BY_NAME schema-evolution binding.
  • Lifecycle/cache: Issue found in shared delete-file cache scoping across split filesystem identities.
  • Tests: Added tests cover useful positive paths but miss the accepted edge cases, and one regression leaks session state on failure.
  • Validation: Static review only; per the review prompt I did not build, run tests, or modify source. .worktree_initialized, thirdparty/installed, and thirdparty/installed/bin/protoc are absent in this checkout.

Comment thread be/src/format_v2/expr/equality_delete_predicate.cpp Outdated
// Migrated Hive tables may have no field ids in their physical files. Once the mapper has
// selected BY_NAME mode, bind the delete key by its delete-file name or by the table column's
// historical name mapping, exactly as normal column materialization does for a renamed field.
const auto table_field_it = std::ranges::find_if(

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.

This lookup makes equality-delete binding depend on the query projection. _projected_columns comes from the scan's required slots, so a query that reads only data or count(*) does not necessarily include the equality key's table metadata. For an old field-id-less file with physical legacy_id, a current table field current_id with name_mapping = legacy_id, and an equality delete file for field id 0 named current_id, the test where current_id is projected works, but SELECT data ... reaches this code with no table field for id 0. The fallback then treats the key as a missing column and uses a NULL literal, so a non-null delete key like current_id = 2 will not delete the row. Equality-delete key binding needs to use table schema/history metadata independent of the selected columns, or add the delete key as a hidden mapping before resolving the data file field.

Comment thread be/src/format_v2/table/iceberg_reader.cpp Outdated
Comment thread be/src/format_v2/table/iceberg_reader.cpp Outdated
@github-actions github-actions Bot dismissed their stale review July 12, 2026 13:07

Superseded by review 4680092049 after repairing inline anchors.

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65502

Problem Summary: Follow-up review found that equality-delete evaluation could derive a zero batch size from an unread first block column, BY_NAME equality-key binding depended on query projection and did not share the mapper's complete name semantics, and parsed delete-file caches were not isolated by filesystem identity. It also found that the Iceberg regression suite could leak its FileScannerV2 session override after a failure. Derive predicate row count from populated block columns, resolve hidden equality keys from the complete current table schema through the common BY_NAME matcher, include fs_name in both delete cache keys, and restore the session variable in a finally block.

### Release note

Fix Iceberg V2 equality and position delete handling for lazy reads, schema evolution, and multi-filesystem scans.

### Check List (For Author)

- Test: Unit Test / Regression test
    - `EqualityDeletePredicateTest.*:IcebergV2ReaderTest.Iceberg*Delete*` (29 tests)
    - Iceberg schema-evolution regression suite loaded successfully; external Iceberg data execution was disabled by the validation environment
- Behavior changed: Yes, V2 delete filtering now uses reliable batch sizing, complete schema name mapping, and filesystem-scoped caches
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@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.

Automated review completed after three rounds, including main-agent risk scan, full-review subagents, risk-focused subagents, candidate verification, duplicate suppression against existing threads, and final convergence.

I found three additional blocking issues in the current head:

  • Missing equality-delete keys are always compared as NULL, which does not preserve Iceberg initial-default/default semantics for old files.
  • Equality-delete cache keys are still structurally ambiguous when raw paths are concatenated with colon-prefixed field ids.
  • IcebergTableReader::prepare_split() now reads SplitReadOptions::cache unconditionally even though the pointer has no default initializer.

Checkpoint conclusions: data correctness is still at risk for equality deletes in the missing/defaulted-column and cache-collision cases; lifecycle/test determinism is at risk for the uninitialized cache pointer; concurrency did not show a distinct new issue; position-delete grouping/bounds and delete-conjunct polarity were rechecked without additional non-duplicate findings; no configuration, persistence, FE-BE protocol, or observability change requires a separate comment here.

User focus: no additional user-provided focus was supplied.

Validation: static review only. I did not build or run tests, per the review instructions for this GitHub Actions review environment.

Comment thread be/src/format_v2/table/iceberg_reader.cpp Outdated
Comment thread be/src/format_v2/table/iceberg_reader.cpp
Comment thread be/src/format_v2/table/iceberg_reader.cpp
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65502

Problem Summary: Iceberg V2 equality delete handling could treat fields missing from old data files as NULL even when the field has a non-null initial default. Delete-file cache keys also allowed structural collisions between raw paths and appended equality field ids, and split cache pointers could be read before initialization. Carry Iceberg initial defaults through FE and BE schema metadata, use them for missing equality keys, length-encode cache key components, and initialize the split cache pointer. The existing lazy-batch row-count fix prevents the attached equality-delete filter-size coredump.

### Release note

Fix Iceberg V2 equality and position delete correctness across schema evolution and cached splits.

### Check List (For Author)

- Test: Regression test / Unit Test
    - ./build.sh --be --fe
    - EqualityDeletePredicateTest.*:IcebergV2ReaderTest.Iceberg*Delete* (30 passed)
    - ExternalUtilTest, IcebergUtilsTest (25 passed)
- Behavior changed: Yes, missing Iceberg fields use their initial defaults and delete cache keys are structurally isolated
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@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 5:19 PM.
Workflow run: https://github.com/apache/doris/actions/runs/29198769084

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: 29931 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit e62593c6ad5bda74410250aadae14782de70105b, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17780	4065	4110	4065
q2	2014	337	203	203
q3	10563	1464	846	846
q4	4759	474	346	346
q5	8280	859	578	578
q6	324	168	138	138
q7	849	859	629	629
q8	10645	1640	1588	1588
q9	5934	4449	4422	4422
q10	6853	1813	1500	1500
q11	513	347	315	315
q12	722	570	431	431
q13	18105	3426	2809	2809
q14	271	267	251	251
q15	q16	780	776	712	712
q17	1026	1002	929	929
q18	6795	5695	5663	5663
q19	1164	1293	1148	1148
q20	744	650	532	532
q21	5541	2627	2526	2526
q22	436	359	300	300
Total cold run time: 104098 ms
Total hot run time: 29931 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4388	4328	4292	4292
q2	279	319	212	212
q3	4547	4965	4427	4427
q4	2063	2143	1383	1383
q5	4481	4355	4352	4352
q6	223	180	203	180
q7	2355	1897	1694	1694
q8	2547	2281	2263	2263
q9	8255	7831	7921	7831
q10	4769	4726	4250	4250
q11	578	479	425	425
q12	774	763	577	577
q13	3218	3610	2902	2902
q14	294	291	277	277
q15	q16	709	718	651	651
q17	1351	1553	1321	1321
q18	7948	7356	7335	7335
q19	1079	1049	1098	1049
q20	2230	2215	1930	1930
q21	5266	4636	4490	4490
q22	514	457	412	412
Total cold run time: 57868 ms
Total hot run time: 52253 ms

@hello-stephen

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

query5	4327	633	492	492
query6	471	223	206	206
query7	4950	610	336	336
query8	341	185	172	172
query9	8805	4102	4069	4069
query10	464	362	284	284
query11	5885	2348	2154	2154
query12	160	105	103	103
query13	1296	634	428	428
query14	6310	5290	4987	4987
query14_1	4328	4296	4277	4277
query15	216	209	180	180
query16	1035	475	456	456
query17	1109	704	586	586
query18	2471	471	354	354
query19	203	182	146	146
query20	111	106	106	106
query21	225	161	133	133
query22	13664	13657	13384	13384
query23	17419	16531	16129	16129
query23_1	16364	16335	16208	16208
query24	7491	1774	1280	1280
query24_1	1313	1309	1301	1301
query25	554	454	396	396
query26	1325	355	215	215
query27	2579	617	371	371
query28	4514	2024	2037	2024
query29	1113	644	509	509
query30	343	273	233	233
query31	1114	1094	979	979
query32	111	64	64	64
query33	533	341	275	275
query34	1185	1132	650	650
query35	770	826	683	683
query36	1428	1399	1217	1217
query37	156	109	97	97
query38	1874	1713	1647	1647
query39	926	919	933	919
query39_1	865	882	887	882
query40	254	162	143	143
query41	71	104	61	61
query42	92	95	93	93
query43	325	322	286	286
query44	1414	754	771	754
query45	200	184	180	180
query46	1079	1229	748	748
query47	2367	2381	2225	2225
query48	404	399	303	303
query49	582	416	317	317
query50	1165	428	338	338
query51	10937	11030	11044	11030
query52	85	89	74	74
query53	256	293	204	204
query54	283	232	213	213
query55	74	72	65	65
query56	290	296	294	294
query57	1418	1408	1319	1319
query58	278	260	252	252
query59	1588	1714	1497	1497
query60	299	275	260	260
query61	154	153	152	152
query62	699	646	560	560
query63	242	200	201	200
query64	2829	1046	841	841
query65	4884	4765	4761	4761
query66	1806	522	440	440
query67	29611	29530	29291	29291
query68	3189	1519	1033	1033
query69	413	308	271	271
query70	1094	994	980	980
query71	348	324	317	317
query72	3040	2733	2483	2483
query73	796	819	472	472
query74	5093	4957	4751	4751
query75	2619	2604	2213	2213
query76	2332	1201	800	800
query77	355	376	288	288
query78	12181	12269	11670	11670
query79	1274	1106	787	787
query80	583	537	439	439
query81	458	320	290	290
query82	235	157	122	122
query83	311	317	295	295
query84	270	161	127	127
query85	886	582	516	516
query86	335	295	272	272
query87	1813	1815	1759	1759
query88	3685	2868	2777	2777
query89	429	403	373	373
query90	2166	194	200	194
query91	199	187	161	161
query92	63	61	58	58
query93	1485	1505	1006	1006
query94	534	372	318	318
query95	781	503	484	484
query96	1132	812	357	357
query97	2681	2699	2545	2545
query98	219	213	206	206
query99	1163	1179	1047	1047
Total cold run time: 264672 ms
Total hot run time: 180574 ms

@hello-stephen

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

query1	0.01	0.00	0.00
query2	0.09	0.05	0.05
query3	0.26	0.13	0.13
query4	1.62	0.14	0.14
query5	0.24	0.23	0.23
query6	1.24	1.06	1.09
query7	0.04	0.01	0.00
query8	0.06	0.04	0.03
query9	0.40	0.31	0.31
query10	0.56	0.55	0.54
query11	0.20	0.15	0.14
query12	0.17	0.15	0.15
query13	0.46	0.47	0.48
query14	1.03	1.02	1.01
query15	0.61	0.59	0.59
query16	0.32	0.33	0.32
query17	1.16	1.09	1.12
query18	0.22	0.21	0.22
query19	2.05	1.97	1.98
query20	0.02	0.01	0.02
query21	15.45	0.23	0.13
query22	4.70	0.06	0.05
query23	16.12	0.31	0.13
query24	2.96	0.43	0.33
query25	0.12	0.05	0.05
query26	0.73	0.21	0.15
query27	0.04	0.04	0.04
query28	3.51	0.89	0.52
query29	12.47	4.04	3.19
query30	0.29	0.16	0.17
query31	2.77	0.58	0.32
query32	3.22	0.59	0.48
query33	3.21	3.22	3.16
query34	15.52	4.26	3.50
query35	3.54	3.50	3.56
query36	0.56	0.43	0.42
query37	0.09	0.07	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.16	0.16
query41	0.08	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.04
Total cold run time: 96.49 s
Total hot run time: 24.98 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 95.40% (249/261) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 74.76% (30190/40381)
Line Coverage 58.76% (331435/564096)
Region Coverage 55.56% (278291/500859)
Branch Coverage 56.77% (122900/216492)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 0.00% (0/10) 🎉
Increment coverage report
Complete coverage report

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#65502

Problem Summary: The Iceberg equality-delete schema-change regression test indexed the SQL string literal before invoking the sql helper. Groovy therefore attempted to take index 1 from a one-character string and failed before the test queries ran. Store the SQL result rows first, validate that the variable exists, and then extract the session value for restoration.

### Release note

None

### Check List (For Author)

- Test: Regression test
    - Remote minimal regression test covering the same SQL-result extraction passed
- Behavior changed: No
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@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.

Static review of PR 65502 at head daf0c5006176be580f7599e606fcc4eaaf1d6283. I found one remaining blocker, left inline.

Checkpoint conclusions: goal/scope is clear and the remaining issue is patch-scoped to the new Iceberg initial-default carrier. Concurrency/lifecycle/cache behavior was rechecked against the existing threads and current patch; I found no additional non-duplicated cache or split-lifetime issue. Compatibility and parallel paths were checked for scanner V2 versus fallback, Parquet/ORC delete handling, thrift optionality, and generated outputs; no additional issue found beyond the default-carrier problem. Condition checks and delete-predicate polarity/row sizing were rechecked across BE consumers; no additional issue found. Tests cover integer defaults and several BE delete paths, but miss the timestamp and VARBINARY-mapped default cases called out inline. Observability is not materially changed. Transaction/persistence/data-write boundaries are not directly changed; FE-BE schema/default transport is the relevant boundary and is where the blocker sits. Performance risk appears bounded to the new delete/default handling paths; no extra performance finding. Static review only per prompt; I did not build or run tests.

Comment thread fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java Outdated
@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17650	4128	4095	4095
q2	2013	345	200	200
q3	10294	1440	812	812
q4	4682	468	341	341
q5	7519	854	588	588
q6	180	174	142	142
q7	786	840	631	631
q8	9351	1554	1571	1554
q9	5664	4421	4375	4375
q10	6695	1844	1521	1521
q11	530	342	319	319
q12	707	565	449	449
q13	18147	3457	2780	2780
q14	272	260	243	243
q15	q16	796	781	713	713
q17	1010	917	966	917
q18	7139	5868	5530	5530
q19	1157	1268	1077	1077
q20	730	639	541	541
q21	5620	2573	2402	2402
q22	442	376	305	305
Total cold run time: 101384 ms
Total hot run time: 29535 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4469	4365	4375	4365
q2	294	318	222	222
q3	4599	4967	4361	4361
q4	2073	2150	1403	1403
q5	4476	4368	4320	4320
q6	230	177	135	135
q7	1710	2040	1904	1904
q8	2689	2342	2366	2342
q9	8134	8128	7869	7869
q10	4718	4664	4246	4246
q11	589	458	404	404
q12	790	784	557	557
q13	3241	3594	3004	3004
q14	312	314	280	280
q15	q16	717	738	654	654
q17	1385	1331	1351	1331
q18	8079	7466	7378	7378
q19	1152	1132	1099	1099
q20	2220	2213	1945	1945
q21	5270	4625	4550	4550
q22	520	450	404	404
Total cold run time: 57667 ms
Total hot run time: 52773 ms

@hello-stephen

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

query5	4360	618	494	494
query6	446	214	223	214
query7	4841	590	348	348
query8	336	184	173	173
query9	8775	4114	4073	4073
query10	482	365	286	286
query11	5938	2366	2173	2173
query12	163	103	106	103
query13	1265	629	450	450
query14	6888	5340	4970	4970
query14_1	4293	4307	4332	4307
query15	217	205	185	185
query16	1044	486	485	485
query17	1292	735	593	593
query18	2712	477	358	358
query19	284	196	162	162
query20	117	107	110	107
query21	231	159	137	137
query22	13653	13558	13320	13320
query23	17397	16563	16237	16237
query23_1	16255	16284	16318	16284
query24	7495	1789	1322	1322
query24_1	1330	1315	1323	1315
query25	570	469	401	401
query26	1299	374	220	220
query27	2484	601	389	389
query28	4374	2063	2039	2039
query29	1093	654	511	511
query30	339	261	234	234
query31	1139	1100	983	983
query32	104	64	61	61
query33	547	338	274	274
query34	1211	1149	658	658
query35	763	811	666	666
query36	1395	1390	1243	1243
query37	162	159	92	92
query38	1870	1710	1634	1634
query39	916	921	898	898
query39_1	877	870	884	870
query40	239	161	136	136
query41	65	61	61	61
query42	94	92	94	92
query43	327	325	278	278
query44	1411	773	766	766
query45	196	199	178	178
query46	1029	1186	749	749
query47	2386	2349	2243	2243
query48	406	370	296	296
query49	584	437	314	314
query50	1037	442	350	350
query51	10903	10797	10802	10797
query52	93	89	76	76
query53	264	275	204	204
query54	281	248	228	228
query55	79	71	73	71
query56	297	284	270	270
query57	1418	1421	1326	1326
query58	290	258	265	258
query59	1536	1630	1462	1462
query60	322	269	261	261
query61	160	152	155	152
query62	707	656	582	582
query63	253	202	207	202
query64	2791	1008	906	906
query65	4886	4739	4761	4739
query66	1767	510	384	384
query67	29560	29511	29350	29350
query68	3128	1581	1007	1007
query69	413	311	266	266
query70	1072	994	956	956
query71	340	315	319	315
query72	3125	2693	2388	2388
query73	874	765	460	460
query74	5123	4965	4751	4751
query75	2618	2604	2249	2249
query76	2324	1193	765	765
query77	358	370	274	274
query78	12120	12350	11765	11765
query79	1442	1182	807	807
query80	1323	518	471	471
query81	539	326	278	278
query82	656	163	125	125
query83	389	321	297	297
query84	284	158	127	127
query85	956	606	530	530
query86	440	290	261	261
query87	1841	1832	1727	1727
query88	3717	2834	2836	2834
query89	464	402	367	367
query90	1959	193	201	193
query91	209	195	165	165
query92	65	65	60	60
query93	1730	1448	957	957
query94	714	350	331	331
query95	787	504	478	478
query96	1068	756	357	357
query97	2683	2653	2551	2551
query98	210	207	198	198
query99	1151	1168	1031	1031
Total cold run time: 266816 ms
Total hot run time: 180849 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.10	0.05	0.04
query3	0.26	0.13	0.14
query4	1.61	0.15	0.13
query5	0.24	0.23	0.23
query6	1.26	1.08	1.08
query7	0.04	0.01	0.00
query8	0.06	0.03	0.04
query9	0.40	0.32	0.33
query10	0.58	0.59	0.55
query11	0.20	0.14	0.14
query12	0.18	0.14	0.14
query13	0.47	0.47	0.48
query14	1.03	1.01	1.01
query15	0.61	0.58	0.61
query16	0.33	0.33	0.33
query17	1.08	1.16	1.10
query18	0.23	0.22	0.23
query19	2.03	1.97	1.98
query20	0.01	0.01	0.01
query21	15.44	0.23	0.13
query22	4.70	0.05	0.05
query23	16.11	0.32	0.12
query24	2.93	0.42	0.33
query25	0.11	0.06	0.05
query26	0.76	0.20	0.15
query27	0.04	0.04	0.04
query28	3.52	0.89	0.53
query29	12.47	4.14	3.26
query30	0.28	0.16	0.17
query31	2.78	0.57	0.31
query32	3.22	0.58	0.49
query33	3.15	3.30	3.22
query34	15.55	4.22	3.52
query35	3.54	3.51	3.53
query36	0.56	0.45	0.43
query37	0.10	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.02
query40	0.17	0.16	0.15
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.04
Total cold run time: 96.42 s
Total hot run time: 25.15 s

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65502

Problem Summary: Iceberg equality-delete keys can be absent from older data files after schema evolution. Both scanner implementations treated such keys as NULL even when Iceberg assigned a non-null initial default, and the default carrier was not type-safe for timestamp and VARBINARY-mapped values. This change sends complete current schema metadata, normalizes initial defaults by type, materializes missing keys in FileScanner V1, and builds typed missing-key expressions in FileScanner V2. It preserves Iceberg type promotion when the table and delete-file key types differ.

### Release note

Fix Iceberg equality deletes for evolved columns with initial defaults in FileScanner V1 and V2.

### Check List (For Author)

- Test: Unit Test
    - Remote BE ASAN build
    - 3 focused Iceberg BE unit tests
    - IcebergUtilsTest#testParseSchemaPreservesInitialDefault
    - C++ format check
- Behavior changed: Yes, both file scanners now apply equality deletes using the logical Iceberg initial value for columns absent from older files
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

### What problem does this PR solve?

Issue Number: close #0

Related PR: apache#65502

Problem Summary: Format the missing Iceberg equality-delete key helper with the project clang-format 16 style.

### Release note

None

### Check List (For Author)

- Test: No need to test (format-only change); clang-format 16 verification passed
- Behavior changed: No
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@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.

Automated static review for PR 65502 at live head 7c234e2ae69228b430977711dbcac3e6a1d3e448.

I found four remaining blocking issues and left inline comments.

Checkpoint conclusions:

  • Scope/user focus: reviewed the authoritative Iceberg delete/default/schema-change diff; no additional user-provided focus was supplied.
  • Correctness/schema compatibility: remaining issues affect BY_NAME equality-delete binding, binary-like initial defaults, V1 ORC missing-key materialization, and TIMESTAMPTZ equality-delete semantics.
  • Lifecycle/cache/row-count: rechecked split cache keys, failed-load behavior, position bounds, delete polarity, and row-count carriers; no additional non-duplicate finding beyond existing threads.
  • Tests: current tests miss the accepted edge cases called out inline.
  • Validation: static review only; per review prompt I did not build, run tests, or modify source. Live head differs from the local checkout only by a formatting-only change in build_missing_equality_delete_key_expr, which does not affect these findings.

return field.has_identifier_field_id() &&
field.get_identifier_field_id() == field_id;
});
if (field_it != _data_reader.file_schema.end() ||

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.

This still lets a partial-id migrated file bypass BY_NAME binding. mapping_mode() intentionally switches the split to BY_NAME when any data column lacks an Iceberg field id, so normal scan columns are resolved by ColumnMapper's name/identifier/alias rules. This helper, however, first accepts any file column whose field id equals the equality key id and returns before it checks the mode. If an imported file has the real key column matched only by name_mapping and some other physical column carrying the stale key id, the delete predicate compares against that unrelated column and misses or misapplies equality deletes. Please skip the id fast path when mapping_mode() == BY_NAME and resolve hidden equality keys through the same BY_NAME matcher as normal materialization.

bytes.putLong(uuid.getLeastSignificantBits());
return Base64.getEncoder().encodeToString(bytes.array());
}
return humanValue;

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.

This still sends the wrong representation for binary-like defaults when varbinary mapping is disabled. In that mode UUID/BINARY become Doris STRING and FIXED becomes CHAR, so the BE branch that Base64-decodes TYPE_VARBINARY never runs; the STRING/CHAR serde keeps this humanValue text as-is. Equality delete files, however, read BYTE_ARRAY/FIXED_LEN_BYTE_ARRAY keys as raw bytes into string columns. For example an added BINARY default 00 01 02 ff is carried as "AAEC/w==", but the delete key column contains raw bytes, so the missing-key literal does not match the delete row. Please normalize UUID/BINARY/FIXED defaults to the same raw-byte representation for the STRING/CHAR mapping as well, or carry enough type metadata for BE to decode them before building the literal.

if (i < _expand_columns.size()) {
_expand_columns[i].name = table_col_name;
}
if (it == field_id_to_file_col_name.end()) {

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.

This missing-key branch registers a constant for the equality key, but the ORC path still treats the same synthetic column as physically present. After this branch runs, the loop still appends table_col_name to ctx->column_names and add_children() marks it with exists=true. ORC therefore does not put it in _fill_missing_cols; _init_file_column_mapping() adds it to _read_file_cols, and the batch read later returns Wrong read column because the old ORC file has no such physical column. Please leave the hidden equality key in the block/default-materialization path without marking it as an existing ORC child, and add an end-to-end V1 ORC missing-key equality-delete test.

EqualityDeleteFilter* result) {
DORIS_CHECK(result != nullptr);
std::unique_ptr<format::FileReader> reader;
RETURN_IF_ERROR(_create_delete_file_reader(delete_file, scan_params, delete_io_ctx, &reader));

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.

The reader created here needs the same TIMESTAMPTZ mapping as the data reader. Normal TableReader construction passes enable_mapping_timestamp_tz into Parquet/ORC readers, but _create_delete_file_reader() uses those constructors' default false, so adjusted-UTC timestamp keys in equality-delete files are loaded as DATETIMEV2. The data side may still be TIMESTAMPTZ and then gets cast to the delete key type through the session timezone before _equal() compares raw column values. Around a DST fold, two different instants can collapse to the same local DATETIMEV2 and an equality delete for one instant can delete rows for the other. Please pass the effective timestamp-tz mapping flag into V2 delete-file readers and add equality-delete coverage with enable_mapping_timestamp_tz=true in a non-UTC timezone.

@github-actions

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/29222716042

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

@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.

Automated static review for PR 65502 at head 7c234e2ae69228b430977711dbcac3e6a1d3e448.

I found one remaining non-duplicated blocking issue and left it inline. During live duplicate suppression, two verified findings were not resubmitted because current-head comments 3568056557 and 3568056554 already cover the V1 ORC missing-key path and the enable.mapping.varbinary=false binary-like default representation issue.

Checkpoint conclusions:

  • Scope/user focus: reviewed the authoritative bundle diff and changed-file list; no additional user-provided focus was supplied.
  • Correctness/schema compatibility: one remaining issue affects Iceberg timestamp-with-zone initial-default normalization when enable.mapping.timestamp_tz=false.
  • Lifecycle/cache/row-count: rechecked split cache keys, cache failure behavior, delete polarity, row-count carriers, and V1/V2 paths; no additional non-duplicate finding remains beyond existing live comments.
  • Tests: current tests miss TimestampType.withZone() initial defaults with the default timestamp-tz mapping disabled.
  • Validation: static review only per prompt. I did not build or run tests. .worktree_initialized, thirdparty/installed, and thirdparty/installed/bin/protoc are absent in this checkout; git diff --check produced no output.

if (type.typeId() == TypeID.TIMESTAMP) {
// Iceberg formats timestamps as ISO-8601 (for example 2024-01-01T00:00:00), while
// Doris' DATETIMEV2 default parser requires a space between the date and time.
return humanValue.replace('T', ' ');

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.

This still misses the timestamp-with-zone branch when enable.mapping.timestamp_tz is left at its default false. parseSchema() maps TimestampType.shouldAdjustToUTC() to Doris DATETIMEV2 unless that flag is enabled, but serializeInitialDefault() only knows about enableMappingVarbinary and just replaces T in the Iceberg human string. Iceberg timestamptz human strings carry an offset, so a value like 2024-01-01 00:00:00.123456+00:00 is later parsed by BE through the restored DATETIMEV2 type. DataTypeDateTimeV2SerDe::from_olap_string() expects YYYY-MM-DD HH:MM:SS[.ffffff] and falls back to MIN_DATETIME_V2 on parse failure, so old files missing this equality key compare against the wrong default and matching deletes can be missed. Please pass enableMappingTimestampTz into this normalization and emit the offset-free Doris datetime representation when the Iceberg field is timestamptz but Doris maps it to DATETIMEV2; also add coverage for TimestampType.withZone() with enable.mapping.timestamp_tz=false.

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65502

Problem Summary: Iceberg equality-delete keys can be hidden from the query projection, absent from older data files, or mapped by historical names. V1 ORC attempted to read absent keys as physical columns, BY_NAME resolution could select stale field IDs, and binary or timestamptz initial defaults could lose their physical representation. Preserve complete schema metadata, resolve BY_NAME keys only by names and aliases, delay missing-key materialization until the batch row count is known, preserve binary defaults through an explicit Base64 marker, and inherit timestamp-with-zone mapping for delete readers.

### Release note

Fix Iceberg equality-delete filtering across schema evolution, binary mappings, ORC files, and timestamp-with-zone values.

### Check List (For Author)

- Test:
    - Unit Test: 65 focused BE Iceberg and equality-delete tests passed
    - Unit Test: 25 focused FE schema and Iceberg utility tests passed
- Behavior changed: Yes, equality deletes now use correct schema mappings and initial-default representations
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@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.

I found four blocking correctness/compatibility issues in the Iceberg equality-delete schema-evolution changes.

Critical checkpoint conclusions:

  • Goal/test proof: the goal is to preserve equality-delete correctness across evolved schemas and missing keys. Current tests cover many default and V2 cases, but they do not cover the four reported cases.
  • Scope/focus: the change is broadly focused on this feature, but the FE schema/default metadata source and V1 hidden-key helper paths need tightening.
  • Concurrency/lifecycle: no new thread or lock issue found. The V2 delete-file cache lifecycle/keying was rechecked and is covered by current code or existing comments.
  • Compatibility/protocol: MCF-4 is a blocking FE/BE rolling/pruned-descriptor compatibility gap. Optional Thrift fields otherwise look compatible.
  • Parallel paths: V2 has fallbacks that V1 lacks; V1 multi-key and name-mapping hidden-key paths diverge from the corrected V2 behavior.
  • Error handling, memory, nullability: MCF-2 is a const-column/nullability materialization issue, and MCF-4 is a hard CHECK on descriptor shape. No additional memory-safety issue found.
  • Data correctness: MCF-1, MCF-2, and MCF-3 can leave equality-deleted rows visible.
  • Observability/config/persistence/writes: no new config, persistence, or write-path concern found.
  • Tests: missing coverage remains for snapshot binary-like defaults, V1 multi-key missing/default keys, V1 id-less/name-mapped hidden keys, and V1 pruned-descriptor compatibility.

Review focus: no additional user-provided focus was supplied.
Validation: review-only per prompt; no builds or tests were run. Round 3 convergence completed with all subagents returning NO_NEW_VALUABLE_FINDINGS against this comment set.

// historical names, types, and initial defaults when an old data file lacks such a key.
ExternalUtil.initSchemaInfoForAllColumn(
params, -1L, source.getTargetTable().getColumns(), nameMapping,
IcebergUtils.getBase64EncodedInitialDefaultFieldIds(icebergTable.schema()));

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.

This uses the selected scan columns from source.getTargetTable().getColumns(), but computes the Base64-default field-id set from icebergTable.schema(), which is the table's latest schema. Time-travel/branch/tag scans load the requested snapshot into StatementContext; IcebergExternalTable.getFullSchema() then comes from that snapshot schema via IcebergUtils.getIcebergSchema(). If the selected snapshot still has a UUID/BINARY/FIXED field with an initial default that is absent or changed in the latest schema, ExternalUtil will send the default string without initial_default_value_base64_encoded. BE then treats the Base64 text as the literal string/VARBINARY default instead of decoding to the raw equality key bytes, so missing-key equality deletes can be missed. Please derive the Base64 field-id set from the same Iceberg schema/schema id used to build the scan columns, or carry this encoding bit with each parsed Column, and add snapshot/branch coverage for a binary-like initial default.

[&](const ColumnWithTypeAndName& col) { return col.name == name; });
DORIS_CHECK(expand_col != _expand_columns.end());
(*this->col_name_to_block_idx_ref())[name] = block->columns();
block->insert({value->clone_resized(rows), expand_col->type, 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.

This leaves the synthesized missing-key column as a ColumnConst. That works for single-column equality deletes because SimpleEqualityDelete expands consts before probing, but the V1 multi-key path hashes data columns directly in MultiEqualityDelete::filter_data_block() and then compares them directly in _equal(). ColumnConst does not implement the batch update_hashes_with_value() method and its compare_at() expects the RHS to also be const, while delete-file columns are materialized normal columns. A delete file on (id, added_key) against an old data file where added_key is logically the initial default will fail before it can remove matching rows. Please insert a full column here (or normalize consts in MultiEqualityDelete) and add a V1 multi-column equality-delete test with one absent/defaulted key.

_expand_columns[i].name = table_col_name;
}

if (it == field_id_to_file_col_name.end()) {

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.

At this point ctx->table_info_node has already been built with the Iceberg name mapping and can resolve id-less files by alias, but the hidden equality-key remap ignores it and only probes field_id_to_file_col_name. For migrated Parquet/ORC files without field IDs, projected columns are still readable through *_field_id_with_name_mapping(), while an unprojected equality-delete key falls into this branch and gets materialized as NULL/default. A delete on a renamed field such as table current_id mapped to physical legacy_id can therefore leave matching rows visible. Please resolve the hidden key through the same name-mapping node before treating it as missing, and add V1 coverage for an unprojected equality key in an id-less/name-mapped file.

break;
}
}
DORIS_CHECK(table_field != nullptr);

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.

This new path assumes the FE scan descriptor always carries the full current schema. That is true for the new IcebergScanNode, but older/pruned descriptors used initSchemaInfoForPrunedColumn() and only included projected slots, so an unprojected equality-delete key can be absent from history_schema_info during a rolling upgrade or with an older descriptor. V2 handles that case by falling back to projected metadata or the delete-file name/type when full schema metadata is unavailable; V1 reaches this DORIS_CHECK(table_field != nullptr) and aborts before it can apply even the NULL/default-free semantics. Please add the same compatibility fallback or return a controlled non-crashing error for missing schema metadata, and cover a pruned-descriptor V1 missing-key case.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 89.36% (42/47) 🎉
Increment coverage report
Complete coverage report

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.

2 participants