prolly-map 0.1.0

Content-addressed versioned map storage primitives.
Documentation
#![cfg(feature = "sqlite")]

mod common;

use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};

use prolly::{Config, Prolly, SqliteStore};

#[test]
fn sqlite_store_is_send_and_sync() {
    fn assert_send_sync<T: Send + Sync>() {}

    assert_send_sync::<SqliteStore>();
}

#[test]
fn sqlite_store_supports_store_contract_operations() {
    let store = SqliteStore::open_in_memory().unwrap();
    common::assert_store_contract(&store);
}

#[test]
fn sqlite_store_supports_manifest_contract_operations() {
    let store = SqliteStore::open_in_memory().unwrap();
    common::assert_manifest_store_contract(&store);
}

#[test]
fn sqlite_store_supports_node_scan_contract_operations() {
    let store = SqliteStore::open_in_memory().unwrap();
    common::assert_node_store_scan_contract(store);
}

#[test]
fn sqlite_store_persists_prolly_tree_nodes_across_reopen() {
    let path = temp_db_path("persist");
    remove_sqlite_files(&path);

    let config = Config::builder()
        .min_chunk_size(2)
        .max_chunk_size(4)
        .chunking_factor(2)
        .build();

    let tree = {
        let store = SqliteStore::open(&path).unwrap();
        let prolly = Prolly::new(store, config.clone());
        let mut tree = prolly.create();

        for i in 0..40 {
            tree = prolly
                .put(
                    &tree,
                    format!("k{i:02}").into_bytes(),
                    format!("v{i:02}").into_bytes(),
                )
                .unwrap();
        }

        tree
    };

    {
        let store = SqliteStore::open(&path).unwrap();
        let prolly = Prolly::new(store, config);

        assert_eq!(prolly.get(&tree, b"k00").unwrap(), Some(b"v00".to_vec()));
        assert_eq!(prolly.get(&tree, b"k17").unwrap(), Some(b"v17".to_vec()));
        assert_eq!(prolly.get(&tree, b"k39").unwrap(), Some(b"v39".to_vec()));
        assert_eq!(prolly.get(&tree, b"missing").unwrap(), None);
    }

    remove_sqlite_files(&path);
}

#[test]
fn sqlite_store_persists_named_root_across_reopen() {
    let path = temp_db_path("root-manifest");
    remove_sqlite_files(&path);

    let config = Config::builder()
        .min_chunk_size(2)
        .max_chunk_size(4)
        .chunking_factor(2)
        .build();

    let tree = {
        let store = Arc::new(SqliteStore::open(&path).unwrap());
        let prolly = Prolly::new(store.clone(), config.clone());
        let tree = prolly.create();
        let tree = prolly
            .put(&tree, b"project/name".to_vec(), b"trail".to_vec())
            .unwrap();

        prolly.publish_named_root(b"main", &tree).unwrap();
        tree
    };

    {
        let store = Arc::new(SqliteStore::open(&path).unwrap());
        let prolly = Prolly::new(store, config.clone());
        let loaded = prolly.load_named_root(b"main").unwrap().unwrap();
        assert_eq!(loaded, tree);

        assert_eq!(
            prolly.get(&loaded, b"project/name").unwrap(),
            Some(b"trail".to_vec())
        );
    }

    remove_sqlite_files(&path);
}

fn temp_db_path(label: &str) -> PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    std::env::temp_dir().join(format!(
        "trail-prolly-{label}-{}-{nanos}.db",
        std::process::id()
    ))
}

fn remove_sqlite_files(path: &Path) {
    let _ = fs::remove_file(path);
    let _ = fs::remove_file(path.with_extension("db-wal"));
    let _ = fs::remove_file(path.with_extension("db-shm"));
}