pgwire_replication/
lsn.rs

1//! PostgreSQL Log Sequence Number (LSN) type.
2//!
3//! PostgreSQL displays LSNs as `X/Y` (uppercase hex), where:
4//! - `X` is the high 32 bits
5//! - `Y` is the low 32 bits
6//!
7//! Each part is up to 8 hex digits; leading zeros are typically omitted.
8
9use std::fmt;
10use std::str::FromStr;
11
12/// Error returned when parsing an invalid LSN string.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ParseLsnError(pub String);
15
16impl fmt::Display for ParseLsnError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        write!(f, "invalid LSN: {}", self.0)
19    }
20}
21
22impl std::error::Error for ParseLsnError {}
23
24/// PostgreSQL Log Sequence Number.
25///
26/// Represents a position in the write-ahead log (WAL). LSNs are used to
27/// track replication progress and identify specific points in the WAL stream.
28///
29/// # Format
30///
31/// PostgreSQL displays LSNs as `XXXXXXXX/YYYYYYYY` where:
32/// - `XXXXXXXX` is the high 32 bits (segment file number)
33/// - `YYYYYYYY` is the low 32 bits (offset within segment)
34///
35/// # Example
36///
37/// ```
38/// use pgwire_replication::lsn::Lsn;
39///
40/// let lsn = Lsn::parse("16/B374D848").unwrap();
41/// assert_eq!(lsn.to_string(), "16/B374D848");
42///
43/// // Or use FromStr
44/// let lsn: Lsn = "16/B374D848".parse().unwrap();
45/// ```
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
47pub struct Lsn(pub u64);
48
49impl Lsn {
50    /// The zero LSN, representing "start from the beginning" or "no position".
51    pub const ZERO: Lsn = Lsn(0);
52
53    /// Parse an LSN from PostgreSQL's `XXXXXXXX/YYYYYYYY` format.
54    ///
55    /// # Errors
56    ///
57    /// Returns `ParseLsnError` if the string is not valid LSN format.
58    pub fn parse(s: &str) -> Result<Lsn, ParseLsnError> {
59        let (hi_str, lo_str) = s
60            .split_once('/')
61            .ok_or_else(|| ParseLsnError(format!("missing '/' separator: {s}")))?;
62
63        let hi = u64::from_str_radix(hi_str, 16)
64            .map_err(|_| ParseLsnError(format!("invalid high part '{hi_str}': {s}")))?;
65
66        let lo = u64::from_str_radix(lo_str, 16)
67            .map_err(|_| ParseLsnError(format!("invalid low part '{lo_str}': {s}")))?;
68
69        Ok(Lsn((hi << 32) | lo))
70    }
71
72    /// Format as PostgreSQL's `XXXXXXXX/YYYYYYYY` string.
73    #[inline]
74    pub fn to_pg_string(self) -> String {
75        self.to_string()
76    }
77
78    /// Returns `true` if this is the zero LSN.
79    #[inline]
80    pub fn is_zero(self) -> bool {
81        self.0 == 0
82    }
83
84    /// Returns the raw 64-bit value.
85    #[inline]
86    pub fn as_u64(self) -> u64 {
87        self.0
88    }
89
90    /// Create an LSN from a raw 64-bit value.
91    #[inline]
92    pub fn from_u64(value: u64) -> Self {
93        Lsn(value)
94    }
95}
96
97impl fmt::Display for Lsn {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        let v: u64 = self.as_u64();
100        let hi = (v >> 32) as u32;
101        let lo = (v & 0xFFFF_FFFF) as u32;
102        write!(f, "{:X}/{:X}", hi, lo)
103    }
104}
105
106impl FromStr for Lsn {
107    type Err = ParseLsnError;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        Lsn::parse(s)
111    }
112}
113
114impl From<u64> for Lsn {
115    fn from(value: u64) -> Self {
116        Lsn(value)
117    }
118}
119
120impl From<Lsn> for u64 {
121    fn from(lsn: Lsn) -> Self {
122        lsn.0
123    }
124}