use core::num::NonZeroU64;
use super::inconsistency::invariant_violation;
use super::{
WarpId, WarpTable, WarpTableEntry, WarpTableInconsistency, WarpTableRow,
WarpTableVacantInsertOutcome,
};
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub enum WarpTableInsertOutcome<Id, Row: WarpTableRow> {
Inserted {
id: Id,
row: usize,
},
Full {
rejected: Row,
},
IdentifiersExhausted {
rejected: Row,
},
UniqueConflict {
conflict: Row::UniqueConflict,
rejected: Row,
},
}
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub enum WarpTableInsertWithIdOutcome<Id, Row: WarpTableRow> {
Inserted {
id: Id,
row: usize,
},
AlreadyPresent {
id: Id,
existing_row: usize,
rejected: Row,
},
Full {
id: Id,
rejected: Row,
},
UniqueConflict {
id: Id,
conflict: Row::UniqueConflict,
rejected: Row,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum WarpTableInsertAllOutcome {
Completed,
}
impl<Row: WarpTableRow, Brand> WarpTable<Row, Brand> {
pub fn insert_with_id(
&mut self,
id: Row::Id,
row: Row,
) -> WarpTableInsertWithIdOutcome<Row::Id, Row> {
let vacancy = match self.entry(id) {
WarpTableEntry::Present {
dense_row: existing_row,
..
} => {
return WarpTableInsertWithIdOutcome::AlreadyPresent {
id,
existing_row,
rejected: row,
};
}
WarpTableEntry::Vacant { vacancy } => vacancy,
};
match vacancy.insert(row) {
WarpTableVacantInsertOutcome::Inserted { id, row } => {
WarpTableInsertWithIdOutcome::Inserted { id, row }
}
WarpTableVacantInsertOutcome::Full { id, rejected } => {
WarpTableInsertWithIdOutcome::Full { id, rejected }
}
WarpTableVacantInsertOutcome::UniqueConflict {
id,
conflict,
rejected,
} => WarpTableInsertWithIdOutcome::UniqueConflict {
id,
conflict,
rejected,
},
}
}
pub fn insert_all_with_id(
&mut self,
rows: impl IntoIterator<Item = (Row::Id, Row)>,
mut observe: impl FnMut(WarpTableInsertWithIdOutcome<Row::Id, Row>),
) -> WarpTableInsertAllOutcome {
for (id, row) in rows {
observe(self.insert_with_id(id, row));
}
WarpTableInsertAllOutcome::Completed
}
}
impl<Row, IdRow, Brand> WarpTable<Row, Brand>
where
Row: WarpTableRow<Id = WarpId<IdRow>>,
{
pub fn insert(&mut self, row: Row) -> WarpTableInsertOutcome<WarpId<IdRow>, Row> {
if self.ids.len() != self.len {
invariant_violation(WarpTableInconsistency::IdColumnLengthMismatch {
ids: self.ids.len(),
rows: self.len,
});
}
if self.len >= self.maximum_rows {
return WarpTableInsertOutcome::Full { rejected: row };
}
let attempts = self.len.saturating_add(1);
self.insert_generated(row, attempts)
}
fn insert_generated(
&mut self,
mut row: Row,
attempts: usize,
) -> WarpTableInsertOutcome<WarpId<IdRow>, Row> {
for _ in 0..attempts {
let Some(sequence) = self.next_id else {
return WarpTableInsertOutcome::IdentifiersExhausted { rejected: row };
};
let id = WarpId::new(sequence);
match self.insert_with_id(id, row) {
WarpTableInsertWithIdOutcome::Inserted {
row: warp_table_row,
..
} => {
self.next_id = following_sequence(sequence);
return WarpTableInsertOutcome::Inserted {
id,
row: warp_table_row,
};
}
WarpTableInsertWithIdOutcome::AlreadyPresent { rejected, .. } => {
self.next_id = following_sequence(sequence);
row = rejected;
}
WarpTableInsertWithIdOutcome::UniqueConflict {
conflict, rejected, ..
} => {
return WarpTableInsertOutcome::UniqueConflict { conflict, rejected };
}
WarpTableInsertWithIdOutcome::Full { .. } => invariant_violation(
WarpTableInconsistency::CapacityReachedDuringGeneratedInsert,
),
}
}
invariant_violation(WarpTableInconsistency::IdentifierSequenceBlocked { attempts })
}
pub fn insert_all(
&mut self,
rows: impl IntoIterator<Item = Row>,
mut observe: impl FnMut(WarpTableInsertOutcome<WarpId<IdRow>, Row>),
) -> WarpTableInsertAllOutcome {
for row in rows {
observe(self.insert(row));
}
WarpTableInsertAllOutcome::Completed
}
}
#[inline]
fn following_sequence(sequence: NonZeroU64) -> Option<NonZeroU64> {
sequence.get().checked_add(1).and_then(NonZeroU64::new)
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use alloc::string::String;
use core::{num::NonZeroU32, panic::AssertUnwindSafe};
use std::panic::catch_unwind;
#[derive(WarpTableRow)]
struct Item {
value: u32,
}
#[test]
fn exhausted_identifier_search_preserves_rows_and_reports_its_attempt_bound()
-> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(super::super::Capacity::new(
NonZeroU32::MIN.saturating_add(1),
))
.map_err(|_| "table unavailable")?;
let id = WarpId::new(NonZeroU64::MIN);
assert!(matches!(
table.insert_with_id(id, Item { value: 11 }),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
let Err(payload) = catch_unwind(AssertUnwindSafe(|| {
table.insert_generated(Item { value: 17 }, 1)
})) else {
return Err("exhausted search did not stop");
};
assert_eq!(
payload
.downcast_ref::<String>()
.ok_or("unexpected diagnostic")?,
"WarpTable invariant violated: IdentifierSequenceBlocked { attempts: 1 }"
);
assert_eq!(table.ids(), &[id]);
assert_eq!(table.columns.value, [11]);
assert_eq!(table.next_id, Some(NonZeroU64::MIN.saturating_add(1)));
Ok(())
}
#[test]
fn lost_admission_capacity_cannot_consume_the_next_identifier_or_existing_row()
-> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(super::super::Capacity::new(NonZeroU32::MIN))
.map_err(|_| "table unavailable")?;
let WarpTableInsertOutcome::Inserted { id, .. } = table.insert(Item { value: 11 }) else {
return Err("first insertion refused");
};
let next = table.next_id;
let Err(payload) = catch_unwind(AssertUnwindSafe(|| {
table.insert_generated(Item { value: 17 }, 1)
})) else {
return Err("lost admission capacity did not stop insertion");
};
assert_eq!(
payload
.downcast_ref::<String>()
.ok_or("unexpected diagnostic")?,
"WarpTable invariant violated: CapacityReachedDuringGeneratedInsert"
);
assert_eq!(table.ids(), &[id]);
assert_eq!(table.columns.value, [11]);
assert_eq!(table.next_id, next);
Ok(())
}
}