use std::path::PathBuf;
use crate::error::{Result, WalError};
use super::meta::SegmentMeta;
#[derive(Debug, Default)]
pub struct SegmentContinuity {
previous: Option<(PathBuf, u64)>,
}
impl SegmentContinuity {
pub fn new() -> Self {
Self::default()
}
pub fn check(&self, segment: &SegmentMeta) -> Result<()> {
let Some((previous_path, previous_last_lsn)) = &self.previous else {
return Ok(());
};
let expected_lsn = previous_last_lsn.saturating_add(1);
if segment.first_lsn > expected_lsn {
let err = WalError::SegmentLsnGap {
path: segment.path.display().to_string(),
previous_path: previous_path.display().to_string(),
previous_last_lsn: *previous_last_lsn,
expected_lsn,
found_lsn: segment.first_lsn,
};
crate::diag::segment_lsn_gap(
&err,
&segment.path,
previous_path,
*previous_last_lsn,
expected_lsn,
segment.first_lsn,
);
return Err(err);
}
Ok(())
}
pub fn completed(&mut self, segment: &SegmentMeta, last_lsn: u64) {
if last_lsn != 0 {
self.previous = Some((segment.path.clone(), last_lsn));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn meta(first_lsn: u64) -> SegmentMeta {
SegmentMeta {
path: Path::new("/wal").join(super::super::segment_filename(first_lsn)),
first_lsn,
file_size: 0,
}
}
#[test]
fn first_segment_may_start_anywhere() {
let continuity = SegmentContinuity::new();
assert!(continuity.check(&meta(5000)).is_ok());
}
#[test]
fn contiguous_segments_pass() {
let mut continuity = SegmentContinuity::new();
let first = meta(1);
continuity.check(&first).unwrap();
continuity.completed(&first, 42);
assert!(continuity.check(&meta(43)).is_ok());
}
#[test]
fn gap_between_segments_is_rejected() {
let mut continuity = SegmentContinuity::new();
let first = meta(1);
continuity.completed(&first, 42);
match continuity.check(&meta(90)) {
Err(WalError::SegmentLsnGap {
expected_lsn,
found_lsn,
..
}) => {
assert_eq!(expected_lsn, 43);
assert_eq!(found_lsn, 90);
}
other => panic!("expected a segment LSN gap, got {other:?}"),
}
}
#[test]
fn empty_segment_keeps_the_previous_boundary() {
let mut continuity = SegmentContinuity::new();
let first = meta(1);
continuity.completed(&first, 42);
let empty = meta(43);
continuity.check(&empty).unwrap();
continuity.completed(&empty, 0);
assert!(continuity.check(&meta(43)).is_ok());
assert!(continuity.check(&meta(90)).is_err());
}
}