use crate::types::EpochId;
use smallvec::SmallVec;
#[derive(Debug, Clone)]
pub struct VersionLog<T> {
entries: SmallVec<[(EpochId, T); 1]>,
}
impl<T> Default for VersionLog<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> VersionLog<T> {
#[must_use]
pub fn new() -> Self {
Self {
entries: SmallVec::new(),
}
}
#[must_use]
pub fn with_value(epoch: EpochId, value: T) -> Self {
let mut entries = SmallVec::new();
entries.push((epoch, value));
Self { entries }
}
#[must_use]
pub fn latest(&self) -> Option<&T> {
self.entries.last().map(|(_, v)| v)
}
#[must_use]
pub fn latest_entry(&self) -> Option<&(EpochId, T)> {
self.entries.last()
}
#[must_use]
pub fn latest_epoch(&self) -> Option<EpochId> {
self.entries.last().map(|(e, _)| *e)
}
#[must_use]
pub fn at(&self, epoch: EpochId) -> Option<&T> {
let last = self.entries.last()?;
if last.0 <= epoch {
return Some(&last.1);
}
let idx = self.entries.partition_point(|(e, _)| *e <= epoch);
if idx == 0 {
None
} else {
Some(&self.entries[idx - 1].1)
}
}
pub fn append(&mut self, epoch: EpochId, value: T) {
debug_assert!(
self.entries.last().map_or(true, |(e, _)| epoch >= *e),
"VersionLog::append: epoch {epoch:?} is before last entry {:?}",
self.entries.last().map(|(e, _)| e)
);
self.entries.push((epoch, value));
}
pub fn remove_pending(&mut self) {
while self
.entries
.last()
.is_some_and(|(e, _)| *e == EpochId::PENDING)
{
self.entries.pop();
}
}
pub fn pop_n_pending(&mut self, n: usize) {
for _ in 0..n {
if self
.entries
.last()
.is_some_and(|(e, _)| *e == EpochId::PENDING)
{
self.entries.pop();
} else {
break;
}
}
}
pub fn finalize_pending(&mut self, real_epoch: EpochId) {
for entry in self.entries.iter_mut().rev() {
if entry.0 == EpochId::PENDING {
entry.0 = real_epoch;
} else {
break;
}
}
}
pub fn truncate_after(&mut self, epoch: EpochId) {
let keep = self.entries.partition_point(|(e, _)| *e <= epoch);
self.entries.truncate(keep);
}
pub fn gc(&mut self, min_epoch: EpochId) {
if self.entries.len() <= 1 {
return;
}
let first_recent = self.entries.partition_point(|(e, _)| *e < min_epoch);
if first_recent == 0 {
return;
}
let baseline = first_recent - 1;
if baseline > 0 {
self.entries.drain(..baseline);
}
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn history(&self) -> &[(EpochId, T)] {
self.entries.as_slice()
}
pub fn iter(&self) -> impl Iterator<Item = &(EpochId, T)> {
self.entries.iter()
}
#[cfg(test)]
#[must_use]
pub fn spilled(&self) -> bool {
self.entries.spilled()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn epoch(n: u64) -> EpochId {
EpochId::new(n)
}
#[test]
fn test_empty_log() {
let log: VersionLog<i32> = VersionLog::new();
assert!(log.is_empty());
assert_eq!(log.len(), 0);
assert!(log.latest().is_none());
assert!(log.latest_entry().is_none());
assert!(log.latest_epoch().is_none());
assert!(log.at(epoch(0)).is_none());
assert!(log.history().is_empty());
}
#[test]
fn test_single_entry() {
let log = VersionLog::with_value(epoch(5), 42);
assert!(!log.is_empty());
assert_eq!(log.len(), 1);
assert_eq!(log.latest(), Some(&42));
assert_eq!(log.latest_epoch(), Some(epoch(5)));
assert!(!log.spilled());
}
#[test]
fn test_multiple_entries() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
log.append(epoch(5), 50);
assert_eq!(log.len(), 3);
assert_eq!(log.latest(), Some(&50));
assert_eq!(log.latest_epoch(), Some(epoch(5)));
}
#[test]
fn test_at_exact_match() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
log.append(epoch(5), 50);
assert_eq!(log.at(epoch(1)), Some(&10));
assert_eq!(log.at(epoch(3)), Some(&30));
assert_eq!(log.at(epoch(5)), Some(&50));
}
#[test]
fn test_at_between_versions() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
log.append(epoch(5), 50);
assert_eq!(log.at(epoch(2)), Some(&10));
assert_eq!(log.at(epoch(4)), Some(&30));
assert_eq!(log.at(epoch(100)), Some(&50));
}
#[test]
fn test_at_before_first() {
let mut log = VersionLog::new();
log.append(epoch(5), 50);
assert!(log.at(epoch(0)).is_none());
assert!(log.at(epoch(4)).is_none());
assert_eq!(log.at(epoch(5)), Some(&50));
}
#[test]
fn test_append_same_epoch() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(1), 20);
assert_eq!(log.len(), 2);
assert_eq!(log.latest(), Some(&20));
assert_eq!(log.at(epoch(1)), Some(&20));
}
#[test]
fn test_remove_pending() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(EpochId::PENDING, 20);
log.append(EpochId::PENDING, 30);
assert_eq!(log.len(), 3);
log.remove_pending();
assert_eq!(log.len(), 1);
assert_eq!(log.latest(), Some(&10));
}
#[test]
fn test_remove_pending_no_pending() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(2), 20);
log.remove_pending();
assert_eq!(log.len(), 2);
assert_eq!(log.latest(), Some(&20));
}
#[test]
fn test_remove_pending_all_pending() {
let mut log = VersionLog::new();
log.append(EpochId::PENDING, 10);
log.remove_pending();
assert!(log.is_empty());
}
#[test]
fn test_finalize_pending() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(EpochId::PENDING, 20);
log.append(EpochId::PENDING, 30);
log.finalize_pending(epoch(5));
assert_eq!(log.len(), 3);
let history = log.history();
assert_eq!(history[0].0, epoch(1));
assert_eq!(history[1].0, epoch(5));
assert_eq!(history[2].0, epoch(5));
assert_eq!(log.at(epoch(3)), Some(&10));
assert_eq!(log.at(epoch(5)), Some(&30));
}
#[test]
fn test_truncate_after() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
log.append(epoch(5), 50);
log.append(epoch(7), 70);
log.truncate_after(epoch(4));
assert_eq!(log.len(), 2);
assert_eq!(log.latest(), Some(&30));
assert_eq!(log.latest_epoch(), Some(epoch(3)));
}
#[test]
fn test_truncate_after_exact_epoch() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
log.append(epoch(5), 50);
log.truncate_after(epoch(3));
assert_eq!(log.len(), 2);
assert_eq!(log.latest(), Some(&30));
}
#[test]
fn test_truncate_after_before_all() {
let mut log = VersionLog::new();
log.append(epoch(5), 50);
log.truncate_after(epoch(1));
assert!(log.is_empty());
}
#[test]
fn test_gc_keeps_baseline_and_recent() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
log.append(epoch(5), 50);
log.append(epoch(7), 70);
log.gc(epoch(5));
assert_eq!(log.len(), 3);
let history = log.history();
assert_eq!(history[0], (epoch(3), 30));
assert_eq!(history[1], (epoch(5), 50));
assert_eq!(history[2], (epoch(7), 70));
}
#[test]
fn test_gc_all_old() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
log.gc(epoch(100));
assert_eq!(log.len(), 1);
assert_eq!(log.latest(), Some(&30));
}
#[test]
fn test_gc_all_recent() {
let mut log = VersionLog::new();
log.append(epoch(5), 50);
log.append(epoch(7), 70);
log.gc(epoch(1));
assert_eq!(log.len(), 2);
}
#[test]
fn test_gc_single_entry() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.gc(epoch(100));
assert_eq!(log.len(), 1);
}
#[test]
fn test_default() {
let log: VersionLog<i32> = VersionLog::default();
assert!(log.is_empty());
}
#[test]
fn test_smallvec_inline_single_entry() {
let log = VersionLog::with_value(epoch(1), 42);
assert!(!log.spilled());
}
#[test]
fn test_smallvec_spills_on_second_entry() {
let mut log = VersionLog::with_value(epoch(1), 42);
log.append(epoch(2), 43);
assert!(log.spilled());
}
#[test]
fn test_iter() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
let collected: Vec<_> = log.iter().collect();
assert_eq!(collected.len(), 2);
assert_eq!(collected[0], &(epoch(1), 10));
assert_eq!(collected[1], &(epoch(3), 30));
}
#[test]
fn test_history_slice() {
let mut log = VersionLog::new();
log.append(epoch(1), "first");
log.append(epoch(5), "second");
let history = log.history();
assert_eq!(history.len(), 2);
assert_eq!(history[0].1, "first");
assert_eq!(history[1].1, "second");
}
#[test]
fn test_latest_entry() {
let mut log = VersionLog::new();
log.append(epoch(3), 30);
log.append(epoch(7), 70);
let entry = log.latest_entry().unwrap();
assert_eq!(entry.0, epoch(7));
assert_eq!(entry.1, 70);
}
#[test]
fn test_pop_n_pending_partial() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(EpochId::PENDING, 20);
log.append(EpochId::PENDING, 30);
log.append(EpochId::PENDING, 40);
log.pop_n_pending(2);
assert_eq!(log.len(), 2);
assert_eq!(log.latest(), Some(&20));
assert_eq!(log.latest_epoch(), Some(EpochId::PENDING));
}
#[test]
fn test_pop_n_pending_stops_at_committed() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(EpochId::PENDING, 20);
log.pop_n_pending(5);
assert_eq!(log.len(), 1);
assert_eq!(log.latest(), Some(&10));
}
#[test]
fn test_pop_n_pending_zero() {
let mut log = VersionLog::new();
log.append(EpochId::PENDING, 10);
log.pop_n_pending(0);
assert_eq!(log.len(), 1);
}
#[test]
fn test_clone() {
let mut log = VersionLog::new();
log.append(epoch(1), 10);
log.append(epoch(3), 30);
let cloned = log.clone();
assert_eq!(cloned.len(), 2);
assert_eq!(cloned.latest(), Some(&30));
}
}