pipecircuit 0.0.25

Correct. Robust. Fast. Portable. Pick four.
Documentation
#![cfg(feature = "secondary-indexes")]

use core::num::NonZeroU32;
use core::ops::Bound::{Excluded, Included, Unbounded};
use pipecircuit::storage::query_workspace::{
    QueryChunk, QueryWorkspace, QueryWorkspaceError, required_query_chunks,
};
use pipecircuit::storage::warp_table::{
    Capacity, RangeIndexBounds, RangeIndexLookupError, RangeMembershipError, WarpTable,
    WarpTableInsertWithIdOutcome, WarpTableRemoveOutcome, WarpTableRow, WorkspaceSelectionError,
};
use proptest::prelude::*;

#[derive(WarpTableRow)]
#[index(primary = u32)]
struct Unit {
    #[index(range)]
    x: i32,
    #[index(range)]
    y: u16,
    #[index(secondary)]
    team: u8,
}

fn table(capacity: u32) -> Result<WarpTable<Unit>, &'static str> {
    WarpTable::try_new(Capacity::new(NonZeroU32::new(capacity).ok_or("capacity")?))
        .map_err(|_| "table")
}

fn insert(
    table: &mut WarpTable<Unit>,
    id: u32,
    x: i32,
    y: u16,
    team: u8,
) -> Result<(), &'static str> {
    if matches!(
        table.insert_with_id(id, Unit { x, y, team }),
        WarpTableInsertWithIdOutcome::Inserted { .. }
    ) {
        Ok(())
    } else {
        Err("insert")
    }
}

#[test]
fn range_composition_reuses_chunks_across_edges_and_dense_relocation() -> Result<(), &'static str> {
    for capacity in [1, 63, 64, 65, 4095, 4096, 4097, 65535, 65536, 65537, 100000] {
        let mut table = table(capacity)?;
        for id in 0..capacity {
            insert(
                &mut table,
                id,
                (id % 131) as i32 - 65,
                (id % 19) as u16,
                (id % 3) as u8,
            )?;
        }
        assert!(matches!(
            table.remove(0),
            WarpTableRemoveOutcome::Removed { .. }
        ));
        insert(&mut table, capacity, -91, 7, 1)?;
        let mut chunks = core::iter::repeat_with(|| QueryChunk::EMPTY)
            .take(required_query_chunks(capacity as usize).map_err(|_| "chunks")?)
            .collect::<Vec<_>>();
        let mut scratch = QueryChunk::EMPTY;
        let mut workspace =
            QueryWorkspace::new(capacity as usize, chunks.as_mut_slice(), &mut scratch)
                .map_err(|_| "workspace")?;
        for bounds in [
            (Unbounded, Unbounded),
            (Included(-17), Excluded(33)),
            (Included(91), Included(91)),
            (Excluded(0), Included(0)),
            (Unbounded, Excluded(-18)),
            (Excluded(20), Unbounded),
        ] {
            let lookup = table
                .query_memberships()
                .find_x_in(bounds)
                .map_err(|_| "range")?;
            assert_eq!(lookup.bounds(), &RangeIndexBounds::from_range(bounds));
            let selected = lookup
                .in_workspace(workspace.membership().map_err(|_| "members")?)
                .map_err(|_| "range workspace")?
                .and(|q| q.find_team(1))
                .map_err(|_| "and")?
                .or(|q| q.find_y_in(3..=6))
                .map_err(|_| "or")?
                .and_not(|q| q.find_x_in(-9..=9))
                .map_err(|_| "difference")?
                .xor(|q| q.find_y_in(0..=1))
                .map_err(|_| "xor")?;
            let expected = table
                .query()
                .find_x_in(bounds)
                .map_err(|_| "owned range")?
                .and(|q| q.find_team(1))
                .or(|q| q.find_y_in(3..=6))
                .map_err(|_| "owned or")?
                .and_not(|q| q.find_x_in(-9..=9))
                .map_err(|_| "owned difference")?
                .xor(|q| q.find_y_in(0..=1))
                .map_err(|_| "owned xor")?;
            assert_eq!(selected.len(), expected.len());
            assert_eq!(
                selected.rows().map(|r| *r.id).collect::<Vec<_>>(),
                expected.rows().map(|r| *r.id).collect::<Vec<_>>()
            );
            let sparse = table
                .query_memberships()
                .find_team(2)
                .in_workspace(workspace.membership().map_err(|_| "members")?)
                .map_err(|_| "start")?
                .and(|q| q.find_x_in(bounds))
                .map_err(|_| "sparse range")?;
            assert_eq!(
                sparse.len(),
                table
                    .query()
                    .find_team(2)
                    .and(|q| q.find_x_in(bounds))
                    .map_err(|_| "expected sparse")?
                    .len()
            );
        }
    }
    Ok(())
}

#[test]
fn refusals_leave_membership_reusable_and_preserve_exact_reasons() -> Result<(), &'static str> {
    let mut table = table(8)?;
    insert(&mut table, 1, 5, 3, 1)?;
    let foreign = self::table(8)?;
    let mut chunks = [QueryChunk::EMPTY];
    let mut scratch = QueryChunk::EMPTY;
    let mut workspace =
        QueryWorkspace::new(8, &mut chunks, &mut scratch).map_err(|_| "workspace")?;
    let selection = table
        .query_memberships()
        .find_team(1)
        .in_workspace(workspace.membership().map_err(|_| "members")?)
        .map_err(|_| "start")?;
    assert!(
        matches!(selection.and(|q| q.find_x_in((Included(9), Included(1)))), Err(RangeMembershipError::Lookup(RangeIndexLookupError::ReversedBounds { bounds })) if bounds == RangeIndexBounds::from_range((Included(9), Included(1))))
    );
    assert_eq!(workspace.membership().map_err(|_| "members")?.len(), 1);
    let selection = table
        .query_memberships()
        .find_x_in(..)
        .map_err(|_| "range")?
        .in_workspace(workspace.membership().map_err(|_| "members")?)
        .map_err(|_| "start")?;
    assert!(matches!(
        selection.or(|_| foreign.query_memberships().find_y_in(..)),
        Err(RangeMembershipError::Selection(
            WorkspaceSelectionError::DifferentTables
        ))
    ));
    assert_eq!(
        workspace
            .membership()
            .map_err(|_| "members")?
            .slots()
            .collect::<Vec<_>>(),
        [0]
    );
    let mismatched_index =
        pipecircuit::storage::warp_table::__private::RangeIndex::<i32>::try_new(7)
            .map_err(|_| "index")?;
    let selection = table
        .query_memberships()
        .find_team(1)
        .in_workspace(workspace.membership().map_err(|_| "members")?)
        .map_err(|_| "start")?;
    assert!(matches!(
        selection.and(|_| {
            pipecircuit::storage::warp_table::__private::bind_range_membership_lookup(
                &table,
                &mismatched_index,
                ..,
            )
        }),
        Err(RangeMembershipError::Selection(
            WorkspaceSelectionError::Workspace(QueryWorkspaceError::DifferentCapacities {
                left: 8,
                right: 7
            })
        ))
    ));
    assert_eq!(
        workspace
            .membership()
            .map_err(|_| "members")?
            .slots()
            .collect::<Vec<_>>(),
        [0]
    );
    let mut workspace =
        QueryWorkspace::new(7, &mut chunks, &mut scratch).map_err(|_| "workspace")?;
    let mut members = workspace.membership().map_err(|_| "members")?;
    let _inserted = members.insert(5).map_err(|_| "insert")?;
    assert!(matches!(
        table
            .query_memberships()
            .find_x_in(..)
            .map_err(|_| "range")?
            .in_workspace(members),
        Err(RangeMembershipError::Selection(
            WorkspaceSelectionError::Workspace(QueryWorkspaceError::DifferentCapacities {
                left: 7,
                right: 8
            })
        ))
    ));
    assert_eq!(
        workspace
            .membership()
            .map_err(|_| "members")?
            .slots()
            .collect::<Vec<_>>(),
        [5]
    );
    Ok(())
}

proptest! {
    #![proptest_config(ProptestConfig::with_cases(128))]
    #[test]
    fn mixed_composition_matches_row_predicates(
        rows in prop::collection::vec((any::<i16>(), any::<u16>(), 0_u8..4), 0..256),
        bounds in prop::array::uniform4(any::<i16>()),
        y in any::<u16>(),
        team in 0_u8..5,
    ) {
        let mut table = table(256).map_err(TestCaseError::fail)?;
        for (id, (x, y, team)) in rows.iter().copied().enumerate() {
            insert(&mut table, id as u32, i32::from(x), y, team).map_err(TestCaseError::fail)?;
        }
        let mut chunks = [QueryChunk::EMPTY];
        let mut scratch = QueryChunk::EMPTY;
        let mut workspace = QueryWorkspace::new(256, &mut chunks, &mut scratch).map_err(|_| TestCaseError::fail("workspace"))?;
        let [a,b,c,d] = bounds;
        let first = i32::from(a.min(b))..=i32::from(a.max(b));
        let second = i32::from(c.min(d))..=i32::from(c.max(d));
        let selected = table.query_memberships().find_x_in(first.clone()).map_err(|_| TestCaseError::fail("range"))?
            .in_workspace(workspace.membership().map_err(|_| TestCaseError::fail("members"))?).map_err(|_| TestCaseError::fail("start"))?
            .xor(|q| q.find_team(team)).map_err(|_| TestCaseError::fail("xor"))?
            .or(|q| q.find_y_in(..y)).map_err(|_| TestCaseError::fail("union"))?
            .and(|q| q.find_x_in(second.clone())).map_err(|_| TestCaseError::fail("intersection"))?;
        let expected = rows.iter().enumerate().filter(|(_, (x, ry, rt))| ((first.contains(&i32::from(*x)) != (*rt == team)) || *ry < y) && second.contains(&i32::from(*x))).map(|(id, _)| id as u32).collect::<Vec<_>>();
        prop_assert_eq!(selected.len() as usize, expected.len());
        prop_assert_eq!(selected.ids().collect::<Vec<_>>(), expected);
    }
}