candystore 1.2.2

A lean, efficient and fast persistent in-process key-value store
Documentation
mod common;

use std::collections::HashMap;

use candystore::{CandyStore, Config, Error};
use tempfile::tempdir;

#[test]
fn test_iter_items_allows_rotation_while_paused() -> Result<(), Error> {
    use std::{
        sync::{Arc, mpsc},
        thread,
        time::Duration,
    };
    let dir = tempdir().unwrap();
    let db = Arc::new(CandyStore::open(
        dir.path(),
        Config {
            max_data_file_size: 128,
            checkpoint_interval: None,
            checkpoint_delta_bytes: None,
            compaction_throughput_bytes_per_sec: 0,
            ..Config::default()
        },
    )?);
    db.set("seed", vec![1; 64])?;
    let mut iterator = db.iter_items();
    assert!(iterator.next().unwrap().is_ok());
    let (done_tx, done_rx) = mpsc::channel();
    let writer_db = db.clone();
    let writer = thread::spawn(move || {
        let result = writer_db.set("next", vec![2; 64]);
        done_tx.send(()).unwrap();
        result
    });
    let completed = done_rx.recv_timeout(Duration::from_secs(5)).is_ok();
    drop(iterator);
    writer.join().unwrap()?;
    assert!(
        completed,
        "rotation was blocked until the iterator was dropped"
    );
    assert_eq!(db.get("next")?, Some(vec![2; 64]));
    Ok(())
}

#[test]
fn test_iter_items_empty_db() -> Result<(), Error> {
    let dir = tempdir().unwrap();
    let db = CandyStore::open(dir.path(), Config::default())?;

    let items: Vec<_> = db.iter_items().collect::<Result<_, _>>()?;
    assert!(items.is_empty());

    Ok(())
}

#[test]
fn test_iter_items_basic() -> Result<(), Error> {
    let dir = tempdir().unwrap();
    let db = CandyStore::open(dir.path(), Config::default())?;

    let mut expected = HashMap::new();
    for i in 0..100 {
        let key = format!("iter_key_{i:04}");
        let value = format!("iter_val_{i:04}");
        db.set(&key, &value)?;
        expected.insert(key.into_bytes(), value.into_bytes());
    }

    let items: HashMap<Vec<u8>, Vec<u8>> = db.iter_items().collect::<Result<_, _>>()?;
    assert_eq!(items.len(), expected.len());
    assert_eq!(items, expected);

    Ok(())
}

#[test]
fn test_iter_items_after_updates_and_removes() -> Result<(), Error> {
    let dir = tempdir().unwrap();
    let db = CandyStore::open(dir.path(), Config::default())?;

    for i in 0..50 {
        db.set(format!("key_{i:04}"), format!("val_{i:04}"))?;
    }

    for i in 0..20 {
        db.set(format!("key_{i:04}"), format!("updated_{i:04}"))?;
    }

    for i in 40..50 {
        db.remove(format!("key_{i:04}"))?;
    }

    let items: HashMap<Vec<u8>, Vec<u8>> = db.iter_items().collect::<Result<_, _>>()?;
    assert_eq!(items.len(), 40);

    for i in 0..20 {
        let key = format!("key_{i:04}");
        assert_eq!(
            items.get(key.as_bytes()),
            Some(&format!("updated_{i:04}").into_bytes())
        );
    }
    for i in 20..40 {
        let key = format!("key_{i:04}");
        assert_eq!(
            items.get(key.as_bytes()),
            Some(&format!("val_{i:04}").into_bytes())
        );
    }
    for i in 40..50 {
        let key = format!("key_{i:04}");
        assert!(!items.contains_key(key.as_bytes()));
    }

    Ok(())
}

#[test]
fn test_iter_items_skips_empty_rows_before_later_entries() -> Result<(), Error> {
    let dir = tempdir().unwrap();
    let db = CandyStore::open(dir.path(), Config::default())?;

    db.set("alpha", "one")?;
    db.set("bravo", "two")?;
    db.set("charlie", "three")?;

    let items: HashMap<Vec<u8>, Vec<u8>> = db.iter_items().collect::<Result<_, _>>()?;
    assert_eq!(items.len(), 3);
    assert_eq!(items.get(b"alpha".as_slice()), Some(&b"one".to_vec()));
    assert_eq!(items.get(b"bravo".as_slice()), Some(&b"two".to_vec()));
    assert_eq!(items.get(b"charlie".as_slice()), Some(&b"three".to_vec()));

    Ok(())
}

#[test]
fn test_iter_items_with_splits_and_rotation() -> Result<(), Error> {
    let dir = tempdir().unwrap();
    let db = CandyStore::open(dir.path(), common::small_file_config())?;

    let mut expected = HashMap::new();
    for i in 0..2000 {
        let key = format!("split_iter_key_{i:05}");
        let value = format!("split_iter_val_{i:05}_{}", "x".repeat(48));
        db.set(&key, &value)?;
        expected.insert(key.into_bytes(), value.into_bytes());
    }

    let items: HashMap<Vec<u8>, Vec<u8>> = db.iter_items().collect::<Result<_, _>>()?;
    assert_eq!(items.len(), expected.len());
    assert_eq!(items, expected);

    Ok(())
}