pipecircuit 0.0.23

Correct. Robust. Fast. Portable. Pick four.
Documentation
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> {
    /// Inserts a row under a caller-supplied ID.
    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,
            },
        }
    }

    /// Inserts every caller-identified row through [`Self::insert_with_id`].
    ///
    /// `observe` receives each outcome by value, including every rejected row.
    /// The operation allocates no result collection of its own.
    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>>,
{
    /// Inserts a row under the next available typed sequential identifier.
    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 })
    }

    /// Inserts every row through [`Self::insert`] under generated identifiers.
    ///
    /// `observe` receives each outcome by value, including every generated ID
    /// and rejected row. The operation allocates no result collection of its own.
    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(())
    }
}