1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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)
}