use super::super::dense_key_index::{DenseKeyIndex, required_buckets};
use super::super::inconsistency::invariant_violation;
use super::super::{ColumnLease, IndexKey};
use super::{UniqueIndex, UniqueIndexBacking, UniqueIndexInconsistency};
pub type CallerUniqueIndex<'storage, Key> =
UniqueIndex<Key, CallerUniqueIndexStorage<'storage, Key>>;
#[derive(Debug)]
pub struct CallerUniqueIndexStorage<'storage, Key: IndexKey> {
pub keys: ColumnLease<'storage, Key>,
pub buckets: &'storage mut [u32],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UniqueIndexStorageError {
CapacityOverflow { maximum_keys: usize },
KeysNotEmpty { keys: usize },
KeyCapacityInsufficient { required: usize, available: usize },
BucketCapacityInsufficient { required: usize, available: usize },
}
#[derive(Debug)]
pub struct UniqueIndexStorageRefusal<'storage, Key: IndexKey> {
pub storage: CallerUniqueIndexStorage<'storage, Key>,
pub reason: UniqueIndexStorageError,
}
impl<Key: IndexKey> UniqueIndex<Key> {
pub fn required_buckets(maximum_keys: usize) -> Result<usize, UniqueIndexStorageError> {
if u32::try_from(maximum_keys).is_err() {
return Err(UniqueIndexStorageError::CapacityOverflow { maximum_keys });
}
required_buckets(maximum_keys)
.ok_or(UniqueIndexStorageError::CapacityOverflow { maximum_keys })
}
pub fn try_from_storage(
maximum_keys: usize,
storage: CallerUniqueIndexStorage<'_, Key>,
) -> Result<CallerUniqueIndex<'_, Key>, UniqueIndexStorageRefusal<'_, Key>> {
let validation = (|| {
let buckets = Self::required_buckets(maximum_keys)?;
if !storage.keys.is_empty() {
return Err(UniqueIndexStorageError::KeysNotEmpty {
keys: storage.keys.len(),
});
}
if storage.keys.capacity() < maximum_keys {
return Err(UniqueIndexStorageError::KeyCapacityInsufficient {
required: maximum_keys,
available: storage.keys.capacity(),
});
}
if storage.buckets.len() < buckets {
return Err(UniqueIndexStorageError::BucketCapacityInsufficient {
required: buckets,
available: storage.buckets.len(),
});
}
Ok(())
})();
if let Err(reason) = validation {
return Err(UniqueIndexStorageRefusal { storage, reason });
}
Ok(UniqueIndex {
maximum_keys,
keys: storage.keys,
key_index: DenseKeyIndex::from_buckets(storage.buckets),
})
}
}
impl<'storage, Key: IndexKey> UniqueIndexBacking<Key> for CallerUniqueIndexStorage<'storage, Key> {
type Keys = ColumnLease<'storage, Key>;
type Buckets = &'storage mut [u32];
fn keys(keys: &Self::Keys) -> &[Key] {
keys.as_slice()
}
fn keys_mut(keys: &mut Self::Keys) -> &mut [Key] {
keys.as_mut_slice()
}
fn append(keys: &mut Self::Keys, key: Key) {
if keys.push(key).is_err() {
invariant_violation(UniqueIndexInconsistency::KeyStorageFull {
capacity: keys.capacity(),
});
}
}
fn remove(keys: &mut Self::Keys, row: usize) {
if keys.swap_remove(row).is_none() {
invariant_violation(UniqueIndexInconsistency::KeyUnavailable { row });
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::MaybeUninit;
#[test]
#[should_panic(expected = "KeyStorageFull")]
fn corrupt_capacity_cannot_silently_lose_a_key() {
let mut storage = [];
let mut keys = ColumnLease::new(&mut storage);
CallerUniqueIndexStorage::append(&mut keys, 1_u64);
}
#[test]
#[should_panic(expected = "KeyUnavailable")]
fn corrupt_row_cannot_silently_skip_removal() {
let mut storage = [MaybeUninit::<u64>::uninit(); 1];
let mut keys = ColumnLease::new(&mut storage);
CallerUniqueIndexStorage::remove(&mut keys, 0);
}
}