prolly-map 0.3.0

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

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

fn build_versions<S>(prolly: &Prolly<S>) -> (prolly::Tree, prolly::Tree)
where
    S: prolly::Store,
{
    let mut base = prolly.create();
    for idx in 0..20 {
        base = prolly
            .put(
                &base,
                format!("k{idx:03}").into_bytes(),
                format!("base-{idx:03}").into_bytes(),
            )
            .unwrap();
    }

    let mut other = base.clone();
    other = prolly.delete(&other, b"k002").unwrap();
    other = prolly
        .put(&other, b"k004".to_vec(), b"changed-004".to_vec())
        .unwrap();
    other = prolly
        .put(&other, b"k007".to_vec(), b"changed-007".to_vec())
        .unwrap();
    other = prolly.delete(&other, b"k012").unwrap();
    other = prolly
        .put(&other, b"k015".to_vec(), b"changed-015".to_vec())
        .unwrap();
    other = prolly
        .put(&other, b"k021".to_vec(), b"added-021".to_vec())
        .unwrap();

    (base, other)
}

fn diff_keys(diffs: &[Diff]) -> Vec<Vec<u8>> {
    diffs.iter().map(|diff| diff.key().to_vec()).collect()
}

#[test]
fn diff_from_cursor_resumes_strictly_after_key() {
    let prolly = Prolly::new(MemStore::new(), small_node_config());
    let (base, other) = build_versions(&prolly);

    let diffs = prolly
        .diff_from_cursor(
            &base,
            &other,
            &RangeCursor::after_key(b"k004".to_vec()),
            Some(b"k016"),
        )
        .unwrap();

    assert_eq!(
        diff_keys(&diffs),
        vec![b"k007".to_vec(), b"k012".to_vec(), b"k015".to_vec()]
    );
}

#[test]
fn diff_pages_resume_to_reconstruct_bounded_diff() {
    let prolly = Prolly::new(MemStore::new(), small_node_config());
    let (base, other) = build_versions(&prolly);

    let expected = prolly
        .diff_from_cursor(
            &base,
            &other,
            &RangeCursor::after_key(b"k003".to_vec()),
            Some(b"k022"),
        )
        .unwrap();

    let mut cursor = RangeCursor::after_key(b"k003".to_vec());
    let mut actual = Vec::new();
    let mut page_count = 0usize;

    loop {
        let page = prolly
            .diff_page(&base, &other, &cursor, Some(b"k022"), 2)
            .unwrap();
        page_count += 1;
        actual.extend(page.diffs);

        let Some(next) = page.next_cursor else {
            break;
        };
        assert!(!next.is_start());
        cursor = next;
    }

    assert_eq!(actual, expected);
    assert!(page_count > 1);
}

#[test]
fn zero_limit_diff_page_is_noop() {
    let prolly = Prolly::new(MemStore::new(), small_node_config());
    let (base, other) = build_versions(&prolly);
    let cursor = RangeCursor::after_key(b"k004".to_vec());

    let page = prolly.diff_page(&base, &other, &cursor, None, 0).unwrap();

    assert!(page.diffs.is_empty());
    assert_eq!(page.next_cursor, Some(cursor));
}

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

    fn block_on<F: Future>(future: F) -> F::Output {
        let waker = futures_util::task::noop_waker();
        let mut cx = Context::from_waker(&waker);
        let mut future = Box::pin(future);

        loop {
            match future.as_mut().poll(&mut cx) {
                Poll::Ready(value) => return value,
                Poll::Pending => std::thread::yield_now(),
            }
        }
    }

    #[test]
    fn async_diff_pages_match_sync_diff_pages() {
        let store = Arc::new(MemStore::new());
        let config = small_node_config();
        let sync_prolly = Prolly::new(store.clone(), config.clone());
        let async_prolly = AsyncProlly::new(SyncStoreAsAsync::new(store), config);
        let (base, other) = build_versions(&sync_prolly);

        let expected = sync_prolly
            .diff_from_cursor(
                &base,
                &other,
                &RangeCursor::after_key(b"k003".to_vec()),
                Some(b"k022"),
            )
            .unwrap();

        let mut cursor = RangeCursor::after_key(b"k003".to_vec());
        let mut actual = Vec::new();

        loop {
            let page =
                block_on(async_prolly.diff_page(&base, &other, &cursor, Some(b"k022"), 2)).unwrap();
            actual.extend(page.diffs);

            let Some(next) = page.next_cursor else {
                break;
            };
            cursor = next;
        }

        assert_eq!(actual, expected);
    }
}