use yo_common::{bytes_eq, hash_key, tag_of};
use crate::blob::Blob;
use crate::scan::Cursor;
pub const MAX_ROWS: usize = 0x00FF_FFFE;
pub const NAME_MAX: usize = u16::MAX as usize;
const ROW: u32 = 0x00FF_FFFF;
const EMPTY: u32 = 0xFFFF_FFFF;
const TOMB: u32 = 0x00FF_FFFF;
const LOAD_NUM: usize = 3;
const LOAD_DEN: usize = 4;
const MIN_SLOTS: usize = 16;
const LONG_NAME: usize = 255;
const PREFIX: usize = 2;
const LONG_TAIL: usize = 255;
const TAIL_PREFIX: usize = 5;
const HOME_BITS: u32 = 24;
#[derive(Debug, Clone, Copy)]
struct Row {
at: u32,
packed: u32,
}
impl Row {
#[inline]
fn new(at: u32, len: usize, h: u64) -> Row {
let len = u32::try_from(len.min(LONG_NAME)).expect("LONG_NAME is one byte");
Row {
at,
packed: ((h as u32 & ((1 << HOME_BITS) - 1)) << 8) | len,
}
}
#[inline]
const fn len_byte(self) -> usize {
(self.packed & 0xFF) as usize
}
#[inline]
const fn home(self) -> usize {
(self.packed >> 8) as usize
}
}
#[derive(Debug, Clone)]
pub struct Elements<V> {
slots: Box<[u32]>,
dead: u32,
tailed: bool,
rows: Vec<Row>,
vals: Vec<V>,
names: Blob,
}
impl<V: Copy> Default for Elements<V> {
fn default() -> Elements<V> {
Elements::new()
}
}
impl<V: Copy> Elements<V> {
#[must_use]
pub fn new() -> Elements<V> {
Elements {
slots: Box::new([]),
dead: 0,
rows: Vec::new(),
vals: Vec::new(),
names: Blob::new(),
tailed: false,
}
}
#[must_use]
pub fn tailed(n: usize, blob: usize) -> Elements<V> {
let mut e = Elements::with_capacity(n);
e.names = Blob::with_capacity(blob);
e.tailed = true;
e
}
#[must_use]
pub fn with_capacity(n: usize) -> Elements<V> {
let mut e = Elements::new();
e.reserve(n);
e
}
pub fn reserve(&mut self, n: usize) {
if n == 0 {
return;
}
self.rows.reserve(n.saturating_sub(self.rows.len()));
self.vals.reserve(n.saturating_sub(self.vals.len()));
if (n + self.dead as usize) * LOAD_DEN > self.slots.len() * LOAD_NUM {
self.grow_to(slots_for(n));
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.rows.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
#[inline]
#[must_use]
pub fn get(&self, name: &[u8]) -> Option<&V> {
let at = self.find(name)?;
Some(&self.vals[at])
}
#[inline]
pub fn get_mut(&mut self, name: &[u8]) -> Option<&mut V> {
let at = self.find(name)?;
Some(&mut self.vals[at])
}
#[inline]
#[must_use]
pub fn contains(&self, name: &[u8]) -> bool {
self.find(name).is_some()
}
#[inline]
#[must_use]
pub fn index_of(&self, name: &[u8]) -> Option<usize> {
self.find(name)
}
#[inline]
#[must_use]
pub fn hash_of(name: &[u8]) -> u64 {
hash(name)
}
#[inline]
#[must_use]
pub fn contains_hashed(&self, h: u64, name: &[u8]) -> bool {
self.find_hashed(h, name).is_some()
}
#[inline]
#[must_use]
pub fn index_of_hashed(&self, h: u64, name: &[u8]) -> Option<usize> {
self.find_hashed(h, name)
}
#[inline]
#[must_use]
pub fn get_hashed(&self, h: u64, name: &[u8]) -> Option<&V> {
let at = self.find_hashed(h, name)?;
Some(&self.vals[at])
}
#[inline]
pub fn get_hashed_mut(&mut self, h: u64, name: &[u8]) -> Option<&mut V> {
let at = self.find_hashed(h, name)?;
Some(&mut self.vals[at])
}
pub fn insert(&mut self, name: &[u8], value: V) -> Result<Option<V>, Full> {
self.insert_hashed(hash(name), name, value)
}
pub fn insert_hashed(&mut self, h: u64, name: &[u8], value: V) -> Result<Option<V>, Full> {
if name.len() > NAME_MAX {
return Err(Full::Name);
}
if let Some(at) = self.find_hashed(h, name) {
return Ok(Some(std::mem::replace(&mut self.vals[at], value)));
}
if self.rows.len() >= MAX_ROWS {
return Err(Full::Rows);
}
self.reserve_one();
let at = u32::try_from(self.rows.len()).expect("MAX_ROWS is under u32::MAX");
let name_at = self.push_name(name);
self.rows.push(Row::new(name_at, name.len(), h));
self.vals.push(value);
self.put_slot(h, at);
Ok(None)
}
pub fn set_tailed(
&mut self,
name: &[u8],
tail: &[u8],
value: V,
) -> Result<(usize, bool), Full> {
debug_assert!(self.tailed, "this table does not keep tails");
if name.len() > NAME_MAX {
return Err(Full::Name);
}
let h = hash(name);
if let Some(at) = self.find_hashed(h, name) {
self.rewrite_tail(at, name, tail);
self.vals[at] = value;
return Ok((at, false));
}
if self.rows.len() >= MAX_ROWS {
return Err(Full::Rows);
}
self.reserve_one();
let at = self.rows.len();
let name_at = self.push_name(name);
self.push_tail(tail);
self.rows.push(Row::new(name_at, name.len(), h));
self.vals.push(value);
self.put_slot(h, u32::try_from(at).expect("MAX_ROWS is under u32::MAX"));
Ok((at, true))
}
fn rewrite_tail(&mut self, at: usize, name: &[u8], tail: &[u8]) {
let gone = self.footprint(&self.rows[at]);
let name_at = self.push_name(name);
self.push_tail(tail);
self.rows[at].at = name_at;
self.names.release(gone);
self.maybe_compact_names();
}
#[inline]
#[must_use]
pub fn tail(&self, name: &[u8]) -> Option<&[u8]> {
let at = self.find(name)?;
Some(self.tail_of(&self.rows[at]))
}
#[inline]
#[must_use]
pub fn tail_len(&self, name: &[u8]) -> Option<usize> {
let at = self.find(name)?;
Some(self.tail_len_of(&self.rows[at]))
}
#[inline]
#[must_use]
pub fn pair_at(&self, idx: usize) -> Option<(&[u8], &[u8])> {
let row = self.rows.get(idx)?;
Some((self.name_of(row), self.tail_of(row)))
}
pub fn pairs(&self) -> impl Iterator<Item = (&[u8], &[u8])> {
self.rows.iter().map(|r| (self.name_of(r), self.tail_of(r)))
}
pub fn remove(&mut self, name: &[u8]) -> Option<V> {
let at = self.find(name)?;
Some(self.remove_row(at))
}
#[inline]
pub fn remove_hashed(&mut self, h: u64, name: &[u8]) -> Option<V> {
let at = self.find_hashed(h, name)?;
Some(self.remove_row(at))
}
pub fn remove_at(&mut self, idx: usize) -> Option<V> {
if idx >= self.rows.len() {
return None;
}
Some(self.remove_row(idx))
}
#[inline]
#[must_use]
pub fn at(&self, idx: usize) -> Option<(&[u8], &V)> {
let row = self.rows.get(idx)?;
Some((self.name_of(row), &self.vals[idx]))
}
#[inline]
pub fn at_mut(&mut self, idx: usize) -> Option<&mut V> {
self.vals.get_mut(idx)
}
pub fn take_at(&mut self, idx: usize) -> Option<(Vec<u8>, V)> {
if idx >= self.rows.len() {
return None;
}
let name = self.name_of(&self.rows[idx]).to_vec();
let value = self.remove_row(idx);
Some((name, value))
}
pub fn iter(&self) -> impl Iterator<Item = (&[u8], &V)> {
self.rows
.iter()
.zip(&self.vals)
.map(|(r, v)| (self.name_of(r), v))
}
pub fn payloads_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.vals.iter_mut()
}
pub fn scan<F>(&self, cursor: Cursor, count: usize, mut f: F) -> Cursor
where
F: FnMut(&[u8], &V),
{
self.scan_rows(cursor, count, |e, at| {
f(e.name_of(&e.rows[at]), &e.vals[at]);
})
}
pub fn scan_pairs<F>(&self, cursor: Cursor, count: usize, mut f: F) -> Cursor
where
F: FnMut(&[u8], &[u8]),
{
self.scan_rows(cursor, count, |e, at| {
let row = &e.rows[at];
f(e.name_of(row), e.tail_of(row));
})
}
fn scan_rows<F>(&self, cursor: Cursor, count: usize, mut f: F) -> Cursor
where
F: FnMut(&Elements<V>, usize),
{
if self.rows.is_empty() {
return Cursor::END;
}
let here = cursor.rebase(1);
let top = self.rows.len() - 1;
let mut at = match here.idx() {
Some(idx) => (idx as usize).min(top),
None => top,
};
for _ in 0..count.max(1) {
f(self, at);
if at == 0 {
return Cursor::END;
}
at -= 1;
}
Cursor::at(1, 0, at as u64)
}
pub fn clear(&mut self) {
self.rows.clear();
self.vals.clear();
self.names.clear();
for slot in &mut self.slots {
*slot = EMPTY;
}
self.dead = 0;
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.slot_bytes() + self.row_bytes() + self.names.memory_bytes()
}
#[must_use]
pub fn slot_bytes(&self) -> usize {
self.slots.len() * size_of::<u32>()
}
#[must_use]
pub fn row_bytes(&self) -> usize {
self.rows.capacity() * size_of::<Row>() + self.vals.capacity() * size_of::<V>()
}
#[must_use]
pub fn name_bytes(&self) -> usize {
self.names.memory_bytes()
}
#[inline]
#[must_use]
pub const fn dead_name_bytes(&self) -> usize {
self.names.dead()
}
#[inline]
fn find(&self, name: &[u8]) -> Option<usize> {
self.find_hashed(hash(name), name)
}
#[inline]
fn find_hashed(&self, h: u64, name: &[u8]) -> Option<usize> {
if self.rows.is_empty() {
return None;
}
let mask = self.slots.len() - 1;
let tag = tag_of(h);
let mut at = (h as usize) & mask;
loop {
let slot = self.slots[at];
if slot == EMPTY {
return None;
}
if slot >> 24 == u32::from(tag) {
let row = slot & ROW;
if row != ROW && bytes_eq(self.name_of(&self.rows[row as usize]), name) {
return Some(row as usize);
}
}
at = (at + 1) & mask;
}
}
fn put_slot(&mut self, h: u64, row: u32) {
let mask = self.slots.len() - 1;
let mut at = (h as usize) & mask;
while self.slots[at] & ROW != ROW {
at = (at + 1) & mask;
}
if self.slots[at] == TOMB {
self.dead -= 1;
}
self.slots[at] = (u32::from(tag_of(h)) << 24) | row;
}
fn remove_row(&mut self, at: usize) -> V {
let last = self.rows.len() - 1;
self.clear_slot(at);
if at != last {
self.repoint(last, at);
self.rows.swap(at, last);
self.vals.swap(at, last);
}
let row = self.rows.pop().expect("the table was not empty");
let value = self.vals.pop().expect("a payload per row");
let gone = self.footprint(&row);
self.names.release(gone);
self.maybe_compact_names();
value
}
fn clear_slot(&mut self, row: usize) {
let mask = self.slots.len() - 1;
let at = self.slot_of(row);
if self.slots[(at + 1) & mask] != EMPTY {
self.slots[at] = TOMB;
self.dead += 1;
return;
}
self.slots[at] = EMPTY;
let mut back = at.wrapping_sub(1) & mask;
while self.slots[back] == TOMB {
self.slots[back] = EMPTY;
self.dead -= 1;
back = back.wrapping_sub(1) & mask;
}
}
fn repoint(&mut self, from: usize, to: usize) {
let at = self.slot_of(from);
let to = u32::try_from(to).expect("a row index fits in 24 bits");
self.slots[at] = (self.slots[at] & !ROW) | to;
}
fn slot_of(&self, row: usize) -> usize {
let mask = self.slots.len() - 1;
let want = u32::try_from(row).expect("a row index fits in 24 bits");
let mut at = self.home_of(row, mask);
loop {
debug_assert!(self.slots[at] != EMPTY, "the row being moved has a slot");
if self.slots[at] & ROW == want {
return at;
}
at = (at + 1) & mask;
}
}
fn reserve_one(&mut self) {
let want = self.rows.len() + 1;
crate::grow::reserve(&mut self.rows, 1);
crate::grow::reserve(&mut self.vals, 1);
if (want + self.dead as usize) * LOAD_DEN > self.slots.len() * LOAD_NUM {
self.grow_to(slots_for(want));
}
}
fn grow_to(&mut self, slots: usize) {
let slots = slots.max(MIN_SLOTS).next_power_of_two();
let mask = slots - 1;
let old = std::mem::replace(&mut self.slots, vec![EMPTY; slots].into_boxed_slice());
self.dead = 0;
for &slot in &old {
if slot & ROW == ROW {
continue;
}
let row = (slot & ROW) as usize;
let mut at = self.home_of(row, mask);
while self.slots[at] != EMPTY {
at = (at + 1) & mask;
}
self.slots[at] = slot;
}
}
#[inline(always)]
fn home_of(&self, idx: usize, mask: usize) -> usize {
let row = self.rows[idx];
if mask < 1 << HOME_BITS {
row.home() & mask
} else {
self.home_by_hash(&row, mask)
}
}
#[cold]
#[inline(never)]
fn home_by_hash(&self, row: &Row, mask: usize) -> usize {
hash(self.name_of(row)) as usize & mask
}
fn push_name(&mut self, name: &[u8]) -> u32 {
if name.len() < LONG_NAME {
return self.names.push(name);
}
let len = u16::try_from(name.len()).expect("the caller checked NAME_MAX");
let at = self.names.push(&len.to_le_bytes());
self.names.push(name);
at
}
#[inline(always)]
fn name_of(&self, row: &Row) -> &[u8] {
let len = row.len_byte();
if len < LONG_NAME {
self.names.read(row.at, len)
} else {
self.long_name(row.at)
}
}
#[cold]
#[inline(never)]
fn long_name(&self, at: u32) -> &[u8] {
self.names.read(at + PREFIX as u32, self.long_len(at))
}
#[inline]
fn long_len(&self, at: u32) -> usize {
let head = self.names.read(at, PREFIX);
usize::from(u16::from_le_bytes([head[0], head[1]]))
}
#[inline(always)]
fn name_span(&self, row: &Row) -> usize {
let len = row.len_byte();
if len < LONG_NAME {
len
} else {
PREFIX + self.long_len(row.at)
}
}
#[inline(always)]
fn footprint(&self, row: &Row) -> usize {
let name = self.name_span(row);
if !self.tailed {
return name;
}
name + self.tail_span(row.at + name as u32)
}
fn push_tail(&mut self, tail: &[u8]) {
if tail.len() < LONG_TAIL {
self.names.push(&[tail.len() as u8]);
} else {
let len = u32::try_from(tail.len()).expect("a value is under four gigabytes");
self.names.push(&[LONG_TAIL as u8]);
self.names.push(&len.to_le_bytes());
}
self.names.push(tail);
}
#[inline]
fn tail_head(&self, at: u32) -> (usize, usize) {
let len = usize::from(self.names.read(at, 1)[0]);
if len < LONG_TAIL {
return (len, 1);
}
let head = self.names.read(at + 1, 4);
let long = u32::from_le_bytes(head.try_into().expect("four bytes"));
(long as usize, TAIL_PREFIX)
}
#[inline]
fn tail_span(&self, at: u32) -> usize {
let (len, prefix) = self.tail_head(at);
prefix + len
}
#[inline]
fn tail_of(&self, row: &Row) -> &[u8] {
let at = row.at + self.name_span(row) as u32;
let (len, prefix) = self.tail_head(at);
self.names.read(at + prefix as u32, len)
}
#[inline]
fn tail_len_of(&self, row: &Row) -> usize {
let at = row.at + self.name_span(row) as u32;
self.tail_head(at).0
}
fn maybe_compact_names(&mut self) {
if !self.names.worth_compacting() {
return;
}
let rows = &mut self.rows;
let tailed = self.tailed;
self.names.compact(|keep| {
for row in rows.iter_mut() {
let len = row.len_byte();
let mut take = if len < LONG_NAME {
len
} else {
let head = keep.peek(row.at, PREFIX);
PREFIX + usize::from(u16::from_le_bytes([head[0], head[1]]))
};
if tailed {
let at = row.at + take as u32;
let head = keep.peek(at, 1)[0];
take += if usize::from(head) < LONG_TAIL {
1 + usize::from(head)
} else {
let long = keep.peek(at + 1, 4);
TAIL_PREFIX + u32::from_le_bytes(long.try_into().expect("four")) as usize
};
}
keep.moved(&mut row.at, take);
}
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Full {
Name,
Rows,
}
#[inline]
fn hash(name: &[u8]) -> u64 {
hash_key(name)
}
fn slots_for(n: usize) -> usize {
((n * LOAD_DEN) / LOAD_NUM + 1)
.max(MIN_SLOTS)
.next_power_of_two()
}
#[cfg(test)]
mod tests {
use super::*;
type Set = Elements<()>;
fn set(members: &[&[u8]]) -> Set {
let mut s = Set::new();
for m in members {
s.insert(m, ()).expect("room");
}
s
}
#[test]
fn an_empty_table_allocates_nothing() {
let e = Set::new();
assert_eq!(e.len(), 0);
assert!(e.is_empty());
assert_eq!(e.memory_bytes(), 0);
assert!(!e.contains(b"anything"));
}
#[test]
fn what_goes_in_comes_out() {
let mut h: Elements<u64> = Elements::new();
assert_eq!(h.insert(b"name", 7), Ok(None));
assert_eq!(h.insert(b"age", 41), Ok(None));
assert_eq!(h.get(b"name"), Some(&7));
assert_eq!(h.get(b"age"), Some(&41));
assert_eq!(h.get(b"missing"), None);
assert_eq!(h.len(), 2);
}
#[test]
fn writing_a_field_again_replaces_it_and_says_so() {
let mut h: Elements<u64> = Elements::new();
assert_eq!(h.insert(b"f", 1), Ok(None));
assert_eq!(h.insert(b"f", 2), Ok(Some(1)));
assert_eq!(h.len(), 1, "an overwrite is not a second element");
assert_eq!(h.get(b"f"), Some(&2));
}
#[test]
fn rewriting_a_field_does_not_write_its_name_again() {
let mut h: Elements<u64> = Elements::new();
h.insert(b"a-fairly-long-field-name", 1).expect("room");
let after_first = h.memory_bytes();
for i in 0..1000 {
h.insert(b"a-fairly-long-field-name", i).expect("room");
}
assert_eq!(h.memory_bytes(), after_first);
assert_eq!(h.dead_name_bytes(), 0);
}
#[test]
fn removing_takes_the_element_out() {
let mut s = set(&[b"a", b"b", b"c"]);
assert_eq!(s.remove(b"b"), Some(()));
assert_eq!(s.remove(b"b"), None);
assert_eq!(s.len(), 2);
assert!(s.contains(b"a"));
assert!(s.contains(b"c"));
assert!(!s.contains(b"b"));
}
#[test]
fn the_rows_stay_dense_through_removals() {
let mut s = set(&[b"a", b"b", b"c", b"d", b"e"]);
s.remove(b"a").expect("there");
s.remove(b"c").expect("there");
assert_eq!(s.len(), 3);
let mut seen: Vec<Vec<u8>> = (0..s.len())
.map(|i| s.at(i).expect("dense").0.to_vec())
.collect();
seen.sort();
assert_eq!(seen, vec![b"b".to_vec(), b"d".to_vec(), b"e".to_vec()]);
assert_eq!(s.at(3), None);
}
#[test]
fn a_set_drained_one_draw_at_a_time_stays_correct() {
let names: Vec<Vec<u8>> = (0..500u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
let mut taken = Vec::new();
while !s.is_empty() {
let idx = (taken.len() * 7 + 3) % s.len();
let (name, ()) = s.take_at(idx).expect("in range");
assert!(!s.contains(&name), "it came out and stayed out");
taken.push(name);
}
assert_eq!(taken.len(), names.len());
taken.sort();
let mut want = names;
want.sort();
assert_eq!(taken, want);
}
#[test]
fn removals_do_not_hide_what_is_behind_them() {
let mut s = Set::new();
let names: Vec<Vec<u8>> = (0..200u32).map(|i| format!("k{i}").into_bytes()).collect();
for n in &names {
s.insert(n, ()).expect("room");
}
for n in names.iter().step_by(3) {
assert_eq!(s.remove(n), Some(()));
}
for (i, n) in names.iter().enumerate() {
assert_eq!(s.contains(n), i % 3 != 0, "member {i}");
}
}
#[test]
fn growth_keeps_everything_findable() {
let names: Vec<Vec<u8>> = (0..5000u32)
.map(|i| format!("member-number-{i}").into_bytes())
.collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
assert_eq!(s.len(), names.len());
for n in &names {
assert!(s.contains(n));
}
assert!(!s.contains(b"member-number-5000"));
}
#[test]
fn a_walk_reads_them_in_the_order_they_went_in() {
let s = set(&[b"first", b"second", b"third"]);
let seen: Vec<&[u8]> = s.iter().map(|(n, ())| n).collect();
assert_eq!(seen, vec![&b"first"[..], &b"second"[..], &b"third"[..]]);
}
#[test]
fn presizing_does_not_change_what_the_table_says() {
let mut a = Set::with_capacity(1000);
let mut b = Set::new();
for i in 0..1000u32 {
let n = format!("m{i}").into_bytes();
a.insert(&n, ()).expect("room");
b.insert(&n, ()).expect("room");
}
assert_eq!(a.len(), b.len());
for i in 0..1000u32 {
assert!(a.contains(format!("m{i}").as_bytes()));
}
}
#[test]
fn a_name_that_is_too_long_is_refused_and_not_truncated() {
let mut s = Set::new();
let long = vec![b'x'; NAME_MAX + 1];
assert_eq!(s.insert(&long, ()), Err(Full::Name));
assert!(s.is_empty());
let ok = vec![b'x'; NAME_MAX];
assert_eq!(s.insert(&ok, ()), Ok(None));
}
#[test]
fn a_name_too_long_to_measure_in_a_row_reads_back_whole() {
let lens = [0, 1, 2, 253, 254, 255, 256, 257, 1000, NAME_MAX];
let names: Vec<Vec<u8>> = lens
.iter()
.enumerate()
.map(|(i, &n)| vec![b'a' + u8::try_from(i).expect("under 26"); n])
.collect();
let mut s = Set::new();
for name in &names {
assert_eq!(s.insert(name, ()), Ok(None), "length {}", name.len());
}
assert_eq!(s.len(), names.len(), "two of them collided into one row");
for name in &names {
assert!(s.contains(name), "length {} went missing", name.len());
}
let mut back: Vec<Vec<u8>> = s.iter().map(|(n, ())| n.to_vec()).collect();
back.sort();
let mut want = names.clone();
want.sort();
assert_eq!(back, want, "a walk gave back different bytes");
for (i, name) in names.iter().enumerate() {
assert_eq!(s.remove(name), Some(()), "length {}", name.len());
for later in &names[i + 1..] {
assert!(s.contains(later), "length {} lost", later.len());
}
}
assert!(s.is_empty());
}
#[test]
fn long_names_survive_the_blob_giving_its_dead_bytes_back() {
let mut s = Set::new();
let names: Vec<Vec<u8>> = (0..200u32)
.map(|i| format!("{i:0>500}").into_bytes())
.collect();
for name in &names {
s.insert(name, ()).expect("room");
}
let keep: Vec<Vec<u8>> = (0..100u32)
.map(|i| format!("keep-{i:0>500}").into_bytes())
.collect();
for name in &keep {
s.insert(name, ()).expect("room");
}
let before = s.name_bytes();
for name in &names {
assert_eq!(s.remove(name), Some(()));
}
assert!(
s.name_bytes() * 2 < before,
"the rebuild never ran, the blob went from {before} to {}",
s.name_bytes()
);
assert_eq!(s.len(), keep.len());
for name in &keep {
assert!(s.contains(name), "a long name moved wrongly");
}
let mut back: Vec<Vec<u8>> = s.iter().map(|(n, ())| n.to_vec()).collect();
back.sort();
let mut want = keep.clone();
want.sort();
assert_eq!(back, want);
}
#[test]
fn dead_name_bytes_come_back() {
let mut s = Set::new();
let long: Vec<Vec<u8>> = (0..400u32)
.map(|i| format!("{i:0>64}").into_bytes())
.collect();
for n in &long {
s.insert(n, ()).expect("room");
}
let full = s.memory_bytes();
for n in long.iter().take(390) {
s.remove(n).expect("there");
}
assert!(
s.memory_bytes() < full,
"the blob shrank, {} against {full}",
s.memory_bytes()
);
assert!(
s.dead_name_bytes() < 4096,
"{} bytes left dead",
s.dead_name_bytes()
);
for n in long.iter().skip(390) {
assert!(s.contains(n), "still findable after the blob moved");
}
}
#[test]
fn clearing_keeps_the_allocation_and_forgets_the_elements() {
let mut s = set(&[b"a", b"b", b"c"]);
let before = s.memory_bytes();
s.clear();
assert!(s.is_empty());
assert!(!s.contains(b"a"));
assert_eq!(s.memory_bytes(), before, "the room is kept for the refill");
s.insert(b"a", ()).expect("room");
assert!(s.contains(b"a"));
}
#[test]
fn no_live_slot_can_look_like_a_marker() {
let mut s = Set::new();
for i in 0..2000u32 {
s.insert(format!("m{i}").as_bytes(), ()).expect("room");
}
let live = s.slots.iter().filter(|v| **v & ROW != ROW).count();
assert_eq!(live, s.len());
assert_eq!(s.dead, 0, "nothing has been removed yet");
const { assert!(MAX_ROWS < ROW as usize, "a row index is never all ones") }
}
fn check(s: &Set, names: &[Vec<u8>]) {
assert_eq!(s.len(), names.len());
let mut live = 0usize;
let mut dead = 0usize;
for &slot in &s.slots {
if slot == EMPTY {
} else if slot == TOMB {
dead += 1;
} else {
assert!(slot & ROW != ROW, "a slot is live, empty or dead");
assert!(((slot & ROW) as usize) < s.len(), "a live slot names a row");
live += 1;
}
}
assert_eq!(live, s.len(), "one live slot per row and no more");
assert_eq!(
dead, s.dead as usize,
"the dead count is what is in the array"
);
assert!(
s.len() + s.dead as usize <= s.slots.len() * LOAD_NUM / LOAD_DEN,
"there is always an empty slot left for a probe to stop at"
);
for (i, name) in names.iter().enumerate() {
assert_eq!(
s.index_of(name),
Some(i),
"{name:?} is not where it was put"
);
assert!(s.slot_of(i) < s.slots.len(), "row {i} has no slot");
}
}
#[test]
fn a_removal_leaves_the_table_whole() {
let names: Vec<Vec<u8>> = (0..500u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for name in &names {
s.insert(name, ()).expect("room");
}
let mut live = names.clone();
for i in (0..live.len()).rev().step_by(3) {
let gone = live.swap_remove(i);
assert!(s.remove(&gone).is_some(), "{gone:?} was there");
}
check(&s, &live);
for name in names.iter().filter(|n| !live.contains(n)) {
assert!(!s.contains(name), "{name:?} came back");
}
}
#[test]
fn a_table_churned_in_place_does_not_fill_up_with_markers() {
let mut s = Set::new();
for i in 0..1000u32 {
s.insert(format!("m{i}").as_bytes(), ()).expect("room");
}
let slots = s.slots.len();
for i in 1000..100_000u32 {
let gone = format!("m{}", i - 1000);
assert!(s.remove(gone.as_bytes()).is_some());
s.insert(format!("m{i}").as_bytes(), ()).expect("room");
assert_eq!(s.len(), 1000);
}
assert_eq!(s.slots.len(), slots, "the array is the size it started at");
let live: Vec<Vec<u8>> = (99_000..100_000u32)
.map(|i| format!("m{i}").into_bytes())
.collect();
for name in &live {
assert!(s.contains(name), "{name:?} is missing after the churn");
}
}
#[test]
fn emptying_a_set_a_member_at_a_time_leaves_nothing_behind() {
let names: Vec<Vec<u8>> = (0..2000u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for name in &names {
s.insert(name, ()).expect("room");
}
let slots = s.slots.len();
for name in &names {
assert!(s.remove(name).is_some(), "{name:?} was there");
}
assert!(s.is_empty());
assert_eq!(s.dead, 0, "the drain cleared its own markers");
assert!(s.slots.iter().all(|v| *v == EMPTY));
for name in &names {
assert!(!s.contains(name), "{name:?} came back");
}
for name in &names {
s.insert(name, ()).expect("room");
}
check(&s, &names);
assert_eq!(s.slots.len(), slots, "the array is the size it was");
}
#[test]
fn a_removal_never_makes_the_array_fuller() {
let names: Vec<Vec<u8>> = (0..3000u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for name in &names {
s.insert(name, ()).expect("room");
}
let mut was = s.len() + s.dead as usize;
for i in (0..names.len()).rev().step_by(7) {
s.remove(&names[i]).expect("was there");
let now = s.len() + s.dead as usize;
assert!(
now <= was,
"{now} occupied against {was} before the removal"
);
was = now;
}
}
#[test]
fn a_rebuild_clears_the_markers() {
let mut s = Set::new();
for i in 0..1000u32 {
s.insert(format!("m{i}").as_bytes(), ()).expect("room");
}
for i in (0..1000u32).step_by(2) {
s.remove(format!("m{i}").as_bytes()).expect("was there");
}
assert!(s.dead > 0, "some of those removals left a marker");
s.grow_to(s.slots.len() * 2);
assert_eq!(s.dead, 0, "and a rebuild took all of them");
for i in (1..1000u32).step_by(2) {
assert!(s.contains(format!("m{i}").as_bytes()));
}
}
fn scan_all(s: &Set, page: usize) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut c = Cursor::START;
loop {
c = s.scan(c, page, |n, ()| out.push(n.to_vec()));
if c.is_end() {
return out;
}
}
}
#[test]
fn a_scan_of_a_still_collection_returns_everything_once() {
let names: Vec<Vec<u8>> = (0..300u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
for page in [1, 7, 10, 1000] {
let mut seen = scan_all(&s, page);
assert_eq!(seen.len(), names.len(), "page {page} returned a duplicate");
seen.sort();
let mut want = names.clone();
want.sort();
assert_eq!(seen, want, "page {page}");
}
}
#[test]
fn scanning_an_empty_collection_is_over_immediately() {
let s = Set::new();
let mut hit = 0;
assert!(s.scan(Cursor::START, 10, |_, ()| hit += 1).is_end());
assert_eq!(hit, 0);
}
#[test]
fn a_scan_never_misses_a_member_that_stayed() {
let names: Vec<Vec<u8>> = (0..400u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
let doomed: Vec<Vec<u8>> = names.iter().step_by(7).cloned().collect();
let mut gone = 0usize;
let mut seen: Vec<Vec<u8>> = Vec::new();
let mut c = Cursor::START;
loop {
c = s.scan(c, 9, |n, ()| seen.push(n.to_vec()));
for n in doomed.iter().skip(gone).take(3) {
s.remove(n);
}
gone = (gone + 3).min(doomed.len());
if c.is_end() {
break;
}
}
for n in &names {
if doomed.contains(n) {
continue;
}
assert!(
seen.contains(n),
"{} was there all along",
String::from_utf8_lossy(n)
);
}
}
#[test]
fn a_stale_cursor_is_answered_and_not_refused() {
let s = set(&[b"a", b"b", b"c"]);
let mut seen = Vec::new();
let c = s.scan(Cursor::at(1, 0, 900), 2, |n, ()| seen.push(n.to_vec()));
assert_eq!(seen, vec![b"c".to_vec(), b"b".to_vec()]);
assert_eq!(c.idx(), Some(0));
let mut also = Vec::new();
s.scan(Cursor::at(16, 9, 4), 99, |n, ()| also.push(n.to_vec()));
assert_eq!(also.len(), 3);
}
#[test]
fn taking_by_index_and_by_name_leave_the_same_table() {
let mut by_index = set(&[b"a", b"b", b"c", b"d"]);
let mut by_name = set(&[b"a", b"b", b"c", b"d"]);
let name = by_index.at(1).expect("in range").0.to_vec();
assert_eq!(by_index.remove_at(1), Some(()));
assert_eq!(by_name.remove(&name), Some(()));
assert_eq!(by_index.remove_at(99), None);
let mut left: Vec<Vec<u8>> = by_index.iter().map(|(n, ())| n.to_vec()).collect();
let mut also: Vec<Vec<u8>> = by_name.iter().map(|(n, ())| n.to_vec()).collect();
left.sort();
also.sort();
assert_eq!(left, also);
assert_eq!(left.len(), 3);
}
#[test]
fn a_row_is_eight_bytes_whatever_the_collection_stores() {
assert_eq!(size_of::<Row>(), 8);
assert_eq!(size_of::<Row>() + size_of::<()>(), 8, "a set member");
assert_eq!(size_of::<Row>() + size_of::<f64>(), 16, "a sorted set");
assert_eq!(size_of::<Row>() + size_of::<u32>(), 12, "a hash field");
}
fn tailed() -> Elements<()> {
Elements::tailed(8, 64)
}
#[test]
fn a_tail_comes_back_whatever_length_it_is() {
let mut t = tailed();
let long = vec![b'z'; 4000];
for (name, tail) in [
(&b"empty"[..], &b""[..]),
(b"one", b"1"),
(b"short", b"a value"),
(b"at254", &vec![b'y'; 254][..]),
(b"at255", &vec![b'x'; 255][..]),
(b"long", &long[..]),
] {
t.set_tailed(name, tail, ()).expect("room");
}
assert_eq!(t.tail(b"empty"), Some(&b""[..]));
assert_eq!(t.tail(b"one"), Some(&b"1"[..]));
assert_eq!(t.tail(b"short"), Some(&b"a value"[..]));
assert_eq!(t.tail_len(b"at254"), Some(254));
assert_eq!(t.tail_len(b"at255"), Some(255), "past the one byte length");
assert_eq!(t.tail(b"long"), Some(&long[..]));
assert_eq!(t.tail(b"absent"), None);
assert_eq!(t.len(), 6);
}
#[test]
fn rewriting_a_tail_leaves_every_other_row_alone() {
let mut t = tailed();
for i in 0..200 {
t.set_tailed(format!("f{i:04}").as_bytes(), b"v", ())
.expect("room");
}
for tail in [&b"a much longer value than before"[..], b"x", &[b'q'; 900]] {
let (row, fresh) = t.set_tailed(b"f0100", tail, ()).expect("room");
assert!(!fresh, "the field was already there");
assert_eq!(t.tail(b"f0100"), Some(tail));
assert_eq!(t.pair_at(row).map(|(n, _)| n), Some(&b"f0100"[..]));
}
for i in 0..200 {
let name = format!("f{i:04}");
let want: &[u8] = if i == 100 { &[b'q'; 900] } else { b"v" };
assert_eq!(t.tail(name.as_bytes()), Some(want), "field {i}");
}
}
#[test]
fn a_compaction_keeps_names_and_tails_together() {
let mut t = tailed();
for i in 0..500 {
t.set_tailed(format!("f{i:04}").as_bytes(), b"a value here", ())
.expect("room");
}
for i in 0..400 {
t.remove(format!("f{i:04}").as_bytes()).expect("there");
}
for i in 400..500 {
let name = format!("f{i:04}");
assert_eq!(t.tail(name.as_bytes()), Some(&b"a value here"[..]), "{i}");
}
let pairs: Vec<_> = t.pairs().map(|(n, v)| (n.to_vec(), v.to_vec())).collect();
assert_eq!(pairs.len(), 100);
assert!(pairs.iter().all(|(_, v)| v == b"a value here"));
}
#[test]
fn a_payload_can_be_changed_in_place() {
let mut h: Elements<i64> = Elements::new();
h.insert(b"counter", 1).expect("room");
*h.get_mut(b"counter").expect("there") += 41;
assert_eq!(h.get(b"counter"), Some(&42));
assert_eq!(h.get_mut(b"nothing"), None);
}
}