use aok::Void;
use tempfile::tempdir;
use wedb_embed::{
BlockSplitBloomFilter, BloomChainMeta, BloomFilterAddResult, BloomFilterInsertOptions,
CuckooChainMeta, CuckooFilterHelper, CuckooFilterInsertOptions, WeDb,
};
#[ctor::ctor(unsafe)]
fn _log_init() {
log_init::init();
}
#[test]
fn test_block_split_bloom_math_and_filter() -> Void {
let bits_1000 = BlockSplitBloomFilter::optimal_num_of_bits(1000, 0.01);
assert!(bits_1000.is_power_of_two());
assert!(bits_1000 >= 32 * 8);
let bytes_1000 = BlockSplitBloomFilter::optimal_num_of_bytes(1000, 0.01);
assert_eq!(bytes_1000, bits_1000 / 8);
let mut data = vec![0u8; 32];
let h1 = BlockSplitBloomFilter::hash(b"hello");
let h2 = BlockSplitBloomFilter::hash(b"world");
assert!(!BlockSplitBloomFilter::find_hash(&data, h1));
assert!(!BlockSplitBloomFilter::find_hash(&data, h2));
BlockSplitBloomFilter::insert_hash(&mut data, h1);
assert!(BlockSplitBloomFilter::find_hash(&data, h1));
assert!(!BlockSplitBloomFilter::find_hash(&data, h2));
BlockSplitBloomFilter::insert_hash(&mut data, h2);
assert!(BlockSplitBloomFilter::find_hash(&data, h1));
assert!(BlockSplitBloomFilter::find_hash(&data, h2));
Ok(())
}
#[test]
fn test_bloom_chain_metadata_codec_and_capacity() -> Void {
let meta = BloomChainMeta::new(100, 0.01, 2, 1, 1000, 256);
assert_eq!(meta.get_capacity(), 100);
assert!(meta.is_scaling());
let encoded = meta.encode();
assert_eq!(encoded.len(), BloomChainMeta::ENCODED_SIZE);
let decoded = BloomChainMeta::decode(&encoded).expect("decode failed");
assert_eq!(decoded.base_capacity, 100);
assert_eq!(decoded.error_rate, 0.01);
assert_eq!(decoded.expansion, 2);
assert_eq!(decoded.n_filters, 1);
assert_eq!(decoded.bloom_bytes, 256);
let non_scaling_meta = BloomChainMeta::new(500, 0.01, 0, 1, 0, 512);
assert!(!non_scaling_meta.is_scaling());
assert_eq!(non_scaling_meta.get_capacity(), 500);
Ok(())
}
#[test]
fn test_bloom_reserve_add_exists_info_scaling() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.bf_reserve("bfkey", 0.01, 1000, 2)?;
assert!(db.bf_reserve("bfkey", 0.01, 1000, 2).is_err());
assert!(db.bf_add("bfkey", "item1")?);
assert!(!db.bf_add("bfkey", "item1")?);
assert!(db.bf_exists("bfkey", "item1")?);
assert!(!db.bf_exists("bfkey", "item2")?);
let madd_res = db.bf_madd("bfkey", &["item2", "item3"])?;
assert_eq!(madd_res, vec![true, true]);
let mex_res = db.bf_mexists("bfkey", &["item1", "item2", "item4"])?;
assert_eq!(mex_res, vec![true, true, false]);
let info = db.bf_info("bfkey")?;
assert_eq!(info.n_filters, 1);
assert_eq!(info.size, 3);
assert_eq!(db.bf_card("bfkey")?, 3);
let dir_scale = tempdir()?;
let db_scale = WeDb::open(dir_scale.path())?;
db_scale.bf_reserve("bf_scale", 0.01, 5, 2)?;
for i in 0..15 {
let item = format!("item_{i}");
assert!(db_scale.bf_add("bf_scale", item)?);
}
let scale_info = db_scale.bf_info("bf_scale")?;
assert!(scale_info.n_filters > 1);
assert_eq!(scale_info.size, 15);
for i in 0..15 {
let item = format!("item_{i}");
assert!(db_scale.bf_exists("bf_scale", item)?);
}
let dir_fixed = tempdir()?;
let db_fixed = WeDb::open(dir_fixed.path())?;
db_fixed.bf_reserve("bf_fixed", 0.01, 3, 0)?;
for i in 0..3 {
let item = format!("elem_{i}");
assert!(db_fixed.bf_add("bf_fixed", item)?);
}
assert!(db_fixed.bf_add("bf_fixed", "elem_overflow").is_err());
Ok(())
}
#[test]
fn test_cuckoo_filter_helper_and_meta() -> Void {
let req_buckets = CuckooFilterHelper::calculate_required_buckets(1000, 2)?;
assert!(req_buckets.is_power_of_two());
let hash = CuckooFilterHelper::hash(b"test_item");
let fp = CuckooFilterHelper::generate_fingerprint(hash);
assert_ne!(fp, 0);
let alt_hash = CuckooFilterHelper::get_alt_hash(fp, hash);
let original_hash = CuckooFilterHelper::get_alt_hash(fp, alt_hash);
assert_eq!(hash, original_hash);
let num_buckets = 64u32;
let b1 = (hash % (num_buckets as u64)) as u32;
let b2 = CuckooFilterHelper::get_alt_bucket_index(b1, fp, num_buckets);
let b1_recovered = CuckooFilterHelper::get_alt_bucket_index(b2, fp, num_buckets);
assert_eq!(b1, b1_recovered);
let meta = CuckooChainMeta::new(1024, 2, 20, 1, 2048, 1, 0);
assert_eq!(meta.get_total_capacity(), 1024);
assert!(meta.is_scaling());
let encoded = meta.encode();
assert_eq!(encoded.len(), CuckooChainMeta::ENCODED_SIZE);
let decoded = CuckooChainMeta::decode(&encoded).expect("cuckoo decode failed");
assert_eq!(decoded.base_capacity, 1024);
assert_eq!(decoded.bucket_size, 2);
assert_eq!(decoded.max_iterations, 20);
assert_eq!(decoded.expansion, 1);
assert_eq!(decoded.page_size, 2048);
Ok(())
}
#[test]
fn test_cuckoo_filter_crud_and_kickout() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.cf_reserve("cfkey", 1000, 2, 20, 1)?;
assert!(db.cf_reserve("cfkey", 1000, 2, 20, 1).is_err());
assert!(db.cf_add("cfkey", "alpha")?);
assert!(db.cf_add("cfkey", "beta")?);
assert!(db.cf_exists("cfkey", "alpha")?);
assert!(db.cf_exists("cfkey", "beta")?);
assert!(!db.cf_exists("cfkey", "gamma")?);
assert!(!db.cf_addnx("cfkey", "alpha")?);
assert!(db.cf_addnx("cfkey", "gamma")?);
assert!(db.cf_exists("cfkey", "gamma")?);
assert_eq!(db.cf_count("cfkey", "alpha")?, 1);
assert!(db.cf_add("cfkey", "alpha")?);
assert_eq!(db.cf_count("cfkey", "alpha")?, 2);
assert!(db.cf_del("cfkey", "alpha")?);
assert_eq!(db.cf_count("cfkey", "alpha")?, 1);
assert!(db.cf_del("cfkey", "alpha")?);
assert_eq!(db.cf_count("cfkey", "alpha")?, 0);
assert!(!db.cf_exists("cfkey", "alpha")?);
assert!(!db.cf_del("cfkey", "alpha")?);
let mex = db.cf_mexists("cfkey", &["alpha", "beta", "gamma", "delta"])?;
assert_eq!(mex, vec![false, true, true, false]);
let info = db.cf_info("cfkey")?;
assert_eq!(info.bucket_size, 2);
assert_eq!(info.num_filters, 1);
assert_eq!(info.num_items_deleted, 2);
assert_eq!(info.size, 2);
let dir_dense = tempdir()?;
let db_dense = WeDb::open(dir_dense.path())?;
db_dense.cf_reserve("cf_dense", 16, 2, 20, 2)?;
for i in 0..50 {
let item = format!("dense_item_{i}");
assert!(db_dense.cf_add("cf_dense", item)?);
}
for i in 0..50 {
let item = format!("dense_item_{i}");
assert!(db_dense.cf_exists("cf_dense", item)?);
}
let dense_info = db_dense.cf_info("cf_dense")?;
assert!(dense_info.num_filters > 1);
assert_eq!(dense_info.size, 50);
let ins_res = db_dense.cf_insert(
"cf_ins",
&["one", "two"],
&CuckooFilterInsertOptions::default(),
)?;
assert_eq!(ins_res, vec![true, true]);
let nx_res = db_dense.cf_insertnx(
"cf_ins",
&["one", "three"],
&CuckooFilterInsertOptions::default(),
)?;
assert_eq!(nx_res, vec![false, true]);
let bf_ins_res = db.bf_insert(
"bf_custom_ins",
&["x", "y"],
&BloomFilterInsertOptions {
capacity: 500,
error_rate: 0.001,
expansion: 2,
auto_create: true,
},
)?;
assert_eq!(
bf_ins_res,
vec![BloomFilterAddResult::Ok, BloomFilterAddResult::Ok]
);
Ok(())
}
#[test]
fn test_bloom_edge_cases_and_insert_options() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
assert!(db.bf_reserve("err_k", 0.01, 0, 2).is_err());
assert!(db.bf_reserve("err_k", 0.0, 100, 2).is_err());
assert!(db.bf_reserve("err_k", 1.0, 100, 2).is_err());
assert!(db.bf_reserve("err_k", -0.5, 100, 2).is_err());
let empty_items: &[&str] = &[];
let empty_res = db.bf_insert(
"bf_empty",
empty_items,
&BloomFilterInsertOptions::default(),
)?;
assert!(empty_res.is_empty());
assert!(
db.bf_insert(
"bf_nocreate",
&["item1"],
&BloomFilterInsertOptions {
auto_create: false,
..Default::default()
}
)
.is_err()
);
assert_eq!(db.bf_card("non_existent_bf")?, 0);
assert!(!db.bf_exists("non_existent_bf", "foo")?);
let mex = db.bf_mexists("non_existent_bf", &["a", "b"])?;
assert_eq!(mex, vec![false, false]);
assert!(db.bf_info("non_existent_bf").is_err());
Ok(())
}
#[test]
fn test_cuckoo_filter_edge_cases_and_multi_item_batch() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
assert!(db.cf_reserve("cf_err", 1, 2, 20, 1).is_err()); assert!(db.cf_reserve("cf_err", 100, 0, 20, 1).is_err()); assert!(db.cf_reserve("cf_err", 100, 2, 0, 1).is_err()); assert!(db.cf_reserve("cf_err", 100, 2, 20, 40000).is_err());
let empty_items: &[&str] = &[];
let empty_res = db.cf_insert(
"cf_empty",
empty_items,
&CuckooFilterInsertOptions::default(),
)?;
assert!(empty_res.is_empty());
assert!(
db.cf_insert(
"cf_nocreate",
&["item1"],
&CuckooFilterInsertOptions {
auto_create: false,
..Default::default()
}
)
.is_err()
);
assert_eq!(db.cf_count("non_existent_cf", "foo")?, 0);
assert!(!db.cf_del("non_existent_cf", "foo")?);
assert!(!db.cf_exists("non_existent_cf", "foo")?);
let mut items = Vec::new();
for i in 0..100 {
items.push(format!("batch_item_{i}"));
}
let insert_res = db.cf_insert(
"cf_batch",
&items,
&CuckooFilterInsertOptions {
capacity: 8,
bucket_size: 2,
max_iterations: 10,
expansion: 2,
page_size: 2048,
auto_create: true,
nx: false,
},
)?;
assert_eq!(insert_res.len(), 100);
assert!(insert_res.iter().all(|&ok| ok));
for item in &items {
assert!(db.cf_exists("cf_batch", item)?);
}
let info = db.cf_info("cf_batch")?;
assert_eq!(info.size, 100);
assert!(info.num_filters > 1);
db.cf_reserve("cf_fixed", 4, 2, 5, 0)?;
let mut count = 0;
for i in 0..100 {
let it = format!("fixed_{i}");
if db.cf_add("cf_fixed", it).is_ok() {
count += 1;
} else {
break;
}
}
assert!(count > 0 && count < 100);
Ok(())
}
#[test]
fn test_bloom_and_cuckoo_deep_scaling_and_binary_items() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.bf_reserve("bf_bin", 0.01, 100, 2)?;
let bin_items: Vec<Vec<u8>> = vec![
vec![],
vec![0u8],
vec![0u8, 1, 2, 3, 255, 254],
b"normal_text".to_vec(),
vec![b'a'; 1000],
];
for item in &bin_items {
assert!(!db.bf_exists("bf_bin", item)?);
assert!(db.bf_add("bf_bin", item)?);
assert!(db.bf_exists("bf_bin", item)?);
}
for item in &bin_items {
assert!(!db.bf_add("bf_bin", item)?);
}
db.cf_reserve("cf_dup", 64, 4, 20, 2)?;
let dup_target = "same_key_item";
for count in 1..=5 {
assert!(db.cf_add("cf_dup", dup_target)?);
assert_eq!(db.cf_count("cf_dup", dup_target)?, count);
}
for expected_left in (0..5).rev() {
assert!(db.cf_del("cf_dup", dup_target)?);
assert_eq!(db.cf_count("cf_dup", dup_target)?, expected_left as u64);
}
assert!(!db.cf_del("cf_dup", dup_target)?);
assert!(!db.cf_exists("cf_dup", dup_target)?);
db.bf_reserve("bf_deep_scale", 0.01, 10, 1)?; for i in 0..60 {
let it = format!("deep_item_{i}");
assert!(db.bf_add("bf_deep_scale", it)?);
}
let deep_info = db.bf_info("bf_deep_scale")?;
assert!(deep_info.n_filters >= 5);
assert_eq!(deep_info.size, 60);
for i in 0..60 {
let it = format!("deep_item_{i}");
assert!(db.bf_exists("bf_deep_scale", it)?);
}
Ok(())
}