armdb 0.3.1

sharded bitcask key-value storage optimized for NVMe
Documentation
use std::hash::Hash;

use armour_core::{Fuid, GetRefs, Id64, IdBrand};

use crate::armour::collection::Collection;
use crate::key::Key;
use crate::{
    Codec, CollectionMeta, DbResult, TypedMap, TypedTree, TypedWriteHook, ZeroMap, ZeroTree,
};

/// Write/delete in an index collection from a generated hook.
pub trait IndexTarget: SchemaCollection {
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()>;
    fn delete_entry(&self, k: &Self::K) -> DbResult<()>;
}

/// Key that is a branded ID: round-trips through u64.
pub trait BrandedKey: Key {
    const BRAND: &'static str;
    fn from_id(id: u64) -> Self;
    fn id_value(&self) -> u64;
}

impl<H: IdBrand> BrandedKey for Fuid<H>
where
    Fuid<H>: Key,
{
    const BRAND: &'static str = H::BRAND;
    fn from_id(id: u64) -> Self {
        Fuid::from_be_bytes(id.to_be_bytes())
    }
    fn id_value(&self) -> u64 {
        self.get()
    }
}

impl<T: IdBrand> BrandedKey for Id64<T>
where
    Id64<T>: Key,
{
    const BRAND: &'static str = T::BRAND;
    fn from_id(id: u64) -> Self {
        Id64::from_be_bytes(id.to_be_bytes())
    }
    fn id_value(&self) -> u64 {
        self.get()
    }
}

/// Typed collection access for the schema registry and validator.
/// Each impl knows how to scan and check key presence.
pub trait SchemaCollection: Collection + Send + Sync + 'static {
    type K: Key + GetRefs + 'static;
    type V: CollectionMeta + GetRefs + 'static;

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V));
    fn contains_key(&self, key: &Self::K) -> bool;
    /// false — no record; true — record exists and `f` was called with the value.
    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool;
}

impl<T, C, H> SchemaCollection for TypedTree<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + Ord + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    type K = T::SelfId;
    type V = T;

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
        for (k, v) in self.iter() {
            f(&k, v);
        }
    }

    fn contains_key(&self, key: &Self::K) -> bool {
        self.get(key).is_some()
    }

    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
        match self.get(key) {
            Some(v) => {
                f(&*v);
                true
            }
            None => false,
        }
    }
}

impl<T, C, H> IndexTarget for TypedTree<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + Ord + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
        self.put(k, v.clone()).map(drop)
    }

    fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
        self.delete(k).map(drop)
    }
}

impl<T, C, H> SchemaCollection for TypedMap<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + Hash + Eq + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    type K = T::SelfId;
    type V = T;

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
        self.for_each(|k, v| f(k, v));
    }

    fn contains_key(&self, key: &Self::K) -> bool {
        self.contains(key)
    }

    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
        match self.get(key) {
            Some(v) => {
                f(&*v);
                true
            }
            None => false,
        }
    }
}

impl<T, C, H> IndexTarget for TypedMap<T::SelfId, T, C, H>
where
    T: CollectionMeta + GetRefs + Clone + Send + Sync + 'static,
    T::SelfId: Key + GetRefs + Hash + Eq + Send + Sync + 'static,
    C: Codec<T> + Default + Send + Sync + 'static,
    H: TypedWriteHook<T::SelfId, T> + 'static,
{
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
        self.put(k, v.clone()).map(drop)
    }

    fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
        self.delete(k).map(drop)
    }
}

/// `SchemaCollection`/`IndexTarget` for Zero collections are identical across
/// durability backends, so the impl pair is stamped per backend by a macro.
/// (A single `D: Durability` impl is avoided on purpose: method impl blocks in
/// zero_tree.rs/zero_map.rs are split per backend, and the macro expands to
/// exactly the code that those blocks provide.)
macro_rules! zero_tree_schema_impls {
    ($dur:ty) => {
        impl<T, const V: usize, H> SchemaCollection for ZeroTree<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + Ord + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            type K = T::SelfId;
            type V = T;

            fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
                for (k, v) in self.iter() {
                    f(&k, &v);
                }
            }

            fn contains_key(&self, key: &Self::K) -> bool {
                self.contains(key)
            }

            fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
                match self.get(key) {
                    Some(v) => {
                        f(&v);
                        true
                    }
                    None => false,
                }
            }
        }

        impl<T, const V: usize, H> IndexTarget for ZeroTree<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + Ord + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
                self.put(k, v).map(drop)
            }

            fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
                self.delete(k).map(drop)
            }
        }
    };
}

zero_tree_schema_impls!(crate::durability::Bitcask);
zero_tree_schema_impls!(crate::durability::Fixed);

macro_rules! zero_map_schema_impls {
    ($dur:ty) => {
        impl<T, const V: usize, H> SchemaCollection for ZeroMap<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + Hash + Eq + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            type K = T::SelfId;
            type V = T;

            fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
                self.for_each(|k, v| f(k, v));
            }

            fn contains_key(&self, key: &Self::K) -> bool {
                self.contains(key)
            }

            fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
                match self.get(key) {
                    Some(v) => {
                        f(&v);
                        true
                    }
                    None => false,
                }
            }
        }

        impl<T, const V: usize, H> IndexTarget for ZeroMap<T::SelfId, V, T, H, $dur>
        where
            T: CollectionMeta
                + GetRefs
                + Copy
                + zerocopy::IntoBytes
                + zerocopy::FromBytes
                + zerocopy::Immutable
                + Send
                + Sync
                + 'static,
            T::SelfId: Key + GetRefs + Hash + Eq + Send + Sync + 'static,
            H: TypedWriteHook<T::SelfId, T> + 'static,
        {
            fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
                self.put(k, v).map(drop)
            }

            fn delete_entry(&self, k: &Self::K) -> DbResult<()> {
                self.delete(k).map(drop)
            }
        }
    };
}

zero_map_schema_impls!(crate::durability::Bitcask);
zero_map_schema_impls!(crate::durability::Fixed);