use super::super::dense_key_index::{
CommitOutcome, PrepareInsertOutcome, PreparedCommit, PreparedVacancy,
};
use super::{IndexKey, UniqueIndex, UniqueIndexInconsistency};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UniqueIndexInsertError<Key> {
AlreadyPresent {
key: Key,
existing_row: usize,
},
Full {
key: Key,
maximum_keys: usize,
},
Inconsistent {
key: Key,
reason: UniqueIndexInconsistency,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum UniqueIndexInsertOutcome<Key> {
Inserted { key: Key, row: usize },
}
pub struct PreparedUniqueIndexInsert<'index, Key: IndexKey> {
index: &'index mut UniqueIndex<Key>,
insertion: PreparedCommit,
key: Key,
}
impl<Key: IndexKey> UniqueIndex<Key> {
pub fn try_prepare_insert(
&mut self,
key: Key,
) -> Result<PreparedUniqueIndexInsert<'_, Key>, UniqueIndexInsertError<Key>> {
let vacancy = match self
.key_index
.prepare_insert(key, &self.keys)
.map_err(|reason| UniqueIndexInsertError::Inconsistent {
key,
reason: reason.into(),
})? {
PrepareInsertOutcome::Vacant(vacancy) => vacancy,
PrepareInsertOutcome::Present { row } => {
return Err(UniqueIndexInsertError::AlreadyPresent {
key,
existing_row: row,
});
}
};
if self.keys.len() >= self.maximum_keys {
return Err(UniqueIndexInsertError::Full {
key,
maximum_keys: self.maximum_keys,
});
}
let row = self.keys.len();
let insertion = self.prepare_primary_insert(key, vacancy, row)?;
Ok(PreparedUniqueIndexInsert {
index: self,
insertion,
key,
})
}
fn prepare_primary_insert(
&self,
key: Key,
vacancy: PreparedVacancy,
row: usize,
) -> Result<PreparedCommit, UniqueIndexInsertError<Key>> {
self.key_index
.prepare_commit(vacancy, row)
.map_err(|reason| UniqueIndexInsertError::Inconsistent {
key,
reason: reason.into(),
})
}
}
impl<Key: IndexKey> PreparedUniqueIndexInsert<'_, Key> {
pub fn commit(self) -> UniqueIndexInsertOutcome<Key> {
let CommitOutcome::Committed { row } = self.index.key_index.commit_prepared(self.insertion);
self.index.keys.push(self.key);
UniqueIndexInsertOutcome::Inserted { key: self.key, row }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reserved_primary_row_preserves_the_rejected_key_and_vacancy() -> Result<(), &'static str> {
let mut index = UniqueIndex::<u32>::try_new(1).map_err(|_| "index unavailable")?;
let PrepareInsertOutcome::Vacant(vacancy) = index
.key_index
.prepare_insert(7, &index.keys)
.map_err(|_| "vacancy unavailable")?
else {
return Err("fresh index was occupied");
};
let row = usize::try_from(u32::MAX).map_err(|_| "row boundary unavailable")?;
assert_eq!(
index.prepare_primary_insert(7, vacancy, row),
Err(UniqueIndexInsertError::Inconsistent {
key: 7,
reason: UniqueIndexInconsistency::InsertRowReserved {
bucket: vacancy.bucket(),
row,
},
})
);
assert!(index.keys.is_empty());
assert_eq!(
index
.try_prepare_insert(7)
.map_err(|_| "valid insertion refused")?
.commit(),
UniqueIndexInsertOutcome::Inserted { key: 7, row: 0 }
);
Ok(())
}
}