#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VlsnRange {
first: u64,
last: u64,
commit_vlsn: u64,
sync_vlsn: u64,
}
impl Default for VlsnRange {
fn default() -> Self {
Self::new()
}
}
impl VlsnRange {
pub fn new() -> Self {
VlsnRange { first: 0, last: 0, commit_vlsn: 0, sync_vlsn: 0 }
}
pub fn with_range(first: u64, last: u64) -> Self {
assert!(
first == 0 || first <= last,
"first ({}) must be <= last ({})",
first,
last
);
VlsnRange { first, last, commit_vlsn: 0, sync_vlsn: 0 }
}
pub fn get_first(&self) -> u64 {
self.first
}
pub fn get_last(&self) -> u64 {
self.last
}
pub fn get_commit_vlsn(&self) -> u64 {
self.commit_vlsn
}
pub fn get_sync_vlsn(&self) -> u64 {
self.sync_vlsn
}
pub fn first(&self) -> u64 {
self.first
}
pub fn last(&self) -> u64 {
self.last
}
pub fn is_empty(&self) -> bool {
self.first == 0
}
pub fn contains(&self, vlsn: u64) -> bool {
if self.first == 0 {
return false;
}
self.first <= vlsn && vlsn <= self.last
}
pub fn len(&self) -> u64 {
if self.first == 0 {
return 0;
}
self.last - self.first + 1
}
pub fn extend(&mut self, vlsn: u64) {
assert!(vlsn > 0, "Cannot extend with NULL_VLSN (0)");
if self.first == 0 || vlsn < self.first {
self.first = vlsn;
}
if vlsn > self.last {
self.last = vlsn;
}
}
pub fn update_commit(&mut self, vlsn: u64) {
if vlsn > self.commit_vlsn {
self.commit_vlsn = vlsn;
}
}
pub fn update_sync(&mut self, vlsn: u64) {
if vlsn > self.sync_vlsn {
self.sync_vlsn = vlsn;
}
}
pub fn truncate_after(&mut self, vlsn: u64) {
if vlsn == 0 || (self.first > 0 && vlsn < self.first) {
self.first = 0;
self.last = 0;
self.commit_vlsn = 0;
self.sync_vlsn = 0;
return;
}
if self.first == 0 {
return;
}
self.last = vlsn;
if self.commit_vlsn > vlsn {
self.commit_vlsn = vlsn;
}
if self.sync_vlsn > vlsn {
self.sync_vlsn = vlsn;
}
}
pub fn merge(&mut self, other: &VlsnRange) {
self.first = match (self.first, other.first) {
(0, b) => b,
(a, 0) => a,
(a, b) => a.min(b),
};
self.last = match (self.last, other.last) {
(0, b) => b,
(a, 0) => a,
(a, b) => a.max(b),
};
self.commit_vlsn = match (self.commit_vlsn, other.commit_vlsn) {
(0, b) => b,
(a, 0) => a,
(a, b) => a.max(b),
};
self.sync_vlsn = match (self.sync_vlsn, other.sync_vlsn) {
(0, b) => b,
(a, 0) => a,
(a, b) => a.max(b),
};
}
}
impl std::fmt::Display for VlsnRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"first={} last={} commit={} sync={}",
self.first, self.last, self.commit_vlsn, self.sync_vlsn
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_empty() {
let range = VlsnRange::new();
assert!(range.is_empty());
assert_eq!(range.get_first(), 0);
assert_eq!(range.get_last(), 0);
assert_eq!(range.get_commit_vlsn(), 0);
assert_eq!(range.get_sync_vlsn(), 0);
assert_eq!(range.len(), 0);
}
#[test]
fn test_with_range() {
let range = VlsnRange::with_range(5, 10);
assert!(!range.is_empty());
assert_eq!(range.get_first(), 5);
assert_eq!(range.get_last(), 10);
assert_eq!(range.len(), 6);
}
#[test]
fn test_with_range_single() {
let range = VlsnRange::with_range(7, 7);
assert!(!range.is_empty());
assert_eq!(range.len(), 1);
}
#[test]
fn test_contains() {
let range = VlsnRange::with_range(5, 10);
assert!(!range.contains(4));
assert!(range.contains(5));
assert!(range.contains(7));
assert!(range.contains(10));
assert!(!range.contains(11));
}
#[test]
fn test_contains_empty() {
let range = VlsnRange::new();
assert!(!range.contains(0));
assert!(!range.contains(1));
assert!(!range.contains(100));
}
#[test]
fn test_extend_from_empty() {
let mut range = VlsnRange::new();
range.extend(5);
assert!(!range.is_empty());
assert_eq!(range.get_first(), 5);
assert_eq!(range.get_last(), 5);
assert_eq!(range.len(), 1);
}
#[test]
fn test_extend_forward() {
let mut range = VlsnRange::with_range(5, 10);
range.extend(15);
assert_eq!(range.get_first(), 5);
assert_eq!(range.get_last(), 15);
assert_eq!(range.len(), 11);
}
#[test]
fn test_extend_backward() {
let mut range = VlsnRange::with_range(5, 10);
range.extend(2);
assert_eq!(range.get_first(), 2);
assert_eq!(range.get_last(), 10);
}
#[test]
fn test_extend_within() {
let mut range = VlsnRange::with_range(5, 10);
range.extend(7);
assert_eq!(range.get_first(), 5);
assert_eq!(range.get_last(), 10);
}
#[test]
fn test_commit_vlsn() {
let mut range = VlsnRange::with_range(1, 10);
assert_eq!(range.get_commit_vlsn(), 0);
range.update_commit(5);
assert_eq!(range.get_commit_vlsn(), 5);
range.update_commit(8);
assert_eq!(range.get_commit_vlsn(), 8);
range.update_commit(3);
assert_eq!(range.get_commit_vlsn(), 8);
}
#[test]
fn test_sync_vlsn() {
let mut range = VlsnRange::with_range(1, 10);
assert_eq!(range.get_sync_vlsn(), 0);
range.update_sync(4);
assert_eq!(range.get_sync_vlsn(), 4);
range.update_sync(9);
assert_eq!(range.get_sync_vlsn(), 9);
range.update_sync(2);
assert_eq!(range.get_sync_vlsn(), 9);
}
#[test]
fn test_truncate_after_middle() {
let mut range = VlsnRange::with_range(5, 20);
range.update_commit(15);
range.update_sync(18);
range.truncate_after(12);
assert_eq!(range.get_first(), 5);
assert_eq!(range.get_last(), 12);
assert_eq!(range.get_commit_vlsn(), 12);
assert_eq!(range.get_sync_vlsn(), 12);
assert_eq!(range.len(), 8);
}
#[test]
fn test_truncate_after_before_first() {
let mut range = VlsnRange::with_range(5, 10);
range.truncate_after(3);
assert!(range.is_empty());
assert_eq!(range.len(), 0);
}
#[test]
fn test_truncate_after_at_last() {
let mut range = VlsnRange::with_range(5, 10);
range.truncate_after(10);
assert_eq!(range.get_first(), 5);
assert_eq!(range.get_last(), 10);
assert_eq!(range.len(), 6);
}
#[test]
fn test_truncate_empty() {
let mut range = VlsnRange::new();
range.truncate_after(5);
assert!(range.is_empty());
}
#[test]
fn test_truncate_to_zero() {
let mut range = VlsnRange::with_range(1, 10);
range.update_commit(5);
range.update_sync(7);
range.truncate_after(0);
assert!(range.is_empty());
assert_eq!(range.get_commit_vlsn(), 0);
assert_eq!(range.get_sync_vlsn(), 0);
}
#[test]
fn test_merge_both_non_empty() {
let mut range_a = VlsnRange::with_range(5, 10);
range_a.update_commit(8);
range_a.update_sync(7);
let mut range_b = VlsnRange::with_range(8, 15);
range_b.update_commit(12);
range_b.update_sync(14);
range_a.merge(&range_b);
assert_eq!(range_a.get_first(), 5);
assert_eq!(range_a.get_last(), 15);
assert_eq!(range_a.get_commit_vlsn(), 12);
assert_eq!(range_a.get_sync_vlsn(), 14);
}
#[test]
fn test_merge_with_empty() {
let mut range_a = VlsnRange::with_range(5, 10);
range_a.update_commit(8);
let range_b = VlsnRange::new();
range_a.merge(&range_b);
assert_eq!(range_a.get_first(), 5);
assert_eq!(range_a.get_last(), 10);
assert_eq!(range_a.get_commit_vlsn(), 8);
}
#[test]
fn test_merge_empty_with_non_empty() {
let mut range_a = VlsnRange::new();
let mut range_b = VlsnRange::with_range(3, 7);
range_b.update_commit(5);
range_a.merge(&range_b);
assert_eq!(range_a.get_first(), 3);
assert_eq!(range_a.get_last(), 7);
assert_eq!(range_a.get_commit_vlsn(), 5);
}
#[test]
fn test_merge_disjoint_ranges() {
let mut range_a = VlsnRange::with_range(1, 5);
let range_b = VlsnRange::with_range(10, 15);
range_a.merge(&range_b);
assert_eq!(range_a.get_first(), 1);
assert_eq!(range_a.get_last(), 15);
}
#[test]
fn test_display() {
let mut range = VlsnRange::with_range(1, 10);
range.update_commit(5);
range.update_sync(8);
let s = format!("{}", range);
assert!(s.contains("first=1"));
assert!(s.contains("last=10"));
assert!(s.contains("commit=5"));
assert!(s.contains("sync=8"));
}
#[test]
fn test_default() {
let range = VlsnRange::default();
assert!(range.is_empty());
}
#[test]
fn test_clone_eq() {
let mut range = VlsnRange::with_range(1, 10);
range.update_commit(5);
range.update_sync(8);
let cloned = range.clone();
assert_eq!(range, cloned);
}
#[test]
fn test_commit_vlsn_le_last() {
let mut range = VlsnRange::with_range(1, 20);
range.update_commit(20);
assert!(range.get_commit_vlsn() <= range.get_last());
range.truncate_after(15);
assert!(
range.get_commit_vlsn() <= range.get_last(),
"commit vlsn must be clamped after truncation"
);
}
#[test]
fn test_sync_vlsn_le_last() {
let mut range = VlsnRange::with_range(1, 20);
range.update_sync(18);
range.truncate_after(12);
assert!(
range.get_sync_vlsn() <= range.get_last(),
"sync vlsn must be clamped after truncation"
);
}
#[test]
fn test_extend_maintains_first_le_last() {
let mut range = VlsnRange::new();
for v in [10u64, 3, 20, 7, 15, 1, 25] {
range.extend(v);
assert!(
range.get_first() <= range.get_last(),
"first <= last violated after extend({})",
v
);
}
assert_eq!(range.get_first(), 1);
assert_eq!(range.get_last(), 25);
}
#[test]
fn test_truncate_preserves_first_le_last() {
for last in 1u64..=20 {
let mut range = VlsnRange::with_range(1, 20);
range.truncate_after(last);
if !range.is_empty() {
assert!(
range.get_first() <= range.get_last(),
"first <= last violated when truncate_after({})",
last
);
}
}
}
#[test]
fn test_merge_maintains_first_le_last() {
let cases: &[(u64, u64, u64, u64)] =
&[(1, 10, 5, 15), (5, 5, 5, 5), (1, 1, 100, 100), (3, 7, 1, 4)];
for &(af, al, bf, bl) in cases {
let mut a = VlsnRange::with_range(af, al);
let b = VlsnRange::with_range(bf, bl);
a.merge(&b);
assert!(
a.get_first() <= a.get_last(),
"first <= last violated after merge ({},{}) + ({},{})",
af,
al,
bf,
bl
);
}
}
#[test]
fn test_commit_vlsn_monotone() {
let mut range = VlsnRange::with_range(1, 100);
let updates = [5u64, 10, 8, 20, 15, 30];
let mut prev = 0u64;
for &v in &updates {
range.update_commit(v);
let current = range.get_commit_vlsn();
assert!(current >= prev, "commit vlsn must not decrease");
prev = current;
}
}
#[test]
fn test_sync_vlsn_monotone() {
let mut range = VlsnRange::with_range(1, 100);
let updates = [3u64, 12, 7, 25, 18, 40];
let mut prev = 0u64;
for &v in &updates {
range.update_sync(v);
let current = range.get_sync_vlsn();
assert!(current >= prev, "sync vlsn must not decrease");
prev = current;
}
}
#[test]
fn test_empty_range_contains_nothing() {
let range = VlsnRange::new();
for v in [0u64, 1, 100, u64::MAX / 2] {
assert!(!range.contains(v));
}
}
#[test]
fn test_len_formula() {
let cases: &[(u64, u64)] = &[(1, 1), (1, 10), (5, 20), (100, 100)];
for &(f, l) in cases {
let range = VlsnRange::with_range(f, l);
assert_eq!(
range.len(),
l - f + 1,
"len mismatch for ({},{})",
f,
l
);
}
assert_eq!(VlsnRange::new().len(), 0);
}
}