armdb 0.2.0

sharded bitcask key-value storage optimized for NVMe
Documentation
//! Durability test: Db::close flushes all collection write buffers so that a
//! drop + reopen sees every previously-acknowledged write.
#![cfg(all(feature = "typed-tree", feature = "armour", feature = "rapira-codec"))]

use armdb::armour::Db;
use armdb::{CollectionMeta, Config, NoHook, RapiraCodec};
use armour_core::GetType;
use rapira::Rapira;
use tempfile::tempdir;

// ---------------------------------------------------------------------------
// Domain type
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, PartialEq, Rapira, GetType)]
struct FlushItem {
    value: u64,
}

impl CollectionMeta for FlushItem {
    type SelfId = [u8; 8];
    const NAME: &'static str = "db_close_flush_items";
    const VERSION: u16 = 1;
}

fn item_key(id: u64) -> [u8; 8] {
    id.to_be_bytes()
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

/// Db::close must flush collection write buffers before returning.
/// On reopen all 100 previously-written entries must be readable.
#[test]
fn db_close_flushes_collections() {
    let dir = tempdir().unwrap();
    const N: u64 = 100;

    // Phase 1: insert N entries, then call db.close().
    {
        let db = Db::open(dir.path()).unwrap();
        let tree = db
            .open_typed_tree::<FlushItem, RapiraCodec, _>(Config::test(), NoHook, &[])
            .unwrap();
        for i in 0..N {
            tree.put(&item_key(i), FlushItem { value: i }).unwrap();
        }
        // Explicit close flushes collection buffers and persists lengths.
        db.close().expect("db.close() must not fail");
        // Drop here; we've already called close().
    }

    // Phase 2: reopen and verify all entries are present.
    {
        let db = Db::open(dir.path()).unwrap();
        let tree = db
            .open_typed_tree::<FlushItem, RapiraCodec, _>(Config::test(), NoHook, &[])
            .unwrap();
        for i in 0..N {
            let item = tree
                .get(&item_key(i))
                .unwrap_or_else(|| panic!("entry {i} missing after close + reopen"));
            assert_eq!(item.value, i, "entry {i} has wrong value after reopen");
        }
        assert_eq!(tree.len(), N as usize, "collection length mismatch");
    }
}