Skip to content

[fix](iceberg) Decode Parquet timestamptz columns as instants by the Iceberg table type - #69845

Merged
Gabriel39 merged 3 commits into
apache:masterfrom
morningman:b2m-t35x-iceberg-int96-timestamptz
Nov 2, 2026
Merged

Gabriel39 merged 3 commits into
apache:masterfrom
morningman:b2m-t35x-iceberg-int96-timestamptz

Conversation

@morningman

@morningman morningman commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #69711

Problem Summary:

In short. An Iceberg timestamptz column stored as Parquet INT96, the encoding Spark and Hive use for timestamps, reads shifted by the session time zone's offset in any non-UTC session: in Asia/Shanghai, 2024-01-01T00:00:00Z reads as 2024-01-01 00:00:00+08:00, eight hours early, while Iceberg itself reads 2024-01-01 08:00:00+08:00. Equality deletes on such a key miss their rows for the same reason. This PR has the FE mark each timestamptz field of the scan's schema dictionary, and FileScannerV2 then decodes the matching Parquet columns of data files and equality-delete files as instants, which is how Iceberg reads them. It came up in the review of #69711.

Background.

  • Parquet INT96 has no logical annotation, so a reader cannot tell from the file whether an INT96 column holds an instant or a wall clock. Spark writes its timestamp type (an instant) as INT96 by default, and add_files, migrate and snapshot attach such files to Iceberg tables unchanged, as timestamptz columns.
  • Iceberg does not look at the file annotation at all: its Parquet readers choose the timestamp reader from the table type, so a timestamptz field reads INT96, and INT64 with or without the UTC annotation, as an instant.
  • The FE sends parquet_timestamp_semantics_version = 1 for every file scan. Parquet scans therefore always use FileScannerV2, and INT96 decodes as a UTC wall clock (DATETIMEV2) unless the table format supplies the semantics. Paimon has the same ambiguity (TIMESTAMP and TIMESTAMP_LTZ share INT96) and solves it with timestamp_is_adjusted_to_utc on each field of its schema dictionary, which PaimonReader applies to the file schema. The Iceberg FE never set that field.

The problem, and what it cost. For a timestamptz column, the projection casts the INT96 wall clock to TIMESTAMPTZ in the session time zone, so the instant moves by the session offset. Reproduced on master with an INT96 file attached like add_files and an Iceberg-written INT64 file holding the same instants, both in one table:

Session Rows Iceberg Java Before After
UTC INT96 and INT64 2024-01-01 00:00:00 UTC correct correct
Asia/Shanghai INT64 (written by Iceberg) 2024-01-01 08:00:00+08:00 correct correct
Asia/Shanghai INT96 (written by Spark) 2024-01-01 08:00:00+08:00 2024-01-01 00:00:00+08:00, 8 hours early 2024-01-01 08:00:00+08:00

The result is the same with field ids in the file, with only the table's name mapping, whatever the catalog's enable.mapping.timestamp_tz says (master always maps timestamptz to TIMESTAMPTZ), and with enable_file_scanner_v2 on or off. The same shift breaks equality deletes, which compare the projected values:

Equality delete on a timestamptz key, non-UTC session Before After
INT96 data key, Iceberg-written tombstone of the same instant the tombstone misses the row the row is deleted

Wrong values and missed or extra deletes come back without any error.

How this PR fixes it.

  • FE: IcebergSchemaUtils.buildField sets timestamp_is_adjusted_to_utc = true on every timestamptz field, at every nesting level; the dictionary also carries the hidden equality-delete keys. timestamp fields stay unmarked: marking them false would make BE read the Parquet statistics of an adjusted-UTC INT64 file as UTC wall clocks while its rows still decode as session wall clocks, so row-group pruning could drop matching rows.
  • BE, data files: IcebergTableReader::annotate_file_schema copies the marker onto the Parquet file column that TableColumnMapper will select for each field, using the mapper's own matchers (find_column_by_field_id / find_column_by_name) after the positional fallback ids are stamped, so field ids, the table's name mapping and positional ids all work. Like the mapper, it accepts an ID-less wrapper at every depth when allow_idless_complex_wrapper_projection is on. It then rewrites the column type with the helper PaimonReader already used, moved to schema_history_util as apply_file_timestamp_semantics. The mapping, the scan request and ParquetReader all see TIMESTAMPTZ, and INT96 decodes as an instant. Only a DATETIMEV2 or TIMESTAMPTZ leaf takes the marker; a TIMESTAMP_NS leaf keeps its nanoseconds. Columns without a marked leaf keep the types the file reader reported, and a plan from an older FE carries no marker and reads as before.
  • BE, equality deletes: three kinds of column are read without a ColumnMapping: a hidden key column, the parent struct that is read for the NULLs of a nested key the data file lacks, and the key columns of an equality-delete file. Their projections now carry the markers of the annotated file column (equality_delete_root_projection, built on attach_timestamp_semantics, which the mapper now exports). Without them Parquet would decode an INT96 leaf as DATETIMEV2 under a TIMESTAMPTZ file schema, and the read would fail its type check. That holds even when the query projects the column, because reading it as a predicate column replaces the projection its mapping marked. Equality-delete files get the same annotation, so tombstones and data keys of a timestamptz field are both instants. The DATETIMEV2 / TIMESTAMPTZ comparison rule from [fix](be) Compare Iceberg equality-delete keys in the wider of the delete and data file types #69711 stays for plans from older FEs.
  • Refactor: find_current_schema and build_schema_identity_from_external_field (now also carrying the marker) move to schema_history_util; TableReader's four copies of the current-schema lookup use the helper.

Results.

  • FE unit test IcebergSchemaUtilsTest#timestamptzFieldsCarryInstantMarkerAtEveryLevel: top-level, struct, list and map timestamptz fields are marked; timestamp and timestamp_ns fields are not, under both values of the mapping flag.
  • 5 new BE unit tests in iceberg_reader_test.cpp, all failing without the BE change: the file-schema annotation (field ids at every level, TIMESTAMP_NS untouched, ORC untouched); INT96 timestamptz in Asia/Shanghai with field ids, name mapping and positional ids, with and without the marker; INT96 inside a struct and a list; an INT96 equality key against an Iceberg tombstone, hidden or projected; and the DST fall-back above. Leaving the delete files unannotated fails the DST case (the second tombstone deletes the first row); projecting a hidden key or a tombstone without its marker fails both equality-delete cases with a read error; marking a TIMESTAMP_NS leaf fails the annotation case. With neither data nor delete files annotated (the reader before this PR), the INT96 key deletes nothing and the DST case deletes both rows. The existing case for an older FE's plan (no marker) still passes.
  • 2 more BE unit tests from review:
    • INT96 under a nested ID-less wrapper struct, read and used as an equality key. It fails when the walk accepts the wrapper only at the root.
    • A nested equality key missing from an older file whose parent struct holds an INT96 timestamptz. It checks the survivors with the parent hidden and projected, that the projected timestamp reads as an instant in Asia/Shanghai, and a plan without the marker. It fails with the reader's type check when the parent is read without the marker.
  • End to end on a local cluster: an Iceberg table holding an INT96 Parquet file attached like add_files (one table with field ids in the file, one relying on schema.name-mapping.default) and an Iceberg-written INT64 file with the same instants, read through three catalogs (enable.mapping.timestamp_tz unset, true, false), with enable_file_scanner_v2 on and off, in UTC and Asia/Shanghai: 24 combinations. Before this PR the INT96 rows were 8 hours early in every Asia/Shanghai combination (8 of the 16 measured); with it the INT96 rows equal the INT64 rows in all 24, and the INT64 rows and the UTC results are unchanged.

branch-4.1 and branch-4.2 have the same code.

Classes, and how they call each other.

  • IcebergSchemaUtils (FE): builds the schema dictionary; now marks timestamptz fields.
  • schema_history_util: find_current_schema, build_schema_identity_from_external_field, and apply_file_timestamp_semantics (moved from PaimonReader).
  • IcebergTableReader: annotate_file_schema → _apply_positional_id_fallback (the existing logic) and _apply_table_timestamp_semantics; _append_equality_delete_predicates (hidden keys), _build_missing_equality_delete_key_expr (the parent of a missing nested key) and _load_equality_delete_file (tombstones) project their columns with the markers through equality_delete_root_projection.
  • column_mapper: attach_timestamp_semantics is exported.
FE  IcebergSchemaUtils.buildField  --timestamptz-->  TField.timestamp_is_adjusted_to_utc = true
                                                     (history_schema_info, current_schema_id = -1)
BE  TableReader::open_reader
      +-- IcebergTableReader::annotate_file_schema                     (Parquet only)
      |     +-- _apply_positional_id_fallback
      |     +-- _apply_table_timestamp_semantics
      |           +-- find_current_schema, build_schema_identity_from_external_field
      |           +-- find_column_by_field_id | find_column_by_name    (the mapper's matchers)
      |           +-- apply_file_timestamp_semantics                   (shared with PaimonReader)
      +-- TableColumnMapper: mapping/projection take the marker from the file column
      +-- ParquetReader::open: apply_projection_timestamp_semantics -> INT96 decodes as TIMESTAMPTZ
    IcebergTableReader::_append_equality_delete_predicates
      +-- equality_delete_root_projection -> attach_timestamp_semantics   (hidden key)
      +-- _build_missing_equality_delete_key_expr
            +-- equality_delete_root_projection                           (parent of a missing key)
    IcebergTableReader::_load_equality_delete_file -> _apply_table_timestamp_semantics
                                                   + equality_delete_root_projection (tombstones)

ORC files are not changed: ORC's file type already separates TIMESTAMP from TIMESTAMP_INSTANT.

Release note

Fix Iceberg timestamptz columns stored as Parquet INT96 (written by Spark or Hive and attached with add_files, migrate or snapshot) reading shifted by the session time zone's offset in non-UTC sessions, and equality deletes on such keys missing their rows.

Check List (For Author)

  • Test
    • Regression test
    • Unit Test
      • BE: run-be-ut.sh --run --filter='IcebergV2ReaderTest.*:Paimon*ReaderTest.*:Hudi*Test.*:TableReaderTest.*:ColumnMapper*:FlussUnionLakeReaderTest.*:EqualityDeletePredicateTest.*:*PositionDelete*': 532 of 532 pass on macOS arm64 (ASAN), including the 5 new cases. After the first review round, IcebergV2ReaderTest passes 122 of 122, including the new nested ID-less wrapper case. It fails when the walk accepts the wrapper only at the root. After the second review round, the whole filter passes 534 of 534 and IcebergV2ReaderTest 123 of 123, including the new missing nested key case. It fails when the missing key's parent is read without the marker. They ran on a local base; the commit was then applied to master 2a4191c1dc9 without conflicts, and the changed sources and their test files were syntax-checked against that tree with the UT build flags.
      • FE: mvn -pl :fe-connector-iceberg -am package -Dtest=IcebergSchemaUtilsTest: 35 of 35 pass, checkstyle passes.
    • Manual test (add detailed scripts or steps below)
      • The local end-to-end check under Results, with an ASAN BE and the Iceberg connector built from this change.
    • No need to test or manual test. Explain why:
  • Behavior changed:
    • No.
    • Yes. Parquet columns of Iceberg timestamptz fields decode as instants by the table type, as Iceberg reads them: INT96 no longer shifts by the session offset, an INT64 timestamp without the UTC annotation under a timestamptz field reads its value as UTC, and equality deletes on timestamptz keys compare instants. Plans from an older FE are unchanged.
  • Does this need documentation?
    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

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

…Iceberg table type

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69711

Problem Summary: An Iceberg timestamptz column stored as Parquet INT96, the
encoding Spark and Hive use for timestamps and which add_files, migrate and
snapshot attach unchanged, read shifted by the session time zone's offset in
any non-UTC session. INT96 has no instant annotation, FE sends Parquet
timestamp-semantics version 1 for every file scan, so FileScannerV2 decoded
INT96 as a UTC wall clock and the projection cast it to TIMESTAMPTZ in the
session time zone: in Asia/Shanghai 2024-01-01T00:00:00Z read as
2024-01-01 00:00:00+08:00, eight hours early, while Iceberg reads
2024-01-01 08:00:00+08:00. Equality deletes on such a key missed their rows,
and in a catalog with enable.mapping.timestamp_tz=false a tombstone at a DST
fall-back time deleted both rows that share the local time.

FE now sets timestamp_is_adjusted_to_utc on every timestamptz field of the
Iceberg schema dictionary, as the Paimon connector does; timestamp fields stay
unmarked. IcebergTableReader copies the marker onto the Parquet file column the
mapper selects for each field (by field id, the table's name mapping or the
positional fallback ids) and rewrites its type with
apply_file_timestamp_semantics, moved from PaimonReader to schema_history_util.
Hidden equality-delete keys and equality-delete files carry the marker too, so
data keys and tombstones of a timestamptz field are both instants. Columns
without a marked leaf keep their file types, and plans from older FEs carry no
marker and read as before. find_current_schema and
build_schema_identity_from_external_field move to schema_history_util.

### Release note

Fix Iceberg timestamptz columns stored as Parquet INT96 (written by Spark or
Hive and attached with add_files, migrate or snapshot) reading shifted by the
session time zone's offset in non-UTC sessions, and equality deletes on such
keys missing their rows.

### Check List (For Author)

- Test: Unit Test (5 new BE cases in iceberg_reader_test.cpp; FE
  IcebergSchemaUtilsTest) and Manual test (local cluster reading INT96 and
  Iceberg-written INT64 files of one table)
- Behavior changed: Yes (Parquet columns of Iceberg timestamptz fields decode
  as instants by the table type)
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Selected model is at capacity. Please try a different model.
Workflow run: https://github.com/apache/doris/actions/runs/40874610164

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

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17635	5147	5067	5067
q2	2175	298	250	250
q3	10243	1403	818	818
q4	4683	476	338	338
q5	7436	800	522	522
q6	180	185	154	154
q7	752	799	597	597
q8	9249	1345	1108	1108
q9	5559	4220	4204	4204
q10	6813	1297	1039	1039
q11	448	251	232	232
q12	636	400	287	287
q13	18125	3714	3045	3045
q14	271	270	253	253
q15	q16	732	713	670	670
q17	1233	746	589	589
q18	6987	6202	6117	6117
q19	1258	954	611	611
q20	391	337	231	231
q21	5599	2150	2328	2150
q22	361	297	257	257
Total cold run time: 100766 ms
Total hot run time: 28539 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5622	5600	5638	5600
q2	716	562	544	544
q3	5220	5382	4935	4935
q4	2036	2160	1582	1582
q5	5246	5164	5703	5164
q6	278	196	148	148
q7	2368	2078	1872	1872
q8	3128	2909	2734	2734
q9	8090	8083	7972	7972
q10	3738	3728	3237	3237
q11	643	444	432	432
q12	718	696	520	520
q13	3575	3998	3282	3282
q14	324	303	305	303
q15	q16	682	739	660	660
q17	8485	7682	7138	7138
q18	12914	12228	12845	12228
q19	913	788	793	788
q20	2155	2168	1899	1899
q21	5787	4716	4842	4716
q22	531	450	398	398
Total cold run time: 73169 ms
Total hot run time: 66152 ms

@hello-stephen

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

query5	4322	575	439	439
query6	425	206	191	191
query7	4805	449	235	235
query8	328	168	150	150
query9	8760	3185	3237	3185
query10	491	323	273	273
query11	5999	3848	3570	3570
query12	153	93	89	89
query13	1270	468	331	331
query14	6083	3945	3683	3683
query14_1	3417	3663	3407	3407
query15	211	203	182	182
query16	961	423	425	423
query17	863	657	540	540
query18	2424	423	320	320
query19	197	167	136	136
query20	85	86	84	84
query21	205	131	107	107
query22	12951	13005	12850	12850
query23	13367	12961	12508	12508
query23_1	12584	12653	12800	12653
query24	7396	828	489	489
query24_1	494	489	488	488
query25	532	421	367	367
query26	1254	263	145	145
query27	2767	446	282	282
query28	4611	2077	2079	2077
query29	1583	593	460	460
query30	295	216	186	186
query31	1002	930	801	801
query32	147	94	91	91
query33	529	318	254	254
query34	901	866	528	528
query35	735	765	669	669
query36	835	802	717	717
query37	130	101	83	83
query38	1791	1775	1681	1681
query39	791	795	750	750
query39_1	739	724	737	724
query40	229	123	99	99
query41	75	73	69	69
query42	90	87	84	84
query43	352	370	311	311
query44	1325	732	717	717
query45	179	177	167	167
query46	860	962	597	597
query47	2706	2856	2872	2856
query48	313	309	226	226
query49	582	398	291	291
query50	676	281	208	208
query51	10627	10266	10141	10141
query52	79	78	70	70
query53	185	205	151	151
query54	233	188	177	177
query55	76	66	63	63
query56	259	217	219	217
query57	1622	1636	1554	1554
query58	276	268	259	259
query59	2130	2193	1946	1946
query60	294	242	230	230
query61	154	153	148	148
query62	395	337	282	282
query63	187	156	160	156
query64	2760	964	773	773
query65	3328	3272	3266	3266
query66	1789	452	297	297
query67	20184	20284	19972	19972
query68	3108	1008	628	628
query69	391	285	238	238
query70	850	782	782	782
query71	308	249	217	217
query72	2755	2583	2316	2316
query73	533	541	305	305
query74	4632	4521	4331	4331
query75	2306	2300	1899	1899
query76	2314	1020	587	587
query77	329	373	270	270
query78	9809	9695	9196	9196
query79	996	856	532	532
query80	1261	433	373	373
query81	585	330	271	271
query82	628	141	109	109
query83	304	194	188	188
query84	308	116	103	103
query85	880	442	381	381
query86	395	243	229	229
query87	1992	1931	1823	1823
query88	3818	2785	2778	2778
query89	334	279	246	246
query90	1901	172	166	166
query91	173	162	130	130
query92	103	90	91	90
query93	1047	1004	575	575
query94	633	319	266	266
query95	590	332	304	304
query96	697	486	241	241
query97	2451	2427	2383	2383
query98	161	156	154	154
query99	778	765	660	660
Total cold run time: 235704 ms
Total hot run time: 151609 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.05	0.06
query3	0.27	0.10	0.09
query4	1.60	0.10	0.10
query5	0.26	0.24	0.24
query6	1.17	0.68	0.67
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.41	0.35	0.35
query10	0.60	0.58	0.57
query11	0.21	0.15	0.14
query12	0.17	0.14	0.15
query13	0.49	0.50	0.50
query14	1.05	1.01	1.00
query15	0.63	0.62	0.60
query16	0.31	0.31	0.36
query17	1.11	1.09	1.14
query18	0.21	0.19	0.19
query19	2.10	1.97	2.02
query20	0.02	0.01	0.01
query21	15.49	0.19	0.13
query22	4.86	0.05	0.06
query23	16.30	0.31	0.12
query24	2.82	0.52	0.43
query25	0.10	0.05	0.06
query26	1.63	0.22	0.15
query27	0.04	0.05	0.03
query28	3.65	0.85	0.36
query29	12.47	4.21	3.32
query30	0.28	0.13	0.10
query31	2.77	0.56	0.33
query32	3.22	0.61	0.51
query33	3.13	3.27	3.35
query34	15.28	4.04	3.41
query35	3.38	3.35	3.37
query36	0.55	0.44	0.44
query37	0.09	0.06	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.16	0.14	0.14
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: 97.29 s
Total hot run time: 24.49 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.

Review converged in round 1. The change correctly carries Iceberg timestamptz markers through the ordinary field-id, name-mapping, positional, equality-delete, Parquet conversion, and Paimon compatibility paths, but it has one verified P1: recursive ID-less STRUCT wrappers are supported by the mapper yet the new marker walk stops before the marked leaf, shifting nested INT96 instants and hidden equality-delete keys in non-UTC sessions. No existing inline review threads were present. The review is static-only as required; no builds or tests were run. Concurrency, persistence, protocol, and configuration checkpoints are unaffected. The added tests cover direct and nested field-id paths, equality deletes, DST folds, and FE marker emission, but do not cover the nested ID-less-wrapper case described in the inline finding. No additional user focus was supplied.

Review converged after 1 round(s).

return key || value;
}
case TYPE_STRUCT: {
bool annotated = false;

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] Propagate Iceberg timestamp markers through nested ID-less wrappers

TableColumnMapper explicitly enables allow_idless_complex_wrapper_projection recursively for Iceberg Parquet, so it can resolve a selected leaf through an ID-less intermediate STRUCT. The new timestamp walk uses find_matched_file_column(..., false) for every STRUCT child, stops at that wrapper, and leaves the leaf's timestamp_is_adjusted_to_utc unset. With an outer-ID/inner-ID-less/leaf-ID file (the shape covered by ParquetRecursivelyRetainsIdlessWrapperForSelectedLeafId) and an INT96 timestamptz leaf, the scan still maps the leaf but decodes it as a session-local DATETIMEV2, shifting the instant; a hidden equality-delete key through the same path is affected too. Pass the same allow-wrapper flag through the recursive annotation or reuse the mapper's recursive resolver, and add a nested INT96 timestamptz regression test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f2b97b1. annotate_matched_timestamp_semantics() now takes allow_idless_complex_wrapper_projection and passes it down to every level, so the STRUCT branch resolves a child through an ID-less wrapper the way TableColumnMapper does below the root. BY_NAME mapping is unchanged. The equality-delete paths reuse the same walk, so nested keys under a wrapper get the marker too.

The new IcebergV2ReaderTest.IcebergInt96TimestampTzUnderIdlessWrapperReadsInstantWithTableMarker writes outer (field id 10) holding an ID-less inner struct whose INT96 ts (field id 30) carries the marker. It reads the file in Asia/Shanghai, and applies an adjusted-UTC equality delete on ts, once with outer projected and once with the key hidden. Passing false again in the STRUCT branch makes it fail: the values read eight hours early (2023-12-31 16:00:00.123456+00:00), and the tombstone leaves its row (ids {1, 2} instead of {2}).

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (14/14) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.62% (36428/46934)
Line Coverage 62.85% (411680/654993)
Region Coverage 59.30% (348574/587776)
Branch Coverage 60.24% (161699/268408)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 100% (0/0) 🎉
Increment coverage report
Complete coverage report

…rappers at any depth

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69845

Problem Summary:

`TableColumnMapper` resolves an Iceberg Parquet field id through an
ID-less complex wrapper at any depth when
`allow_idless_complex_wrapper_projection` is on: Iceberg's Parquet
projection keeps such a wrapper for a selected nested id
(`ParquetRecursivelyRetainsIdlessWrapperForSelectedLeafId`). The
timestamp marker walk added earlier in this PR
(`annotate_matched_timestamp_semantics`) allowed the wrapper only at the
root and passed `false` for every STRUCT child. In a file whose outer
struct has a field id, the struct inside it has none, and the leaf has one
again, an INT96 timestamptz leaf was still mapped but never marked. It
decoded as a UTC wall clock, and the projection converted that in the
session time zone, so in Asia/Shanghai it read eight hours early. An
equality key through the same path compared that wall clock with the
adjusted-UTC tombstone an Iceberg writer stores, and the tombstone missed
its row.

The walk now passes the mapper's flag down to every level, so it follows
the same path as the mapper. BY_NAME mapping is unchanged.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New `IcebergV2ReaderTest.IcebergInt96TimestampTzUnderIdlessWrapperReadsInstantWithTableMarker`:
      reads such a file in Asia/Shanghai, and applies an equality delete on
      the nested key with the key projected and hidden
    - `IcebergV2ReaderTest`: 122 tests pass
- Behavior changed: No (within this PR's fix)
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morningman

Copy link
Copy Markdown
Contributor Author

Addressed review round 1 in f2b97b1.

  • 4414291784: the timestamp marker walk now accepts an ID-less wrapper at every depth, as TableColumnMapper does, so an INT96 timestamptz leaf under a nested ID-less struct reads as an instant, and an equality key through that path compares as one too. New unit test included. IcebergV2ReaderTest passes 122 of 122.

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

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.

Request changes: one new P1 was verified at head f2b97b1. When an equality key is absent beneath an existing nullable struct, its ancestor read bypasses the new timestamp-marker projection. An otherwise valid older Parquet file with an INT96 timestamptz sibling then fails with a file-block/reader type mismatch. The inline finding describes the trigger and fix direction.

All 11 changed files and their affected FE schema, mapper, data/delete reader, physical conversion, pruning and materialization paths were reviewed statically. Three reviewers completed a second round with NO_NEW_VALUABLE_FINDINGS; all candidates and risk foci have explicit dispositions. Static review converged after 2 rounds. This does not mean the reported bug is fixed.

Existing finding 4414291784 is fixed at this head: the ID-less-wrapper option now propagates through every recursive annotation level, with an added nested INT96 test covering projected and hidden present keys. It is neither reposted nor retained as a current blocking comment. The new finding concerns the separate missing-key ancestor path and requires no ID-less wrapper. No additional user focus was supplied.

Critical checkpoint conclusions:

  1. Goal and correctness: the FE marker reaches ordinary matched Parquet leaves, and inspected tests assert instants and delete survivors. The missing-key ancestor composition remains incorrect as reported.
  2. Scope and reuse: changes are focused; current-schema/identity extraction is shared, and the Paimon timestamp helper move preserves behavior.
  3. Concurrency and locks: annotation and request overrides operate on per-file/per-reader copies; no new threads, shared mutable cached metadata, locks or lock-order dependencies were found.
  4. Lifecycle and memory safety: schema/request values own their child trees; native readers copy cached physical metadata before type synchronization. No new dangling reference, ownership cycle or static-initialization dependency was found.
  5. Configuration: none added. Both existing timestamp-mapping flag values and markerless requests were inspected.
  6. Compatibility: the existing optional marker is reused; old-FE requests without it retain prior file types. No persisted format or public connector SPI change; mixed-version execution and binary ABI were not tested.
  7. Thrift/protobuf: no definitions changed, so the cross-branch field-ID audit is not applicable.
  8. Parallel paths: normal, projected/hidden present equality keys, tombstones, nested mapping and Paimon were traced. The omitted missing-key path is the P1. ORC wall-clock DST ambiguity with legacy mapping disabled and position-delete-system-table INT96 row semantics are preexisting limitations.
  9. Conditions and invariants: field-ID/name matching reuses mapper resolvers, wrapper recursion agrees, and unmarked/TIMESTAMP_NS leaves preserve physical types. Known malformed containers return errors.
  10. Test coverage: six new BE cases and the FE recursive-marker case were inspected, including matching modes, old markers, nested wrappers, mapping flags and DST folds. The missing nested key plus marked sibling combination is absent and should be added.
  11. Expected results: timestamp strings and survivor IDs are consistent with the fixture semantics; no regression output files or source-.cpp test includes changed.
  12. Observability: existing column/type errors and profile ownership remain intact; no new I/O or asynchronous mechanism requires separate counters.
  13. Persistence, transactions and failover: no edit-log, publication, visible-version, delete-bitmap or transaction-state change.
  14. Data writes and atomicity: production writes are unchanged. The reported read-side violation fails explicitly rather than silently continuing.
  15. FE-BE propagation: normal, pinned, lazy and retained equality schemas recurse through the same FE builder. The incomplete-key ancestor projection is the verified missing transport step.
  16. Performance: added work is bounded per-split schema traversal/type construction, with no new per-row work or extra file I/O identified. No benchmark claim is made.
  17. Further checks: late-filter layout guards, implicit MAP-key reads, defaults/NULL ancestors, multiple/empty deletes, projection merging and cached-schema ownership were rechecked. No further new defect was substantiated.

Compatibility checks used Iceberg's field-ID projection and nullable-ancestor equality-delete rules in the Iceberg specification.

Validation limits: this was static inspection only; no builds, tests, runtime checks, benchmarks or binary audits were executed. Added fixtures are Arrow-generated, and this review did not validate real external-writer artifacts, FE-to-BE integration, or an exhaustive marker-aware null/container, pruning-enabled/disabled, mixed-format and split-reuse matrix. These are execution-validation limits; no source-review candidate remains unresolved.

Review converged after 2 round(s).

// predicate columns here and must keep the marker its mapping carried.
auto projection = format::LocalColumnIndex::top_level(field_column_id);
format::attach_timestamp_semantics(root->timestamp_is_adjusted_to_utc,
root->children, &projection);

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] Preserve markers on missing-key ancestor projections

Please apply the same timestamp override in _build_missing_equality_delete_key_expr, which still adds its physical ancestor through the markerless _append_file_scan_column at line 2040. For an older Parquet file containing nullable payload {ts: INT96 timestamptz}, a newer schema adding payload.k INT DEFAULT 7, and a nonempty equality delete on k, SELECT id reads payload to preserve ancestor NULLs. The new annotation makes its file-block type STRUCT<TIMESTAMPTZ>, but this request carries no descendant marker, so Parquet still builds STRUCT<DATETIMEV2> and check_block_column_type fails the query. Reuse the annotated projection for this incomplete-key branch too, and extend the missing-nested-key test with a timestamp sibling hidden from the SELECT.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4aa737f. The projection that the hidden-key path built (top_level plus attach_timestamp_semantics from the annotated file column) is now equality_delete_root_projection(), and _build_missing_equality_delete_key_expr uses it to add the missing key's ancestor as a predicate column, the same way the hidden key and the delete file's keys are added. The failure was wider than SELECT id. When the query projects payload (or SELECT *), the mapper's non-predicate projection of payload does carry the marker, but adding the root as a predicate column for the ancestor read replaced it (FileScanRequestBuilder::_add_column drops the non-predicate entry), so those queries hit the same check_block_column_type error. The key does not need a default either: the ancestor is read whatever the default is. The new IcebergV2ReaderTest.IcebergMissingNestedEqualityKeyAncestorKeepsInt96TimestampTzMarker writes an older file with a nullable payload {existing INT, ts INT96 timestamptz} and deletes on the later-added payload.k (default 7) in Asia/Shanghai. With payload hidden or projected, the only survivor is id 2, the row under the NULL parent. With a tombstone that matches nothing, all three rows survive and the projected ts reads as the instant. A plan without the marker reads as before. Reverting only the ancestor read to _append_file_scan_column makes the new test fail at its first marked case with that error (Parquet column payload ... is read as Nullable(Struct(existing:Nullable(INT), ts:Nullable(DateTimeV2(6)))) but its file block column is Nullable(Struct(existing:Nullable(INT), ts:Nullable(TimeStampTz(6))))), after the unmarked case passes.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17592	5153	5014	5014
q2	2107	311	285	285
q3	10202	1359	810	810
q4	4681	459	336	336
q5	7421	793	527	527
q6	178	178	152	152
q7	737	789	597	597
q8	9249	1314	1039	1039
q9	5412	4182	4154	4154
q10	6818	1278	1023	1023
q11	424	249	232	232
q12	640	404	290	290
q13	18080	3604	3023	3023
q14	268	273	244	244
q15	q16	720	712	657	657
q17	1186	736	572	572
q18	7002	6137	6102	6102
q19	1102	953	611	611
q20	386	334	224	224
q21	4964	2161	2287	2161
q22	347	292	260	260
Total cold run time: 99516 ms
Total hot run time: 28313 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5550	5588	5610	5588
q2	682	570	540	540
q3	5191	5352	4897	4897
q4	1991	2127	1532	1532
q5	5238	5127	5471	5127
q6	262	206	159	159
q7	2368	2064	1878	1878
q8	2965	2656	2698	2656
q9	8074	7997	7938	7938
q10	3700	3910	3240	3240
q11	624	452	433	433
q12	702	707	553	553
q13	3515	3813	3266	3266
q14	346	315	274	274
q15	q16	711	705	652	652
q17	8287	7108	6947	6947
q18	13031	12174	12948	12174
q19	911	783	798	783
q20	2139	2148	1904	1904
q21	5572	4631	4770	4631
q22	476	435	432	432
Total cold run time: 72335 ms
Total hot run time: 65604 ms

@hello-stephen

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

query5	4330	576	430	430
query6	430	207	190	190
query7	4828	453	232	232
query8	321	169	165	165
query9	8788	3283	3222	3222
query10	469	323	268	268
query11	6053	3893	3562	3562
query12	156	94	91	91
query13	1256	441	314	314
query14	6119	3938	3685	3685
query14_1	3390	3448	3443	3443
query15	208	196	186	186
query16	935	438	412	412
query17	851	664	539	539
query18	2428	438	314	314
query19	206	163	135	135
query20	87	86	87	86
query21	214	131	105	105
query22	13070	13010	12851	12851
query23	13407	12656	12537	12537
query23_1	12600	12388	12404	12388
query24	7361	820	464	464
query24_1	479	485	473	473
query25	503	402	334	334
query26	1267	261	136	136
query27	2757	439	271	271
query28	4619	2045	2065	2045
query29	1520	569	433	433
query30	313	227	177	177
query31	1010	928	807	807
query32	147	97	87	87
query33	514	292	234	234
query34	882	864	523	523
query35	745	756	646	646
query36	793	820	725	725
query37	119	96	81	81
query38	1775	1739	1695	1695
query39	778	785	737	737
query39_1	744	732	739	732
query40	215	111	91	91
query41	68	65	61	61
query42	85	84	82	82
query43	343	376	317	317
query44	1399	725	709	709
query45	176	182	169	169
query46	837	953	581	581
query47	3004	2833	2764	2764
query48	309	311	238	238
query49	577	426	303	303
query50	701	301	216	216
query51	10548	10311	10468	10311
query52	91	89	70	70
query53	183	224	158	158
query54	228	209	177	177
query55	70	70	71	70
query56	232	227	222	222
query57	1661	1610	1563	1563
query58	286	267	260	260
query59	2134	2191	1970	1970
query60	291	246	224	224
query61	147	146	146	146
query62	394	341	280	280
query63	194	158	158	158
query64	2754	927	763	763
query65	3325	3275	3277	3275
query66	1800	426	315	315
query67	20055	20085	19790	19790
query68	3022	1021	640	640
query69	393	291	240	240
query70	832	786	775	775
query71	296	236	210	210
query72	2688	2558	2310	2310
query73	523	532	302	302
query74	4655	4491	4334	4334
query75	2340	2294	1927	1927
query76	2320	1035	605	605
query77	362	382	291	291
query78	9556	9758	9145	9145
query79	905	845	549	549
query80	499	450	365	365
query81	529	323	276	276
query82	206	133	110	110
query83	203	206	187	187
query84	301	135	112	112
query85	838	497	447	447
query86	305	238	235	235
query87	2018	1941	1796	1796
query88	3825	2795	2795	2795
query89	324	289	249	249
query90	2096	182	166	166
query91	182	217	125	125
query92	98	86	91	86
query93	992	996	574	574
query94	442	296	252	252
query95	584	393	298	298
query96	691	497	240	240
query97	2439	2451	2327	2327
query98	160	157	151	151
query99	755	764	645	645
Total cold run time: 233928 ms
Total hot run time: 151164 ms

@hello-stephen

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

query1	0.00	0.01	0.00
query2	0.10	0.06	0.05
query3	0.27	0.08	0.08
query4	1.61	0.10	0.09
query5	0.24	0.24	0.24
query6	1.18	0.68	0.68
query7	0.03	0.01	0.00
query8	0.06	0.04	0.03
query9	0.40	0.35	0.36
query10	0.55	0.59	0.62
query11	0.19	0.14	0.14
query12	0.18	0.15	0.14
query13	0.49	0.49	0.49
query14	1.05	1.03	1.01
query15	0.63	0.61	0.61
query16	0.35	0.36	0.34
query17	1.12	1.18	1.15
query18	0.20	0.18	0.19
query19	2.17	1.98	2.03
query20	0.01	0.01	0.01
query21	15.49	0.19	0.13
query22	4.88	0.06	0.05
query23	16.26	0.31	0.12
query24	2.97	0.55	0.36
query25	0.12	0.07	0.07
query26	0.90	0.21	0.16
query27	0.06	0.05	0.06
query28	3.61	0.81	0.34
query29	12.54	4.10	3.26
query30	0.28	0.13	0.10
query31	2.78	0.56	0.32
query32	3.22	0.59	0.51
query33	3.15	3.34	3.22
query34	15.30	3.97	3.39
query35	3.34	3.32	3.34
query36	0.55	0.44	0.45
query37	0.09	0.06	0.06
query38	0.06	0.03	0.03
query39	0.04	0.03	0.03
query40	0.17	0.15	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.81 s
Total hot run time: 24.35 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage `` 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (14/14) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 67.23% (32561/48432)
Line Coverage 52.12% (345061/662030)
Region Coverage 47.56% (280536/589915)
Branch Coverage 49.26% (132554/269066)

… a missing equality key

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69845

Problem Summary:

When an Iceberg equality-delete key is a nested field that an older data
file does not store, but the file does store the key's parent struct,
`_build_missing_equality_delete_key_expr` reads the whole parent so that
the key stays NULL under a NULL parent. It added that read through
`_append_file_scan_column`, a bare top-level projection without timestamp
markers. Earlier in this PR, `annotate_file_schema` started rewriting each
marked timestamptz leaf of the Parquet file schema to TIMESTAMPTZ. The
file block is built from that schema, but the Parquet reader takes the
timestamp semantics only from the projection.

Reproduction: an older Parquet file holds a nullable
`payload {existing INT, ts INT96 timestamptz}` (Spark's encoding), the
table later added `payload.k`, and a non-empty equality delete is keyed on
`payload.k`. Scans of that file failed in `check_block_column_type`:
Parquet read `payload` as STRUCT<existing, DATETIMEV2(6)> into a
STRUCT<existing, TIMESTAMPTZ(6)> file block. Projecting `payload` did not
help: adding the root as a predicate column replaced the projection its
mapping had marked. Before this PR the file schema kept the physical
types, so the same read succeeded.

The fix moves the annotated projection the hidden-key path already built
(`top_level` plus `attach_timestamp_semantics` from the annotated file
column) into `equality_delete_root_projection()`. The missing key's
ancestor, the hidden key and the delete file's keys all use it now. The
ancestor is still added as a predicate column through
`FileScanRequestBuilder`, so a root already in the request is merged or
moved as before. A plan without markers builds the same request as before.

### Release note

None

### Check List (For Author)

- Test: Unit Test (to be run)
    - New `IcebergV2ReaderTest.IcebergMissingNestedEqualityKeyAncestorKeepsInt96TimestampTzMarker`:
      in Asia/Shanghai, with the key's parent holding an INT96 timestamptz
      sibling, checks the surviving rows with the parent hidden and
      projected, that the projected sibling reads as an instant, and a plan
      without the marker
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morningman

Copy link
Copy Markdown
Contributor Author

Addressed review round 2 in 4aa737f.

  • 4419065184: when a nested equality key is missing from an older data file, the reader still reads the key's parent struct for its NULLs. That read now carries the timestamp markers of the annotated file column, through the same projection helper the hidden key and the delete file's keys use. A scan of such a file whose parent struct holds an INT96 timestamptz no longer fails the reader's type check, whether or not the query projects the parent. New unit test included. IcebergV2ReaderTest passes 123 of 123, and the PR's whole BE UT filter 534 of 534.

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Selected model is at capacity. Please try a different model.
Workflow run: https://github.com/apache/doris/actions/runs/40990597992

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

------ Round 1 ----------------------------------
============================================
q1	17581	5193	5029	5029
q2	2143	319	249	249
q3	10241	1384	832	832
q4	4674	458	334	334
q5	7430	802	532	532
q6	179	180	155	155
q7	765	784	601	601
q8	9253	1400	1136	1136
q9	5557	4214	4223	4214
q10	6820	1282	1013	1013
q11	421	249	234	234
q12	641	406	281	281
q13	18072	4100	3001	3001
q14	266	269	255	255
q15	q16	741	710	629	629
q17	1204	753	584	584
q18	6923	6187	6105	6105
q19	1118	952	611	611
q20	390	342	224	224
q21	5675	2455	2470	2455
q22	408	321	265	265
Total cold run time: 100502 ms
Total hot run time: 28739 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5956	5917	6109	5917
q2	745	605	561	561
q3	5649	5773	5312	5312
q4	2026	2175	1544	1544
q5	5789	5805	5617	5617
q6	250	189	152	152
q7	2284	1975	1794	1794
q8	3094	2637	2658	2637
q9	7814	7464	7558	7464
q10	3655	3557	3221	3221
q11	575	411	384	384
q12	656	687	560	560
q13	3291	3542	3103	3103
q14	295	287	264	264
q15	q16	661	689	618	618
q17	7917	7000	6890	6890
q18	12910	12096	12819	12096
q19	961	847	831	831
q20	2123	2140	1916	1916
q21	5599	4607	4810	4607
q22	490	431	403	403
Total cold run time: 72740 ms
Total hot run time: 65891 ms

@hello-stephen

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

query5	4314	569	448	448
query6	426	210	185	185
query7	4796	448	225	225
query8	320	164	155	155
query9	8773	3208	3181	3181
query10	472	316	277	277
query11	5949	3843	3546	3546
query12	150	108	86	86
query13	1274	444	317	317
query14	6158	3894	3658	3658
query14_1	3370	3387	3351	3351
query15	208	195	173	173
query16	928	453	439	439
query17	850	645	522	522
query18	2402	421	295	295
query19	184	162	126	126
query20	86	80	81	80
query21	205	124	104	104
query22	12853	12937	12815	12815
query23	13495	12867	12385	12385
query23_1	12356	12587	12647	12587
query24	7376	814	472	472
query24_1	458	468	476	468
query25	504	390	343	343
query26	1265	250	134	134
query27	2785	430	269	269
query28	4623	2009	2025	2009
query29	1574	607	456	456
query30	307	226	186	186
query31	1024	923	830	830
query32	142	98	91	91
query33	536	304	257	257
query34	906	850	500	500
query35	748	747	667	667
query36	843	802	737	737
query37	132	101	85	85
query38	1786	1750	1649	1649
query39	788	774	742	742
query39_1	733	728	738	728
query40	230	119	100	100
query41	74	76	70	70
query42	94	88	82	82
query43	348	365	315	315
query44	1322	700	704	700
query45	180	175	189	175
query46	834	987	571	571
query47	2748	2961	2695	2695
query48	305	316	245	245
query49	610	403	300	300
query50	698	281	218	218
query51	10375	10559	10370	10370
query52	81	83	74	74
query53	186	214	159	159
query54	245	208	189	189
query55	78	71	70	70
query56	248	222	216	216
query57	1656	1640	1562	1562
query58	298	276	266	266
query59	2146	2237	2085	2085
query60	292	253	236	236
query61	162	162	165	162
query62	398	330	294	294
query63	201	162	163	162
query64	2850	962	728	728
query65	3314	3263	3255	3255
query66	1766	440	290	290
query67	20151	20079	19885	19885
query68	3296	1011	610	610
query69	392	286	245	245
query70	852	776	753	753
query71	297	234	205	205
query72	2866	2671	2276	2276
query73	533	525	298	298
query74	4637	4483	4334	4334
query75	2341	2289	1895	1895
query76	2398	1038	581	581
query77	338	379	270	270
query78	9680	9768	9146	9146
query79	1010	842	539	539
query80	1414	431	329	329
query81	592	317	270	270
query82	619	133	102	102
query83	277	198	183	183
query84	301	116	100	100
query85	1111	429	376	376
query86	399	237	238	237
query87	1979	1945	1798	1798
query88	3811	2775	2754	2754
query89	331	285	235	235
query90	1885	169	164	164
query91	158	148	120	120
query92	102	89	83	83
query93	1077	990	580	580
query94	636	297	260	260
query95	560	323	310	310
query96	686	491	242	242
query97	2454	2434	2332	2332
query98	165	150	146	146
query99	772	771	662	662
Total cold run time: 235735 ms
Total hot run time: 150837 ms

@hello-stephen

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

query1	0.01	0.00	0.01
query2	0.10	0.06	0.05
query3	0.26	0.09	0.08
query4	1.61	0.10	0.10
query5	0.23	0.25	0.24
query6	1.17	0.68	0.68
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.40	0.33	0.36
query10	0.60	0.57	0.56
query11	0.20	0.14	0.15
query12	0.18	0.15	0.14
query13	0.48	0.48	0.47
query14	1.02	1.01	1.00
query15	0.63	0.61	0.61
query16	0.33	0.35	0.36
query17	1.18	1.11	1.11
query18	0.20	0.19	0.19
query19	2.14	2.03	2.03
query20	0.02	0.01	0.01
query21	15.57	0.19	0.14
query22	4.88	0.06	0.05
query23	16.28	0.33	0.13
query24	3.18	0.46	0.41
query25	0.09	0.05	0.04
query26	0.95	0.23	0.15
query27	0.05	0.05	0.04
query28	3.30	0.78	0.36
query29	12.48	4.12	3.24
query30	0.28	0.13	0.10
query31	2.77	0.55	0.32
query32	3.22	0.59	0.50
query33	3.23	3.26	3.17
query34	15.33	3.99	3.37
query35	3.33	3.32	3.31
query36	0.57	0.46	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.17	0.14	0.14
query41	0.09	0.03	0.03
query42	0.03	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.88 s
Total hot run time: 24.25 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage `` 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (14/14) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 67.23% (32561/48432)
Line Coverage 52.12% (345011/661992)
Region Coverage 47.54% (280426/589842)
Branch Coverage 49.26% (132515/269037)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (14/14) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.50% (36373/46934)
Line Coverage 62.77% (411138/654959)
Region Coverage 59.19% (347862/587703)
Branch Coverage 60.13% (161366/268379)

@morningman

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.

No new actionable findings were verified at head 4aa737f. The complete 11-file aggregate diff and affected FE schema, mapper, data/delete reader, physical conversion, pruning and materialization paths were reviewed statically. The review converged in the preserved first round: the complete reviewer and both risk reviewers finished with NO_NEW_VALUABLE_FINDINGS, all candidates have evidence-backed dispositions, and no source-review question remains unresolved. The interrupted schema assignment was recovered within that same round.

Both existing P1 findings are fixed at this head. 4414291784 is addressed by forwarding the ID-less-wrapper option through every recursive annotation level. 4419065184 is addressed by using the shared marker-bearing root projection for missing-key ancestor reads as well as present keys and tombstones. The added tests inspect hidden/projected keys and nullable ancestors. Neither issue is reposted or retained in existing_blocking_comment_ids. No additional user review focus was supplied.

The inspected interaction is: FE recursive TField marker -> scan schema dictionary -> fresh per-reader file schema -> Iceberg annotation -> mapper/equality-root projection -> Parquet semantic schema and decoder -> file block/delete comparison -> table materialization.

Critical checkpoint conclusions:

  1. Goal and correctness: the marker supplies instant semantics for INT96 timestamptz data and equality keys. Inspected assertions cover UTC instant strings and delete survivors; the release note describes this behavior. No additional introduced correctness defect was verified.
  2. Scope and reuse: changes are focused. Current-schema/identity extraction and timestamp type rewriting reuse shared helpers; the moved Paimon implementation preserves behavior apart from generalized error wording.
  3. Concurrency and locks: annotation occurs on fresh reader-owned schema during open. No new thread, shared cached-schema mutation, lock, critical section or lock-order dependency was introduced.
  4. Lifecycle and memory safety: schema definitions own their child trees; current-schema pointers retain the existing scan-parameter lifetime, with nonempty/null guards at callers. No new dangling reference, ownership cycle or cross-translation-unit static initializer was found.
  5. Configuration: no setting was added. Current catalog planning always enables physical instant mapping; raw legacy false properties do not produce marked/false scan parameters. Older markerless requests retain prior behavior.
  6. Compatibility: the existing optional marker is reused, with no wire definition, persisted file format or connector SPI change. Unmarked plans and unmarked/TIMESTAMP_NS leaves preserve their file types; ORC retains its separate timestamp-instant path. Runtime upgrade and binary compatibility were not tested.
  7. Thrift/protobuf IDs: no definitions changed; cross-branch field-ID auditing is not applicable.
  8. Parallel paths: ordinary data, hidden/projected equality keys, missing-key ancestors and tombstone roots were traced. Paimon retains equivalent rewriting; native metadata is copied before reader-local type synchronization. No new omitted supported path was verified.
  9. Conditions and invariants: annotation reuses mapper field-ID/name resolvers and the recursive wrapper flag. Positional IDs precede annotation. Comments explain INT96 ambiguity, nullable ancestors and old-plan/NS handling; malformed container errors propagate.
  10. Test coverage: all seven added BE cases and the FE recursive-marker case were inspected, including identity modes, nested STRUCT/LIST/MAP annotation, wrappers, precision/nullability, old plans, both synthetic mapping flags, hidden/projected/missing keys and DST folds. Existing Paimon tests reach the extracted helper. None were executed here.
  11. Expected results: inspected UTC timestamps and survivor IDs agree with fixture semantics. No regression output file changed, and no new test includes a source .cpp file.
  12. Observability: work remains inside existing reader-open/schema timing and column/type diagnostics. No new asynchronous operation, I/O mechanism or per-row logging requires additional metrics.
  13. Persistence, transactions and failover: no EditLog, snapshot selection, visible-version, publish, delete-bitmap or transaction-state change was introduced.
  14. Data writes and atomicity: production changes affect reads; new fixture writers are test-only. No new production write/commit or crash-cleanup path was introduced.
  15. FE-BE propagation: ordinary, pinned, lazy and historical equality-key dictionaries recurse through the same builder. Root and descendant markers reach data and delete scan requests, including replacement of projected roots by predicate roots.
  16. Performance: added traversal/type rebuilding is bounded by schema size at reader open or delete-cache load. No new per-row schema work, redundant file I/O or unbounded retained allocation was identified; no performance result is claimed.
  17. Further checks: predicate localization, footer/page statistics, dictionary/Bloom boundaries, aggregates, projection merging, NULL/default ancestors and split-local ownership were rechecked. The proposed mixed-format false-flag issue is prevented by the current FE producer invariant. A partial complex-MAP-key example already violates unchanged TableReader child-count invariants before this PR, so its earlier marker mismatch does not establish a new regression.

Complex map-key compatibility was checked against the Iceberg nested-type specification, which permits nested key types; the preexisting materialization limitation above is not claimed fixed.

Validation limits: static source inspection only. No builds, tests, runtime checks, benchmarks or binary audits were executed. Arrow-generated fixtures and the author's reported runs do not independently validate actual Spark/Hive artifacts, FE-to-BE integration, mixed-version/format execution, or an exhaustive pruning-enabled/disabled, split-reuse and performance matrix in this review. These are execution-validation limits; no introduced-bug candidate or assigned source scope remains unresolved.

Review converged after 1 round(s).

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

Iceberg timestamptz fields represent instants, while Parquet INT96 has no timezone annotation. This change carries the table semantics from the shared FE schema builder through scan parameters into BE file-schema annotation and marker-bearing projections for data and equality-delete reads.

Static review completed for head 4aa737f against base 2a4191c, focusing on FE and the necessary FE/BE contract. No actionable introduced P0/P1/P2 issue was found. Both previous findings, 4414291784 and 4419065184, are fixed in the current code and have targeted test coverage. APPROVE.

No builds or tests were run during this review. The supplied current-head context reports successful P0 Regression and a failed FE coverage check. Required-check rules were not supplied; this code approval does not establish merge readiness.

Automated FE-focused static review (GPT-6.1 Sol, medium). No build or tests were run.

@Gabriel39
Gabriel39 merged commit a9253e9 into apache:master Nov 2, 2026
52 of 53 checks 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