armdb 0.11.2

sharded bitcask key-value storage optimized for NVMe
Documentation
use crate::error::DbResult;
use crate::held_ops::HeldOps;

/// A write decision for one key.
///
/// Returned by an [`upsert`](crate::ConstTree::upsert) closure — which sees one
/// key — and by an [`update_many`](crate::ConstTree::update_many) closure, which
/// decides per key across a batch. Both take the decision under the shard lock
/// and apply exactly one of:
///
/// - [`Put`](Upsert::Put) — insert or overwrite the value
/// - [`Keep`](Upsert::Keep) — leave the row unchanged (no write, no hook)
/// - [`Delete`](Upsert::Delete) — remove the row if present; no-op if absent
///
/// `update_many` reports the outcome per key as an [`Applied`](crate::Applied).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Upsert<T> {
    /// Store `T` at the key (insert or overwrite).
    Put(T),
    /// Do not write; the existing row (or absence) is left as-is.
    Keep,
    /// Delete the key if it exists; absent keys stay absent.
    Delete,
}

mod sealed {
    pub trait Sealed {}
    impl<T> Sealed for super::Upsert<T> {}
    impl<T, R> Sealed for (super::Upsert<T>, R) {}
}

/// Converts a closure result into an [`Upsert`] decision plus an optional
/// caller-facing value.
///
/// Implemented for [`Upsert<T>`] (`Out = ()`) and `(Upsert<T>, R)` (`Out = R`),
/// so sites that need no result avoid writing `, ()` in every match arm.
pub trait IntoUpsert<T>: sealed::Sealed {
    /// Value returned from the `upsert` method after the decision is applied.
    type Out;
    /// Split into the engine decision and the value handed back to the caller.
    fn into_parts(self) -> (Upsert<T>, Self::Out);
}

impl<T> IntoUpsert<T> for Upsert<T> {
    type Out = ();
    fn into_parts(self) -> (Upsert<T>, ()) {
        (self, ())
    }
}

impl<T, R> IntoUpsert<T> for (Upsert<T>, R) {
    type Out = R;
    fn into_parts(self) -> (Upsert<T>, R) {
        self
    }
}

/// Generic decide-and-write over a held shard. Threads `FnOnce` through `Option`
/// because `HeldOps::with_value` does not invoke its closure when the key is absent.
pub(crate) fn upsert_via_held<S, U>(
    shard: &mut S,
    key: &S::Key,
    f: impl FnOnce(Option<&S::Val>) -> U,
) -> DbResult<U::Out>
where
    S: HeldOps,
    U: IntoUpsert<S::Val>,
{
    let mut f = Some(f);
    let (decision, out) = match shard.with_value(key, |cur| {
        let u = f.take().expect("upsert closure still present");
        u(Some(cur)).into_parts()
    })? {
        Some(parts) => parts,
        None => {
            let u = f.take().expect("upsert closure present on absent key");
            u(None).into_parts()
        }
    };

    match decision {
        Upsert::Put(v) => shard.put(key, v)?,
        Upsert::Keep => {}
        Upsert::Delete => {
            shard.delete(key)?;
        }
    }
    Ok(out)
}