use super::blocks::SlotLocation;
use super::key::checked_ordered_word;
use super::{RangeIndex, RangeIndexInconsistency, RangeIndexKey};
use crate::storage::warp_table::secondary_index::StableSlot;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RangeIndexInsertError<Key> {
Full {
key: Key,
slot: StableSlot,
maximum_rows: usize,
},
AlreadyPresent {
key: Key,
slot: StableSlot,
},
OrderedWordOutsideWidth {
key: Key,
ordered_word: u64,
bits: u32,
},
Inconsistent {
key: Key,
slot: StableSlot,
reason: RangeIndexInconsistency,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum RangeIndexInsertOutcome<Key> {
Inserted { key: Key, slot: StableSlot },
}
pub struct PreparedRangeIndexInsert<'index, Key: RangeIndexKey> {
index: &'index mut RangeIndex<Key>,
key: Key,
slot: StableSlot,
location: SlotLocation,
previous_member_count: u64,
committed: bool,
}
impl<Key: RangeIndexKey> RangeIndex<Key> {
pub fn try_prepare_insert(
&mut self,
key: Key,
slot: StableSlot,
) -> Result<PreparedRangeIndexInsert<'_, Key>, RangeIndexInsertError<Key>> {
self.validate_storage()
.map_err(|reason| inconsistent(key, slot, reason))?;
let word = checked_ordered_word(key).map_err(|outside| {
RangeIndexInsertError::OrderedWordOutsideWidth {
key: outside.key,
ordered_word: outside.ordered_word,
bits: outside.bits,
}
})?;
let location = self.locate_slot(slot);
if let Ok(location) = location
&& self
.member_at(location)
.map_err(|reason| inconsistent(key, slot, reason))?
{
return Err(RangeIndexInsertError::AlreadyPresent { key, slot });
}
let members = usize::try_from(self.member_count).map_err(|_| {
inconsistent(
key,
slot,
RangeIndexInconsistency::MemberCountUnrepresentable {
members: self.member_count,
},
)
})?;
if members >= self.maximum_rows {
return Err(RangeIndexInsertError::Full {
key,
slot,
maximum_rows: self.maximum_rows,
});
}
let location = location.map_err(|reason| inconsistent(key, slot, reason))?;
let stale_word = self
.word_at(location)
.map_err(|reason| inconsistent(key, slot, reason))?;
if stale_word != 0 {
return Err(inconsistent(
key,
slot,
RangeIndexInconsistency::StaleBitMembership {
bit: stale_word.trailing_zeros(),
},
));
}
let previous_member_count = self.member_count;
let next_member_count = self.member_count.checked_add(1).ok_or_else(|| {
inconsistent(
key,
slot,
RangeIndexInconsistency::MemberCountUnrepresentable {
members: self.member_count,
},
)
})?;
self.write_slot(location, true, word)
.map_err(|reason| inconsistent(key, slot, reason))?;
self.member_count = next_member_count;
Ok(PreparedRangeIndexInsert {
index: self,
key,
slot,
location,
previous_member_count,
committed: false,
})
}
}
impl<Key: RangeIndexKey> PreparedRangeIndexInsert<'_, Key> {
pub fn commit(mut self) -> RangeIndexInsertOutcome<Key> {
self.committed = true;
RangeIndexInsertOutcome::Inserted {
key: self.key,
slot: self.slot,
}
}
}
impl<Key: RangeIndexKey> Drop for PreparedRangeIndexInsert<'_, Key> {
fn drop(&mut self) {
if !self.committed {
let rollback = self.index.write_slot(self.location, false, 0);
debug_assert!(rollback.is_ok());
self.index.member_count = self.previous_member_count;
}
}
}
pub(super) fn inconsistent<Key>(
key: Key,
slot: StableSlot,
reason: RangeIndexInconsistency,
) -> RangeIndexInsertError<Key> {
RangeIndexInsertError::Inconsistent { key, slot, reason }
}