Skip to main content

nodedb_wal/segment/
meta.rs

1//! Segment filename conventions and on-disk metadata.
2
3use std::path::{Path, PathBuf};
4
5/// Default segment target size: 64 MiB.
6///
7/// This is a soft limit — the writer finishes the current record before rolling.
8/// Actual segments may be slightly larger than this value.
9pub const DEFAULT_SEGMENT_TARGET_SIZE: u64 = 64 * 1024 * 1024;
10
11/// Segment file extension.
12pub(crate) const SEGMENT_EXTENSION: &str = "seg";
13
14/// Segment file prefix.
15pub(crate) const SEGMENT_PREFIX: &str = "wal-";
16
17/// Metadata about a WAL segment file on disk.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct SegmentMeta {
20    /// Path to the segment file on disk.
21    pub path: PathBuf,
22
23    /// First LSN stored in this segment (from the filename).
24    pub first_lsn: u64,
25
26    /// File size in bytes.
27    pub file_size: u64,
28}
29
30impl SegmentMeta {
31    /// Path to the companion double-write buffer file.
32    pub fn dwb_path(&self) -> PathBuf {
33        self.path.with_extension("dwb")
34    }
35}
36
37impl Ord for SegmentMeta {
38    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
39        self.first_lsn.cmp(&other.first_lsn)
40    }
41}
42
43impl PartialOrd for SegmentMeta {
44    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
45        Some(self.cmp(other))
46    }
47}
48
49/// Build a segment filename from a starting LSN.
50pub fn segment_filename(first_lsn: u64) -> String {
51    format!("{SEGMENT_PREFIX}{first_lsn:020}.{SEGMENT_EXTENSION}")
52}
53
54/// Build a full segment path within a WAL directory.
55pub fn segment_path(wal_dir: &Path, first_lsn: u64) -> PathBuf {
56    wal_dir.join(segment_filename(first_lsn))
57}
58
59/// Parse the first_lsn from a segment filename.
60///
61/// Returns `None` if the filename doesn't match the expected pattern.
62pub(crate) fn parse_segment_filename(filename: &str) -> Option<u64> {
63    let stem = filename.strip_prefix(SEGMENT_PREFIX)?;
64    let lsn_str = stem.strip_suffix(&format!(".{SEGMENT_EXTENSION}"))?;
65    lsn_str.parse::<u64>().ok()
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn segment_filename_format() {
74        assert_eq!(segment_filename(1), "wal-00000000000000000001.seg");
75        assert_eq!(segment_filename(999), "wal-00000000000000000999.seg");
76        assert_eq!(segment_filename(u64::MAX), "wal-18446744073709551615.seg");
77    }
78
79    #[test]
80    fn parse_segment_filename_valid() {
81        assert_eq!(
82            parse_segment_filename("wal-00000000000000000001.seg"),
83            Some(1)
84        );
85        assert_eq!(
86            parse_segment_filename("wal-00000000000000000999.seg"),
87            Some(999)
88        );
89    }
90
91    #[test]
92    fn parse_segment_filename_invalid() {
93        assert_eq!(parse_segment_filename("wal.log"), None);
94        assert_eq!(parse_segment_filename("wal-abc.seg"), None);
95        assert_eq!(parse_segment_filename("other-00001.seg"), None);
96        assert_eq!(parse_segment_filename("wal-00001.dwb"), None);
97    }
98}