Skip to main content

graft_core/
lsn.rs

1use std::{
2    fmt::Display,
3    num::{NonZero, ParseIntError},
4    ops::{Bound, RangeBounds, RangeInclusive},
5};
6
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9use zerocopy::{ByteHash, Immutable, IntoBytes, KnownLayout, TryFromBytes, ValidityError};
10
11#[derive(Debug, Error)]
12#[error("LSN must be non-zero")]
13pub struct InvalidLSN;
14
15impl<S, D: TryFromBytes> From<ValidityError<S, D>> for InvalidLSN {
16    fn from(_: ValidityError<S, D>) -> Self {
17        InvalidLSN
18    }
19}
20
21#[derive(
22    Debug,
23    Clone,
24    Copy,
25    PartialEq,
26    Eq,
27    PartialOrd,
28    Ord,
29    Serialize,
30    Deserialize,
31    TryFromBytes,
32    KnownLayout,
33    IntoBytes,
34    Immutable,
35    ByteHash,
36)]
37#[repr(transparent)]
38pub struct LSN(NonZero<u64>);
39
40// We use NonZero to enable Option optimizations
41static_assertions::assert_eq_size!(Option<LSN>, LSN);
42
43impl LSN {
44    pub const FIRST: Self = unsafe { Self::new_unchecked(1) };
45    pub const LAST: Self = unsafe { Self::new_unchecked(u64::MAX) };
46    pub const ALL: RangeInclusive<Self> = Self::FIRST..=Self::LAST;
47
48    #[inline]
49    pub fn new(lsn: u64) -> Self {
50        Self(NonZero::new(lsn).expect("LSN must be non-zero"))
51    }
52
53    #[inline]
54    const unsafe fn new_unchecked(lsn: u64) -> Self {
55        unsafe { Self(NonZero::new_unchecked(lsn)) }
56    }
57
58    #[inline]
59    pub fn next(&self) -> Option<Self> {
60        self.0.checked_add(1).map(Self)
61    }
62
63    #[inline]
64    pub fn saturating_next(&self) -> Self {
65        Self(self.0.saturating_add(1))
66    }
67
68    #[inline]
69    pub fn prev(&self) -> Option<Self> {
70        let n = self.0.get().saturating_sub(1);
71        if n == 0 {
72            None
73        } else {
74            // SAFETY: n is non-zero
75            unsafe { Some(Self(NonZero::new_unchecked(n))) }
76        }
77    }
78
79    #[inline]
80    pub fn saturating_prev(&self) -> Self {
81        let n = self.0.get().saturating_sub(1);
82        if n == 0 {
83            Self::FIRST
84        } else {
85            // SAFETY: n is non-zero
86            unsafe { Self(NonZero::new_unchecked(n)) }
87        }
88    }
89
90    /// Returns the difference between this LSN and another LSN.
91    /// If the other LSN is greater than this LSN, None is returned.
92    #[inline]
93    pub fn since(&self, other: &Self) -> Option<u64> {
94        let me = self.0.get();
95        let other = other.0.get();
96        if me >= other { Some(me - other) } else { None }
97    }
98
99    /// Formats the LSN as a fixed-width hexadecimal string.
100    /// The string will be 16 characters long, with leading zeros.
101    pub fn format_fixed_hex(&self) -> String {
102        format!("{:0>16x}", self.0)
103    }
104
105    /// Parses an LSN from a hexadecimal string.
106    pub fn from_hex(s: &str) -> Result<Self, ParseIntError> {
107        Ok(Self::new(u64::from_str_radix(s, 16)?))
108    }
109}
110
111impl Display for LSN {
112    #[inline]
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        self.0.fmt(f)
115    }
116}
117
118impl<'a> TryFrom<&'a u64> for &'a LSN {
119    type Error = InvalidLSN;
120
121    #[inline]
122    fn try_from(value: &'a u64) -> Result<Self, Self::Error> {
123        Ok(zerocopy::try_transmute_ref!(value)?)
124    }
125}
126
127impl From<&LSN> for u64 {
128    #[inline]
129    fn from(value: &LSN) -> Self {
130        value.0.get()
131    }
132}
133
134impl From<LSN> for u64 {
135    #[inline]
136    fn from(lsn: LSN) -> Self {
137        lsn.0.get()
138    }
139}
140
141impl TryFrom<u64> for LSN {
142    type Error = InvalidLSN;
143
144    #[inline]
145    fn try_from(lsn: u64) -> Result<Self, Self::Error> {
146        match NonZero::new(lsn) {
147            Some(lsn) => Ok(Self(lsn)),
148            None => Err(InvalidLSN),
149        }
150    }
151}
152
153impl PartialEq<u64> for LSN {
154    #[inline]
155    fn eq(&self, other: &u64) -> bool {
156        self.0.get() == *other
157    }
158}
159
160impl PartialOrd<u64> for LSN {
161    #[inline]
162    fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
163        self.0.get().partial_cmp(other)
164    }
165}
166
167impl<E: zerocopy::ByteOrder> From<LSN> for zerocopy::U64<E> {
168    #[inline]
169    fn from(val: LSN) -> Self {
170        zerocopy::U64::new(val.0.get())
171    }
172}
173
174impl<E: zerocopy::ByteOrder> TryFrom<zerocopy::U64<E>> for LSN {
175    type Error = InvalidLSN;
176
177    #[inline]
178    fn try_from(value: zerocopy::U64<E>) -> Result<Self, Self::Error> {
179        match NonZero::new(value.get()) {
180            Some(lsn) => Ok(Self(lsn)),
181            None => Err(InvalidLSN),
182        }
183    }
184}
185
186pub trait LSNRangeExt {
187    fn try_len(&self) -> Option<usize>;
188    fn try_start(&self) -> Option<LSN>;
189    fn try_start_exclusive(&self) -> Option<LSN>;
190    fn try_end(&self) -> Option<LSN>;
191    fn try_end_exclusive(&self) -> Option<LSN>;
192    fn iter(&self) -> LSNRangeIter;
193}
194
195impl<T: RangeBounds<LSN>> LSNRangeExt for T {
196    fn try_len(&self) -> Option<usize> {
197        let start = self.try_start()?;
198        let end = self.try_end_exclusive()?;
199        end.since(&start).map(|len| len as usize)
200    }
201
202    fn try_start(&self) -> Option<LSN> {
203        match self.start_bound() {
204            Bound::Included(lsn) => Some(*lsn),
205            Bound::Excluded(lsn) => Some(lsn.saturating_next()),
206            Bound::Unbounded => None,
207        }
208    }
209
210    fn try_start_exclusive(&self) -> Option<LSN> {
211        match self.start_bound() {
212            Bound::Included(lsn) => lsn.prev(),
213            Bound::Excluded(lsn) => Some(*lsn),
214            Bound::Unbounded => None,
215        }
216    }
217
218    fn try_end(&self) -> Option<LSN> {
219        match self.end_bound() {
220            Bound::Included(lsn) => Some(*lsn),
221            Bound::Excluded(lsn) => Some(lsn.saturating_prev()),
222            Bound::Unbounded => None,
223        }
224    }
225
226    fn try_end_exclusive(&self) -> Option<LSN> {
227        match self.end_bound() {
228            Bound::Included(lsn) => lsn.next(),
229            Bound::Excluded(lsn) => Some(*lsn),
230            Bound::Unbounded => None,
231        }
232    }
233
234    fn iter(&self) -> LSNRangeIter {
235        let start = self.try_start().unwrap_or(LSN::FIRST).into();
236        let end = self.try_end().unwrap_or(LSN::LAST).into();
237        LSNRangeIter { range: start..=end }
238    }
239}
240
241#[derive(Debug, Clone)]
242#[must_use = "iterators are lazy and do nothing unless consumed"]
243pub struct LSNRangeIter {
244    range: RangeInclusive<u64>,
245}
246
247impl Iterator for LSNRangeIter {
248    type Item = LSN;
249
250    fn next(&mut self) -> Option<Self::Item> {
251        self.range.next().map(|n| {
252            // SAFETY: we know n is non-zero because next is monotonically
253            // increasing
254            unsafe { LSN::new_unchecked(n) }
255        })
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[graft_test::test]
264    fn test_lsn_next() {
265        let lsn = LSN::FIRST;
266        assert_eq!(lsn.saturating_next(), 2);
267    }
268}