prolly-map 0.4.0

Content-addressed versioned map storage primitives.
Documentation
use prolly::{Config, MemStore, Prolly};

fn small_config() -> Config {
    Config::builder()
        .min_chunk_size(2)
        .max_chunk_size(4)
        .chunking_factor(2)
        .build()
}

fn build_tree(prolly: &Prolly<MemStore>, count: usize) -> prolly::Tree {
    let mut tree = prolly.create();
    for idx in 0..count {
        tree = prolly
            .put(
                &tree,
                format!("k{idx:03}").into_bytes(),
                format!("v{idx:03}").into_bytes(),
            )
            .unwrap();
    }
    tree
}

#[test]
fn debug_tree_renders_empty_tree() {
    let prolly = Prolly::new(MemStore::new(), small_config());
    let view = prolly.debug_tree(&prolly.create()).unwrap();

    assert!(view.levels.is_empty());
    assert_eq!(view.to_text(), "empty tree");
}

#[test]
fn debug_tree_groups_nodes_by_level_root_first() {
    let prolly = Prolly::new(MemStore::new(), small_config());
    let tree = build_tree(&prolly, 40);

    let view = prolly.debug_tree(&tree).unwrap();

    assert!(view.levels.len() > 1);
    assert!(view
        .levels
        .windows(2)
        .all(|pair| pair[0].level > pair[1].level));
    assert!(view
        .levels
        .first()
        .unwrap()
        .nodes
        .iter()
        .all(|node| !node.leaf));
    assert!(view
        .levels
        .last()
        .unwrap()
        .nodes
        .iter()
        .all(|node| node.leaf));
    assert!(view
        .levels
        .iter()
        .flat_map(|level| &level.nodes)
        .all(|node| node.entry_count <= node.max_entries && node.encoded_bytes > 0));

    let rendered = view.to_text();
    assert!(rendered.contains("level"));
    assert!(rendered.contains("fill="));
    assert!(rendered.contains("keys=\""));
}

#[test]
fn debug_compare_trees_highlights_shared_and_rewritten_subtrees() {
    let prolly = Prolly::new(MemStore::new(), small_config());
    let before = build_tree(&prolly, 80);
    let after = prolly
        .put(&before, b"k042".to_vec(), b"updated".to_vec())
        .unwrap();

    let comparison = prolly.debug_compare_trees(&before, &after).unwrap();

    assert!(comparison.shared_nodes > 0);
    assert!(comparison.left_only_nodes > 0);
    assert!(comparison.right_only_nodes > 0);
    assert!(comparison.shared_bytes > 0);
    assert!(comparison.left_only_bytes > 0);
    assert!(comparison.right_only_bytes > 0);
    assert!(comparison.levels.iter().any(|level| level.shared_nodes > 0));
    assert!(comparison
        .levels
        .iter()
        .flat_map(|level| &level.nodes)
        .any(|node| node.status == prolly::TreeDebugNodeStatus::Shared));

    let rendered = comparison.to_text();
    assert!(rendered.contains("shared="));
    assert!(rendered.contains("left_only="));
    assert!(rendered.contains("right_only="));
}

#[test]
fn debug_compare_empty_trees_is_empty() {
    let prolly = Prolly::new(MemStore::new(), small_config());
    let empty = prolly.create();

    let comparison = prolly.debug_compare_trees(&empty, &empty).unwrap();

    assert_eq!(comparison.shared_nodes, 0);
    assert_eq!(comparison.left_only_nodes, 0);
    assert_eq!(comparison.right_only_nodes, 0);
    assert_eq!(comparison.to_text(), "empty comparison");
}

#[cfg(feature = "async-store")]
mod async_tests {
    use super::small_config;
    use futures_util::task::noop_waker;
    use prolly::{AsyncProlly, MemStore, SyncStoreAsAsync};
    use std::future::Future;
    use std::pin::Pin;
    use std::sync::Arc;
    use std::task::{Context, Poll};

    fn block_on<F: Future>(future: F) -> F::Output {
        let waker = noop_waker();
        let mut cx = Context::from_waker(&waker);
        let mut future = Box::pin(future);
        loop {
            match Pin::new(&mut future).poll(&mut cx) {
                Poll::Ready(output) => return output,
                Poll::Pending => std::thread::yield_now(),
            }
        }
    }

    #[test]
    fn async_debug_tree_and_compare_match_sync_adapter_path() {
        let store = Arc::new(MemStore::new());
        let prolly = AsyncProlly::new(SyncStoreAsAsync::new(store), small_config());
        let mut tree = prolly.create();

        for idx in 0..80 {
            tree = block_on(prolly.put(
                &tree,
                format!("k{idx:03}").into_bytes(),
                format!("v{idx:03}").into_bytes(),
            ))
            .unwrap();
        }

        let updated = block_on(prolly.put(&tree, b"k042".to_vec(), b"updated".to_vec())).unwrap();
        let view = block_on(prolly.debug_tree(&updated)).unwrap();
        let comparison = block_on(prolly.debug_compare_trees(&tree, &updated)).unwrap();

        assert!(view.levels.len() > 1);
        assert!(comparison.shared_nodes > 0);
        assert!(comparison.right_only_nodes > 0);
        assert!(comparison.to_text().contains("right_only="));
    }
}