use super::unique_direct_index::{UniqueDirectIndexPointIter, NONE_PTR};
use crate::indexes::RowPointer;
use core::mem;
use core::ops::{Bound, RangeBounds};
use core::slice::Iter;
use spacetimedb_sats::memory_usage::MemoryUsage;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UniqueDirectFixedCapIndex {
array: Box<[RowPointer]>,
len: usize,
}
impl MemoryUsage for UniqueDirectFixedCapIndex {
fn heap_usage(&self) -> usize {
let Self { array, len } = self;
array.heap_usage() + len.heap_usage()
}
}
impl UniqueDirectFixedCapIndex {
pub fn new(cap: usize) -> Self {
Self {
len: 0,
array: vec![NONE_PTR; cap].into(),
}
}
pub fn clone_structure(&self) -> Self {
Self::new(self.array.len())
}
pub fn insert(&mut self, key: usize, val: RowPointer) -> Result<(), RowPointer> {
let slot = &mut self.array[key];
let in_slot = *slot;
if in_slot == NONE_PTR {
*slot = val.with_reserved_bit(true);
self.len += 1;
Ok(())
} else {
Err(in_slot.with_reserved_bit(false))
}
}
pub fn delete(&mut self, key: usize) -> bool {
let Some(slot) = self.array.get_mut(key) else {
return false;
};
let old_val = mem::replace(slot, NONE_PTR);
let deleted = old_val != NONE_PTR;
self.len -= deleted as usize;
deleted
}
pub fn seek_point(&self, key: usize) -> UniqueDirectIndexPointIter {
let point = self.array.get(key).copied().filter(|slot| *slot != NONE_PTR);
UniqueDirectIndexPointIter::new(point)
}
pub fn seek_range(&self, range: &impl RangeBounds<usize>) -> UniqueDirectFixedCapIndexRangeIter {
let end = match range.end_bound() {
Bound::Included(&e) => e + 1,
Bound::Excluded(&e) => e,
Bound::Unbounded => self.array.len(),
};
let start = match range.start_bound() {
Bound::Included(&s) => s,
Bound::Excluded(&s) => s + 1,
Bound::Unbounded => 0,
};
let start = start.min(end);
UniqueDirectFixedCapIndexRangeIter::new(self.array.get(start..end).unwrap_or_default())
}
pub fn num_keys(&self) -> usize {
self.len
}
pub fn len(&self) -> usize {
self.len
}
#[allow(unused)] pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
self.array.fill(NONE_PTR);
self.len = 0;
}
pub(crate) fn can_merge(&self, other: &Self, ignore: impl Fn(&RowPointer) -> bool) -> Result<(), RowPointer> {
for (slot_s, slot_o) in self.array.iter().zip(other.array.iter()) {
let ptr_s = slot_s.with_reserved_bit(false);
if *slot_s != NONE_PTR && *slot_o != NONE_PTR && !ignore(&ptr_s) {
return Err(ptr_s);
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct UniqueDirectFixedCapIndexRangeIter<'a> {
iter: Iter<'a, RowPointer>,
}
impl<'a> UniqueDirectFixedCapIndexRangeIter<'a> {
fn new(slice: &'a [RowPointer]) -> Self {
let iter = slice.iter();
Self { iter }
}
}
impl Iterator for UniqueDirectFixedCapIndexRangeIter<'_> {
type Item = RowPointer;
fn next(&mut self) -> Option<Self::Item> {
self.iter
.find(|slot| **slot != NONE_PTR)
.map(|ptr| ptr.with_reserved_bit(false))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::table_index::unique_direct_index::test::gen_row_pointers;
use core::ops::Range;
use proptest::prelude::*;
fn range(start: u8, end: u8) -> Range<usize> {
let min = start.min(end);
let max = start.max(end);
min as usize..max as usize
}
fn setup(start: u8, end: u8) -> (UniqueDirectFixedCapIndex, Range<usize>, Vec<RowPointer>) {
let range = range(start, end);
let (keys, ptrs): (Vec<_>, Vec<_>) = range.clone().zip(gen_row_pointers()).unzip();
let mut index = UniqueDirectFixedCapIndex::new(u8::MAX as usize + 1);
for (key, ptr) in keys.iter().zip(&ptrs) {
index.insert(*key, *ptr).unwrap();
}
assert_eq!(index.len(), range.end - range.start);
(index, range, ptrs)
}
proptest! {
#[test]
fn seek_range_gives_back_inserted(start: u8, end: u8) {
let (index, range, ptrs) = setup(start, end);
let ptrs_found = index.seek_range(&range).collect::<Vec<_>>();
assert_eq!(ptrs, ptrs_found);
}
#[test]
fn inserting_again_errors(start: u8, end: u8) {
let (mut index, keys, ptrs) = setup(start, end);
for (key, ptr) in keys.zip(&ptrs) {
assert_eq!(index.insert(key, *ptr).unwrap_err(), *ptr)
}
}
#[test]
fn deleting_allows_reinsertion(start: u8, end: u8, key: u8) {
let (mut index, range, _) = setup(start, end);
if range.start == range.end {
return Err(TestCaseError::Reject("empty range".into()));
}
let key = (key as usize).clamp(range.start, range.end.saturating_sub(1));
let ptr = index.seek_point(key).next().unwrap();
assert!(index.delete(key));
assert!(!index.delete(key));
assert_eq!(index.len(), range.end - range.start - 1);
index.insert(key, ptr).unwrap();
assert_eq!(index.len(), range.end - range.start);
}
}
}