armdb 0.1.13

sharded bitcask key-value storage optimized for NVMe
Documentation
#[cfg(feature = "var-collections")]
use armdb::VarTree;
use armdb::{Config, ConstTree};
use tempfile::tempdir;

// ---------------------------------------------------------------------------
// ConstTree tests
// ---------------------------------------------------------------------------

#[test]
fn test_const_put_get() {
    let dir = tempdir().unwrap();
    let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();

    for i in 0..10u64 {
        let key = i.to_be_bytes();
        let value = (i * 100).to_be_bytes();
        tree.put(&key, &value).unwrap();
    }

    for i in 0..10u64 {
        let key = i.to_be_bytes();
        let expected = (i * 100).to_be_bytes();
        let got = tree.get(&key).expect("key should exist");
        assert_eq!(got, expected, "mismatch at key {i}");
    }
}

#[test]
fn test_const_overwrite() {
    let dir = tempdir().unwrap();
    let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();

    let key = 1u64.to_be_bytes();
    let value_a = 100u64.to_be_bytes();
    let value_b = 200u64.to_be_bytes();

    tree.put(&key, &value_a).unwrap();
    assert_eq!(tree.get(&key).unwrap(), value_a);

    tree.put(&key, &value_b).unwrap();
    assert_eq!(tree.get(&key).unwrap(), value_b);
}

#[test]
fn test_const_delete() {
    let dir = tempdir().unwrap();
    let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();

    let key = 42u64.to_be_bytes();
    let value = 99u64.to_be_bytes();

    tree.put(&key, &value).unwrap();
    assert!(tree.get(&key).is_some());

    let deleted = tree.delete(&key).unwrap();
    assert!(
        deleted.is_some(),
        "delete should return Some for existing key"
    );
    assert_eq!(deleted.unwrap(), value);
    assert!(tree.get(&key).is_none(), "get after delete should be None");
}

#[test]
fn test_const_delete_missing() {
    let dir = tempdir().unwrap();
    let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();

    let key = 999u64.to_be_bytes();
    let deleted = tree.delete(&key).unwrap();
    assert!(
        deleted.is_none(),
        "delete of missing key should return None"
    );
}

#[test]
fn test_const_contains() {
    let dir = tempdir().unwrap();
    let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();

    let key = 7u64.to_be_bytes();
    let value = 77u64.to_be_bytes();

    assert!(!tree.contains(&key), "should not contain before put");

    tree.put(&key, &value).unwrap();
    assert!(tree.contains(&key), "should contain after put");

    tree.delete(&key).unwrap();
    assert!(!tree.contains(&key), "should not contain after delete");
}

#[test]
fn test_const_prefix_iter() {
    let dir = tempdir().unwrap();
    let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();

    // Keys with prefix 0x00: [0x00, 0x01, 0,0,0,0,0,0] and [0x00, 0x02, 0,0,0,0,0,0]
    let key1: [u8; 8] = [0x00, 0x01, 0, 0, 0, 0, 0, 0];
    let key2: [u8; 8] = [0x00, 0x02, 0, 0, 0, 0, 0, 0];
    // Key with prefix 0x01: [0x01, 0x01, 0,0,0,0,0,0]
    let key3: [u8; 8] = [0x01, 0x01, 0, 0, 0, 0, 0, 0];

    let val1 = 1u64.to_be_bytes();
    let val2 = 2u64.to_be_bytes();
    let val3 = 3u64.to_be_bytes();

    tree.put(&key1, &val1).unwrap();
    tree.put(&key2, &val2).unwrap();
    tree.put(&key3, &val3).unwrap();

    let entries = tree.prefix_iter(&[0x00]).collect_vec();
    assert_eq!(entries.len(), 2, "prefix 0x00 should match 2 entries");

    // Verify the matched keys are the expected ones
    let keys: Vec<[u8; 8]> = entries.iter().map(|(k, _)| *k).collect();
    assert!(keys.contains(&key1));
    assert!(keys.contains(&key2));

    let entries_01 = tree.prefix_iter(&[0x01]).collect_vec();
    assert_eq!(entries_01.len(), 1, "prefix 0x01 should match 1 entry");
    assert_eq!(entries_01[0].0, key3);
}

#[test]
fn test_const_len() {
    let dir = tempdir().unwrap();
    let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();

    assert_eq!(tree.len(), 0);
    assert!(tree.is_empty());

    // Insert 5 distinct keys
    for i in 0..5u64 {
        let key = i.to_be_bytes();
        let value = i.to_be_bytes();
        tree.put(&key, &value).unwrap();
    }
    assert_eq!(tree.len(), 5);
    assert!(!tree.is_empty());

    // Overwrite an existing key — len should stay 5
    let key = 2u64.to_be_bytes();
    let new_value = 222u64.to_be_bytes();
    tree.put(&key, &new_value).unwrap();
    assert_eq!(tree.len(), 5, "overwrite should not change len");

    // Delete one key — len should be 4
    tree.delete(&0u64.to_be_bytes()).unwrap();
    assert_eq!(tree.len(), 4);
}

// ---------------------------------------------------------------------------
// VarTree tests
// ---------------------------------------------------------------------------

#[cfg(feature = "var-collections")]
#[test]
fn test_var_put_get() {
    let dir = tempdir().unwrap();
    let tree = VarTree::<[u8; 8]>::open(dir.path(), Config::test()).unwrap();

    for i in 0..10u64 {
        let key = i.to_be_bytes();
        // Variable-length values: repeat the byte i+1 times
        let value: Vec<u8> = vec![i as u8; (i as usize) + 1];
        tree.put(&key, &value).unwrap();
    }

    for i in 0..10u64 {
        let key = i.to_be_bytes();
        let expected: Vec<u8> = vec![i as u8; (i as usize) + 1];
        let got = tree.get(&key).expect("key should exist");
        assert_eq!(got.as_bytes(), expected.as_slice(), "mismatch at key {i}");
    }
}

#[cfg(feature = "var-collections")]
#[test]
fn test_var_overwrite() {
    let dir = tempdir().unwrap();
    let tree = VarTree::<[u8; 8]>::open(dir.path(), Config::test()).unwrap();

    let key = 1u64.to_be_bytes();
    let value_a = b"hello";
    let value_b = b"world!!!";

    tree.put(&key, value_a).unwrap();
    assert_eq!(tree.get(&key).unwrap().as_bytes(), value_a);

    tree.put(&key, value_b).unwrap();
    assert_eq!(tree.get(&key).unwrap().as_bytes(), value_b);
}

#[cfg(feature = "var-collections")]
#[test]
fn test_var_delete() {
    let dir = tempdir().unwrap();
    let tree = VarTree::<[u8; 8]>::open(dir.path(), Config::test()).unwrap();

    let key = 42u64.to_be_bytes();
    let value = b"to be deleted";

    tree.put(&key, value).unwrap();
    assert!(tree.get(&key).is_some());

    let deleted = tree.delete(&key).unwrap();
    assert!(deleted, "delete should return true for existing key");
    assert!(tree.get(&key).is_none(), "get after delete should be None");
}

#[cfg(feature = "var-collections")]
#[test]
fn test_var_contains() {
    let dir = tempdir().unwrap();
    let tree = VarTree::<[u8; 8]>::open(dir.path(), Config::test()).unwrap();

    let key = 7u64.to_be_bytes();
    let value = b"present";

    assert!(!tree.contains(&key), "should not contain before put");

    tree.put(&key, value).unwrap();
    assert!(tree.contains(&key), "should contain after put");

    tree.delete(&key).unwrap();
    assert!(!tree.contains(&key), "should not contain after delete");
}

#[cfg(feature = "var-collections")]
#[test]
fn test_var_prefix_iter() {
    let dir = tempdir().unwrap();
    let tree = VarTree::<[u8; 8]>::open(dir.path(), Config::test()).unwrap();

    // Keys with prefix 0x00
    let key1: [u8; 8] = [0x00, 0x01, 0, 0, 0, 0, 0, 0];
    let key2: [u8; 8] = [0x00, 0x02, 0, 0, 0, 0, 0, 0];
    // Key with prefix 0x01
    let key3: [u8; 8] = [0x01, 0x01, 0, 0, 0, 0, 0, 0];

    tree.put(&key1, b"val1").unwrap();
    tree.put(&key2, b"val2").unwrap();
    tree.put(&key3, b"val3").unwrap();

    // collect_keys
    let keys = tree.prefix_iter(&[0x00]).collect_keys();
    assert_eq!(keys.len(), 2, "prefix 0x00 should match 2 keys");
    assert!(keys.contains(&key1));
    assert!(keys.contains(&key2));

    // collect_entries
    let entries = tree.prefix_iter(&[0x00]).collect_entries();
    assert_eq!(entries.len(), 2, "prefix 0x00 should match 2 entries");

    let entry_keys: Vec<[u8; 8]> = entries.iter().map(|(k, _)| *k).collect();
    assert!(entry_keys.contains(&key1));
    assert!(entry_keys.contains(&key2));

    // Verify values are correct
    for (k, v) in &entries {
        if *k == key1 {
            assert_eq!(v.as_bytes(), b"val1");
        } else if *k == key2 {
            assert_eq!(v.as_bytes(), b"val2");
        }
    }

    // prefix 0x01 should match 1 key
    let keys_01 = tree.prefix_iter(&[0x01]).collect_keys();
    assert_eq!(keys_01.len(), 1);
    assert_eq!(keys_01[0], key3);
}

#[cfg(feature = "var-collections")]
#[test]
fn test_var_large_value() {
    let dir = tempdir().unwrap();
    let tree = VarTree::<[u8; 8]>::open(dir.path(), Config::test()).unwrap();

    let key = 1u64.to_be_bytes();
    // 1000 bytes — exceeds ByteView inline threshold (20 bytes), takes heap path
    let value: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();

    tree.put(&key, &value).unwrap();

    let got = tree.get(&key).expect("large value should be retrievable");
    assert_eq!(got.len(), 1000);
    assert_eq!(got.as_bytes(), value.as_slice());
}

#[cfg(feature = "var-collections")]
#[test]
fn test_var_inline_value() {
    let dir = tempdir().unwrap();
    let tree = VarTree::<[u8; 8]>::open(dir.path(), Config::test()).unwrap();

    let key = 2u64.to_be_bytes();
    // 10 bytes — within ByteView inline threshold (20 bytes)
    let value: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    tree.put(&key, &value).unwrap();

    let got = tree.get(&key).expect("inline value should be retrievable");
    assert_eq!(got.len(), 10);
    assert_eq!(got.as_bytes(), &value);
}