armdb 0.11.0

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

use armour_core::Typ;

use crate::armour::inspection::ScanCapability;
use crate::codec::InspectionEncoding;
use crate::error::DbResult;
use crate::key::Key;
use armour_core::StorageClass;

pub(crate) type Step = ControlFlow<()>;
pub(crate) type RowFn<'f, K, V> = &'f mut dyn FnMut(&K, &V) -> DbResult<Step>;
pub(crate) type KeyFn<'f, K> = &'f mut dyn FnMut(&K) -> DbResult<Step>;
pub(crate) type FallibleRowFn<'f, K, V> = &'f mut dyn FnMut(DbResult<(&K, &V)>) -> Step;

/// Value ⇄ protocol bytes. Deliberately NOT the storage codec.
pub(crate) trait Wire<V>: Send + Sync + 'static {
    /// This wire's bytes are byte-identical to the stored bytes.
    const PASSTHROUGH: bool = false;

    fn encode_to(&self, value: &V, out: &mut Vec<u8>) -> DbResult<()>;
    fn decode(&self, bytes: &[u8]) -> DbResult<V>;
    fn size(&self, value: &V) -> DbResult<u32>;

    fn encode(&self, value: &V) -> DbResult<Vec<u8>> {
        let mut out = Vec::new();
        self.encode_to(value, &mut out)?;
        Ok(out)
    }

    /// One `apply_batch` item — separate because Zero's live error strings are
    /// operation-specific.
    fn decode_batch_item(&self, bytes: &[u8]) -> DbResult<V> {
        self.decode(bytes)
            .map_err(|_| crate::error::DbError::Client("invalid value bytes in batch"))
    }

    /// Reject a corrupt stored row. Reached only when
    /// `Self::PASSTHROUGH && S::RAW_WIRE_BYTES`.
    fn validate_stored(&self, _raw: &[u8]) -> DbResult<()> {
        Err(crate::error::DbError::Client(
            "wire does not validate stored bytes",
        ))
    }
}

// Needed only as a bound below; `access::mod` owns the crate-visible re-export.
use crate::held_ops::HeldOps;

pub(crate) trait Access: Send + Sync + 'static {
    type Key: Key;
    type Val;
    type Shard<'a>: HeldOps<Key = Self::Key, Val = Self::Val>
    where
        Self: 'a;

    const STORAGE: StorageClass;
    /// Values are stored encoded, so `stored_bytes` can answer a read directly.
    const RAW_WIRE_BYTES: bool = false;

    fn encoding(&self, ty: &Typ) -> InspectionEncoding;
    fn approx_len(&self) -> u64;
    /// `count(exact = true)` on the RPC surface.
    fn rpc_exact_count(&self) -> DbResult<u64>;
    /// `CountMode::Exact` on the Inspector surface.
    fn inspection_exact_count(&self) -> DbResult<u64>;
    fn scan_capability(&self) -> ScanCapability;
    /// Per-shard fetch size input for [`try_scan_range`](Self::try_scan_range):
    /// the Inspection adapter computes `inspection_batch(limit, self.inspection_shard_count())`.
    ///
    /// Default `1` — Tree families walk a single ordered structure and ignore
    /// `batch`, and `inspection_batch` debug-asserts a non-zero shard count.
    fn inspection_shard_count(&self) -> usize {
        1
    }
    fn reversed(&self) -> bool;

    fn with_value<R>(
        &self,
        key: &Self::Key,
        f: impl FnOnce(&Self::Val) -> R,
    ) -> DbResult<Option<R>>;
    fn contains(&self, key: &Self::Key) -> DbResult<bool>;
    fn stored_bytes(&self, _key: &Self::Key) -> DbResult<Option<Vec<u8>>> {
        Ok(None)
    }
    fn entry_len<W: Wire<Self::Val>>(&self, key: &Self::Key, wire: &W) -> DbResult<Option<u32>> {
        self.with_value(key, |v| wire.size(v))?.transpose()
    }

    fn with_first<R>(&self, f: impl FnOnce(&Self::Key, &Self::Val) -> R) -> DbResult<Option<R>>;
    fn with_last<R>(&self, f: impl FnOnce(&Self::Key, &Self::Val) -> R) -> DbResult<Option<R>>;
    /// Lenient bounded scan — the RPC `range` policy.
    fn scan_range(
        &self,
        start: Bound<&Self::Key>,
        end: Bound<&Self::Key>,
        f: RowFn<'_, Self::Key, Self::Val>,
    ) -> DbResult<()>;
    /// Strict bounded scan — the Inspector `ordered_page` policy.
    ///
    /// `batch` is the per-shard fetch size the Map families feed to their
    /// `inspection_range_bounds` iterator; live Inspection derives it from the
    /// page limit and the shard count (`inspection_batch`), so the caller — not
    /// the collection — owns that decision. Tree families iterate a single
    /// ordered structure and ignore it.
    fn try_scan_range(
        &self,
        start: Bound<&Self::Key>,
        end: Bound<&Self::Key>,
        batch: usize,
        f: RowFn<'_, Self::Key, Self::Val>,
    ) -> DbResult<()>;
    fn scan_keys(
        &self,
        start: Bound<&Self::Key>,
        end: Bound<&Self::Key>,
        f: KeyFn<'_, Self::Key>,
    ) -> DbResult<()>;
    fn scan_all(&self, f: FallibleRowFn<'_, Self::Key, Self::Val>);

    /// Rows [`scan_all`](Self::scan_all) would hand to the visitor as `Err`.
    ///
    /// Default `0` without scanning — Typed/Zero values are decoded eagerly and
    /// cannot skip. Families whose `scan_all` can skip corrupt rows override and
    /// count those errors (one extra pass; the schema validator already pays O(N)).
    fn undecodable_rows(&self) -> u64 {
        0
    }

    /// The `(rows, bytes)` budget one `project` call runs under on this
    /// collection — `min(hook budget, config budget)`, already resolved.
    ///
    /// The schema declaration reads this rather than reconstructing the clamp,
    /// so `validate`/`repair` cannot evaluate a different contract than writes.
    /// Reading it from the *opened* collection is what makes that guarantee
    /// hold: the budget comes from the outermost hook actually installed, so a
    /// user hook wrapping the carrier that overrides `projection_limits` (or
    /// forgets to forward it) moves both paths together.
    ///
    /// `None` for families that cannot host a projecting hook.
    fn effective_projection_limits(&self) -> Option<(usize, usize)> {
        None
    }

    fn put(&self, key: &Self::Key, value: Self::Val) -> DbResult<()>;
    fn put_ref(&self, key: &Self::Key, value: &Self::Val) -> DbResult<()>;
    fn delete(&self, key: &Self::Key) -> DbResult<()>;
    fn atomic_at<R>(
        &self,
        key: &Self::Key,
        f: impl FnOnce(&mut Self::Shard<'_>) -> DbResult<R>,
    ) -> DbResult<R>;
}