@@ -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
361393class 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+
483539class 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