@@ -589,5 +589,127 @@ def test_checkpoint_independence(self, db, cf, temp_db_path):
589589 shutil .rmtree (checkpoint_dir , ignore_errors = True )
590590
591591
592+ class TestRangeCost :
593+ """Tests for range cost estimation."""
594+
595+ def test_range_cost_returns_float (self , db , cf ):
596+ """Test that range_cost returns a float value."""
597+ with db .begin_txn () as txn :
598+ txn .put (cf , b"key_a" , b"value_a" )
599+ txn .put (cf , b"key_z" , b"value_z" )
600+ txn .commit ()
601+
602+ cost = cf .range_cost (b"key_a" , b"key_z" )
603+ assert isinstance (cost , float )
604+ assert cost >= 0.0
605+
606+ def test_range_cost_empty_cf (self , db , cf ):
607+ """Test range_cost on an empty column family."""
608+ cost = cf .range_cost (b"a" , b"z" )
609+ assert isinstance (cost , float )
610+ assert cost >= 0.0
611+
612+ def test_range_cost_key_order_irrelevant (self , db , cf ):
613+ """Test that key order does not matter."""
614+ with db .begin_txn () as txn :
615+ txn .put (cf , b"aaa" , b"1" )
616+ txn .put (cf , b"zzz" , b"2" )
617+ txn .commit ()
618+
619+ cost_ab = cf .range_cost (b"aaa" , b"zzz" )
620+ cost_ba = cf .range_cost (b"zzz" , b"aaa" )
621+ assert cost_ab == cost_ba
622+
623+ def test_range_cost_narrow_vs_wide (self , db , cf ):
624+ """Test that a wider range costs at least as much as a narrow one."""
625+ with db .begin_txn () as txn :
626+ for i in range (50 ):
627+ txn .put (cf , f"key:{ i :04d} " .encode (), f"val:{ i } " .encode ())
628+ txn .commit ()
629+
630+ narrow = cf .range_cost (b"key:0010" , b"key:0015" )
631+ wide = cf .range_cost (b"key:0000" , b"key:0049" )
632+ # Wide range should generally cost >= narrow range
633+ assert wide >= narrow
634+
635+ def test_range_cost_comparison (self , db , cf ):
636+ """Test comparing costs of different ranges."""
637+ with db .begin_txn () as txn :
638+ for i in range (100 ):
639+ txn .put (cf , f"user:{ i :04d} " .encode (), f"data:{ i } " .encode ())
640+ txn .commit ()
641+
642+ cost_a = cf .range_cost (b"user:0000" , b"user:0009" )
643+ cost_b = cf .range_cost (b"user:0000" , b"user:0099" )
644+ # Both should be valid floats
645+ assert isinstance (cost_a , float )
646+ assert isinstance (cost_b , float )
647+
648+
649+ class TestLoadConfigFromIni :
650+ """Tests for loading column family config from INI files."""
651+
652+ def test_save_and_load_roundtrip (self , temp_db_path ):
653+ """Test that saving and loading config produces equivalent results."""
654+ original = tidesdb .default_column_family_config ()
655+ original .write_buffer_size = 32 * 1024 * 1024
656+ original .compression_algorithm = tidesdb .CompressionAlgorithm .ZSTD_COMPRESSION
657+ original .enable_bloom_filter = True
658+ original .bloom_fpr = 0.001
659+ original .sync_mode = tidesdb .SyncMode .SYNC_FULL
660+ original .min_levels = 7
661+ original .use_btree = True
662+
663+ ini_path = os .path .join (temp_db_path , "test_config.ini" )
664+ tidesdb .save_config_to_ini (ini_path , "my_cf" , original )
665+
666+ loaded = tidesdb .load_config_from_ini (ini_path , "my_cf" )
667+
668+ assert loaded .write_buffer_size == original .write_buffer_size
669+ assert loaded .compression_algorithm == original .compression_algorithm
670+ assert loaded .enable_bloom_filter == original .enable_bloom_filter
671+ assert abs (loaded .bloom_fpr - original .bloom_fpr ) < 1e-9
672+ assert loaded .sync_mode == original .sync_mode
673+ assert loaded .min_levels == original .min_levels
674+ assert loaded .use_btree == original .use_btree
675+
676+ def test_load_nonexistent_file_raises (self , temp_db_path ):
677+ """Test that loading from a non-existent file raises error."""
678+ ini_path = os .path .join (temp_db_path , "nonexistent.ini" )
679+ with pytest .raises (tidesdb .TidesDBError ):
680+ tidesdb .load_config_from_ini (ini_path , "my_cf" )
681+
682+ def test_load_preserves_all_fields (self , temp_db_path ):
683+ """Test that all configuration fields survive a save/load roundtrip."""
684+ original = tidesdb .default_column_family_config ()
685+ original .level_size_ratio = 8
686+ original .dividing_level_offset = 3
687+ original .klog_value_threshold = 1024
688+ original .index_sample_ratio = 2
689+ original .block_index_prefix_len = 32
690+ original .sync_interval_us = 500000
691+ original .skip_list_max_level = 16
692+ original .skip_list_probability = 0.5
693+ original .min_disk_space = 200 * 1024 * 1024
694+ original .l1_file_count_trigger = 8
695+ original .l0_queue_stall_threshold = 30
696+
697+ ini_path = os .path .join (temp_db_path , "full_config.ini" )
698+ tidesdb .save_config_to_ini (ini_path , "full_cf" , original )
699+
700+ loaded = tidesdb .load_config_from_ini (ini_path , "full_cf" )
701+
702+ assert loaded .level_size_ratio == original .level_size_ratio
703+ assert loaded .dividing_level_offset == original .dividing_level_offset
704+ assert loaded .klog_value_threshold == original .klog_value_threshold
705+ assert loaded .index_sample_ratio == original .index_sample_ratio
706+ assert loaded .block_index_prefix_len == original .block_index_prefix_len
707+ assert loaded .sync_interval_us == original .sync_interval_us
708+ assert loaded .skip_list_max_level == original .skip_list_max_level
709+ assert loaded .min_disk_space == original .min_disk_space
710+ assert loaded .l1_file_count_trigger == original .l1_file_count_trigger
711+ assert loaded .l0_queue_stall_threshold == original .l0_queue_stall_threshold
712+
713+
592714if __name__ == "__main__" :
593715 pytest .main ([__file__ , "-v" ])
0 commit comments