mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-08-05 23:23:05 -07:00
1963 lines
60 KiB
Rust
1963 lines
60 KiB
Rust
//! Generic rollback tests for all vec types.
|
|
//!
|
|
//! This module contains two sets of rollback tests:
|
|
//! 1. Generic rollback tests - work with ALL vec types (BytesVec, ZeroCopyVec, PcoVec, LZ4Vec, ZstdVec, EagerVec)
|
|
//! These use only push/truncate operations available on all vecs.
|
|
//! 2. Raw-only rollback tests - work with raw vecs (BytesVec, ZeroCopyVec) only
|
|
//! These test update/hole operations specific to raw vecs.
|
|
|
|
use rawdb::Database;
|
|
use std::ops::DerefMut;
|
|
use tempfile::TempDir;
|
|
use vecdb::{
|
|
AnyStoredVec, ImportOptions, ImportableVec, ReadableVec, Result, Stamp, StoredVec, VecReader,
|
|
Version, WritableVec,
|
|
};
|
|
|
|
// ============================================================================
|
|
// Test Setup
|
|
// ============================================================================
|
|
|
|
fn setup_db() -> Result<(Database, TempDir)> {
|
|
let temp = TempDir::new()?;
|
|
let db = Database::open(temp.path())?;
|
|
Ok((db, temp))
|
|
}
|
|
|
|
// ============================================================================
|
|
// PART 1: Generic Rollback Tests (ALL vec types)
|
|
// ============================================================================
|
|
// These tests use only push/truncate operations and work with any StoredVec.
|
|
|
|
mod generic_rollback {
|
|
use super::*;
|
|
|
|
fn import_with_changes<V>(db: &Database, name: &str, changes: u16) -> Result<V>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let mut options: ImportOptions = (db, name, Version::TWO).into();
|
|
options = options.with_saved_stamped_changes(changes);
|
|
V::forced_import_with(options)
|
|
}
|
|
|
|
fn run_basic_rollback<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.stamp(), Stamp::new(1));
|
|
|
|
// Stamp 2: [0, 1, 2, 3, 4, 5, 6]
|
|
vec.push(5);
|
|
vec.push(6);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 5, 6]);
|
|
assert_eq!(vec.stamp(), Stamp::new(2));
|
|
|
|
// Rollback to stamp 1
|
|
vec.rollback()?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_with_truncation<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: [0, 1, 2, 3, 4, 5, 6, 7]
|
|
vec.push(5);
|
|
vec.push(6);
|
|
vec.push(7);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 5, 6, 7]);
|
|
|
|
// Rollback - should restore to [0, 1, 2, 3, 4]
|
|
vec.rollback()?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_multiple_sequential_rollbacks<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: [0, 1, 2, 3, 4, 5]
|
|
vec.push(5);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
// Stamp 3: [0, 1, 2, 3, 4, 5, 6]
|
|
vec.push(6);
|
|
vec.stamped_write_with_changes(Stamp::new(3))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 5, 6]);
|
|
|
|
// Rollback to stamp 2
|
|
vec.rollback()?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 5]);
|
|
assert_eq!(vec.stamp(), Stamp::new(2));
|
|
|
|
// Rollback to stamp 1
|
|
vec.rollback()?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_then_save_new_state<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: [0, 1, 2, 3, 4, 5]
|
|
vec.push(5);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
// Rollback to stamp 1
|
|
vec.rollback()?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4]);
|
|
|
|
// Save new stamp 2: [0, 1, 2, 3, 4, 99]
|
|
vec.push(99);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 99]);
|
|
assert_eq!(vec.stamp(), Stamp::new(2));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_to_empty<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Stamp 1: []
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.collect(), Vec::<u32>::new());
|
|
|
|
// Stamp 2: [0, 1, 2]
|
|
vec.push(0);
|
|
vec.push(1);
|
|
vec.push(2);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2]);
|
|
|
|
// Rollback to empty
|
|
vec.rollback()?;
|
|
assert_eq!(vec.collect(), Vec::<u32>::new());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_before<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Build stamps 1-5
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
vec.push(5);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
vec.push(6);
|
|
vec.stamped_write_with_changes(Stamp::new(3))?;
|
|
|
|
vec.push(7);
|
|
vec.stamped_write_with_changes(Stamp::new(4))?;
|
|
|
|
vec.push(8);
|
|
vec.stamped_write_with_changes(Stamp::new(5))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
|
|
// Rollback before stamp 4 (should go to stamp 3)
|
|
let _ = vec.rollback_before(Stamp::new(4))?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 5, 6]);
|
|
assert_eq!(vec.stamp(), Stamp::new(3));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_deep_rollback_chain<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Build chain of stamps with pushes only
|
|
vec.stamped_write_with_changes(Stamp::new(1))?; // []
|
|
|
|
vec.push(0);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?; // [0]
|
|
|
|
vec.push(1);
|
|
vec.stamped_write_with_changes(Stamp::new(3))?; // [0, 1]
|
|
|
|
vec.push(2);
|
|
vec.stamped_write_with_changes(Stamp::new(4))?; // [0, 1, 2]
|
|
|
|
vec.push(3);
|
|
vec.push(4);
|
|
vec.stamped_write_with_changes(Stamp::new(5))?; // [0, 1, 2, 3, 4]
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4]);
|
|
|
|
// Rollback through chain
|
|
vec.rollback()?; // -> 4
|
|
assert_eq!(vec.collect(), vec![0, 1, 2]);
|
|
|
|
vec.rollback()?; // -> 3
|
|
assert_eq!(vec.collect(), vec![0, 1]);
|
|
|
|
vec.rollback()?; // -> 2
|
|
assert_eq!(vec.collect(), vec![0]);
|
|
|
|
vec.rollback()?; // -> 1
|
|
assert_eq!(vec.collect(), Vec::<u32>::new());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_persistence<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
|
|
// Create and populate
|
|
{
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
vec.push(5);
|
|
vec.push(6);
|
|
vec.stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
// Rollback and flush
|
|
vec.rollback()?;
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
}
|
|
|
|
// Reopen and verify
|
|
{
|
|
let vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.stamp(), Stamp::new(1));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_reset<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let mut vec = import_with_changes::<V>(&db, "test", 10)?;
|
|
|
|
// Add initial data and flush
|
|
for i in 0..10 {
|
|
vec.push(i);
|
|
}
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.len(), 10);
|
|
assert_eq!(vec.stored_len(), 10);
|
|
assert_eq!(vec.pushed_len(), 0);
|
|
assert_eq!(vec.collect(), vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
|
|
// Add more data without flushing
|
|
vec.push(10);
|
|
vec.push(11);
|
|
assert_eq!(vec.len(), 12);
|
|
assert_eq!(vec.stored_len(), 10);
|
|
assert_eq!(vec.pushed_len(), 2);
|
|
|
|
// Reset should clear everything
|
|
vec.reset()?;
|
|
assert_eq!(vec.len(), 0);
|
|
assert_eq!(vec.stored_len(), 0);
|
|
assert_eq!(vec.pushed_len(), 0);
|
|
assert_eq!(vec.collect(), Vec::<u32>::new());
|
|
|
|
// Should be able to add new data after reset
|
|
vec.push(100);
|
|
vec.push(101);
|
|
vec.push(102);
|
|
assert_eq!(vec.len(), 3);
|
|
assert_eq!(vec.stored_len(), 0);
|
|
assert_eq!(vec.pushed_len(), 3);
|
|
assert_eq!(vec.collect(), vec![100, 101, 102]);
|
|
|
|
// Flush the new data
|
|
vec.stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.len(), 3);
|
|
assert_eq!(vec.stored_len(), 3);
|
|
assert_eq!(vec.pushed_len(), 0);
|
|
assert_eq!(vec.collect(), vec![100, 101, 102]);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Test modules for each vec type
|
|
mod bytes {
|
|
use super::*;
|
|
use vecdb::BytesVec;
|
|
type V = BytesVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn basic_rollback() -> Result<()> {
|
|
run_basic_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_persistence() -> Result<()> {
|
|
run_rollback_persistence::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
mod zerocopy {
|
|
use super::*;
|
|
use vecdb::ZeroCopyVec;
|
|
type V = ZeroCopyVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn basic_rollback() -> Result<()> {
|
|
run_basic_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_persistence() -> Result<()> {
|
|
run_rollback_persistence::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "pco")]
|
|
mod pco {
|
|
use super::*;
|
|
use vecdb::PcoVec;
|
|
type V = PcoVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn basic_rollback() -> Result<()> {
|
|
run_basic_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_persistence() -> Result<()> {
|
|
run_rollback_persistence::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "lz4")]
|
|
mod lz4 {
|
|
use super::*;
|
|
use vecdb::LZ4Vec;
|
|
type V = LZ4Vec<usize, u32>;
|
|
|
|
#[test]
|
|
fn basic_rollback() -> Result<()> {
|
|
run_basic_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_persistence() -> Result<()> {
|
|
run_rollback_persistence::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "zstd")]
|
|
mod zstd {
|
|
use super::*;
|
|
use vecdb::ZstdVec;
|
|
type V = ZstdVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn basic_rollback() -> Result<()> {
|
|
run_basic_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_persistence() -> Result<()> {
|
|
run_rollback_persistence::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
mod eager_zerocopy {
|
|
use super::*;
|
|
use vecdb::{EagerVec, ZeroCopyVec};
|
|
type V = EagerVec<ZeroCopyVec<usize, u32>>;
|
|
|
|
#[test]
|
|
fn basic_rollback() -> Result<()> {
|
|
run_basic_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_persistence() -> Result<()> {
|
|
run_rollback_persistence::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "pco")]
|
|
mod eager_pco {
|
|
use super::*;
|
|
use vecdb::{EagerVec, PcoVec};
|
|
type V = EagerVec<PcoVec<usize, u32>>;
|
|
|
|
#[test]
|
|
fn basic_rollback() -> Result<()> {
|
|
run_basic_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_persistence() -> Result<()> {
|
|
run_rollback_persistence::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PART 2: Raw-Only Rollback Tests (BytesVec and ZeroCopyVec)
|
|
// ============================================================================
|
|
// These tests use update/hole operations specific to raw vecs.
|
|
|
|
mod raw_rollback {
|
|
use super::*;
|
|
|
|
// ============================================================================
|
|
// Trait for raw vec rollback operations
|
|
// ============================================================================
|
|
|
|
/// Trait for raw vecs that support rollback operations.
|
|
pub trait RollbackVec: StoredVec<I = usize, T = u32> + DerefMut
|
|
where
|
|
Self::Target: RollbackOps,
|
|
{
|
|
fn import_with_changes<'a>(
|
|
db: &'a Database,
|
|
name: &'a str,
|
|
changes: u16,
|
|
) -> Result<(Self, ImportOptions<'a>)>;
|
|
}
|
|
|
|
/// Operations required for rollback testing.
|
|
pub trait RollbackOps {
|
|
type Reader;
|
|
|
|
fn update(&mut self, index: usize, value: u32) -> Result<()>;
|
|
fn take(&mut self, index: usize) -> Option<u32>;
|
|
fn stamped_write_with_changes(&mut self, stamp: Stamp) -> Result<()>;
|
|
fn rollback(&mut self) -> Result<()>;
|
|
fn rollback_before(&mut self, stamp: Stamp) -> Result<Stamp>;
|
|
fn stamp(&self) -> Stamp;
|
|
fn stored_len(&self) -> usize;
|
|
fn collect(&self) -> Vec<u32>;
|
|
fn collect_holed(&self) -> Vec<Option<u32>>;
|
|
fn get_with_reader(&self, index: usize, reader: &Self::Reader) -> Option<u32>;
|
|
fn reader(&self) -> Self::Reader;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Implementations for ZeroCopyVec
|
|
// ============================================================================
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
use vecdb::{ReadWriteRawVec, ZeroCopyStrategy, ZeroCopyVec};
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
impl RollbackVec for ZeroCopyVec<usize, u32> {
|
|
fn import_with_changes<'a>(
|
|
db: &'a Database,
|
|
name: &'a str,
|
|
changes: u16,
|
|
) -> Result<(Self, ImportOptions<'a>)> {
|
|
let mut options: ImportOptions = (db, name, Version::TWO).into();
|
|
options = options.with_saved_stamped_changes(changes);
|
|
let vec = Self::forced_import_with(options)?;
|
|
Ok((vec, options))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
impl RollbackOps for ReadWriteRawVec<usize, u32, ZeroCopyStrategy<u32>> {
|
|
type Reader = VecReader<usize, u32, ZeroCopyStrategy<u32>>;
|
|
|
|
fn update(&mut self, index: usize, value: u32) -> Result<()> {
|
|
ReadWriteRawVec::update(self, index, value)
|
|
}
|
|
|
|
fn take(&mut self, index: usize) -> Option<u32> {
|
|
let reader = self.reader();
|
|
let result = ReadWriteRawVec::take(self, index, &reader);
|
|
drop(reader);
|
|
result
|
|
}
|
|
|
|
fn stamped_write_with_changes(&mut self, stamp: Stamp) -> Result<()> {
|
|
WritableVec::stamped_write_with_changes(self, stamp)
|
|
}
|
|
|
|
fn rollback(&mut self) -> Result<()> {
|
|
WritableVec::rollback(self)
|
|
}
|
|
|
|
fn rollback_before(&mut self, stamp: Stamp) -> Result<Stamp> {
|
|
WritableVec::rollback_before(self, stamp)
|
|
}
|
|
|
|
fn stamp(&self) -> Stamp {
|
|
AnyStoredVec::stamp(self)
|
|
}
|
|
|
|
fn stored_len(&self) -> usize {
|
|
AnyStoredVec::stored_len(self)
|
|
}
|
|
|
|
fn collect(&self) -> Vec<u32> {
|
|
ReadableVec::collect(self)
|
|
}
|
|
|
|
fn collect_holed(&self) -> Vec<Option<u32>> {
|
|
ReadWriteRawVec::collect_holed(self)
|
|
}
|
|
|
|
fn get_with_reader(&self, index: usize, reader: &Self::Reader) -> Option<u32> {
|
|
ReadWriteRawVec::get_with_reader(self, index, reader)
|
|
}
|
|
|
|
fn reader(&self) -> Self::Reader {
|
|
ReadWriteRawVec::reader(self)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Implementations for BytesVec
|
|
// ============================================================================
|
|
|
|
#[cfg(not(feature = "zerocopy"))]
|
|
use vecdb::ReadWriteRawVec;
|
|
use vecdb::{BytesStrategy, BytesVec};
|
|
|
|
impl RollbackVec for BytesVec<usize, u32> {
|
|
fn import_with_changes<'a>(
|
|
db: &'a Database,
|
|
name: &'a str,
|
|
changes: u16,
|
|
) -> Result<(Self, ImportOptions<'a>)> {
|
|
let mut options: ImportOptions = (db, name, Version::TWO).into();
|
|
options = options.with_saved_stamped_changes(changes);
|
|
let vec = Self::forced_import_with(options)?;
|
|
Ok((vec, options))
|
|
}
|
|
}
|
|
|
|
impl RollbackOps for ReadWriteRawVec<usize, u32, BytesStrategy<u32>> {
|
|
type Reader = VecReader<usize, u32, BytesStrategy<u32>>;
|
|
|
|
fn update(&mut self, index: usize, value: u32) -> Result<()> {
|
|
ReadWriteRawVec::update(self, index, value)
|
|
}
|
|
|
|
fn take(&mut self, index: usize) -> Option<u32> {
|
|
let reader = self.reader();
|
|
let result = ReadWriteRawVec::take(self, index, &reader);
|
|
drop(reader);
|
|
result
|
|
}
|
|
|
|
fn stamped_write_with_changes(&mut self, stamp: Stamp) -> Result<()> {
|
|
WritableVec::stamped_write_with_changes(self, stamp)
|
|
}
|
|
|
|
fn rollback(&mut self) -> Result<()> {
|
|
WritableVec::rollback(self)
|
|
}
|
|
|
|
fn rollback_before(&mut self, stamp: Stamp) -> Result<Stamp> {
|
|
WritableVec::rollback_before(self, stamp)
|
|
}
|
|
|
|
fn stamp(&self) -> Stamp {
|
|
AnyStoredVec::stamp(self)
|
|
}
|
|
|
|
fn stored_len(&self) -> usize {
|
|
AnyStoredVec::stored_len(self)
|
|
}
|
|
|
|
fn collect(&self) -> Vec<u32> {
|
|
ReadableVec::collect(self)
|
|
}
|
|
|
|
fn collect_holed(&self) -> Vec<Option<u32>> {
|
|
ReadWriteRawVec::collect_holed(self)
|
|
}
|
|
|
|
fn get_with_reader(&self, index: usize, reader: &Self::Reader) -> Option<u32> {
|
|
ReadWriteRawVec::get_with_reader(self, index, reader)
|
|
}
|
|
|
|
fn reader(&self) -> Self::Reader {
|
|
ReadWriteRawVec::reader(self)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Generic Rollback Test Functions
|
|
// ============================================================================
|
|
|
|
fn run_basic_single_rollback<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Initial state: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
// Modify to [0, 1, 99, 3, 4]
|
|
vec.deref_mut().update(2, 99)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 99, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(2));
|
|
|
|
// Rollback to stamp 1
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_with_truncation<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Initial state: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
|
|
// Add more: [0, 1, 2, 3, 4, 5, 6, 7]
|
|
vec.push(5);
|
|
vec.push(6);
|
|
vec.push(7);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4, 5, 6, 7]);
|
|
|
|
// Rollback - should restore to [0, 1, 2, 3, 4]
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_multiple_sequential_rollbacks<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: [0, 1, 2, 3, 4, 5]
|
|
vec.push(5);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
// Stamp 3: [0, 1, 2, 3, 4, 5, 6]
|
|
vec.push(6);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(3))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4, 5, 6]);
|
|
|
|
// Rollback to stamp 2
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4, 5]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(2));
|
|
|
|
// Rollback to stamp 1
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_then_save_new_state<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: [0, 1, 2, 3, 4, 5]
|
|
vec.push(5);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
// Rollback to stamp 1
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
|
|
// Now save a different state 2: [0, 1, 2, 3, 4, 99]
|
|
vec.push(99);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4, 99]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(2));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_with_updates<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: [0, 99, 2, 88, 4] - update multiple values
|
|
vec.deref_mut().update(1, 99)?;
|
|
vec.deref_mut().update(3, 88)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 99, 2, 88, 4]);
|
|
|
|
// Rollback to stamp 1 - should restore original values
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_with_holes<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: delete some items (creating holes)
|
|
let _ = vec.deref_mut().take(1);
|
|
let _ = vec.deref_mut().take(3);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 2, 4]);
|
|
|
|
// Rollback to stamp 1 - should restore deleted items
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_with_truncation_and_updates<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: extend + update
|
|
vec.deref_mut().update(1, 99)?;
|
|
vec.push(5);
|
|
vec.push(6);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 99, 2, 3, 4, 5, 6]);
|
|
|
|
// Rollback - should restore length AND value
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_with_holes_and_updates<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: delete + update
|
|
let _ = vec.deref_mut().take(1);
|
|
vec.deref_mut().update(2, 99)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 99, 3, 4]);
|
|
|
|
// Rollback - should restore deleted item AND original value
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_multiple_updates_to_same_index<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: [100, 1, 2, 3, 4]
|
|
vec.deref_mut().update(0, 100)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
// Stamp 3: [200, 1, 2, 3, 4]
|
|
vec.deref_mut().update(0, 200)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(3))?;
|
|
|
|
// Stamp 4: [300, 1, 2, 3, 4]
|
|
vec.deref_mut().update(0, 300)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(4))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![300, 1, 2, 3, 4]);
|
|
|
|
// Rollback to stamp 3
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![200, 1, 2, 3, 4]);
|
|
|
|
// Rollback to stamp 2
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![100, 1, 2, 3, 4]);
|
|
|
|
// Rollback to stamp 1
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_complex_mixed_operations<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
for i in 0..10 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: Complex operations
|
|
// - Delete indices 1, 3, 5
|
|
// - Update indices 2, 6, 8
|
|
// - Push new values 100, 101
|
|
let _ = vec.deref_mut().take(1);
|
|
let _ = vec.deref_mut().take(3);
|
|
let _ = vec.deref_mut().take(5);
|
|
vec.deref_mut().update(2, 222)?;
|
|
vec.deref_mut().update(6, 666)?;
|
|
vec.deref_mut().update(8, 888)?;
|
|
vec.push(100);
|
|
vec.push(101);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(
|
|
vec.deref_mut().collect(),
|
|
vec![0, 222, 4, 666, 7, 888, 9, 100, 101]
|
|
);
|
|
|
|
// Rollback - should restore everything
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(
|
|
vec.deref_mut().collect(),
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_to_empty<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: []
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.deref_mut().collect(), Vec::<u32>::new());
|
|
|
|
// Stamp 2: [0, 1, 2]
|
|
vec.push(0);
|
|
vec.push(1);
|
|
vec.push(2);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2]);
|
|
|
|
// Rollback to empty
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), Vec::<u32>::new());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_reset<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Add initial data and flush
|
|
for i in 0..10 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.len(), 10);
|
|
assert_eq!(vec.deref_mut().stored_len(), 10);
|
|
assert_eq!(vec.pushed_len(), 0);
|
|
assert_eq!(
|
|
vec.deref_mut().collect(),
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
);
|
|
|
|
// Add more data without flushing
|
|
vec.push(10);
|
|
vec.push(11);
|
|
assert_eq!(vec.len(), 12);
|
|
assert_eq!(vec.deref_mut().stored_len(), 10);
|
|
assert_eq!(vec.pushed_len(), 2);
|
|
|
|
// Reset should clear everything
|
|
vec.reset()?;
|
|
assert_eq!(vec.len(), 0);
|
|
assert_eq!(vec.deref_mut().stored_len(), 0);
|
|
assert_eq!(vec.pushed_len(), 0);
|
|
assert_eq!(vec.deref_mut().collect(), Vec::<u32>::new());
|
|
|
|
// Should be able to add new data after reset
|
|
vec.push(100);
|
|
vec.push(101);
|
|
vec.push(102);
|
|
assert_eq!(vec.len(), 3);
|
|
assert_eq!(vec.deref_mut().stored_len(), 0);
|
|
assert_eq!(vec.pushed_len(), 3);
|
|
assert_eq!(vec.deref_mut().collect(), vec![100, 101, 102]);
|
|
|
|
// Flush the new data
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.len(), 3);
|
|
assert_eq!(vec.deref_mut().stored_len(), 3);
|
|
assert_eq!(vec.pushed_len(), 0);
|
|
assert_eq!(vec.deref_mut().collect(), vec![100, 101, 102]);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_deep_rollback_chain<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Build a chain of 10 stamps with different operations
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?; // []
|
|
|
|
vec.push(0);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?; // [0]
|
|
|
|
vec.push(1);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(3))?; // [0, 1]
|
|
|
|
vec.deref_mut().update(0, 10)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(4))?; // [10, 1]
|
|
|
|
vec.push(2);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(5))?; // [10, 1, 2]
|
|
|
|
let _ = vec.deref_mut().take(1);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(6))?; // [10, 2]
|
|
|
|
vec.push(3);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(7))?; // [10, 2, 3]
|
|
|
|
vec.deref_mut().update(0, 20)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(8))?; // [20, 2, 3]
|
|
|
|
vec.push(4);
|
|
vec.push(5);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(9))?; // [20, 2, 3, 4, 5]
|
|
|
|
vec.deref_mut().update(2, 33)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(10))?; // [20, 33, 3, 4, 5]
|
|
assert_eq!(vec.deref_mut().collect(), vec![20, 33, 3, 4, 5]);
|
|
|
|
// Rollback through the chain
|
|
vec.deref_mut().rollback()?; // -> 9
|
|
assert_eq!(vec.deref_mut().collect(), vec![20, 2, 3, 4, 5]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 8
|
|
assert_eq!(vec.deref_mut().collect(), vec![20, 2, 3]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 7
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 2, 3]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 6
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 2]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 5
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 1, 2]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 4
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 1]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 3
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 2
|
|
assert_eq!(vec.deref_mut().collect(), vec![0]);
|
|
|
|
vec.deref_mut().rollback()?; // -> 1
|
|
assert_eq!(vec.deref_mut().collect(), Vec::<u32>::new());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_all_elements_updated<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4]
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: Update ALL elements
|
|
for i in 0..5 {
|
|
vec.deref_mut().update(i, (i * 100) as u32)?;
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 100, 200, 300, 400]);
|
|
|
|
// Rollback - should restore all original values
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4]);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_multiple_holes_then_rollback<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
for i in 0..10 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Stamp 2: Delete every other element
|
|
for i in (0..10).step_by(2) {
|
|
let _ = vec.deref_mut().take(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![1, 3, 5, 7, 9]);
|
|
|
|
// Rollback - should restore all deleted items
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(
|
|
vec.deref_mut().collect(),
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_before<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Build stamps 1-5
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
vec.push(5);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
vec.push(6);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(3))?;
|
|
|
|
vec.push(7);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(4))?;
|
|
|
|
vec.push(8);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(5))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
|
|
// Rollback before stamp 4 (should go to stamp 3)
|
|
let _ = vec.deref_mut().rollback_before(Stamp::new(4))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![0, 1, 2, 3, 4, 5, 6]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(3));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Regression test: rollback-after-rollback with delete_at losing entries.
|
|
///
|
|
/// After the first rollback, restored entries sit in `updated.current`.
|
|
/// If `delete_at` removes one from `updated.current` during reprocessing,
|
|
/// and `serialize_changes` only iterated `updated.current` keys (the old bug),
|
|
/// the entry's prev value would be lost from the change file.
|
|
/// On a second rollback, the slot would contain stale on-disk data
|
|
/// instead of the correct rolled-back value.
|
|
fn run_rollback_after_rollback_with_delete<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
// Stamp 1 (baseline): [10, 20, 30, 40, 50]
|
|
for &v in &[10, 20, 30, 40, 50] {
|
|
vec.push(v);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 20, 30, 40, 50]);
|
|
|
|
// Stamp 2: update slot 2 (30 → 99), delete slot 1 (creates hole)
|
|
vec.deref_mut().update(2, 99)?;
|
|
let _ = vec.deref_mut().take(1);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 99, 40, 50]);
|
|
|
|
// First rollback → back to stamp 1
|
|
vec.deref_mut().rollback()?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 20, 30, 40, 50]);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
// Now reprocess: delete slot 2 (the one we just restored), update slot 3
|
|
// This simulates an address becoming empty during reprocessing
|
|
let _ = vec.deref_mut().take(2); // removes 30 from updated.current
|
|
vec.deref_mut().update(3, 88)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(3))?;
|
|
assert_eq!(vec.deref_mut().collect(), vec![10, 20, 88, 50]);
|
|
|
|
// Second rollback → must go back to stamp 1 values
|
|
vec.deref_mut().rollback()?;
|
|
let result = vec.deref_mut().collect();
|
|
assert_eq!(
|
|
result,
|
|
vec![10, 20, 30, 40, 50],
|
|
"Second rollback must restore all original values. \
|
|
Slot 2 was deleted during reprocessing but its prev value (30) \
|
|
must still be tracked in the change file."
|
|
);
|
|
assert_eq!(vec.deref_mut().stamp(), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_rollback_after_untracked_checkpoint<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps + AnyStoredVec,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let (mut vec, _) = V::import_with_changes(&db, "test", 10)?;
|
|
|
|
for i in 0..100 {
|
|
vec.push(i);
|
|
}
|
|
|
|
AnyStoredVec::any_stamped_write_maybe_with_changes(vec.deref_mut(), Stamp::new(1), false)?;
|
|
|
|
vec.deref_mut().update(65, 999)?;
|
|
AnyStoredVec::any_stamped_write_maybe_with_changes(vec.deref_mut(), Stamp::new(2), true)?;
|
|
|
|
vec.deref_mut().rollback()?;
|
|
|
|
assert_eq!(vec.len(), 100);
|
|
assert_eq!(vec.deref_mut().collect()[65], 65);
|
|
assert_eq!(RollbackOps::stamp(vec.deref_mut()), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ============================================================================
|
|
// Test instantiation for each raw vec type
|
|
// ============================================================================
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
mod zerocopy {
|
|
use super::*;
|
|
use vecdb::ZeroCopyVec;
|
|
type V = ZeroCopyVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn basic_single_rollback() -> Result<()> {
|
|
run_basic_single_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_updates() -> Result<()> {
|
|
run_rollback_with_updates::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_holes() -> Result<()> {
|
|
run_rollback_with_holes::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation_and_updates() -> Result<()> {
|
|
run_rollback_with_truncation_and_updates::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_holes_and_updates() -> Result<()> {
|
|
run_rollback_with_holes_and_updates::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_updates_to_same_index() -> Result<()> {
|
|
run_multiple_updates_to_same_index::<V>()
|
|
}
|
|
#[test]
|
|
fn complex_mixed_operations() -> Result<()> {
|
|
run_complex_mixed_operations::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_all_elements_updated() -> Result<()> {
|
|
run_rollback_all_elements_updated::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_holes_then_rollback() -> Result<()> {
|
|
run_multiple_holes_then_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_after_rollback_with_delete() -> Result<()> {
|
|
run_rollback_after_rollback_with_delete::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_after_untracked_checkpoint() -> Result<()> {
|
|
run_rollback_after_untracked_checkpoint::<V>()
|
|
}
|
|
}
|
|
|
|
mod bytes {
|
|
use super::*;
|
|
use vecdb::BytesVec;
|
|
type V = BytesVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn basic_single_rollback() -> Result<()> {
|
|
run_basic_single_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation() -> Result<()> {
|
|
run_rollback_with_truncation::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_sequential_rollbacks() -> Result<()> {
|
|
run_multiple_sequential_rollbacks::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_then_save_new_state() -> Result<()> {
|
|
run_rollback_then_save_new_state::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_updates() -> Result<()> {
|
|
run_rollback_with_updates::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_holes() -> Result<()> {
|
|
run_rollback_with_holes::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_truncation_and_updates() -> Result<()> {
|
|
run_rollback_with_truncation_and_updates::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_with_holes_and_updates() -> Result<()> {
|
|
run_rollback_with_holes_and_updates::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_updates_to_same_index() -> Result<()> {
|
|
run_multiple_updates_to_same_index::<V>()
|
|
}
|
|
#[test]
|
|
fn complex_mixed_operations() -> Result<()> {
|
|
run_complex_mixed_operations::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_to_empty() -> Result<()> {
|
|
run_rollback_to_empty::<V>()
|
|
}
|
|
#[test]
|
|
fn deep_rollback_chain() -> Result<()> {
|
|
run_deep_rollback_chain::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_all_elements_updated() -> Result<()> {
|
|
run_rollback_all_elements_updated::<V>()
|
|
}
|
|
#[test]
|
|
fn multiple_holes_then_rollback() -> Result<()> {
|
|
run_multiple_holes_then_rollback::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_before() -> Result<()> {
|
|
run_rollback_before::<V>()
|
|
}
|
|
#[test]
|
|
fn reset() -> Result<()> {
|
|
run_reset::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_after_rollback_with_delete() -> Result<()> {
|
|
run_rollback_after_rollback_with_delete::<V>()
|
|
}
|
|
#[test]
|
|
fn rollback_after_untracked_checkpoint() -> Result<()> {
|
|
run_rollback_after_untracked_checkpoint::<V>()
|
|
}
|
|
}
|
|
} // end mod raw_rollback
|
|
|
|
// ============================================================================
|
|
// PART 3: Checkpoint Rollback Tests (ALL vec types)
|
|
// ============================================================================
|
|
|
|
mod checkpoint_rollback {
|
|
use super::*;
|
|
|
|
fn run<V>() -> Result<()>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let (db, _temp) = setup_db()?;
|
|
let options = ImportOptions::new(&db, "test", Version::TWO).with_saved_stamped_changes(10);
|
|
let mut vec = V::forced_import_with(options)?;
|
|
|
|
for i in 0..100 {
|
|
vec.push(i);
|
|
}
|
|
AnyStoredVec::any_stamped_write_maybe_with_changes(&mut vec, Stamp::new(1), false)?;
|
|
|
|
vec.push(100);
|
|
AnyStoredVec::any_stamped_write_maybe_with_changes(&mut vec, Stamp::new(2), true)?;
|
|
WritableVec::rollback(&mut vec)?;
|
|
|
|
assert_eq!(vec.collect(), (0..100).collect::<Vec<_>>());
|
|
assert_eq!(AnyStoredVec::stamp(&vec), Stamp::new(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn bytes() -> Result<()> {
|
|
run::<vecdb::BytesVec<usize, u32>>()
|
|
}
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
#[test]
|
|
fn zerocopy() -> Result<()> {
|
|
run::<vecdb::ZeroCopyVec<usize, u32>>()
|
|
}
|
|
|
|
#[cfg(feature = "pco")]
|
|
#[test]
|
|
fn pco() -> Result<()> {
|
|
run::<vecdb::PcoVec<usize, u32>>()
|
|
}
|
|
|
|
#[cfg(feature = "lz4")]
|
|
#[test]
|
|
fn lz4() -> Result<()> {
|
|
run::<vecdb::LZ4Vec<usize, u32>>()
|
|
}
|
|
|
|
#[cfg(feature = "zstd")]
|
|
#[test]
|
|
fn zstd() -> Result<()> {
|
|
run::<vecdb::ZstdVec<usize, u32>>()
|
|
}
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
#[test]
|
|
fn eager_zerocopy() -> Result<()> {
|
|
run::<vecdb::EagerVec<vecdb::ZeroCopyVec<usize, u32>>>()
|
|
}
|
|
|
|
#[cfg(feature = "pco")]
|
|
#[test]
|
|
fn eager_pco() -> Result<()> {
|
|
run::<vecdb::EagerVec<vecdb::PcoVec<usize, u32>>>()
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PART 4: Comprehensive Integration Test
|
|
// ============================================================================
|
|
// Complex rollback + flush + reopen test with file integrity verification.
|
|
|
|
mod integration {
|
|
use crate::raw_rollback::{RollbackOps, RollbackVec};
|
|
|
|
use super::*;
|
|
use sha2::{Digest, Sha256};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
/// Compute SHA-256 hash of the vecdb data file and regions directory
|
|
/// Only hashes data (file) and regions/*, ignoring changes directory
|
|
fn compute_directory_hash(dir: &Path) -> Result<String> {
|
|
use std::path::PathBuf;
|
|
|
|
let mut hasher = Sha256::new();
|
|
|
|
// Collect all files in sorted order for deterministic hashing
|
|
let mut files: Vec<PathBuf> = Vec::new();
|
|
|
|
// Hash the data file if it exists
|
|
let data_file = dir.join("data");
|
|
if data_file.exists() && data_file.is_file() {
|
|
files.push(data_file);
|
|
}
|
|
|
|
// Hash files in the regions directory, excluding changes subdirectory
|
|
let regions_dir = dir.join("regions");
|
|
if regions_dir.exists() {
|
|
fn collect_files(dir: &Path, files: &mut Vec<PathBuf>) {
|
|
let Ok(entries) = fs::read_dir(dir) else {
|
|
return;
|
|
};
|
|
for entry in entries.filter_map(|e| e.ok()) {
|
|
let path = entry.path();
|
|
if path.components().any(|c| c.as_os_str() == "changes") {
|
|
continue;
|
|
}
|
|
if path.is_dir() {
|
|
collect_files(&path, files);
|
|
} else if path.is_file() {
|
|
files.push(path);
|
|
}
|
|
}
|
|
}
|
|
collect_files(®ions_dir, &mut files);
|
|
}
|
|
|
|
files.sort();
|
|
|
|
// Hash each file's relative path and contents
|
|
for file_path in &files {
|
|
// Hash the relative path
|
|
if let Ok(rel_path) = file_path.strip_prefix(dir) {
|
|
hasher.update(rel_path.to_string_lossy().as_bytes());
|
|
}
|
|
|
|
// Hash the file contents
|
|
let contents = fs::read(file_path)?;
|
|
hasher.update(&contents);
|
|
}
|
|
|
|
let hash = hasher.finalize();
|
|
Ok(hash.iter().map(|b| format!("{:02x}", b)).collect())
|
|
}
|
|
|
|
/// Comprehensive integration test: rollback + flush + reopen with integrity verification.
|
|
///
|
|
/// This test verifies that after rollback + flush + close + reopen:
|
|
/// 1. Data can be correctly read back using individual gets
|
|
/// 2. Data can be correctly read back using iterators
|
|
/// 3. Redo operations produce the same readable state
|
|
fn run_data_integrity_rollback_flush_reopen<V>() -> Result<()>
|
|
where
|
|
V: RollbackVec,
|
|
V::Target: RollbackOps,
|
|
{
|
|
// Create database
|
|
let (database, temp) = setup_db()?;
|
|
let test_path = temp.path();
|
|
|
|
let (mut vec, _) = V::import_with_changes(&database, "vec", 10)?;
|
|
|
|
// Phase 1: Initial work
|
|
for i in 0..5 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(1))?;
|
|
|
|
// Phase 2: More work
|
|
for i in 5..10 {
|
|
vec.push(i);
|
|
}
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(2))?;
|
|
|
|
// Checkpoint 1
|
|
let checkpoint1_data = vec.deref_mut().collect_holed();
|
|
let checkpoint1_stamp = vec.deref_mut().stamp();
|
|
let _checkpoint1_hash = compute_directory_hash(test_path)?;
|
|
|
|
// Phase 3: Three more operations with flush
|
|
vec.deref_mut().update(2, 100)?;
|
|
vec.deref_mut().update(7, 200)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(3))?;
|
|
|
|
vec.push(20);
|
|
vec.push(21);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(4))?;
|
|
|
|
let _ = vec.deref_mut().take(5);
|
|
vec.push(30);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(5))?;
|
|
|
|
// Checkpoint 2
|
|
let checkpoint2_data = vec.deref_mut().collect_holed();
|
|
let checkpoint2_stamp = vec.deref_mut().stamp();
|
|
let _checkpoint2_hash = compute_directory_hash(test_path)?;
|
|
|
|
// Undo last 3 operations
|
|
vec.deref_mut().rollback()?;
|
|
vec.deref_mut().rollback()?;
|
|
vec.deref_mut().rollback()?;
|
|
|
|
// Verify in-memory data matches checkpoint1
|
|
let after_undo_data = vec.deref_mut().collect_holed();
|
|
let after_undo_stamp = vec.deref_mut().stamp();
|
|
|
|
assert_eq!(after_undo_stamp, checkpoint1_stamp);
|
|
assert_eq!(after_undo_data, checkpoint1_data);
|
|
|
|
// Flush and close
|
|
vec.deref_mut()
|
|
.stamped_write_with_changes(checkpoint1_stamp)?;
|
|
let _after_flush_hash = compute_directory_hash(test_path)?;
|
|
|
|
drop(vec);
|
|
|
|
// Reopen
|
|
let (mut vec, _) = V::import_with_changes(&database, "vec", 10)?;
|
|
|
|
// Verify using individual gets
|
|
let reader = vec.deref_mut().reader();
|
|
let mut data_via_gets = Vec::new();
|
|
for i in 0..vec.len() {
|
|
let value = vec.get_with_reader(i, &reader);
|
|
data_via_gets.push(value);
|
|
}
|
|
drop(reader);
|
|
|
|
assert_eq!(data_via_gets, checkpoint1_data);
|
|
|
|
// Verify using iterator
|
|
let data_via_iter = vec.deref_mut().collect_holed();
|
|
assert_eq!(data_via_iter, checkpoint1_data);
|
|
|
|
// Redo the same 3 operations
|
|
vec.deref_mut().update(2, 100)?;
|
|
vec.deref_mut().update(7, 200)?;
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(3))?;
|
|
|
|
vec.push(20);
|
|
vec.push(21);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(4))?;
|
|
|
|
let _ = vec.deref_mut().take(5);
|
|
vec.push(30);
|
|
vec.deref_mut().stamped_write_with_changes(Stamp::new(5))?;
|
|
|
|
// Verify in-memory data matches checkpoint2
|
|
let after_redo_data = vec.deref_mut().collect_holed();
|
|
let after_redo_stamp = vec.deref_mut().stamp();
|
|
|
|
assert_eq!(after_redo_stamp, checkpoint2_stamp);
|
|
assert_eq!(after_redo_data, checkpoint2_data);
|
|
|
|
// Flush and close
|
|
vec.deref_mut()
|
|
.stamped_write_with_changes(checkpoint2_stamp)?;
|
|
drop(vec);
|
|
|
|
// Reopen again
|
|
let (vec, _) = V::import_with_changes(&database, "vec", 10)?;
|
|
|
|
// Verify using individual gets
|
|
let reader = vec.deref().reader();
|
|
let mut data_via_gets = Vec::new();
|
|
for i in 0..vec.len() {
|
|
let value = vec.get_with_reader(i, &reader);
|
|
data_via_gets.push(value);
|
|
}
|
|
drop(reader);
|
|
|
|
assert_eq!(data_via_gets, checkpoint2_data);
|
|
|
|
// Verify using iterator
|
|
let data_via_iter = vec.deref().collect_holed();
|
|
assert_eq!(data_via_iter, checkpoint2_data);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
mod zerocopy {
|
|
use super::*;
|
|
use vecdb::ZeroCopyVec;
|
|
type V = ZeroCopyVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn data_integrity_rollback_flush_reopen() -> Result<()> {
|
|
run_data_integrity_rollback_flush_reopen::<V>()
|
|
}
|
|
}
|
|
|
|
mod bytes {
|
|
use super::*;
|
|
use vecdb::BytesVec;
|
|
type V = BytesVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn data_integrity_rollback_flush_reopen() -> Result<()> {
|
|
run_data_integrity_rollback_flush_reopen::<V>()
|
|
}
|
|
}
|
|
}
|