use std::cmp::Ordering;
use yo_common::num;
use yo_common::{Code, Error, Result};
use crate::frozen::{self, Broken};
pub const SLICE_SIZE: u64 = 4096;
const SLICE_BITS: u32 = SLICE_SIZE.trailing_zeros();
const SPARSE_MAX: usize = 10;
const SPARSE_MIN: usize = 5;
const FORM_SLICES: u8 = 1;
const HAS_INSERT: u8 = 0x80;
const LAYOUT_SPARSE: u8 = 1;
const LAYOUT_DENSE: u8 = 2;
pub const INDEX_MAX: u64 = u64::MAX - 1;
pub const ELEMENT_MAX: usize = num::DOUBLE_MAX + 2;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Element<'a> {
Int(i64),
Float(f64),
Str(&'a [u8]),
Short(Short),
}
impl<'a> Element<'a> {
pub fn text<'b>(&'b self, buf: &'b mut [u8; ELEMENT_MAX]) -> &'b [u8]
where
'a: 'b,
{
match *self {
Element::Str(s) => s,
Element::Short(ref s) => s.as_bytes(),
Element::Int(i) => {
let mut digits = [0u8; num::DIGITS_MAX];
let text = num::i64_digits(&mut digits, i);
let n = text.len();
buf[..n].copy_from_slice(text);
&buf[..n]
}
Element::Float(d) => {
let mut wide = [0u8; num::DOUBLE_MAX];
let text = num::write_double(&mut wide, d);
let mut n = text.len();
buf[..n].copy_from_slice(text);
if !text.iter().any(|&c| c == b'.' || c == b'e' || c == b'E') {
buf[n] = b'.';
buf[n + 1] = b'0';
n += 2;
}
&buf[..n]
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Short {
buf: [u8; INLINE_MAX],
len: u8,
}
impl Short {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.buf[..usize::from(self.len)]
}
}
impl core::fmt::Debug for Short {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", String::from_utf8_lossy(self.as_bytes()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Word(u64);
const INLINE_MAX: usize = 7;
const BLOB_MAX: usize = u32::MAX as usize;
const VALUE_MAX: usize = (1 << 30) - 1;
const TAG_MASK: u64 = 0b11;
const TAG_BLOB: u64 = 0;
const TAG_INT: u64 = 1;
const TAG_FLOAT: u64 = 2;
const TAG_STR: u64 = 3;
const INT_LO: i64 = -(1 << 61);
const INT_HI: i64 = (1 << 61) - 1;
impl Word {
const EMPTY: Word = Word(0);
const fn is_empty(self) -> bool {
self.0 == 0
}
const fn tag(self) -> u64 {
self.0 & TAG_MASK
}
const fn from_int(i: i64) -> Word {
Word(((i as u64) << 2) | TAG_INT)
}
const fn to_int(self) -> i64 {
(self.0 as i64) >> 2
}
const fn from_float_bits(bits: u64) -> Word {
Word((bits & !TAG_MASK) | TAG_FLOAT)
}
const fn to_float(self) -> f64 {
f64::from_bits(self.0 & !TAG_MASK)
}
fn from_short(s: &[u8]) -> Word {
let mut v = TAG_STR | ((s.len() as u64) << 2);
for (i, &b) in s.iter().enumerate() {
v |= u64::from(b) << (8 * (i + 1));
}
Word(v)
}
const fn short_len(self) -> usize {
((self.0 >> 2) & 0b111) as usize
}
fn to_short(self) -> Short {
let n = self.short_len();
let mut buf = [0u8; INLINE_MAX];
for (i, out) in buf.iter_mut().take(n).enumerate() {
*out = ((self.0 >> (8 * (i + 1))) & 0xff) as u8;
}
Short { buf, len: n as u8 }
}
const fn from_blob(start: usize, len: usize) -> Word {
Word(((start as u64) << 32) | ((len as u64) << 2) | TAG_BLOB)
}
const fn blob_span(self) -> (usize, usize) {
let start = (self.0 >> 32) as usize;
let len = ((self.0 >> 2) & 0x3fff_ffff) as usize;
(start, len)
}
}
#[derive(Debug, Clone)]
struct Slice {
count: u16,
layout: Layout,
}
#[derive(Debug, Clone)]
enum Layout {
Sparse { offs: Vec<u16>, words: Vec<Word> },
Dense { offset: u16, words: Vec<Word> },
}
impl Slice {
fn words_mut(&mut self) -> &mut [Word] {
match &mut self.layout {
Layout::Sparse { words, .. } | Layout::Dense { words, .. } => words,
}
}
fn words(&self) -> &[Word] {
match &self.layout {
Layout::Sparse { words, .. } | Layout::Dense { words, .. } => words,
}
}
fn high(&self) -> u16 {
match &self.layout {
Layout::Sparse { offs, .. } => *offs.last().expect("a slice is never empty"),
Layout::Dense { offset, words } => offset + (words.len() as u16) - 1,
}
}
fn get(&self, off: u16) -> Word {
match &self.layout {
Layout::Sparse { offs, words } => match offs.binary_search(&off) {
Ok(at) => words[at],
Err(_) => Word::EMPTY,
},
Layout::Dense { offset, words } => {
if off < *offset {
return Word::EMPTY;
}
let at = usize::from(off - offset);
words.get(at).copied().unwrap_or(Word::EMPTY)
}
}
}
fn put(&mut self, off: u16, w: Word) -> Word {
let old = match &mut self.layout {
Layout::Sparse { offs, words } => match offs.binary_search(&off) {
Ok(at) => std::mem::replace(&mut words[at], w),
Err(at) => {
offs.insert(at, off);
words.insert(at, w);
Word::EMPTY
}
},
Layout::Dense { offset, words } => {
if off < *offset {
let gap = usize::from(*offset - off);
words.splice(0..0, std::iter::repeat_n(Word::EMPTY, gap));
*offset = off;
std::mem::replace(&mut words[0], w)
} else {
let at = usize::from(off - *offset);
if at >= words.len() {
words.resize(at + 1, Word::EMPTY);
}
std::mem::replace(&mut words[at], w)
}
}
};
if old.is_empty() {
self.count += 1;
}
old
}
fn take(&mut self, off: u16) -> Word {
let old = match &mut self.layout {
Layout::Sparse { offs, words } => match offs.binary_search(&off) {
Ok(at) => {
offs.remove(at);
words.remove(at)
}
Err(_) => Word::EMPTY,
},
Layout::Dense { offset, words } => {
if off < *offset {
Word::EMPTY
} else {
let at = usize::from(off - *offset);
match words.get_mut(at) {
Some(slot) => std::mem::replace(slot, Word::EMPTY),
None => Word::EMPTY,
}
}
}
};
if !old.is_empty() {
self.count -= 1;
self.trim();
}
old
}
fn trim(&mut self) {
let Layout::Dense { offset, words } = &mut self.layout else {
return;
};
while words.last().is_some_and(|w| w.is_empty()) {
words.pop();
}
let lead = words.iter().take_while(|w| w.is_empty()).count();
if lead > 0 {
words.drain(..lead);
*offset += lead as u16;
}
}
fn span(&self) -> usize {
match &self.layout {
Layout::Sparse { offs, .. } => match (offs.first(), offs.last()) {
(Some(lo), Some(hi)) => usize::from(hi - lo) + 1,
_ => 0,
},
Layout::Dense { words, .. } => words.len(),
}
}
fn rebalance(&mut self) {
let count = usize::from(self.count);
match &self.layout {
Layout::Sparse { .. } => {
if count > SPARSE_MAX && self.span() <= count * 2 {
self.make_dense();
}
}
Layout::Dense { .. } => {
if count <= SPARSE_MIN || self.span() > count * 4 {
self.make_sparse();
}
}
}
}
fn make_dense(&mut self) {
let Layout::Sparse { offs, words } = &self.layout else {
return;
};
let base = offs[0];
let span = self.span();
let mut window = vec![Word::EMPTY; span];
for (&off, &w) in offs.iter().zip(words) {
window[usize::from(off - base)] = w;
}
self.layout = Layout::Dense {
offset: base,
words: window,
};
}
fn make_sparse(&mut self) {
let Layout::Dense { offset, words } = &self.layout else {
return;
};
let mut offs = Vec::with_capacity(usize::from(self.count));
let mut vals = Vec::with_capacity(usize::from(self.count));
for (i, &w) in words.iter().enumerate() {
if !w.is_empty() {
offs.push(offset + (i as u16));
vals.push(w);
}
}
self.layout = Layout::Sparse { offs, words: vals };
}
fn memory_bytes(&self) -> usize {
match &self.layout {
Layout::Sparse { offs, words } => {
offs.capacity() * 2 + words.capacity() * size_of::<Word>()
}
Layout::Dense { words, .. } => words.capacity() * size_of::<Word>(),
}
}
fn window<F>(&self, from: u16, to: u16, reverse: bool, f: &mut F) -> bool
where
F: FnMut(u16, Word) -> bool,
{
match &self.layout {
Layout::Sparse { offs, words } => {
let a = offs.partition_point(|&o| o < from);
let b = offs.partition_point(|&o| o <= to);
if reverse {
for i in (a..b).rev() {
if !f(offs[i], words[i]) {
return false;
}
}
} else {
for i in a..b {
if !f(offs[i], words[i]) {
return false;
}
}
}
}
Layout::Dense { offset, words } => {
let base = *offset;
let end = base + (words.len() as u16) - 1;
if to < base || from > end {
return true;
}
let a = usize::from(from.max(base) - base);
let b = usize::from(to.min(end) - base);
let window = &words[a..=b];
let at = |i: usize| base + ((a + i) as u16);
if reverse {
for (i, w) in window.iter().enumerate().rev() {
if !w.is_empty() && !f(at(i), *w) {
return false;
}
}
} else {
for (i, w) in window.iter().enumerate() {
if !w.is_empty() && !f(at(i), *w) {
return false;
}
}
}
}
}
true
}
}
#[derive(Debug, Clone, Default)]
pub struct Array {
slices: Vec<(u64, Slice)>,
blob: Vec<u8>,
dead: usize,
count: u64,
insert: Option<u64>,
}
const COMPACT_MIN: usize = 4096;
impl Array {
#[must_use]
pub fn new() -> Array {
Array::default()
}
#[must_use]
pub fn len(&self) -> u64 {
match self.slices.last() {
Some((id, slice)) => id * SLICE_SIZE + u64::from(slice.high()) + 1,
None => 0,
}
}
#[must_use]
pub const fn count(&self) -> u64 {
self.count
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
#[must_use]
pub fn get(&self, idx: u64) -> Option<Element<'_>> {
let (id, off) = split(idx);
let at = self.find(id).ok()?;
let w = self.slices[at].1.get(off);
self.decode(w)
}
pub fn set(&mut self, idx: u64, val: &[u8]) -> Result<bool> {
let w = self.encode(val)?;
let (id, off) = split(idx);
let at = match self.find(id) {
Ok(at) => at,
Err(at) => {
self.slices.insert(
at,
(
id,
Slice {
count: 0,
layout: Layout::Sparse {
offs: Vec::new(),
words: Vec::new(),
},
},
),
);
at
}
};
let old = self.slices[at].1.put(off, w);
self.slices[at].1.rebalance();
self.retire(old);
self.maybe_compact();
if old.is_empty() {
self.count += 1;
Ok(true)
} else {
Ok(false)
}
}
pub fn del(&mut self, idx: u64) -> bool {
let (id, off) = split(idx);
let Ok(at) = self.find(id) else {
return false;
};
let old = self.slices[at].1.take(off);
if old.is_empty() {
return false;
}
self.retire(old);
self.count -= 1;
if self.slices[at].1.count == 0 {
self.slices.remove(at);
} else {
self.slices[at].1.rebalance();
}
self.maybe_compact();
true
}
pub fn delete_range(&mut self, lo: u64, hi: u64) -> u64 {
if lo > hi {
return 0;
}
let (lo_id, lo_off) = split(lo);
let (hi_id, hi_off) = split(hi);
let first = match self.find(lo_id) {
Ok(at) | Err(at) => at,
};
let mut gone = 0;
let mut at = first;
while at < self.slices.len() && self.slices[at].0 <= hi_id {
let id = self.slices[at].0;
let from = if id == lo_id { lo_off } else { 0 };
let to = if id == hi_id {
hi_off
} else {
(SLICE_SIZE - 1) as u16
};
gone += self.clear_within(at, from, to);
if self.slices[at].1.count == 0 {
self.slices.remove(at);
} else {
self.slices[at].1.rebalance();
at += 1;
}
}
self.count -= gone;
self.maybe_compact();
self.maybe_compact_slices();
gone
}
#[must_use]
pub const fn next_index(&self) -> Option<u64> {
match self.insert {
None => Some(0),
Some(i) if i >= INDEX_MAX => None,
Some(i) => Some(i + 1),
}
}
pub const fn seek(&mut self, idx: u64) {
self.insert = if idx == 0 { None } else { Some(idx - 1) };
}
pub fn append<'v>(&mut self, values: impl Iterator<Item = &'v [u8]> + Clone) -> Result<u64> {
let n = values.clone().count() as u64;
let over = || Error::new(Code::Invalid, INSERT_OVERFLOW);
let start = self.next_index().ok_or_else(over)?;
if n == 0 {
return Ok(self.insert.unwrap_or(0));
}
let last = start.checked_add(n - 1).filter(|l| *l <= INDEX_MAX);
let last = last.ok_or_else(over)?;
for (i, v) in values.enumerate() {
self.set(start + i as u64, v)?;
}
self.insert = Some(last);
Ok(last)
}
pub fn ring<'v>(&mut self, size: u64, values: impl Iterator<Item = &'v [u8]>) -> Result<u64> {
debug_assert!(size > 0, "the caller refuses a size of zero");
let old_span = self.len();
let keep = if old_span == 0 || size == old_span {
0
} else if size < old_span {
size
} else if self.insert.is_some() && self.next_cursor() < old_span {
old_span
} else {
0
};
if keep > 0 {
self.rework(old_span, keep)?;
}
let mut cursor = self.insert.unwrap_or(0);
for v in values {
cursor = self.next_cursor();
if cursor >= size {
cursor %= size;
}
self.set(cursor, v)?;
self.insert = Some(cursor);
}
Ok(cursor)
}
const fn next_cursor(&self) -> u64 {
match self.insert {
None => 0,
Some(i) => i.wrapping_add(1),
}
}
fn rework(&mut self, old_span: u64, keep: u64) -> Result<()> {
let anchor = match self.insert {
None => old_span - 1,
Some(i) => i % old_span,
};
let back = |i: u64| if i == 0 { old_span - 1 } else { i - 1 };
let forward = |i: u64| if i + 1 == old_span { 0 } else { i + 1 };
let mut kept = 0;
let mut src = anchor;
while kept < keep && self.get(src).is_some() {
kept += 1;
src = back(src);
}
src = forward(src);
let mut fresh = Array::new();
for dst in 0..kept {
let mut buf = [0u8; ELEMENT_MAX];
let el = self.get(src).expect("the walk stopped at the first hole");
fresh.set(dst, el.text(&mut buf))?;
src = forward(src);
}
fresh.insert = kept.checked_sub(1);
*self = fresh;
Ok(())
}
pub fn last_items<F>(&self, count: u64, newest_first: bool, mut f: F) -> u64
where
F: FnMut(Option<Element<'_>>),
{
let steps = count.min(self.count);
if steps == 0 {
return 0;
}
let span = self.len();
let anchor = self.insert.unwrap_or(span - 1);
let near = steps.min(anchor + 1);
let wrapped = steps - near;
let near_lo = anchor - (near - 1);
let wrapped_lo = span - wrapped;
let mut emit = |i: u64| f(self.get(i));
if newest_first {
(near_lo..=anchor).rev().for_each(&mut emit);
(wrapped_lo..span).rev().for_each(&mut emit);
} else {
(wrapped_lo..span).for_each(&mut emit);
(near_lo..=anchor).for_each(&mut emit);
}
steps
}
pub fn scan<F>(&self, start: u64, end: u64, mut f: F)
where
F: FnMut(u64, Element<'_>) -> bool,
{
let reverse = start > end;
let (lo, hi) = if reverse { (end, start) } else { (start, end) };
let (lo_id, lo_off) = split(lo);
let (hi_id, hi_off) = split(hi);
let first = match self.find(lo_id) {
Ok(at) | Err(at) => at,
};
let last = match self.find(hi_id) {
Ok(at) => at + 1,
Err(at) => at,
};
let mut visit = |at: usize| {
let (id, slice) = &self.slices[at];
let from = if *id == lo_id { lo_off } else { 0 };
let to = if *id == hi_id {
hi_off
} else {
(SLICE_SIZE - 1) as u16
};
let base = id * SLICE_SIZE;
slice.window(from, to, reverse, &mut |off, w| {
let el = self.decode(w).expect("a populated word decodes");
f(base + u64::from(off), el)
})
};
if reverse {
for at in (first..last).rev() {
if !visit(at) {
return;
}
}
} else {
for at in first..last {
if !visit(at) {
return;
}
}
}
}
#[must_use]
pub fn info(&self, full: bool) -> Info {
let mut info = Info {
count: self.count,
len: self.len(),
next_insert: self.next_index().unwrap_or(0),
slices: self.slices.len() as u64,
directory_size: self.slices.capacity() as u64,
slice_size: SLICE_SIZE,
..Info::default()
};
if !full {
return info;
}
let (mut window, mut filled, mut room) = (0u64, 0u64, 0u64);
for (_, slice) in &self.slices {
match &slice.layout {
Layout::Dense { words, .. } => {
info.dense_slices += 1;
window += words.len() as u64;
filled += u64::from(slice.count);
}
Layout::Sparse { offs, .. } => {
info.sparse_slices += 1;
room += offs.capacity() as u64;
}
}
}
let ratio = |a: u64, b: u64| if b == 0 { 0.0 } else { a as f64 / b as f64 };
info.avg_dense_size = ratio(window, info.dense_slices);
info.avg_dense_fill = ratio(filled, window);
info.avg_sparse_size = ratio(room, info.sparse_slices);
info
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.slices.capacity() * size_of::<(u64, Slice)>()
+ self
.slices
.iter()
.map(|(_, s)| s.memory_bytes())
.sum::<usize>()
+ self.blob.capacity()
}
pub fn freeze(&self, out: &mut Vec<u8>) {
out.push(match self.insert {
Some(_) => FORM_SLICES | HAS_INSERT,
None => FORM_SLICES,
});
if let Some(at) = self.insert {
frozen::put_uint(out, at);
}
frozen::put_uint(out, self.count);
frozen::put_uint(out, (self.blob.len() - self.dead) as u64);
for (_, slice) in &self.slices {
for w in slice.words() {
if !w.is_empty() && w.tag() == TAG_BLOB {
let (start, len) = w.blob_span();
out.extend_from_slice(&self.blob[start..start + len]);
}
}
}
frozen::put_uint(out, self.slices.len() as u64);
let mut at = 0usize;
for (id, slice) in &self.slices {
frozen::put_uint(out, *id);
match &slice.layout {
Layout::Sparse { offs, words } => {
out.push(LAYOUT_SPARSE);
frozen::put_uint(out, words.len() as u64);
for (&off, &w) in offs.iter().zip(words) {
frozen::put_uint(out, u64::from(off));
frozen::put_uint(out, moved(w, &mut at));
}
}
Layout::Dense { offset, words } => {
out.push(LAYOUT_DENSE);
frozen::put_uint(out, u64::from(*offset));
frozen::put_uint(out, words.len() as u64);
for &w in words {
frozen::put_uint(out, moved(w, &mut at));
}
}
}
}
}
pub fn thaw(bytes: &[u8]) -> core::result::Result<Array, Broken> {
let mut cut = frozen::Cut::new(bytes);
let tag = cut.byte()?;
if tag & !HAS_INSERT != FORM_SLICES {
return Err(Broken::Form);
}
let insert = if tag & HAS_INSERT != 0 {
let at = cut.uint()?;
if at > INDEX_MAX {
return Err(Broken::Body);
}
Some(at)
} else {
None
};
let count = cut.uint()?;
let blob = cut.bytes()?.to_vec();
let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
if n > cut.rest().len() {
return Err(Broken::Body);
}
let mut slices: Vec<(u64, Slice)> = Vec::with_capacity(n);
let mut used = 0usize;
let mut seen = 0u64;
for _ in 0..n {
let id = cut.uint()?;
if id > INDEX_MAX >> SLICE_BITS {
return Err(Broken::Body);
}
if slices.last().is_some_and(|(last, _)| id <= *last) {
return Err(Broken::Body);
}
let slice = read_slice(&mut cut, blob.len(), &mut used)?;
seen += u64::from(slice.count);
slices.push((id, slice));
}
if seen != count || used != blob.len() {
return Err(Broken::Body);
}
Ok(Array {
slices,
blob,
dead: 0,
count,
insert,
})
}
fn find(&self, id: u64) -> core::result::Result<usize, usize> {
self.slices.binary_search_by(|(have, _)| {
if *have < id {
Ordering::Less
} else if *have > id {
Ordering::Greater
} else {
Ordering::Equal
}
})
}
fn clear_within(&mut self, at: usize, from: u16, to: u16) -> u64 {
let slice = &mut self.slices[at].1;
let mut gone = 0u64;
let mut dead = 0usize;
match &mut slice.layout {
Layout::Sparse { offs, words } => {
let lo = offs.partition_point(|&o| o < from);
let hi = offs.partition_point(|&o| o <= to);
for w in &words[lo..hi] {
gone += 1;
if w.tag() == TAG_BLOB {
dead += w.blob_span().1;
}
}
offs.drain(lo..hi);
words.drain(lo..hi);
}
Layout::Dense { offset, words } => {
let base = *offset;
let lo = usize::from(from.saturating_sub(base));
if to >= base && lo < words.len() {
let hi = usize::from(to - base).min(words.len() - 1);
for w in &mut words[lo..=hi] {
if !w.is_empty() {
gone += 1;
if w.tag() == TAG_BLOB {
dead += w.blob_span().1;
}
*w = Word::EMPTY;
}
}
}
}
}
slice.count -= gone as u16;
slice.trim();
self.dead += dead;
gone
}
fn retire(&mut self, w: Word) {
if !w.is_empty() && w.tag() == TAG_BLOB {
self.dead += w.blob_span().1;
}
}
fn encode(&mut self, val: &[u8]) -> Result<Word> {
if let Some(i) = num::parse_i64(val)
&& (INT_LO..=INT_HI).contains(&i)
{
return Ok(Word::from_int(i));
}
if let Some(w) = float_word(val) {
return Ok(w);
}
if val.len() <= INLINE_MAX {
return Ok(Word::from_short(val));
}
if val.len() > VALUE_MAX {
return Err(Error::new(Code::Full, VALUE_TOO_LONG));
}
if self.blob.len() + val.len() > BLOB_MAX {
self.compact();
}
if self.blob.len() + val.len() > BLOB_MAX {
return Err(Error::new(Code::Full, BLOB_TOO_LONG));
}
let start = self.blob.len();
self.blob.extend_from_slice(val);
Ok(Word::from_blob(start, val.len()))
}
fn decode(&self, w: Word) -> Option<Element<'_>> {
if w.is_empty() {
return None;
}
Some(match w.tag() {
TAG_INT => Element::Int(w.to_int()),
TAG_FLOAT => Element::Float(w.to_float()),
TAG_STR => Element::Short(w.to_short()),
_ => {
let (start, len) = w.blob_span();
Element::Str(&self.blob[start..start + len])
}
})
}
fn compact(&mut self) {
let mut fresh = Vec::with_capacity(self.blob.len() - self.dead);
for (_, slice) in &mut self.slices {
for w in slice.words_mut() {
if w.is_empty() || w.tag() != TAG_BLOB {
continue;
}
let (start, len) = w.blob_span();
let to = fresh.len();
fresh.extend_from_slice(&self.blob[start..start + len]);
*w = Word::from_blob(to, len);
}
}
self.blob = fresh;
self.dead = 0;
}
fn maybe_compact(&mut self) {
if self.dead >= COMPACT_MIN && self.dead * 2 >= self.blob.len() {
self.compact();
}
}
fn maybe_compact_slices(&mut self) {
if self.slices.capacity() > 16 && self.slices.capacity() > self.slices.len() * 4 {
self.slices.shrink_to_fit();
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Info {
pub count: u64,
pub len: u64,
pub next_insert: u64,
pub slices: u64,
pub directory_size: u64,
pub slice_size: u64,
pub dense_slices: u64,
pub sparse_slices: u64,
pub avg_dense_size: f64,
pub avg_dense_fill: f64,
pub avg_sparse_size: f64,
}
pub const BLOB_TOO_LONG: &str = "array values exceed the four gigabyte per key limit";
pub const VALUE_TOO_LONG: &str = "array value exceeds the one gigabyte limit";
pub const INSERT_OVERFLOW: &str = "insert index overflow";
fn moved(w: Word, at: &mut usize) -> u64 {
if w.is_empty() || w.tag() != TAG_BLOB {
return w.0;
}
let (_, len) = w.blob_span();
let start = *at;
*at += len;
Word::from_blob(start, len).0
}
fn read_word(
cut: &mut frozen::Cut<'_>,
blob: usize,
used: &mut usize,
) -> core::result::Result<Word, Broken> {
let w = Word(cut.uint()?);
if !w.is_empty() && w.tag() == TAG_BLOB {
let (start, len) = w.blob_span();
if len <= INLINE_MAX || start + len > blob {
return Err(Broken::Body);
}
*used += len;
}
Ok(w)
}
fn read_slice(
cut: &mut frozen::Cut<'_>,
blob: usize,
used: &mut usize,
) -> core::result::Result<Slice, Broken> {
match cut.byte()? {
LAYOUT_SPARSE => {
let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
if n == 0 || n > cut.rest().len() {
return Err(Broken::Body);
}
let mut offs: Vec<u16> = Vec::with_capacity(n);
let mut words = Vec::with_capacity(n);
for _ in 0..n {
let off = u16::try_from(cut.uint()?).map_err(|_| Broken::Body)?;
if u64::from(off) >= SLICE_SIZE {
return Err(Broken::Body);
}
if offs.last().is_some_and(|last| off <= *last) {
return Err(Broken::Body);
}
let w = read_word(cut, blob, used)?;
if w.is_empty() {
return Err(Broken::Body);
}
offs.push(off);
words.push(w);
}
Ok(Slice {
count: n as u16,
layout: Layout::Sparse { offs, words },
})
}
LAYOUT_DENSE => {
let offset = u16::try_from(cut.uint()?).map_err(|_| Broken::Body)?;
let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
if n == 0 || n > cut.rest().len() {
return Err(Broken::Body);
}
if u64::from(offset) + n as u64 > SLICE_SIZE {
return Err(Broken::Body);
}
let mut words = Vec::with_capacity(n);
let mut live = 0u16;
for _ in 0..n {
let w = read_word(cut, blob, used)?;
if !w.is_empty() {
live += 1;
}
words.push(w);
}
if words[0].is_empty() || words[n - 1].is_empty() {
return Err(Broken::Body);
}
Ok(Slice {
count: live,
layout: Layout::Dense { offset, words },
})
}
_ => Err(Broken::Form),
}
}
#[inline]
const fn split(idx: u64) -> (u64, u16) {
(idx >> SLICE_BITS, (idx & (SLICE_SIZE - 1)) as u16)
}
fn float_word(val: &[u8]) -> Option<Word> {
let body = match val.first() {
Some(b'-') if val.len() > 1 => &val[1..],
Some(_) => val,
None => return None,
};
let mut dots = 0;
for &c in body {
match c {
b'.' => dots += 1,
b'0'..=b'9' => {}
_ => return None,
}
}
if dots != 1 {
return None;
}
let d = num::parse_f64(val)?;
if !d.is_finite() {
return None;
}
let trunc = f64::from_bits(d.to_bits() & !TAG_MASK);
let mut buf = [0u8; ELEMENT_MAX];
let el = Element::Float(trunc);
if el.text(&mut buf) == val {
Some(Word::from_float_bits(trunc.to_bits()))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn read(a: &Array, idx: u64) -> Option<Vec<u8>> {
let el = a.get(idx)?;
let mut buf = [0u8; ELEMENT_MAX];
Some(el.text(&mut buf).to_vec())
}
fn set(a: &mut Array, idx: u64, val: &[u8]) -> bool {
a.set(idx, val).expect("a value that fits")
}
fn scan(a: &Array, start: u64, end: u64, limit: usize) -> Vec<(u64, Vec<u8>)> {
let mut got = Vec::new();
a.scan(start, end, |i, el| {
let mut buf = [0u8; ELEMENT_MAX];
got.push((i, el.text(&mut buf).to_vec()));
got.len() < limit
});
got
}
fn last(a: &Array, count: u64, newest_first: bool) -> Vec<Option<Vec<u8>>> {
let mut got = Vec::new();
let n = a.last_items(count, newest_first, |el| {
got.push(el.map(|e| {
let mut buf = [0u8; ELEMENT_MAX];
e.text(&mut buf).to_vec()
}));
});
assert_eq!(n as usize, got.len(), "the count is what it emitted");
got
}
fn append(a: &mut Array, vals: &[&[u8]]) -> Result<u64> {
a.append(vals.iter().copied())
}
fn ring(a: &mut Array, size: u64, vals: &[&[u8]]) -> u64 {
a.ring(size, vals.iter().copied()).expect("values that fit")
}
#[test]
fn a_value_comes_back_the_way_it_went_in() {
let mut a = Array::new();
assert!(set(&mut a, 0, b"hello"));
assert!(set(&mut a, 1, b"a much longer value than fits in a word"));
assert!(set(&mut a, 2, b"42"));
assert!(set(&mut a, 3, b"1.5"));
assert!(set(&mut a, 4, b""));
assert_eq!(read(&a, 0).as_deref(), Some(&b"hello"[..]));
assert_eq!(
read(&a, 1).as_deref(),
Some(&b"a much longer value than fits in a word"[..])
);
assert_eq!(read(&a, 2).as_deref(), Some(&b"42"[..]));
assert_eq!(read(&a, 3).as_deref(), Some(&b"1.5"[..]));
assert_eq!(read(&a, 4).as_deref(), Some(&b""[..]));
assert_eq!(read(&a, 5), None);
}
#[test]
fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
let mut a = Array::new();
assert_eq!(a.len(), 0);
assert_eq!(a.count(), 0);
assert!(a.is_empty());
set(&mut a, 1_000_000, b"x");
assert_eq!(a.len(), 1_000_001);
assert_eq!(a.count(), 1);
assert!(!a.is_empty());
set(&mut a, 5, b"y");
assert_eq!(a.len(), 1_000_001, "a lower index does not move the length");
assert_eq!(a.count(), 2);
a.del(1_000_000);
assert_eq!(a.len(), 6, "and the length comes back down when it goes");
assert_eq!(a.count(), 1);
}
#[test]
fn an_overwrite_does_not_count_as_a_fill() {
let mut a = Array::new();
assert!(set(&mut a, 7, b"first"));
assert!(!set(&mut a, 7, b"second"));
assert_eq!(a.count(), 1);
assert_eq!(read(&a, 7).as_deref(), Some(&b"second"[..]));
}
#[test]
fn deleting_the_last_element_leaves_nothing_behind() {
let mut a = Array::new();
set(&mut a, 3, b"x");
assert!(a.del(3));
assert!(!a.del(3), "and a second delete finds nothing");
assert!(a.is_empty());
assert_eq!(a.len(), 0);
assert!(a.slices.is_empty(), "the slice went with the last element");
}
#[test]
fn the_index_space_runs_to_the_top() {
let mut a = Array::new();
set(&mut a, 0, b"low");
set(&mut a, INDEX_MAX, b"high");
assert_eq!(read(&a, INDEX_MAX).as_deref(), Some(&b"high"[..]));
assert_eq!(a.count(), 2);
assert_eq!(a.len(), u64::MAX, "the highest index plus one");
assert_eq!(a.slices.len(), 2);
}
#[test]
fn a_slice_changes_layout_when_the_shape_of_it_changes() {
let mut a = Array::new();
for i in 0..SPARSE_MAX as u64 {
set(&mut a, i, b"x");
}
assert!(
matches!(a.slices[0].1.layout, Layout::Sparse { .. }),
"ten scattered elements do not want an index"
);
set(&mut a, 10, b"x");
assert!(
matches!(a.slices[0].1.layout, Layout::Dense { .. }),
"eleven consecutive ones do"
);
for i in 0..8 {
a.del(i);
}
assert!(
matches!(a.slices[0].1.layout, Layout::Sparse { .. }),
"three left is under the floor"
);
assert_eq!(a.count(), 3);
assert_eq!(read(&a, 10).as_deref(), Some(&b"x"[..]));
}
#[test]
fn a_wide_slice_stays_sparse_however_many_elements_it_has() {
let mut a = Array::new();
for i in 0..40 {
set(&mut a, i * 100, b"x");
}
assert!(
matches!(a.slices[0].1.layout, Layout::Sparse { .. }),
"forty elements over four thousand positions is not a window"
);
for i in 0..40 {
assert_eq!(read(&a, i * 100).as_deref(), Some(&b"x"[..]), "at {i}");
}
}
#[test]
fn a_dense_window_grows_downwards_too() {
let mut a = Array::new();
for i in (0..20u64).rev() {
set(&mut a, i, b"v");
}
assert!(matches!(a.slices[0].1.layout, Layout::Dense { .. }));
for i in 0..20 {
assert_eq!(read(&a, i).as_deref(), Some(&b"v"[..]), "at {i}");
}
assert_eq!(a.count(), 20);
assert_eq!(a.len(), 20);
}
#[test]
fn a_range_delete_costs_what_it_touches_and_not_what_it_spans() {
let mut a = Array::new();
set(&mut a, 1, b"a");
set(&mut a, 500_000, b"b");
set(&mut a, INDEX_MAX, b"c");
assert_eq!(a.delete_range(0, INDEX_MAX), 3);
assert!(a.is_empty());
assert!(a.slices.is_empty());
assert_eq!(a.delete_range(0, INDEX_MAX), 0, "and again finds nothing");
}
#[test]
fn a_range_delete_takes_the_ends_and_leaves_the_rest() {
let mut a = Array::new();
for i in 0..30_000u64 {
set(&mut a, i, b"x");
}
assert_eq!(a.delete_range(100, 29_899), 29_800);
assert_eq!(a.count(), 200);
assert_eq!(read(&a, 99).as_deref(), Some(&b"x"[..]));
assert_eq!(read(&a, 100), None);
assert_eq!(read(&a, 29_899), None);
assert_eq!(read(&a, 29_900).as_deref(), Some(&b"x"[..]));
assert_eq!(a.len(), 30_000);
}
#[test]
fn a_backwards_range_deletes_nothing() {
let mut a = Array::new();
set(&mut a, 5, b"x");
assert_eq!(a.delete_range(9, 4), 0);
assert_eq!(a.count(), 1);
}
#[test]
fn only_a_value_that_prints_back_the_same_becomes_a_number() {
let cases: &[(&[u8], bool)] = &[
(b"0", true),
(b"42", true),
(b"-42", true),
(b"9007199254740993", true),
(b"007", false),
(b"+7", false),
(b"-0", false),
(b" 7", false),
(b"7 ", false),
(b"", false),
];
for &(val, want) in cases {
let mut a = Array::new();
set(&mut a, 0, val);
let is_int = matches!(a.get(0), Some(Element::Int(_)));
assert_eq!(is_int, want, "{}", String::from_utf8_lossy(val));
assert_eq!(read(&a, 0).as_deref(), Some(val), "round trip");
}
}
#[test]
fn only_a_double_that_prints_back_the_same_is_stored_as_one() {
let cases: &[(&[u8], bool)] = &[
(b"1.0", true),
(b"1.5", true),
(b"-2.25", true),
(b"0.0", true),
(b"3.14", false),
(b"1.10", false),
(b"-0.0", true),
(b"1.", false),
(b".5", false),
(b"1e5", false),
(b"nan", false),
(b"inf", false),
];
for &(val, want) in cases {
let mut a = Array::new();
set(&mut a, 0, val);
let is_float = matches!(a.get(0), Some(Element::Float(_)));
assert_eq!(is_float, want, "{}", String::from_utf8_lossy(val));
assert_eq!(read(&a, 0).as_deref(), Some(val), "round trip");
}
}
#[test]
fn the_blob_is_compacted_once_enough_of_it_is_dead() {
let mut a = Array::new();
let long = vec![b'a'; 64];
for i in 0..1000 {
set(&mut a, i, &long);
}
let full = a.blob.len();
assert_eq!(full, 64_000);
for i in 0..1000 {
set(&mut a, i, b"short");
}
assert!(a.blob.len() < full / 2, "{} bytes left", a.blob.len());
assert_eq!(a.count(), 1000);
for i in 0..1000 {
assert_eq!(read(&a, i).as_deref(), Some(&b"short"[..]), "at {i}");
}
}
#[test]
fn compaction_keeps_the_values_that_survive_it() {
let mut a = Array::new();
for i in 0..2000u64 {
let val = format!("value number {i} padded out past the inline limit");
set(&mut a, i, val.as_bytes());
}
for i in (0..2000u64).step_by(2) {
a.del(i);
}
assert!(a.dead * 2 < a.blob.len(), "the blob was rewritten");
for i in (1..2000u64).step_by(2) {
let want = format!("value number {i} padded out past the inline limit");
assert_eq!(read(&a, i).as_deref(), Some(want.as_bytes()), "at {i}");
}
}
#[test]
fn a_value_over_the_ceiling_is_an_error_and_not_a_panic() {
let mut a = Array::new();
let huge = vec![b'x'; VALUE_MAX + 1];
let e = a.set(0, &huge).unwrap_err();
assert_eq!(e.code(), Code::Full);
assert_eq!(e.message(), VALUE_TOO_LONG);
assert!(a.is_empty(), "and nothing was written");
}
#[test]
fn a_word_holds_what_it_was_given() {
assert!(Word::EMPTY.is_empty());
for i in [0i64, 1, -1, INT_LO, INT_HI, 12345, -99999] {
let w = Word::from_int(i);
assert!(!w.is_empty());
assert_eq!(w.tag(), TAG_INT);
assert_eq!(w.to_int(), i, "{i}");
}
for d in [0.0f64, 1.5, -2.25, 1e300] {
let bits = d.to_bits() & !TAG_MASK;
let w = Word::from_float_bits(bits);
assert!(!w.is_empty());
assert_eq!(w.tag(), TAG_FLOAT);
assert_eq!(w.to_float().to_bits(), bits);
}
for s in [&b""[..], b"a", b"abc", b"1234567"] {
let w = Word::from_short(s);
assert!(!w.is_empty(), "{s:?}");
assert_eq!(w.tag(), TAG_STR);
assert_eq!(w.to_short().as_bytes(), s);
}
let w = Word::from_blob(4_000_000_000, 1_000_000);
assert_eq!(w.tag(), TAG_BLOB);
assert_eq!(w.blob_span(), (4_000_000_000, 1_000_000));
assert!(!w.is_empty());
}
#[test]
fn what_it_holds_is_what_it_says_it_holds() {
let mut a = Array::new();
assert_eq!(a.memory_bytes(), 0);
for i in 0..1000u64 {
set(&mut a, i * 7, b"a value past the inline limit");
}
let held = a.memory_bytes();
assert!(held > 29_000, "{held} bytes for 29 kilobytes of values");
a.delete_range(0, u64::MAX - 1);
assert!(
a.memory_bytes() < held / 2,
"{} bytes left of {held}",
a.memory_bytes()
);
}
#[test]
fn it_agrees_with_a_map_over_a_scramble_of_writes() {
use std::collections::BTreeMap;
let mut a = Array::new();
let mut want: BTreeMap<u64, Vec<u8>> = BTreeMap::new();
let mut seed = 0x9e37_79b9_7f4a_7c15u64;
let mut next = || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
for step in 0..20_000u64 {
let idx = next() % 20_000;
match step % 5 {
0..=2 => {
let val = format!("v{step}");
let was_new = set(&mut a, idx, val.as_bytes());
assert_eq!(was_new, want.insert(idx, val.into_bytes()).is_none());
}
3 => {
assert_eq!(a.del(idx), want.remove(&idx).is_some());
}
_ => {
let hi = idx + (next() % 500);
let gone = a.delete_range(idx, hi);
let keys: Vec<u64> = want.range(idx..=hi).map(|(k, _)| *k).collect();
assert_eq!(gone, keys.len() as u64);
for k in keys {
want.remove(&k);
}
}
}
assert_eq!(a.count(), want.len() as u64, "count after step {step}");
}
assert_eq!(
a.len(),
want.keys().next_back().map_or(0, |k| k + 1),
"the high water mark"
);
for (&idx, val) in &want {
assert_eq!(read(&a, idx).as_deref(), Some(&val[..]), "at {idx}");
}
}
#[test]
fn a_scan_finds_the_elements_and_steps_over_the_holes() {
let mut a = Array::new();
set(&mut a, 0, b"a");
set(&mut a, 5, b"b");
set(&mut a, SLICE_SIZE * 2 + 7, b"c");
let all = vec![
(0, b"a".to_vec()),
(5, b"b".to_vec()),
(SLICE_SIZE * 2 + 7, b"c".to_vec()),
];
assert_eq!(scan(&a, 0, INDEX_MAX, usize::MAX), all);
let mut backwards = all.clone();
backwards.reverse();
assert_eq!(scan(&a, INDEX_MAX, 0, usize::MAX), backwards);
assert_eq!(scan(&a, 1, 5, usize::MAX), all[1..2].to_vec());
assert_eq!(scan(&a, 6, SLICE_SIZE, usize::MAX), Vec::new());
assert_eq!(scan(&a, 0, INDEX_MAX, 2), all[..2].to_vec());
assert_eq!(scan(&Array::new(), 0, INDEX_MAX, usize::MAX), Vec::new());
}
#[test]
fn a_scan_reads_both_layouts_the_same_way() {
let mut a = Array::new();
for i in 0..40u64 {
set(&mut a, i, format!("v{i}").as_bytes());
}
for i in (0..40u64).step_by(2) {
a.del(i);
}
let odd: Vec<(u64, Vec<u8>)> = (1..40u64)
.step_by(2)
.map(|i| (i, format!("v{i}").into_bytes()))
.collect();
assert_eq!(scan(&a, 0, 100, usize::MAX), odd);
let mut b = Array::new();
for i in (1..40u64).step_by(2) {
set(&mut b, i, format!("v{i}").as_bytes());
}
assert_eq!(scan(&b, 0, 100, usize::MAX), odd);
}
#[test]
fn the_cursor_moves_only_when_something_appends_to_it() {
let mut a = Array::new();
assert_eq!(a.next_index(), Some(0));
set(&mut a, 0, b"set");
assert_eq!(a.next_index(), Some(0));
assert_eq!(append(&mut a, &[b"x", b"y"]).expect("room"), 1);
assert_eq!(read(&a, 0).as_deref(), Some(&b"x"[..]));
assert_eq!(a.next_index(), Some(2));
a.seek(100);
assert_eq!(a.next_index(), Some(100));
assert_eq!(append(&mut a, &[b"z"]).expect("room"), 100);
assert_eq!(read(&a, 100).as_deref(), Some(&b"z"[..]));
a.seek(0);
assert_eq!(a.next_index(), Some(0));
}
#[test]
fn an_append_that_would_run_off_the_top_writes_nothing() {
let mut a = Array::new();
a.seek(INDEX_MAX - 1);
let e = append(&mut a, &[b"x", b"y", b"z"]).unwrap_err();
assert_eq!(e.code(), Code::Invalid);
assert_eq!(e.message(), INSERT_OVERFLOW);
assert_eq!(a.count(), 0, "and none of the batch landed");
assert_eq!(append(&mut a, &[b"x", b"y"]).expect("room"), INDEX_MAX);
assert_eq!(a.next_index(), None);
assert_eq!(
append(&mut a, &[b"z"]).unwrap_err().message(),
INSERT_OVERFLOW
);
}
#[test]
fn a_ring_wraps_round_at_its_size() {
let mut a = Array::new();
assert_eq!(ring(&mut a, 3, &[b"a", b"b", b"c"]), 2);
assert_eq!(ring(&mut a, 3, &[b"d", b"e"]), 1);
assert_eq!(a.len(), 3, "it never grows past the size it was given");
assert_eq!(a.count(), 3);
assert_eq!(read(&a, 0).as_deref(), Some(&b"d"[..]));
assert_eq!(read(&a, 1).as_deref(), Some(&b"e"[..]));
assert_eq!(read(&a, 2).as_deref(), Some(&b"c"[..]));
}
#[test]
fn a_ring_that_changes_size_is_renumbered_oldest_first() {
let mut a = Array::new();
ring(&mut a, 3, &[b"a", b"b", b"c", b"d", b"e"]);
assert_eq!(ring(&mut a, 5, &[b"f"]), 3);
assert_eq!(
(0..4).map(|i| read(&a, i)).collect::<Vec<_>>(),
vec![
Some(b"c".to_vec()),
Some(b"d".to_vec()),
Some(b"e".to_vec()),
Some(b"f".to_vec())
]
);
let mut b = Array::new();
ring(&mut b, 3, &[b"a", b"b", b"c", b"d", b"e"]);
assert_eq!(ring(&mut b, 2, &[b"f"]), 0);
assert_eq!(b.count(), 2);
assert_eq!(read(&b, 0).as_deref(), Some(&b"f"[..]));
assert_eq!(read(&b, 1).as_deref(), Some(&b"e"[..]));
}
#[test]
fn a_hole_cuts_what_a_resize_keeps() {
let mut a = Array::new();
ring(&mut a, 4, &[b"a", b"b", b"c", b"d", b"e"]);
a.del(2);
assert_eq!(ring(&mut a, 8, &[b"f"]), 2);
assert_eq!(
(0..3).map(|i| read(&a, i)).collect::<Vec<_>>(),
vec![
Some(b"d".to_vec()),
Some(b"e".to_vec()),
Some(b"f".to_vec())
]
);
}
#[test]
fn the_last_items_walk_wraps_and_reports_the_holes() {
let mut a = Array::new();
ring(&mut a, 4, &[b"a", b"b", b"c", b"d", b"e"]);
assert_eq!(
last(&a, 3, false),
vec![
Some(b"c".to_vec()),
Some(b"d".to_vec()),
Some(b"e".to_vec())
]
);
assert_eq!(
last(&a, 3, true),
vec![
Some(b"e".to_vec()),
Some(b"d".to_vec()),
Some(b"c".to_vec())
]
);
assert_eq!(last(&a, 99, false).len(), 4);
assert_eq!(last(&a, 0, false), Vec::new());
assert_eq!(last(&Array::new(), 5, false), Vec::new());
let mut b = Array::new();
set(&mut b, 0, b"a");
set(&mut b, 2, b"c");
assert_eq!(last(&b, 5, false), vec![None, Some(b"c".to_vec())]);
}
#[test]
fn a_copy_of_an_array_remembers_the_cursor() {
let mut a = Array::new();
append(&mut a, &[b"x", b"y"]).expect("room");
let mut b = a.clone();
assert_eq!(b.next_index(), Some(2));
assert_eq!(append(&mut b, &[b"z"]).expect("room"), 2);
assert_eq!(a.next_index(), Some(2), "and the two do not share it");
}
fn round_trip(a: &Array) -> Array {
let mut buf = Vec::new();
a.freeze(&mut buf);
let back = Array::thaw(&buf).expect("what freeze wrote");
assert_eq!(back.count(), a.count(), "the population");
assert_eq!(back.len(), a.len(), "the high water mark");
assert_eq!(back.next_index(), a.next_index(), "the insert cursor");
assert_eq!(back.slices.len(), a.slices.len(), "the slice count");
for ((id, was), (back_id, now)) in a.slices.iter().zip(&back.slices) {
assert_eq!(id, back_id, "the slice ids");
assert_eq!(was.count, now.count, "slice {id} holds the same number");
assert_eq!(
matches!(was.layout, Layout::Dense { .. }),
matches!(now.layout, Layout::Dense { .. }),
"slice {id} came back in the layout it left in"
);
}
assert_eq!(
scan(&back, 0, u64::MAX, usize::MAX),
scan(a, 0, u64::MAX, usize::MAX)
);
back
}
#[test]
fn a_frozen_array_comes_back_with_every_value_it_held() {
let mut a = Array::new();
set(&mut a, 0, b"12345");
set(&mut a, 1, b"1.5");
set(&mut a, 2, b"short");
set(
&mut a,
3,
b"a value well past the seven bytes a word can inline",
);
set(&mut a, 9_000_000_000_000, b"a long way up the index space");
let back = round_trip(&a);
assert_eq!(read(&back, 0).as_deref(), Some(&b"12345"[..]));
assert_eq!(read(&back, 1).as_deref(), Some(&b"1.5"[..]));
assert_eq!(read(&back, 2).as_deref(), Some(&b"short"[..]));
assert_eq!(
read(&back, 3).as_deref(),
Some(&b"a value well past the seven bytes a word can inline"[..])
);
assert_eq!(
read(&back, 9_000_000_000_000).as_deref(),
Some(&b"a long way up the index space"[..])
);
assert_eq!(read(&back, 4), None, "and a hole is still a hole");
assert_eq!(back.get(0), Some(Element::Int(12345)), "still an integer");
assert_eq!(back.get(1), Some(Element::Float(1.5)), "still a double");
round_trip(&Array::new());
}
#[test]
fn both_layouts_come_back_in_the_layout_they_left_in() {
let mut dense = Array::new();
for i in 0..=SPARSE_MAX as u64 {
set(&mut dense, i, b"x");
}
assert!(matches!(dense.slices[0].1.layout, Layout::Dense { .. }));
round_trip(&dense);
let mut holed = dense.clone();
for i in 2..5 {
holed.del(i);
}
assert!(matches!(holed.slices[0].1.layout, Layout::Dense { .. }));
assert_eq!(holed.count(), 8);
let back = round_trip(&holed);
assert_eq!(read(&back, 1).as_deref(), Some(&b"x"[..]));
assert_eq!(read(&back, 4), None);
let mut sparse = Array::new();
for i in 0..40 {
set(&mut sparse, i * 100, b"x");
}
assert!(matches!(sparse.slices[0].1.layout, Layout::Sparse { .. }));
round_trip(&sparse);
}
#[test]
fn freezing_an_array_leaves_the_dead_blob_bytes_behind() {
let mut a = Array::new();
let long = vec![b'v'; 200];
for _ in 0..8 {
set(&mut a, 0, &long);
}
assert!(a.dead > 0, "there is dead space to leave behind");
let mut buf = Vec::new();
a.freeze(&mut buf);
let back = Array::thaw(&buf).expect("what freeze wrote");
assert_eq!(back.dead, 0, "a demotion is a compaction");
assert_eq!(back.blob.len(), a.blob.len() - a.dead);
assert_eq!(read(&back, 0).as_deref(), Some(&long[..]));
assert!(
buf.len() < a.blob.len(),
"and the dead bytes never went out"
);
}
#[test]
fn a_frozen_array_keeps_the_insert_cursor() {
let mut a = Array::new();
append(&mut a, &[b"x", b"y", b"z"]).expect("room");
let mut back = round_trip(&a);
assert_eq!(back.next_index(), Some(3));
assert_eq!(append(&mut back, &[b"w"]).expect("room"), 3);
let mut untouched = Array::new();
set(&mut untouched, 99, b"x");
let mut back = round_trip(&untouched);
assert_eq!(back.next_index(), Some(0), "a cursor nothing has moved");
assert_eq!(append(&mut back, &[b"first"]).expect("room"), 0);
}
#[test]
fn a_frozen_array_that_arrives_damaged_is_an_error_and_not_a_panic() {
let mut a = Array::new();
for i in 0..200u64 {
set(
&mut a,
i * 7,
format!("value:{i:04} and enough bytes to reach the blob").as_bytes(),
);
}
let mut buf = Vec::new();
a.freeze(&mut buf);
assert!(Array::thaw(&buf).is_ok(), "the body it wrote reads back");
assert!(Array::thaw(&[]).is_err(), "nothing at all");
assert!(Array::thaw(&[99]).is_err(), "a form nobody wrote");
for cut in 1..buf.len().min(96) {
assert!(Array::thaw(&buf[..cut]).is_err(), "cut at {cut}");
}
for at in 0..buf.len().min(96) {
for bit in 0..8 {
let mut bad = buf.clone();
bad[at] ^= 1 << bit;
let _ = Array::thaw(&bad);
}
}
}
}