pipecircuit 0.0.11

Correct. Robust. Fast. Portable.
Documentation
use super::{
    UniqueIndex, UniqueIndexCreationError, UniqueIndexInconsistency, UniqueIndexInsertError,
    UniqueIndexInsertOutcome, UniqueIndexLookupError, UniqueIndexLookupOutcome,
    UniqueIndexRemoveError, UniqueIndexRemoveOutcome, UniqueIndexReplaceError,
    UniqueIndexReplaceOutcome,
};
use crate::storage::warp_table::IndexKey;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Key(u64);

impl IndexKey for Key {
    fn index_word(self) -> u64 {
        self.0
    }
}

fn insert(index: &mut UniqueIndex<Key>, key: Key) -> Result<(), &'static str> {
    let prepared = index
        .try_prepare_insert(key)
        .map_err(|_| "unique insertion was not prepared")?;
    let UniqueIndexInsertOutcome::Inserted {
        key: inserted,
        row: _,
    } = prepared.commit();
    if inserted != key {
        return Err("unique insertion changed its key");
    }
    Ok(())
}

#[test]
fn unique_index_reserves_bounded_storage() {
    let Ok(index) = UniqueIndex::<Key>::try_new(3) else {
        return;
    };
    assert_eq!(index.maximum_keys, 3);
    assert!(index.keys.capacity() >= 3);
    assert!(matches!(
        UniqueIndex::<Key>::try_new(usize::MAX),
        Err(UniqueIndexCreationError::CapacityOverflow { maximum_keys })
            if maximum_keys == usize::MAX
    ));
    assert!(matches!(
        UniqueIndex::<Key>::try_new(usize::MAX / 4),
        Err(UniqueIndexCreationError::IndexStorageUnavailable { .. })
    ));
}

#[test]
fn unique_index_insertions_are_atomic_bounded_and_unique() -> Result<(), &'static str> {
    let mut index = UniqueIndex::try_new(1).map_err(|_| "unique index unavailable")?;

    {
        let _abandoned = index
            .try_prepare_insert(Key(1))
            .map_err(|_| "unique insertion was not prepared")?;
    }
    assert_eq!(
        index.find(Key(1)),
        Ok(UniqueIndexLookupOutcome::Absent { key: Key(1) })
    );

    insert(&mut index, Key(1))?;
    assert_eq!(
        index.find(Key(1)),
        Ok(UniqueIndexLookupOutcome::Present {
            key: Key(1),
            row: 0,
        })
    );
    assert_eq!(
        index.try_prepare_insert(Key(1)).err(),
        Some(UniqueIndexInsertError::AlreadyPresent {
            key: Key(1),
            existing_row: 0,
        })
    );
    assert_eq!(
        index.try_prepare_insert(Key(2)).err(),
        Some(UniqueIndexInsertError::Full {
            key: Key(2),
            maximum_keys: 1,
        })
    );
    Ok(())
}

#[test]
fn unique_index_replacement_preserves_dense_rows() -> Result<(), &'static str> {
    let mut index = UniqueIndex::try_new(3).map_err(|_| "unique index unavailable")?;
    insert(&mut index, Key(1))?;
    insert(&mut index, Key(2))?;

    assert_eq!(
        index.try_prepare_replace(Key(9), Key(3), 0).err(),
        Some(UniqueIndexReplaceError::SourceAbsent {
            source: Key(9),
            target: Key(3),
        })
    );
    assert_eq!(
        index.try_prepare_replace(Key(1), Key(2), 0).err(),
        Some(UniqueIndexReplaceError::TargetPresent {
            source: Key(1),
            target: Key(2),
            existing_row: 1,
        })
    );
    assert_eq!(
        index.try_prepare_replace(Key(1), Key(1), 1).err(),
        Some(UniqueIndexReplaceError::Inconsistent {
            source: Key(1),
            target: Key(1),
            reason: UniqueIndexInconsistency::ExpectedRowMismatch {
                expected: 1,
                actual: 0,
            },
        })
    );
    assert_eq!(
        index.try_prepare_replace(Key(1), Key(3), 1).err(),
        Some(UniqueIndexReplaceError::Inconsistent {
            source: Key(1),
            target: Key(3),
            reason: UniqueIndexInconsistency::ExpectedRowMismatch {
                expected: 1,
                actual: 0,
            },
        })
    );

    let unchanged = index
        .try_prepare_replace(Key(1), Key(1), 0)
        .map_err(|_| "unchanged replacement was not prepared")?;
    assert_eq!(
        unchanged.commit(),
        UniqueIndexReplaceOutcome::Unchanged {
            key: Key(1),
            row: 0,
        }
    );
    let replacement = index
        .try_prepare_replace(Key(1), Key(3), 0)
        .map_err(|_| "replacement was not prepared")?;
    assert_eq!(
        replacement.commit(),
        UniqueIndexReplaceOutcome::Replaced {
            source: Key(1),
            target: Key(3),
            row: 0,
        }
    );
    assert_eq!(
        index.find(Key(1)),
        Ok(UniqueIndexLookupOutcome::Absent { key: Key(1) })
    );
    assert_eq!(
        index.find(Key(3)),
        Ok(UniqueIndexLookupOutcome::Present {
            key: Key(3),
            row: 0,
        })
    );
    Ok(())
}

#[test]
fn unique_index_removal_follows_dense_relocation() -> Result<(), &'static str> {
    let mut index = UniqueIndex::try_new(3).map_err(|_| "unique index unavailable")?;
    assert_eq!(
        index.try_prepare_remove(Key(1)).err(),
        Some(UniqueIndexRemoveError::Absent { key: Key(1) })
    );
    insert(&mut index, Key(1))?;
    insert(&mut index, Key(2))?;
    insert(&mut index, Key(3))?;
    assert_eq!(
        index.try_prepare_remove(Key(9)).err(),
        Some(UniqueIndexRemoveError::Absent { key: Key(9) })
    );

    let removal = index
        .try_prepare_remove(Key(2))
        .map_err(|_| "unique removal was not prepared")?;
    assert_eq!(
        removal.commit(),
        UniqueIndexRemoveOutcome::Removed {
            key: Key(2),
            row: 1,
        }
    );
    assert_eq!(
        index.find(Key(3)),
        Ok(UniqueIndexLookupOutcome::Present {
            key: Key(3),
            row: 1,
        })
    );
    Ok(())
}

#[test]
fn unique_index_preserves_exact_inconsistency_facts() -> Result<(), &'static str> {
    let mut index = UniqueIndex::try_new(2).map_err(|_| "unique index unavailable")?;
    insert(&mut index, Key(1))?;
    insert(&mut index, Key(2))?;
    let _ = index.keys.pop();

    let reason = UniqueIndexInconsistency::KeyUnavailable { row: 1 };
    assert_eq!(
        index.find(Key(2)),
        Err(UniqueIndexLookupError::Inconsistent {
            key: Key(2),
            reason,
        })
    );
    assert_eq!(
        index.try_prepare_insert(Key(2)).err(),
        Some(UniqueIndexInsertError::Inconsistent {
            key: Key(2),
            reason,
        })
    );
    assert_eq!(
        index.try_prepare_remove(Key(1)).err(),
        Some(UniqueIndexRemoveError::Inconsistent {
            key: Key(1),
            reason,
        })
    );
    assert_eq!(
        index.try_prepare_replace(Key(2), Key(3), 1).err(),
        Some(UniqueIndexReplaceError::Inconsistent {
            source: Key(2),
            target: Key(3),
            reason,
        })
    );
    Ok(())
}

#[test]
fn unique_index_inconsistency_maps_every_dense_lookup_fact() {
    use super::super::dense_key_index::IndexInconsistency;

    for (dense, unique) in [
        (
            IndexInconsistency::BucketUnavailable { bucket: 3 },
            UniqueIndexInconsistency::BucketUnavailable { bucket: 3 },
        ),
        (
            IndexInconsistency::StoredRowUnrepresentable { stored_row: 7 },
            UniqueIndexInconsistency::StoredRowUnrepresentable { stored_row: 7 },
        ),
        (
            IndexInconsistency::KeyUnavailable { row: 11 },
            UniqueIndexInconsistency::KeyUnavailable { row: 11 },
        ),
        (
            IndexInconsistency::StoredRowMismatch {
                expected: 13,
                actual: 17,
            },
            UniqueIndexInconsistency::StoredRowMismatch {
                expected: 13,
                actual: 17,
            },
        ),
        (
            IndexInconsistency::ProbeLimitReached,
            UniqueIndexInconsistency::ProbeLimitReached,
        ),
    ] {
        assert_eq!(UniqueIndexInconsistency::from(dense), unique);
    }
}