armdb 0.1.14

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

use armour_core::{KeyScheme, KeyType};

/// Key metadata trait — describes key encoding for armdb collections.
///
/// Analogous to `Cid` in the armour crate, but without encode/decode methods.
/// Only carries compile-time constants for [`TreeMeta`](armour::TreeMeta) construction.
///
/// Implementations must guarantee that the type is safe to transmute to/from
/// `[u8; size_of::<Self>()]` — all bit patterns valid, no padding.
/// Use [`impl_key_zerocopy!`] for zerocopy types or [`impl_key_bytemuck!`]
/// for bytemuck types to get correct implementations.
///
/// # Example
///
/// ```ignore
/// use armdb::{Key, impl_key_zerocopy};
/// use armour_core::{KeyScheme, KeyType};
///
/// impl_key_zerocopy!(UserId, KeyScheme::Typed(&[KeyType::Fuid]));
/// ```
pub trait Key: Copy {
    /// Key encoding scheme.
    const KEY_SCHEME: KeyScheme;
    /// Bits used for group_id / shard routing.
    const GROUP_BITS: u32 = 0;

    fn as_bytes(&self) -> &[u8];
    fn as_bytes_mut(&mut self) -> &mut [u8];
    fn from_bytes(bytes: &[u8]) -> Self;
    fn zeroed() -> Self;
}

// ── Location trait ─────────────────────────────────────────────────────────

/// Marker trait for disk location types stored in SkipList nodes.
/// DiskLoc (12 bytes) for Bitcask, u32 (4 bytes) for FixedStore slot_id.
pub trait Location: Copy + Send + Sync + 'static {
    fn zeroed() -> Self;
}

impl Location for crate::disk_loc::DiskLoc {
    fn zeroed() -> Self {
        crate::disk_loc::DiskLoc::new(0, 0, 0, 0)
    }
}

impl Location for u32 {
    fn zeroed() -> Self {
        0
    }
}

// ── macros ──────────────────────────────────────────────────────────────────

/// Implement [`Key`] for a type that derives `zerocopy::{FromBytes, IntoBytes, Immutable}`.
#[macro_export]
macro_rules! impl_key_zerocopy {
    ($ty:ty, $scheme:expr) => {
        impl $crate::Key for $ty {
            const KEY_SCHEME: armour_core::KeyScheme = $scheme;

            #[inline(always)]
            fn as_bytes(&self) -> &[u8] {
                zerocopy::IntoBytes::as_bytes(self)
            }
            #[inline(always)]
            fn as_bytes_mut(&mut self) -> &mut [u8] {
                zerocopy::IntoBytes::as_mut_bytes(self)
            }
            #[inline(always)]
            fn from_bytes(bytes: &[u8]) -> Self {
                debug_assert_eq!(bytes.len(), size_of::<Self>());
                // SAFETY: size checked in debug, caller guarantees correct len.
                // zerocopy's read_from_bytes does a runtime check we want to skip
                // in release. The type derives FromBytes so all bit patterns valid.
                unsafe { std::ptr::read(bytes.as_ptr().cast()) }
            }
            #[inline(always)]
            fn zeroed() -> Self {
                zerocopy::FromZeros::new_zeroed()
            }
        }
    };
    ($ty:ty, $scheme:expr, group_bits = $gb:expr) => {
        impl $crate::Key for $ty {
            const KEY_SCHEME: armour_core::KeyScheme = $scheme;
            const GROUP_BITS: u32 = $gb;

            #[inline(always)]
            fn as_bytes(&self) -> &[u8] {
                zerocopy::IntoBytes::as_bytes(self)
            }
            #[inline(always)]
            fn as_bytes_mut(&mut self) -> &mut [u8] {
                zerocopy::IntoBytes::as_mut_bytes(self)
            }
            #[inline(always)]
            fn from_bytes(bytes: &[u8]) -> Self {
                debug_assert_eq!(bytes.len(), size_of::<Self>());
                unsafe { std::ptr::read(bytes.as_ptr().cast()) }
            }
            #[inline(always)]
            fn zeroed() -> Self {
                zerocopy::FromZeros::new_zeroed()
            }
        }
    };
}

/// Implement [`Key`] for a type that derives `bytemuck::{Pod, Zeroable}`.
#[macro_export]
macro_rules! impl_key_bytemuck {
    ($ty:ty, $scheme:expr) => {
        impl $crate::Key for $ty {
            const KEY_SCHEME: armour_core::KeyScheme = $scheme;

            #[inline(always)]
            fn as_bytes(&self) -> &[u8] {
                bytemuck::bytes_of(self)
            }
            #[inline(always)]
            fn as_bytes_mut(&mut self) -> &mut [u8] {
                bytemuck::bytes_of_mut(self)
            }
            #[inline(always)]
            fn from_bytes(bytes: &[u8]) -> Self {
                debug_assert_eq!(bytes.len(), size_of::<Self>());
                unsafe { std::ptr::read(bytes.as_ptr().cast()) }
            }
            #[inline(always)]
            fn zeroed() -> Self {
                bytemuck::Zeroable::zeroed()
            }
        }
    };
    ($ty:ty, $scheme:expr, group_bits = $gb:expr) => {
        impl $crate::Key for $ty {
            const KEY_SCHEME: armour_core::KeyScheme = $scheme;
            const GROUP_BITS: u32 = $gb;

            #[inline(always)]
            fn as_bytes(&self) -> &[u8] {
                bytemuck::bytes_of(self)
            }
            #[inline(always)]
            fn as_bytes_mut(&mut self) -> &mut [u8] {
                bytemuck::bytes_of_mut(self)
            }
            #[inline(always)]
            fn from_bytes(bytes: &[u8]) -> Self {
                debug_assert_eq!(bytes.len(), size_of::<Self>());
                unsafe { std::ptr::read(bytes.as_ptr().cast()) }
            }
            #[inline(always)]
            fn zeroed() -> Self {
                bytemuck::Zeroable::zeroed()
            }
        }
    };
}

// ── byte arrays ─────────────────────────────────────────────────────────────

impl<const N: usize> Key for [u8; N] {
    const KEY_SCHEME: KeyScheme = KeyScheme::Typed(&[KeyType::Array(N)]);

    #[inline(always)]
    fn as_bytes(&self) -> &[u8] {
        self
    }
    #[inline(always)]
    fn as_bytes_mut(&mut self) -> &mut [u8] {
        self
    }
    #[inline(always)]
    fn from_bytes(bytes: &[u8]) -> Self {
        debug_assert_eq!(bytes.len(), N);
        unsafe { ptr::read(bytes.as_ptr().cast()) }
    }
    #[inline(always)]
    fn zeroed() -> Self {
        [0u8; N]
    }
}

// ── rend (feature "armour") ─────────────────────────────────────────────────

#[cfg(feature = "armour")]
impl_key_zerocopy!(rend::u64_be, KeyScheme::Typed(&[KeyType::U64]));

// ── armour-core ID types ─────────────────────────────────────────────────────

impl<H> Key for armour_core::fuid::Fuid<H> {
    const KEY_SCHEME: KeyScheme = KeyScheme::Typed(&[KeyType::Fuid]);

    #[inline(always)]
    fn as_bytes(&self) -> &[u8] {
        zerocopy::IntoBytes::as_bytes(self)
    }
    #[inline(always)]
    fn as_bytes_mut(&mut self) -> &mut [u8] {
        zerocopy::IntoBytes::as_mut_bytes(self)
    }
    #[inline(always)]
    fn from_bytes(bytes: &[u8]) -> Self {
        debug_assert_eq!(bytes.len(), size_of::<Self>());
        unsafe { ptr::read(bytes.as_ptr().cast()) }
    }
    #[inline(always)]
    fn zeroed() -> Self {
        zerocopy::FromZeros::new_zeroed()
    }
}

impl<T> Key for armour_core::id64::Id64<T> {
    const KEY_SCHEME: KeyScheme = KeyScheme::Typed(&[KeyType::U64]);

    #[inline(always)]
    fn as_bytes(&self) -> &[u8] {
        zerocopy::IntoBytes::as_bytes(self)
    }
    #[inline(always)]
    fn as_bytes_mut(&mut self) -> &mut [u8] {
        zerocopy::IntoBytes::as_mut_bytes(self)
    }
    #[inline(always)]
    fn from_bytes(bytes: &[u8]) -> Self {
        debug_assert_eq!(bytes.len(), size_of::<Self>());
        unsafe { ptr::read(bytes.as_ptr().cast()) }
    }
    #[inline(always)]
    fn zeroed() -> Self {
        zerocopy::FromZeros::new_zeroed()
    }
}

// ── uuid ────────────────────────────────────────────────────────────────────

#[cfg(feature = "uuid")]
impl_key_bytemuck!(uuid::Uuid, KeyScheme::Typed(&[KeyType::Array(16)]));

// ── solana (feature "solana") ────────────────────────────────────────────────

#[cfg(feature = "solana")]
impl_key_bytemuck!(
    solana_pubkey::Pubkey,
    KeyScheme::Typed(&[KeyType::Array(32)])
);

#[cfg(feature = "solana")]
impl_key_bytemuck!(
    solana_signature::Signature,
    KeyScheme::Typed(&[KeyType::Array(64)])
);