use std::borrow::Cow;
use thiserror::Error;
const TIER_SHIFT: u32 = 62;
const PAYLOAD_MASK: u64 = (1u64 << TIER_SHIFT) - 1;
const TIER_SINGLETON: u64 = 0;
const TIER_SMALL: u64 = 1;
const TIER_DENSE: u64 = 2;
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
pub enum PostingsError {
#[error("varint overflows u64 (more than 10 bytes, or a 10th byte with high bits set)")]
VarintOverflow,
#[error("varint runs past the end of the buffer")]
VarintTruncated,
#[error("posting block offset {offset} is out of bounds for a {len}-byte region")]
OffsetOutOfBounds { offset: usize, len: usize },
#[error("local position overflows u32 while decoding delta-encoded postings")]
PositionOverflow,
#[error("dense (Roaring) postings are reserved but not yet implemented")]
ReservedDenseTier,
}
pub fn encode_varint(mut value: u64, out: &mut Vec<u8>) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
pub fn decode_varint(buf: &[u8]) -> Result<(u64, usize), PostingsError> {
let mut value: u64 = 0;
let mut shift: u32 = 0;
for (i, &byte) in buf.iter().enumerate() {
if i == 9 && byte > 0x01 {
return Err(PostingsError::VarintOverflow);
}
value |= u64::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
return Ok((value, i + 1));
}
shift += 7;
if i == 9 {
return Err(PostingsError::VarintOverflow);
}
}
Err(PostingsError::VarintTruncated)
}
pub fn encode_postings(postings: &[u32], region: &mut Vec<u8>) -> u64 {
debug_assert!(
!postings.is_empty(),
"a term always has at least one posting"
);
debug_assert!(
postings.windows(2).all(|w| w[0] < w[1]),
"postings must be strictly ascending"
);
if postings.len() == 1 {
return (TIER_SINGLETON << TIER_SHIFT) | u64::from(postings[0]);
}
let offset = region.len() as u64;
encode_varint(postings.len() as u64, region);
let mut prev = 0u32;
for (i, &pos) in postings.iter().enumerate() {
let delta = if i == 0 { pos } else { pos - prev };
encode_varint(u64::from(delta), region);
prev = pos;
}
(TIER_SMALL << TIER_SHIFT) | offset
}
pub fn decode_postings(value: u64, region: &[u8]) -> Result<Cow<'static, [u32]>, PostingsError> {
let tier = value >> TIER_SHIFT;
let payload = value & PAYLOAD_MASK;
match tier {
TIER_SINGLETON => Ok(Cow::Owned(vec![payload as u32])),
TIER_SMALL => decode_delta_block(payload as usize, region).map(Cow::Owned),
TIER_DENSE => Err(PostingsError::ReservedDenseTier),
_ => unreachable!("tier is two bits, values 0..=3, and 3 is unused"),
}
}
pub fn posting_len(value: u64, region: &[u8]) -> Result<u32, PostingsError> {
let tier = value >> TIER_SHIFT;
let payload = value & PAYLOAD_MASK;
match tier {
TIER_SINGLETON => Ok(1),
TIER_SMALL => {
let offset = payload as usize;
let cursor = region
.get(offset..)
.ok_or(PostingsError::OffsetOutOfBounds {
offset,
len: region.len(),
})?;
let (count, _) = decode_varint(cursor)?;
u32::try_from(count).map_err(|_| PostingsError::PositionOverflow)
}
TIER_DENSE => Err(PostingsError::ReservedDenseTier),
_ => unreachable!("tier is two bits, values 0..=3, and 3 is unused"),
}
}
fn decode_delta_block(offset: usize, region: &[u8]) -> Result<Vec<u32>, PostingsError> {
let mut cursor = region
.get(offset..)
.ok_or(PostingsError::OffsetOutOfBounds {
offset,
len: region.len(),
})?;
let (count, consumed) = decode_varint(cursor)?;
cursor = &cursor[consumed..];
let mut out = Vec::with_capacity(count as usize);
let mut pos: u32 = 0;
for i in 0..count {
let (delta, consumed) = decode_varint(cursor)?;
cursor = &cursor[consumed..];
let delta = u32::try_from(delta).map_err(|_| PostingsError::PositionOverflow)?;
pos = if i == 0 {
delta
} else {
pos.checked_add(delta)
.ok_or(PostingsError::PositionOverflow)?
};
out.push(pos);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varint_round_trips_boundary_values() {
for value in [
0u64,
1,
127,
128,
255,
300,
16_383,
16_384,
u32::MAX as u64,
u64::MAX / 2,
u64::MAX - 1,
u64::MAX,
] {
let mut buf = Vec::new();
encode_varint(value, &mut buf);
let (decoded, consumed) = decode_varint(&buf).unwrap();
assert_eq!(decoded, value, "round trip for {value}");
assert_eq!(consumed, buf.len(), "consumed all bytes for {value}");
}
}
#[test]
fn varint_consumes_only_its_own_bytes() {
let mut buf = Vec::new();
encode_varint(300, &mut buf);
buf.extend_from_slice(&[0xAA, 0xBB]); let (decoded, consumed) = decode_varint(&buf).unwrap();
assert_eq!(decoded, 300);
assert_eq!(consumed, 2); }
#[test]
fn varint_truncated_is_an_error() {
assert_eq!(decode_varint(&[0x80]), Err(PostingsError::VarintTruncated));
assert_eq!(decode_varint(&[]), Err(PostingsError::VarintTruncated));
}
#[test]
fn varint_overflow_is_an_error() {
let overflow = [0x80u8; 11];
assert_eq!(decode_varint(&overflow), Err(PostingsError::VarintOverflow));
let mut ten = vec![0x80u8; 9];
ten.push(0x02);
assert_eq!(decode_varint(&ten), Err(PostingsError::VarintOverflow));
let mut max = Vec::new();
encode_varint(u64::MAX, &mut max);
assert_eq!(max.len(), 10);
assert_eq!(decode_varint(&max).unwrap().0, u64::MAX);
}
#[test]
fn singleton_inlines_and_touches_no_region() {
let mut region = Vec::new();
let value = encode_postings(&[42], &mut region);
assert!(region.is_empty(), "singleton must not write to the region");
assert_eq!(value >> TIER_SHIFT, TIER_SINGLETON);
assert_eq!(
decode_postings(value, ®ion).unwrap().into_owned(),
vec![42]
);
}
#[test]
fn small_term_round_trips_as_deltas() {
let mut region = Vec::new();
let postings = [0u32, 1, 5, 6, 1000, 1_000_000];
let value = encode_postings(&postings, &mut region);
assert_eq!(value >> TIER_SHIFT, TIER_SMALL);
assert!(!region.is_empty());
assert_eq!(
decode_postings(value, ®ion).unwrap().into_owned(),
postings.to_vec()
);
}
#[test]
fn multiple_terms_share_one_region() {
let mut region = Vec::new();
let a = encode_postings(&[2, 4, 6], &mut region);
let b = encode_postings(&[1, 3], &mut region);
let c = encode_postings(&[9], &mut region); assert_eq!(
decode_postings(a, ®ion).unwrap().into_owned(),
vec![2, 4, 6]
);
assert_eq!(
decode_postings(b, ®ion).unwrap().into_owned(),
vec![1, 3]
);
assert_eq!(decode_postings(c, ®ion).unwrap().into_owned(), vec![9]);
}
#[test]
fn posting_len_matches_the_decoded_length_without_walking_it() {
let mut region = Vec::new();
let one = encode_postings(&[42], &mut region);
assert_eq!(posting_len(one, ®ion).unwrap(), 1);
let postings = [0u32, 1, 5, 6, 1000, 1_000_000];
let small = encode_postings(&postings, &mut region);
assert_eq!(posting_len(small, ®ion).unwrap(), postings.len() as u32);
assert_eq!(
posting_len(small, ®ion).unwrap() as usize,
decode_postings(small, ®ion).unwrap().len()
);
assert_eq!(
posting_len(TIER_DENSE << TIER_SHIFT, ®ion),
Err(PostingsError::ReservedDenseTier)
);
}
#[test]
fn dense_tier_is_a_named_error() {
let value = TIER_DENSE << TIER_SHIFT;
assert_eq!(
decode_postings(value, &[]),
Err(PostingsError::ReservedDenseTier)
);
}
#[test]
fn out_of_bounds_offset_is_an_error() {
let value = (TIER_SMALL << TIER_SHIFT) | 100;
assert_eq!(
decode_postings(value, &[0x01, 0x00]),
Err(PostingsError::OffsetOutOfBounds {
offset: 100,
len: 2
})
);
}
}