icydb-core 0.180.12

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use crate::{
    db::{
        direction::Direction,
        index::{IndexEntryValue, IndexStore, IndexStoreVisit, RawIndexStoreKey},
    },
    testing::test_memory,
    traits::Storable,
};
use std::{borrow::Cow, ops::Bound};

#[test]
fn visit_raw_entries_in_range_preserves_directional_store_order() {
    let mut index_store = IndexStore::init(test_memory(91));
    for value in [1_u8, 2, 3] {
        let raw_key = <RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(vec![value]));
        let raw_entry = IndexEntryValue::presence();
        index_store.insert(raw_key, raw_entry);
    }

    let lower = Bound::Included(<RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(
        vec![1],
    )));
    let upper = Bound::Included(<RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(
        vec![3],
    )));
    let mut asc = Vec::new();
    index_store
        .visit_raw_entries_in_range((&lower, &upper), Direction::Asc, |raw_key, _| {
            asc.push(raw_key.as_bytes()[0]);
            Ok(false)
        })
        .expect("asc scan should succeed");
    assert_eq!(asc, vec![1, 2, 3], "asc scan should follow raw key order");

    let mut desc = Vec::new();
    index_store
        .visit_raw_entries_in_range((&lower, &upper), Direction::Desc, |raw_key, _| {
            desc.push(raw_key.as_bytes()[0]);
            Ok(false)
        })
        .expect("desc scan should succeed");
    assert_eq!(
        desc,
        vec![3, 2, 1],
        "desc scan should reverse raw key order"
    );
}

#[test]
fn visit_entries_preserves_store_order_and_supports_early_stop() {
    let mut index_store = IndexStore::init(test_memory(92));
    for value in [3_u8, 1, 2] {
        let raw_key = <RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(vec![value]));
        let raw_entry = IndexEntryValue::presence();
        index_store.insert(raw_key, raw_entry);
    }

    let mut visited = Vec::new();
    let _: Result<(), std::convert::Infallible> = index_store.visit_entries(|raw_key, _| {
        visited.push(raw_key.as_bytes()[0]);
        Ok(if visited.len() == 2 {
            IndexStoreVisit::Stop
        } else {
            IndexStoreVisit::Continue
        })
    });

    assert_eq!(
        visited,
        vec![1, 2],
        "index entry traversal should preserve raw store order and stop without allocation"
    );
}

#[test]
fn heap_index_store_preserves_range_order_and_early_stop() {
    let mut index_store = IndexStore::init_heap();
    for value in [3_u8, 1, 2] {
        let raw_key = <RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(vec![value]));
        let raw_entry = IndexEntryValue::presence();
        index_store.insert(raw_key, raw_entry);
    }

    let lower = Bound::Included(<RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(
        vec![1],
    )));
    let upper = Bound::Included(<RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(
        vec![3],
    )));
    let mut asc = Vec::new();
    index_store
        .visit_raw_entries_in_range((&lower, &upper), Direction::Asc, |raw_key, _| {
            asc.push(raw_key.as_bytes()[0]);
            Ok(false)
        })
        .expect("heap asc scan should succeed");
    assert_eq!(asc, vec![1, 2, 3]);

    let mut desc = Vec::new();
    index_store
        .visit_raw_entries_in_range((&lower, &upper), Direction::Desc, |raw_key, _| {
            desc.push(raw_key.as_bytes()[0]);
            Ok(false)
        })
        .expect("heap desc scan should succeed");
    assert_eq!(desc, vec![3, 2, 1]);

    let mut stopped = Vec::new();
    let _: Result<(), std::convert::Infallible> = index_store.visit_entries(|raw_key, _| {
        stopped.push(raw_key.as_bytes()[0]);
        Ok(if stopped.len() == 2 {
            IndexStoreVisit::Stop
        } else {
            IndexStoreVisit::Continue
        })
    });
    assert_eq!(stopped, vec![1, 2]);
}