use super::bucket::{next_bucket, probe_distance, reduce};
use super::{DenseKeyIndex, EMPTY_ROW, FindOutcome, IndexInconsistency};
use crate::storage::warp_table::IndexKey;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::storage::warp_table) struct PreparedRemoval<Key> {
bucket: usize,
pub(in crate::storage::warp_table) row: usize,
pub(in crate::storage::warp_table) relocation: RowRelocation<Key>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::storage::warp_table) enum RowRelocation<Key> {
Stable,
Relocated {
key: Key,
bucket: usize,
from: usize,
to: usize,
stored_to: u32,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub(in crate::storage::warp_table) enum PrepareRemoveOutcome<Key> {
Prepared(PreparedRemoval<Key>),
Absent,
}
impl DenseKeyIndex {
pub(in crate::storage::warp_table) fn prepare_remove<Key: IndexKey>(
&self,
key: Key,
keys: &[Key],
last_row: usize,
) -> Result<PrepareRemoveOutcome<Key>, IndexInconsistency> {
let (bucket, stored_row, row) = match self.find(key, keys)? {
FindOutcome::Present {
bucket,
stored_row,
row,
} => (bucket, stored_row, row),
FindOutcome::Absent => return Ok(PrepareRemoveOutcome::Absent),
};
let relocation = if row == last_row {
RowRelocation::Stable
} else {
let Some(moved_key) = keys.get(last_row).copied() else {
return Err(IndexInconsistency::KeyUnavailable { row: last_row });
};
match self.find(moved_key, keys)? {
FindOutcome::Present {
bucket: moved_bucket,
row: moved_row,
..
} if moved_row == last_row => RowRelocation::Relocated {
key: moved_key,
bucket: moved_bucket,
from: last_row,
to: row,
stored_to: stored_row,
},
FindOutcome::Present { row: actual, .. } => {
return Err(IndexInconsistency::StoredRowMismatch {
expected: last_row,
actual,
});
}
FindOutcome::Absent => {
return Err(IndexInconsistency::KeyUnavailable { row: last_row });
}
}
};
self.validate_cluster_after(bucket, keys)?;
Ok(PrepareRemoveOutcome::Prepared(PreparedRemoval {
bucket,
row,
relocation,
}))
}
fn validate_cluster_after<Key: IndexKey>(
&self,
removed_bucket: usize,
keys: &[Key],
) -> Result<(), IndexInconsistency> {
let mut bucket = next_bucket(removed_bucket, 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(());
}
let row = usize::try_from(stored_row)
.map_err(|_| IndexInconsistency::StoredRowUnrepresentable { stored_row })?;
if keys.get(row).is_none() {
return Err(IndexInconsistency::KeyUnavailable { row });
}
bucket = next_bucket(bucket, self.buckets.len());
}
Err(IndexInconsistency::ProbeLimitReached)
}
#[allow(
clippy::indexing_slicing,
reason = "prepared removal proves every modulo bucket and stored row before mutation"
)]
pub(in crate::storage::warp_table) fn commit_remove<Key: IndexKey>(
&mut self,
removal: PreparedRemoval<Key>,
keys: &[Key],
) {
let relocated = match removal.relocation {
RowRelocation::Stable => None,
RowRelocation::Relocated {
key,
bucket,
from: _,
to,
stored_to,
} => {
self.buckets[bucket] = stored_to;
Some((key, to))
}
};
let mut vacancy = removal.bucket;
let mut bucket = next_bucket(vacancy, self.buckets.len());
loop {
let stored_row = self.buckets[bucket];
if stored_row == EMPTY_ROW {
self.buckets[vacancy] = EMPTY_ROW;
return;
}
let row = stored_row as usize;
let word = match relocated {
Some((key, relocated_row)) if row == relocated_row => key.index_word(),
_ => keys[row].index_word(),
};
let ideal = reduce(word, self.buckets.len());
if probe_distance(ideal, bucket, self.buckets.len())
> probe_distance(ideal, vacancy, self.buckets.len())
{
self.buckets[vacancy] = stored_row;
vacancy = bucket;
}
bucket = next_bucket(bucket, self.buckets.len());
}
}
}