pipecircuit 0.0.23

Correct. Robust. Fast. Portable. Pick four.
Documentation
use super::super::dense_key_index::{
    CommitOutcome, PrepareInsertOutcome, PreparedCommit, PreparedVacancy,
};
use super::{IndexKey, UniqueIndex, UniqueIndexInconsistency};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UniqueIndexInsertError<Key> {
    AlreadyPresent {
        key: Key,
        existing_row: usize,
    },
    Full {
        key: Key,
        maximum_keys: usize,
    },
    Inconsistent {
        key: Key,
        reason: UniqueIndexInconsistency,
    },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum UniqueIndexInsertOutcome<Key> {
    Inserted { key: Key, row: usize },
}

pub struct PreparedUniqueIndexInsert<'index, Key: IndexKey> {
    index: &'index mut UniqueIndex<Key>,
    insertion: PreparedCommit,
    key: Key,
}

impl<Key: IndexKey> UniqueIndex<Key> {
    pub fn try_prepare_insert(
        &mut self,
        key: Key,
    ) -> Result<PreparedUniqueIndexInsert<'_, Key>, UniqueIndexInsertError<Key>> {
        let vacancy = match self
            .key_index
            .prepare_insert(key, &self.keys)
            .map_err(|reason| UniqueIndexInsertError::Inconsistent {
                key,
                reason: reason.into(),
            })? {
            PrepareInsertOutcome::Vacant(vacancy) => vacancy,
            PrepareInsertOutcome::Present { row } => {
                return Err(UniqueIndexInsertError::AlreadyPresent {
                    key,
                    existing_row: row,
                });
            }
        };
        if self.keys.len() >= self.maximum_keys {
            return Err(UniqueIndexInsertError::Full {
                key,
                maximum_keys: self.maximum_keys,
            });
        }
        let row = self.keys.len();
        let insertion = self.prepare_primary_insert(key, vacancy, row)?;
        Ok(PreparedUniqueIndexInsert {
            index: self,
            insertion,
            key,
        })
    }

    fn prepare_primary_insert(
        &self,
        key: Key,
        vacancy: PreparedVacancy,
        row: usize,
    ) -> Result<PreparedCommit, UniqueIndexInsertError<Key>> {
        self.key_index
            .prepare_commit(vacancy, row)
            .map_err(|reason| UniqueIndexInsertError::Inconsistent {
                key,
                reason: reason.into(),
            })
    }
}

impl<Key: IndexKey> PreparedUniqueIndexInsert<'_, Key> {
    pub fn commit(self) -> UniqueIndexInsertOutcome<Key> {
        let CommitOutcome::Committed { row } = self.index.key_index.commit_prepared(self.insertion);
        self.index.keys.push(self.key);
        UniqueIndexInsertOutcome::Inserted { key: self.key, row }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reserved_primary_row_preserves_the_rejected_key_and_vacancy() -> Result<(), &'static str> {
        let mut index = UniqueIndex::<u32>::try_new(1).map_err(|_| "index unavailable")?;
        let PrepareInsertOutcome::Vacant(vacancy) = index
            .key_index
            .prepare_insert(7, &index.keys)
            .map_err(|_| "vacancy unavailable")?
        else {
            return Err("fresh index was occupied");
        };
        let row = usize::try_from(u32::MAX).map_err(|_| "row boundary unavailable")?;
        assert_eq!(
            index.prepare_primary_insert(7, vacancy, row),
            Err(UniqueIndexInsertError::Inconsistent {
                key: 7,
                reason: UniqueIndexInconsistency::InsertRowReserved {
                    bucket: vacancy.bucket(),
                    row,
                },
            })
        );
        assert!(index.keys.is_empty());
        assert_eq!(
            index
                .try_prepare_insert(7)
                .map_err(|_| "valid insertion refused")?
                .commit(),
            UniqueIndexInsertOutcome::Inserted { key: 7, row: 0 }
        );
        Ok(())
    }
}