armdb 0.11.2

sharded bitcask key-value storage optimized for NVMe
Documentation
//! [`UniqueIndex`], the opaque wrapper over a FixedStore [`ZeroMap`] that
//! backs a unique namespace, and the [`unique_index!`](crate::unique_index)
//! declaration macro.
//!
//! Deliberately **not** a [`crate::ProjectableIndex`]: that trait's
//! `put_bytes`/`delete_bytes` are public, and implementing it here would hand
//! out the write path this type exists to withhold. `UniqueIndex` has no
//! public write method of any kind, no `Deref` to the inner `ZeroMap`, and no
//! accessor that returns it — reads only from the outside. Claim enforcement
//! reaches it through the crate-private [`ClaimTarget`] seam instead
//! (`Projected::and_unique` declares, `db.tx()`'s `resolve_claims` writes).

use std::hash::Hash;
use std::sync::Arc;

use crate::error::{DbError, DbResult};
use crate::{CollectionMeta, Key, NoHook, TtlHook, ZeroMap};

use super::ClaimTarget;
use super::row::{UniqueOwner, UniqueRow};

/// Implemented only by [`unique_index!`](crate::unique_index). The bound that
/// keeps [`crate::armour::Db::open_unique_index`] from being aimed at an
/// ordinary collection.
pub trait UniqueNamespace:
    CollectionMeta
    + Copy
    + zerocopy::FromBytes
    + zerocopy::IntoBytes
    + zerocopy::Immutable
    + Send
    + Sync
    + 'static
{
    fn from_row(row: UniqueRow) -> Self;
    fn row(&self) -> UniqueRow;
}

pub(crate) type Inner<N> = ZeroMap<
    <N as CollectionMeta>::SelfId,
    { size_of::<UniqueRow>() },
    N,
    TtlHook<N, NoHook>,
    crate::durability::Fixed,
>;

/// A unique namespace: `key -> owner`, engine-owned rows, **no public write
/// API**. See the module docs for why this is not a [`crate::ProjectableIndex`].
pub struct UniqueIndex<N>
where
    N: UniqueNamespace,
    N::SelfId: Key + Send + Sync + Hash + Eq,
{
    inner: Arc<Inner<N>>,
}

impl<N> UniqueIndex<N>
where
    N: UniqueNamespace,
    N::SelfId: Key + Send + Sync + Hash + Eq,
{
    /// Wrap an already-open handle. `pub(crate)`: only
    /// [`crate::armour::Db::open_unique_index`] constructs a `UniqueIndex` —
    /// there is no path from outside the crate to an inner `ZeroMap` handle
    /// of the right shape.
    pub(crate) fn from_inner(inner: Arc<Inner<N>>) -> Self {
        Self { inner }
    }

    pub fn name(&self) -> &'static str {
        N::NAME
    }

    /// The current owner of `key`, or `None` when unclaimed. Also `None` for
    /// a row whose `key_len` cannot address its own buffer (corrupt data) —
    /// decoding it further would be unsound, so it reads the same as absent.
    pub fn owner_of(&self, key: &N::SelfId) -> Option<UniqueOwner> {
        let row = self.inner.get(key)?.row();
        row.is_well_formed().then(|| row.owner())
    }

    pub fn contains(&self, key: &N::SelfId) -> bool {
        self.inner.contains(key)
    }
}

/// The write seam — crate-private, so this impl grants no capability outside
/// the crate: `UniqueIndex` itself still has no public write method. Lives
/// here (not in `mod.rs`, where [`ClaimTarget`] is declared) because it needs
/// the private `inner` field, which is only in scope within this module.
impl<N> ClaimTarget for UniqueIndex<N>
where
    N: UniqueNamespace,
    N::SelfId: Key + Send + Sync + Hash + Eq,
{
    fn collection_name(&self) -> &'static str {
        N::NAME
    }

    fn write_row(&self, key: &[u8], row: UniqueRow) -> DbResult<()> {
        if key.len() != size_of::<N::SelfId>() {
            return Err(DbError::CorruptedEntry { offset: 0 });
        }
        let k = <N::SelfId as Key>::from_bytes(key);
        self.inner.put(&k, &N::from_row(row))?;
        Ok(())
    }

    fn delete_row(&self, key: &[u8]) -> DbResult<()> {
        if key.len() != size_of::<N::SelfId>() {
            return Err(DbError::CorruptedEntry { offset: 0 });
        }
        let k = <N::SelfId as Key>::from_bytes(key);
        self.inner.delete(&k)?;
        Ok(())
    }

    fn scan_rows(&self, f: &mut dyn FnMut(&[u8], UniqueRow) -> DbResult<()>) -> DbResult<()> {
        let mut err: Option<DbError> = None;
        self.inner.for_each(|k, v| {
            if err.is_some() {
                return;
            }
            if let Err(e) = f(k.as_bytes(), v.row()) {
                err = Some(e);
            }
        });
        match err {
            Some(e) => Err(e),
            None => Ok(()),
        }
    }

    fn as_tx_participant(&self) -> Arc<dyn crate::txn::participant::TxParticipant> {
        Arc::clone(&self.inner) as Arc<dyn crate::txn::participant::TxParticipant>
    }
}

/// Declare a unique namespace type: a `#[repr(transparent)]` newtype over
/// [`UniqueRow`] carrying a [`CollectionMeta`] impl (TTL wired to the row's
/// own `expires_at` via [`UniqueRow::deadline`]) and [`UniqueNamespace`].
///
/// ```ignore
/// unique_index!(Usernames, key = UsernameKey, name = "usernames");
/// ```
///
/// Every generated path is routed through `$crate::…` (T10 review F4):
/// unlike [`impl_key_zerocopy!`], which takes an `armour_core` value as an
/// argument and so is only ever invoked where the caller already has
/// `armour_core` in scope, this macro takes none — a downstream crate
/// depending only on `armdb` would otherwise fail to compile on the bare
/// `zerocopy::…`/`armour_core::…` paths the expansion names. `armdb`
/// re-exports both (`$crate::zerocopy`, `$crate::armour_core`) for exactly
/// this.
///
/// The hand-written paths above (`impl $crate::armour_core::GetType for
/// $name { … }` below) resolve hygienically through `$crate` like any other
/// `macro_rules!` substitution. The **derive** paths need a second piece:
/// `zerocopy-derive`'s generated code does not itself go through whatever
/// path named the derive macro — it independently defaults to an absolute
/// `::zerocopy`, resolved at the invocation site's own crate root, unless
/// told otherwise via `#[zerocopy(crate = "…")]` (zerocopy-derive >= 0.8.56;
/// see the version comment on the `zerocopy` dependency in `Cargo.toml`).
/// That attribute takes a string, and a string cannot carry `$crate`
/// hygiene, so the path is the literal `"armdb::zerocopy"` — correct as long
/// as the downstream crate names the dependency `armdb` (Cargo's default;
/// only a `package = "armdb"` rename would need `$name` derived by hand
/// instead of through this macro).
#[macro_export]
macro_rules! unique_index {
    ($name:ident, key = $key:ty, name = $lit:literal) => {
        #[derive(
            Clone,
            Copy,
            Debug,
            PartialEq,
            Eq,
            $crate::zerocopy::FromBytes,
            $crate::zerocopy::IntoBytes,
            $crate::zerocopy::Immutable,
            $crate::zerocopy::KnownLayout,
        )]
        #[zerocopy(crate = "armdb::zerocopy")]
        #[repr(transparent)]
        pub struct $name($crate::UniqueRow);

        impl $crate::armour_core::GetType for $name {
            const TYPE: $crate::armour_core::Typ =
                <$crate::UniqueRow as $crate::armour_core::GetType>::TYPE;
        }

        impl $crate::CollectionMeta for $name {
            type SelfId = $key;
            const NAME: &'static str = $lit;
            const TTL: Option<$crate::TtlPolicy<Self>> = Some($crate::TtlPolicy {
                revision: 1,
                deadline: |v: &Self, _k: &$key| v.0.deadline(),
            });
        }

        impl $crate::UniqueNamespace for $name {
            fn from_row(row: $crate::UniqueRow) -> Self {
                Self(row)
            }
            fn row(&self) -> $crate::UniqueRow {
                self.0
            }
        }
    };
}