Skip to content

[fix](fe) Resolve DEFAULT(column) on Iceberg tables from the write schema - #69488

Merged
morningman merged 7 commits into
apache:masterfrom
morningman:b2m-t35j-iceberg-default-col-write-target
Nov 2, 2026
Merged

morningman merged 7 commits into
apache:masterfrom
morningman:b2m-t35j-iceberg-default-col-write-target

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, #57531, #65851

Problem Summary:

In short. On an Iceberg table, DEFAULT(column) anywhere except UPDATE ... SET and MERGE ... VALUES reads the default from the read schema, which carries no Iceberg write-default. This PR resolves it against the write schema that the statement pinned for its target, as branch-4.1 and branch-4.2 do.

Background. An Iceberg write-default applies to rows written after it is set. So it lives in the writer schema that a write statement pins (IcebergWriteSchemaContext), not in the columns of the read schema. #65851 resolves DEFAULT(column) against that pinned schema in UPDATE ... SET, MERGE ... UPDATE SET and MERGE ... INSERT VALUES (ConnectorWriteSchemaUtils). Every other DEFAULT(column) either fails at binding or reaches RewriteDefaultExpression (#57531), which reads the default from the bound column. branch-4.1 #66538, also in branch-4.2, resolves these against the pinned write target too. It rejects DEFAULT(column) on an Iceberg column in a statement that does not write the table.

The problem, and what it cost. Reproduced on a local cluster with a file:// format-version 3 table: CREATE TABLE t (id INT, c INT), ALTER TABLE t ADD COLUMN d INT DEFAULT 42, one row (0, 0), so d = 42.

Statement Master today After this PR (same as branch-4.2)
INSERT INTO t (id, c, d) VALUES (1, 1, DEFAULT(d)) Unknown column 'd' in 'table list' writes 42
INSERT INTO t (id, c, d) SELECT 2, 2, DEFAULT(d) FROM t WHERE id = 0 writes NULL writes 42
INSERT INTO t (id, c, d) SELECT 3, 3, DEFAULT(d) Unknown column 'd' writes 42
UPDATE t SET c = 100 WHERE d = DEFAULT(d) updates nothing: d = NULL makes the plan an empty set updates every row, d = 42 is pushed down to the scan
MERGE INTO t USING (SELECT 0 AS id) s ON t.id = s.id AND t.d = DEFAULT(t.d) WHEN MATCHED THEN UPDATE SET c = 300 matches nothing updates row 0
SELECT DEFAULT(d) FROM t NULL DEFAULT(d) must reference a column of the table that this statement writes

The UPDATE and MERGE cases fail silently: the statement succeeds and changes nothing.

How this PR fixes it.

  • fe-core cannot tell from a column whether its connector keeps defaults in the read schema. Hive does: an HMS default shows up in both schemas. Iceberg does not. The new connector capability SUPPORTS_WRITE_ONLY_DEFAULTS says which kind a table is. The Iceberg connector declares it. The Hive connector passes it on to iceberg-on-HMS tables, like the other per-table capabilities, but only through the two-argument getTableSchema. Since [improvement](datasource) Version Iceberg partition views and SQL cache metadata #69299, a statement that pins the table at its latest snapshot reads the schema from the snapshot overload, which reflects none of them, so on an iceberg-on-HMS table such a statement keeps master's results (for example, UPDATE t SET c = 100 WHERE d = DEFAULT(d) still changes no row). Reflecting the sibling's capabilities there changes planning for all of them and is left to a follow-up PR.
  • For a target that declares it, InsertUtils.normalizePlan resolves DEFAULT(column) in INSERT ... VALUES and in the select lists of INSERT ... SELECT, including the replacements of SELECT * REPLACE(...), against the pinned write schema. It does this by name, before the source relations are bound, so the name always refers to a target column, as in branch-4.2. In MySQL, DEFAULT(column) in a SELECT refers to a source column instead. A derived table in FROM is a query of its own and is left alone: DEFAULT(column) in it still names a column of its own FROM clause.
  • RewriteDefaultExpression takes the default for a DEFAULT(column) bound to such a table from the pinned write column with the same field id. That covers the WHERE of UPDATE and the ON of MERGE. A MERGE resolves it whatever its WHEN branches, also when they all delete, as branch-4.2 does. If the statement pinned no write schema for that table, it fails. A DELETE pins the writer schema too, for its delete sink, but it writes no row values, so RowLevelDmlCommand marks its target and DEFAULT(column) on it fails with DEFAULT(d) cannot be used in a DELETE: the defaults of this table apply only to the rows that a statement writes, as in branch-4.2, whose DELETE installs no write schema context. Master reads NULL there, so DELETE FROM t WHERE d = DEFAULT(d) deletes nothing; resolved to the write-default it would delete every row with d = 42.
  • A resolved default is cast to the type of its writer column before the expression around it is bound, as RewriteDefaultExpression already does for a bound DEFAULT(column). The parsed literal -1 is a TINYINT, so without the cast bit_count(DEFAULT(c)) on an INT column would count 8 bits instead of 32. The external UPDATE, MERGE and changelog paths use the same helper. RewriteDefaultExpression rejects a bound DEFAULT(column) whose writer column has another type than the read column the statement was bound with, which happens only when the table changed outside Doris after Doris cached it, and asks to refresh the table: the expressions around it were bound against the read type.
  • Hive, Paimon and internal tables do not declare the capability, so they behave as before.
  • The new enum constant changes the connector plugin API surface, so connector.plugin.api.version goes from 26.0 to 27.0, as for the other recent changes to that surface ([improvement](datasource) Version Iceberg partition views and SQL cache metadata #69299, [feature](fe) Gate catalog SSRF endpoints and driver_url via a connector security surface #69588, [improvement](datasource) Bound and fairly schedule external scan planning #69298, [improvement](fe) Default external scans to bounded split batches #69683 and [fix](fe) Stop split enumeration when queries finish or terminate #69835 took 22.0 to 26.0).

Where DEFAULT(column) resolves on an Iceberg target (direct Iceberg catalogs).

Statement Position of DEFAULT(column) Result branch-4.2
INSERT / INSERT OVERWRITE VALUES rows the target column's write-default, typed as the column same
the INSERT's own select lists (with or without FROM, set operation branches, SELECT * REPLACE) the target column's write-default, by name, before binding same, except * REPLACE, which branch-4.2 binds to the source
GROUP BY / ROLLUP outputs, a derived table in FROM, WITH definitions, subqueries, WHERE / ON bound like any DEFAULT(column) (last row) same, except derived tables, which branch-4.2 resolves against the target
a VALUES branch of a set operation fails with an unknown column, as on master same
a qualifier other than the target (DEFAULT(src.c), DEFAULT(ctl.db.t.c)) in the positions above fails: column in assignment list is invalid same
UPDATE SET / WHERE write-default same
MERGE ON and WHEN ... AND conditions, whatever the WHEN branches, also when they all delete write-default same
UPDATE SET / INSERT VALUES write-default same
DELETE (also DELETE ... USING) anywhere fails: cannot be used in a DELETE fails: its DELETE has no write schema context
any a bound DEFAULT(x.c) x is the written table: its write-default by field id; an OLAP or Hive table: that column's default; another Iceberg table: fails with must reference a column of the table that this statement writes same

A bound DEFAULT(column) whose writer column has another type than the read column fails and asks to refresh the table. Where DEFAULT(column) is bound rather than resolved by name (the third row), it keeps its meaning from before this PR and names a column of its own FROM clause, as on master and branch-4.2: for example in INSERT INTO t SELECT id, DEFAULT(d) FROM src GROUP BY id, d with an OLAP or Hive src, it takes the default of src.d.

Results.

  • Local cluster, with the table above: the table shows what each statement did before and after. EXPLAIN UPDATE t SET c = 5 WHERE d = DEFAULT(d) goes from an empty set to a scan with d = 42 pushed down to Iceberg.
  • Unit tests: 6 new tests in InsertUtilsTest, 6 in the new RewriteDefaultExpressionTest and 1 in RowLevelDmlCommandTest. They cover VALUES, and INSERT ... SELECT both from a staging relation whose own column is also name and without FROM: each resolves to the pinned default, and an unknown column fails. DEFAULT(column) inside a derived table, qualified or not and also under a UNION branch, is left to binding. A SELECT * REPLACE(DEFAULT(name) AS name) replacement resolves like the rest of the select list and keeps the star's qualifier and EXCEPT list. The pinned write column wins over the read column. A statement that writes nothing fails. A table without the capability is unchanged. With master's InsertUtils and RewriteDefaultExpression, the 5 tests of the fix fail and the 2 tests of the unchanged path pass. The SELECT * REPLACE test fails with this PR's previous InsertUtils, where the replacement stays DEFAULT(name). IcebergConnectorTest, HiveConnectorMetadataSiblingDelegationTest and ConnectorPluginSurfaceTest pass. So do the fe-core tests around the write schema: ConnectorWriteSchemaUtilsTest, ExternalRowLevelUpdatePlanBuilderTest, ExternalRowLevelMergePlanBuilderTest, ChangelogRowLevelDmlTransformTest and PluginDrivenExternalTableTest. Two of the tests pin the typing: bit_count(DEFAULT(bits)) gets the default as an INT in VALUES and in SELECT, and fails without the cast; a BIGINT writer column over an INT read column is rejected, directly and under an IF bound to INT. DEFAULT(column) on the target of a DELETE fails even with a pinned write-default, and only a DELETE marks its target (an UPDATE does not); skipping the check, or marking UPDATE instead of DELETE, makes exactly these two tests fail. PositionDeleteRowLevelDmlTransformTest, PhysicalPlanTranslatorIcebergRowLevelDmlTest, DeleteFromCommandTest and UpdateCommandTest pass.
  • Regression: test_iceberg_initial_defaults now writes rows 20 to 22 with DEFAULT(default_int) in VALUES and in INSERT ... SELECT with and without FROM. It then updates them with WHERE default_int = DEFAULT(default_int) and MERGE ... ON ... = DEFAULT(...), for Parquet and ORC, and checks the rows in the .out. A plain SELECT DEFAULT(default_int) must fail, and so must a DELETE ... WHERE default_int = DEFAULT(default_int) on rows 20 to 22, which hold the write-default. The suite passed locally before the DELETE block was added, and the whole suite, DELETE block included, has since passed in two External regression runs on CI. With master's FE it fails at the first of these statements: Unknown column 'default_int' in 'table list'.

Release note

Fix: On Iceberg tables, DEFAULT(column) in INSERT ... VALUES, INSERT ... SELECT and the WHERE / ON conditions of UPDATE and MERGE now resolves to the column's write-default. Before, it failed, wrote NULL, or matched no row. SELECT DEFAULT(column) on an Iceberg table now fails instead of returning NULL, and so does DEFAULT(column) in a DELETE on an Iceberg table, which used to be NULL and delete nothing.

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'), ALTER TABLE t ADD COLUMN d INT DEFAULT 42, then the 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. INSERT ... SELECT DEFAULT(column) into an Iceberg table, also in the replacements of SELECT * REPLACE, names a target column, even when the source has a column of that name; inside a derived table in FROM it still names a column of that table's own FROM clause. In a DELETE, DEFAULT(column) on an Iceberg table is rejected.
  • 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

/review

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed exact head bda3e28cdab081693dc2dd8d739d784005b7ce9d against the supplied authoritative aggregate patch. Five new verified findings: one P1 and four P2, all inline. No existing review threads or blocking comments were present on refreshed intake. No additional user focus was supplied.

Critical checkpoint conclusions:

  • Goal and proof: ordinary INSERT VALUES/SELECT and UPDATE/MERGE predicates now consult pinned write defaults; the inline findings show incorrect typed-expression results and uncovered statement/schema paths. Added tests exercise the ordinary cases but do not prove the broader behavior.
  • Scope, clarity, reuse: the capability-based change is focused and connector-agnostic. Reusing the default resolver loses referenced-column typing and reuses a narrower assignment-name validator.
  • Concurrency and locks: no new threads or locks. Existing table read-lock/finally-unlock structure remains. Independently cached read schemas versus live writer schemas expose the reported type-evolution issue; no separate lock-order/deadlock defect found.
  • Lifecycle and initialization: write schemas/identities remain statement-scoped; INSERT and row-level command pinning, retry/prepared-execution reset, plugin metadata scope and classloader boundaries were traced. No new lifetime/static-initialization defect found. Overwrite repinning behavior also exists for preexisting bare DEFAULT and is not a new finding here.
  • Configuration: no configuration items added.
  • Compatibility: the enum changes the plugin surface; the deliberate major API bump to 20.0, version pin and plugin-surface baseline agree. Metadata method signatures are unchanged. No new wire or storage format is introduced.
  • Thrift/protobuf IDs: no gensrc definitions changed; branch field-ID checks are not applicable.
  • Parallel paths: INSERT/OVERWRITE/EXPLAIN, UPDATE/MERGE/DELETE and CHANGELOG/POSITION_DELETE paths were traced. Row-level commands pin before planning. Aggregate/CTE/subquery normalization and snapshot Hive capability inheritance are incomplete as reported.
  • Conditional checks: capability gates preserve ordinary Hive/Paimon/internal behavior; table-ID plus field-ID matching is consistent. Fully qualified target names are wrongly rejected as reported. A differing source alias is not raised because the PR deliberately specifies target-name semantics.
  • Tests and modularity: added JUnit 5 cases cover direct VALUES/projection/no-FROM, unknown names, bound pin/absent pin, capability-disabled behavior and ordinary sibling delegation. Missing coverage includes typed nested expressions, grouped/nested queries, full qualification, evolved read/write types and snapshot sibling delegation.
  • Changed expected results: the ordered Parquet/ORC rows 20-22 and expected SELECT rejection match the added statements and existing default value 35. Result-file generation provenance was not independently verified.
  • Observability: existing analysis errors and statement tracing suffice for this planning change; no new distributed operation or metrics requirement identified.
  • Persistence and failover: no EditLog/replay or durable metadata mutation protocol changes.
  • Writes, atomicity and crashes: planning precedes existing writer transaction/finalization/commit fences. No new commit/rollback or crash-cleanup defect found; the P1 expression issue can persist the wrong computed value in an otherwise valid write.
  • FE/BE state: no new transmitted variables or serialization/send paths. Integer-width function semantics were cross-checked in FE and BE for the P1 finding.
  • Performance: traversal and default lookup add planning work proportional to query size and target width. No substantiated material CPU/memory regression or repeated external-I/O defect found.
  • Other invariants: no BE allocation ownership, partition visible-version, or MoW delete-bitmap changes. Errors propagate through existing analysis exceptions. Stable connector field IDs and writer metadata fences remain in place, with the reported default-type caveat.

Validation was static only, as required: all 15 changed files, related callers/control flow, tests and expected outputs were inspected; no build, runtime test, source change, or format rewrite was performed. No unresolved candidate or assigned static coverage gap remains. Runtime execution and output-generation provenance remain unverified. Convergence and review severity are reported separately by the delivery helper.

Review converged after 2 round(s).

if (!writeOnlyDefaultColumns.isPresent()) {
return value;
}
return (NamedExpression) ConnectorWriteSchemaUtils.resolveDefaultReferences(value,

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 referenced column type before binding its consumers

For an Iceberg t(c INT) with write-default -1, INSERT INTO t(c) SELECT bit_count(DEFAULT(c)) should evaluate bit_count on an INT and write 32. This new route instead produces OneRow(bit_count(-1:TINYINT)): ConnectorWriteSchemaUtils.resolveDefaultReferences parses the default SQL without casting it to the referenced column type, and the parser chooses TINYINT for -1. BitCount has width-specific signatures, and both its FE evaluator and BE implementation return 8 for TINYINT -1, versus 32 for INT -1. Casting the completed expression to the sink's INT cannot restore the lost value; VALUES expressions have the same problem. Cast each substituted default to its referenced writer column's type before binding surrounding operators/functions, as the bound DEFAULT rewrite does.

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 cf75568. ConnectorWriteSchemaUtils.resolveDefaultReferences now casts each resolved default to the type of the writer column it references (TypeCoercionUtils.castUnbound) before the expression around it is bound, so bit_count(DEFAULT(c)) on an INT column with write-default -1 gets an INT -1 in VALUES and in SELECT. The external UPDATE / MERGE / changelog paths use the same helper and get the same typing. branch-4.2 is not affected: it builds write defaults as literals of the column type. Test: InsertUtilsTest#writeOnlyDefaultIsTypedAsItsColumnBeforeBinding (VALUES and SELECT); with the previous commit the argument of bit_count is TinyIntLiteral(-1).

*/
private static Plan resolveSelectDefaultReferences(Plan query, Optional<List<Column>> writeOnlyDefaultColumns,
TableIf table, List<String> targetNameParts) {
return query.rewriteUp(plan -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Normalize defaults in every SELECT output shape and nested query

With target t(c INT) having a write-default, INSERT INTO t(c) SELECT DEFAULT(c) FROM (SELECT 1 AS k) s GROUP BY k parses as Sink(t.c) -> Aggregate(groupBy=k, output=DEFAULT(c)) -> Alias(s) -> OneRow(k=1). The lambda handles only Project/OneRow outputs, so it leaves this DEFAULT untouched and binding fails with unknown column c against source s.k; removing GROUP BY resolves the same target reference successfully. CTE definitions and scalar-subquery plans also escape this traversal: command-level WITH is attached after normalization, CTE definitions are extraPlans(), and subquery plans are not expression children. Resolve target defaults in aggregate/repeat outputs and explicitly traverse these separately owned query plans, with grouped/CTE/subquery coverage.

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.

Not changing this in this PR. The INSERT ... SELECT resolution covers the select lists of the main query (projects and the one-row relation), the same shapes as branch-4.2, which this PR ports (InsertUtils#resolveIcebergSelectDefaultReferences there). In an aggregate output, a WITH clause or a subquery, DEFAULT(c) keeps its meaning from before this PR and names a column of its own FROM clause, as in MySQL: with no such column the statement fails with an unknown-column error; a column of the target table itself gets the pinned writer default through the bound rewrite; a column of another Iceberg table now fails with "must reference a column of the table that this statement writes" instead of answering NULL; a column of a Hive or OLAP source gets that column's default, as before. Resolving target names in those shapes needs other analysis stages (the command-level WITH is attached after normalizePlan; CTE and subquery plans are not children of the project), so it would be a separate change.

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.

Correction to my reply above: for a derived table in FROM, "keeps its meaning from before this PR" did not hold. The walk descended into it and resolved its DEFAULT(c) against the target, as #69488 (comment) found. 724071d stops the walk there, so a derived table now behaves as described above; aggregate outputs, WITH and scalar subqueries are unchanged by that commit.

return value;
}
return (NamedExpression) ConnectorWriteSchemaUtils.resolveDefaultReferences(value,
writeOnlyDefaultColumns.get(), (ExternalTable) table, ConnectContext.get(), targetNameParts, null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Accept fully qualified target column references

INSERT INTO ctl.db.t(c) SELECT DEFAULT(ctl.db.t.c) now reaches ConnectorWriteSchemaUtils.resolveDefaultReferences, which passes the four-part name to UpdateCommand.checkAssignmentColumn. That assignment-LHS validator accepts only one, two, or three name parts and throws column in assignment list is invalid for this unambiguous target reference. Ordinary Nereids column binding explicitly supports catalog.db.table.column, and this form should also resolve against the pinned target schema. Use qualification validation that includes the catalog component rather than applying the narrower UPDATE-assignment grammar; cover the fully qualified form in SELECT and VALUES.

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.

Not changing this here. DEFAULT(...) in VALUES / SELECT accepts the qualifications that the existing Iceberg UPDATE / MERGE DEFAULT handling accepts, through the same UpdateCommand.checkAssignmentColumn (branch-4.2 does the same): c, t.c, db.t.c. ctl.db.t.c fails with "column in assignment list is invalid" instead of resolving; it does not write a wrong value. Accepting catalog-qualified names would change the assignment-column rule of all DML paths, so it belongs in its own change.

if (originalTable.filter(PluginDrivenExternalTable.class::isInstance)
.filter(table -> ((PluginDrivenExternalTable) table).hasWriteOnlyDefaults())
.isPresent()) {
column = pinnedWriteColumn(context.cascadesContext.getStatementContext(), originalTable.get(), column)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Derive the result type after selecting the writer column

targetType is computed from the read column before this substitution and remains unchanged afterward. The read schema can still be cached as INT after an external Iceberg promotion to LONG, while pinAndGet independently loads current LONG writer columns and a new write-default such as 4900000001. Then UPDATE t SET other = 'x' WHERE DEFAULT(score) = 4900000001 produces Filter(CAST(4900000001 AS INT) = 4900000001) above the cached INT scan, instead of using the BIGINT writer default, causing overflow/error or an incorrect predicate. The writer fences compare the current writer columns/identity and do not validate this stale predicate type. Compute the type from the selected writer column and add a same-field-ID read-INT/write-BIGINT 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 cf75568. RewriteDefaultExpression now takes the result type from the pinned writer column, after the substitution. Test: RewriteDefaultExpressionTest#writeOnlyDefaultTakesTheTypeOfThePinnedWriterColumn pins a BIGINT writer column with default 4900000001 over an INT read column with the same field id and expects a BIGINT literal; with the previous commit the result is cast(4900000001 as INT).

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.

Follow-up: superseded in 688e8d5 (see the newer thread on this file). Typing the substituted default as the writer column fixed the DEFAULT node itself but not the expressions bound around it against the read type, so a writer column whose type differs from the read column now fails with a refresh hint instead.

ConnectorCapability.SUPPORTS_STORAGE_PREDICATE_PRUNING,
ConnectorCapability.SUPPORTS_NESTED_COLUMN_SCHEMA_CHANGE));
ConnectorCapability.SUPPORTS_NESTED_COLUMN_SCHEMA_CHANGE,
ConnectorCapability.SUPPORTS_WRITE_ONLY_DEFAULTS));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Reflect this capability for snapshot schemas as well

The two-argument getTableSchema reflects this set, but the snapshot overload still returns the sibling schema directly. For SELECT DEFAULT(c) FROM hms_catalog.db.iceberg_t FOR VERSION AS OF <snapshot_id>, PluginDrivenMvccExternalTable.loadSnapshot publishes that unreflected historical schema. tableCapabilities() reads the ambient pinned schema, while the Hive catalog itself does not declare write-only defaults, so hasWriteOnlyDefaults() is false and the bound rewrite returns NULL for a nullable column instead of rejecting this read-only use. Direct Iceberg catalogs retain the guard through their connector-wide capability. Apply the same reflection to getTableSchema(session, handle, snapshot) and test that overload.

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.

Not in this PR. HiveConnectorMetadata#getTableSchema(session, handle, snapshot) returns the sibling's schema without reflectSiblingCapabilities for every inherited capability, not only this one (auto-analyze, Top-N lazy materialization, nested column prune, field-id access path, storage predicate pruning, nested column DDL); that predates this PR. So for a time-travel read of an iceberg-on-HMS table, SELECT DEFAULT(c) ... FOR VERSION AS OF keeps master's current answer (NULL) instead of the new error; nothing is written. Reflecting capabilities on the snapshot path changes scan planning for all of them and should come with its own tests, so it is left to a separate change.

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.

Correction to my reply above. Since #69299 (merged on 10-26, after that reply), the latest Iceberg pin retains its schema, so fe-core publishes the schema from getTableSchema(session, handle, snapshot) for every statement that pins the table, not only for time travel. On an iceberg-on-HMS table that overload returns the sibling schema without reflectSiblingCapabilities, so hasWriteOnlyDefaults() is false in those statements: DEFAULT(c) in UPDATE WHERE / MERGE ON still evaluates to NULL and changes no row, and SELECT DEFAULT(c) still returns NULL, the same results as master today. Direct Iceberg catalogs are not affected, because the Iceberg connector declares the capability connector-wide.

From the code, the overload leaves out every capability the Hive connector reflects from the sibling, not only this one (auto and sampled analyze, Top-N lazy materialization, nested column pruning, field-id access paths, storage predicate pruning, nested column DDL). Reflecting them in the snapshot overload changes planning for all of them, so it will come in its own PR with its own tests, after this one. The PR description now states the iceberg-on-HMS limitation.

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.

Follow-up: fixed in #69999. The snapshot overload of getTableSchema now reflects the same inherited capabilities as the two-argument one (both go through one helper), so an iceberg-on-HMS table keeps them in every statement that pins a snapshot: explicit time travel, and since #69299 the latest-snapshot pin as well. Checked on a local cluster against a Hive 3.1.2 metastore: through the HMS catalog, SELECT DEFAULT(d) FROM t FOR VERSION AS OF <snapshot> now fails with DEFAULT(d) must reference a column of the table that this statement writes, as it does through an Iceberg catalog, and UPDATE ... WHERE d = DEFAULT(d) and MERGE ... ON ... AND t.d = DEFAULT(t.d) now update the rows.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17612	5322	5250	5250
q2	2178	335	301	301
q3	10106	1491	892	892
q4	4700	463	334	334
q5	7452	828	535	535
q6	207	205	157	157
q7	822	825	607	607
q8	9276	1525	1178	1178
q9	5788	4457	4406	4406
q10	6890	1305	1006	1006
q11	454	250	230	230
q12	641	435	290	290
q13	18096	3695	2996	2996
q14	282	275	254	254
q15	q16	746	726	670	670
q17	1347	784	593	593
q18	7049	6177	6116	6116
q19	1109	1097	638	638
q20	386	343	231	231
q21	5279	2190	2399	2190
q22	379	310	246	246
Total cold run time: 100799 ms
Total hot run time: 29120 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5957	5915	5940	5915
q2	717	552	536	536
q3	5220	5399	4946	4946
q4	2109	2251	1538	1538
q5	5283	5469	5817	5469
q6	297	212	155	155
q7	2366	2107	2032	2032
q8	3264	2760	2794	2760
q9	8169	7766	8066	7766
q10	3810	3672	3281	3281
q11	652	461	411	411
q12	719	725	507	507
q13	3628	3951	3227	3227
q14	315	326	283	283
q15	q16	692	738	625	625
q17	8439	7614	7323	7323
q18	13025	12263	12882	12263
q19	1047	863	868	863
q20	2150	2165	1908	1908
q21	5960	4779	4924	4779
q22	534	459	411	411
Total cold run time: 74353 ms
Total hot run time: 66998 ms

@hello-stephen

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

query5	4308	565	446	446
query6	428	209	210	209
query7	4836	470	217	217
query8	330	182	155	155
query9	8796	3115	3074	3074
query10	485	320	258	258
query11	5993	3838	3550	3550
query12	144	93	88	88
query13	1261	463	335	335
query14	6173	3911	3671	3671
query14_1	3405	3411	3392	3392
query15	208	198	195	195
query16	955	425	394	394
query17	867	674	530	530
query18	2433	425	318	318
query19	193	170	133	133
query20	84	84	82	82
query21	219	124	107	107
query22	12943	13066	12885	12885
query23	13519	12971	12586	12586
query23_1	12556	12532	12657	12532
query24	7372	830	463	463
query24_1	471	474	475	474
query25	545	431	386	386
query26	1273	271	144	144
query27	2772	439	270	270
query28	4635	1968	1981	1968
query29	1586	603	470	470
query30	315	230	181	181
query31	1019	929	812	812
query32	149	92	92	92
query33	532	307	254	254
query34	899	843	496	496
query35	760	750	640	640
query36	826	820	700	700
query37	125	101	100	100
query38	1783	1745	1669	1669
query39	792	769	750	750
query39_1	734	727	736	727
query40	221	118	103	103
query41	76	68	69	68
query42	86	92	85	85
query43	345	359	309	309
query44	1306	687	700	687
query45	188	178	166	166
query46	883	964	561	561
query47	3035	2936	2754	2754
query48	296	308	214	214
query49	570	392	311	311
query50	671	278	219	219
query51	10368	10400	10402	10400
query52	76	77	69	69
query53	183	204	164	164
query54	256	204	185	185
query55	74	66	61	61
query56	229	203	201	201
query57	1632	1626	1567	1567
query58	297	265	266	265
query59	2069	2163	1955	1955
query60	291	246	226	226
query61	149	143	145	143
query62	402	325	301	301
query63	211	159	154	154
query64	2763	979	754	754
query65	3306	3264	3268	3264
query66	1784	413	309	309
query67	20347	20068	19986	19986
query68	2965	1011	604	604
query69	373	295	259	259
query70	860	763	775	763
query71	296	228	208	208
query72	2734	2638	2337	2337
query73	510	521	294	294
query74	4674	4522	4331	4331
query75	2345	2254	1884	1884
query76	2316	1024	596	596
query77	347	362	278	278
query78	9941	9773	9249	9249
query79	1020	835	523	523
query80	1148	445	325	325
query81	587	318	276	276
query82	574	130	103	103
query83	307	194	189	189
query84	314	119	100	100
query85	862	416	369	369
query86	392	233	232	232
query87	2000	1960	1821	1821
query88	3624	2663	2646	2646
query89	353	272	242	242
query90	1874	162	161	161
query91	162	148	124	124
query92	96	89	80	80
query93	1000	987	576	576
query94	620	310	255	255
query95	560	394	303	303
query96	650	478	233	233
query97	2459	2461	2317	2317
query98	157	151	145	145
query99	774	769	645	645
Total cold run time: 235439 ms
Total hot run time: 151076 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 bda3e28cdab081693dc2dd8d739d784005b7ce9d, data reload: false

query1	0.01	0.01	0.01
query2	0.09	0.05	0.05
query3	0.26	0.08	0.09
query4	1.61	0.11	0.09
query5	0.26	0.26	0.25
query6	1.17	0.68	0.67
query7	0.04	0.00	0.00
query8	0.05	0.04	0.04
query9	0.42	0.33	0.34
query10	0.58	0.57	0.59
query11	0.20	0.15	0.15
query12	0.19	0.15	0.14
query13	0.49	0.49	0.50
query14	1.03	1.04	1.00
query15	0.63	0.62	0.60
query16	0.34	0.34	0.35
query17	1.16	1.14	1.10
query18	0.21	0.18	0.20
query19	2.14	2.03	2.04
query20	0.02	0.01	0.02
query21	15.48	0.21	0.13
query22	4.79	0.05	0.05
query23	16.25	0.31	0.12
query24	3.10	0.46	0.42
query25	0.10	0.07	0.05
query26	1.10	0.22	0.16
query27	0.06	0.05	0.05
query28	3.47	0.78	0.36
query29	12.50	4.21	3.25
query30	0.29	0.14	0.09
query31	2.77	0.55	0.33
query32	3.22	0.59	0.50
query33	3.24	3.17	3.21
query34	15.34	4.02	3.38
query35	3.32	3.32	3.35
query36	0.54	0.46	0.42
query37	0.10	0.06	0.07
query38	0.05	0.04	0.03
query39	0.04	0.04	0.03
query40	0.18	0.15	0.15
query41	0.08	0.02	0.02
query42	0.04	0.03	0.03
query43	0.04	0.04	0.04
Total cold run time: 97 s
Total hot run time: 24.29 s

@morningman
morningman force-pushed the b2m-t35j-iceberg-default-col-write-target branch from bda3e28 to cf75568 Compare October 23, 2026 08:28
@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.

Static review of all 16 changed paths at cf75568, including related binding, writer-schema, capability, and commit paths. One new P2 finding: the bound DEFAULT rewrite can widen its result after an enclosing IF has retained an INT signature, causing a valid BIGINT default to become NULL or overflow during folding.

The existing P1 4355508072 (parsed default width before binding) and the direct DEFAULT result-type instance in P2 4355508096 are fixed at this head. No existing P0/P1 remains applicable. Qualification (4355508088), grouped/CTE/subquery traversal (4355508080), and HMS historical-schema capability reflection (4355508100) remain covered by their existing threads and replies; they are not reposted. The new finding concerns a different narrowing cast synthesized later by the enclosing IF consumer.

Critical checkpoint conclusions:

  • Goal and proof: writer-only defaults now use the target's pinned schema in the covered INSERT and row-level paths, and read-only use is rejected. Added tests cover these cases, but the enclosing-consumer promotion case in the inline finding remains incorrect.
  • Scope and clarity: changes are localized and reuse the capability accessor, statement schema pin, and shared default resolver.
  • Concurrency and locks: no new threads, shared mutators, locks, or lock order; existing statement ownership and connector generation fences remain in use. No new heavy work inside a newly introduced lock was found.
  • Lifecycle and initialization: writer columns/identity are pinned before normalization or synthesis, selected by target table and field ID, and cleared across retries/executions. Branch selection remains in the existing writer scope. No static initialization dependency is introduced.
  • Configuration: no configuration item is added.
  • Compatibility: connector API major and test pin move from 20.0 to 21.0 with the new enum surface entry. Metadata method surface is unchanged. No storage-format or FE/BE protocol change.
  • Thrift/protobuf: no gensrc definitions are changed.
  • Parallel paths: inspected VALUES, SELECT projects/one-row plans, INSERT OVERWRITE, UPDATE/MERGE assignments and predicates, changelog helper consumers, and direct/HMS Iceberg delegation. Existing traversal/qualification/snapshot limits are identified above.
  • Conditions and errors: capability gates preserve other connectors' behavior; absent matching target/field and invalid references fail explicitly. Optional absent Iceberg defaults serialize as SQL NULL; required absent defaults fail.
  • Test coverage: positive/negative FE tests, capability/SPI tests, and Parquet/ORC regression additions cover the intended paths. Consumer binding plus rewrite under a stale read type needs coverage as requested inline. Tests were inspected, not executed.
  • Expected results: both new ordered regression blocks match rows 20-22, default35, UPDATE labels and the MERGE change to row22. Result-generation provenance was not independently verified.
  • Observability: existing analysis errors identify the DEFAULT reference/column; no new operational metric or log is needed for these local substitutions.
  • Transactions and persistence: no EditLog, replay, failover or persistence algorithm changes; existing writer schema/identity checks and commit fences remain responsible for remote metadata races.
  • Writes and atomicity: existing row-level transaction/commit machinery is reused. The inline finding concerns an incorrect pre-write predicate or analysis failure; no new crash/resource/partial-commit mechanism was identified.
  • FE/BE variables: no new transported variable or sending path.
  • Performance and memory: planning work is linear in visited plan/expressions with a writer-column lookup per DEFAULT; no new per-row runtime work or substantiated performance defect. No BE memory ownership, partition visible-version or MoW delete-bitmap code changes.
  • Other issues and user focus: no additional user focus was supplied. All initial risks and candidates are accepted, duplicate-fenced or dismissed with recorded evidence.

Coverage limits: static inspection only, as required; no builds, unit/regression execution, live external-cluster behavior, or output-generation provenance were validated. No unresolved static candidate or unreviewed changed path remains. Convergence is reported separately by the delivery metadata.

Review converged after 2 round(s).

}
// The pinned writer column can be wider than the read column the slot was bound with, e.g. after a type
// promotion committed outside Doris, and the default is a value of the writer column.
DataType targetType = DataType.fromCatalogType(column.getType());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Bind enclosing consumers against the writer column type

With a cached read score INT and a same-field-ID writer score BIGINT whose write-default is 4900000001, consider:

UPDATE t SET score = DEFAULT(score)
WHERE IF(TRUE, DEFAULT(score), 0) = 4900000001;

Binding produces Filter(CAST(IF(TRUE, DEFAULT(score:INT), 0):INT AS BIGINT) = 4900000001) -> Scan(score:INT). This rewrite correctly replaces DEFAULT with a BIGINT literal, but Rewriter preserves function signatures, so If.withChildren/FunctionParams retain IF's INT result. FoldConstantRuleOnFE.visitIf then calls ensureSameResultType on the true branch and synthesizes CAST(4900000001 AS INT): loose casting yields NULL and updates no rows, while strict casting throws overflow. The predicate should be true for every row.

The earlier direct DEFAULT result-type issue is fixed here; this narrowing cast is created separately by the already-bound IF consumer. Resolve the pinned writer type before binding its consumers, or rebind their signatures/coercions after substitution, and cover an enclosing IF through both analysis and rewrite.

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 688e8d5. Agreed: once IF, CASE or COALESCE have been bound against the read type, a wider writer-typed default cannot be substituted from the DEFAULT node, because the rewriter keeps their signatures and constant folding narrows the value back. The read and writer types of one field only differ while Doris's cached read schema predates a type change committed outside Doris (reads go through the table cache, the writer schema is loaded for the statement), so the bound rewrite now fails such a DEFAULT and asks to refresh the table instead of retyping its consumers. When the two types agree, which is every case without an outside type change, nothing changes. DEFAULT in VALUES, in INSERT ... SELECT projections and in UPDATE SET is resolved against the writer schema before binding, so its consumers already bind against the writer type. RewriteDefaultExpressionTest covers a direct DEFAULT and one under an IF bound to INT; both are rejected.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17605	5165	5047	5047
q2	2155	309	243	243
q3	10236	1388	818	818
q4	4680	460	336	336
q5	7428	790	540	540
q6	178	185	147	147
q7	748	786	591	591
q8	9254	1340	1060	1060
q9	5631	4200	4188	4188
q10	6831	1292	1041	1041
q11	423	243	235	235
q12	654	401	286	286
q13	18092	4119	3029	3029
q14	264	276	246	246
q15	q16	728	723	647	647
q17	1200	744	567	567
q18	6940	6198	6111	6111
q19	1245	973	596	596
q20	379	338	233	233
q21	5155	2123	2298	2123
q22	349	305	247	247
Total cold run time: 100175 ms
Total hot run time: 28331 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5615	5608	5583	5583
q2	698	556	540	540
q3	5232	5362	4942	4942
q4	1982	2149	1547	1547
q5	5244	5195	5679	5195
q6	264	201	155	155
q7	2338	2123	1869	1869
q8	3063	2814	2671	2671
q9	7994	8083	8074	8074
q10	3744	3675	3269	3269
q11	621	430	415	415
q12	681	710	529	529
q13	3526	4014	3180	3180
q14	302	298	283	283
q15	q16	709	703	627	627
q17	8270	7515	7426	7426
q18	12988	12184	12919	12184
q19	909	768	772	768
q20	2128	2142	1948	1948
q21	5546	4581	4641	4581
q22	514	463	397	397
Total cold run time: 72368 ms
Total hot run time: 66183 ms

@hello-stephen

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

query5	4312	576	441	441
query6	422	214	191	191
query7	4809	451	227	227
query8	318	159	160	159
query9	8790	3097	3114	3097
query10	478	308	260	260
query11	5976	3867	3562	3562
query12	147	89	86	86
query13	1247	442	329	329
query14	6088	3901	3632	3632
query14_1	3448	3400	3414	3400
query15	205	199	178	178
query16	939	418	412	412
query17	887	666	545	545
query18	2425	443	313	313
query19	205	167	140	140
query20	84	84	85	84
query21	212	128	107	107
query22	12898	13161	12788	12788
query23	13447	12913	12399	12399
query23_1	12660	12672	12634	12634
query24	7394	827	460	460
query24_1	483	479	483	479
query25	540	424	372	372
query26	1268	266	144	144
query27	2764	444	272	272
query28	4621	1959	1941	1941
query29	1560	615	460	460
query30	303	226	183	183
query31	1022	918	822	822
query32	141	94	97	94
query33	537	332	226	226
query34	889	864	488	488
query35	723	749	635	635
query36	818	857	708	708
query37	127	95	84	84
query38	1760	1718	1645	1645
query39	783	788	744	744
query39_1	725	722	747	722
query40	224	112	97	97
query41	67	64	62	62
query42	85	85	84	84
query43	343	355	302	302
query44	1301	681	688	681
query45	184	171	167	167
query46	822	979	561	561
query47	2872	2955	2663	2663
query48	291	300	220	220
query49	581	404	294	294
query50	668	270	206	206
query51	10383	10363	10404	10363
query52	76	79	67	67
query53	192	209	156	156
query54	243	202	198	198
query55	71	64	63	63
query56	233	208	216	208
query57	1674	1595	1588	1588
query58	289	260	274	260
query59	2086	2123	1947	1947
query60	270	240	228	228
query61	150	139	143	139
query62	386	337	281	281
query63	189	157	157	157
query64	2744	926	778	778
query65	3316	3259	3273	3259
query66	1765	428	302	302
query67	20048	20244	20072	20072
query68	3114	991	609	609
query69	399	282	249	249
query70	824	747	763	747
query71	300	236	207	207
query72	2727	2565	2272	2272
query73	502	524	294	294
query74	4583	4490	4320	4320
query75	2326	2311	1898	1898
query76	2315	1013	598	598
query77	347	373	266	266
query78	9774	9633	9104	9104
query79	1041	832	514	514
query80	525	434	398	398
query81	519	319	274	274
query82	571	130	104	104
query83	300	209	180	180
query84	316	121	103	103
query85	819	429	366	366
query86	321	246	232	232
query87	2017	1936	1792	1792
query88	3619	2673	2683	2673
query89	346	283	245	245
query90	1893	169	164	164
query91	163	159	131	131
query92	97	85	89	85
query93	1002	998	589	589
query94	434	319	284	284
query95	566	395	292	292
query96	653	489	233	233
query97	2495	2440	2286	2286
query98	160	153	148	148
query99	752	769	647	647
Total cold run time: 233786 ms
Total hot run time: 150589 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.10	0.05	0.05
query3	0.27	0.09	0.09
query4	1.61	0.08	0.10
query5	0.22	0.24	0.24
query6	1.17	0.68	0.67
query7	0.04	0.01	0.01
query8	0.05	0.04	0.04
query9	0.39	0.34	0.35
query10	0.57	0.59	0.57
query11	0.20	0.14	0.14
query12	0.19	0.15	0.15
query13	0.52	0.50	0.49
query14	1.03	1.02	1.03
query15	0.64	0.59	0.60
query16	0.34	0.34	0.34
query17	1.15	1.10	1.12
query18	0.20	0.18	0.19
query19	2.10	1.97	1.99
query20	0.02	0.01	0.01
query21	15.51	0.19	0.14
query22	4.96	0.05	0.05
query23	16.24	0.32	0.13
query24	3.10	0.58	0.69
query25	0.12	0.07	0.04
query26	1.64	0.21	0.16
query27	0.05	0.05	0.03
query28	3.39	0.71	0.35
query29	12.48	4.10	3.27
query30	0.29	0.12	0.10
query31	2.79	0.55	0.33
query32	3.22	0.59	0.52
query33	3.24	3.17	3.28
query34	15.42	3.96	3.36
query35	3.36	3.36	3.34
query36	0.54	0.45	0.42
query37	0.09	0.06	0.06
query38	0.06	0.04	0.03
query39	0.04	0.03	0.03
query40	0.16	0.15	0.15
query41	0.09	0.03	0.02
query42	0.03	0.04	0.04
query43	0.05	0.04	0.03
Total cold run time: 97.69 s
Total hot run time: 24.43 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 95.74% (45/47) 🎉
Increment coverage report
Complete coverage report

@morningman
morningman force-pushed the b2m-t35j-iceberg-default-col-write-target branch from cf75568 to f1287fd Compare October 23, 2026 14:26
@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17603	5135	5068	5068
q2	2151	302	263	263
q3	10261	1362	849	849
q4	4677	461	331	331
q5	7445	798	540	540
q6	181	184	161	161
q7	756	798	603	603
q8	9259	1317	1101	1101
q9	5410	4150	4164	4150
q10	6816	1285	1027	1027
q11	429	251	231	231
q12	634	410	303	303
q13	18075	3506	3029	3029
q14	271	278	251	251
q15	q16	717	718	662	662
q17	1179	744	586	586
q18	6928	6135	6119	6119
q19	1104	945	599	599
q20	377	344	225	225
q21	4869	2197	2335	2197
q22	352	306	264	264
Total cold run time: 99494 ms
Total hot run time: 28559 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5616	5619	5632	5619
q2	671	570	545	545
q3	5174	5391	4906	4906
q4	2016	2170	1610	1610
q5	5254	5160	5440	5160
q6	251	201	162	162
q7	2338	1991	1888	1888
q8	3067	2661	2754	2661
q9	8004	8011	7865	7865
q10	3711	3689	3264	3264
q11	624	466	428	428
q12	663	715	649	649
q13	3567	3936	3295	3295
q14	299	309	289	289
q15	q16	710	722	693	693
q17	8288	7724	7015	7015
q18	13013	12157	12869	12157
q19	907	834	829	829
q20	2150	2155	1909	1909
q21	5902	4906	5091	4906
q22	483	493	415	415
Total cold run time: 72708 ms
Total hot run time: 66265 ms

@hello-stephen

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

query5	4330	590	446	446
query6	425	227	191	191
query7	4793	438	240	240
query8	330	171	157	157
query9	8782	3142	3114	3114
query10	492	310	262	262
query11	6056	3833	3546	3546
query12	149	88	86	86
query13	1246	463	321	321
query14	6159	3992	3685	3685
query14_1	3407	3446	3412	3412
query15	206	197	182	182
query16	947	438	407	407
query17	850	669	549	549
query18	2418	431	322	322
query19	194	164	143	143
query20	86	88	84	84
query21	224	151	110	110
query22	13003	13100	12818	12818
query23	13428	12817	12513	12513
query23_1	12506	12567	12562	12562
query24	7383	848	459	459
query24_1	481	471	479	471
query25	572	416	352	352
query26	1270	257	146	146
query27	2756	440	273	273
query28	4658	1951	1959	1951
query29	1584	609	475	475
query30	309	213	187	187
query31	1018	923	831	831
query32	147	94	97	94
query33	538	303	243	243
query34	951	848	510	510
query35	756	763	653	653
query36	853	814	725	725
query37	129	113	80	80
query38	1794	1724	1685	1685
query39	808	817	792	792
query39_1	739	736	742	736
query40	221	127	100	100
query41	78	69	74	69
query42	92	88	86	86
query43	360	376	324	324
query44	1407	695	734	695
query45	184	187	170	170
query46	868	1023	597	597
query47	2761	3068	2909	2909
query48	304	318	225	225
query49	573	432	290	290
query50	714	290	217	217
query51	10262	10393	10477	10393
query52	81	84	72	72
query53	195	210	159	159
query54	237	209	183	183
query55	70	69	69	69
query56	265	213	205	205
query57	1655	1618	1571	1571
query58	291	263	275	263
query59	2084	2135	1941	1941
query60	299	252	230	230
query61	147	170	152	152
query62	390	335	275	275
query63	201	160	167	160
query64	2777	971	794	794
query65	3352	3296	3280	3280
query66	1784	412	296	296
query67	20051	20043	20079	20043
query68	2997	1025	613	613
query69	376	286	254	254
query70	867	792	794	792
query71	307	240	204	204
query72	2666	2532	2278	2278
query73	523	545	301	301
query74	4647	4486	4378	4378
query75	2340	2295	1916	1916
query76	2311	1022	617	617
query77	371	389	273	273
query78	9758	9657	9171	9171
query79	1356	867	520	520
query80	1236	427	360	360
query81	593	323	270	270
query82	572	135	106	106
query83	312	206	190	190
query84	314	132	107	107
query85	854	430	372	372
query86	398	239	229	229
query87	2001	1945	1817	1817
query88	3765	2690	2669	2669
query89	332	278	247	247
query90	1916	176	176	176
query91	168	154	129	129
query92	102	90	90	90
query93	1148	1050	577	577
query94	630	310	278	278
query95	585	345	377	345
query96	672	507	234	234
query97	2430	2429	2323	2323
query98	167	155	149	149
query99	771	753	625	625
Total cold run time: 235829 ms
Total hot run time: 151566 ms

@hello-stephen

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

query1	0.01	0.00	0.00
query2	0.09	0.05	0.04
query3	0.25	0.09	0.09
query4	1.61	0.12	0.11
query5	0.25	0.24	0.25
query6	1.17	0.68	0.68
query7	0.04	0.01	0.00
query8	0.06	0.04	0.04
query9	0.41	0.36	0.34
query10	0.58	0.62	0.59
query11	0.19	0.14	0.14
query12	0.18	0.15	0.15
query13	0.49	0.49	0.50
query14	1.03	1.01	1.01
query15	0.64	0.61	0.61
query16	0.36	0.37	0.36
query17	1.14	1.13	1.15
query18	0.21	0.21	0.21
query19	2.09	1.98	1.97
query20	0.02	0.01	0.01
query21	15.42	0.19	0.13
query22	5.03	0.05	0.05
query23	16.27	0.34	0.12
query24	2.67	0.47	0.50
query25	0.11	0.08	0.06
query26	1.59	0.22	0.15
query27	0.05	0.05	0.05
query28	3.80	0.82	0.37
query29	12.51	4.20	3.37
query30	0.28	0.13	0.09
query31	2.77	0.56	0.32
query32	3.22	0.59	0.50
query33	3.26	3.17	3.24
query34	15.30	4.01	3.41
query35	3.34	3.33	3.33
query36	0.54	0.44	0.43
query37	0.11	0.06	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.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 97.51 s
Total hot run time: 24.6 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 94.00% (47/50) 🎉
Increment coverage report
Complete coverage report

@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 exact head 688e8d59412ab4269516ff421a22015d5df4e281 across all 17 changed files and related planning/write paths. No new inline findings. Existing P1 #4355508072 is fixed: each substituted default is typed as its referenced writer column before consumer binding. Existing type-drift findings #4355508096 and #4358048801 are fixed by rejecting mismatched read/writer types with a refresh hint.

Known P2 limits remain covered by existing threads, without duplicate comments: #4355508080 (Aggregate/Repeat outputs and separately owned CTE/scalar-subquery plans), #4355508088 (four-part target qualification), and #4355508100 (historical Iceberg-on-HMS capability reflection). These are verified dispositions, with no unresolved new candidate. No existing P0/P1 finding still applies. No additional user focus was supplied.

Critical checkpoint conclusions:

  1. Purpose and proof: ordinary VALUES/SELECT projections and bound-target UPDATE WHERE/MERGE ON now use the pinned writer default. Inspected unit/regression additions cover these paths; the known scope limits above remain.
  2. Focus and reuse: the change is localized and reuses the capability union, pinned writer schema, shared DEFAULT resolver and coercion helpers; generic planning stays connector-agnostic.
  3. Concurrency and locks: no new threads, lock acquisitions or shared mutable state; rewriting consumes the existing statement pin in the established planning lock scope. No new lock-order or concurrency defect found.
  4. Lifecycle: INSERT, overwrite and row-level writes pin before normalization; branch selection survives overwrite's inner INSERT. Prepared execution/retry reset clears writer schemas and identities. No new static-initialization or resource lifecycle.
  5. Configuration: no configuration change.
  6. Compatibility: the added capability is recorded in the plugin surface and the API major is consistently 21.0. ConnectorMetadata methods and storage/wire formats are unchanged.
  7. Thrift/protobuf: no definitions changed; cross-branch field-ID comparison is inapplicable.
  8. Parallel paths: explicit/implicit VALUES columns, FROM/no-FROM projections, overwrite/EXPLAIN, UPDATE, MERGE and shared changelog callers were traced. Plain Hive/Paimon/internal paths retain their existing non-opt-in behavior.
  9. Conditions and failures: missing target fields and mismatched writer types fail with context. Stable field-ID lookup avoids selecting a replacement field solely by reused name; target-name SELECT semantics are explicit and tested.
  10. Test coverage: inspected positive/negative tests cover capability declaration/reflection, writer-versus-read defaults, unknown columns, non-target rejection, non-opt-in behavior, width-sensitive numeric typing, and direct/enclosing type drift. End-to-end additions cover Parquet and ORC.
  11. Expected results: the new ordered rows 20/21/22 with default_int=35 and UPDATE/MERGE strings agree with the fixture and statements by inspection. Output-generation provenance and runtime passing status were not independently verified.
  12. Observability: analysis errors identify the DEFAULT reference or conflicting types and refresh action. No new distributed operation needs additional metrics/logging.
  13. Persistence and failover: no EditLog, replay or transaction-state change; existing writer metadata-identity/schema fences and commit paths remain in use.
  14. Writes and atomicity: changed expressions flow into existing analyzed connector sinks; no new commit, crash cleanup or partial-write mechanism. No new atomicity issue found.
  15. FE/BE propagation: no new transmitted variable or operator.
  16. Performance: one plan traversal and bounded linear column lookup per DEFAULT; no new per-row evaluation or concrete significant regression found.
  17. Core invariants and remaining issues: no new visibility-version, delete-bitmap or native memory-ownership path; errors fail analysis before execution. Final recheck found no additional substantiated issue beyond the existing threads.

Validation was static-only: no builds, tests, cluster execution or source modifications. Runtime behavior, full plugin packaging and test-generation provenance remain outside this invocation's validation.

Review converged after 2 round(s).

@hello-stephen

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

query1	0.01	0.01	0.01
query2	0.09	0.06	0.05
query3	0.27	0.09	0.08
query4	1.61	0.11	0.10
query5	0.23	0.24	0.24
query6	1.16	0.67	0.68
query7	0.04	0.01	0.01
query8	0.05	0.03	0.04
query9	0.40	0.35	0.34
query10	0.57	0.57	0.61
query11	0.20	0.14	0.16
query12	0.18	0.16	0.15
query13	0.49	0.49	0.48
query14	1.03	1.02	1.01
query15	0.62	0.60	0.61
query16	0.31	0.35	0.35
query17	1.15	1.13	1.14
query18	0.21	0.19	0.19
query19	2.10	1.96	1.97
query20	0.02	0.02	0.01
query21	15.51	0.19	0.13
query22	4.88	0.05	0.04
query23	16.34	0.31	0.13
query24	2.81	0.93	0.73
query25	0.12	0.07	0.04
query26	1.61	0.21	0.15
query27	0.05	0.03	0.04
query28	3.41	0.73	0.35
query29	12.47	4.26	3.32
query30	0.29	0.13	0.10
query31	2.78	0.56	0.32
query32	3.23	0.60	0.50
query33	3.11	3.16	3.17
query34	15.32	3.98	3.38
query35	3.31	3.32	3.36
query36	0.55	0.47	0.41
query37	0.09	0.06	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.16	0.14
query41	0.09	0.04	0.03
query42	0.04	0.02	0.03
query43	0.05	0.03	0.03
Total cold run time: 97.06 s
Total hot run time: 24.58 s

morningman and others added 7 commits October 31, 2026 11:17
…hema

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66538, apache#57531, apache#65851

Problem Summary: An Iceberg write-default exists only in the writer schema
that a write statement pins (IcebergWriteSchemaContext); the columns of the
read schema carry no default. Master resolves DEFAULT(column) against that
writer schema only in UPDATE / MERGE SET and in MERGE INSERT VALUES, and
everywhere else falls back to the read column. On a v3 table whose column d
has write-default 42, INSERT ... VALUES (DEFAULT(d)) and INSERT ... SELECT
DEFAULT(d) without FROM fail with "Unknown column", INSERT ... SELECT
DEFAULT(d) FROM t writes NULL, UPDATE ... WHERE d = DEFAULT(d) and MERGE
... ON ... d = DEFAULT(d) compare d with NULL and silently match no row, and
SELECT DEFAULT(d) FROM t returns NULL. branch-4.1 and branch-4.2 (apache#66538)
resolve all of these against the pinned write target, by name against the
target in INSERT ... SELECT, and reject DEFAULT(column) on an Iceberg column
in a statement that does not write the table.

This change adds the connector capability SUPPORTS_WRITE_ONLY_DEFAULTS,
declared by the Iceberg connector and inherited by iceberg-on-HMS tables.
For a table that declares it, InsertUtils resolves DEFAULT(column) in INSERT
VALUES and in the select lists of INSERT ... SELECT by name against the
pinned writer schema, and RewriteDefaultExpression takes the default of a
DEFAULT(column) bound to the table from the pinned writer column with the
same field id, which covers the UPDATE and MERGE conditions, and rejects it
when the statement pinned no writer schema for the table. Hive, Paimon and
OLAP tables keep the current behavior. The new enum constant changes the
connector plugin API surface, so the plugin API version goes from 26.0 to
27.0.

### Release note

Fix: On Iceberg tables, DEFAULT(column) in INSERT VALUES, INSERT ... SELECT
and the WHERE / ON conditions of UPDATE and MERGE now resolves to the
column's write-default. Before, it failed, wrote NULL, or matched no row.
SELECT DEFAULT(column) on an Iceberg table now fails instead of returning
NULL.

### Check List (For Author)

- Test: Regression test / Unit Test
    - test_iceberg_initial_defaults: writes rows 20-22 with
      DEFAULT(default_int) in VALUES and in INSERT ... SELECT with and
      without FROM, then updates them with UPDATE ... WHERE default_int =
      DEFAULT(default_int) and MERGE ... ON ... = DEFAULT(...), for Parquet
      and ORC; SELECT DEFAULT(default_int) must fail. Passes locally; with
      master's FE it fails at the first INSERT with "Unknown column
      'default_int'"
    - InsertUtilsTest: VALUES and INSERT ... SELECT (from a staging relation
      and without FROM) resolve against the pinned writer schema; unknown
      column fails; a table without the capability is unchanged
    - RewriteDefaultExpressionTest: pinned writer column wins, no pinned
      schema fails, a table without the capability keeps the read default
    - IcebergConnectorTest, HiveConnectorMetadataSiblingDelegationTest,
      ConnectorPluginSurfaceTest
- Behavior changed: Yes. See the release note.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
### What problem does this PR solve?

Issue Number: None

Related PR: apache#66538

Problem Summary: Two review findings on the previous commit.

ConnectorWriteSchemaUtils.resolveDefaultReferences parsed the default SQL
of a writer column and substituted it without a cast, so the default kept
the type the parser gives the literal: -1 is a TINYINT. For an INT column
with write-default -1, INSERT INTO t(c) VALUES (bit_count(DEFAULT(c))) and
INSERT INTO t(c) SELECT bit_count(DEFAULT(c)) evaluated bit_count on a
TINYINT and wrote 8 instead of 32; casting the finished value to INT cannot
restore that. The external UPDATE, MERGE and changelog paths use the same
helper and typed DEFAULT(column) the same way. The resolved default is now
cast to its column's type before the expression around it is bound, as
RewriteDefaultExpression does for a bound DEFAULT(column). branch-4.2
builds write defaults as literals of the column type and is not affected.

RewriteDefaultExpression took the result type from the read column before
substituting the pinned writer column. After a type promotion committed
outside Doris, the read schema can still hold an INT column while the
writer column is a BIGINT whose default an INT cannot hold, such as
4900000001, and the default was cast to INT. The type now comes from the
writer column the default is taken from.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - InsertUtilsTest#writeOnlyDefaultIsTypedAsItsColumnBeforeBinding:
      VALUES and SELECT pass DEFAULT(bits) to bit_count as an INT; with
      the previous commit it is a TINYINT
    - RewriteDefaultExpressionTest#writeOnlyDefaultTakesTheTypeOfThePinned
      WriterColumn: an INT read column with a BIGINT pinned writer column
      gives the BIGINT default; with the previous commit it is
      cast(4900000001 as INT)
    - InsertUtilsTest, RewriteDefaultExpressionTest,
      ConnectorWriteSchemaUtilsTest, ConnectorChangelogPlanBuilderTest,
      ChangelogRowLevelDmlTransformTest,
      ExternalRowLevelUpdatePlanBuilderTest,
      ExternalRowLevelMergePlanBuilderTest, PluginDrivenExternalTableTest:
      93 tests pass
- Behavior changed: No. DEFAULT(column) inside an expression of an
  external UPDATE / MERGE / changelog assignment now has the column's
  type, which is the type a bound DEFAULT(column) already had.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
### What problem does this PR solve?

Issue Number: None

Related PR: apache#66538

Problem Summary: apache#69394 added two ConnectorWriteSchemaUtilsTest cases that
expect resolveDefaultReferences to return the default with the type the
parser gives its literal (9 is a TINYINT). The previous commit casts the
resolved default to its writer column's type, so for the INT test column the
result is now an INT literal 9, and both cases failed in FE UT once this
branch met master. The two expectations now use the column's type.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ConnectorWriteSchemaUtilsTest, RewriteDefaultExpressionTest, InsertUtilsTest
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… read type

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66538

Problem Summary: Review follow-up. The previous commits typed the default
that replaces a bound DEFAULT(column) as its pinned writer column. That type
differs from the read column's only when the table changed outside Doris
after Doris cached its read schema, e.g. an INT column promoted to BIGINT
whose new write-default is 4900000001. The expressions around
DEFAULT(column) were already bound against the read type and the rewriter
keeps their signatures, so in

    UPDATE t SET ... WHERE IF(TRUE, DEFAULT(score), 0) = 4900000001

the IF keeps its INT result type and constant folding casts 4900000001 back
to INT: NULL in non-strict mode, so no row matches, and an overflow error in
strict mode. A substituted value cannot repair the types its consumers were
bound with, so the rewrite now fails such a DEFAULT and asks to refresh the
table. When the two types agree, which is every case without a type change
made outside Doris, nothing changes.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - RewriteDefaultExpressionTest: a DEFAULT whose writer type differs is rejected, directly and under an IF bound to the read type
    - ConnectorWriteSchemaUtilsTest, InsertUtilsTest, ExternalRowLevelUpdatePlanBuilderTest, ExternalRowLevelMergePlanBuilderTest, ChangelogRowLevelDmlTransformTest, PluginDrivenExternalTableTest
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…T on its own FROM clause

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69488

Problem Summary:

For a target with write-only defaults (Iceberg), `InsertUtils.normalizePlan`
resolves `DEFAULT(column)` in the select lists of an `INSERT ... SELECT` by
name against the target's pinned writer schema, before the source relations
are bound. It walked the whole query plan with `rewriteUp`, so it also
rewrote the projections of a derived table in `FROM`, which is a query of its
own. With a source `src(c INT DEFAULT 7)` and a target `t(c INT)` whose
write-default is 9,

    INSERT INTO t(c) SELECT v FROM (SELECT DEFAULT(c) AS v FROM src) q

wrote 9 instead of 7, and `DEFAULT(src.c)` inside the derived table failed
with "column in assignment list is invalid", because its qualifier was
checked against the target. branch-4.2's
`resolveIcebergSelectDefaultReferences` walks the plan the same way.

The walk now stops at `LogicalSubQueryAlias`. `DEFAULT(column)` inside a
derived table is left to binding and names a column of that table's own
`FROM` clause, as it does on master. The select lists of the INSERT's own
query, set operation branches included, are resolved as before, still
bottom-up.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New `InsertUtilsTest#writeOnlyDefaultInDerivedTableNamesItsOwnSource`
      covers an unqualified and a qualified `DEFAULT(score)` inside a derived
      table (left to binding while the outer `DEFAULT(name)` resolves to the
      pinned default), and a UNION whose first branch resolves and whose
      second branch reads a derived table. It fails with the previous
      `rewriteUp` walk.
    - `InsertUtilsTest`, `ConnectorWriteSchemaUtilsTest`,
      `RewriteDefaultExpressionTest`, `IvmDeltaRewriterTest` and
      `IvmLinearDeltaHandlerTest` pass, as does the fe-core checkstyle.
- Behavior changed: Yes. Within this PR, `DEFAULT(column)` in a derived
  table of an `INSERT ... SELECT` into an Iceberg table no longer resolves
  against the target; it keeps master's meaning.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ELECT against the target

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69488

Problem Summary:

For a target with write-only defaults (Iceberg), `InsertUtils.normalizePlan`
resolves `DEFAULT(column)` in the select lists of the INSERT's own query by
name against the target's pinned writer schema, before the source relations
are bound, by rewriting the expression tree of each projection. `UnboundStar`
keeps the REPLACE list of `SELECT * REPLACE(expr AS c)` in a field instead of
as expression children, so the rewrite never reached a `DEFAULT(c)` there and
binding later resolved it against the source column. With a source
`src(c INT DEFAULT 7)` and a target `t(c INT)` whose write-default is 9,

    INSERT INTO t(c) SELECT * REPLACE(DEFAULT(c) AS c) FROM src

wrote 7, while `INSERT INTO t(c) SELECT DEFAULT(c) FROM src` wrote 9.
branch-4.2's `resolveIcebergDefaultReferences` walks each projection the same
way and has the same gap.

`resolveDefaultReferences` now also resolves each replacement of a star and
rebuilds the star with the same qualifier, EXCEPT list and SQL position. A
replacement follows the rules of the rest of the select list, so a qualifier
other than the target's (`DEFAULT(src.c)`) fails there too. A star inside a
derived table is still left to binding, and a star without REPLACE keeps its
expression.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New `InsertUtilsTest#writeOnlyDefaultInStarReplacementResolvesByTargetColumnName`
      covers `src.* EXCEPT(score) REPLACE(DEFAULT(name) AS name)`: the
      replacement becomes the pinned default, and the qualifier and the
      EXCEPT list are kept. With the previous `InsertUtils` the replacement
      is still `DEFAULT(name)`.
- Behavior changed: Yes. Within this PR, `DEFAULT(column)` in a
  `SELECT * REPLACE` of an `INSERT ... SELECT` into an Iceberg table names
  the target column, as in the rest of the INSERT's select lists.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ly defaults

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69488

Problem Summary:

`RowLevelDmlCommand` pins the target's writer schema for every row-level
DML, a DELETE included: its delete sink takes the writer columns from it.
`RewriteDefaultExpression` takes the default of a `DEFAULT(column)` on a
table with write-only defaults (Iceberg) from that pinned schema, so on a
table whose `c` has write-default 35,

    DELETE FROM t WHERE c = DEFAULT(c)

deleted every row with `c = 35`. A DELETE writes no row values, so no write
default applies to it. branch-4.2 rejects `DEFAULT(column)` there, because
its DELETE installs no write schema context, and master without this PR
read the default from the read schema, which carries none, and deleted
nothing.

`RowLevelDmlCommand` now also marks the target of a DELETE in the
`StatementContext` when it pins the writer schema, on the run and the
EXPLAIN paths alike, and `RewriteDefaultExpression` rejects
`DEFAULT(column)` on a marked table that has write-only defaults. UPDATE and
MERGE are not marked and resolve as before. The mark is cleared with the
pinned schemas when a prepared statement starts its next execution.

### Release note

None

### Check List (For Author)

- Test: Unit Test / Regression test
    - New `RewriteDefaultExpressionTest#writeOnlyDefaultInADeleteIsRejected`
      and `RowLevelDmlCommandTest#onlyADeleteMarksItsTargetAsWritingNoRowValues`;
      9 classes, 85 cases pass locally (`RewriteDefaultExpressionTest`,
      `RowLevelDmlCommandTest`, `PositionDeleteRowLevelDmlTransformTest`,
      `ChangelogRowLevelDmlTransformTest`,
      `PhysicalPlanTranslatorIcebergRowLevelDmlTest`, `InsertUtilsTest`,
      `ConnectorWriteSchemaUtilsTest`, `DeleteFromCommandTest`,
      `UpdateCommandTest`)
    - `test_iceberg_initial_defaults` expects the error for a DELETE with
      `DEFAULT(default_int)` on rows that hold the write-default; not run
      locally (needs the Iceberg docker environment)
- Behavior changed: Yes. `DEFAULT(column)` in a DELETE on a table with
  write-only defaults is rejected (master evaluated it to NULL).
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@morningman
morningman force-pushed the b2m-t35j-iceberg-default-col-write-target branch from 7ddd7c9 to 985b588 Compare October 31, 2026 03:29
@morningman

Copy link
Copy Markdown
Contributor Author

Rebased onto master 339e087. The only conflict was in ConnectorPluginSurfaceTest: #69835 took connector plugin API 26.0, so this PR now moves connector.plugin.api.version from 26.0 to 27.0 (the test, the pom, the first commit's message and the description follow); the other six commits are unchanged.

Local, on the rebased head: InsertUtilsTest, RewriteDefaultExpressionTest, RowLevelDmlCommandTest, ConnectorWriteSchemaUtilsTest, ExternalRowLevelUpdatePlanBuilderTest, ExternalRowLevelMergePlanBuilderTest, ChangelogRowLevelDmlTransformTest, PluginDrivenExternalTableTest, PositionDeleteRowLevelDmlTransformTest, PhysicalPlanTranslatorIcebergRowLevelDmlTest, DeleteFromCommandTest, UpdateCommandTest, IcebergConnectorTest, HiveConnectorMetadataSiblingDelegationTest and ConnectorPluginSurfaceTest pass (180 tests), with checkstyle on fe-core and the spi, iceberg and hive connectors.

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

Static review of apache/doris PR #69488 at the contracted head 985b588 found one verified P1 issue. Delete-only MERGE statements are dispatched as RowLevelDmlOp.MERGE, so the new DELETE marker is absent even though the sink writes only position deletes; DEFAULT(t.c) in the MERGE ON predicate can therefore use the Iceberg write-default and delete matching rows. The capability declaration, API 27.0 surface baseline, writer-schema typing, INSERT plan traversal, statement reset lifecycle, and regression/unit-test structure were inspected and found consistent outside this issue. Existing P1 threads for plain DELETE, derived-query traversal, and default typing were independently revalidated as fixed on this head and are not duplicated. No additional user review focus was provided. No builds or tests were run because this invocation is static-only. The critical review checkpoints are covered: the task goal is incomplete only for delete-only MERGE semantics; no new configuration, Thrift/Protobuf, persistence, or FE-BE protocol changes were found; statement lifecycle/reset behavior and mixed MERGE branches were traced; and no separate concurrency, locking, observability, or performance defect was substantiated. Review converged after 2 bounded rounds.

Review converged after 2 round(s).

@@ -154,7 +154,7 @@ public void run(ConnectContext ctx, StmtExecutor stmtExecutor) throws Exception
public Plan getExplainPlan(ConnectContext ctx) {

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] Guard write-only defaults in delete-only MERGE

pinWriteSchema marks connectorDeleteTargets only for RowLevelDmlOp.DELETE. A valid MERGE with no insert/update branch (for example, WHEN MATCHED THEN DELETE) is dispatched as RowLevelDmlOp.MERGE, even though its sink writes no row values. Its ON predicate therefore lets RewriteDefaultExpression replace DEFAULT(t.c) with the pinned Iceberg write-default; with c defaulting to 35, ... ON t.id = s.id AND t.c = DEFAULT(t.c) WHEN MATCHED THEN DELETE deletes rows whose c is 35. Treat an all-delete/no-insert MERGE as a delete target (including EXPLAIN), or reject this DEFAULT use, and add coverage.

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.

Keeping this as is. A MERGE resolves DEFAULT(column) in its ON condition against the writer schema it pins, whatever its WHEN branches, and so does branch-4.2, which this PR ports. There, IcebergMergeCommand installs the write schema context for every MERGE, delete-only ones included, both when it runs and for EXPLAIN (ExplainCommand installs it again from the merge sink), and RewriteDefaultExpression resolves a target column from it. So on branch-4.2 too, MERGE ... ON t.id = s.id AND t.c = DEFAULT(t.c) WHEN MATCHED THEN DELETE deletes the matched rows whose c is 35. Master evaluates DEFAULT(t.c) to NULL there and deletes nothing.

To be precise about the DELETE thread (#69488 (comment)): the line follows branch-4.2 and depends on the kind of statement. branch-4.2 gives a DELETE no write schema context, so DEFAULT(c) fails there, and gives every MERGE one. "Writes no row values" explains why the DELETE has none. It is not a test applied to each MERGE. Rejecting a delete-only MERGE would break, after an upgrade, a statement that runs on branch-4.1 and branch-4.2. It would also make the same ON condition fail or resolve depending on whether the MERGE has an UPDATE or INSERT branch, although the condition selects the same rows to delete either way. The description now states that a MERGE resolves it whatever its WHEN branches, also when they all delete.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17585	5234	5077	5077
q2	2159	301	253	253
q3	10249	1369	828	828
q4	4685	458	337	337
q5	7429	801	542	542
q6	184	190	153	153
q7	778	806	597	597
q8	9241	1331	1020	1020
q9	5414	4199	4194	4194
q10	6827	1300	1023	1023
q11	429	250	228	228
q12	641	400	289	289
q13	18098	3690	2993	2993
q14	268	275	251	251
q15	q16	720	726	672	672
q17	1202	765	575	575
q18	7028	6203	6108	6108
q19	1107	976	615	615
q20	393	336	223	223
q21	5116	2137	2277	2137
q22	349	295	256	256
Total cold run time: 99902 ms
Total hot run time: 28371 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5588	5623	5577	5577
q2	686	550	527	527
q3	5181	5336	4902	4902
q4	1977	2125	1570	1570
q5	5230	5160	5471	5160
q6	267	217	153	153
q7	2342	2043	1882	1882
q8	3066	2742	2836	2742
q9	8067	8071	8165	8071
q10	3713	3651	3308	3308
q11	613	426	397	397
q12	696	721	546	546
q13	3518	3999	3292	3292
q14	325	312	297	297
q15	q16	684	723	673	673
q17	8320	7565	6966	6966
q18	12992	12372	13105	12372
q19	931	808	810	808
q20	2189	2161	1946	1946
q21	5902	4828	4878	4828
q22	510	431	387	387
Total cold run time: 72797 ms
Total hot run time: 66404 ms

@hello-stephen

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

query5	4303	578	439	439
query6	416	206	185	185
query7	4804	458	233	233
query8	320	170	152	152
query9	8805	3144	3171	3144
query10	482	305	252	252
query11	6015	3813	3531	3531
query12	147	93	91	91
query13	1272	445	334	334
query14	6100	3919	3629	3629
query14_1	3465	3415	3436	3415
query15	213	200	184	184
query16	953	421	441	421
query17	861	652	540	540
query18	2419	428	309	309
query19	198	167	140	140
query20	86	94	85	85
query21	208	135	106	106
query22	12978	12905	12704	12704
query23	13531	12962	12495	12495
query23_1	12650	12597	12519	12519
query24	7365	825	498	498
query24_1	499	482	501	482
query25	539	432	386	386
query26	1266	258	137	137
query27	2786	437	271	271
query28	4617	2022	2020	2020
query29	1546	614	466	466
query30	298	222	188	188
query31	1015	927	806	806
query32	140	97	91	91
query33	533	307	245	245
query34	901	842	500	500
query35	731	750	644	644
query36	833	814	738	738
query37	126	101	86	86
query38	1795	1717	1687	1687
query39	803	796	744	744
query39_1	733	740	730	730
query40	219	126	99	99
query41	73	70	68	68
query42	95	87	82	82
query43	339	361	310	310
query44	1313	713	708	708
query45	178	182	168	168
query46	876	945	561	561
query47	2922	3041	2696	2696
query48	296	302	217	217
query49	573	389	292	292
query50	665	273	207	207
query51	10328	10554	10455	10455
query52	81	76	70	70
query53	182	206	146	146
query54	220	199	178	178
query55	71	67	62	62
query56	232	207	220	207
query57	1642	1596	1567	1567
query58	274	272	264	264
query59	2117	2288	2026	2026
query60	283	242	223	223
query61	148	144	143	143
query62	375	338	282	282
query63	184	156	158	156
query64	2714	961	762	762
query65	3315	3264	3251	3251
query66	1778	415	313	313
query67	20013	20273	19917	19917
query68	3021	997	622	622
query69	377	293	258	258
query70	836	773	822	773
query71	280	243	213	213
query72	2748	2643	2324	2324
query73	518	537	292	292
query74	4598	4511	4357	4357
query75	2315	2262	1885	1885
query76	2312	1013	565	565
query77	373	376	263	263
query78	9834	9777	9172	9172
query79	988	902	520	520
query80	671	420	353	353
query81	553	311	268	268
query82	577	130	106	106
query83	269	193	179	179
query84	314	125	94	94
query85	835	421	363	363
query86	332	238	230	230
query87	1997	1944	1801	1801
query88	3781	2768	2739	2739
query89	329	287	238	238
query90	1865	160	160	160
query91	166	153	128	128
query92	99	87	91	87
query93	1010	1014	565	565
query94	448	296	270	270
query95	561	384	303	303
query96	690	483	237	237
query97	2449	2428	2351	2351
query98	163	152	147	147
query99	778	760	659	659
Total cold run time: 234127 ms
Total hot run time: 150931 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.09	0.05	0.05
query3	0.26	0.09	0.08
query4	1.60	0.10	0.10
query5	0.23	0.25	0.24
query6	1.17	0.68	0.67
query7	0.05	0.01	0.00
query8	0.06	0.04	0.04
query9	0.39	0.35	0.35
query10	0.58	0.60	0.58
query11	0.20	0.14	0.15
query12	0.18	0.14	0.14
query13	0.49	0.48	0.49
query14	1.03	1.00	1.01
query15	0.64	0.60	0.61
query16	0.36	0.35	0.34
query17	1.11	1.14	1.14
query18	0.20	0.18	0.18
query19	2.09	2.01	1.97
query20	0.02	0.01	0.01
query21	15.50	0.18	0.14
query22	4.96	0.05	0.05
query23	16.27	0.32	0.13
query24	2.95	0.50	0.36
query25	0.11	0.07	0.03
query26	1.19	0.22	0.16
query27	0.05	0.03	0.05
query28	3.73	0.76	0.36
query29	12.48	4.17	3.33
query30	0.28	0.13	0.10
query31	2.77	0.56	0.33
query32	3.23	0.59	0.50
query33	3.14	3.22	3.28
query34	15.46	3.99	3.38
query35	3.34	3.29	3.34
query36	0.56	0.46	0.43
query37	0.09	0.06	0.06
query38	0.06	0.04	0.04
query39	0.04	0.03	0.03
query40	0.16	0.16	0.14
query41	0.10	0.03	0.03
query42	0.04	0.03	0.03
query43	0.05	0.04	0.04
Total cold run time: 97.31 s
Total hot run time: 24.32 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 94.29% (66/70) 🎉
Increment coverage report
Complete coverage report

@morningman

Copy link
Copy Markdown
Contributor Author

run external

@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 write defaults belong to the statement-pinned writer schema. This change resolves INSERT defaults before consumer binding and bound UPDATE/MERGE references by table and field identity, preserves column types, and rejects unsupported SELECT/DELETE uses.

Static review of all 20 changed files and relevant FE call chains at 985b588 found no blocking P0/P1/P2 issue. Previously reported typing, derived-query scope, star-replacement and DELETE issues are fixed. The delete-only MERGE comment was rechecked against current code and local branch-4.2 code: resolving its ON condition from the pinned writer schema is intentional regardless of WHEN branches. Remaining documented traversal, qualification and HMS snapshot limitations do not establish a new regression requiring this PR to expand scope.

APPROVE for the reviewed FE scope. No builds or tests were run. Supplied current-head status reports P0, FE UT and external regression success, but FE coverage failure; required-check completeness and merge eligibility are separate from this code review.

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

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

LGTM

@morningman
morningman merged commit 7e6f6cb into apache:master Nov 2, 2026
67 of 69 checks passed
@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

morningman added a commit that referenced this pull request Nov 3, 2026
…t a snapshot (#69999)

### What problem does this PR solve?

Issue Number: None

Related PR: #69299, #69488

Problem Summary:

#### In short

An Iceberg table stored in HMS and read through an HMS catalog
(`'type'='hms'`) loses every capability it inherits from the embedded
Iceberg connector in any statement that pins the table at a snapshot.
Since #69299 that is every statement that reads it: it gets no Top-N
lazy materialization and no nested column pruning, and `DEFAULT(column)`
in the conditions of `UPDATE` and `MERGE` silently matches nothing. This
PR makes the snapshot overload of `HiveConnectorMetadata#getTableSchema`
pass the inherited capabilities on, as the latest-schema overload
already does. It is a master-only fix (branch-4.x has no connector SPI),
left over from #69488 (review comment 4355508100).

#### Background

fe-core resolves a table-scoped capability
(`PluginDrivenExternalTable.hasCapability`) from the catalog's connector
or from the per-table set in the table's schema. It never asks the
embedded connector that built the schema of an iceberg-on-HMS table. So
`HiveConnectorMetadata` copies the capabilities that fe-core resolves
per table (`SIBLING_INHERITABLE_CAPABILITIES`: auto and sampled analyze,
Top-N lazy materialization, nested column pruning, field-id access
paths, storage predicate pruning, nested column DDL and write-only
defaults) from the owning connector into the per-table set of each
schema it delegates. Iceberg declares seven of the eight; it does not
declare sampled analyze.

It did this only in `getTableSchema(session, handle)`.
`getTableSchema(session, handle, snapshot)` returned the sibling's
schema unchanged. fe-core uses that overload for explicit time travel.
Since #69299 it also uses it for the latest-snapshot pin of every
statement that reads an Iceberg table: the pin retains its schema
(`retainSchema(true)`), `PluginDrivenMvccExternalTable` builds the
schema at that pin, and `getSchemaCacheValue()`, where `hasCapability`
reads the per-table set, returns it for the rest of the statement.

#### The problem, and what it cost

Reproduced on a local cluster with a standalone Hive 3.1.2 metastore.
Two identical format-version 3 tables `(id INT, c INT, s STRUCT<a:INT,
b:STRING>)` get two rows, then `ADD COLUMN d INT DEFAULT 42`, `RENAME
COLUMN s.a TO a2` and a third row with `d = 7`. The same statements then
run against one table through the Iceberg catalog and against the other
through the HMS catalog.

| Statement | Iceberg catalog | HMS catalog, master | HMS catalog, this
PR |
|---|---|---|---|
| `EXPLAIN SELECT * FROM t ORDER BY id LIMIT 1`, also with `FOR VERSION
AS OF` | `VMaterializeNode` | no lazy materialization |
`VMaterializeNode` |
| `EXPLAIN SELECT struct_element(s, 'a2') FROM t` | `all access paths:
[s.a2]` | no pruning | `all access paths: [s.a2]` |
| `UPDATE t SET c = 100 WHERE d = DEFAULT(d)` | updates rows 0 and 1 |
updates nothing | updates rows 0 and 1 |
| `MERGE INTO t USING (SELECT 0 AS id) s ON t.id = s.id AND t.d =
DEFAULT(t.d) WHEN MATCHED THEN UPDATE SET c = 300` | updates row 0 |
updates nothing | updates row 0 |
| `DELETE FROM t WHERE d = DEFAULT(d)` | fails: `cannot be used in a
DELETE` | deletes nothing | fails the same way |
| `SELECT DEFAULT(d) FROM t` | fails: `must reference a column of the
table that this statement writes` | returns NULL | fails the same way |

The `UPDATE`, `MERGE` and `DELETE` rows fail silently: the statement
succeeds and changes nothing. Reads of the renamed field return the
right values in all three columns: on master the HMS catalog does not
prune the struct at all, and with this PR it prunes it with field-id
access paths, as the Iceberg catalog does. `INSERT ... VALUES
(DEFAULT(d))` and `INSERT ... SELECT DEFAULT(d)` already wrote 42
through both catalogs.

#### How this PR fixes it

Both overloads now go through one private helper,
`delegatedTableSchema`. It resolves the owning sibling once, asks it for
the schema (at the pin, for the snapshot overload), and passes the
result through the existing `reflectSiblingCapabilities`. The snapshot
reaches the sibling unchanged. A hudi-on-HMS table is unaffected: the
Hudi connector declares no capabilities, so its schema comes back as it
was. A plain Hive table never reaches this branch.

#### Results

Through the HMS catalog, every statement in the table above now plans
and answers as it does through the Iceberg catalog.
hubgeter added a commit to hubgeter/doris that referenced this pull request Nov 4, 2026
…load

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#65851, apache#69488, apache#69499

Problem Summary: Required and nested ADD COLUMN defaults must retain their SQL values when converted to Iceberg metadata. Normalize decimal scale, valid Boolean whitespace, compact UUIDs and escaped strings; preserve nanosecond gap semantics and temporal precision while keeping unqualified zoned values compatible with existing connector inputs. Resolve Iceberg zoned defaults through the session timezone and reject non-finite floating defaults before metadata publication. Verify real Hadoop metadata commits/reloads, extend read/write regression coverage and update both existing v2 rejection assertions.

### Release note

Typed literal defaults for required and nested Iceberg column additions preserve their values through metadata serialization and reads/writes. Non-finite floating defaults fail before commit.

### Check List (For Author)

- Test: 89 focused FE tests passed; full build.sh --fe, Checkstyle and both connector architecture gates passed. The preset regression runner generated the new expected output and then successfully compared all four affected external suites against isolated REST/MinIO services. Local read/write validation used the branch-built FE and an ASAN BE cache; BE code is unchanged.
- Behavior changed: Yes; correct typed default conversion and prevent publication of unreadable floating defaults. Existing SHOW CREATE default presentation remains unchanged.
- Does this need documentation: Yes; the connector contract and fixture requirements are updated in this commit.
hubgeter added a commit to hubgeter/doris that referenced this pull request Nov 4, 2026
### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#65851, apache#69488

Problem Summary: Equivalent explicit and session-zone TIMESTAMPTZ defaults can differ by one hour when fractional rounding crosses a DST overlap. A valid civil timestamp can also resolve outside the UTC range supported by Doris and become an unreadable default after schema commit. Preserve civil precision until the existing Iceberg session boundary, round the resolved instant at the original declared precision, and reject unsupported final UTC years before staging. Share the conversion across single, batch and nested additions and retain unparameterized SPI epoch microseconds.

### Release note

Iceberg zoned column defaults consistently interpret explicit and implicit time zones across DST rounding and reject unsupported UTC values before committing schema changes.

### Check List (For Author)

- Test: 91 focused FE tests passed; full build.sh --fe, Checkstyle and both connector architecture gates passed. The preset regression runner generated expected results and passed the four affected external suites, including old/new files, explicit/session CET equality, UTC upper/lower rejection and batch atomicity. Local native validation uses an existing ASAN BE cache; C++ code is unchanged.
- Behavior changed: Yes; zoned ADD COLUMN defaults resolve time zones before target rounding and validate the final UTC range. Existing default presentation is preserved.
- Does this need documentation: Yes; the connector contract describes the precision and UTC-range policy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer. backport/4.2-master

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants