Skip to main content

blockworx_doc/
rev.rs

1//! The commit sequence number — the first component of the total write
2//! order. Totality is structural: the head rev lives in the document and
3//! is minted only by a successful `try_apply`, so a refused commit
4//! consumes nothing and the log cannot gap. A `Rev` outside the fold is
5//! inert (`try_apply` takes no rev), so `next` stays public while minting
6//! stays module-private.
7//!
8//! [`DocStamp`] lives here as the other half of the version question: a
9//! rev names a position in the log, a stamp names one document *value*
10//! inside one process.
11
12use std::sync::atomic::{AtomicU64, Ordering};
13
14use serde::{Deserialize, Serialize};
15
16#[derive(
17    Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Serialize, Deserialize,
18)]
19pub struct Rev(u64);
20
21impl Rev {
22    /// The empty document: no commit accepted yet. Real revs start at 1,
23    /// so ZERO sorts below every accepted write.
24    pub const ZERO: Rev = Rev(0);
25
26    /// Test fixtures only: production revs enter a process exclusively via
27    /// `Document::try_apply` (`next`).
28    #[cfg(any(test, feature = "fixtures"))]
29    pub(crate) const fn new(n: u64) -> Self {
30        Self(n)
31    }
32
33    pub const fn get(self) -> u64 {
34        self.0
35    }
36
37    #[must_use]
38    pub const fn next(self) -> Self {
39        Self(self.0 + 1)
40    }
41
42    /// `n` positions on from this one — the same arithmetic
43    /// [`Self::next`] is, for a caller counting a run of them out.
44    #[must_use]
45    pub const fn forward(self, n: u64) -> Self {
46        Self(self.0 + n)
47    }
48
49    /// The position before this one, or `None` at [`Self::ZERO`]. Inert
50    /// like [`Self::next`]: a rev outside the fold names a position, and
51    /// the log's positions are contiguous by construction.
52    #[must_use]
53    pub const fn prev(self) -> Option<Self> {
54        match self.0.checked_sub(1) {
55            Some(before) => Some(Self(before)),
56            None => None,
57        }
58    }
59}
60
61/// Identifies one *value* of a [`Document`](crate::document::Document).
62/// A fresh stamp is minted process-wide wherever a distinct value is
63/// born and a clone carries its source's, so equal stamps imply
64/// identical content — which is what lets a derived index tell "the same
65/// document" from "the same rev" without hashing the contents.
66///
67/// Process-local and deliberately not serialized: a stamp names nothing
68/// outside the process that minted it, and a rev is what a log carries.
69#[derive(Clone, Copy, PartialEq, Eq, Debug)]
70pub struct DocStamp(u64);
71
72impl DocStamp {
73    pub(crate) fn next() -> Self {
74        static NEXT: AtomicU64 = AtomicU64::new(0);
75        Self(NEXT.fetch_add(1, Ordering::Relaxed))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn next_advances_and_orders() {
85        let one = Rev::ZERO.next();
86        assert_eq!(one, Rev::new(1));
87        assert!(Rev::ZERO < one);
88    }
89
90    #[test]
91    fn prev_walks_back_and_stops_at_the_empty_document() {
92        assert_eq!(Rev::new(2).prev(), Some(Rev::new(1)));
93        assert_eq!(Rev::new(1).prev(), Some(Rev::ZERO));
94        assert_eq!(Rev::ZERO.prev(), None);
95    }
96}