Skip to main content

nodedb_wal/segment/
meta.rs

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