lkv 0.1.0

A lightweight and fast embedded key-value store for Rust.
Documentation
use super::*;

#[test]
#[cfg(not(windows))]
fn single_file_compact_and_vacuum() -> Result<()> {
    let dir = temp_dir();
    let mut db = Database::open(&dir)?;
    {
        let mut txn = db.begin_write()?;
        for i in 0..1_000u32 {
            txn.put(i.to_le_bytes(), [b'x'; 100])?;
        }
        txn.commit()?;
    }
    db.compact()?;
    {
        let mut txn = db.begin_write()?;
        for i in 0..1_000u32 {
            txn.put(i.to_le_bytes(), [b'y'; 100])?;
        }
        txn.commit()?;
    }
    db.compact()?;
    let before = fs::metadata(&dir)?.len();
    db.vacuum()?;
    let after = fs::metadata(&dir)?.len();
    assert!(after < before);
    assert_eq!(db.get(500u32.to_le_bytes())?, Some(&[b'y'; 100][..]));
    assert_eq!(fs::read_dir(dir.parent().unwrap())?.count(), 1);
    drop(db);
    let db = Database::open(&dir)?;
    assert_eq!(db.len()?, 1_000);
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[test]
#[cfg(not(windows))]
fn mappings_exclude_metadata_and_overlay_and_snapshot_survives_vacuum() -> Result<()> {
    let dir = temp_dir();
    let mut db = Database::open(&dir)?;
    db.put(b"stable", b"old")?;
    db.compact()?;
    let snapshot = db.snapshot()?;
    db.put(b"overlay-only", b"new")?;
    assert!(db.storage.len()? > db.base.mapping.len() as u64);
    db.vacuum()?;

    assert_eq!(snapshot.get(b"stable")?, Some(b"old".as_slice()));
    assert_eq!(snapshot.get(b"overlay-only")?, None);
    assert_eq!(db.get(b"overlay-only")?, Some(b"new".as_slice()));
    drop(snapshot);
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[test]
fn failed_published_base_install_poisons_the_handle() -> Result<()> {
    let dir = temp_dir();
    let mut db = Database::open(&dir)?;
    let injected = Error::from_io(ErrorKind::Other, "injected base install failure");

    let error = db
        .finish_superblock_install(Err(injected))
        .expect_err("published base install must fail");
    assert_eq!(error.kind(), ErrorKind::Other);
    assert!(matches!(db.begin_write(), Err(Error::Poisoned)));

    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[test]
fn stats_describe_active_storage_without_scanning() -> Result<()> {
    let mut db = Database::memory()?;
    let initial = db.stats()?;
    assert_eq!(initial.generation, 1);
    assert_eq!(initial.base_entries, 0);
    assert_eq!(initial.overlay_entries, 0);
    assert_eq!(initial.overlay_log_bytes, 0);
    assert_eq!(initial.stale_bytes, 0);
    assert!(initial.base_bytes > 0);
    assert_eq!(initial.storage_bytes, DATA_START + initial.base_bytes);

    db.put(b"live", b"value")?;
    db.put(b"deleted", b"value")?;
    db.delete(b"deleted")?;
    let overlay = db.stats()?;
    assert_eq!(overlay.base_entries, 0);
    assert_eq!(overlay.overlay_entries, 2);
    assert!(overlay.overlay_log_bytes > 0);
    assert_eq!(overlay.overlay_memory_bytes, db.overlay_memory_usage());

    db.compact()?;
    let compacted = db.stats()?;
    assert_eq!(compacted.generation, 2);
    assert_eq!(compacted.base_entries, 1);
    assert_eq!(compacted.overlay_entries, 0);
    assert_eq!(compacted.overlay_log_bytes, 0);
    assert!(compacted.stale_bytes > 0);

    db.vacuum()?;
    let vacuumed = db.stats()?;
    assert_eq!(vacuumed.generation, 3);
    assert_eq!(vacuumed.base_entries, 1);
    assert_eq!(vacuumed.overlay_entries, 0);
    assert_eq!(vacuumed.stale_bytes, 0);
    assert_eq!(vacuumed.storage_bytes, DATA_START + vacuumed.base_bytes);
    Ok(())
}

#[test]
fn stale_vacuum_file_is_inspected_and_removed_explicitly() -> Result<()> {
    let dir = temp_dir();
    let db = Database::open(&dir)?;
    let temporary = super::super::maintenance::compacting_path(
        db.path.as_deref().expect("persistent test database"),
    );
    fs::write(&temporary, b"partial vacuum")?;
    assert!(db.has_stale_vacuum()?);
    assert!(db.remove_stale_vacuum()?);
    assert!(!db.has_stale_vacuum()?);
    assert!(!db.remove_stale_vacuum()?);
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[cfg(unix)]
#[test]
fn vacuum_refuses_a_compacting_symlink_without_touching_its_target() -> Result<()> {
    use std::os::unix::fs::symlink;

    let dir = temp_dir();
    let mut db = Database::open(&dir)?;
    db.put(b"key", b"value")?;
    let victim = dir.with_file_name("victim");
    fs::write(&victim, b"must survive")?;
    symlink(
        &victim,
        super::super::maintenance::compacting_path(
            db.path.as_deref().expect("persistent test database"),
        ),
    )?;

    let error = db.vacuum().expect_err("existing symlink must be refused");
    assert_eq!(error.kind(), ErrorKind::AlreadyExists);
    assert_eq!(
        db.remove_stale_vacuum().unwrap_err().kind(),
        ErrorKind::InvalidInput
    );
    assert_eq!(fs::read(&victim)?, b"must survive");
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}