Skip to content

fix(spec): Skip the partition name check once the source column is dropped - #3420

Open
xanderbailey wants to merge 2 commits into
apache:mainfrom
xanderbailey:xb/partition-name-conflict-source-column
Open

xanderbailey wants to merge 2 commits into
apache:mainfrom
xanderbailey:xb/partition-name-conflict-source-column

Conversation

@xanderbailey

@xanderbailey xanderbailey commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Working towards #3056

Supersedes #3412, which scoped the check to the current spec. That was not correct - see @mbutrovich's review there.

What changes are included in this PR?

validate_schema_field_names rejected a new schema field whose name matched a partition field name in any spec, unless some schema already had that name. Iceberg keeps specs forever, so the name stayed reserved even when the spec was only retained to interpret old metadata.

Java runs an equivalent check in checkAndAddPartitionName, reached from Builder.add as setCurrentSchema rebuilds every spec. It skips a field whose source column is absent from the new schema, and otherwise allows the name only for the source column itself. This PR adopts that rule.

Every spec stays in scope, so nothing previously rejected is accepted now unless its source column is gone. name_exists_in_any_schema is dropped now, it was our own approximation of the identity-partition case, and it allowed a name that an old schema had even when it resolved to a different field in the new one.

The check now iterates partition fields rather than schema names, so the result no longer depends on whether the schema or the spec is added first.

Not addressed

build() rebinds the default spec with bind(), which requires the source column to exist, so dropping the source column of the current spec still fails. Java uses buildUnchecked() there and tolerates it. Happy to follow up separately if that's worth matching.

Are these changes tested?

Unit tests in table_metadata_builder.rs:

AI Disclosure

Claude Code was used to trace the Java call paths, write the tests. I reviewed the result.

…opped

`validate_schema_field_names` rejected any new schema field whose name
matched a partition field name in any spec, unless some schema already
had that name. Iceberg keeps specs forever, so a name used by a spec
whose source column has since been dropped stayed reserved even though
the spec is only retained to interpret old metadata.

Iceberg Java runs the same check, in `checkAndAddPartitionName`, as
`setCurrentSchema` rebuilds every spec. It skips a field whose source
column is absent from the new schema, and otherwise allows the name only
for the source column itself:

    boolean sourceFieldExists = schema.findField(sourceColumnId) != null;
    if (sourceFieldExists) {
      Preconditions.checkArgument(
          schemaField == null || schemaField.fieldId() == sourceColumnId, ...);
    }

Adopt that rule. It keeps every spec in scope, so nothing that was
rejected before is accepted now unless its source column is gone, and it
drops the `name_exists_in_any_schema` escape hatch, which was a
Rust-only approximation of the identity-partition case.

The check now reads partition fields rather than schema names, so its
result no longer depends on whether the schema or the spec is added
first.

`partition_name_exists` loses its only caller and is removed.

Note a related divergence this does not address: `build()` rebinds the
default spec with `bind()`, which requires the source column to exist,
so dropping the source column of the *current* spec still fails. Java
uses `buildUnchecked()` there and tolerates it.

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

Thanks for this! I wanted to double-check the part about the result no longer depending on order, so I tried a few orderings on Java (core at 059e7ea94, JDK 21) and on this branch.

Setup: an unpartitioned data: string table, spec bucket(data, 16) AS bucket_data, and a new schema data, bucket_data (id 2).

order Java this PR (and main)
add schema → add spec → set current schema rejected accepted
add spec → add current schema rejected rejected
add spec → add schema (not current) accepted rejected

As far as I can tell, Java only runs this check in setCurrentSchema, when it rebuilds every spec against the new schema. Here it runs in add_schema, so the first order gets through and the third is stricter than Java. Running the check from set_current_schema instead would line up with Java in all three cases. The first one already happens on main, though, so a follow-up is fine if you'd rather keep this PR focused.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema evolution rejects field names reused from superseded partition specs

2 participants