Skip to content

[fix](fe) Bind Iceberg branch writes to the table's current schema - #69499

Open
morningman wants to merge 2 commits into
apache:masterfrom
morningman:b2m-t35k-iceberg-branch-write-current-schema
Open

morningman wants to merge 2 commits into
apache:masterfrom
morningman:b2m-t35k-iceberg-branch-write-current-schema

Conversation

@morningman

@morningman morningman commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #66538, #65851

Problem Summary:

In short. A write to an Iceberg branch binds to the schema of the branch's head snapshot. Iceberg, Spark and the Iceberg docs all bind a branch write to the table's current schema, and so do branch-4.1 and branch-4.2 (#66538). This PR does the same on master.

Background. #65851 pins a statement-scoped writer schema for every Iceberg write (IcebergWriteSchemaContext): the columns that the target list binds to, their write-defaults, and the partition spec and sort order bound to that schema. For INSERT [OVERWRITE] INTO t@branch(b) it pins the schema of the branch head snapshot, although a read of the branch already uses the table-current schema. Iceberg commits a branch write with the table-current schema: SnapshotProducer records base.currentSchemaId() as the schema of the new snapshot, whatever its target ref. So master also needs validateBranchWriterSchema, which checks that the current schema can describe files written with the branch head schema. The Iceberg docs ("Schema selection with branches and tags") say that a write to a branch uses the table schema and only a read of a tag uses the snapshot's schema. Spark 4.0 with Iceberg 1.10.1 binds branch writes the same way: after a rename on main, a write to an older branch accepts the new name and rejects the old one. branch-4.1 and branch-4.2 (#66538) pin table.schema() for branch writes.

The problem, and what it cost. Reproduced on a local cluster with a file:// format-version 3 table t (id INT, c INT) holding one row, with a branch b created before the change on main:

Change on main after the branch Statement Master today After this PR (same as Spark and branch-4.2)
RENAME COLUMN c c2 INSERT INTO t@branch(b) (id, c2) VALUES (1, 1) Unknown column 'c2' in target table writes the row
RENAME COLUMN c c2 INSERT INTO t@branch(b) (id, c) VALUES (2, 2) writes the row Unknown column 'c' in target table
ADD COLUMN p INT, then ADD PARTITION KEY p INSERT INTO t@branch(b) (id, c) VALUES (1, 1) Iceberg partition field 1000 references source field 3 outside pinned schema 0 writes the row, p is NULL
write-default of default_int changed from 35 to 36 (test_iceberg_initial_defaults, through the Iceberg API) INSERT OVERWRITE TABLE t@branch(b) (id, default_int) VALUES (14, DEFAULT) writes 35 writes 36

The partition case has no way out: only a successful write to the branch moves its head to a newer schema, and every write to it fails.

How this PR fixes it.

  • IcebergWriteSchemaContext.create still checks that the target ref exists and is a branch, not a tag. It then pins table.schema() for a branch, as for main, and binds the partition spec and sort order to that schema.
  • validateCurrentSchema, the check before the commit, compares the pinned schema id with the table-current schema id for branch writes too. A schema change after the statement pinned its schema now fails the commit. Before, only a change of the branch head did.
  • validateBranchWriterSchema is removed. The written files now carry the schema that Iceberg records for the new snapshot.

Results.

  • Local cluster: the table above shows each statement before and after.
  • Unit tests in IcebergWritePlanProviderTest: the five tests that pinned the branch head schema now expect the current schema. Together they cover the column names (also through getWriteColumns and the sink's schema JSON), the write-defaults, a current required column with and without a default (without one, an omitted value fails at analysis, as for main), and a schema change after pinning, which must fail the commit. Two new tests cover a current partition spec whose source column the branch head lacks, and a tag or a missing ref, which is still rejected. With master's IcebergWriteSchemaContext, 6 of these 7 tests fail and the tag test passes. All 1691 tests of fe-connector-iceberg pass.
  • Regression: test_iceberg_initial_defaults, write/test_iceberg_write_evolution_refs, branch_tag/iceberg_branch_tag_schema_change_extended and test_iceberg_schema_ref_actions_matrix now expect what branch-4.2 expects. In test_iceberg_initial_defaults the branch write gets the current write-default 36. The other three write an older branch with the current column names and expect the old names to fail. All four pass locally. Without this change each one fails at the new expectation: the branch rows of test_iceberg_initial_defaults read 35, test_iceberg_write_evolution_refs and iceberg_branch_tag_schema_change_extended get Unknown column 'zone' and Unknown column 'new_col', and in test_iceberg_schema_ref_actions_matrix the INSERT with the old column name succeeds.

Release note

Fix: Writes to an Iceberg branch now use the table's current schema (column names, write-defaults and partition spec), as Spark does, instead of the schema of the branch's head snapshot.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
      On a local cluster with a file:// Iceberg catalog: CREATE TABLE t (id INT, c INT) PROPERTIES ('format-version'='3'), insert a row, ALTER TABLE t CREATE BRANCH b, then the changes and statements in the table above.
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
      See the release note. A write to a branch created before a schema change must use the current column names; the old names, which master accepted, now fail.
  • 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

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

Reviewed all nine changed files at a218feb8b0981efa6a17163b11fabe8d2b06fdd8. No substantiated new findings and no existing inline findings to carry forward. No additional user focus was provided.

Branch writes now bind column names, types, defaults, partitioning and sort metadata to the table-current schema while retaining the selected branch as the parent snapshot. This matches Iceberg's documented schema selection and the repository-pinned Iceberg 1.12.0 snapshot implementation.

Critical checkpoint conclusions:

  1. Goal and evidence: The central schema resolver and commit check implement current-schema branch writes. Changed unit and regression cases cover renames, current defaults, required fields, partition-source evolution, invalid refs and subsequent schema changes.
  2. Scope and reuse: The production change is focused, reuses the existing statement-scoped writer context and removes obsolete branch-schema compatibility traversal.
  3. Concurrency and locks: No new threads or locks. Concurrent external metadata changes are checked before/after transaction initialization, before commit and at the final metadata CAS, including SDK retries; existing lock order is unchanged.
  4. Lifecycle: The immutable context remains statement-owned. Transaction leases, cleanup and static initialization are unchanged; no new ownership cycle was found.
  5. Configuration: No configuration items are added or changed.
  6. Compatibility: Branch column binding intentionally changes to current names/defaults. No public SPI, file-format or wire-layout change is introduced; existing serializers and field-ID handling remain in use.
  7. Thrift/protobuf: No definitions are changed; cross-release field-ID checks are inapplicable.
  8. Parallel paths: INSERT and static/dynamic/whole-table/empty OVERWRITE retain branch routing. Main row-level writes and REWRITE retain their established paths. Parquet/ORC and historical tag/snapshot cases were inspected.
  9. Conditional checks and errors: Missing/tag refs still fail explicitly. Schema/format/table identity and retained spec/order checks remain; omitted required fields without defaults and explicit invalid nulls retain engine/BE enforcement.
  10. Test coverage: Changed JUnit 5 cases and all four regression suites were inspected, including negative names/refs and main/branch/tag isolation. Historical-source INSERT is EXPLAIN-only; required-without-default and later-schema cases primarily verify metadata/context validation rather than full SQL or retry execution.
  11. Expected results: All changed golden rows match their SQL, including default 36 and evolved nested payload/note positions. Exact overwrite output checks replacement and retained rows. Branch payload.extra/metric lack direct value assertions; golden-file generation provenance was not independently verified.
  12. Observability: Existing errors identify table/schema/ref or field context and provide retry guidance. This change adds no operational subsystem requiring new metrics or logging.
  13. Persistence and failover: No Doris EditLog or persisted FE metadata is changed. Iceberg's existing transaction commit/retry protocol remains authoritative; crash/failover behavior was not exercised.
  14. Data writes and atomicity: Target binding, physical sink schema and commit-file metadata consume the same pinned context. Existing atomic metadata commit, overwrite conflict checks and rollback cleanup remain in place; no introduced write-loss or isolation defect was found statically.
  15. FE/BE variables: No new variables or wire fields. Existing schema JSON carries the current shape, bound ordinals/IDs are checked, and downstream recursive required-value validation remains active.
  16. Performance: The change removes repeated branch compatibility indexes and adds no new remote operation or hot loop. No introduced performance issue was identified.
  17. Other concerns: Separate read/write metadata namespaces prevent historical source schemas from replacing the writer schema. All investigated risks have evidence-based dispositions; no unresolved correctness candidate remains.

This was a static-only review: no builds or tests were run and no repository source files were modified. Runtime test results reported by the author were not independently reproduced. The test-depth limits above do not establish a verified defect.

Review converged after 2 round(s).

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

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

hubgeter
hubgeter previously approved these changes Oct 23, 2026
924060929
924060929 previously approved these changes Oct 23, 2026
@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

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

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17619	5166	5057	5057
q2	2126	315	248	248
q3	10263	1389	810	810
q4	4682	469	331	331
q5	7428	791	530	530
q6	187	201	154	154
q7	743	788	588	588
q8	9239	1337	1065	1065
q9	5505	4187	4187	4187
q10	6819	1281	1055	1055
q11	427	249	224	224
q12	634	405	277	277
q13	18090	3570	3018	3018
q14	266	272	247	247
q15	q16	726	707	676	676
q17	1204	757	576	576
q18	7051	6172	6113	6113
q19	1107	950	588	588
q20	401	345	235	235
q21	5022	2134	2285	2134
q22	344	306	249	249
Total cold run time: 99883 ms
Total hot run time: 28362 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5640	5629	5605	5605
q2	667	573	510	510
q3	5195	5396	4882	4882
q4	2014	2141	1556	1556
q5	5278	5108	5556	5108
q6	262	207	159	159
q7	2332	2099	1892	1892
q8	3081	2868	2610	2610
q9	7977	8095	7940	7940
q10	3746	3710	3259	3259
q11	610	426	402	402
q12	682	687	514	514
q13	3558	3881	3368	3368
q14	322	316	294	294
q15	q16	681	709	657	657
q17	8305	7428	7349	7349
q18	13024	12267	12929	12267
q19	908	781	782	781
q20	2141	2139	1927	1927
q21	5698	4652	4896	4652
q22	501	452	414	414
Total cold run time: 72622 ms
Total hot run time: 66146 ms

@hello-stephen

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

query5	4306	600	442	442
query6	434	203	197	197
query7	4803	442	224	224
query8	317	171	150	150
query9	8780	3067	3066	3066
query10	498	305	252	252
query11	5897	3851	3553	3553
query12	149	89	86	86
query13	1243	440	328	328
query14	6119	3875	3627	3627
query14_1	3372	3399	3364	3364
query15	209	194	190	190
query16	952	473	378	378
query17	860	666	531	531
query18	2431	428	313	313
query19	203	165	138	138
query20	89	82	84	82
query21	216	125	108	108
query22	12904	13127	12690	12690
query23	13341	13005	12537	12537
query23_1	12684	12553	12544	12544
query24	7390	827	468	468
query24_1	469	468	475	468
query25	557	415	370	370
query26	1262	262	141	141
query27	2768	440	278	278
query28	4629	1929	1884	1884
query29	1571	597	474	474
query30	299	219	188	188
query31	1012	931	814	814
query32	156	91	95	91
query33	539	305	247	247
query34	905	847	483	483
query35	739	739	637	637
query36	820	826	723	723
query37	121	98	84	84
query38	1776	1758	1674	1674
query39	790	811	747	747
query39_1	744	722	744	722
query40	235	121	97	97
query41	74	70	68	68
query42	92	86	82	82
query43	345	359	310	310
query44	1309	676	681	676
query45	213	181	165	165
query46	834	973	553	553
query47	2942	2935	2755	2755
query48	293	301	210	210
query49	568	392	284	284
query50	686	272	213	213
query51	10734	10529	10371	10371
query52	82	75	68	68
query53	185	206	162	162
query54	233	189	177	177
query55	73	70	63	63
query56	231	206	218	206
query57	1648	1600	1544	1544
query58	283	264	247	247
query59	2128	2091	1922	1922
query60	284	237	221	221
query61	147	150	148	148
query62	389	331	292	292
query63	185	156	165	156
query64	2755	939	772	772
query65	3328	3358	3261	3261
query66	1801	426	300	300
query67	20246	20167	20127	20127
query68	3285	997	614	614
query69	389	287	253	253
query70	832	776	788	776
query71	313	236	211	211
query72	2818	2596	2320	2320
query73	531	522	291	291
query74	4634	4536	4337	4337
query75	2339	2283	1903	1903
query76	2400	1000	583	583
query77	339	374	269	269
query78	9678	9722	9245	9245
query79	988	856	538	538
query80	1283	424	333	333
query81	605	319	274	274
query82	868	135	103	103
query83	301	194	185	185
query84	310	115	103	103
query85	876	427	388	388
query86	398	238	232	232
query87	1970	1935	1785	1785
query88	3652	2683	2642	2642
query89	330	292	247	247
query90	1951	167	157	157
query91	184	149	126	126
query92	96	79	83	79
query93	1005	1001	578	578
query94	653	303	265	265
query95	570	332	297	297
query96	658	489	231	231
query97	2474	2417	2322	2322
query98	162	152	145	145
query99	765	771	653	653
Total cold run time: 236116 ms
Total hot run time: 150596 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.09	0.05	0.05
query3	0.26	0.09	0.10
query4	1.61	0.12	0.10
query5	0.22	0.24	0.24
query6	1.17	0.68	0.68
query7	0.05	0.01	0.01
query8	0.05	0.04	0.04
query9	0.41	0.35	0.36
query10	0.57	0.58	0.56
query11	0.20	0.14	0.15
query12	0.18	0.14	0.15
query13	0.51	0.48	0.48
query14	1.05	1.01	1.02
query15	0.62	0.62	0.60
query16	0.35	0.35	0.36
query17	1.14	1.07	1.18
query18	0.22	0.19	0.20
query19	2.10	2.03	2.06
query20	0.02	0.02	0.01
query21	15.50	0.19	0.14
query22	4.89	0.05	0.04
query23	16.28	0.32	0.12
query24	2.84	0.65	0.41
query25	0.11	0.04	0.06
query26	1.46	0.22	0.15
query27	0.05	0.03	0.05
query28	3.67	0.82	0.38
query29	12.49	4.14	3.24
query30	0.30	0.12	0.10
query31	2.78	0.55	0.33
query32	3.23	0.58	0.50
query33	3.31	3.25	3.33
query34	15.30	4.00	3.38
query35	3.36	3.35	3.34
query36	0.55	0.49	0.44
query37	0.10	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.08	0.03	0.02
query42	0.03	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 97.45 s
Total hot run time: 24.39 s

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66538, apache#65851

Problem Summary: A write to an Iceberg branch (INSERT [OVERWRITE] INTO
t@branch(b)) pins the schema of the branch head snapshot as its writer
schema, while a read of the branch uses the table-current schema. Iceberg
validates a branch commit against the table-current schema and stamps
that schema on the new snapshot, Spark binds branch writes to the current
schema, and the Iceberg docs ("Schema selection with branches and tags")
say writes to a branch use the table schema. So once the table schema
evolves, a write to an older branch rejects the current column names and
accepts the old ones, writes the branch head's write-default instead of
the current one, and fails outright when the current partition spec has a
source column the branch head schema does not have. Master also needs an
extra check (validateBranchWriterSchema) because the files it writes carry
a different schema than the one Iceberg records for the new snapshot.
branch-4.1 and branch-4.2 (apache#66538) bind branch writes to table.schema().

This change pins table.schema() for branch writes, keeps the check that
the target is an existing branch and not a tag, and fences the commit on
the table-current schema id for branch writes as for writes to main. The
branch head checks are removed.

### Release note

Fix: Writes to an Iceberg branch now use the table's current schema
(column names, write-defaults and partition spec), as Spark does, instead
of the schema of the branch's head snapshot.

### Check List (For Author)

- Test: Regression test / Unit Test
    - test_iceberg_initial_defaults (a branch write gets the current
      write-default 36), write/test_iceberg_write_evolution_refs,
      branch_tag/iceberg_branch_tag_schema_change_extended and
      test_iceberg_schema_ref_actions_matrix (an older branch takes the
      current column names and rejects the old ones) now expect what
      branch-4.2 expects. They pass locally; without this change each of
      them fails at the new expectation
    - IcebergWritePlanProviderTest: a branch created before a rename writes
      the new name; the branch write uses the current write-defaults, binds
      a current required column without a default, and binds the current
      partition spec whose source column the branch head lacks; a schema
      change after pinning fails the commit; a tag or a missing ref is
      rejected
- Behavior changed: Yes. See the release note.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@morningman
morningman dismissed stale reviews from 924060929 and hubgeter via 2937f34 October 23, 2026 09:22
@morningman
morningman force-pushed the b2m-t35k-iceberg-branch-write-current-schema branch from a218feb to 2937f34 Compare October 23, 2026 09:22
@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 for one P1 correctness issue: a branch INSERT OVERWRITE can discard the writer generation used to expand DEFAULT, then silently bind those values to reordered columns during its inner INSERT. The inline finding gives the concrete interleaving and repair direction. No existing blocking inline comments apply.

Reviewed all nine changed paths and the upstream binding/default, sink, transaction refresh/retry, branch targeting, and regression-result paths at 2937f34. The current-schema rule itself matches Iceberg's schema-selection contract; the remaining issue is preserving that schema through the complete statement.

Critical checkpoint conclusions:

  • Goal and evidence: current names, required-column metadata, write-defaults, partition-source evolution, and late schema rejection are covered by inspected unit/regression cases. C1 prevents full statement-generation correctness.
  • Scope and clarity: the production change is focused; the obsolete branch/current compatibility walker is appropriately removed when the writer uses the current schema.
  • Concurrency and locks: external schema updates can race with the two overwrite planning passes (C1). Begin, refresh, precommit, and final metadata-CAS checks protect the retained inner pin. No new locks or lock-order changes.
  • Lifecycle and initialization: statement scope, tracked table leases, transaction ownership, and cleanup mechanisms are unchanged. No new static initialization dependencies.
  • Configuration: no settings added or changed.
  • Compatibility: the branch column/default binding change is intentional and described in the release note. No public connector API, symbol, or storage-format change.
  • Thrift/protobuf: untouched; no field-ID or enum change.
  • Parallel paths: checked normal INSERT, static/dynamic/empty/whole-table OVERWRITE, main versus branch, tag/numeric historical sources, and existing row-level/REWRITE boundaries. Branch targeting and overwrite ancestry/conflict checks remain intact.
  • Conditional checks and errors: missing refs and tag write targets still fail explicitly. Schema/format/spec/order/identity drift is rejected after the inner pin; the lost outer pin in C1 bypasses the intended error.
  • Test coverage: added/updated cases cover current names/defaults, required-field column metadata, missing/tag refs, and partition evolution. They omit the inter-pass concurrent reorder in C1; the required-column unit case does not itself execute a branch omission error.
  • Expected results: all changed rows match their SQL projections and setup, including DEFAULT 36, nested payload labels, and branch/main/tag isolation. The full overwrite result still detects surviving duplicate seed rows after removal of the count assertion.
  • Observability: existing table/schema/spec identifiers and retry errors remain useful; no separate new metric/logging requirement found. C1 can currently commit without an error.
  • Transactions and persistence: Iceberg metadata CAS/replay validation remains installed; no Doris EditLog or replay format changes. No new failover mechanism or persistence gap found beyond the discarded planning witness.
  • Writes, atomicity, and crashes: the metadata commit remains atomic and targets the requested branch. C1 can atomically publish wrong column values. No new crash-cleanup owner or leak path identified; crash behavior was not executed.
  • FE/BE data passing: existing sink schema/spec/order carriers are reused; no new transmitted variable or send path.
  • Performance and memory: the per-field branch compatibility walk is removed; no new expensive loop or allocation pattern. BE MemTracker, internal partition visibility, and MoW delete-bitmap mechanics are unchanged.
  • Other concerns and user focus: no additional user focus was supplied; no further distinct actionable issue found after complete-scope and missed-problem rechecks.

Validation limits: static review only, as instructed. No builds, tests, cluster runs, or runtime reproduction were performed; expected-output generation provenance was not independently established. These limits do not leave an unresolved code candidate. Review convergence and delivery are reported separately by this invocation.

Review converged after 2 round(s).

}
// A branch selects only the parent snapshot / ref. Iceberg validates a branch write against the
// table-current schema and stamps that schema on the new snapshot, and Spark binds it the same way.
Schema schema = table.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.

[P1] Preserve the writer generation across overwrite replanning

For a v3 table ordered [a, b] with write-defaults 1 and 2, run INSERT OVERWRITE t@branch(br) VALUES (DEFAULT, DEFAULT). InsertOverwriteTableCommand.run first pins that schema and normalizePlan produces InlineTable(1, 2). If another Iceberg client commits updateSchema().moveFirst("b").commit() before the inner INSERT starts, InsertIntoTableCommand.initPlan calls resetConnectorStatementScope() and discards both the original schema and metadata identity. It then pins [b, a] while needNormalizePlan=false retains those already-expanded literals. BindSink.getColumnToChildOutput maps by ordinal, producing Sink[b <- 1, a <- 2]: the defaults are silently written into the wrong columns, and all schema checks pass against the second pin. Before this change, a table-only reorder left both branch-head schema pins unchanged. Preserve the outer writer context/identity across this handoff, or restart normalization from the original DEFAULT expressions after repinning; cover the inter-pass reorder with a controlled concurrency test.

@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
TPC-H: Total hot run time: 28692 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 2937f3442328f6ab64215bcaea1aa079c57af64c, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17602	5170	5039	5039
q2	2177	313	252	252
q3	10227	1383	825	825
q4	4691	467	344	344
q5	7466	797	519	519
q6	186	180	153	153
q7	740	798	614	614
q8	9278	1294	1039	1039
q9	5527	4198	4178	4178
q10	6840	1301	1034	1034
q11	431	253	226	226
q12	641	403	276	276
q13	18090	4044	3032	3032
q14	270	274	253	253
q15	q16	762	728	674	674
q17	1208	735	577	577
q18	7035	6180	6099	6099
q19	1119	956	599	599
q20	383	340	228	228
q21	5612	2491	2434	2434
q22	392	335	297	297
Total cold run time: 100677 ms
Total hot run time: 28692 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5966	5909	6084	5909
q2	756	609	579	579
q3	5629	5773	5407	5407
q4	1988	2135	1564	1564
q5	5598	5533	5892	5533
q6	241	190	143	143
q7	2197	2042	1793	1793
q8	2954	2688	2678	2678
q9	7923	7912	7841	7841
q10	3608	3592	3133	3133
q11	572	406	376	376
q12	676	665	503	503
q13	3325	3603	3027	3027
q14	288	288	265	265
q15	q16	654	725	617	617
q17	7718	7165	6879	6879
q18	12978	12188	12929	12188
q19	886	778	775	775
q20	2132	2136	1933	1933
q21	5621	4555	4802	4555
q22	511	452	422	422
Total cold run time: 72221 ms
Total hot run time: 66120 ms

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

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

@hello-stephen

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

query5	4304	595	435	435
query6	434	206	193	193
query7	4834	440	241	241
query8	320	163	150	150
query9	8772	3041	3113	3041
query10	486	305	257	257
query11	6013	3874	3560	3560
query12	154	93	90	90
query13	1278	448	329	329
query14	6139	3912	3584	3584
query14_1	3425	3368	3397	3368
query15	212	199	184	184
query16	957	415	406	406
query17	875	648	529	529
query18	2424	418	302	302
query19	205	165	136	136
query20	88	88	86	86
query21	214	127	109	109
query22	13070	12986	12716	12716
query23	13645	12961	12576	12576
query23_1	12612	12493	12562	12493
query24	7372	811	457	457
query24_1	483	465	474	465
query25	539	428	368	368
query26	1253	255	142	142
query27	2762	437	270	270
query28	4615	1929	1940	1929
query29	1578	603	456	456
query30	316	227	186	186
query31	997	936	817	817
query32	185	95	95	95
query33	529	314	255	255
query34	907	848	494	494
query35	749	752	636	636
query36	804	802	710	710
query37	126	98	84	84
query38	1815	1734	1679	1679
query39	799	802	732	732
query39_1	737	731	743	731
query40	241	136	109	109
query41	72	74	74	74
query42	89	86	86	86
query43	343	366	318	318
query44	1311	687	690	687
query45	182	179	179	179
query46	854	977	545	545
query47	2917	2776	2846	2776
query48	304	309	217	217
query49	575	412	283	283
query50	677	269	207	207
query51	10513	10522	10190	10190
query52	74	79	69	69
query53	189	210	149	149
query54	275	186	183	183
query55	73	66	62	62
query56	249	206	198	198
query57	1691	1603	1555	1555
query58	302	261	277	261
query59	2106	2163	1935	1935
query60	276	242	219	219
query61	145	142	147	142
query62	390	329	281	281
query63	186	157	158	157
query64	2784	938	744	744
query65	3314	3265	3248	3248
query66	1778	432	302	302
query67	20239	20164	20093	20093
query68	2981	1000	605	605
query69	407	285	240	240
query70	902	772	757	757
query71	299	236	215	215
query72	2754	2594	2329	2329
query73	523	534	291	291
query74	4624	4491	4327	4327
query75	2337	2282	1915	1915
query76	2308	1028	576	576
query77	340	368	268	268
query78	9814	9756	9285	9285
query79	1009	906	523	523
query80	835	428	340	340
query81	581	316	268	268
query82	823	132	102	102
query83	280	190	177	177
query84	322	122	104	104
query85	1008	426	373	373
query86	378	247	235	235
query87	2001	1957	1797	1797
query88	3702	2666	2657	2657
query89	356	286	240	240
query90	1722	168	170	168
query91	161	154	127	127
query92	97	88	88	88
query93	1034	1018	578	578
query94	558	300	270	270
query95	565	398	296	296
query96	671	477	234	234
query97	2456	2455	2302	2302
query98	158	155	145	145
query99	792	755	642	642
Total cold run time: 235886 ms
Total hot run time: 150375 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.10	0.05	0.04
query3	0.26	0.07	0.09
query4	1.61	0.10	0.10
query5	0.23	0.24	0.24
query6	1.17	0.68	0.68
query7	0.04	0.01	0.01
query8	0.05	0.04	0.04
query9	0.40	0.36	0.35
query10	0.57	0.60	0.58
query11	0.20	0.15	0.14
query12	0.18	0.15	0.15
query13	0.48	0.48	0.48
query14	1.01	1.01	1.00
query15	0.61	0.60	0.61
query16	0.34	0.36	0.33
query17	1.11	1.13	1.13
query18	0.19	0.19	0.19
query19	2.11	2.00	1.98
query20	0.02	0.02	0.01
query21	15.52	0.19	0.14
query22	4.88	0.05	0.05
query23	16.28	0.32	0.12
query24	2.90	0.58	0.42
query25	0.11	0.07	0.07
query26	1.00	0.22	0.14
query27	0.06	0.03	0.05
query28	3.53	0.76	0.35
query29	12.50	4.15	3.26
query30	0.28	0.12	0.10
query31	2.78	0.55	0.33
query32	3.22	0.59	0.51
query33	3.14	3.25	3.15
query34	15.30	3.96	3.40
query35	3.36	3.35	3.34
query36	0.56	0.48	0.42
query37	0.10	0.06	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.16	0.13
query41	0.09	0.04	0.03
query42	0.04	0.02	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.63 s
Total hot run time: 24.29 s

…n its planning passes

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66538, apache#65851

Problem Summary: Review follow-up. INSERT OVERWRITE into a connector table
plans twice. InsertOverwriteTableCommand.run pins the writer schema and
normalizes the plan against it: bare DEFAULT and empty rows are expanded by
position and values are cast to the pinned column types. The inner
InsertIntoTableCommand that runs the plan is built with
needNormalizePlan=false, and every attempt of its initPlan resets the
statement's connector scope and pins the writer schema again. Without a
column list BindSink then maps the normalized values by position onto the
second pin, and every later fence (the bound write metadata identity,
validateBoundWriteColumns, the begin and commit schema checks) compares
against that second pin. So when another client reorders the columns
between the passes, e.g. updateSchema().moveFirst("b") on [a, b] during

    INSERT OVERWRITE t VALUES (DEFAULT, DEFAULT)

the defaults are silently written as b <- 1, a <- 2. Main-branch overwrites
have had this exposure since apache#65851 (pin, then expand defaults) and apache#66529
(repin on every attempt); the previous commit gives branch overwrites the
same one, because a branch write now pins the table's current schema.
branch-4.2 carries the pinned writer context from the outer pass into the
inner insert, so both passes share one generation.

The overwrite now records on the inner insert's context the writer columns
and write metadata identity it normalized against, and the inner insert,
after pinning again, fails the statement with "retry the statement" when
either differs. Nothing changes when the table did not change between the
passes or for any other insert.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - InsertUtilsTest: a reordered writer schema and a changed write metadata identity between the passes fail; the same generation, and an insert that normalizes its own plan, pass
    - InsertOverwriteTableCommandTest, InsertOverwriteDroppedTargetTest, InsertOverwriteSkipAuthResetTest, IcebergStaticPartitionLiteralTest, InsertIntoTableCommandTest, InsertIntoTableCommandTableStreamTest, CloudInsertProfileRetryTest, DictionaryInsertTargetDropRaceTest, PluginDrivenTableSinkBindingTest, PluginDrivenTableSinkTest, ReadLockTest, RowLevelDmlCommandTest
- Behavior changed: Yes. An INSERT OVERWRITE into a connector table whose writer schema or write metadata changes while the statement plans now fails with "retry the statement" instead of binding its values by position to the new schema.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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