This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
The following commit(s) were added to refs/heads/main by this push:
new 945c8db4c fix(table): reuse partition field IDs on v3 tables (#4110)
945c8db4c is described below
commit 945c8db4c82b4fb1c2decc29109bf9630515b0d2
Author: Bishop Kammeraad <[email protected]>
AuthorDate: Sat Oct 24 21:42:14 2026 +0000
fix(table): reuse partition field IDs on v3 tables (#4110)
* fix(table): reuse partition field IDs on v3 tables
UpdateSpec only looked up historical partition fields when the format
version was exactly 2, so v3 tables got a fresh field ID when a removed
partition field was added back. Java does this for format version >= 2.
* test: cover removing and re-adding a partition field in one update
Co-authored-by: Copilot App <[email protected]>
---------
Co-authored-by: Kevin Liu <[email protected]>
Co-authored-by: Copilot App <[email protected]>
---
pyiceberg/table/update/spec.py | 2 +-
tests/table/test_init.py | 24 ++++++++++++++++++++++++
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/pyiceberg/table/update/spec.py b/pyiceberg/table/update/spec.py
index e060d6c26..5b55d96b1 100644
--- a/pyiceberg/table/update/spec.py
+++ b/pyiceberg/table/update/spec.py
@@ -268,7 +268,7 @@ class UpdateSpec(UpdateTableMetadata["UpdateSpec"]):
return PartitionSpec(*partition_fields, spec_id=new_spec_id)
def _partition_field(self, transform_key: tuple[int, Transform[Any, Any]],
name: str | None) -> PartitionField:
- if self._transaction.table_metadata.format_version == 2:
+ if self._transaction.table_metadata.format_version >= 2:
source_id, transform = transform_key
historical_fields = []
for spec in self._transaction.table_metadata.specs().values():
diff --git a/tests/table/test_init.py b/tests/table/test_init.py
index 5ee5e4566..20acc870a 100644
--- a/tests/table/test_init.py
+++ b/tests/table/test_init.py
@@ -1479,6 +1479,30 @@ def test_last_assigned_partition_id(table_v2: Table) ->
None:
AssertLastAssignedPartitionId(last_assigned_partition_id=1).validate(base_metadata)
[email protected]("table", [lf("table_v2"), lf("table_v3")])
+def test_update_spec_reuses_removed_partition_field_id(table: Table) -> None:
+ txn = table.transaction()
+ txn.update_spec().remove_field("x").add_field("y", BucketTransform(16),
"y_bucket").commit()
+ txn.update_spec().add_identity("x").commit()
+
+ assert txn.table_metadata.spec().fields == (
+ PartitionField(source_id=2, field_id=1001,
transform=BucketTransform(16), name="y_bucket"),
+ PartitionField(source_id=1, field_id=1000,
transform=IdentityTransform(), name="x"),
+ )
+ assert txn.table_metadata.last_partition_id == 1001
+
+
[email protected]("table", [lf("table_v2"), lf("table_v3")])
+def test_update_spec_remove_and_add_same_field_reuses_id(table: Table) -> None:
+ txn = table.transaction()
+ txn.update_spec().remove_field("x").add_identity("x").commit()
+
+ assert txn.table_metadata.spec().fields == (
+ PartitionField(source_id=1, field_id=1000,
transform=IdentityTransform(), name="x"),
+ )
+ assert txn.table_metadata.last_partition_id == 1000
+
+
def test_assert_default_spec_id(table_v2: Table) -> None:
base_metadata = table_v2.metadata
AssertDefaultSpecId(default_spec_id=base_metadata.default_spec_id).validate(base_metadata)