From ab75eb7b6c7c58fc48539e85bc729bfeba1bd4ba Mon Sep 17 00:00:00 2001 From: Bishop Kammeraad Date: Thu, 15 Oct 2026 10:06:49 -0400 Subject: [PATCH 1/2] fix(table): validate snapshot sequence numbers on v3 tables The check that rejects a snapshot whose sequence number is not greater than the table's last sequence number only ran for format version 2. Java applies it to every format version after 1. --- pyiceberg/table/update/__init__.py | 2 +- tests/table/test_init.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pyiceberg/table/update/__init__.py b/pyiceberg/table/update/__init__.py index 64838b0bd6..c93916327e 100644 --- a/pyiceberg/table/update/__init__.py +++ b/pyiceberg/table/update/__init__.py @@ -435,7 +435,7 @@ def _(update: AddSnapshotUpdate, base_metadata: TableMetadata, context: _TableMe elif base_metadata.snapshot_by_id(update.snapshot.snapshot_id) is not None: raise ValueError(f"Snapshot with id {update.snapshot.snapshot_id} already exists") elif ( - base_metadata.format_version == 2 + base_metadata.format_version >= 2 and update.snapshot.sequence_number is not None and update.snapshot.sequence_number <= base_metadata.last_sequence_number and update.snapshot.parent_snapshot_id is not None diff --git a/tests/table/test_init.py b/tests/table/test_init.py index 3d160781e3..1c0904714d 100644 --- a/tests/table/test_init.py +++ b/tests/table/test_init.py @@ -1966,6 +1966,26 @@ def test_add_snapshot_update_fails_with_smaller_first_row_id(table_v3: Table) -> update_table_metadata(table_v3.metadata, (AddSnapshotUpdate(snapshot=new_snapshot),)) +@pytest.mark.parametrize("table", [lf("table_v2"), lf("table_v3")]) +def test_add_snapshot_update_fails_with_stale_sequence_number(table: Table) -> None: + new_snapshot = Snapshot( + snapshot_id=25, + parent_snapshot_id=3055729675574597004, + sequence_number=table.metadata.last_sequence_number, + timestamp_ms=1602638593590, + manifest_list="s3:/a/b/c.avro", + summary=Summary(Operation.APPEND), + schema_id=1, + first_row_id=2, + ) + + with pytest.raises( + ValueError, + match="Cannot add snapshot with sequence number 34 older than last sequence number 34", + ): + update_table_metadata(table.metadata, (AddSnapshotUpdate(snapshot=new_snapshot),)) + + def test_add_snapshot_update_updates_next_row_id(table_v3: Table) -> None: new_snapshot = Snapshot( snapshot_id=25, From e3cf96f637e53eada25f89d74ff8687afa255638 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Sat, 24 Oct 2026 15:28:25 -0700 Subject: [PATCH 2/2] test: derive expected sequence number from the fixture The stale sequence-number test built the snapshot from the fixture's last_sequence_number but hardcoded 34 in the expected error message. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/table/test_init.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/table/test_init.py b/tests/table/test_init.py index 1c0904714d..4c1e276441 100644 --- a/tests/table/test_init.py +++ b/tests/table/test_init.py @@ -1968,10 +1968,11 @@ def test_add_snapshot_update_fails_with_smaller_first_row_id(table_v3: Table) -> @pytest.mark.parametrize("table", [lf("table_v2"), lf("table_v3")]) def test_add_snapshot_update_fails_with_stale_sequence_number(table: Table) -> None: + last_sequence_number = table.metadata.last_sequence_number new_snapshot = Snapshot( snapshot_id=25, parent_snapshot_id=3055729675574597004, - sequence_number=table.metadata.last_sequence_number, + sequence_number=last_sequence_number, timestamp_ms=1602638593590, manifest_list="s3:/a/b/c.avro", summary=Summary(Operation.APPEND), @@ -1981,7 +1982,10 @@ def test_add_snapshot_update_fails_with_stale_sequence_number(table: Table) -> N with pytest.raises( ValueError, - match="Cannot add snapshot with sequence number 34 older than last sequence number 34", + match=( + f"Cannot add snapshot with sequence number {last_sequence_number} " + f"older than last sequence number {last_sequence_number}" + ), ): update_table_metadata(table.metadata, (AddSnapshotUpdate(snapshot=new_snapshot),))