#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) enum Val {
#[default]
Absent,
Inline { len: u8, buf: [u8; 23] },
Heap(Vec<u8>),
}
impl Val {
fn store(v: &[u8]) -> Val {
if v.len() <= 23 {
let mut buf = [0u8; 23];
buf[..v.len()].copy_from_slice(v);
Val::Inline { len: v.len() as u8, buf }
} else {
Val::Heap(v.to_vec())
}
}
fn bytes(&self) -> Option<&[u8]> {
match self {
Val::Absent => None,
Val::Inline { len, buf } => Some(&buf[..*len as usize]),
Val::Heap(v) => Some(v),
}
}
fn heap(&self) -> u64 {
match self {
Val::Heap(v) => (v.len().max(1) as u64).next_multiple_of(16) + 16,
_ => 0,
}
}
}
#[derive(Debug)]
pub(crate) struct DocValues {
n: usize,
vals: Vec<Val>,
}
impl DocValues {
pub(crate) fn new(n: usize) -> Self {
Self { n, vals: Vec::new() }
}
pub(crate) fn set(&mut self, id: u32, values: &[Option<&[u8]>]) {
let base = id as usize * self.n;
if self.vals.len() < base + self.n {
self.vals.resize(base + self.n, Val::Absent);
}
for f in 0..self.n {
self.vals[base + f] = match values.get(f).copied().flatten() {
Some(v) => Val::store(v),
None => Val::Absent,
};
}
}
pub(crate) fn clear(&mut self, id: u32) {
self.set(id, &[]);
}
pub(crate) fn get(&self, id: u32, field: usize) -> Option<&[u8]> {
if field >= self.n {
return None;
}
self.vals.get(id as usize * self.n + field)?.bytes()
}
pub(crate) fn arity(&self) -> usize {
self.n
}
pub(crate) fn approx_bytes(&self) -> u64 {
self.vals.capacity() as u64 * std::mem::size_of::<Val>() as u64
+ self.vals.iter().map(Val::heap).sum::<u64>()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_value_slot_is_thirty_two_bytes_either_way() {
assert_eq!(std::mem::size_of::<Val>(), 32);
}
#[test]
fn short_values_stay_inline_long_ones_spill() {
assert!(matches!(Val::store(b"42"), Val::Inline { .. }));
assert!(matches!(Val::store(&[b'x'; 23]), Val::Inline { .. }));
assert!(matches!(Val::store(&[b'x'; 24]), Val::Heap(_)));
assert_eq!(Val::store(b"").bytes(), Some(&b""[..]), "empty is a value");
assert_eq!(Val::Absent.bytes(), None, "absent is not");
}
#[test]
fn set_get_clear_across_the_stride() {
let mut dv = DocValues::new(2);
dv.set(3, &[Some(b"active"), Some(&[b'y'; 40])]);
assert_eq!(dv.get(3, 0), Some(&b"active"[..]));
assert_eq!(dv.get(3, 1), Some(&[b'y'; 40][..]), "long values round-trip");
assert_eq!(dv.get(3, 2), None, "past the declared arity");
assert_eq!(dv.get(0, 0), None, "a document that was never set");
dv.set(3, &[Some(b"gone")]);
assert_eq!(dv.get(3, 1), None, "not carried over from the last write");
dv.clear(3);
assert_eq!(dv.get(3, 0), None);
}
#[test]
fn approx_bytes_counts_the_spill() {
let mut dv = DocValues::new(1);
dv.set(0, &[Some(b"short")]);
let inline_only = dv.approx_bytes();
dv.set(0, &[Some(&[b'z'; 200])]);
assert!(dv.approx_bytes() > inline_only, "a spilled value costs heap");
}
}