Skip to content

Commit e4c3669

Browse files
authored
Merge pull request #47 from tidesdb/0-9-5
issue #46 extend python library to match c library db.h functionality…
2 parents cb19234 + 4de87b4 commit e4c3669

4 files changed

Lines changed: 135 additions & 3 deletions

File tree

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.4"
7+
version = "0.9.5"
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
@@ -30,7 +30,7 @@
3030
COMMIT_HOOK_FUNC,
3131
)
3232

33-
__version__ = "0.9.1"
33+
__version__ = "0.9.5"
3434
__all__ = [
3535
"TidesDB",
3636
"Transaction",

src/tidesdb/tidesdb.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ class _CConfig(Structure):
232232
("log_level", c_int),
233233
("block_cache_size", c_size_t),
234234
("max_open_sstables", c_size_t),
235+
("max_memory_usage", c_size_t),
235236
("log_to_file", c_int),
236237
("log_truncation_at", c_size_t),
237238
]
@@ -444,6 +445,12 @@ class _CCacheStats(Structure):
444445
_lib.tidesdb_register_comparator.argtypes = [c_void_p, c_char_p, COMPARATOR_FUNC, c_void_p, DESTROY_FUNC]
445446
_lib.tidesdb_register_comparator.restype = c_int
446447

448+
_lib.tidesdb_get_comparator.argtypes = [c_void_p, c_char_p, POINTER(COMPARATOR_FUNC), POINTER(c_void_p)]
449+
_lib.tidesdb_get_comparator.restype = c_int
450+
451+
_lib.tidesdb_delete_column_family.argtypes = [c_void_p, c_void_p]
452+
_lib.tidesdb_delete_column_family.restype = c_int
453+
447454

448455
@dataclass
449456
class Config:
@@ -455,6 +462,7 @@ class Config:
455462
log_level: LogLevel = LogLevel.LOG_INFO
456463
block_cache_size: int = 64 * 1024 * 1024
457464
max_open_sstables: int = 256
465+
max_memory_usage: int = 0
458466
log_to_file: bool = False
459467
log_truncation_at: int = 24 * 1024 * 1024
460468

@@ -1230,6 +1238,7 @@ def __init__(self, config: Config) -> None:
12301238
log_level=int(config.log_level),
12311239
block_cache_size=config.block_cache_size,
12321240
max_open_sstables=config.max_open_sstables,
1241+
max_memory_usage=config.max_memory_usage,
12331242
log_to_file=1 if config.log_to_file else 0,
12341243
log_truncation_at=config.log_truncation_at,
12351244
)
@@ -1251,6 +1260,7 @@ def open(
12511260
log_level: LogLevel = LogLevel.LOG_INFO,
12521261
block_cache_size: int = 64 * 1024 * 1024,
12531262
max_open_sstables: int = 256,
1263+
max_memory_usage: int = 0,
12541264
log_to_file: bool = False,
12551265
log_truncation_at: int = 24 * 1024 * 1024,
12561266
) -> TidesDB:
@@ -1264,6 +1274,7 @@ def open(
12641274
log_level: Logging level
12651275
block_cache_size: Size of block cache in bytes
12661276
max_open_sstables: Maximum number of open SSTables
1277+
max_memory_usage: Global memory limit in bytes (0 = auto, 50% of system RAM)
12671278
log_to_file: Write logs to file instead of stderr
12681279
log_truncation_at: Log file truncation size in bytes (0 = no truncation)
12691280
@@ -1277,6 +1288,7 @@ def open(
12771288
log_level=log_level,
12781289
block_cache_size=block_cache_size,
12791290
max_open_sstables=max_open_sstables,
1291+
max_memory_usage=max_memory_usage,
12801292
log_to_file=log_to_file,
12811293
log_truncation_at=log_truncation_at,
12821294
)
@@ -1599,11 +1611,51 @@ def c_comparator(key1_ptr, key1_size, key2_ptr, key2_size, ctx_ptr):
15991611
self._comparator_refs.append(c_func)
16001612

16011613
result = _lib.tidesdb_register_comparator(
1602-
self._db, name.encode("utf-8"), c_func, None, None
1614+
self._db, name.encode("utf-8"), c_func, c_void_p(None), DESTROY_FUNC()
16031615
)
16041616
if result != TDB_SUCCESS:
16051617
raise TidesDBError.from_code(result, "failed to register comparator")
16061618

1619+
def get_comparator(self, name: str) -> bool:
1620+
"""
1621+
Check if a comparator is registered by name.
1622+
1623+
Args:
1624+
name: Name of the comparator to look up
1625+
1626+
Returns:
1627+
True if the comparator is registered, False otherwise
1628+
"""
1629+
if self._closed:
1630+
raise TidesDBError("Database is closed")
1631+
1632+
fn = COMPARATOR_FUNC()
1633+
ctx = c_void_p()
1634+
result = _lib.tidesdb_get_comparator(
1635+
self._db, name.encode("utf-8"), ctypes.byref(fn), ctypes.byref(ctx)
1636+
)
1637+
return result == TDB_SUCCESS
1638+
1639+
def delete_column_family(self, cf: ColumnFamily) -> None:
1640+
"""
1641+
Delete a column family by pointer (skips name lookup).
1642+
1643+
This is faster than drop_column_family() when you already hold a
1644+
ColumnFamily handle, as it avoids a redundant linear scan by name.
1645+
1646+
Args:
1647+
cf: Column family handle to delete
1648+
1649+
Raises:
1650+
TidesDBError: If deletion fails
1651+
"""
1652+
if self._closed:
1653+
raise TidesDBError("Database is closed")
1654+
1655+
result = _lib.tidesdb_delete_column_family(self._db, cf._cf)
1656+
if result != TDB_SUCCESS:
1657+
raise TidesDBError.from_code(result, "failed to delete column family")
1658+
16071659
def __enter__(self) -> TidesDB:
16081660
return self
16091661

tests/test_tidesdb.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,5 +887,85 @@ def crashing_hook(ops, commit_seq):
887887
assert txn.get(cf, b"crash_key") == b"crash_val"
888888

889889

890+
class TestGetComparator:
891+
"""Tests for get_comparator operations."""
892+
893+
def test_builtin_comparator_exists(self, db):
894+
"""Test that built-in comparators are registered."""
895+
assert db.get_comparator("memcmp") is True
896+
assert db.get_comparator("reverse") is True
897+
assert db.get_comparator("lexicographic") is True
898+
899+
def test_nonexistent_comparator(self, db):
900+
"""Test that a non-existent comparator returns False."""
901+
assert db.get_comparator("nonexistent_comp") is False
902+
903+
def test_custom_comparator_registered(self, db):
904+
"""Test that a custom registered comparator is found."""
905+
def my_cmp(k1: bytes, k2: bytes) -> int:
906+
if k1 < k2:
907+
return -1
908+
elif k1 > k2:
909+
return 1
910+
return 0
911+
912+
db.register_comparator("my_test_cmp", my_cmp)
913+
assert db.get_comparator("my_test_cmp") is True
914+
915+
916+
class TestDeleteColumnFamily:
917+
"""Tests for delete_column_family (by pointer) operations."""
918+
919+
def test_delete_by_pointer(self, db):
920+
"""Test deleting a column family by pointer."""
921+
db.create_column_family("del_cf")
922+
cf = db.get_column_family("del_cf")
923+
assert cf is not None
924+
925+
db.delete_column_family(cf)
926+
927+
with pytest.raises(tidesdb.TidesDBError):
928+
db.get_column_family("del_cf")
929+
930+
def test_delete_with_data(self, db):
931+
"""Test deleting a column family that contains data."""
932+
db.create_column_family("data_cf")
933+
cf = db.get_column_family("data_cf")
934+
935+
with db.begin_txn() as txn:
936+
txn.put(cf, b"key1", b"value1")
937+
txn.commit()
938+
939+
db.delete_column_family(cf)
940+
941+
with pytest.raises(tidesdb.TidesDBError):
942+
db.get_column_family("data_cf")
943+
944+
945+
class TestMaxMemoryUsage:
946+
"""Tests for max_memory_usage configuration."""
947+
948+
def test_config_default(self):
949+
"""Test that default max_memory_usage is 0."""
950+
config = tidesdb.Config(db_path="/tmp/test")
951+
assert config.max_memory_usage == 0
952+
953+
def test_open_with_max_memory_usage(self, temp_db_path):
954+
"""Test opening a database with max_memory_usage set."""
955+
db = tidesdb.TidesDB.open(temp_db_path, max_memory_usage=0)
956+
assert db is not None
957+
db.close()
958+
959+
def test_config_with_max_memory_usage(self, temp_db_path):
960+
"""Test Config with explicit max_memory_usage."""
961+
config = tidesdb.Config(
962+
db_path=temp_db_path,
963+
max_memory_usage=512 * 1024 * 1024, # 512MB
964+
)
965+
db = tidesdb.TidesDB(config)
966+
assert db is not None
967+
db.close()
968+
969+
890970
if __name__ == "__main__":
891971
pytest.main([__file__, "-v"])

0 commit comments

Comments
 (0)