Skip to content

Commit bed0269

Browse files
authored
Merge pull request #32 from tidesdb/tdb740-align
Tdb740 align
2 parents b11763d + 06e5c72 commit bed0269

3 files changed

Lines changed: 199 additions & 5 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.5.0"
7+
version = "0.6.0"
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
TidesDBError,
2424
default_config,
2525
default_column_family_config,
26+
save_config_to_ini,
27+
COMPARATOR_FUNC,
2628
)
2729

2830
__version__ = "7.3.1"
@@ -42,4 +44,6 @@
4244
"TidesDBError",
4345
"default_config",
4446
"default_column_family_config",
47+
"save_config_to_ini",
48+
"COMPARATOR_FUNC",
4549
]

src/tidesdb/tidesdb.py

Lines changed: 194 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ class _CColumnFamilyConfig(Structure):
176176
("min_levels", c_int),
177177
("dividing_level_offset", c_int),
178178
("klog_value_threshold", c_size_t),
179-
("compression_algo", c_int),
179+
("compression_algorithm", c_int),
180180
("enable_bloom_filter", c_int),
181181
("bloom_fpr", c_double),
182182
("enable_block_indexes", c_int),
@@ -219,6 +219,13 @@ class _CStats(Structure):
219219
("level_sizes", POINTER(c_size_t)),
220220
("level_num_sstables", POINTER(c_int)),
221221
("config", POINTER(_CColumnFamilyConfig)),
222+
("total_keys", c_uint64),
223+
("total_data_size", c_uint64),
224+
("avg_key_size", c_double),
225+
("avg_value_size", c_double),
226+
("level_key_counts", POINTER(c_uint64)),
227+
("read_amp", c_double),
228+
("hit_rate", c_double),
222229
]
223230

224231

@@ -356,6 +363,31 @@ class _CCacheStats(Structure):
356363
_lib.tidesdb_get_cache_stats.argtypes = [c_void_p, POINTER(_CCacheStats)]
357364
_lib.tidesdb_get_cache_stats.restype = c_int
358365

366+
_lib.tidesdb_backup.argtypes = [c_void_p, c_char_p]
367+
_lib.tidesdb_backup.restype = c_int
368+
369+
_lib.tidesdb_rename_column_family.argtypes = [c_void_p, c_char_p, c_char_p]
370+
_lib.tidesdb_rename_column_family.restype = c_int
371+
372+
_lib.tidesdb_cf_update_runtime_config.argtypes = [c_void_p, POINTER(_CColumnFamilyConfig), c_int]
373+
_lib.tidesdb_cf_update_runtime_config.restype = c_int
374+
375+
_lib.tidesdb_cf_config_save_to_ini.argtypes = [c_char_p, c_char_p, POINTER(_CColumnFamilyConfig)]
376+
_lib.tidesdb_cf_config_save_to_ini.restype = c_int
377+
378+
_lib.tidesdb_is_flushing.argtypes = [c_void_p]
379+
_lib.tidesdb_is_flushing.restype = c_int
380+
381+
_lib.tidesdb_is_compacting.argtypes = [c_void_p]
382+
_lib.tidesdb_is_compacting.restype = c_int
383+
384+
# Comparator function type: int (*)(const uint8_t*, size_t, const uint8_t*, size_t, void*)
385+
COMPARATOR_FUNC = ctypes.CFUNCTYPE(c_int, POINTER(c_uint8), c_size_t, POINTER(c_uint8), c_size_t, c_void_p)
386+
DESTROY_FUNC = ctypes.CFUNCTYPE(None, c_void_p)
387+
388+
_lib.tidesdb_register_comparator.argtypes = [c_void_p, c_char_p, COMPARATOR_FUNC, c_void_p, DESTROY_FUNC]
389+
_lib.tidesdb_register_comparator.restype = c_int
390+
359391

360392
@dataclass
361393
class Config:
@@ -402,7 +434,7 @@ def _to_c_struct(self) -> _CColumnFamilyConfig:
402434
c_config.min_levels = self.min_levels
403435
c_config.dividing_level_offset = self.dividing_level_offset
404436
c_config.klog_value_threshold = self.klog_value_threshold
405-
c_config.compression_algo = int(self.compression_algorithm)
437+
c_config.compression_algorithm = int(self.compression_algorithm)
406438
c_config.enable_bloom_filter = 1 if self.enable_bloom_filter else 0
407439
c_config.bloom_fpr = self.bloom_fpr
408440
c_config.enable_block_indexes = 1 if self.enable_block_indexes else 0
@@ -432,6 +464,13 @@ class Stats:
432464
memtable_size: int
433465
level_sizes: list[int]
434466
level_num_sstables: list[int]
467+
total_keys: int
468+
total_data_size: int
469+
avg_key_size: float
470+
avg_value_size: float
471+
level_key_counts: list[int]
472+
read_amp: float
473+
hit_rate: float
435474
config: ColumnFamilyConfig | None = None
436475

437476

@@ -462,7 +501,7 @@ def default_column_family_config() -> ColumnFamilyConfig:
462501
min_levels=c_config.min_levels,
463502
dividing_level_offset=c_config.dividing_level_offset,
464503
klog_value_threshold=c_config.klog_value_threshold,
465-
compression_algorithm=CompressionAlgorithm(c_config.compression_algo),
504+
compression_algorithm=CompressionAlgorithm(c_config.compression_algorithm),
466505
enable_bloom_filter=bool(c_config.enable_bloom_filter),
467506
bloom_fpr=c_config.bloom_fpr,
468507
enable_block_indexes=bool(c_config.enable_block_indexes),
@@ -480,6 +519,23 @@ def default_column_family_config() -> ColumnFamilyConfig:
480519
)
481520

482521

522+
def save_config_to_ini(file_path: str, cf_name: str, config: ColumnFamilyConfig) -> None:
523+
"""
524+
Save column family configuration to a custom INI file.
525+
526+
Args:
527+
file_path: Path to the INI file to create/overwrite
528+
cf_name: Name of the column family (used as section name)
529+
config: Configuration to save
530+
"""
531+
c_config = config._to_c_struct()
532+
result = _lib.tidesdb_cf_config_save_to_ini(
533+
file_path.encode("utf-8"), cf_name.encode("utf-8"), ctypes.byref(c_config)
534+
)
535+
if result != TDB_SUCCESS:
536+
raise TidesDBError.from_code(result, "failed to save config to INI file")
537+
538+
483539
class Iterator:
484540
"""Iterator for traversing key-value pairs in a column family."""
485541

@@ -620,6 +676,38 @@ def flush_memtable(self) -> None:
620676
if result != TDB_SUCCESS:
621677
raise TidesDBError.from_code(result, "failed to flush memtable")
622678

679+
def is_flushing(self) -> bool:
680+
"""Check if a flush operation is in progress for this column family."""
681+
return bool(_lib.tidesdb_is_flushing(self._cf))
682+
683+
def is_compacting(self) -> bool:
684+
"""Check if a compaction operation is in progress for this column family."""
685+
return bool(_lib.tidesdb_is_compacting(self._cf))
686+
687+
def update_runtime_config(self, config: ColumnFamilyConfig, persist_to_disk: bool = True) -> None:
688+
"""
689+
Update runtime-safe configuration settings for this column family.
690+
691+
Updatable settings (safe to change at runtime):
692+
- write_buffer_size: Memtable flush threshold
693+
- skip_list_max_level: Skip list level for new memtables
694+
- skip_list_probability: Skip list probability for new memtables
695+
- bloom_fpr: False positive rate for new SSTables
696+
- index_sample_ratio: Index sampling ratio for new SSTables
697+
- sync_mode: Durability mode
698+
- sync_interval_us: Sync interval in microseconds
699+
700+
Args:
701+
config: New configuration settings
702+
persist_to_disk: If True, save changes to config.ini
703+
"""
704+
c_config = config._to_c_struct()
705+
result = _lib.tidesdb_cf_update_runtime_config(
706+
self._cf, ctypes.byref(c_config), 1 if persist_to_disk else 0
707+
)
708+
if result != TDB_SUCCESS:
709+
raise TidesDBError.from_code(result, "failed to update runtime config")
710+
623711
def get_stats(self) -> Stats:
624712
"""Get statistics for this column family."""
625713
stats_ptr = POINTER(_CStats)()
@@ -631,6 +719,7 @@ def get_stats(self) -> Stats:
631719

632720
level_sizes = []
633721
level_num_sstables = []
722+
level_key_counts = []
634723

635724
if c_stats.num_levels > 0:
636725
if c_stats.level_sizes:
@@ -639,6 +728,9 @@ def get_stats(self) -> Stats:
639728
if c_stats.level_num_sstables:
640729
for i in range(c_stats.num_levels):
641730
level_num_sstables.append(c_stats.level_num_sstables[i])
731+
if c_stats.level_key_counts:
732+
for i in range(c_stats.num_levels):
733+
level_key_counts.append(c_stats.level_key_counts[i])
642734

643735
config = None
644736
if c_stats.config:
@@ -649,7 +741,7 @@ def get_stats(self) -> Stats:
649741
min_levels=c_cfg.min_levels,
650742
dividing_level_offset=c_cfg.dividing_level_offset,
651743
klog_value_threshold=c_cfg.klog_value_threshold,
652-
compression_algorithm=CompressionAlgorithm(c_cfg.compression_algo),
744+
compression_algorithm=CompressionAlgorithm(c_cfg.compression_algorithm),
653745
enable_bloom_filter=bool(c_cfg.enable_bloom_filter),
654746
bloom_fpr=c_cfg.bloom_fpr,
655747
enable_block_indexes=bool(c_cfg.enable_block_indexes),
@@ -671,6 +763,13 @@ def get_stats(self) -> Stats:
671763
memtable_size=c_stats.memtable_size,
672764
level_sizes=level_sizes,
673765
level_num_sstables=level_num_sstables,
766+
total_keys=c_stats.total_keys,
767+
total_data_size=c_stats.total_data_size,
768+
avg_key_size=c_stats.avg_key_size,
769+
avg_value_size=c_stats.avg_value_size,
770+
level_key_counts=level_key_counts,
771+
read_amp=c_stats.read_amp,
772+
hit_rate=c_stats.hit_rate,
674773
config=config,
675774
)
676775

@@ -1126,6 +1225,97 @@ def get_cache_stats(self) -> CacheStats:
11261225
num_partitions=c_stats.num_partitions,
11271226
)
11281227

1228+
def backup(self, backup_dir: str) -> None:
1229+
"""
1230+
Create an on-disk snapshot of the database without blocking normal reads/writes.
1231+
1232+
Args:
1233+
backup_dir: Path to the backup directory (must be non-existent or empty)
1234+
1235+
Raises:
1236+
TidesDBError: If backup fails (e.g., directory not empty, I/O error)
1237+
"""
1238+
if self._closed:
1239+
raise TidesDBError("Database is closed")
1240+
1241+
result = _lib.tidesdb_backup(self._db, backup_dir.encode("utf-8"))
1242+
if result != TDB_SUCCESS:
1243+
raise TidesDBError.from_code(result, "failed to create backup")
1244+
1245+
def rename_column_family(self, old_name: str, new_name: str) -> None:
1246+
"""
1247+
Atomically rename a column family and its underlying directory.
1248+
1249+
The operation waits for any in-progress flush or compaction to complete
1250+
before renaming.
1251+
1252+
Args:
1253+
old_name: Current name of the column family
1254+
new_name: New name for the column family
1255+
1256+
Raises:
1257+
TidesDBError: If rename fails (e.g., old_name not found, new_name exists)
1258+
"""
1259+
if self._closed:
1260+
raise TidesDBError("Database is closed")
1261+
1262+
result = _lib.tidesdb_rename_column_family(
1263+
self._db, old_name.encode("utf-8"), new_name.encode("utf-8")
1264+
)
1265+
if result != TDB_SUCCESS:
1266+
raise TidesDBError.from_code(result, "failed to rename column family")
1267+
1268+
def register_comparator(
1269+
self,
1270+
name: str,
1271+
comparator_fn: callable,
1272+
ctx: object = None,
1273+
) -> None:
1274+
"""
1275+
Register a custom comparator for use with column families.
1276+
1277+
The comparator function determines the sort order of keys throughout the
1278+
entire system: memtables, SSTables, block indexes, and iterators.
1279+
1280+
Built-in comparators (automatically registered):
1281+
- "memcmp": Binary byte-by-byte comparison (default)
1282+
- "lexicographic": Null-terminated string comparison
1283+
- "uint64": Unsigned 64-bit integer comparison
1284+
- "int64": Signed 64-bit integer comparison
1285+
- "reverse": Reverse binary comparison
1286+
- "case_insensitive": Case-insensitive ASCII comparison
1287+
1288+
Args:
1289+
name: Name of the comparator (used in ColumnFamilyConfig.comparator_name)
1290+
comparator_fn: Function with signature (key1: bytes, key2: bytes) -> int
1291+
Returns < 0 if key1 < key2, 0 if equal, > 0 if key1 > key2
1292+
ctx: Optional context object (not currently used, reserved for future)
1293+
1294+
Note:
1295+
Comparators must be registered BEFORE creating column families that use them.
1296+
Once set, a comparator cannot be changed for a column family.
1297+
"""
1298+
if self._closed:
1299+
raise TidesDBError("Database is closed")
1300+
1301+
# Wrap Python function in C-compatible callback
1302+
def c_comparator(key1_ptr, key1_size, key2_ptr, key2_size, ctx_ptr):
1303+
key1 = ctypes.string_at(key1_ptr, key1_size) if key1_ptr and key1_size > 0 else b""
1304+
key2 = ctypes.string_at(key2_ptr, key2_size) if key2_ptr and key2_size > 0 else b""
1305+
return comparator_fn(key1, key2)
1306+
1307+
# Create C function pointer and store reference to prevent garbage collection
1308+
c_func = COMPARATOR_FUNC(c_comparator)
1309+
if not hasattr(self, "_comparator_refs"):
1310+
self._comparator_refs = []
1311+
self._comparator_refs.append(c_func)
1312+
1313+
result = _lib.tidesdb_register_comparator(
1314+
self._db, name.encode("utf-8"), c_func, None, None
1315+
)
1316+
if result != TDB_SUCCESS:
1317+
raise TidesDBError.from_code(result, "failed to register comparator")
1318+
11291319
def __enter__(self) -> TidesDB:
11301320
return self
11311321

0 commit comments

Comments
 (0)