spacetimedb_table/table_index/
unique_direct_fixed_cap_index.rs

1use super::unique_direct_index::{UniqueDirectIndexPointIter, NONE_PTR};
2use crate::indexes::RowPointer;
3use crate::MemoryUsage;
4use core::mem;
5use core::ops::{Bound, RangeBounds};
6use core::slice::Iter;
7
8/// A direct index with for relating unsigned integer keys to [`RowPointer`].
9/// The index is provided a capacity on creation and will have that during its lifetime.
10///
11/// These indices are intended for small fixed capacities
12/// and will be efficient for both monotonic and random insert patterns for small capacities.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct UniqueDirectFixedCapIndex {
15    /// The array holding the elements.
16    array: Box<[RowPointer]>,
17    /// The number of keys indexed.
18    len: usize,
19}
20
21impl MemoryUsage for UniqueDirectFixedCapIndex {
22    fn heap_usage(&self) -> usize {
23        let Self { array, len } = self;
24        array.heap_usage() + len.heap_usage()
25    }
26}
27
28impl UniqueDirectFixedCapIndex {
29    /// Returns a new fixed capacity index.
30    pub fn new(cap: usize) -> Self {
31        Self {
32            len: 0,
33            array: vec![NONE_PTR; cap].into(),
34        }
35    }
36
37    /// Clones the structure of the index and returns one with the same capacity.
38    pub fn clone_structure(&self) -> Self {
39        Self::new(self.array.len())
40    }
41
42    /// Inserts the relation `key -> val` to this index.
43    ///
44    /// If `key` was already present in the index, does not add an association with `val`.
45    /// Returns the existing associated value instead.
46    ///
47    /// Panics if the key is beyond the fixed capacity of this index.
48    pub fn insert(&mut self, key: usize, val: RowPointer) -> Result<(), RowPointer> {
49        // Fetch the slot.
50        let slot = &mut self.array[key];
51        let in_slot = *slot;
52        if in_slot == NONE_PTR {
53            // We have `NONE_PTR`, so not set yet.
54            *slot = val.with_reserved_bit(true);
55            self.len += 1;
56            Ok(())
57        } else {
58            Err(in_slot.with_reserved_bit(false))
59        }
60    }
61
62    /// Deletes `key` from this map.
63    ///
64    /// Returns whether `key` was present.
65    pub fn delete(&mut self, key: usize) -> bool {
66        let Some(slot) = self.array.get_mut(key) else {
67            return false;
68        };
69        let old_val = mem::replace(slot, NONE_PTR);
70        let deleted = old_val != NONE_PTR;
71        self.len -= deleted as usize;
72        deleted
73    }
74
75    /// Returns an iterator yielding the potential [`RowPointer`] for `key`.
76    pub fn seek_point(&self, key: usize) -> UniqueDirectIndexPointIter {
77        let point = self.array.get(key).copied().filter(|slot| *slot != NONE_PTR);
78        UniqueDirectIndexPointIter::new(point)
79    }
80
81    /// Returns an iterator yielding all the [`RowPointer`] that correspond to the provided `range`.
82    pub fn seek_range(&self, range: &impl RangeBounds<usize>) -> UniqueDirectFixedCapIndexRangeIter {
83        // Translate `range` to `start..end`.
84        let end = match range.end_bound() {
85            Bound::Included(&e) => e + 1,
86            Bound::Excluded(&e) => e,
87            Bound::Unbounded => self.array.len(),
88        };
89        let start = match range.start_bound() {
90            Bound::Included(&s) => s,
91            Bound::Excluded(&s) => s + 1,
92            Bound::Unbounded => 0,
93        };
94
95        // Normalize `start` so that `start <= end`.
96        let start = start.min(end);
97
98        // Make the iterator.
99        UniqueDirectFixedCapIndexRangeIter::new(self.array.get(start..end).unwrap_or_default())
100    }
101
102    /// Returns the number of unique keys in the index.
103    pub fn num_keys(&self) -> usize {
104        self.len
105    }
106
107    /// Returns the total number of entries in the index.
108    pub fn len(&self) -> usize {
109        self.len
110    }
111
112    /// Returns whether there are any entries in the index.
113    #[allow(unused)] // No use for this currently.
114    pub fn is_empty(&self) -> bool {
115        self.len() == 0
116    }
117
118    /// Deletes all entries from the index, leaving it empty.
119    pub fn clear(&mut self) {
120        self.array.fill(NONE_PTR);
121        self.len = 0;
122    }
123
124    /// Returns whether `other` can be merged into `self`
125    /// with an error containing the element in `self` that caused the violation.
126    ///
127    /// The closure `ignore` indicates whether a row in `self` should be ignored.
128    pub(crate) fn can_merge(&self, other: &Self, ignore: impl Fn(&RowPointer) -> bool) -> Result<(), RowPointer> {
129        for (slot_s, slot_o) in self.array.iter().zip(other.array.iter()) {
130            let ptr_s = slot_s.with_reserved_bit(false);
131            if *slot_s != NONE_PTR && *slot_o != NONE_PTR && !ignore(&ptr_s) {
132                // For the same key, we found both slots occupied, so we cannot merge.
133                return Err(ptr_s);
134            }
135        }
136        Ok(())
137    }
138}
139
140/// An iterator over a range of keys in a [`UniqueDirectFixedCapIndex`].
141#[derive(Debug)]
142pub struct UniqueDirectFixedCapIndexRangeIter<'a> {
143    iter: Iter<'a, RowPointer>,
144}
145
146impl<'a> UniqueDirectFixedCapIndexRangeIter<'a> {
147    fn new(slice: &'a [RowPointer]) -> Self {
148        let iter = slice.iter();
149        Self { iter }
150    }
151}
152
153impl Iterator for UniqueDirectFixedCapIndexRangeIter<'_> {
154    type Item = RowPointer;
155    fn next(&mut self) -> Option<Self::Item> {
156        self.iter
157            // Make sure the row exists.
158            .find(|slot| **slot != NONE_PTR)
159            .map(|ptr| ptr.with_reserved_bit(false))
160    }
161}
162
163#[cfg(test)]
164mod test {
165    use super::*;
166    use crate::table_index::unique_direct_index::test::gen_row_pointers;
167    use core::ops::Range;
168    use proptest::prelude::*;
169
170    fn range(start: u8, end: u8) -> Range<usize> {
171        let min = start.min(end);
172        let max = start.max(end);
173        min as usize..max as usize
174    }
175
176    fn setup(start: u8, end: u8) -> (UniqueDirectFixedCapIndex, Range<usize>, Vec<RowPointer>) {
177        let range = range(start, end);
178        let (keys, ptrs): (Vec<_>, Vec<_>) = range.clone().zip(gen_row_pointers()).unzip();
179
180        let mut index = UniqueDirectFixedCapIndex::new(u8::MAX as usize + 1);
181        for (key, ptr) in keys.iter().zip(&ptrs) {
182            index.insert(*key, *ptr).unwrap();
183        }
184        assert_eq!(index.len(), range.end - range.start);
185        (index, range, ptrs)
186    }
187
188    proptest! {
189        #[test]
190        fn seek_range_gives_back_inserted(start: u8, end: u8) {
191            let (index, range, ptrs) = setup(start, end);
192            let ptrs_found = index.seek_range(&range).collect::<Vec<_>>();
193            assert_eq!(ptrs, ptrs_found);
194        }
195
196        #[test]
197        fn inserting_again_errors(start: u8, end: u8) {
198            let (mut index, keys, ptrs) = setup(start, end);
199            for (key, ptr) in keys.zip(&ptrs) {
200                assert_eq!(index.insert(key, *ptr).unwrap_err(), *ptr)
201            }
202        }
203
204        #[test]
205        fn deleting_allows_reinsertion(start: u8, end: u8, key: u8) {
206            let (mut index, range, _) = setup(start, end);
207
208            if range.start == range.end {
209                return Err(TestCaseError::Reject("empty range".into()));
210            }
211
212            let key = (key as usize).clamp(range.start, range.end.saturating_sub(1));
213
214            let ptr = index.seek_point(key).next().unwrap();
215            assert!(index.delete(key));
216            assert!(!index.delete(key));
217            assert_eq!(index.len(), range.end - range.start - 1);
218
219            index.insert(key, ptr).unwrap();
220            assert_eq!(index.len(), range.end - range.start);
221        }
222    }
223}