use core::cmp::Ordering;
use core::ops::Bound;
use yo_common::num::i64_digits;
use yo_common::small::Small;
use yo_common::{Code, Error, Result};
use yo_kv::{Elements, Rank, Set, SetLimits, Slab, rank};
use crate::head::Kind;
use crate::read::Value;
pub const KEY_MAX: usize = yo_kv::NAME_MAX - 1;
const KEY_INLINE: usize = 32;
const TAG_NULL: u8 = 0;
const TAG_FALSE: u8 = 1;
const TAG_TRUE: u8 = 2;
const TAG_NUM: u8 = 3;
const TAG_TEXT: u8 = 4;
#[derive(Clone)]
pub struct Key(Small<u8, KEY_INLINE>);
impl Key {
#[must_use]
pub fn null() -> Key {
Key(Small::collect([TAG_NULL]))
}
#[must_use]
pub fn bool(v: bool) -> Key {
Key(Small::collect([if v { TAG_TRUE } else { TAG_FALSE }]))
}
#[must_use]
pub fn int(v: i64) -> Key {
let (neg, mant) = if v < 0 {
(true, v.unsigned_abs())
} else {
(false, v as u64)
};
number(Class::of(neg, mant == 0), mant, i32::from(bits(mant)))
}
#[must_use]
pub fn float(v: f64) -> Key {
if v.is_nan() {
return number(Class::Nan, 0, 0);
}
if v.is_infinite() {
return number(
if v.is_sign_negative() {
Class::NegInf
} else {
Class::PosInf
},
0,
0,
);
}
let raw = v.to_bits();
let neg = raw >> 63 == 1;
let exponent = ((raw >> 52) & 0x7ff) as i32;
let fraction = raw & ((1 << 52) - 1);
let (mant, scale) = if exponent == 0 {
(fraction, -1074)
} else {
(fraction | (1 << 52), exponent - 1075)
};
number(
Class::of(neg, mant == 0),
mant,
scale + i32::from(bits(mant)),
)
}
#[must_use]
pub fn text(v: &str) -> Key {
Key::text_bytes(v.as_bytes())
}
#[must_use]
pub fn text_bytes(v: &[u8]) -> Key {
let mut k = Small::collect([TAG_TEXT]);
k.extend_from_slice(v);
Key(k)
}
#[must_use]
pub fn word(v: &str) -> Option<Key> {
let mut rest = v.as_bytes();
let word = next_word(&mut rest)?;
if next_word(&mut rest).is_some() {
return None;
}
Some(fold(word))
}
#[must_use]
pub fn of(v: Value<'_>) -> Option<Key> {
match v.kind() {
Kind::Null => Some(Key::null()),
Kind::Bool => Some(Key::bool(v.as_bool()?)),
Kind::Int => Some(Key::int(v.as_int()?)),
Kind::Float => Some(Key::float(v.as_float()?)),
Kind::Text => Some(Key::text_bytes(v.text_bytes()?)),
Kind::Array | Kind::Object => None,
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
#[must_use]
pub fn is_too_long(&self) -> bool {
self.as_bytes().len() > KEY_MAX
}
}
impl PartialEq for Key {
fn eq(&self, other: &Key) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl Eq for Key {}
impl core::fmt::Debug for Key {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let b = self.as_bytes();
match b.first() {
Some(&TAG_NULL) => f.write_str("null"),
Some(&TAG_FALSE) => f.write_str("false"),
Some(&TAG_TRUE) => f.write_str("true"),
Some(&TAG_TEXT) => write!(f, "{:?}", String::from_utf8_lossy(&b[1..])),
Some(&TAG_NUM) => write!(f, "{}", Hex(&b[1..])),
_ => f.write_str("<no key>"),
}
}
}
struct Hex<'a>(&'a [u8]);
impl core::fmt::Display for Hex<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for b in self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Class {
NegInf = 0,
Negative = 1,
Zero = 2,
Positive = 3,
PosInf = 4,
Nan = 5,
}
impl Class {
fn of(neg: bool, zero: bool) -> Class {
match (zero, neg) {
(true, _) => Class::Zero,
(false, true) => Class::Negative,
(false, false) => Class::Positive,
}
}
}
fn bits(mant: u64) -> u16 {
(64 - mant.leading_zeros()) as u16
}
fn number(class: Class, mant: u64, place: i32) -> Key {
let (place, mant) = match class {
Class::Negative | Class::Positive => (place, mant << mant.leading_zeros()),
_ => (0, 0),
};
let place = ((place + 32768) as u16).to_be_bytes();
let flip = if class == Class::Negative { 0xff } else { 0 };
let mut k = [TAG_NUM, class as u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
k[2..4].copy_from_slice(&place);
k[4..].copy_from_slice(&mant.to_be_bytes());
for b in &mut k[2..] {
*b ^= flip;
}
Key(Small::from_slice(&k))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexKind {
Equality,
Ordered,
Array,
Text,
}
impl IndexKind {
#[must_use]
pub fn is_ordered(self) -> bool {
self == IndexKind::Ordered
}
#[must_use]
pub fn is_multi(self) -> bool {
matches!(self, IndexKind::Array | IndexKind::Text)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct TooLong;
pub(crate) fn keys_at(
kind: IndexKind,
at: Value<'_>,
out: &mut Vec<u8>,
) -> core::result::Result<(), TooLong> {
match kind {
IndexKind::Equality | IndexKind::Ordered => {
if let Some(key) = Key::of(at) {
push_key(&key, out)?;
}
}
IndexKind::Array => match at.kind() {
Kind::Array => {
for elem in at.iter() {
if let Some(key) = Key::of(elem) {
push_key(&key, out)?;
}
}
}
Kind::Object => {}
_ => {
if let Some(key) = Key::of(at) {
push_key(&key, out)?;
}
}
},
IndexKind::Text => {
if let Some(text) = at.text_bytes() {
let mut rest = text;
while let Some(word) = next_word(&mut rest) {
push_key(&fold(word), out)?;
}
}
}
}
Ok(())
}
fn push_key(key: &Key, out: &mut Vec<u8>) -> core::result::Result<(), TooLong> {
let bytes = key.as_bytes();
if key.is_too_long() {
return Err(TooLong);
}
let n = bytes.len() as u16;
out.extend_from_slice(&n.to_le_bytes());
out.extend_from_slice(bytes);
Ok(())
}
pub(crate) fn each_key(mut list: &[u8], mut f: impl FnMut(&[u8])) {
while list.len() >= 2 {
let n = usize::from(u16::from_le_bytes([list[0], list[1]]));
let Some(key) = list.get(2..2 + n) else {
return;
};
f(key);
list = &list[2 + n..];
}
}
fn fold(word: &[u8]) -> Key {
Key(Small::collect(
core::iter::once(TAG_TEXT).chain(word.iter().map(u8::to_ascii_lowercase)),
))
}
fn next_word<'a>(rest: &mut &'a [u8]) -> Option<&'a [u8]> {
let start = rest.iter().position(|b| b.is_ascii_alphanumeric())?;
let after = rest[start..]
.iter()
.position(|b| !b.is_ascii_alphanumeric())
.map_or(rest.len(), |n| start + n);
let word = &rest[start..after];
*rest = &rest[after..];
Some(word)
}
#[derive(Debug)]
pub struct PathIndex {
path: Box<[u8]>,
kind: IndexKind,
keys: Elements<u32>,
order: Option<Rank>,
posts: Slab<Set>,
postings: usize,
}
impl PathIndex {
pub(crate) fn new(path: &[u8], kind: IndexKind) -> PathIndex {
PathIndex {
path: path.into(),
kind,
keys: Elements::new(),
order: kind.is_ordered().then(Rank::new),
posts: Slab::new(),
postings: 0,
}
}
#[must_use]
pub fn path(&self) -> &[u8] {
&self.path
}
#[must_use]
pub fn kind(&self) -> IndexKind {
self.kind
}
pub(crate) fn keys_at(
&self,
at: Value<'_>,
out: &mut Vec<u8>,
) -> core::result::Result<(), TooLong> {
keys_at(self.kind, at, out)
}
#[must_use]
pub fn len(&self) -> usize {
self.keys.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
#[must_use]
pub fn postings(&self) -> usize {
self.postings
}
#[must_use]
pub fn get(&self, key: &Key) -> Option<&Set> {
self.posts.get(*self.keys.get(key.as_bytes())?)
}
#[must_use]
pub fn count(&self, key: &Key) -> usize {
self.get(key).map_or(0, Set::len)
}
#[must_use]
pub fn range(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> Ranged<'_> {
let Some((order, start, left)) = self.span(lo, hi) else {
return Ranged {
index: self,
walk: None,
left: 0,
};
};
Ranged {
index: self,
walk: Some(order.iter_from(start)),
left,
}
}
#[must_use]
pub fn range_rev(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> RangedRev<'_> {
let Some((order, start, left)) = self.span(lo, hi) else {
return RangedRev {
index: self,
walk: None,
left: 0,
};
};
RangedRev {
index: self,
walk: Some(order.iter_back_from(start + left - 1)),
left,
}
}
#[must_use]
pub fn count_in(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> usize {
self.range(lo, hi).map(|(_, set)| set.len()).sum()
}
fn span(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> Option<(&Rank, usize, usize)> {
let order = self.order.as_ref()?;
let keys = &self.keys;
let start = match lo {
Bound::Unbounded => 0,
Bound::Included(k) => rank_of(order, keys, k.as_bytes()),
Bound::Excluded(k) => rank_after(order, keys, k.as_bytes()),
};
let end = match hi {
Bound::Unbounded => keys.len(),
Bound::Included(k) => rank_after(order, keys, k.as_bytes()),
Bound::Excluded(k) => rank_of(order, keys, k.as_bytes()),
};
if end <= start {
return None;
}
Some((order, start, end - start))
}
pub(crate) fn add(&mut self, key: &[u8], id: &[u8]) -> Result<()> {
if let Some(&slot) = self.keys.get(key) {
let set = self.posts.get_mut(slot).expect("a row points at its list");
if set.add(id, &SetLimits::DEFAULT) {
self.postings += 1;
}
return Ok(());
}
let mut set = Set::new();
set.add(id, &SetLimits::DEFAULT);
let slot = self.posts.insert(set);
let row = self.keys.len() as u32;
if self.keys.insert(key, slot).is_err() {
self.posts.remove(slot);
return Err(Error::new(
Code::Full,
"the index cannot hold another distinct value",
));
}
let PathIndex { keys, order, .. } = self;
if let Some(order) = order {
let at = rank_of(order, keys, key);
order.insert_at(at, row);
}
self.postings += 1;
Ok(())
}
pub(crate) fn take(&mut self, key: &[u8], id: &[u8]) {
let Some(row) = self.keys.index_of(key) else {
return;
};
let slot = *self.keys.at(row).expect("a row that was just found").1;
let set = self.posts.get_mut(slot).expect("a row points at its list");
if !set.remove(id) {
return;
}
self.postings -= 1;
if !set.is_empty() {
return;
}
self.posts.remove(slot);
self.untrack(key, row);
self.keys.remove_at(row);
}
fn untrack(&mut self, key: &[u8], row: usize) {
let PathIndex { keys, order, .. } = self;
let Some(order) = order else {
return;
};
let rank = rank_of(order, keys, key);
let last = keys.len() - 1;
let moved = if last == row {
None
} else {
let name = keys.at(last).expect("the last row").0;
Some(order.seek(|other| {
let (other_name, _) = keys.at(other as usize).expect("a row the tree holds");
name.cmp(other_name)
}))
};
order.remove_at(rank);
if let Some(at) = moved {
let at = if at > rank { at - 1 } else { at };
order.set_at(at, row as u32);
}
}
pub(crate) fn clear(&mut self) {
self.keys.clear();
self.posts.clear();
self.postings = 0;
if let Some(order) = &mut self.order {
*order = Rank::new();
}
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.keys.memory_bytes()
+ self.posts.slot_bytes()
+ self.posts.iter().map(Set::memory_bytes).sum::<usize>()
+ self.order.as_ref().map_or(0, Rank::bytes)
}
}
pub struct Ranged<'a> {
index: &'a PathIndex,
walk: Option<rank::Walk<'a>>,
left: usize,
}
impl<'a> Iterator for Ranged<'a> {
type Item = (&'a [u8], &'a Set);
fn next(&mut self) -> Option<(&'a [u8], &'a Set)> {
if self.left == 0 {
return None;
}
let row = self.walk.as_mut()?.next()?;
self.left -= 1;
entry(self.index, row)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.left, Some(self.left))
}
}
impl ExactSizeIterator for Ranged<'_> {}
pub struct RangedRev<'a> {
index: &'a PathIndex,
walk: Option<rank::Back<'a>>,
left: usize,
}
impl<'a> Iterator for RangedRev<'a> {
type Item = (&'a [u8], &'a Set);
fn next(&mut self) -> Option<(&'a [u8], &'a Set)> {
if self.left == 0 {
return None;
}
let row = self.walk.as_mut()?.next()?;
self.left -= 1;
entry(self.index, row)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.left, Some(self.left))
}
}
impl ExactSizeIterator for RangedRev<'_> {}
fn rank_of(order: &Rank, keys: &Elements<u32>, key: &[u8]) -> usize {
order.seek(|row| {
let (name, _) = keys.at(row as usize).expect("a row the tree holds");
key.cmp(name)
})
}
fn rank_after(order: &Rank, keys: &Elements<u32>, key: &[u8]) -> usize {
order.seek(|row| {
let (name, _) = keys.at(row as usize).expect("a row the tree holds");
match key.cmp(name) {
Ordering::Less => Ordering::Less,
Ordering::Equal | Ordering::Greater => Ordering::Greater,
}
})
}
fn entry(index: &PathIndex, row: u32) -> Option<(&[u8], &Set)> {
let (name, &slot) = index.keys.at(row as usize)?;
Some((name, index.posts.get(slot)?))
}
pub(crate) fn each_id(set: &Set, mut f: impl FnMut(&[u8])) -> usize {
let mut digits = [0u8; yo_common::num::DIGITS_MAX];
let mut n = 0usize;
for member in set.iter() {
match member {
yo_kv::listpack::Entry::Str(s) => f(s),
yo_kv::listpack::Entry::Int(v) => f(i64_digits(&mut digits, v)),
}
n += 1;
}
n
}
#[cfg(test)]
mod tests {
use super::*;
fn taken(kind: IndexKind, build: impl FnOnce(&mut crate::Builder)) -> Vec<String> {
let mut b = crate::Builder::new();
build(&mut b);
let bytes = b.finish().expect("built").to_vec();
let value = Value::new(&bytes).expect("readable");
let mut list = Vec::new();
keys_at(kind, value, &mut list).expect("short enough");
let mut out = Vec::new();
each_key(&list, |key| {
out.push(format!("{:?}", Key(Small::collect(key.iter().copied()))))
});
out
}
#[test]
fn an_array_index_takes_one_key_per_element() {
let keys = taken(IndexKind::Array, |b| {
b.begin_array().expect("open");
b.text("red").expect("value");
b.int(7).expect("value");
b.begin_object().expect("open");
b.end_object().expect("close");
b.end_array().expect("close");
});
assert_eq!(keys.len(), 2, "the object inside is not a key: {keys:?}");
assert_eq!(keys[0], "\"red\"");
assert_eq!(
taken(IndexKind::Array, |b| b.text("red").expect("v")).len(),
1
);
assert_eq!(
taken(IndexKind::Array, |b| {
b.begin_object().expect("open");
b.end_object().expect("close");
})
.len(),
0
);
}
#[test]
fn a_text_index_splits_on_everything_that_is_not_a_letter_or_a_digit() {
let keys = taken(IndexKind::Text, |b| {
b.text(" The RED car, model 3! ").expect("value")
});
assert_eq!(
keys,
["\"the\"", "\"red\"", "\"car\"", "\"model\"", "\"3\""]
);
assert!(taken(IndexKind::Text, |b| b.text("!!! ...").expect("v")).is_empty());
assert!(taken(IndexKind::Text, |b| b.int(7).expect("v")).is_empty());
}
#[test]
fn a_word_key_is_what_a_text_index_filed_and_a_phrase_is_not_one() {
assert_eq!(Key::word("RED"), Key::word("red"));
assert_eq!(Key::word("red!"), Key::word("red"));
assert!(Key::word("red car").is_none(), "a phrase is two words");
assert!(Key::word("").is_none());
assert!(Key::word("!!!").is_none());
assert_eq!(
Key::word("red").expect("a word"),
Key::text("red"),
"a word that needs no folding is the string key, and there is no \
second text tag to keep them apart"
);
assert_ne!(Key::word("RED").expect("a word"), Key::text("RED"));
}
#[test]
fn a_key_list_reads_back_exactly_what_went_into_it() {
let mut list = Vec::new();
push_key(&Key::text("red"), &mut list).expect("short");
push_key(&Key::int(7), &mut list).expect("short");
push_key(&Key::null(), &mut list).expect("short");
let mut out = Vec::new();
each_key(&list, |key| out.push(key.to_vec()));
assert_eq!(
out,
[
Key::text("red").as_bytes().to_vec(),
Key::int(7).as_bytes().to_vec(),
Key::null().as_bytes().to_vec(),
]
);
let long = "x".repeat(KEY_MAX);
assert!(push_key(&Key::text(&long), &mut list).is_err());
}
#[test]
fn a_number_and_the_string_of_it_are_different_keys() {
assert_ne!(Key::int(7), Key::text("7"));
assert_ne!(Key::null(), Key::text(""));
assert_ne!(Key::bool(true), Key::int(1));
}
#[test]
fn a_float_that_names_a_whole_number_is_that_number() {
assert_eq!(Key::float(7.0), Key::int(7));
assert_eq!(Key::float(-0.0), Key::int(0));
assert_eq!(Key::float(-3.0), Key::int(-3));
assert_ne!(Key::float(7.5), Key::int(7));
assert_ne!(Key::float(1e30), Key::int(i64::MAX));
assert_ne!(Key::float(f64::NAN), Key::float(0.0));
}
#[test]
fn numbers_sort_as_bytes_the_way_they_sort_as_numbers() {
let mut ns = [0i64, -1, i64::MIN, i64::MAX, 7, -7, 1 << 40];
let mut keys: Vec<Key> = ns.iter().map(|&n| Key::int(n)).collect();
ns.sort_unstable();
keys.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
let want: Vec<Key> = ns.iter().map(|&n| Key::int(n)).collect();
assert_eq!(keys, want);
let mut fs = [0.5f64, -0.5, -1.5, 1e300, -1e300, f64::MIN_POSITIVE];
let mut keys: Vec<Key> = fs.iter().map(|&f| Key::float(f)).collect();
fs.sort_by(f64::total_cmp);
keys.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
let want: Vec<Key> = fs.iter().map(|&f| Key::float(f)).collect();
assert_eq!(keys, want);
}
#[test]
fn an_integer_and_a_float_sort_among_each_other() {
let mut mixed: Vec<Key> = [
Key::float(12.5),
Key::int(99),
Key::int(-3),
Key::float(-2.5),
Key::int(0),
Key::float(0.25),
Key::int(13),
]
.to_vec();
mixed.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
let want = [
Key::int(-3),
Key::float(-2.5),
Key::int(0),
Key::float(0.25),
Key::float(12.5),
Key::int(13),
Key::int(99),
];
assert_eq!(mixed, want);
}
#[test]
fn seven_and_seven_point_zero_are_one_key() {
assert_eq!(Key::int(7), Key::float(7.0));
assert_eq!(Key::int(-7), Key::float(-7.0));
assert_eq!(Key::int(0), Key::float(0.0));
assert_eq!(Key::int(0), Key::float(-0.0));
assert_eq!(Key::int(1 << 53), Key::float((1u64 << 53) as f64));
assert_ne!(Key::int(i64::MAX), Key::float(i64::MAX as f64));
}
#[test]
fn the_ends_of_the_number_line_sort_where_they_belong() {
let mut ends = [
Key::float(f64::NAN),
Key::float(f64::INFINITY),
Key::int(1),
Key::float(f64::NEG_INFINITY),
Key::int(-1),
Key::float(f64::MIN),
Key::float(f64::MAX),
]
.to_vec();
ends.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
let want = [
Key::float(f64::NEG_INFINITY),
Key::float(f64::MIN),
Key::int(-1),
Key::int(1),
Key::float(f64::MAX),
Key::float(f64::INFINITY),
Key::float(f64::NAN),
];
assert_eq!(ends, want);
}
#[test]
fn every_number_is_the_same_width() {
for k in [
Key::int(0),
Key::int(i64::MIN),
Key::float(1e300),
Key::float(f64::MIN_POSITIVE),
Key::float(f64::NAN),
Key::float(f64::NEG_INFINITY),
] {
assert_eq!(k.as_bytes().len(), 12, "{k:?}");
}
}
#[test]
fn a_short_key_stays_off_the_heap() {
assert!(Key::int(i64::MIN).0.is_inline());
assert!(Key::text("a-fairly-ordinary-status").0.is_inline());
assert!(!Key::text(&"x".repeat(64)).0.is_inline());
}
#[test]
fn a_key_prints_as_what_it_is() {
assert_eq!(format!("{:?}", Key::null()), "null");
assert_eq!(format!("{:?}", Key::bool(true)), "true");
assert_eq!(format!("{:?}", Key::text("open")), "\"open\"");
assert_eq!(format!("{:?}", Key::int(0)), "0280000000000000000000");
}
fn ordered(ns: impl IntoIterator<Item = i64>) -> PathIndex {
let mut index = PathIndex::new(b"$.n", IndexKind::Ordered);
for n in ns {
index
.add(Key::int(n).as_bytes(), n.to_string().as_bytes())
.expect("room");
}
index
}
fn walked(index: &PathIndex, lo: Bound<&Key>, hi: Bound<&Key>) -> Vec<i64> {
let out: Vec<i64> = index.range(lo, hi).map(|(k, _)| unorder_int(k)).collect();
let mut back: Vec<i64> = index
.range_rev(lo, hi)
.map(|(k, _)| unorder_int(k))
.collect();
back.reverse();
assert_eq!(out, back, "backwards is forwards read the other way");
out
}
fn unorder_int(key: &[u8]) -> i64 {
assert_eq!(key[0], TAG_NUM, "these tests only file numbers");
let class = key[1];
if class == Class::Zero as u8 {
return 0;
}
let flip = if class == Class::Negative as u8 {
0xffu8
} else {
0
};
let place = u16::from_be_bytes([key[2] ^ flip, key[3] ^ flip]) as i32 - 32768;
let mut mant = [0u8; 8];
for (out, b) in mant.iter_mut().zip(&key[4..12]) {
*out = b ^ flip;
}
let n = (u64::from_be_bytes(mant) >> (64 - place)) as i64;
if flip == 0 { n } else { -n }
}
#[test]
fn an_ordered_index_walks_its_keys_in_order() {
let index = ordered((0..500i64).map(|i| (i * 137) % 500 - 250));
assert_eq!(index.len(), 500);
assert_eq!(index.kind(), IndexKind::Ordered);
let all = walked(&index, Bound::Unbounded, Bound::Unbounded);
assert_eq!(all, (-250..250).collect::<Vec<i64>>());
let (lo, hi) = (Key::int(-3), Key::int(4));
assert_eq!(
walked(&index, Bound::Included(&lo), Bound::Excluded(&hi)),
[-3, -2, -1, 0, 1, 2, 3]
);
assert_eq!(
walked(&index, Bound::Excluded(&lo), Bound::Included(&hi)),
[-2, -1, 0, 1, 2, 3, 4]
);
assert_eq!(
walked(&index, Bound::Unbounded, Bound::Excluded(&Key::int(-247))),
[-250, -249, -248]
);
assert_eq!(
walked(&index, Bound::Included(&Key::int(247)), Bound::Unbounded),
[247, 248, 249]
);
}
#[test]
fn a_range_that_names_nothing_is_empty_rather_than_wrong() {
let index = ordered([10i64, 20, 30]);
let (lo, hi) = (Key::int(20), Key::int(20));
assert!(walked(&index, Bound::Excluded(&lo), Bound::Excluded(&hi)).is_empty());
assert_eq!(
walked(&index, Bound::Included(&lo), Bound::Included(&hi)),
[20]
);
assert!(
walked(
&index,
Bound::Included(&Key::int(30)),
Bound::Excluded(&Key::int(10))
)
.is_empty()
);
assert!(
walked(
&index,
Bound::Included(&Key::int(21)),
Bound::Excluded(&Key::int(29))
)
.is_empty()
);
assert!(walked(&index, Bound::Included(&Key::int(31)), Bound::Unbounded).is_empty());
assert!(walked(&index, Bound::Unbounded, Bound::Excluded(&Key::int(10))).is_empty());
assert_eq!(index.count_in(Bound::Unbounded, Bound::Unbounded), 3);
}
#[test]
fn an_equality_index_has_no_range_and_says_so_by_being_empty() {
let mut index = PathIndex::new(b"$.n", IndexKind::Equality);
index.add(Key::int(1).as_bytes(), b"a").expect("room");
assert_eq!(index.kind(), IndexKind::Equality);
assert_eq!(index.range(Bound::Unbounded, Bound::Unbounded).count(), 0);
assert_eq!(index.count_in(Bound::Unbounded, Bound::Unbounded), 0);
assert_eq!(index.count(&Key::int(1)), 1, "equality still works");
}
#[test]
fn removing_keys_from_an_ordered_index_keeps_the_rest_in_order() {
let mut index = ordered(0..200i64);
for n in (0..200i64).step_by(3) {
index.take(Key::int(n).as_bytes(), n.to_string().as_bytes());
}
let left: Vec<i64> = (0..200i64).filter(|n| n % 3 != 0).collect();
assert_eq!(index.len(), left.len());
assert_eq!(walked(&index, Bound::Unbounded, Bound::Unbounded), left);
for n in &left {
assert_eq!(index.count(&Key::int(*n)), 1, "{n} lost its list");
}
for n in (0..200i64).step_by(3) {
assert_eq!(index.count(&Key::int(n)), 0, "{n} kept one");
}
}
#[test]
fn an_ordered_index_that_is_emptied_and_refilled_is_still_ordered() {
let mut index = ordered(0..64i64);
for n in 0..64i64 {
index.take(Key::int(n).as_bytes(), n.to_string().as_bytes());
}
assert!(index.is_empty());
assert_eq!(index.postings(), 0);
assert!(walked(&index, Bound::Unbounded, Bound::Unbounded).is_empty());
for n in (0..32i64).rev() {
index
.add(Key::int(n).as_bytes(), n.to_string().as_bytes())
.expect("room");
}
assert_eq!(
walked(&index, Bound::Unbounded, Bound::Unbounded),
(0..32).collect::<Vec<i64>>()
);
index.clear();
assert_eq!(index.kind(), IndexKind::Ordered, "a clear keeps the kind");
assert!(index.is_empty());
index.add(Key::int(9).as_bytes(), b"9").expect("room");
assert_eq!(walked(&index, Bound::Unbounded, Bound::Unbounded), [9]);
}
#[test]
fn a_key_with_many_documents_counts_once_in_the_order() {
let mut index = PathIndex::new(b"$.n", IndexKind::Ordered);
for i in 0..100 {
index
.add(
Key::int(i64::from(i % 5)).as_bytes(),
format!("d{i}").as_bytes(),
)
.expect("room");
}
assert_eq!(index.len(), 5, "five distinct values");
assert_eq!(index.postings(), 100);
assert_eq!(
walked(&index, Bound::Unbounded, Bound::Unbounded),
[0, 1, 2, 3, 4]
);
assert_eq!(index.count_in(Bound::Unbounded, Bound::Unbounded), 100);
assert_eq!(
index.count_in(Bound::Included(&Key::int(1)), Bound::Included(&Key::int(2))),
40
);
}
#[test]
fn the_last_document_under_a_key_takes_the_key_with_it() {
let mut index = PathIndex::new(b"$.status", IndexKind::Equality);
let open = Key::text("open");
index.add(open.as_bytes(), b"a").expect("room");
index.add(open.as_bytes(), b"b").expect("room");
assert_eq!(index.len(), 1);
assert_eq!(index.postings(), 2);
assert_eq!(index.count(&open), 2);
index.take(open.as_bytes(), b"a");
assert_eq!(index.postings(), 1);
assert_eq!(index.len(), 1);
index.take(open.as_bytes(), b"b");
assert_eq!(index.postings(), 0);
assert!(index.is_empty(), "an empty posting list is not a key");
assert_eq!(index.count(&open), 0);
}
#[test]
fn filing_the_same_document_twice_files_it_once() {
let mut index = PathIndex::new(b"$.status", IndexKind::Equality);
let open = Key::text("open");
index.add(open.as_bytes(), b"a").expect("room");
index.add(open.as_bytes(), b"a").expect("room");
assert_eq!(index.postings(), 1);
index.take(open.as_bytes(), b"a");
assert_eq!(index.postings(), 0);
}
#[test]
fn taking_out_something_that_was_never_filed_changes_nothing() {
let mut index = PathIndex::new(b"$.status", IndexKind::Equality);
let open = Key::text("open");
index.add(open.as_bytes(), b"a").expect("room");
index.take(open.as_bytes(), b"never");
index.take(Key::text("shut").as_bytes(), b"a");
assert_eq!(index.postings(), 1);
assert_eq!(index.count(&open), 1);
}
#[test]
fn a_posting_list_of_numbers_reads_back_as_bytes() {
let mut index = PathIndex::new(b"$.customer", IndexKind::Equality);
let key = Key::int(4);
for id in ["11", "2", "333"] {
index.add(key.as_bytes(), id.as_bytes()).expect("room");
}
let mut got = Vec::new();
let n = each_id(index.get(&key).expect("filed"), |id| {
got.push(String::from_utf8_lossy(id).into_owned());
});
assert_eq!(n, 3);
got.sort();
assert_eq!(got, ["11", "2", "333"]);
}
}