armdb 0.14.2

sharded bitcask key-value storage optimized for NVMe
Documentation
use std::ops::ControlFlow;

use armour_core::{GetRefs, GetType, StorageClass};

use crate::access::Access;
use crate::armour::collection::Collection;
use crate::key::Key;
use crate::{CollectionMeta, DbResult};

mod sealed {
    pub trait Sealed {}
}

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

/// 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 + sealed::Sealed {
    type K: Key + GetRefs + GetType + 'static;
    type V: CollectionMeta + GetRefs + 'static;

    /// Physical storage class of this collection.
    const STORAGE: StorageClass;

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V));

    /// Count rows a [`scan`](Self::scan) would silently skip because their stored
    /// value cannot be decoded (or the row cannot be read at all).
    ///
    /// A skipped row's relations are never checked, so without this the validator
    /// would report a clean result over a partially-corrupt collection — the log
    /// line a lossy `scan` emits is typically discarded by CI. Collections whose
    /// `scan` cannot skip anything (values are decoded eagerly, or stored inline)
    /// return `0` without a second pass.
    ///
    /// Families that can skip (VarTyped, LSM) pay a second O(N) pass; the
    /// validator is already an O(N) CI/test tool.
    fn undecodable_rows(&self) -> u64 {
        0
    }

    /// The `(rows, bytes)` budget one `project` call runs under on this open
    /// collection, so `validate`/`repair` evaluate the write path's contract
    /// rather than assuming the crate defaults. `None` — not a projecting family.
    #[doc(hidden)]
    fn effective_projection_limits(&self) -> Option<(usize, usize)> {
        None
    }

    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<S, K, V> sealed::Sealed for S
where
    S: Access<Key = K, Val = V> + Collection,
    K: Key + GetRefs + GetType + 'static,
    V: CollectionMeta + GetRefs + 'static,
{
}

impl<S, K, V> SchemaCollection for S
where
    S: Access<Key = K, Val = V> + Collection,
    K: Key + GetRefs + GetType + 'static,
    V: CollectionMeta + GetRefs + 'static,
{
    type K = K;
    type V = V;
    const STORAGE: StorageClass = S::STORAGE;

    fn scan(&self, f: &mut dyn FnMut(&Self::K, &Self::V)) {
        Access::scan_all(self, &mut |row| {
            if let Ok((k, v)) = row {
                f(k, v);
            }
            ControlFlow::Continue(())
        });
    }

    fn undecodable_rows(&self) -> u64 {
        Access::undecodable_rows(self)
    }

    fn effective_projection_limits(&self) -> Option<(usize, usize)> {
        Access::effective_projection_limits(self)
    }

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

    fn with_value(&self, key: &Self::K, f: &mut dyn FnMut(&Self::V)) -> bool {
        matches!(Access::with_value(self, key, |v| f(v)), Ok(Some(())))
    }
}

impl<S, K, V> IndexTarget for S
where
    S: Access<Key = K, Val = V> + Collection,
    K: Key + GetRefs + GetType + 'static,
    V: CollectionMeta + GetRefs + 'static,
{
    fn put_entry(&self, k: &Self::K, v: &Self::V) -> DbResult<()> {
        Access::put_ref(self, k, v)
    }

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