Skip to content

Commit d9cef36

Browse files
authored
Merge pull request #40 from tidesdb/checkpoint-feat
checkpoint api feature support to align with tdb v8.3.2+; correct ver…
2 parents 015bd92 + bbcb942 commit d9cef36

File tree

4 files changed

+86
-2
lines changed

4 files changed

+86
-2
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "tidesdb"
7-
version = "0.9.0"
7+
version = "0.9.1"
88
description = "Official Python bindings for TidesDB - A high-performance embedded key-value storage engine"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/tidesdb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
COMPARATOR_FUNC,
2828
)
2929

30-
__version__ = "7.3.1"
30+
__version__ = "0.9.1"
3131
__all__ = [
3232
"TidesDB",
3333
"Transaction",

src/tidesdb/tidesdb.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,9 @@ class _CCacheStats(Structure):
374374
_lib.tidesdb_backup.argtypes = [c_void_p, c_char_p]
375375
_lib.tidesdb_backup.restype = c_int
376376

377+
_lib.tidesdb_checkpoint.argtypes = [c_void_p, c_char_p]
378+
_lib.tidesdb_checkpoint.restype = c_int
379+
377380
_lib.tidesdb_rename_column_family.argtypes = [c_void_p, c_char_p, c_char_p]
378381
_lib.tidesdb_rename_column_family.restype = c_int
379382

@@ -1300,6 +1303,27 @@ def backup(self, backup_dir: str) -> None:
13001303
if result != TDB_SUCCESS:
13011304
raise TidesDBError.from_code(result, "failed to create backup")
13021305

1306+
def checkpoint(self, checkpoint_dir: str) -> None:
1307+
"""
1308+
Create a lightweight, near-instant snapshot of the database using hard links.
1309+
1310+
Unlike backup(), checkpoint uses hard links instead of copying SSTable data,
1311+
making it near-instant and using no extra disk space until compaction removes
1312+
old SSTables.
1313+
1314+
Args:
1315+
checkpoint_dir: Path to the checkpoint directory (must be non-existent or empty)
1316+
1317+
Raises:
1318+
TidesDBError: If checkpoint fails (e.g., directory not empty, I/O error)
1319+
"""
1320+
if self._closed:
1321+
raise TidesDBError("Database is closed")
1322+
1323+
result = _lib.tidesdb_checkpoint(self._db, checkpoint_dir.encode("utf-8"))
1324+
if result != TDB_SUCCESS:
1325+
raise TidesDBError.from_code(result, "failed to create checkpoint")
1326+
13031327
def rename_column_family(self, old_name: str, new_name: str) -> None:
13041328
"""
13051329
Atomically rename a column family and its underlying directory.

tests/test_tidesdb.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,5 +529,65 @@ def test_compact(self, db, cf):
529529
time.sleep(0.5)
530530

531531

532+
class TestCheckpoint:
533+
"""Tests for checkpoint operations."""
534+
535+
def test_checkpoint_creates_snapshot(self, db, cf, temp_db_path):
536+
"""Test that checkpoint creates a usable snapshot."""
537+
with db.begin_txn() as txn:
538+
txn.put(cf, b"key1", b"value1")
539+
txn.put(cf, b"key2", b"value2")
540+
txn.commit()
541+
542+
checkpoint_dir = temp_db_path + "_checkpoint"
543+
try:
544+
db.checkpoint(checkpoint_dir)
545+
assert os.path.isdir(checkpoint_dir)
546+
547+
with tidesdb.TidesDB.open(checkpoint_dir) as checkpoint_db:
548+
cp_cf = checkpoint_db.get_column_family("test_cf")
549+
with checkpoint_db.begin_txn() as txn:
550+
assert txn.get(cp_cf, b"key1") == b"value1"
551+
assert txn.get(cp_cf, b"key2") == b"value2"
552+
finally:
553+
shutil.rmtree(checkpoint_dir, ignore_errors=True)
554+
555+
def test_checkpoint_existing_dir_raises(self, db, cf, temp_db_path):
556+
"""Test that checkpoint to a non-empty directory raises error."""
557+
with db.begin_txn() as txn:
558+
txn.put(cf, b"key1", b"value1")
559+
txn.commit()
560+
561+
checkpoint_dir = temp_db_path + "_checkpoint"
562+
try:
563+
db.checkpoint(checkpoint_dir)
564+
565+
with pytest.raises(tidesdb.TidesDBError):
566+
db.checkpoint(checkpoint_dir)
567+
finally:
568+
shutil.rmtree(checkpoint_dir, ignore_errors=True)
569+
570+
def test_checkpoint_independence(self, db, cf, temp_db_path):
571+
"""Test that checkpoint is independent from the live database."""
572+
with db.begin_txn() as txn:
573+
txn.put(cf, b"key1", b"original")
574+
txn.commit()
575+
576+
checkpoint_dir = temp_db_path + "_checkpoint"
577+
try:
578+
db.checkpoint(checkpoint_dir)
579+
580+
with db.begin_txn() as txn:
581+
txn.put(cf, b"key1", b"modified")
582+
txn.commit()
583+
584+
with tidesdb.TidesDB.open(checkpoint_dir) as checkpoint_db:
585+
cp_cf = checkpoint_db.get_column_family("test_cf")
586+
with checkpoint_db.begin_txn() as txn:
587+
assert txn.get(cp_cf, b"key1") == b"original"
588+
finally:
589+
shutil.rmtree(checkpoint_dir, ignore_errors=True)
590+
591+
532592
if __name__ == "__main__":
533593
pytest.main([__file__, "-v"])

0 commit comments

Comments
 (0)