Skip to main content

lora_wal/
lsn.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5/// Monotonic log sequence number.
6///
7/// LSN 0 is reserved for "empty / never written" so a snapshot's
8/// `wal_lsn = 0` cannot be mistaken for "I checkpointed at the very first
9/// record". Allocators advance from 1.
10///
11/// Internally an LSN is opaque: callers only rely on the total order. The
12/// representation is left as a single `u64` for now; a future change to a
13/// `(segment_id, offset)` packing is non-breaking because every consumer
14/// goes through these accessors.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
16#[repr(transparent)]
17pub struct Lsn(u64);
18
19impl Lsn {
20    pub const ZERO: Lsn = Lsn(0);
21
22    pub const fn new(value: u64) -> Self {
23        Self(value)
24    }
25
26    pub const fn raw(self) -> u64 {
27        self.0
28    }
29
30    pub const fn is_zero(self) -> bool {
31        self.0 == 0
32    }
33
34    /// Returns the next LSN, or `None` if the counter is exhausted.
35    pub fn checked_next(self) -> Option<Self> {
36        self.0.checked_add(1).map(Self)
37    }
38
39    /// Returns the next LSN, saturating at `u64::MAX`.
40    ///
41    /// Production WAL allocation uses [`Self::checked_next`] so exhaustion
42    /// can surface as a structured error. This convenience method is kept
43    /// non-panicking for low-level callers and tests that only need simple
44    /// monotonic advancement.
45    pub fn next(self) -> Self {
46        self.checked_next().unwrap_or(self)
47    }
48}
49
50impl fmt::Display for Lsn {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "{}", self.0)
53    }
54}
55
56impl From<u64> for Lsn {
57    fn from(value: u64) -> Self {
58        Self(value)
59    }
60}
61
62impl From<Lsn> for u64 {
63    fn from(value: Lsn) -> Self {
64        value.0
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn next_advances_monotonically() {
74        let a = Lsn::new(7);
75        let b = a.next();
76        assert!(b > a);
77        assert_eq!(b.raw(), 8);
78    }
79
80    #[test]
81    fn zero_is_sentinel() {
82        assert!(Lsn::ZERO.is_zero());
83        assert!(!Lsn::new(1).is_zero());
84    }
85
86    #[test]
87    fn overflow_saturates() {
88        assert_eq!(Lsn::new(u64::MAX).next(), Lsn::new(u64::MAX));
89    }
90}