vecdb 0.12.1

High-performance mutable persistent vectors built on rawdb
Documentation
use tempfile::tempdir;
use vecdb::{
    AnyStoredVec, AnyVec, Bytes, ImportOptions, ImportableVec, OverflowVec, OverflowVecValue,
    ReadableVec, Stamp, Version, WritableVec,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct TestValue(u64);

impl Bytes for TestValue {
    type Array = [u8; 8];

    fn to_bytes(&self) -> Self::Array {
        self.0.to_le_bytes()
    }

    fn from_bytes(bytes: &[u8]) -> vecdb::Result<Self> {
        Ok(Self(u64::from_bytes(bytes)?))
    }
}

impl OverflowVecValue for TestValue {
    type Compact = u8;

    const VERSION: Version = Version::ONE;

    fn to_compact(&self) -> Option<Self::Compact> {
        u8::try_from(self.0).ok().filter(|value| *value < 128)
    }

    fn from_compact(compact: Self::Compact) -> Self {
        debug_assert!(compact < 128);
        Self(u64::from(compact))
    }

    fn overflow_index(compact: Self::Compact) -> Option<usize> {
        (compact >= 128).then_some(usize::from(compact & 127))
    }

    fn from_overflow_index(index: usize) -> Self::Compact {
        assert!(index < 128);
        128 | index as u8
    }
}

#[test]
fn large_range_decodes_inline_and_overflow_values() -> vecdb::Result<()> {
    let temp = tempdir()?;
    let db = vecdb::Database::open(temp.path())?;
    let mut vec = OverflowVec::<usize, TestValue>::forced_import(&db, "large", Version::ONE)?;
    let expected: Vec<_> = (0..70_000)
        .map(|index| {
            if index % 2_048 == 0 {
                TestValue(1_000 + index as u64)
            } else {
                TestValue((index % 128) as u64)
            }
        })
        .collect();

    for value in &expected {
        vec.push(*value);
    }
    vec.write()?;

    assert_eq!(vec.collect(), expected);
    assert_eq!(vec.read_only_clone().collect(), expected);
    Ok(())
}

#[test]
fn roundtrip_updates_holes_and_read_only_visibility() -> vecdb::Result<()> {
    let temp = tempdir()?;
    let db = vecdb::Database::open(temp.path())?;
    let mut vec = OverflowVec::<usize, TestValue>::forced_import(&db, "values", Version::ONE)?;
    let read_only = vec.read_only_clone();

    vec.push(TestValue(7));
    vec.push(TestValue(1_000));
    vec.push(TestValue(3));
    let reader = vec.reader();
    assert_eq!(vec.get_with_reader(1, &reader), Some(TestValue(1_000)));
    assert_eq!(read_only.len(), 0);

    vec.write()?;

    let mut cursor = vec.reader().cursor();
    cursor.advance(1);
    assert_eq!(cursor.next(), Some(TestValue(1_000)));
    assert_eq!(cursor.position(), 2);
    assert_eq!(cursor.remaining(), 1);
    assert_eq!(cursor.get(2), Some(TestValue(3)));

    assert_eq!(
        read_only.collect(),
        vec![TestValue(7), TestValue(1_000), TestValue(3)]
    );
    assert_eq!(vec.region_names().len(), 2);

    vec.update_many(vec![(0, TestValue(5_000)), (1, TestValue(6_000))])?;
    vec.delete_many([1, 0]);
    assert_eq!(vec.holes().len(), 2);
    assert_eq!(vec.fill_first_hole_or_push(TestValue(9))?, 0);
    assert_eq!(vec.fill_first_hole_or_push(TestValue(7_000))?, 1);
    assert_eq!(
        vec.collect(),
        vec![TestValue(9), TestValue(7_000), TestValue(3)]
    );

    vec.write()?;
    drop(vec);

    let vec = OverflowVec::<usize, TestValue>::import(&db, "values", Version::ONE)?;
    assert_eq!(
        vec.collect(),
        vec![TestValue(9), TestValue(7_000), TestValue(3)]
    );
    Ok(())
}

#[test]
fn rollback_and_truncation_keep_sidecar_in_sync() -> vecdb::Result<()> {
    let temp = tempdir()?;
    let db = vecdb::Database::open(temp.path())?;
    let options = ImportOptions::new(&db, "rollback", Version::ONE).with_saved_stamped_changes(5);
    let mut vec = OverflowVec::<usize, TestValue>::forced_import_with(options)?;

    vec.push(TestValue(1));
    vec.push(TestValue(1_000));
    vec.push(TestValue(2));
    vec.stamped_write_with_changes(Stamp::new(1))?;

    vec.truncate_if_needed_at(1)?;
    vec.push(TestValue(4_000));
    vec.stamped_write_with_changes(Stamp::new(2))?;
    assert_eq!(vec.collect(), vec![TestValue(1), TestValue(4_000)]);

    vec.rollback()?;
    assert_eq!(vec.stamp(), Stamp::new(1));
    assert_eq!(
        vec.collect(),
        vec![TestValue(1), TestValue(1_000), TestValue(2)]
    );
    Ok(())
}

#[test]
fn forced_version_reset_removes_data_and_holes() -> vecdb::Result<()> {
    let temp = tempdir()?;
    let db = vecdb::Database::open(temp.path())?;
    let mut vec = OverflowVec::<usize, TestValue>::forced_import(&db, "reset", Version::ONE)?;
    vec.push(TestValue(1_000));
    vec.push(TestValue(2_000));
    vec.write()?;
    vec.delete(0);
    vec.write()?;
    assert_eq!(vec.holes().len(), 1);
    drop(vec);

    let mut vec = OverflowVec::<usize, TestValue>::forced_import(&db, "reset", Version::TWO)?;
    assert!(vec.is_empty());
    assert!(vec.holes().is_empty());
    assert_eq!(vec.fill_first_hole_or_push(TestValue(3_000))?, 0);
    vec.write()?;
    assert_eq!(vec.collect(), vec![TestValue(3_000)]);
    Ok(())
}

#[test]
fn fills_holes_in_unwritten_values() -> vecdb::Result<()> {
    let temp = tempdir()?;
    let db = vecdb::Database::open(temp.path())?;
    let mut vec = OverflowVec::<usize, TestValue>::forced_import(&db, "pushed", Version::ONE)?;

    vec.push(TestValue(1));
    vec.push(TestValue(2));
    vec.push(TestValue(3));
    vec.delete(1);

    assert_eq!(vec.fill_first_hole_or_push(TestValue(4))?, 1);
    assert!(vec.holes().is_empty());
    assert_eq!(
        vec.collect(),
        vec![TestValue(1), TestValue(4), TestValue(3)]
    );
    Ok(())
}

#[test]
fn update_many_batches_final_values_across_every_storage_state() -> vecdb::Result<()> {
    let temp = tempdir()?;
    let db = vecdb::Database::open(temp.path())?;
    let mut vec = OverflowVec::<usize, TestValue>::forced_import(&db, "batch", Version::ONE)?;

    for value in [1, 1_000, 2, 2_000] {
        vec.push(TestValue(value));
    }
    vec.write()?;
    vec.delete(2);
    vec.push(TestValue(3));
    vec.push(TestValue(3_000));

    vec.update_many(vec![
        (5, TestValue(6)),
        (2, TestValue(6_000)),
        (1, TestValue(5)),
        (4, TestValue(7_000)),
        (0, TestValue(7)),
    ])?;
    assert_eq!(
        vec.collect(),
        vec![
            TestValue(7),
            TestValue(5),
            TestValue(6_000),
            TestValue(2_000),
            TestValue(7_000),
            TestValue(6),
        ]
    );

    assert!(
        vec.update_many(vec![(0, TestValue(8)), (vec.len(), TestValue(9))])
            .is_err()
    );
    assert_eq!(vec.collect_one(0), Some(TestValue(7)));
    vec.write()?;
    drop(vec);
    let vec = OverflowVec::<usize, TestValue>::import(&db, "batch", Version::ONE)?;
    assert_eq!(
        vec.collect(),
        vec![
            TestValue(7),
            TestValue(5),
            TestValue(6_000),
            TestValue(2_000),
            TestValue(7_000),
            TestValue(6),
        ]
    );
    Ok(())
}

#[test]
fn fill_holes_or_push_many_preserves_value_order_and_indexes() -> vecdb::Result<()> {
    let temp = tempdir()?;
    let db = vecdb::Database::open(temp.path())?;
    let mut vec = OverflowVec::<usize, TestValue>::forced_import(&db, "insert", Version::ONE)?;

    for value in [1, 1_000, 2, 2_000] {
        vec.push(TestValue(value));
    }
    vec.write()?;
    vec.delete_many([1, 2]);
    vec.push(TestValue(3));
    vec.push(TestValue(3_000));
    vec.delete(4);

    assert_eq!(
        vec.fill_holes_or_push_many(vec![
            TestValue(9),
            TestValue(9_000),
            TestValue(10),
            TestValue(10_000),
            TestValue(11),
        ]),
        vec![1, 2, 4, 6, 7]
    );
    assert_eq!(
        vec.collect(),
        vec![
            TestValue(1),
            TestValue(9),
            TestValue(9_000),
            TestValue(2_000),
            TestValue(10),
            TestValue(3_000),
            TestValue(10_000),
            TestValue(11),
        ]
    );
    assert!(vec.fill_holes_or_push_many(Vec::new()).is_empty());

    vec.write()?;
    drop(vec);
    let vec = OverflowVec::<usize, TestValue>::import(&db, "insert", Version::ONE)?;
    assert_eq!(
        vec.collect(),
        vec![
            TestValue(1),
            TestValue(9),
            TestValue(9_000),
            TestValue(2_000),
            TestValue(10),
            TestValue(3_000),
            TestValue(10_000),
            TestValue(11),
        ]
    );
    Ok(())
}