wedb_embed 0.1.2

Embedded database engine providing Redis-like APIs, built on fjall / 嵌入式数据库引擎,提供类似 Redis 的接口,底层基于 fjall 开发
Documentation
use aok::Void;
use tempfile::tempdir;
use wedb_embed::{
  BlockSplitBloomFilter, BloomChainMeta, BloomFilterAddResult, BloomFilterInfo,
  BloomFilterInsertOptions, BloomInfoType, WeDb, prelude::*,
};

#[ctor::ctor(unsafe)]
fn _log_init() {
  log_init::init();
}

#[test]
fn test_block_split_bloom_math_and_filter() -> Void {
  // 验证最优位与字节计算(对标 Kvrocks OptimalNumOfBits/Bytes)
  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);

  // 单块 32 字节测试
  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(), [])?;

  // BF.RESERVE
  db.bf_reserve("bfkey", 0.01, 1000, 2)?;
  assert!(db.bf_reserve("bfkey", 0.01, 1000, 2).is_err());

  // BF.ADD & BF.EXISTS
  assert!(db.bf_add("bfkey", "item1")?);
  assert!(!db.bf_add("bfkey", "item1")?);

  assert!(db.bf_exists("bfkey", "item1")?);
  assert!(!db.bf_exists("bfkey", "item2")?);

  // BF.MADD & BF.MEXISTS
  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]);

  // BF.INFO & BF.CARD
  let info: BloomFilterInfo = db.bf_info("bfkey")?;
  assert_eq!(info.n_filters, 1);
  assert_eq!(info.size, 3);
  assert_eq!(db.bf_card("bfkey")?, 3);

  // BF.INFO by field type
  assert_eq!(db.bf_info_by_type("bfkey", BloomInfoType::Items)?, Some(3));
  assert_eq!(
    db.bf_info_by_type("bfkey", BloomInfoType::Filters)?,
    Some(1)
  );
  assert_eq!(
    db.bf_info_by_type("bfkey", BloomInfoType::Expansion)?,
    Some(2)
  );
  assert!(
    db.bf_info_by_type("bfkey", BloomInfoType::Capacity)?
      .is_some()
  );

  // 自动扩容 (Scaling) 测试
  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)?);
  }

  // 非扩容满过滤器 (Non-scaling full) 测试
  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)?);
  }
  // 超过容量应报错 Full
  assert!(db_fixed.bf_add("bf_fixed", "elem_overflow").is_err());

  // 非扩容模式下的 expansion 查询返回 None
  assert_eq!(
    db_fixed.bf_info_by_type("bf_fixed", BloomInfoType::Expansion)?,
    None
  );

  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());

  // 空 items 测试
  let empty_items: &[&str] = &[];
  let empty_res = db.bf_insert(
    "bf_empty",
    empty_items,
    &BloomFilterInsertOptions::default(),
  )?;
  assert!(empty_res.is_empty());

  // BF.INSERT NOCREATE 对不存在的 key 应报错
  assert!(
    db.bf_insert(
      "bf_nocreate",
      &["item1"],
      &BloomFilterInsertOptions {
        auto_create: false,
        ..Default::default()
      }
    )
    .is_err()
  );

  // BF.CARD 对不存在的 key 返回 0
  assert_eq!(db.bf_card("non_existent_bf")?, 0);

  // BF.EXISTS 对不存在的 key 返回 false
  assert!(!db.bf_exists("non_existent_bf", "foo")?);
  let mex = db.bf_mexists("non_existent_bf", &["a", "b"])?;
  assert_eq!(mex, vec![false, false]);

  // BF.INFO 对不存在的 key 返回 Err
  assert!(db.bf_info("non_existent_bf").is_err());

  // BF.INSERT 自动创建与自定义选项测试
  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]
  );

  // 重复插入应返回 Exist
  let bf_ins_dup = db.bf_insert(
    "bf_custom_ins",
    &["x", "z"],
    &BloomFilterInsertOptions::default(),
  )?;
  assert_eq!(
    bf_ins_dup,
    vec![BloomFilterAddResult::Exist, BloomFilterAddResult::Ok]
  );

  // 自动创建时无效参数报错
  assert!(
    db.bf_insert(
      "bf_inv_cap",
      &["item"],
      &BloomFilterInsertOptions {
        capacity: 0,
        ..Default::default()
      }
    )
    .is_err()
  );
  assert!(
    db.bf_insert(
      "bf_inv_err",
      &["item"],
      &BloomFilterInsertOptions {
        error_rate: 0.0,
        ..Default::default()
      }
    )
    .is_err()
  );

  Ok(())
}

#[test]
fn test_bloom_deep_scaling_and_binary_items() -> Void {
  let dir = tempdir()?;
  let db = WeDb::open(dir.path(), [])?;

  // 1. 测试特殊二进制/空字符串项在 Bloom Filter 中的行为
  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)?);
  }

  // 重复插入二进制项应返回 false
  for item in &bin_items {
    assert!(!db.bf_add("bf_bin", item)?);
  }

  // 2. 多级扩容链深度遍历测试 (Bloom 扩容因子 1: 每次扩展容量相同)
  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(())
}

#[test]
fn test_bloom_comprehensive_info_and_large_batch() -> Void {
  let dir = tempdir()?;
  let db = WeDb::open(dir.path(), [])?;

  // 1. 批量插入 200 个元素
  let items: Vec<String> = (0..200).map(|i| format!("batch_bloom_{i}")).collect();
  let res = db.bf_insert(
    "bf_large_batch",
    &items,
    &BloomFilterInsertOptions {
      capacity: 50,
      error_rate: 0.01,
      expansion: 2,
      auto_create: true,
    },
  )?;
  assert_eq!(res.len(), 200);
  assert!(
    res
      .iter()
      .all(|r| matches!(r, BloomFilterAddResult::Ok | BloomFilterAddResult::Exist))
  );

  // 验证所有元素均存在
  let exists = db.bf_mexists("bf_large_batch", &items)?;
  assert_eq!(exists.len(), 200);
  assert!(exists.iter().all(|&e| e));

  // 验证 info 各个类型
  let info = db.bf_info("bf_large_batch")?;
  assert!(info.size >= 195);
  assert!(info.n_filters >= 3);
  assert_eq!(
    db.bf_info_by_type("bf_large_batch", BloomInfoType::Items)?,
    Some(info.size)
  );
  assert_eq!(
    db.bf_info_by_type("bf_large_batch", BloomInfoType::Filters)?,
    Some(info.n_filters as u64)
  );
  assert_eq!(
    db.bf_info_by_type("bf_large_batch", BloomInfoType::Size)?,
    Some(info.bloom_bytes as u64)
  );
  assert_eq!(
    db.bf_info_by_type("bf_large_batch", BloomInfoType::Capacity)?,
    Some(info.capacity as u64)
  );
  assert_eq!(
    db.bf_info_by_type("bf_large_batch", BloomInfoType::Expansion)?,
    Some(2)
  );
  assert_eq!(
    db.bf_info_by_type("bf_large_batch", BloomInfoType::All)?,
    None
  );

  Ok(())
}

#[test]
fn test_bloom_expired_meta() -> Void {
  let mut meta = BloomChainMeta::new(100, 0.01, 2, 1, 1000, 256);
  assert!(!meta.is_expired(500));
  assert!(meta.is_expired(1500));

  // 过期 meta 返回 None 容量
  meta.base.expire_at = 500;
  assert!(meta.is_expired(600));

  Ok(())
}