ic-storage 0.5.0

Page-based stable-memory B-tree storage for Internet Computer applications
Documentation
//! Public collection adapters require committed IC Memory allocations.
use super::super::{BTreeMap, Config, Error, Lexicographic};
use crate::adapter::*;
use ic_memory::{
    GenericAllocationPolicy, MemoryAllocationPool, MemoryAuthority, MemoryRequest, MemoryRuntime,
    RuntimeOpenError, SealedDeclarationSnapshot, VectorMemory,
};

fn runtime(backing: VectorMemory) -> MemoryRuntime<VectorMemory> {
    let pool =
        MemoryAllocationPool::new(vec![MemoryAuthority::new("test", "test.").unwrap()], vec![])
            .unwrap();
    let declarations = SealedDeclarationSnapshot::new(
        [
            "tree",
            "map",
            "set",
            "cell",
            "vec",
            "heap",
            "log.index",
            "log.data",
        ]
        .map(|key| MemoryRequest::new("test", &format!("test.{key}.v1")).unwrap())
        .to_vec(),
    )
    .unwrap();
    let mut runtime = MemoryRuntime::new(backing).unwrap();
    runtime
        .bootstrap(&declarations, &pool, &GenericAllocationPolicy)
        .unwrap();
    runtime
}

#[test]
fn collections_reopen_through_committed_runtime_allocations() {
    let backing = VectorMemory::default();
    let runtime = runtime(backing.clone());
    assert!(matches!(
        runtime.open_memory("test.unknown.v1"),
        Err(RuntimeOpenError::StableKeyNotCommitted(_))
    ));
    let mut tree = BTreeMap::create(
        runtime.open_memory("test.tree.v1").unwrap(),
        Config::default(),
        Lexicographic,
    )
    .unwrap();
    tree.insert(b"key", b"row").unwrap();
    let mut map = StableBTreeMap::<u64, u64>::new(runtime.open_memory("test.map.v1").unwrap());
    map.insert(7, 11);
    let mut set = StableBTreeSet::<u64>::new(runtime.open_memory("test.set.v1").unwrap());
    set.insert(9);
    let mut cell = StableCell::new(runtime.open_memory("test.cell.v1").unwrap(), 1u64);
    cell.set(3);
    let vector = StableVec::new(runtime.open_memory("test.vec.v1").unwrap());
    vector.push(&5u64);
    let mut heap = StableMinHeap::new(runtime.open_memory("test.heap.v1").unwrap());
    heap.push(&8u64);
    heap.push(&2);
    let log = StableLog::new(
        runtime.open_memory("test.log.index.v1").unwrap(),
        runtime.open_memory("test.log.data.v1").unwrap(),
    );
    log.append(&13u64).unwrap();
    for key in [
        "test.tree.v1",
        "test.map.v1",
        "test.set.v1",
        "test.cell.v1",
        "test.vec.v1",
        "test.heap.v1",
        "test.log.index.v1",
        "test.log.data.v1",
    ] {
        assert!(
            matches!(
                runtime.open_memory(key),
                Err(RuntimeOpenError::AlreadyOpen { .. })
            ),
            "{key}"
        );
    }
    drop((tree, map, set, cell, vector, heap, log));
    for key in [
        "test.tree.v1",
        "test.map.v1",
        "test.set.v1",
        "test.cell.v1",
        "test.vec.v1",
        "test.heap.v1",
        "test.log.index.v1",
        "test.log.data.v1",
    ] {
        drop(runtime.open_memory(key).unwrap());
    }
    drop(runtime);

    let runtime = self::runtime(backing);
    let tree = BTreeMap::open(
        runtime.open_memory("test.tree.v1").unwrap(),
        Config::default(),
        Lexicographic,
    )
    .unwrap();
    assert_eq!(tree.get(b"key").unwrap(), Some(b"row".to_vec()));
    tree.validate().unwrap();
    let map = StableBTreeMap::<u64, u64>::load(runtime.open_memory("test.map.v1").unwrap());
    let mut iter: MapIter<'_, u64, u64> = map.iter();
    assert_eq!(iter.next().unwrap().into_pair(), (7, 11));
    let set = StableBTreeSet::<u64>::init(runtime.open_memory("test.set.v1").unwrap());
    let mut iter: SetIter<'_, u64> = set.iter();
    assert_eq!(iter.next(), Some(9));
    let cell = StableCell::init(runtime.open_memory("test.cell.v1").unwrap(), 99u64);
    assert_eq!(*cell.get(), 3);
    let vector = StableVec::<u64>::init(runtime.open_memory("test.vec.v1").unwrap());
    assert_eq!(vector.get(0), Some(5));
    let mut heap = StableMinHeap::<u64>::init(runtime.open_memory("test.heap.v1").unwrap());
    assert_eq!(heap.pop(), Some(2));
    assert_eq!(heap.pop(), Some(8));
    let log = StableLog::<u64>::init(
        runtime.open_memory("test.log.index.v1").unwrap(),
        runtime.open_memory("test.log.data.v1").unwrap(),
    );
    let mut iter: LogIter<'_, u64> = log.iter();
    assert_eq!(iter.next(), Some(13));
}

#[test]
fn upstream_and_custom_formats_are_not_interchangeable() {
    let runtime = runtime(VectorMemory::default());
    let mut map = StableBTreeMap::<u64, u64>::new(runtime.open_memory("test.map.v1").unwrap());
    map.insert(1, 2);
    drop(map);
    assert!(matches!(
        BTreeMap::open(
            runtime.open_memory("test.map.v1").unwrap(),
            Config::default(),
            Lexicographic
        ),
        Err(Error::Format)
    ));
}

#[test]
fn constructor_errors_release_allocation_leases() {
    let runtime = runtime(VectorMemory::default());
    assert!(matches!(
        BTreeMap::create(
            runtime.open_memory("test.tree.v1").unwrap(),
            Config {
                max_key_bytes: 0,
                ..Config::default()
            },
            Lexicographic,
        ),
        Err(Error::Configuration)
    ));
    let tree = BTreeMap::create(
        runtime.open_memory("test.tree.v1").unwrap(),
        Config::default(),
        Lexicographic,
    )
    .unwrap();
    assert!(matches!(
        runtime.open_memory("test.tree.v1"),
        Err(RuntimeOpenError::AlreadyOpen { .. })
    ));
    drop(tree);
    assert!(matches!(
        BTreeMap::open(
            runtime.open_memory("test.tree.v1").unwrap(),
            Config {
                max_value_bytes: 1,
                ..Config::default()
            },
            Lexicographic
        ),
        Err(Error::Configuration)
    ));
    let tree = BTreeMap::open(
        runtime.open_memory("test.tree.v1").unwrap(),
        Config::default(),
        Lexicographic,
    )
    .unwrap();
    assert!(tree.is_empty());
}