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. Panics on `u64::MAX` — saturating would
35    /// silently violate monotonicity, and overflow at this scale means a
36    /// trillion records per second for ~580 million years.
37    pub fn next(self) -> Self {
38        Self(
39            self.0
40                .checked_add(1)
41                .expect("Lsn overflowed; the WAL has been continuously running for ~580 My"),
42        )
43    }
44}
45
46impl fmt::Display for Lsn {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "{}", self.0)
49    }
50}
51
52impl From<u64> for Lsn {
53    fn from(value: u64) -> Self {
54        Self(value)
55    }
56}
57
58impl From<Lsn> for u64 {
59    fn from(value: Lsn) -> Self {
60        value.0
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn next_advances_monotonically() {
70        let a = Lsn::new(7);
71        let b = a.next();
72        assert!(b > a);
73        assert_eq!(b.raw(), 8);
74    }
75
76    #[test]
77    fn zero_is_sentinel() {
78        assert!(Lsn::ZERO.is_zero());
79        assert!(!Lsn::new(1).is_zero());
80    }
81
82    #[test]
83    #[should_panic(expected = "Lsn overflowed")]
84    fn overflow_panics() {
85        let _ = Lsn::new(u64::MAX).next();
86    }
87}