use super::bucket::{next_bucket, reduce};
use super::{DenseKeyIndex, EMPTY_ROW, IndexInconsistency};
use crate::storage::warp_table::IndexKey;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::storage::warp_table) struct PreparedVacancy {
pub(super) bucket: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::storage::warp_table) struct PreparedCommit {
vacancy: PreparedVacancy,
row: usize,
stored_row: u32,
}
impl PreparedVacancy {
pub(in crate::storage::warp_table) const fn bucket(self) -> usize {
self.bucket
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub(in crate::storage::warp_table) enum PrepareInsertOutcome {
Vacant(PreparedVacancy),
Present { row: usize },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub(in crate::storage::warp_table) enum CommitOutcome {
Committed { row: usize },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::storage::warp_table) enum CommitInconsistency {
RowUnrepresentable {
vacancy: PreparedVacancy,
row: usize,
},
RowReserved {
vacancy: PreparedVacancy,
row: usize,
},
VacancyUnavailable {
vacancy: PreparedVacancy,
row: usize,
},
VacancyOccupied {
vacancy: PreparedVacancy,
row: usize,
occupying_row: u32,
},
}
impl DenseKeyIndex {
pub(in crate::storage::warp_table) fn prepare_insert<Key: IndexKey>(
&self,
key: Key,
keys: &[Key],
) -> Result<PrepareInsertOutcome, IndexInconsistency> {
let mut bucket = reduce(key.index_word(), self.buckets.len());
for _ in 0..self.buckets.len() {
let Some(stored_row) = self.buckets.get(bucket).copied() else {
return Err(IndexInconsistency::BucketUnavailable { bucket });
};
if stored_row == EMPTY_ROW {
return Ok(PrepareInsertOutcome::Vacant(PreparedVacancy { bucket }));
}
let Ok(row) = usize::try_from(stored_row) else {
return Err(IndexInconsistency::StoredRowUnrepresentable { stored_row });
};
let Some(stored_key) = keys.get(row).copied() else {
return Err(IndexInconsistency::KeyUnavailable { row });
};
if stored_key == key {
return Ok(PrepareInsertOutcome::Present { row });
}
bucket = next_bucket(bucket, self.buckets.len());
}
Err(IndexInconsistency::ProbeLimitReached)
}
#[cfg(test)]
pub(in crate::storage::warp_table) fn commit(
&mut self,
vacancy: PreparedVacancy,
row: usize,
) -> Result<CommitOutcome, CommitInconsistency> {
let prepared = self.prepare_commit(vacancy, row)?;
Ok(self.commit_prepared(prepared))
}
pub(in crate::storage::warp_table) fn prepare_commit(
&self,
vacancy: PreparedVacancy,
row: usize,
) -> Result<PreparedCommit, CommitInconsistency> {
let Ok(stored_row) = u32::try_from(row) else {
return Err(CommitInconsistency::RowUnrepresentable { vacancy, row });
};
if stored_row == EMPTY_ROW {
return Err(CommitInconsistency::RowReserved { vacancy, row });
}
let Some(stored) = self.buckets.get(vacancy.bucket) else {
return Err(CommitInconsistency::VacancyUnavailable { vacancy, row });
};
if *stored != EMPTY_ROW {
return Err(CommitInconsistency::VacancyOccupied {
vacancy,
row,
occupying_row: *stored,
});
}
Ok(PreparedCommit {
vacancy,
row,
stored_row,
})
}
#[allow(
clippy::indexing_slicing,
reason = "the prepared commit proves its bucket is present and vacant"
)]
pub(in crate::storage::warp_table) fn commit_prepared(
&mut self,
prepared: PreparedCommit,
) -> CommitOutcome {
self.buckets[prepared.vacancy.bucket] = prepared.stored_row;
CommitOutcome::Committed { row: prepared.row }
}
}