wedb_embed 0.1.0

Embedded Kvrocks-compatible storage engine for WeDb
Documentation
use aok::Void;
use tempfile::tempdir;
use wedb_embed::{WeDb, parse_range_spec};

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

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

    assert_eq!(db.si_add("sikey", &[10, 20, 30, 40, 50])?, 5);
    assert_eq!(db.si_add("sikey", &[20, 60])?, 1);
    assert_eq!(db.si_card("sikey")?, 6);

    assert!(db.si_exists("sikey", 20)?);
    assert!(!db.si_exists("sikey", 99)?);

    let mexist = db.si_mexist("sikey", &[10, 25, 30, 99])?;
    assert_eq!(mexist, vec![true, false, true, false]);

    let range = db.si_range("sikey", 0, 0, 10, false)?;
    assert_eq!(range, vec![10, 20, 30, 40, 50, 60]);

    let page = db.si_range("sikey", 0, 2, 2, false)?;
    assert_eq!(page, vec![30, 40]);

    let cursor_range = db.si_range("sikey", 30, 0, 2, false)?;
    assert_eq!(cursor_range, vec![40, 50]);

    let rev_range = db.si_rev_range("sikey", 0, 0, 10)?;
    assert_eq!(rev_range, vec![60, 50, 40, 30, 20, 10]);

    let rev_cursor = db.si_rev_range("sikey", 40, 0, 2)?;
    assert_eq!(rev_cursor, vec![30, 20]);

    assert_eq!(db.si_rem("sikey", &[20, 40, 99])?, 2);
    assert_eq!(db.si_card("sikey")?, 4);

    Ok(())
}

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

    db.si_add("sikey", &[10, 20, 30, 40, 50, 60, 70, 80])?;

    // [20, 60]
    let spec1 = parse_range_spec("20", "60")?;
    let r1 = db.si_range_by_value("sikey", &spec1)?;
    assert_eq!(r1, vec![20, 30, 40, 50, 60]);

    // (20, (60)
    let spec2 = parse_range_spec("(20", "(60")?;
    let r2 = db.si_range_by_value("sikey", &spec2)?;
    assert_eq!(r2, vec![30, 40, 50]);

    // -inf, +inf with offset & count
    let mut spec3 = parse_range_spec("-inf", "+inf")?;
    spec3.offset = 2;
    spec3.count = Some(3);
    let r3 = db.si_range_by_value("sikey", &spec3)?;
    assert_eq!(r3, vec![30, 40, 50]);

    // Reversed range by value
    let mut spec_rev = parse_range_spec("20", "60")?;
    spec_rev.reversed = true;
    let r_rev = db.si_range_by_value("sikey", &spec_rev)?;
    assert_eq!(r_rev, vec![60, 50, 40, 30, 20]);

    // Aliases test (range_by_score)
    let r_score = db.si_range_by_score("sikey", &spec1)?;
    assert_eq!(r_score, r1);
    let r_rev_score = db.si_rev_range_by_score("sikey", &spec_rev)?;
    assert_eq!(r_rev_score, r_rev);

    Ok(())
}

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

    db.si_add("rank_k", &[10, 20, 30, 40, 50])?;

    assert_eq!(db.si_rank("rank_k", 10)?, Some(0));
    assert_eq!(db.si_rank("rank_k", 30)?, Some(2));
    assert_eq!(db.si_rank("rank_k", 50)?, Some(4));
    assert_eq!(db.si_rank("rank_k", 25)?, None);
    assert_eq!(db.si_rank("rank_k", 99)?, None);

    assert_eq!(db.si_revrank("rank_k", 50)?, Some(0));
    assert_eq!(db.si_revrank("rank_k", 30)?, Some(2));
    assert_eq!(db.si_revrank("rank_k", 10)?, Some(4));
    assert_eq!(db.si_revrank("rank_k", 99)?, None);

    let spec = parse_range_spec("20", "40")?;
    assert_eq!(db.si_count("rank_k", &spec)?, 3);

    let spec_ex = parse_range_spec("(20", "(40")?;
    assert_eq!(db.si_count("rank_k", &spec_ex)?, 1);

    Ok(())
}

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

    db.si_add("rem_k", &[10, 20, 30, 40, 50, 60, 70, 80])?;

    // Remove by value (20, 50] -> 30, 40, 50 removed (3 items)
    let spec = parse_range_spec("(20", "50")?;
    assert_eq!(db.si_rem_range_by_value("rem_k", &spec)?, 3);
    assert_eq!(db.si_card("rem_k")?, 5);
    assert_eq!(
        db.si_range("rem_k", 0, 0, 10, false)?,
        vec![10, 20, 60, 70, 80]
    );

    // Remove by rank 0, 1 -> 10, 20 removed (2 items)
    assert_eq!(db.si_rem_range_by_rank("rem_k", 0, 1)?, 2);
    assert_eq!(db.si_card("rem_k")?, 3);
    assert_eq!(db.si_range("rem_k", 0, 0, 10, false)?, vec![60, 70, 80]);

    // Remove by negative rank -1, -1 -> 80 removed (1 item)
    assert_eq!(db.si_rem_range_by_rank("rem_k", -1, -1)?, 1);
    assert_eq!(db.si_card("rem_k")?, 2);
    assert_eq!(db.si_range("rem_k", 0, 0, 10, false)?, vec![60, 70]);

    // Remove remaining elements: full cleanup check
    assert_eq!(db.si_rem_range_by_rank("rem_k", 0, -1)?, 2);
    assert_eq!(db.si_card("rem_k")?, 0);
    assert!(!db.si_exists("rem_k", 60)?);
    assert_eq!(db.si_mexist("rem_k", &[60, 70])?, vec![false, false]);

    Ok(())
}

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

    // Duplicates within the same add call
    assert_eq!(db.si_add("dup_k", &[100, 100, 200, 100, 300, 200])?, 3);
    assert_eq!(db.si_card("dup_k")?, 3);

    // Duplicates within the same rem call
    assert_eq!(db.si_rem("dup_k", &[100, 100, 200, 100])?, 2);
    assert_eq!(db.si_card("dup_k")?, 1);
    assert_eq!(db.si_card("dup_k")?, 1);

    // Large 64-bit integers & boundary ordering
    let large_ids = [
        0u64,
        1,
        1000,
        1 << 32,
        1 << 60,
        (1 << 63) - 1,
        1 << 63,
        u64::MAX,
    ];
    assert_eq!(db.si_add("large_k", &large_ids)?, 8);
    assert_eq!(db.si_card("large_k")?, 8);

    let range_all = db.si_range("large_k", 0, 0, 100, false)?;
    assert_eq!(range_all, large_ids.to_vec());

    let rev_all = db.si_rev_range("large_k", 0, 0, 100)?;
    let mut expected_rev = large_ids.to_vec();
    expected_rev.reverse();
    assert_eq!(rev_all, expected_rev);

    // Non-existent key operations
    assert_eq!(db.si_card("non_exist")?, 0);
    assert_eq!(db.si_mexist("non_exist", &[1, 2])?, vec![false, false]);
    assert_eq!(
        db.si_range("non_exist", 0, 0, 10, false)?,
        Vec::<u64>::new()
    );
    assert_eq!(db.si_rem("non_exist", &[1, 2])?, 0);
    assert_eq!(db.si_rank("non_exist", 10)?, None);
    assert_eq!(db.si_revrank("non_exist", 10)?, None);
    assert_eq!(db.si_rem_range_by_rank("non_exist", 0, 10)?, 0);

    Ok(())
}

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

    // 1. limit = 0 and count = 0 edge conditions
    db.si_add("edge_k", &[10, 20, 30, 40, 50])?;
    assert_eq!(db.si_range("edge_k", 0, 0, 0, false)?, Vec::<u64>::new());
    assert_eq!(db.si_rev_range("edge_k", 0, 0, 0)?, Vec::<u64>::new());

    let mut spec_count_0 = parse_range_spec("10", "50")?;
    spec_count_0.count = Some(0);
    assert_eq!(
        db.si_range_by_value("edge_k", &spec_count_0)?,
        Vec::<u64>::new()
    );
    assert_eq!(
        db.si_rev_range_by_value("edge_k", &spec_count_0)?,
        Vec::<u64>::new()
    );

    // 2. Empty range cases (min > max, min == max with exclusive)
    let spec_min_gt_max = parse_range_spec("100", "50")?;
    assert_eq!(
        db.si_range_by_value("edge_k", &spec_min_gt_max)?,
        Vec::<u64>::new()
    );
    assert_eq!(
        db.si_rev_range_by_value("edge_k", &spec_min_gt_max)?,
        Vec::<u64>::new()
    );

    let spec_ex_eq = parse_range_spec("(30", "30")?;
    assert_eq!(
        db.si_range_by_value("edge_k", &spec_ex_eq)?,
        Vec::<u64>::new()
    );
    let spec_eq_ex = parse_range_spec("30", "(30")?;
    assert_eq!(
        db.si_range_by_value("edge_k", &spec_eq_ex)?,
        Vec::<u64>::new()
    );

    // 3. Sliding window reverse pagination tests with various offset & limit combinations
    // edge_k has [10, 20, 30, 40, 50]
    // Reversed full is [50, 40, 30, 20, 10]
    assert_eq!(db.si_rev_range("edge_k", 0, 0, 2)?, vec![50, 40]);
    assert_eq!(db.si_rev_range("edge_k", 0, 2, 2)?, vec![30, 20]);
    assert_eq!(db.si_rev_range("edge_k", 0, 4, 2)?, vec![10]);
    assert_eq!(db.si_rev_range("edge_k", 0, 10, 2)?, Vec::<u64>::new());

    // Reversed range by value with offset & count
    let mut spec_rev_page = parse_range_spec("20", "50")?;
    spec_rev_page.reversed = true;
    spec_rev_page.offset = 1;
    spec_rev_page.count = Some(2);
    // In range [20, 50] -> 20, 30, 40, 50. Reversed -> 50, 40, 30, 20. Skip 1 -> 40, 30. Take 2 -> [40, 30]
    assert_eq!(
        db.si_range_by_value("edge_k", &spec_rev_page)?,
        vec![40, 30]
    );

    // 4. si_rem_range_by_score alias test
    let spec_rem = parse_range_spec("20", "40")?;
    assert_eq!(db.si_rem_range_by_score("edge_k", &spec_rem)?, 3); // removes 20, 30, 40
    assert_eq!(db.si_card("edge_k")?, 2); // 10, 50 remain
    assert_eq!(db.si_range("edge_k", 0, 0, 10, false)?, vec![10, 50]);

    // 5. si_clear full cleanup test
    assert_eq!(db.si_clear("edge_k")?, 2);
    assert_eq!(db.si_card("edge_k")?, 0);
    assert!(!db.si_exists("edge_k", 10)?);
    assert!(!db.si_exists("edge_k", 50)?);
    assert_eq!(db.si_range("edge_k", 0, 0, 10, false)?, Vec::<u64>::new());
    assert_eq!(db.si_clear("edge_k")?, 0);

    Ok(())
}

#[test]
fn test_sortedint_advanced_features_and_builder() -> Void {
    use wedb_embed::SortedintRangeSpec;

    let dir = tempdir()?;
    let db = WeDb::open(dir.path())?;

    db.si_add("adv_k", &[10, 20, 30, 40, 50, 60, 70])?;

    // Builder test
    let spec_builder = SortedintRangeSpec::all().with_offset(2).with_count(3);
    assert_eq!(
        db.si_range_by_value("adv_k", &spec_builder)?,
        vec![30, 40, 50]
    );

    let spec_builder_rev = SortedintRangeSpec::all()
        .with_offset(1)
        .with_count(2)
        .with_reversed(true);
    assert_eq!(
        db.si_range_by_value("adv_k", &spec_builder_rev)?,
        vec![60, 50]
    );

    // Reverse without count (unlimited)
    let mut spec_rev_unlimited = SortedintRangeSpec::all().with_reversed(true);
    spec_rev_unlimited.offset = 3;
    assert_eq!(
        db.si_range_by_value("adv_k", &spec_rev_unlimited)?,
        vec![40, 30, 20, 10]
    );

    // Empty clear test
    assert_eq!(db.si_clear("non_existent_key")?, 0);

    Ok(())
}