yo_doc/index.rs
1//! Indexes over a path into a document (`09` sections 4 and 5).
2//!
3//! A collection can find a document by its id already, because the primary
4//! table is keyed by it. An index is what makes it findable by what is inside
5//! it: one element table per indexed path, keyed by the value at that path,
6//! holding the ids of the documents that have it.
7//!
8//! An index answers equality, and an ordered one answers ranges as well. The
9//! array and text kinds file a document under more than one key at a time,
10//! every element of an array or every word of a string, and are asked the same
11//! question an equality index is.
12//!
13//! ```
14//! use std::ops::Bound;
15//! use yo_doc::{Builder, Docs, Key};
16//!
17//! let mut docs = Docs::new();
18//! docs.create_index("$.status")?;
19//! docs.create_ordered_index("$.price")?;
20//! docs.create_text_index("$.name")?;
21//! for (id, status, price, name) in [
22//! ("a", "open", 30, "A red bicycle"),
23//! ("b", "shut", 10, "a blue kite"),
24//! ("c", "open", 20, "A red kite"),
25//! ] {
26//! let mut b = Builder::new();
27//! b.begin_object()?;
28//! b.key(b"status")?;
29//! b.text(status)?;
30//! b.key(b"price")?;
31//! b.int(price)?;
32//! b.key(b"name")?;
33//! b.text(name)?;
34//! b.end_object()?;
35//! let bytes = b.finish()?.to_vec();
36//! docs.put_bytes(id.as_bytes(), &bytes)?;
37//! }
38//!
39//! let mut found = Vec::new();
40//! docs.find("$.status", &Key::text("open"), |id, _| found.push(id.to_vec()))?;
41//! found.sort();
42//! assert_eq!(found, [b"a".to_vec(), b"c".to_vec()]);
43//!
44//! // Cheapest first, up to and including twenty.
45//! let mut upto = Vec::new();
46//! docs.range("$.price", Bound::Unbounded, Bound::Included(&Key::int(20)), |id, _| {
47//! upto.push(id.to_vec())
48//! })?;
49//! assert_eq!(upto, [b"b".to_vec(), b"c".to_vec()]);
50//!
51//! // One word out of the name, with the case folded on both sides.
52//! let red = Key::word("RED").expect("one word");
53//! let mut kites = Vec::new();
54//! docs.find("$.name", &red, |id, _| kites.push(id.to_vec()))?;
55//! kites.sort();
56//! assert_eq!(kites, [b"a".to_vec(), b"c".to_vec()]);
57//! # Ok::<(), yo_common::Error>(())
58//! ```
59//!
60//! # It is the same code again
61//!
62//! The table from key to posting list is [`Elements`], which is a hash's field
63//! table. The posting list is [`Set`], which is a Redis set, so a key that one
64//! document has costs a listpack entry rather than a hash table, a key that a
65//! million documents have is a partitioned element table, and a collection
66//! whose ids are numbers gets an intset and eight bytes a posting. None of that
67//! was written for this.
68//!
69//! It also means intersecting two indexes is `SINTER`, on the same sets, with
70//! the same code, at the same speed. That is the whole of `09` section 5's
71//! "probe each equality index, intersect the smallest result first", and there
72//! is nothing to build for it.
73//!
74//! The order an ordered index walks is the counted B+ tree from `08` section 5,
75//! which is what a sorted set ranks with. That tree holds row numbers and asks
76//! the caller to compare, so it took no changes at all to put index keys under
77//! it instead of zset members. It costs about three bytes per distinct value,
78//! and a range is one descent and then a link hop per leaf, so the cost of a
79//! range is the size of the answer rather than the size of the collection.
80//!
81//! The key table stays unordered either way, because it is a hash's field table
82//! and a hash is not ordered. The order is a separate structure over its row
83//! numbers, which is the same split the sorted set makes rather than a second
84//! design.
85//!
86//! # What a key is
87//!
88//! [`Key`] is the value at the path with a tag byte in front of it, so a
89//! document with the string `"7"` at a path and one with the number seven do
90//! not land on the same key. Every key is written so that comparing two of them
91//! as bytes gives the same answer comparing the values would. Equality does not
92//! need that, but it means the tree can compare keys with `memcmp` and never
93//! decode one.
94//!
95//! There is one tag for numbers rather than one for integers and one for
96//! floats, because a range over a path holding both has to put them in one
97//! order, and two tags cannot. Every finite number is a mantissa times a power
98//! of two, so a numeric key is written as where its leading bit sits, which is
99//! `floor(log2(|v|)) + 1` and is called the place here, followed by the mantissa
100//! shifted up to the top of eight bytes. Two numbers with different places are
101//! ordered by the place alone, and two with the same place are ordered by the
102//! mantissa read from the leading bit down, which is what the shift lines up.
103//! The place is biased by 32768 so that the whole range a f64 can reach sorts as
104//! an unsigned number, and everything after the class byte is flipped for a
105//! negative, because a bigger magnitude there is a smaller number.
106//!
107//! The byte in front of the place is the class, which is one of negative
108//! infinity, negative, zero, positive, positive infinity and NaN. Those five
109//! that are not an ordinary finite value have no size worth writing, so they
110//! carry a place and a mantissa of zero and are ordered by the class alone. NaN
111//! sorts above everything rather than being refused, so a range never has to
112//! think about it.
113//!
114//! An integer and a float that names the same integer, `7` and `7.0`, get the
115//! same key, because both normalise to a leading `111` and a place of three. A
116//! caller asking for seven means seven, and JSON has one number type, so the
117//! alternative is a query that misses documents for a reason nobody can see.
118//!
119//! Types do not interleave either: everything with a smaller tag sorts before
120//! everything with a larger one, so nulls, then booleans, then numbers, then
121//! strings. That one is on purpose. A range over a path is a range over one
122//! type, and a total order across types has to pick an arbitrary answer to
123//! whether a string is above or below a number.
124//!
125//! # What a probe costs
126//!
127//! G15 is that finding a document by a value at an indexed path costs what
128//! `HGET` costs, and the reason to expect that is the section above: the index
129//! is a hash's field table and a probe of it is a probe of one. `benches/yojb`
130//! runs both against the same records so the claim is a ratio. On a 13900K with
131//! nothing else running, nanoseconds:
132//!
133//! ```text
134//! 1024 docs 16384 docs
135//! HGET 23.5 28.1
136//! HGET, 12 byte field 27.2 29.3
137//! probe 38.9 41.0
138//! of which the key 10.2 12.1
139//! so the lookup 28.7 28.8
140//! find 65.5 93.0
141//! ```
142//!
143//! The two `HGET` rows are the same call against a different field width, and
144//! the second one is there because the first is not a fair comparison. An index
145//! key for an integer is twelve bytes, and the hash fields in this bench are the
146//! decimal of the loop counter, so at most five. Measuring the index against the
147//! short one charges it for the longer key and calls the difference index
148//! overhead, which it is not, because a hash asked for a twelve byte field pays
149//! exactly the same thing.
150//!
151//! `probe` is [`PathIndex::count`] with the path already resolved, which is the
152//! index lookup plus the key encoding, and `key` is the encoding on its own.
153//! Take one off the other and the lookup is 28.7 and 28.8 against 27.2 and 29.3
154//! for the hash at the same key width. That is the gate: the probe is one probe
155//! and it costs what a probe costs. It is also flat where `HGET` is not, which
156//! is the table growing from 1024 to 16384 entries showing up on one side and
157//! not the other, and at the larger size the index is the faster of the two.
158//!
159//! `find` is the whole call a caller makes and it is more than one probe by
160//! construction: it looks the path up by name, encodes the key, probes the
161//! index, and then probes the primary table with the id it got back. The second
162//! probe is a document read, and `HGET` does not need one because the value it
163//! returns is in the slot it just found. So an indexed equality that hands back
164//! the document is two probes, and only the first of them is the index.
165//!
166//! The key encoding used to be the largest single piece of this, at 15.6 ns on
167//! an M-series laptop where the whole probe was 28.2, because it pushed twelve
168//! bytes into a [`Small`] one at a time and every push re-reads which variant
169//! the list is on. It is built in a local array and copied in one go now, which
170//! took that to 3.1 ns and the probe to 10.6.
171//!
172//! # More than one key at a time
173//!
174//! An array index files a document under every element of the array at the
175//! path, and a text index under every word of the string. Both answer the same
176//! question an equality index does, so [`PathIndex::find`] and
177//! [`PathIndex::count`] do not know which kind they are on, and the only thing
178//! that changes is how many keys a document has.
179//!
180//! A scalar at the path of an array index is an array of one. A collection
181//! where some documents carry a list of tags and some carry a single tag is a
182//! real collection, and an index that filed one and not the other would miss
183//! documents for a reason nobody can see.
184//!
185//! A text index folds case, so a search has to fold it too, and [`Key::word`]
186//! is what does that on the query side. Everything that is not a letter or a
187//! digit is a separator. That is a word index and not a search engine: there is
188//! no ranking, no stemming and no phrase matching, and the ranking that belongs
189//! on top of it is `10`. Splitting on bytes is also wrong for a language that
190//! does not put spaces between words, and the answer there is a real tokeniser
191//! rather than a rule here that is subtly wrong in another way.
192//!
193//! # What is not indexed
194//!
195//! A path that lands on an object or an array puts nothing in an equality
196//! index, and a document that has no value at the path puts nothing in any
197//! kind. Both are absences rather than errors: an index answers which documents
198//! have a given value there, and neither of those documents does.
199//!
200//! A path that lands on something other than a string puts nothing in a text
201//! index. A number has no words in it, and filing `7` under the key `7` in a
202//! text index would make one kind quietly behave like another.
203
204use core::cmp::Ordering;
205use core::ops::Bound;
206
207use yo_common::num::i64_digits;
208use yo_common::small::Small;
209use yo_common::{Code, Error, Result};
210use yo_kv::{Elements, Rank, Set, SetLimits, Slab, rank};
211
212use crate::head::Kind;
213use crate::read::Value;
214
215/// The longest an index key may be, which is the longest name an element table
216/// takes.
217///
218/// A text value past this cannot be filed, and a write that would have to file
219/// one fails rather than storing a document the index will never find. A silent
220/// absence from an index is a query that returns the wrong answer with no way
221/// to tell, and that is worse than a write that says no.
222pub const KEY_MAX: usize = yo_kv::NAME_MAX - 1;
223
224/// How much of a key sits in the caller's frame before it needs the allocator.
225///
226/// Twelve bytes covers every number, one byte covers a boolean or a null, and a
227/// tag and thirty one bytes covers the short strings that get indexed in
228/// practice: a status, a country, an identifier.
229const KEY_INLINE: usize = 32;
230
231const TAG_NULL: u8 = 0;
232const TAG_FALSE: u8 = 1;
233const TAG_TRUE: u8 = 2;
234const TAG_NUM: u8 = 3;
235const TAG_TEXT: u8 = 4;
236
237/// A value as an index looks it up.
238///
239/// Built from what the caller is searching for, or from what was found at a
240/// path in a document being written. The two go through the same code on
241/// purpose, because a query that encodes its argument differently from the way
242/// the write encoded the document is a query that finds nothing and says
243/// nothing about why.
244#[derive(Clone)]
245pub struct Key(Small<u8, KEY_INLINE>);
246
247impl Key {
248 /// The key for `null`.
249 #[must_use]
250 pub fn null() -> Key {
251 Key(Small::collect([TAG_NULL]))
252 }
253
254 /// The key for a boolean.
255 #[must_use]
256 pub fn bool(v: bool) -> Key {
257 Key(Small::collect([if v { TAG_TRUE } else { TAG_FALSE }]))
258 }
259
260 /// The key for an integer.
261 #[must_use]
262 pub fn int(v: i64) -> Key {
263 let (neg, mant) = if v < 0 {
264 (true, v.unsigned_abs())
265 } else {
266 (false, v as u64)
267 };
268 number(Class::of(neg, mant == 0), mant, i32::from(bits(mant)))
269 }
270
271 /// The key for a float.
272 ///
273 /// A float and an integer that name the same number get the same key, so
274 /// `7.0` and `7` are one key and a search for either finds both.
275 #[must_use]
276 pub fn float(v: f64) -> Key {
277 if v.is_nan() {
278 return number(Class::Nan, 0, 0);
279 }
280 if v.is_infinite() {
281 return number(
282 if v.is_sign_negative() {
283 Class::NegInf
284 } else {
285 Class::PosInf
286 },
287 0,
288 0,
289 );
290 }
291 let raw = v.to_bits();
292 let neg = raw >> 63 == 1;
293 let exponent = ((raw >> 52) & 0x7ff) as i32;
294 let fraction = raw & ((1 << 52) - 1);
295 // A subnormal has no implied leading one and a fixed exponent, and
296 // everything else has both.
297 let (mant, scale) = if exponent == 0 {
298 (fraction, -1074)
299 } else {
300 (fraction | (1 << 52), exponent - 1075)
301 };
302 number(
303 Class::of(neg, mant == 0),
304 mant,
305 scale + i32::from(bits(mant)),
306 )
307 }
308
309 /// The key for a string.
310 #[must_use]
311 pub fn text(v: &str) -> Key {
312 Key::text_bytes(v.as_bytes())
313 }
314
315 /// The key for a string that is already bytes.
316 #[must_use]
317 pub fn text_bytes(v: &[u8]) -> Key {
318 let mut k = Small::collect([TAG_TEXT]);
319 k.extend_from_slice(v);
320 Key(k)
321 }
322
323 /// The key one word is filed under in a text index, or `None` if this is
324 /// not one word.
325 ///
326 /// A search against a text index goes through this rather than
327 /// [`Key::text`], because a text index folds case when it files a document
328 /// and a search that does not fold it finds nothing and says nothing about
329 /// why. Anything that is not letters and digits is a separator, so a phrase
330 /// is two words and answers `None`: matching one is a search this index
331 /// cannot answer on its own, rather than a search for the first word.
332 #[must_use]
333 pub fn word(v: &str) -> Option<Key> {
334 let mut rest = v.as_bytes();
335 let word = next_word(&mut rest)?;
336 if next_word(&mut rest).is_some() {
337 return None;
338 }
339 Some(fold(word))
340 }
341
342 /// The key for a value found in a document, or `None` if it is a container
343 /// and so has no equality key.
344 #[must_use]
345 pub fn of(v: Value<'_>) -> Option<Key> {
346 match v.kind() {
347 Kind::Null => Some(Key::null()),
348 Kind::Bool => Some(Key::bool(v.as_bool()?)),
349 Kind::Int => Some(Key::int(v.as_int()?)),
350 Kind::Float => Some(Key::float(v.as_float()?)),
351 Kind::Text => Some(Key::text_bytes(v.text_bytes()?)),
352 Kind::Array | Kind::Object => None,
353 }
354 }
355
356 /// The bytes this is filed under.
357 #[must_use]
358 pub fn as_bytes(&self) -> &[u8] {
359 self.0.as_slice()
360 }
361
362 /// Whether this key is too long to file.
363 #[must_use]
364 pub fn is_too_long(&self) -> bool {
365 self.as_bytes().len() > KEY_MAX
366 }
367}
368
369impl PartialEq for Key {
370 fn eq(&self, other: &Key) -> bool {
371 self.as_bytes() == other.as_bytes()
372 }
373}
374
375impl Eq for Key {}
376
377impl core::fmt::Debug for Key {
378 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
379 let b = self.as_bytes();
380 match b.first() {
381 Some(&TAG_NULL) => f.write_str("null"),
382 Some(&TAG_FALSE) => f.write_str("false"),
383 Some(&TAG_TRUE) => f.write_str("true"),
384 Some(&TAG_TEXT) => write!(f, "{:?}", String::from_utf8_lossy(&b[1..])),
385 Some(&TAG_NUM) => write!(f, "{}", Hex(&b[1..])),
386 _ => f.write_str("<no key>"),
387 }
388 }
389}
390
391/// Bytes as hex, for the numbers a key holds in an order preserving form that
392/// is not worth decoding back just to print it.
393struct Hex<'a>(&'a [u8]);
394
395impl core::fmt::Display for Hex<'_> {
396 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
397 for b in self.0 {
398 write!(f, "{b:02x}")?;
399 }
400 Ok(())
401 }
402}
403
404/// Where a number sits in the order, before its size is looked at.
405///
406/// The class is the first byte of a numeric key, so the five kinds of number
407/// that are not an ordinary finite value each land somewhere fixed rather than
408/// being encoded into the same bytes the finite ones use.
409#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410enum Class {
411 NegInf = 0,
412 Negative = 1,
413 Zero = 2,
414 Positive = 3,
415 PosInf = 4,
416 /// Not a number, which JSON has no way to write and a document can only get
417 /// from a program that put one there. It sorts above everything rather than
418 /// being refused, so a range never has to think about it.
419 Nan = 5,
420}
421
422impl Class {
423 fn of(neg: bool, zero: bool) -> Class {
424 match (zero, neg) {
425 (true, _) => Class::Zero,
426 (false, true) => Class::Negative,
427 (false, false) => Class::Positive,
428 }
429 }
430}
431
432/// How many bits a magnitude takes.
433fn bits(mant: u64) -> u16 {
434 (64 - mant.leading_zeros()) as u16
435}
436
437/// A number as bytes that sort the way the number does, whether it arrived as
438/// an integer or as a float.
439///
440/// Every finite number is `mantissa * 2^k` for some odd mantissa, so `place` is
441/// where its leading bit sits, which is `floor(log2(|v|)) + 1`. Two numbers with
442/// different `place` are ordered by it alone, and two with the same `place` are
443/// ordered by their mantissas read from the leading bit down. Lining the
444/// mantissa up to the top of eight bytes is what makes that a byte comparison,
445/// and it is also what makes `7` and `7.0` the same bytes: both normalise to a
446/// leading `111` and a `place` of three, whatever they looked like on the way
447/// in.
448///
449/// Negatives get the ten bytes after the class flipped, because a bigger
450/// magnitude is a smaller number.
451fn number(class: Class, mant: u64, place: i32) -> Key {
452 let (place, mant) = match class {
453 // The size of an infinity, a zero or a NaN is not a question, and
454 // writing it as zero keeps every numeric key the same width.
455 Class::Negative | Class::Positive => (place, mant << mant.leading_zeros()),
456 _ => (0, 0),
457 };
458 // Biased so that the whole range a f64 can reach, which is roughly -1074 to
459 // 1025, is an unsigned number that sorts the way the signed one does.
460 let place = ((place + 32768) as u16).to_be_bytes();
461 let flip = if class == Class::Negative { 0xff } else { 0 };
462 // Written into a local array and copied in one go rather than pushed a byte
463 // at a time. Every numeric key is these twelve bytes, the length is known
464 // before the first one is written, and a push has to re-read which variant
465 // the list is on each time round.
466 let mut k = [TAG_NUM, class as u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
467 k[2..4].copy_from_slice(&place);
468 k[4..].copy_from_slice(&mant.to_be_bytes());
469 for b in &mut k[2..] {
470 *b ^= flip;
471 }
472 Key(Small::from_slice(&k))
473}
474
475/// A float as bytes that sort the way the float does.
476/// What an index can be asked, and how many keys a document gets at its path.
477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
478pub enum IndexKind {
479 /// One value at a time. A table from key to posting list and nothing else.
480 Equality,
481 /// One value at a time, or every value between two of them. The same table
482 /// with a counted B+ tree over its rows.
483 Ordered,
484 /// One element of an array at a time. A document with `["red", "blue"]` at
485 /// the path is filed under both, so a search for either finds it.
486 Array,
487 /// One word of a string at a time, folded to lower case. A document with
488 /// `"A red bicycle"` at the path is filed under `a`, `red` and `bicycle`.
489 Text,
490}
491
492impl IndexKind {
493 /// Whether this kind can be asked for a range as well as for a value.
494 #[must_use]
495 pub fn is_ordered(self) -> bool {
496 self == IndexKind::Ordered
497 }
498
499 /// Whether a document can be filed under more than one key at a time.
500 #[must_use]
501 pub fn is_multi(self) -> bool {
502 matches!(self, IndexKind::Array | IndexKind::Text)
503 }
504}
505
506/// A value at an indexed path that cannot be a key, because it is longer than
507/// [`KEY_MAX`].
508///
509/// Carried back rather than turned into an error here, so the layer that knows
510/// which path and which document it was can say so.
511#[derive(Debug, Clone, Copy)]
512pub(crate) struct TooLong;
513
514/// Append every key `at` files under, as a list of one length byte pair and
515/// then that many bytes.
516///
517/// Length prefixed rather than one buffer per key, because an array index files
518/// a document under as many keys as the array is long and a write is not
519/// allowed to allocate per element.
520///
521/// A path that lands on nothing this kind can use puts nothing in the list.
522/// That is an absence and not an error: an index answers which documents have a
523/// given value at the path, and a document with an object there does not have
524/// one.
525pub(crate) fn keys_at(
526 kind: IndexKind,
527 at: Value<'_>,
528 out: &mut Vec<u8>,
529) -> core::result::Result<(), TooLong> {
530 match kind {
531 IndexKind::Equality | IndexKind::Ordered => {
532 if let Some(key) = Key::of(at) {
533 push_key(&key, out)?;
534 }
535 }
536 IndexKind::Array => match at.kind() {
537 // A scalar at the path is an array of one. A caller that files
538 // `["red"]` on one document and `"red"` on the next means the same
539 // thing by both, and an index that disagreed would be a query that
540 // misses documents for a reason nobody can see.
541 Kind::Array => {
542 for elem in at.iter() {
543 if let Some(key) = Key::of(elem) {
544 push_key(&key, out)?;
545 }
546 }
547 }
548 Kind::Object => {}
549 _ => {
550 if let Some(key) = Key::of(at) {
551 push_key(&key, out)?;
552 }
553 }
554 },
555 IndexKind::Text => {
556 if let Some(text) = at.text_bytes() {
557 let mut rest = text;
558 while let Some(word) = next_word(&mut rest) {
559 push_key(&fold(word), out)?;
560 }
561 }
562 }
563 }
564 Ok(())
565}
566
567/// Put one key on the end of a key list.
568fn push_key(key: &Key, out: &mut Vec<u8>) -> core::result::Result<(), TooLong> {
569 let bytes = key.as_bytes();
570 if key.is_too_long() {
571 return Err(TooLong);
572 }
573 // The length fits two bytes because KEY_MAX does, and the check above ran.
574 let n = bytes.len() as u16;
575 out.extend_from_slice(&n.to_le_bytes());
576 out.extend_from_slice(bytes);
577 Ok(())
578}
579
580/// Walk a key list back out again.
581pub(crate) fn each_key(mut list: &[u8], mut f: impl FnMut(&[u8])) {
582 while list.len() >= 2 {
583 let n = usize::from(u16::from_le_bytes([list[0], list[1]]));
584 let Some(key) = list.get(2..2 + n) else {
585 return;
586 };
587 f(key);
588 list = &list[2 + n..];
589 }
590}
591
592/// The next run of letters and digits in `rest`, with `rest` left after it.
593///
594/// Everything else is a separator, so punctuation, spaces and the bytes of a
595/// multi byte character all split. Splitting inside a word of a language that
596/// does not use spaces is wrong, and a real tokeniser is the answer rather than
597/// a rule here that is subtly wrong in a different way, so `10` will bring one.
598/// For the ASCII text that gets a text index today this is what a caller means.
599/// One word as the key a text index files it under, folded to lower case.
600fn fold(word: &[u8]) -> Key {
601 Key(Small::collect(
602 core::iter::once(TAG_TEXT).chain(word.iter().map(u8::to_ascii_lowercase)),
603 ))
604}
605
606fn next_word<'a>(rest: &mut &'a [u8]) -> Option<&'a [u8]> {
607 let start = rest.iter().position(|b| b.is_ascii_alphanumeric())?;
608 let after = rest[start..]
609 .iter()
610 .position(|b| !b.is_ascii_alphanumeric())
611 .map_or(rest.len(), |n| start + n);
612 let word = &rest[start..after];
613 *rest = &rest[after..];
614 Some(word)
615}
616
617/// One index, over one path.
618#[derive(Debug)]
619pub struct PathIndex {
620 /// The path as it was written, kept whole so it can be parsed again per
621 /// lookup. Parsing is a scan of a dozen bytes and it saves an owned step
622 /// type that would have to be kept in step with [`crate::Steps`].
623 path: Box<[u8]>,
624 /// What this index can be asked and how many keys a document gets.
625 kind: IndexKind,
626 /// The key to the slab slot its posting list sits in.
627 keys: Elements<u32>,
628 /// The rows of `keys` in key order, for an ordered index, and nothing at all
629 /// for an equality one.
630 ///
631 /// The table above is unordered, because it is a hash's field table. The
632 /// order lives here instead of being a property of the table, which is the
633 /// same split a sorted set makes: the members are in an element table and
634 /// the rank is a separate tree over its row numbers.
635 order: Option<Rank>,
636 /// The posting lists. A slab rather than a payload beside the row, because
637 /// [`Elements`] moves its last row into the hole on a removal and a posting
638 /// list is not `Copy`.
639 posts: Slab<Set>,
640 /// How many document ids are filed altogether, over every key.
641 postings: usize,
642}
643
644impl PathIndex {
645 /// An empty index over `path`, which has already been checked to parse.
646 pub(crate) fn new(path: &[u8], kind: IndexKind) -> PathIndex {
647 PathIndex {
648 path: path.into(),
649 kind,
650 keys: Elements::new(),
651 order: kind.is_ordered().then(Rank::new),
652 posts: Slab::new(),
653 postings: 0,
654 }
655 }
656
657 /// The path this indexes.
658 #[must_use]
659 pub fn path(&self) -> &[u8] {
660 &self.path
661 }
662
663 /// What this index can be asked.
664 #[must_use]
665 pub fn kind(&self) -> IndexKind {
666 self.kind
667 }
668
669 /// Every key `at` files under in this index.
670 pub(crate) fn keys_at(
671 &self,
672 at: Value<'_>,
673 out: &mut Vec<u8>,
674 ) -> core::result::Result<(), TooLong> {
675 keys_at(self.kind, at, out)
676 }
677
678 /// How many distinct values are filed.
679 #[must_use]
680 pub fn len(&self) -> usize {
681 self.keys.len()
682 }
683
684 /// Whether nothing is filed.
685 #[must_use]
686 pub fn is_empty(&self) -> bool {
687 self.keys.is_empty()
688 }
689
690 /// How many document ids are filed altogether.
691 ///
692 /// One per document that has a scalar at this path, so the difference
693 /// between this and the collection's length is how many documents the index
694 /// does not cover.
695 #[must_use]
696 pub fn postings(&self) -> usize {
697 self.postings
698 }
699
700 /// The documents filed under `key`.
701 ///
702 /// A [`Set`], so it can be intersected with another one by the same code
703 /// `SINTER` uses.
704 #[must_use]
705 pub fn get(&self, key: &Key) -> Option<&Set> {
706 self.posts.get(*self.keys.get(key.as_bytes())?)
707 }
708
709 /// How many documents are filed under `key`.
710 ///
711 /// The number a query planner sorts its filters by, and it is a probe
712 /// rather than a walk.
713 #[must_use]
714 pub fn count(&self, key: &Key) -> usize {
715 self.get(key).map_or(0, Set::len)
716 }
717
718 /// Every key between `lo` and `hi` with the documents filed under it, in
719 /// order.
720 ///
721 /// One descent of the tree and then a link per leaf, so a range of a
722 /// thousand keys costs one search and a handful of hops. An equality index
723 /// has no order to walk and answers nothing at all rather than pretending to
724 /// have a range; the layer above turns that into an error, because a range
725 /// query that silently finds nothing is worse than one that says no.
726 #[must_use]
727 pub fn range(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> Ranged<'_> {
728 let Some((order, start, left)) = self.span(lo, hi) else {
729 return Ranged {
730 index: self,
731 walk: None,
732 left: 0,
733 };
734 };
735 Ranged {
736 index: self,
737 walk: Some(order.iter_from(start)),
738 left,
739 }
740 }
741
742 /// [`PathIndex::range`] backwards, largest key first.
743 #[must_use]
744 pub fn range_rev(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> RangedRev<'_> {
745 let Some((order, start, left)) = self.span(lo, hi) else {
746 return RangedRev {
747 index: self,
748 walk: None,
749 left: 0,
750 };
751 };
752 RangedRev {
753 index: self,
754 walk: Some(order.iter_back_from(start + left - 1)),
755 left,
756 }
757 }
758
759 /// How many documents are filed under any key between `lo` and `hi`.
760 ///
761 /// This reads the keys in the range and not the documents, so it costs the
762 /// number of distinct values rather than the number of postings.
763 #[must_use]
764 pub fn count_in(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> usize {
765 self.range(lo, hi).map(|(_, set)| set.len()).sum()
766 }
767
768 /// Where a range starts and how many keys are in it, or `None` if there is
769 /// no order to walk or nothing in the range.
770 fn span(&self, lo: Bound<&Key>, hi: Bound<&Key>) -> Option<(&Rank, usize, usize)> {
771 let order = self.order.as_ref()?;
772 let keys = &self.keys;
773 let start = match lo {
774 Bound::Unbounded => 0,
775 Bound::Included(k) => rank_of(order, keys, k.as_bytes()),
776 Bound::Excluded(k) => rank_after(order, keys, k.as_bytes()),
777 };
778 let end = match hi {
779 Bound::Unbounded => keys.len(),
780 Bound::Included(k) => rank_after(order, keys, k.as_bytes()),
781 Bound::Excluded(k) => rank_of(order, keys, k.as_bytes()),
782 };
783 if end <= start {
784 return None;
785 }
786 Some((order, start, end - start))
787 }
788
789 /// File `id` under `key`.
790 pub(crate) fn add(&mut self, key: &[u8], id: &[u8]) -> Result<()> {
791 if let Some(&slot) = self.keys.get(key) {
792 let set = self.posts.get_mut(slot).expect("a row points at its list");
793 if set.add(id, &SetLimits::DEFAULT) {
794 self.postings += 1;
795 }
796 return Ok(());
797 }
798 let mut set = Set::new();
799 set.add(id, &SetLimits::DEFAULT);
800 let slot = self.posts.insert(set);
801 let row = self.keys.len() as u32;
802 if self.keys.insert(key, slot).is_err() {
803 self.posts.remove(slot);
804 return Err(Error::new(
805 Code::Full,
806 "the index cannot hold another distinct value",
807 ));
808 }
809 let PathIndex { keys, order, .. } = self;
810 if let Some(order) = order {
811 // The key is in the table already and not in the tree, so the search
812 // compares it against every other key and lands where it belongs.
813 let at = rank_of(order, keys, key);
814 order.insert_at(at, row);
815 }
816 self.postings += 1;
817 Ok(())
818 }
819
820 /// Take `id` out from under `key`, and drop the key if it was the last one.
821 pub(crate) fn take(&mut self, key: &[u8], id: &[u8]) {
822 let Some(row) = self.keys.index_of(key) else {
823 return;
824 };
825 let slot = *self.keys.at(row).expect("a row that was just found").1;
826 let set = self.posts.get_mut(slot).expect("a row points at its list");
827 if !set.remove(id) {
828 return;
829 }
830 self.postings -= 1;
831 if !set.is_empty() {
832 return;
833 }
834 self.posts.remove(slot);
835 self.untrack(key, row);
836 self.keys.remove_at(row);
837 }
838
839 /// Take `row` out of the tree, and tell the tree about the row the element
840 /// table is about to renumber.
841 ///
842 /// The table is dense, so taking a row out moves the last row into the hole
843 /// and one key nobody asked about gets a new number. Where that key sits has
844 /// to be found before anything moves, because afterwards the tree is holding
845 /// a number that means something else. This is the same dance a sorted set
846 /// does, for the same reason.
847 ///
848 /// An equality index has no tree and nothing to do here.
849 fn untrack(&mut self, key: &[u8], row: usize) {
850 let PathIndex { keys, order, .. } = self;
851 let Some(order) = order else {
852 return;
853 };
854 let rank = rank_of(order, keys, key);
855 let last = keys.len() - 1;
856 let moved = if last == row {
857 None
858 } else {
859 let name = keys.at(last).expect("the last row").0;
860 Some(order.seek(|other| {
861 let (other_name, _) = keys.at(other as usize).expect("a row the tree holds");
862 name.cmp(other_name)
863 }))
864 };
865 order.remove_at(rank);
866 if let Some(at) = moved {
867 // Everything above the hole shifted down by one when the row came
868 // out of the tree.
869 let at = if at > rank { at - 1 } else { at };
870 order.set_at(at, row as u32);
871 }
872 }
873
874 /// Throw everything filed away and keep the path and the kind.
875 pub(crate) fn clear(&mut self) {
876 self.keys.clear();
877 self.posts.clear();
878 self.postings = 0;
879 if let Some(order) = &mut self.order {
880 *order = Rank::new();
881 }
882 }
883
884 /// What the index costs, posting lists and the order included.
885 #[must_use]
886 pub fn memory_bytes(&self) -> usize {
887 self.keys.memory_bytes()
888 + self.posts.slot_bytes()
889 + self.posts.iter().map(Set::memory_bytes).sum::<usize>()
890 + self.order.as_ref().map_or(0, Rank::bytes)
891 }
892}
893
894/// Keys in order with their posting lists, from [`PathIndex::range`].
895pub struct Ranged<'a> {
896 index: &'a PathIndex,
897 walk: Option<rank::Walk<'a>>,
898 left: usize,
899}
900
901impl<'a> Iterator for Ranged<'a> {
902 type Item = (&'a [u8], &'a Set);
903
904 fn next(&mut self) -> Option<(&'a [u8], &'a Set)> {
905 if self.left == 0 {
906 return None;
907 }
908 let row = self.walk.as_mut()?.next()?;
909 self.left -= 1;
910 entry(self.index, row)
911 }
912
913 fn size_hint(&self) -> (usize, Option<usize>) {
914 (self.left, Some(self.left))
915 }
916}
917
918impl ExactSizeIterator for Ranged<'_> {}
919
920/// Keys in reverse order with their posting lists, from
921/// [`PathIndex::range_rev`].
922pub struct RangedRev<'a> {
923 index: &'a PathIndex,
924 walk: Option<rank::Back<'a>>,
925 left: usize,
926}
927
928impl<'a> Iterator for RangedRev<'a> {
929 type Item = (&'a [u8], &'a Set);
930
931 fn next(&mut self) -> Option<(&'a [u8], &'a Set)> {
932 if self.left == 0 {
933 return None;
934 }
935 let row = self.walk.as_mut()?.next()?;
936 self.left -= 1;
937 entry(self.index, row)
938 }
939
940 fn size_hint(&self) -> (usize, Option<usize>) {
941 (self.left, Some(self.left))
942 }
943}
944
945impl ExactSizeIterator for RangedRev<'_> {}
946
947/// The rank `key` sits at in `order`, or would sit at.
948///
949/// A free function rather than a method because every caller has the tree and
950/// the table split out of the index already, either because it is about to
951/// write to the tree while reading the table or because it is holding a borrow
952/// of the tree it means to keep.
953fn rank_of(order: &Rank, keys: &Elements<u32>, key: &[u8]) -> usize {
954 order.seek(|row| {
955 let (name, _) = keys.at(row as usize).expect("a row the tree holds");
956 key.cmp(name)
957 })
958}
959
960/// The rank one past `key`, which is where it sits when it is not there and one
961/// to the right of it when it is.
962fn rank_after(order: &Rank, keys: &Elements<u32>, key: &[u8]) -> usize {
963 order.seek(|row| {
964 let (name, _) = keys.at(row as usize).expect("a row the tree holds");
965 match key.cmp(name) {
966 Ordering::Less => Ordering::Less,
967 Ordering::Equal | Ordering::Greater => Ordering::Greater,
968 }
969 })
970}
971
972/// The key and the posting list a tree row names.
973fn entry(index: &PathIndex, row: u32) -> Option<(&[u8], &Set)> {
974 let (name, &slot) = index.keys.at(row as usize)?;
975 Some((name, index.posts.get(slot)?))
976}
977
978/// Hand every id in `set` to `f` as bytes.
979///
980/// A posting list of numeric ids is an intset, so the ids come back as integers
981/// and have to be written out again to be probed with. The digits go in a
982/// buffer on this frame, so a walk over a million postings allocates nothing.
983pub(crate) fn each_id(set: &Set, mut f: impl FnMut(&[u8])) -> usize {
984 let mut digits = [0u8; yo_common::num::DIGITS_MAX];
985 let mut n = 0usize;
986 for member in set.iter() {
987 match member {
988 yo_kv::listpack::Entry::Str(s) => f(s),
989 yo_kv::listpack::Entry::Int(v) => f(i64_digits(&mut digits, v)),
990 }
991 n += 1;
992 }
993 n
994}
995
996#[cfg(test)]
997mod tests {
998 use super::*;
999
1000 /// The keys a kind takes from one value, as printable strings.
1001 fn taken(kind: IndexKind, build: impl FnOnce(&mut crate::Builder)) -> Vec<String> {
1002 let mut b = crate::Builder::new();
1003 build(&mut b);
1004 let bytes = b.finish().expect("built").to_vec();
1005 let value = Value::new(&bytes).expect("readable");
1006 let mut list = Vec::new();
1007 keys_at(kind, value, &mut list).expect("short enough");
1008 let mut out = Vec::new();
1009 each_key(&list, |key| {
1010 out.push(format!("{:?}", Key(Small::collect(key.iter().copied()))))
1011 });
1012 out
1013 }
1014
1015 #[test]
1016 fn an_array_index_takes_one_key_per_element() {
1017 let keys = taken(IndexKind::Array, |b| {
1018 b.begin_array().expect("open");
1019 b.text("red").expect("value");
1020 b.int(7).expect("value");
1021 b.begin_object().expect("open");
1022 b.end_object().expect("close");
1023 b.end_array().expect("close");
1024 });
1025 assert_eq!(keys.len(), 2, "the object inside is not a key: {keys:?}");
1026 assert_eq!(keys[0], "\"red\"");
1027
1028 // A scalar is a list of one, and an object is a list of none.
1029 assert_eq!(
1030 taken(IndexKind::Array, |b| b.text("red").expect("v")).len(),
1031 1
1032 );
1033 assert_eq!(
1034 taken(IndexKind::Array, |b| {
1035 b.begin_object().expect("open");
1036 b.end_object().expect("close");
1037 })
1038 .len(),
1039 0
1040 );
1041 }
1042
1043 #[test]
1044 fn a_text_index_splits_on_everything_that_is_not_a_letter_or_a_digit() {
1045 let keys = taken(IndexKind::Text, |b| {
1046 b.text(" The RED car, model 3! ").expect("value")
1047 });
1048 assert_eq!(
1049 keys,
1050 ["\"the\"", "\"red\"", "\"car\"", "\"model\"", "\"3\""]
1051 );
1052
1053 assert!(taken(IndexKind::Text, |b| b.text("!!! ...").expect("v")).is_empty());
1054 assert!(taken(IndexKind::Text, |b| b.int(7).expect("v")).is_empty());
1055 }
1056
1057 #[test]
1058 fn a_word_key_is_what_a_text_index_filed_and_a_phrase_is_not_one() {
1059 assert_eq!(Key::word("RED"), Key::word("red"));
1060 assert_eq!(Key::word("red!"), Key::word("red"));
1061 assert!(Key::word("red car").is_none(), "a phrase is two words");
1062 assert!(Key::word("").is_none());
1063 assert!(Key::word("!!!").is_none());
1064 assert_eq!(
1065 Key::word("red").expect("a word"),
1066 Key::text("red"),
1067 "a word that needs no folding is the string key, and there is no \
1068 second text tag to keep them apart"
1069 );
1070 assert_ne!(Key::word("RED").expect("a word"), Key::text("RED"));
1071 }
1072
1073 #[test]
1074 fn a_key_list_reads_back_exactly_what_went_into_it() {
1075 let mut list = Vec::new();
1076 push_key(&Key::text("red"), &mut list).expect("short");
1077 push_key(&Key::int(7), &mut list).expect("short");
1078 push_key(&Key::null(), &mut list).expect("short");
1079 let mut out = Vec::new();
1080 each_key(&list, |key| out.push(key.to_vec()));
1081 assert_eq!(
1082 out,
1083 [
1084 Key::text("red").as_bytes().to_vec(),
1085 Key::int(7).as_bytes().to_vec(),
1086 Key::null().as_bytes().to_vec(),
1087 ]
1088 );
1089
1090 let long = "x".repeat(KEY_MAX);
1091 assert!(push_key(&Key::text(&long), &mut list).is_err());
1092 }
1093
1094 #[test]
1095 fn a_number_and_the_string_of_it_are_different_keys() {
1096 assert_ne!(Key::int(7), Key::text("7"));
1097 assert_ne!(Key::null(), Key::text(""));
1098 assert_ne!(Key::bool(true), Key::int(1));
1099 }
1100
1101 #[test]
1102 fn a_float_that_names_a_whole_number_is_that_number() {
1103 assert_eq!(Key::float(7.0), Key::int(7));
1104 assert_eq!(Key::float(-0.0), Key::int(0));
1105 assert_eq!(Key::float(-3.0), Key::int(-3));
1106 assert_ne!(Key::float(7.5), Key::int(7));
1107 assert_ne!(Key::float(1e30), Key::int(i64::MAX));
1108 assert_ne!(Key::float(f64::NAN), Key::float(0.0));
1109 }
1110
1111 #[test]
1112 fn numbers_sort_as_bytes_the_way_they_sort_as_numbers() {
1113 let mut ns = [0i64, -1, i64::MIN, i64::MAX, 7, -7, 1 << 40];
1114 let mut keys: Vec<Key> = ns.iter().map(|&n| Key::int(n)).collect();
1115 ns.sort_unstable();
1116 keys.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
1117 let want: Vec<Key> = ns.iter().map(|&n| Key::int(n)).collect();
1118 assert_eq!(keys, want);
1119
1120 let mut fs = [0.5f64, -0.5, -1.5, 1e300, -1e300, f64::MIN_POSITIVE];
1121 let mut keys: Vec<Key> = fs.iter().map(|&f| Key::float(f)).collect();
1122 fs.sort_by(f64::total_cmp);
1123 keys.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
1124 let want: Vec<Key> = fs.iter().map(|&f| Key::float(f)).collect();
1125 assert_eq!(keys, want);
1126 }
1127
1128 #[test]
1129 fn an_integer_and_a_float_sort_among_each_other() {
1130 // The order this has to produce is the numeric one, and the two ways of
1131 // writing a number are mixed on purpose so that nothing can pass by
1132 // keeping the integers on one side and the floats on the other.
1133 let mut mixed: Vec<Key> = [
1134 Key::float(12.5),
1135 Key::int(99),
1136 Key::int(-3),
1137 Key::float(-2.5),
1138 Key::int(0),
1139 Key::float(0.25),
1140 Key::int(13),
1141 ]
1142 .to_vec();
1143 mixed.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
1144 let want = [
1145 Key::int(-3),
1146 Key::float(-2.5),
1147 Key::int(0),
1148 Key::float(0.25),
1149 Key::float(12.5),
1150 Key::int(13),
1151 Key::int(99),
1152 ];
1153 assert_eq!(mixed, want);
1154 }
1155
1156 #[test]
1157 fn seven_and_seven_point_zero_are_one_key() {
1158 assert_eq!(Key::int(7), Key::float(7.0));
1159 assert_eq!(Key::int(-7), Key::float(-7.0));
1160 assert_eq!(Key::int(0), Key::float(0.0));
1161 // A negative zero is a zero. Nothing else would let a caller who asks
1162 // for zero find a document that has one.
1163 assert_eq!(Key::int(0), Key::float(-0.0));
1164 assert_eq!(Key::int(1 << 53), Key::float((1u64 << 53) as f64));
1165 // And two numbers that are close are still two numbers. `i64::MAX` is
1166 // one below a power of two and the nearest f64 to it is that power of
1167 // two, so these are not the same value and do not get the same key.
1168 assert_ne!(Key::int(i64::MAX), Key::float(i64::MAX as f64));
1169 }
1170
1171 #[test]
1172 fn the_ends_of_the_number_line_sort_where_they_belong() {
1173 let mut ends = [
1174 Key::float(f64::NAN),
1175 Key::float(f64::INFINITY),
1176 Key::int(1),
1177 Key::float(f64::NEG_INFINITY),
1178 Key::int(-1),
1179 Key::float(f64::MIN),
1180 Key::float(f64::MAX),
1181 ]
1182 .to_vec();
1183 ends.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
1184 let want = [
1185 Key::float(f64::NEG_INFINITY),
1186 Key::float(f64::MIN),
1187 Key::int(-1),
1188 Key::int(1),
1189 Key::float(f64::MAX),
1190 Key::float(f64::INFINITY),
1191 // Above everything, so a range never has to think about it.
1192 Key::float(f64::NAN),
1193 ];
1194 assert_eq!(ends, want);
1195 }
1196
1197 #[test]
1198 fn every_number_is_the_same_width() {
1199 for k in [
1200 Key::int(0),
1201 Key::int(i64::MIN),
1202 Key::float(1e300),
1203 Key::float(f64::MIN_POSITIVE),
1204 Key::float(f64::NAN),
1205 Key::float(f64::NEG_INFINITY),
1206 ] {
1207 assert_eq!(k.as_bytes().len(), 12, "{k:?}");
1208 }
1209 }
1210
1211 #[test]
1212 fn a_short_key_stays_off_the_heap() {
1213 assert!(Key::int(i64::MIN).0.is_inline());
1214 assert!(Key::text("a-fairly-ordinary-status").0.is_inline());
1215 assert!(!Key::text(&"x".repeat(64)).0.is_inline());
1216 }
1217
1218 #[test]
1219 fn a_key_prints_as_what_it_is() {
1220 assert_eq!(format!("{:?}", Key::null()), "null");
1221 assert_eq!(format!("{:?}", Key::bool(true)), "true");
1222 assert_eq!(format!("{:?}", Key::text("open")), "\"open\"");
1223 // The class byte for a zero, then a place and a mantissa that are both
1224 // written as zero because the size of a zero is not a question.
1225 assert_eq!(format!("{:?}", Key::int(0)), "0280000000000000000000");
1226 }
1227
1228 /// An ordered index over `$.n` holding the integers given, one document per
1229 /// integer, named after it.
1230 fn ordered(ns: impl IntoIterator<Item = i64>) -> PathIndex {
1231 let mut index = PathIndex::new(b"$.n", IndexKind::Ordered);
1232 for n in ns {
1233 index
1234 .add(Key::int(n).as_bytes(), n.to_string().as_bytes())
1235 .expect("room");
1236 }
1237 index
1238 }
1239
1240 /// The keys a range walks, decoded back to the integers they came from.
1241 fn walked(index: &PathIndex, lo: Bound<&Key>, hi: Bound<&Key>) -> Vec<i64> {
1242 let out: Vec<i64> = index.range(lo, hi).map(|(k, _)| unorder_int(k)).collect();
1243 let mut back: Vec<i64> = index
1244 .range_rev(lo, hi)
1245 .map(|(k, _)| unorder_int(k))
1246 .collect();
1247 back.reverse();
1248 assert_eq!(out, back, "backwards is forwards read the other way");
1249 out
1250 }
1251
1252 /// The number a numeric key was made from, for the whole numbers these
1253 /// tests file.
1254 fn unorder_int(key: &[u8]) -> i64 {
1255 assert_eq!(key[0], TAG_NUM, "these tests only file numbers");
1256 let class = key[1];
1257 if class == Class::Zero as u8 {
1258 return 0;
1259 }
1260 let flip = if class == Class::Negative as u8 {
1261 0xffu8
1262 } else {
1263 0
1264 };
1265 let place = u16::from_be_bytes([key[2] ^ flip, key[3] ^ flip]) as i32 - 32768;
1266 let mut mant = [0u8; 8];
1267 for (out, b) in mant.iter_mut().zip(&key[4..12]) {
1268 *out = b ^ flip;
1269 }
1270 // The mantissa sits at the top of the eight bytes, so shifting it back
1271 // down by however far its leading bit is from `place` gives the integer.
1272 let n = (u64::from_be_bytes(mant) >> (64 - place)) as i64;
1273 if flip == 0 { n } else { -n }
1274 }
1275
1276 #[test]
1277 fn an_ordered_index_walks_its_keys_in_order() {
1278 // Written in an order that is neither sorted nor reverse sorted, and
1279 // over enough keys to push the tree past one leaf. Fewer under Miri,
1280 // and still past one leaf. The stride is coprime with the count either
1281 // way, which is what makes the order neither of the two easy ones, and
1282 // the bounds further down are all well inside the range.
1283 let (n, stride) = if cfg!(miri) { (120i64, 37) } else { (500, 137) };
1284 let half = n / 2;
1285 let index = ordered((0..n).map(|i| (i * stride) % n - half));
1286 assert_eq!(index.len(), n as usize);
1287 assert_eq!(index.kind(), IndexKind::Ordered);
1288
1289 let all = walked(&index, Bound::Unbounded, Bound::Unbounded);
1290 assert_eq!(all, (-half..half).collect::<Vec<i64>>());
1291
1292 let (lo, hi) = (Key::int(-3), Key::int(4));
1293 assert_eq!(
1294 walked(&index, Bound::Included(&lo), Bound::Excluded(&hi)),
1295 [-3, -2, -1, 0, 1, 2, 3]
1296 );
1297 assert_eq!(
1298 walked(&index, Bound::Excluded(&lo), Bound::Included(&hi)),
1299 [-2, -1, 0, 1, 2, 3, 4]
1300 );
1301 assert_eq!(
1302 walked(
1303 &index,
1304 Bound::Unbounded,
1305 Bound::Excluded(&Key::int(-half + 3))
1306 ),
1307 [-half, -half + 1, -half + 2]
1308 );
1309 assert_eq!(
1310 walked(
1311 &index,
1312 Bound::Included(&Key::int(half - 3)),
1313 Bound::Unbounded
1314 ),
1315 [half - 3, half - 2, half - 1]
1316 );
1317 }
1318
1319 #[test]
1320 fn a_range_that_names_nothing_is_empty_rather_than_wrong() {
1321 let index = ordered([10i64, 20, 30]);
1322 let (lo, hi) = (Key::int(20), Key::int(20));
1323 assert!(walked(&index, Bound::Excluded(&lo), Bound::Excluded(&hi)).is_empty());
1324 assert_eq!(
1325 walked(&index, Bound::Included(&lo), Bound::Included(&hi)),
1326 [20]
1327 );
1328 // Backwards bounds, which a caller can hand over by accident.
1329 assert!(
1330 walked(
1331 &index,
1332 Bound::Included(&Key::int(30)),
1333 Bound::Excluded(&Key::int(10))
1334 )
1335 .is_empty()
1336 );
1337 // Between two keys that are there, and past both ends.
1338 assert!(
1339 walked(
1340 &index,
1341 Bound::Included(&Key::int(21)),
1342 Bound::Excluded(&Key::int(29))
1343 )
1344 .is_empty()
1345 );
1346 assert!(walked(&index, Bound::Included(&Key::int(31)), Bound::Unbounded).is_empty());
1347 assert!(walked(&index, Bound::Unbounded, Bound::Excluded(&Key::int(10))).is_empty());
1348 assert_eq!(index.count_in(Bound::Unbounded, Bound::Unbounded), 3);
1349 }
1350
1351 #[test]
1352 fn an_equality_index_has_no_range_and_says_so_by_being_empty() {
1353 let mut index = PathIndex::new(b"$.n", IndexKind::Equality);
1354 index.add(Key::int(1).as_bytes(), b"a").expect("room");
1355 assert_eq!(index.kind(), IndexKind::Equality);
1356 assert_eq!(index.range(Bound::Unbounded, Bound::Unbounded).count(), 0);
1357 assert_eq!(index.count_in(Bound::Unbounded, Bound::Unbounded), 0);
1358 assert_eq!(index.count(&Key::int(1)), 1, "equality still works");
1359 }
1360
1361 #[test]
1362 fn removing_keys_from_an_ordered_index_keeps_the_rest_in_order() {
1363 // Every removal moves the element table's last row into the hole, so the
1364 // tree is holding a row number that has come to mean a different key.
1365 // This is the test that the renumbering is told to it.
1366 // Fewer keys under Miri. What is being checked is that a hole is filled
1367 // from the end and the tree hears about it, which happens on every
1368 // removal, so the count is how many chances the test gets rather than
1369 // part of the claim. Every count below is derived from this one.
1370 let count = if cfg!(miri) { 60i64 } else { 200 };
1371 let mut index = ordered(0..count);
1372 for n in (0..count).step_by(3) {
1373 index.take(Key::int(n).as_bytes(), n.to_string().as_bytes());
1374 }
1375 let left: Vec<i64> = (0..count).filter(|n| n % 3 != 0).collect();
1376 assert_eq!(index.len(), left.len());
1377 assert_eq!(walked(&index, Bound::Unbounded, Bound::Unbounded), left);
1378
1379 // And the keys still find their own posting lists after all that.
1380 for n in &left {
1381 assert_eq!(index.count(&Key::int(*n)), 1, "{n} lost its list");
1382 }
1383 for n in (0..count).step_by(3) {
1384 assert_eq!(index.count(&Key::int(n)), 0, "{n} kept one");
1385 }
1386 }
1387
1388 #[test]
1389 fn an_ordered_index_that_is_emptied_and_refilled_is_still_ordered() {
1390 let mut index = ordered(0..64i64);
1391 for n in 0..64i64 {
1392 index.take(Key::int(n).as_bytes(), n.to_string().as_bytes());
1393 }
1394 assert!(index.is_empty());
1395 assert_eq!(index.postings(), 0);
1396 assert!(walked(&index, Bound::Unbounded, Bound::Unbounded).is_empty());
1397
1398 for n in (0..32i64).rev() {
1399 index
1400 .add(Key::int(n).as_bytes(), n.to_string().as_bytes())
1401 .expect("room");
1402 }
1403 assert_eq!(
1404 walked(&index, Bound::Unbounded, Bound::Unbounded),
1405 (0..32).collect::<Vec<i64>>()
1406 );
1407
1408 index.clear();
1409 assert_eq!(index.kind(), IndexKind::Ordered, "a clear keeps the kind");
1410 assert!(index.is_empty());
1411 index.add(Key::int(9).as_bytes(), b"9").expect("room");
1412 assert_eq!(walked(&index, Bound::Unbounded, Bound::Unbounded), [9]);
1413 }
1414
1415 #[test]
1416 fn a_key_with_many_documents_counts_once_in_the_order() {
1417 let mut index = PathIndex::new(b"$.n", IndexKind::Ordered);
1418 for i in 0..100 {
1419 index
1420 .add(
1421 Key::int(i64::from(i % 5)).as_bytes(),
1422 format!("d{i}").as_bytes(),
1423 )
1424 .expect("room");
1425 }
1426 assert_eq!(index.len(), 5, "five distinct values");
1427 assert_eq!(index.postings(), 100);
1428 assert_eq!(
1429 walked(&index, Bound::Unbounded, Bound::Unbounded),
1430 [0, 1, 2, 3, 4]
1431 );
1432 assert_eq!(index.count_in(Bound::Unbounded, Bound::Unbounded), 100);
1433 assert_eq!(
1434 index.count_in(Bound::Included(&Key::int(1)), Bound::Included(&Key::int(2))),
1435 40
1436 );
1437 }
1438
1439 #[test]
1440 fn the_last_document_under_a_key_takes_the_key_with_it() {
1441 let mut index = PathIndex::new(b"$.status", IndexKind::Equality);
1442 let open = Key::text("open");
1443 index.add(open.as_bytes(), b"a").expect("room");
1444 index.add(open.as_bytes(), b"b").expect("room");
1445 assert_eq!(index.len(), 1);
1446 assert_eq!(index.postings(), 2);
1447 assert_eq!(index.count(&open), 2);
1448
1449 index.take(open.as_bytes(), b"a");
1450 assert_eq!(index.postings(), 1);
1451 assert_eq!(index.len(), 1);
1452 index.take(open.as_bytes(), b"b");
1453 assert_eq!(index.postings(), 0);
1454 assert!(index.is_empty(), "an empty posting list is not a key");
1455 assert_eq!(index.count(&open), 0);
1456 }
1457
1458 #[test]
1459 fn filing_the_same_document_twice_files_it_once() {
1460 let mut index = PathIndex::new(b"$.status", IndexKind::Equality);
1461 let open = Key::text("open");
1462 index.add(open.as_bytes(), b"a").expect("room");
1463 index.add(open.as_bytes(), b"a").expect("room");
1464 assert_eq!(index.postings(), 1);
1465 index.take(open.as_bytes(), b"a");
1466 assert_eq!(index.postings(), 0);
1467 }
1468
1469 #[test]
1470 fn taking_out_something_that_was_never_filed_changes_nothing() {
1471 let mut index = PathIndex::new(b"$.status", IndexKind::Equality);
1472 let open = Key::text("open");
1473 index.add(open.as_bytes(), b"a").expect("room");
1474 index.take(open.as_bytes(), b"never");
1475 index.take(Key::text("shut").as_bytes(), b"a");
1476 assert_eq!(index.postings(), 1);
1477 assert_eq!(index.count(&open), 1);
1478 }
1479
1480 #[test]
1481 fn a_posting_list_of_numbers_reads_back_as_bytes() {
1482 let mut index = PathIndex::new(b"$.customer", IndexKind::Equality);
1483 let key = Key::int(4);
1484 for id in ["11", "2", "333"] {
1485 index.add(key.as_bytes(), id.as_bytes()).expect("room");
1486 }
1487 let mut got = Vec::new();
1488 let n = each_id(index.get(&key).expect("filed"), |id| {
1489 got.push(String::from_utf8_lossy(id).into_owned());
1490 });
1491 assert_eq!(n, 3);
1492 got.sort();
1493 assert_eq!(got, ["11", "2", "333"]);
1494 }
1495}