Skip to content

Commit db117e1

Browse files
authored
[python] Add rollback_to_timestamp to FileStoreTable (#7941)
1 parent 1249eb2 commit db117e1

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

paimon-python/pypaimon/table/file_store_table.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,23 @@ def rollback_helper(self):
298298
return RollbackHelper(
299299
self.snapshot_manager(), self.tag_manager(), self.file_io)
300300

301+
def rollback_to_timestamp(self, timestamp_millis: int) -> None:
302+
"""Rollback table to the latest snapshot with commit time <= the given timestamp.
303+
304+
Args:
305+
timestamp_millis: The timestamp in milliseconds to rollback to.
306+
307+
Raises:
308+
ValueError: If no snapshot exists at or before the given timestamp.
309+
"""
310+
snapshot_mgr = self.snapshot_manager()
311+
snapshot = snapshot_mgr.earlier_or_equal_time_mills(timestamp_millis)
312+
if snapshot is None:
313+
raise ValueError(
314+
f"No snapshot found with timestamp earlier than or equal to {timestamp_millis}ms."
315+
)
316+
self.rollback_to(snapshot.id)
317+
301318
def rename_tag(self, old_name: str, new_name: str) -> None:
302319
"""
303320
Rename a tag.

paimon-python/pypaimon/tests/table/simple_table_test.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,3 +686,76 @@ def test_table_rollback_to_nonexistent_tag(self):
686686
table.rollback_to("no-such-tag")
687687
self.assertIn("no-such-tag", str(context.exception))
688688
self.assertIn("doesn't exist", str(context.exception))
689+
690+
def test_table_rollback_to_timestamp(self):
691+
"""Test table-level rollback to a timestamp."""
692+
schema = Schema.from_pyarrow_schema(
693+
self.pa_schema,
694+
primary_keys=['pt', 'k'],
695+
partition_keys=['pt'],
696+
options={'bucket': '3'}
697+
)
698+
self.catalog.create_table('default.test_rollback_ts', schema, False)
699+
table = self.catalog.get_table('default.test_rollback_ts')
700+
701+
write_builder = table.new_batch_write_builder()
702+
703+
# Write 5 commits
704+
for i in range(5):
705+
table_write = write_builder.new_write()
706+
table_commit = write_builder.new_commit()
707+
data = pa.Table.from_pydict({
708+
'pt': [1],
709+
'k': [i],
710+
'v': [i * 100]
711+
}, schema=self.pk_pa_schema)
712+
table_write.write_arrow(data)
713+
table_commit.commit(table_write.prepare_commit())
714+
table_write.close()
715+
table_commit.close()
716+
717+
snapshot_mgr = table.snapshot_manager()
718+
self.assertEqual(snapshot_mgr.get_latest_snapshot().id, 5)
719+
720+
# Get the timestamp of snapshot 3 and rollback to it
721+
snap3 = snapshot_mgr.get_snapshot_by_id(3)
722+
table.rollback_to_timestamp(snap3.time_millis)
723+
724+
self.assertEqual(snapshot_mgr.get_latest_snapshot().id, 3)
725+
self.assertIsNone(snapshot_mgr.get_snapshot_by_id(4))
726+
self.assertIsNone(snapshot_mgr.get_snapshot_by_id(5))
727+
728+
def test_table_rollback_to_timestamp_no_match(self):
729+
"""Test rollback_to_timestamp raises ValueError when no snapshot exists."""
730+
schema = Schema.from_pyarrow_schema(
731+
self.pa_schema,
732+
primary_keys=['pt', 'k'],
733+
partition_keys=['pt'],
734+
options={'bucket': '3'}
735+
)
736+
self.catalog.create_table('default.test_rollback_ts_nomatch', schema, False)
737+
table = self.catalog.get_table('default.test_rollback_ts_nomatch')
738+
739+
write_builder = table.new_batch_write_builder()
740+
741+
# Write 1 commit
742+
table_write = write_builder.new_write()
743+
table_commit = write_builder.new_commit()
744+
data = pa.Table.from_pydict({
745+
'pt': [1],
746+
'k': [0],
747+
'v': [100]
748+
}, schema=self.pk_pa_schema)
749+
table_write.write_arrow(data)
750+
table_commit.commit(table_write.prepare_commit())
751+
table_write.close()
752+
table_commit.close()
753+
754+
# Use a timestamp before the first snapshot
755+
snapshot_mgr = table.snapshot_manager()
756+
earliest = snapshot_mgr.try_get_earliest_snapshot()
757+
before_earliest = earliest.time_millis - 1
758+
759+
with self.assertRaises(ValueError) as context:
760+
table.rollback_to_timestamp(before_earliest)
761+
self.assertIn("No snapshot found", str(context.exception))

0 commit comments

Comments
 (0)