use crate::docblobs::{Channel, DocBlobs, next_varint, put_varint};
fn encode(offsets: &[u32]) -> Vec<u8> {
let mut out = Vec::new();
let mut prev = 0u32;
for &p in offsets {
put_varint(&mut out, p - prev);
prev = p;
}
out
}
pub(crate) fn walk(blob: &[u8]) -> impl Iterator<Item = u32> + '_ {
let mut at = 0usize;
let mut acc = 0u32;
core::iter::from_fn(move || {
let delta = next_varint(blob, &mut at)?;
acc = acc.checked_add(delta)?;
Some(acc)
})
}
fn decode(blob: &[u8]) -> Vec<u32> {
walk(blob).collect()
}
#[derive(Debug, Default)]
pub(crate) struct Positions {
chan: Channel,
}
impl Positions {
pub(crate) fn set(&mut self, token: &[u8], id: u32, offsets: &[u32]) {
self.chan.set(token, id, encode(offsets));
}
pub(crate) fn remove(&mut self, token: &[u8], id: u32) {
self.chan.remove(token, id);
}
pub(crate) fn blob(&self, token: &[u8], id: u32) -> Option<&[u8]> {
self.chan.get(token)?.get(id)
}
pub(crate) fn get(&self, token: &[u8], id: u32) -> Option<Vec<u32>> {
self.chan.get(token)?.get(id).map(decode)
}
pub(crate) fn ids(&self, token: &[u8]) -> Vec<u32> {
self.chan.get(token).map(DocBlobs::ids).unwrap_or_default()
}
pub(crate) fn approx_bytes(&self) -> u64 {
self.chan.bytes()
}
#[cfg(test)]
pub(crate) fn recompute_bytes(&self) -> u64 {
self.chan.recompute()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varint_delta_roundtrips() {
for case in [
vec![],
vec![0u32],
vec![0, 1, 2, 3],
vec![5, 130, 131, 16_500, 2_000_000],
vec![0, 127, 128, 129, 16_383, 16_384],
] {
assert_eq!(decode(&encode(&case)), case, "roundtrip {case:?}");
}
}
#[test]
fn set_get_remove() {
let mut p = Positions::default();
p.set(b"a", 1, &[0, 5, 9]);
p.set(b"a", 2, &[3]);
p.set(b"b", 1, &[1]);
assert_eq!(p.get(b"a", 1), Some(vec![0, 5, 9]));
assert_eq!(p.get(b"a", 2), Some(vec![3]));
assert_eq!(p.get(b"a", 3), None);
let mut ids = p.ids(b"a");
ids.sort_unstable();
assert_eq!(ids, vec![1, 2]);
p.remove(b"a", 1);
assert_eq!(p.get(b"a", 1), None);
assert_eq!(p.get(b"a", 2), Some(vec![3]));
p.remove(b"b", 1);
assert_eq!(p.ids(b"b"), Vec::<u32>::new(), "empty token dropped");
}
#[test]
fn a_delta_past_the_end_of_the_offset_space_ends_the_walk() {
let mut blob = Vec::new();
put_varint(&mut blob, u32::MAX);
put_varint(&mut blob, 1);
assert_eq!(walk(&blob).collect::<Vec<_>>(), vec![u32::MAX]);
}
#[test]
fn walk_returns_exactly_what_encode_was_given() {
for case in [vec![], vec![0u32], vec![0, 1, 2], vec![7, 4_000_000_000, u32::MAX]] {
let blob = encode(&case);
assert_eq!(walk(&blob).collect::<Vec<_>>(), case, "roundtrip {case:?}");
}
}
#[test]
fn approx_bytes_grows_with_content() {
let mut p = Positions::default();
let empty = p.approx_bytes();
p.set(b"token", 1, &[0, 1, 2, 3, 4, 5, 6, 7]);
assert!(p.approx_bytes() > empty, "storing positions costs bytes");
}
}