bitcasky 0.1.2

Bitcasky is a Rust implementation of the Bitcask key-value store
Documentation
use std::{collections::HashSet, time::Duration};

use bitcasky::internals::common::{
    get_temporary_directory_path, RandomTestingDataGenerator, TestingOperations, TestingOperator,
};
use bitcasky::options::{BitcaskyOptions, SyncStrategy};
use bitcasky::{bitcasky::Bitcasky, error::BitcaskyError};
use test_log::test;

fn execute_testing_operations(bc: &Bitcasky, ops: &TestingOperations) {
    for op in ops.operations() {
        match op.operator() {
            TestingOperator::PUT => bc.put(op.key(), op.value()).unwrap(),
            TestingOperator::DELETE => bc.delete(&op.key()).unwrap(),
            TestingOperator::MERGE => bc.merge().unwrap(),
            TestingOperator::NONE => {}
        }
    }
}

fn get_default_options() -> BitcaskyOptions {
    BitcaskyOptions::default()
        .max_data_file_size(10 * 1024)
        .init_data_file_capacity(100)
        .init_hint_file_capacity(1024)
        .sync_strategy(SyncStrategy::Interval(Duration::from_secs(1)))
        .max_key_size(64)
        .max_value_size(1024)
}

#[test]
fn test_open_db_twice() {
    let dir = get_temporary_directory_path();
    let _bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    let bc2 = Bitcasky::open(&dir, get_default_options());
    assert!(bc2.is_err());
    assert!(matches!(
        bc2.err(),
        Some(BitcaskyError::LockDirectoryFailed(_))
    ));
}

#[test]
fn test_read_write_writing_file() {
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    bc.put("k1", "value1").unwrap();
    bc.put("k2", "value2").unwrap();
    bc.put("k3", "value3").unwrap();
    bc.put("k1", "value4").unwrap();

    assert_eq!(bc.get("k1").unwrap().unwrap(), "value4".as_bytes());
    assert_eq!(bc.get("k2").unwrap().unwrap(), "value2".as_bytes());
    assert_eq!(bc.get("k3").unwrap().unwrap(), "value3".as_bytes());
}

#[test]
fn test_random_put_and_delete() {
    let mut gen = RandomTestingDataGenerator::new(
        64,
        512,
        vec![TestingOperator::PUT, TestingOperator::DELETE],
    );
    let ops = gen.generate_testing_operations(5000);
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    execute_testing_operations(&bc, &ops);

    for op in ops.squash() {
        assert_eq!(bc.get(op.key()).unwrap().unwrap(), op.value());
    }
}

#[test]
fn test_random_put_delete_merge() {
    let mut gen = RandomTestingDataGenerator::new(
        64,
        512,
        vec![
            TestingOperator::PUT,
            TestingOperator::DELETE,
            TestingOperator::MERGE,
        ],
    );
    let ops = gen.generate_testing_operations(10);
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    execute_testing_operations(&bc, &ops);

    for op in ops.squash() {
        assert_eq!(bc.get(op.key()).unwrap().unwrap(), op.value());
    }
}

#[test]
fn test_recovery() {
    let mut gen = RandomTestingDataGenerator::new(
        64,
        512,
        vec![TestingOperator::PUT, TestingOperator::DELETE],
    );
    let ops = gen.generate_testing_operations(5000);
    let dir = get_temporary_directory_path();
    {
        let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
        execute_testing_operations(&bc, &ops);
    }
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    for op in ops.squash() {
        assert_eq!(bc.get(op.key()).unwrap().unwrap(), op.value());
    }
}

#[test]
fn test_delete() {
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    bc.put("k1", "value1").unwrap();
    bc.put("k2", "value2").unwrap();
    bc.put("k3", "value3").unwrap();
    bc.put("k1", "value4").unwrap();

    bc.delete("k1").unwrap();
    assert_eq!(bc.get("k1").unwrap(), None);

    bc.delete("k2").unwrap();
    assert_eq!(bc.get("k2").unwrap(), None);

    bc.delete("k3").unwrap();
    assert_eq!(bc.get("k3").unwrap(), None);
}

#[test]
fn test_delete_not_exists_key() {
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();

    bc.delete("k1").unwrap();
    assert_eq!(bc.get("k1").unwrap(), None);

    bc.delete("k2").unwrap();
    assert_eq!(bc.get("k2").unwrap(), None);

    bc.delete("k3").unwrap();
    assert_eq!(bc.get("k3").unwrap(), None);
}

#[test]
fn test_foreach_keys() {
    let mut gen = RandomTestingDataGenerator::new(64, 512, vec![TestingOperator::PUT]);
    let ops = gen.generate_testing_operations(100);
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    execute_testing_operations(&bc, &ops);

    let mut expected_set: HashSet<Vec<u8>> = HashSet::new();
    let mut actual_set: HashSet<Vec<u8>> = HashSet::new();
    for op in ops.squash() {
        expected_set.insert(op.key());
    }

    bc.foreach_key(|k| {
        actual_set.insert(k.clone());
    })
    .unwrap();
    assert_eq!(expected_set, actual_set);
}

#[test]
fn test_fold_keys() {
    let mut gen = RandomTestingDataGenerator::new(64, 512, vec![TestingOperator::PUT]);
    let ops = gen.generate_testing_operations(100);
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    execute_testing_operations(&bc, &ops);

    let mut expected_set: HashSet<Vec<u8>> = HashSet::new();
    let mut actual_set: HashSet<Vec<u8>> = HashSet::new();
    for op in ops.squash() {
        expected_set.insert(op.key());
    }

    let ret = bc
        .fold_key(
            |k, acc| {
                actual_set.insert(k.clone());
                Ok(Some(acc.unwrap() + 1))
            },
            Some(0),
        )
        .unwrap();
    assert_eq!(expected_set.len(), ret.unwrap());
    assert_eq!(expected_set, actual_set);
}

#[test]
fn test_foreach() {
    let mut gen = RandomTestingDataGenerator::new(64, 512, vec![TestingOperator::PUT]);
    let ops = gen.generate_testing_operations(100);
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    execute_testing_operations(&bc, &ops);

    let expected_pair = ops
        .squash()
        .iter()
        .map(|op| (op.key(), op.value()))
        .collect::<Vec<(Vec<u8>, Vec<u8>)>>();

    let mut actual_pair: Vec<(Vec<u8>, Vec<u8>)> = vec![];
    bc.foreach(|k, v| {
        actual_pair.push((k.clone(), v.clone()));
    })
    .unwrap();

    assert_eq!(expected_pair, actual_pair);
}

#[test]
fn test_fold() {
    let mut gen = RandomTestingDataGenerator::new(64, 512, vec![TestingOperator::PUT]);
    let ops = gen.generate_testing_operations(100);
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    execute_testing_operations(&bc, &ops);

    let expected_pair = ops
        .squash()
        .iter()
        .map(|op| (op.key(), op.value()))
        .collect::<Vec<(Vec<u8>, Vec<u8>)>>();

    let mut actual_pair: Vec<(Vec<u8>, Vec<u8>)> = vec![];
    let ret = bc
        .fold(
            |k, v, acc| {
                actual_pair.push((k.clone(), v.clone()));
                Ok(Some(acc.unwrap() + 1))
            },
            Some(0),
        )
        .unwrap();
    assert_eq!(expected_pair.len(), ret.unwrap());
    assert_eq!(expected_pair, actual_pair);
}

#[test]
fn test_dead_bytes_by_delete() {
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    bc.put("k1", "value1").unwrap();
    bc.put("k2", "value2").unwrap();

    bc.delete("k1").unwrap();

    assert!(
        bc.get_telemetry_data()
            .database
            .storage_aggregate
            .total_fragment
            < 1.0
    );

    bc.delete("k2").unwrap();
    assert_eq!(
        1.0,
        bc.get_telemetry_data()
            .database
            .storage_aggregate
            .total_fragment
    );
}

#[test]
fn test_dead_bytes_by_put() {
    let dir = get_temporary_directory_path();
    let bc = Bitcasky::open(&dir, get_default_options()).unwrap();
    bc.put("k1", "value1").unwrap();
    bc.put("k2", "value2").unwrap();

    bc.put("k1", "value3").unwrap();
    bc.put("k2", "value4").unwrap();
    assert_eq!(
        0.5,
        bc.get_telemetry_data()
            .database
            .storage_aggregate
            .total_fragment
    );
}