haematite 0.6.2

Content-addressed, branchable, actor-native storage engine
Documentation
//! Native-neutral whole-record persistence contract for durable branch records.
//!
//! WAL append/read/flush/recovery is deliberately absent. Node publication is
//! the separate [`crate::store::NodeStore`] contract.

use core::fmt::Debug;

use super::refrecord::BranchRefRecord;
use super::time::Timestamp;

/// Outcome of the seam's create-exclusive operation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum CreateExclusive {
    Installed,
    TargetExists(BranchRefRecord),
}

/// Expected state fenced after a record-directory entry operation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum EntryFence<'a> {
    Present(&'a BranchRefRecord),
    Absent(&'a str),
}

/// Exactly the five whole-record operations required by the branch core.
///
/// Implementations must perform creation identity and sequence comparison in
/// `cas_replace_install`; callers must never synthesize compare-then-put.
///
/// `Send + Sync` is a supertrait obligation, not a per-site bound: a durable
/// record store is a cross-thread object, and v0.6.0 shipped `BranchRefStore`
/// without `Send`/`Sync` because the boxed seam carried no bound (trait
/// objects are `?Send` by default). The supertrait makes every current and
/// future embedding site inherit the auto-traits instead of re-remembering
/// them per box.
pub(super) trait DurableRecordStore: Debug + Send + Sync {
    type Error;

    /// Load every whole record at open, sweeping only backend-pinned temp debris.
    fn list_read_at_open(&mut self) -> Result<Vec<BranchRefRecord>, Self::Error>;

    /// Atomically install `record` only when its durable name is absent.
    fn create_exclusive(
        &mut self,
        record: &BranchRefRecord,
    ) -> Result<CreateExclusive, Self::Error>;

    /// CAS and atomically replace the whole record, returning what was installed.
    fn cas_replace_install(
        &mut self,
        name: &str,
        expected_created: Timestamp,
        expected_seq: u64,
        replacement: &BranchRefRecord,
    ) -> Result<BranchRefRecord, Self::Error>;

    /// Idempotently unlink a whole record. `true` means an entry was removed.
    fn unlink(&mut self, name: &str) -> Result<bool, Self::Error>;

    /// Fence the preceding create, replace, or unlink entry operation.
    fn entry_fence(&mut self, expected: EntryFence<'_>) -> Result<(), Self::Error>;
}

pub(super) fn entry_fence_with_source<S: DurableRecordStore + ?Sized>(
    store: &mut S,
    expected: EntryFence<'_>,
) -> Result<(), super::operation_error::FenceOperationError<S::Error>> {
    super::operation_error::preserve_record_entry_fence(store.entry_fence(expected))
}

#[cfg(test)]
pub(super) fn entry_fence_compat<S: DurableRecordStore + ?Sized>(
    store: &mut S,
    expected: EntryFence<'_>,
) -> Result<(), S::Error> {
    entry_fence_with_source(store, expected).map_err(|error| match error {
        super::operation_error::FenceOperationError::RecordEntry(source) => source,
        super::operation_error::FenceOperationError::NodePublication(_) => {
            unreachable!("node fence returned from record entry core")
        }
    })
}