pipecircuit 0.0.13

Correct. Robust. Fast. Portable.
Documentation
use super::blocks::SlotLocation;
use super::key::checked_ordered_word;
use super::{RangeIndex, RangeIndexInconsistency, RangeIndexKey};
use crate::storage::warp_table::secondary_index::StableSlot;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RangeIndexInsertError<Key> {
    Full {
        key: Key,
        slot: StableSlot,
        maximum_rows: usize,
    },
    AlreadyPresent {
        key: Key,
        slot: StableSlot,
    },
    OrderedWordOutsideWidth {
        key: Key,
        ordered_word: u64,
        bits: u32,
    },
    Inconsistent {
        key: Key,
        slot: StableSlot,
        reason: RangeIndexInconsistency,
    },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum RangeIndexInsertOutcome<Key> {
    Inserted { key: Key, slot: StableSlot },
}

pub struct PreparedRangeIndexInsert<'index, Key: RangeIndexKey> {
    index: &'index mut RangeIndex<Key>,
    key: Key,
    slot: StableSlot,
    location: SlotLocation,
    previous_member_count: u64,
    committed: bool,
}

impl<Key: RangeIndexKey> RangeIndex<Key> {
    pub fn try_prepare_insert(
        &mut self,
        key: Key,
        slot: StableSlot,
    ) -> Result<PreparedRangeIndexInsert<'_, Key>, RangeIndexInsertError<Key>> {
        self.validate_storage()
            .map_err(|reason| inconsistent(key, slot, reason))?;
        let word = checked_ordered_word(key).map_err(|outside| {
            RangeIndexInsertError::OrderedWordOutsideWidth {
                key: outside.key,
                ordered_word: outside.ordered_word,
                bits: outside.bits,
            }
        })?;
        let location = self.locate_slot(slot);
        if let Ok(location) = location
            && self
                .member_at(location)
                .map_err(|reason| inconsistent(key, slot, reason))?
        {
            return Err(RangeIndexInsertError::AlreadyPresent { key, slot });
        }
        let members = usize::try_from(self.member_count).map_err(|_| {
            inconsistent(
                key,
                slot,
                RangeIndexInconsistency::MemberCountUnrepresentable {
                    members: self.member_count,
                },
            )
        })?;
        if members >= self.maximum_rows {
            return Err(RangeIndexInsertError::Full {
                key,
                slot,
                maximum_rows: self.maximum_rows,
            });
        }
        let location = location.map_err(|reason| inconsistent(key, slot, reason))?;
        let stale_word = self
            .word_at(location)
            .map_err(|reason| inconsistent(key, slot, reason))?;
        if stale_word != 0 {
            return Err(inconsistent(
                key,
                slot,
                RangeIndexInconsistency::StaleBitMembership {
                    bit: stale_word.trailing_zeros(),
                },
            ));
        }

        let previous_member_count = self.member_count;
        let next_member_count = self.member_count.checked_add(1).ok_or_else(|| {
            inconsistent(
                key,
                slot,
                RangeIndexInconsistency::MemberCountUnrepresentable {
                    members: self.member_count,
                },
            )
        })?;
        self.write_slot(location, true, word)
            .map_err(|reason| inconsistent(key, slot, reason))?;
        self.member_count = next_member_count;
        Ok(PreparedRangeIndexInsert {
            index: self,
            key,
            slot,
            location,
            previous_member_count,
            committed: false,
        })
    }
}

impl<Key: RangeIndexKey> PreparedRangeIndexInsert<'_, Key> {
    pub fn commit(mut self) -> RangeIndexInsertOutcome<Key> {
        self.committed = true;
        RangeIndexInsertOutcome::Inserted {
            key: self.key,
            slot: self.slot,
        }
    }
}

impl<Key: RangeIndexKey> Drop for PreparedRangeIndexInsert<'_, Key> {
    fn drop(&mut self) {
        if !self.committed {
            let rollback = self.index.write_slot(self.location, false, 0);
            debug_assert!(rollback.is_ok());
            self.index.member_count = self.previous_member_count;
        }
    }
}

pub(super) fn inconsistent<Key>(
    key: Key,
    slot: StableSlot,
    reason: RangeIndexInconsistency,
) -> RangeIndexInsertError<Key> {
    RangeIndexInsertError::Inconsistent { key, slot, reason }
}