lkv 0.1.0

A lightweight and fast embedded key-value store for Rust.
Documentation
use super::*;
use crate::format::log::{Marker, write_record_header};

#[test]
fn database_size_limit_rejects_append_before_mutation() -> Result<()> {
    let dir = temp_dir();
    let db = Database::open(&dir)?;
    let file_len = db.storage.len()?;
    drop(db);
    let mut db = Database::open_with_options(
        &dir,
        DatabaseOptions {
            max_database_bytes: file_len,
            ..DatabaseOptions::default()
        },
    )?;
    let before = db.storage.len()?;
    let error = db.put(b"key", b"value").unwrap_err();
    assert!(matches!(error, Error::DatabaseFull(_)));
    assert_eq!(db.storage.len()?, before);
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[test]
fn memory_database_size_limit_rejects_append_before_mutation() -> Result<()> {
    let probe = Database::memory()?;
    let initial_len = probe.storage.len()?;
    drop(probe);
    let mut db = Database::memory_with_options(DatabaseOptions {
        max_database_bytes: initial_len,
        ..DatabaseOptions::default()
    })?;
    let error = db.put(b"key", b"value").unwrap_err();
    assert!(matches!(error, Error::DatabaseFull(_)));
    assert_eq!(db.storage.len()?, initial_len);
    assert!(db.is_empty()?);
    Ok(())
}

#[test]
fn overlay_limit_stops_writes_without_running_maintenance() -> Result<()> {
    let dir = temp_dir();
    let db = Database::open(&dir)?;
    let file_len = db.storage.len()?;
    drop(db);

    let mut db = Database::open_with_options(
        &dir,
        DatabaseOptions {
            overlay_memory_limit: 0,
            max_database_bytes: file_len + 64,
            ..DatabaseOptions::default()
        },
    )?;
    let mut transaction = db.begin_write()?;
    transaction.put(b"key", b"value")?;
    transaction.commit()?;
    assert_eq!(db.get(b"key")?, Some(b"value".as_slice()));
    let before = db.storage.len()?;
    let error = match db.begin_write() {
        Ok(_) => panic!("write must require maintenance"),
        Err(error) => error,
    };
    assert!(matches!(error, Error::MaintenanceRequired { .. }));
    assert_eq!(db.storage.len()?, before);
    assert_eq!(db.get(b"key")?, Some(b"value".as_slice()));
    assert!(matches!(db.compact(), Err(Error::DatabaseFull(_))));
    drop(db);

    let db = Database::open(&dir)?;
    assert_eq!(db.get(b"key")?, Some(b"value".as_slice()));
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[test]
fn malformed_sizes_fail_without_panicking_or_allocating() -> Result<()> {
    let mut header = vec![0; BASE_HEADER];
    header[..4].copy_from_slice(b"HASH");
    header[4..8].copy_from_slice(&(BASE_HEADER as u32).to_le_bytes());
    header[8..16].copy_from_slice(&u64::MAX.to_le_bytes());
    assert_eq!(
        read_base_header(&header).unwrap_err().kind(),
        ErrorKind::InvalidData
    );
    header[8..16].copy_from_slice(&0u64.to_le_bytes());
    header[16..24].copy_from_slice(&u64::MAX.to_le_bytes());
    let (slots, len) = read_base_header(&header)?;
    assert_eq!(
        validate_base(&header, DATA_START, slots, len)
            .unwrap_err()
            .kind(),
        ErrorKind::InvalidData
    );
    Ok(())
}

#[test]
fn maximal_truncated_overlay_record_is_discarded_before_allocation() -> Result<()> {
    let dir = temp_dir();
    let db = Database::open(&dir)?;
    let offset = db.storage.len()?;
    drop(db);

    let path = dir.clone();
    let mut file = OpenOptions::new().read(true).write(true).open(&path)?;
    let payload_len = u32::MAX;
    file.seek(SeekFrom::Start(offset))?;
    write_record_header(&mut file, Marker::Batch, 0, payload_len, 0)?;
    file.sync_all()?;
    drop(file);

    let db = Database::open(&dir)?;
    assert_eq!(db.storage.len()?, offset);
    assert!(db.is_empty()?);
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[test]
fn public_writes_enforce_allocation_limits_before_file_mutation() -> Result<()> {
    let dir = temp_dir();
    let mut db = Database::open(&dir)?;
    let before = db.storage.len()?;
    let oversized_key = vec![0; MAX_KEY_SIZE + 1];
    let mut transaction = db.begin_write()?;
    let error = transaction
        .put(&oversized_key, b"value")
        .expect_err("oversized key must be rejected");
    assert_eq!(error.kind(), ErrorKind::InvalidInput);
    transaction.abort();
    assert_eq!(db.storage.len()?, before);
    drop(db);
    remove_test_database(&dir)?;
    Ok(())
}

#[test]
fn reserved_value_that_cannot_fit_a_batch_is_rejected_before_allocation() -> Result<()> {
    let mut db = Database::memory()?;
    let mut transaction = db.begin_write()?;
    let error = transaction
        .put_reserved(b"key", u32::MAX as usize, |_| {
            panic!("oversized reservation callback must not run")
        })
        .expect_err("oversized reservation must fail");
    assert_eq!(error.kind(), ErrorKind::InvalidInput);
    assert!(transaction.is_empty());
    Ok(())
}