kevy_index/segment.rs
1//! [`Segment`] — one shard's slice of one index (index-follows-key).
2//!
3//! Entries are `(value, key)` pairs kept in value order, ties broken by
4//! key, each with the row's stored `VALUES` beside it. They live packed in
5//! the leaves of a counted B+ tree, encoded so that byte order is entry
6//! order (see the `seg_codec` module). Nothing maps a key back to its
7//! entry: a write names the row's old value, which the write path read
8//! before it changed the row.
9
10use crate::key_dir::KeyDir;
11use crate::seg_codec::{Codec, Form, be8, put_column};
12use crate::seg_leaf::{Ent, Shape};
13use crate::seg_tree::{Pos, Tree};
14use crate::segment_stats::SegmentStats;
15use crate::spec::IndexSpec;
16use crate::value::IndexValue;
17
18/// Opaque pagination cursor: the last `(value, key)` served. Encoded
19/// by the runtime into the wire cursor; `None` = start.
20///
21/// ```
22/// use kevy_index::{Cursor, IndexValue};
23/// let c = Cursor::new(IndexValue::I64(30), b"user:7".to_vec());
24/// assert_eq!(c.key, b"user:7");
25/// ```
26#[derive(Debug, Clone, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct Cursor {
29 /// Last value served.
30 ///
31 /// ```
32 /// # use kevy_index::{IndexValue, Segment};
33 /// let mut s = Segment::new();
34 /// for (k, v) in [(b"a", 1), (b"b", 2)] { s.apply(k, None, Some(IndexValue::I64(v))); }
35 /// let (_, next) = s.range(&IndexValue::I64(0), &IndexValue::I64(9), None, 1);
36 /// assert_eq!(next.expect("more to read").value, IndexValue::I64(1));
37 /// ```
38 pub value: IndexValue,
39 /// Last key served (tiebreak within a value).
40 ///
41 /// ```
42 /// # use kevy_index::{IndexValue, Segment};
43 /// let mut s = Segment::new();
44 /// for k in [b"x", b"y"] { s.apply(k, None, Some(IndexValue::I64(5))); }
45 /// let (_, next) = s.range(&IndexValue::I64(5), &IndexValue::I64(5), None, 1);
46 /// assert_eq!(next.expect("more to read").key, b"x", "ties break on the key");
47 /// ```
48 pub key: Vec<u8>,
49}
50
51impl Cursor {
52 /// The cursor just past `(value, key)`, the last entry served.
53 ///
54 /// ```
55 /// use kevy_index::{Cursor, IndexValue};
56 /// assert_eq!(Cursor::new(IndexValue::I64(1), b"k".to_vec()).value, IndexValue::I64(1));
57 /// ```
58 pub fn new(value: IndexValue, key: Vec<u8>) -> Cursor {
59 Cursor { value, key }
60 }
61}
62
63/// One shard's slice of one index.
64///
65/// A write passes the row's value before the write (`old`) and after it
66/// (`new`); `None` means the row had, or now has, no entry.
67///
68/// ```
69/// use kevy_index::{IndexValue, Segment};
70/// let mut s = Segment::new();
71/// s.apply(b"u:1", None, Some(IndexValue::I64(30)));
72/// s.apply(b"u:2", None, Some(IndexValue::I64(40)));
73/// let (hits, _) = s.range(&IndexValue::I64(35), &IndexValue::I64(50), None, 10);
74/// assert_eq!(hits, vec![(b"u:2".to_vec(), IndexValue::I64(40))]);
75/// assert_eq!(s.count(&IndexValue::I64(0), &IndexValue::I64(99)), 2);
76/// s.remove(b"u:1", &IndexValue::I64(30));
77/// assert_eq!(s.eq(&IndexValue::I64(30), 10), Vec::<Vec<u8>>::new());
78/// ```
79#[derive(Debug)]
80pub struct Segment {
81 pub(crate) tree: Tree,
82 pub(crate) codec: Codec,
83 /// `entries` and `approx_bytes` are derived in [`Segment::stats`].
84 stats: SegmentStats,
85 key_dir: Option<KeyDir>,
86 /// Scratch for an order key and a payload, reused by every write.
87 ebuf: Vec<u8>,
88 pbuf: Vec<u8>,
89 /// The background repack's hand, driven by [`Segment::tidy`].
90 pub(crate) tidy: crate::seg_tree::Tidy,
91}
92
93impl Default for Segment {
94 fn default() -> Self {
95 Segment::with_codec(Codec::new(Form::Unset, b"", 0))
96 }
97}
98
99impl Segment {
100 pub(crate) fn with_codec(codec: Codec) -> Segment {
101 Segment {
102 tree: Tree::new(shape_of(&codec)),
103 codec,
104 stats: SegmentStats::default(),
105 key_dir: None,
106 ebuf: Vec::new(),
107 pbuf: Vec::new(),
108 tidy: Default::default(),
109 }
110 }
111
112 /// Empty segment. Its value type is set by the first value it holds.
113 ///
114 /// ```
115 /// assert_eq!(kevy_index::Segment::new().stats().entries, 0);
116 /// ```
117 pub fn new() -> Self {
118 Self::default()
119 }
120
121 /// Empty segment that stores `n` declared `VALUES` fields per row.
122 ///
123 /// ```
124 /// use kevy_index::{IndexValue, Segment};
125 /// let mut s = Segment::with_values(1);
126 /// s.apply_with_values(b"k", None, Some(IndexValue::I64(1)), &[Some(b"eu")]);
127 /// assert_eq!(s.stored(&IndexValue::I64(1), b"k", 0), Some(b"eu".to_vec()));
128 /// ```
129 pub fn with_values(n: usize) -> Self {
130 Segment::with_codec(Codec::new(Form::Unset, b"", n))
131 }
132
133 /// Empty segment shaped by `spec`: its value type, the key prefix its
134 /// rows share (left out of every stored key), a composite value stored
135 /// as its own encoding, and the declared `VALUES`. A key outside the
136 /// prefix is still accepted; the segment then stores whole keys.
137 ///
138 /// ```
139 /// use kevy_index::{IndexKind, IndexSpec, IndexValue, Segment, ValType};
140 /// let spec = IndexSpec::builder("age", "user:", IndexKind::Range, ValType::I64)
141 /// .with_field("age")
142 /// .build()?;
143 /// let mut s = Segment::for_spec(&spec);
144 /// s.apply(b"user:42", None, Some(IndexValue::I64(30)));
145 /// assert_eq!(s.eq(&IndexValue::I64(30), 10), vec![b"user:42".to_vec()]);
146 /// # Ok::<(), Box<dyn std::error::Error>>(())
147 /// ```
148 pub fn for_spec(spec: &IndexSpec) -> Self {
149 let form = match (spec.composite(), spec.ty()) {
150 (Some(cols), _) => Form::Composite(cols.iter().map(|c| (c.ty, c.order)).collect()),
151 (None, crate::ValType::I64) => Form::I64,
152 (None, crate::ValType::F64) => Form::F64,
153 (None, _) => Form::Str,
154 };
155 Segment::with_codec(Codec::new(form, spec.prefix(), spec.values().len()))
156 }
157
158 /// Write-path maintenance: the row at `key` held `old` and now
159 /// coerces to `new` (`None` = excluded / row deleted, counted as a
160 /// coerce failure).
161 ///
162 /// ```
163 /// use kevy_index::{IndexValue, Segment};
164 /// let mut s = Segment::new();
165 /// s.apply(b"k", None, Some(IndexValue::I64(1)));
166 /// s.apply(b"k", Some(&IndexValue::I64(1)), Some(IndexValue::I64(2)));
167 /// assert!(s.contains(&IndexValue::I64(2), b"k") && !s.contains(&IndexValue::I64(1), b"k"));
168 /// s.apply(b"k", Some(&IndexValue::I64(2)), None);
169 /// assert_eq!((s.stats().entries, s.stats().coerce_failures), (0, 1));
170 /// ```
171 pub fn apply(&mut self, key: &[u8], old: Option<&IndexValue>, new: Option<IndexValue>) {
172 self.apply_with_values(key, old, new, &[]);
173 }
174
175 /// [`Segment::apply`] plus the row's declared stored values, which
176 /// ride with the entry.
177 ///
178 /// ```
179 /// use kevy_index::{IndexValue, Segment};
180 /// let mut s = Segment::with_values(1);
181 /// s.apply_with_values(b"k", None, Some(IndexValue::I64(1)), &[Some(b"old")]);
182 /// let one = IndexValue::I64(1);
183 /// s.apply_with_values(b"k", Some(&one), Some(one.clone()), &[Some(b"new")]);
184 /// assert_eq!(s.stored(&one, b"k", 0), Some(b"new".to_vec()), "same value, new stored field");
185 /// ```
186 pub fn apply_with_values(
187 &mut self,
188 key: &[u8],
189 old: Option<&IndexValue>,
190 new: Option<IndexValue>,
191 vals: &[Option<&[u8]>],
192 ) {
193 let Some(v) = new else {
194 if let Some(o) = old {
195 self.remove(key, o);
196 }
197 self.stats.coerce_failures += 1;
198 return;
199 };
200 if old.is_some_and(|o| *o == v) && self.codec.arity == 0 {
201 return;
202 }
203 if let Some(o) = old.filter(|o| **o != v) {
204 self.remove(key, o);
205 }
206 if self.codec.form == Form::Unset {
207 self.codec.form = Form::of(&v);
208 self.tree = Tree::new(shape_of(&self.codec));
209 }
210 if !self.codec.form.admits(&v) {
211 // a value of another type than the segment's: excluded
212 self.stats.coerce_failures += 1;
213 return;
214 }
215 self.fit_key(key);
216 self.insert(key, &v, vals);
217 }
218
219 fn insert(&mut self, key: &[u8], v: &IndexValue, vals: &[Option<&[u8]>]) {
220 self.ebuf.clear();
221 self.codec.put_entry(v, key, &mut self.ebuf);
222 self.pbuf.clear();
223 for f in 0..self.codec.arity {
224 put_column(vals.get(f).copied().flatten(), &mut self.pbuf);
225 }
226 let vlen = self.codec.value_len(&self.ebuf);
227 let (codec, ebuf) = (&self.codec, &self.ebuf);
228 let mut held = 0;
229 let e = Ent { key: ebuf, vlen, payload: &self.pbuf };
230 let new = self.tree.insert_seen(e, |t, at| {
231 held = holders(t, codec, &ebuf[..vlen], t.prev_pos(at), t.normalize(at));
232 });
233 if new && held == 1 {
234 self.stats.duplicates += 1;
235 }
236 if let Some(d) = &mut self.key_dir {
237 d.put(key, v);
238 }
239 }
240
241 /// Row deleted: take its entry (held under `old`) out. Not a coerce
242 /// failure.
243 ///
244 /// ```
245 /// use kevy_index::{IndexValue, Segment};
246 /// let mut s = Segment::new();
247 /// s.apply(b"k", None, Some(IndexValue::I64(1)));
248 /// s.remove(b"k", &IndexValue::I64(1));
249 /// assert_eq!((s.stats().entries, s.stats().coerce_failures), (0, 0));
250 /// ```
251 pub fn remove(&mut self, key: &[u8], old: &IndexValue) {
252 if !self.codec.form.admits(old) || !self.codec.fits_key(key) {
253 return;
254 }
255 self.ebuf.clear();
256 self.codec.put_entry(old, key, &mut self.ebuf);
257 let vlen = self.codec.value_len(&self.ebuf);
258 let (codec, ebuf) = (&self.codec, &self.ebuf);
259 let mut held = 0;
260 let gone = self.tree.remove_seen(ebuf, |t, at| {
261 held = holders(t, codec, &ebuf[..vlen], t.prev_pos(at), t.next_pos(at));
262 });
263 if gone && held == 1 {
264 self.stats.duplicates -= 1;
265 }
266 if gone && let Some(d) = &mut self.key_dir {
267 d.remove(key);
268 }
269 }
270
271 /// Re-encode every entry when `key` does not fit the segment's key
272 /// form (it lacks the prefix, or is not all digits after it).
273 fn fit_key(&mut self, key: &[u8]) {
274 if self.codec.fits_key(key) {
275 return;
276 }
277 let wide = self.codec.widened_for(key);
278 let mut entries: Vec<(Vec<u8>, usize, Vec<u8>)> = Vec::with_capacity(self.tree.len);
279 let mut w = crate::seg_walk::Walker::new(self, self.tree.first_pos(), false);
280 while w.advance() {
281 let payload = w.payload().to_vec();
282 let (v, k) = w.pair();
283 let mut e = Vec::new();
284 wide.put_entry(v, k, &mut e);
285 let vlen = wide.value_len(&e);
286 entries.push((e, vlen, payload));
287 }
288 self.codec = wide;
289 self.tree.rebuild(entries.iter().map(|(e, vlen, p)| Ent {
290 key: e,
291 vlen: *vlen,
292 payload: p,
293 }));
294 }
295
296 /// Whether the segment holds `key` under `value`.
297 ///
298 /// ```
299 /// use kevy_index::{IndexValue, Segment};
300 /// let mut s = Segment::new();
301 /// s.apply(b"k", None, Some(IndexValue::I64(7)));
302 /// assert!(s.contains(&IndexValue::I64(7), b"k"));
303 /// assert!(!s.contains(&IndexValue::I64(8), b"k"));
304 /// ```
305 pub fn contains(&self, value: &IndexValue, key: &[u8]) -> bool {
306 self.find(value, key).is_some()
307 }
308
309 /// The position of `(value, key)`, when held.
310 pub(crate) fn find(&self, value: &IndexValue, key: &[u8]) -> Option<Pos> {
311 if !self.codec.form.admits(value) || !self.codec.fits_key(key) {
312 return None;
313 }
314 SCRATCH.with(|b| {
315 let mut e = b.borrow_mut();
316 e.clear();
317 self.codec.put_entry(value, key, &mut e);
318 let p = crate::seg_leaf::Probe::new(&e);
319 let pos = self.tree.lower_bound(&p)?;
320 let l = self.tree.leaf(pos.leaf);
321 (l.cmp_at(&p, pos.slot, &self.tree.ov) == std::cmp::Ordering::Equal).then_some(pos)
322 })
323 }
324
325 /// `key`'s stored value for declared `VALUES` field `field`, where the
326 /// row is held under `value`; `None` when the row has none.
327 ///
328 /// ```
329 /// use kevy_index::{IndexValue, Segment};
330 /// let mut s = Segment::with_values(2);
331 /// s.apply_with_values(b"k", None, Some(IndexValue::I64(1)), &[Some(b"eu"), None]);
332 /// assert_eq!(s.stored(&IndexValue::I64(1), b"k", 0), Some(b"eu".to_vec()));
333 /// assert_eq!(s.stored(&IndexValue::I64(1), b"k", 1), None);
334 /// ```
335 pub fn stored(&self, value: &IndexValue, key: &[u8], field: usize) -> Option<Vec<u8>> {
336 if field >= self.codec.arity {
337 return None;
338 }
339 let pos = self.find(value, key)?;
340 let payload = self.tree.leaf(pos.leaf).tail(pos.slot, &self.tree.ov).payload;
341 crate::seg_codec::nth_column(payload, field).into_vec()
342 }
343
344 /// Every stored value of the row held under `value`, in declared
345 /// `VALUES` order; empty when the index declares none or the row is
346 /// not held.
347 ///
348 /// ```
349 /// use kevy_index::{IndexValue, Segment};
350 /// let mut s = Segment::with_values(2);
351 /// s.apply_with_values(b"k", None, Some(IndexValue::I64(1)), &[Some(b"a"), Some(b"12")]);
352 /// assert_eq!(s.stored_row(&IndexValue::I64(1), b"k"), [Some(b"a".to_vec()), Some(b"12".to_vec())]);
353 /// ```
354 pub fn stored_row(&self, value: &IndexValue, key: &[u8]) -> Vec<Option<Vec<u8>>> {
355 let Some(pos) = self.find(value, key) else { return Vec::new() };
356 let payload = self.tree.leaf(pos.leaf).tail(pos.slot, &self.tree.ov).payload;
357 let mut at = 0;
358 (0..self.codec.arity)
359 .map(|_| crate::seg_codec::column(payload, &mut at).into_vec())
360 .collect()
361 }
362
363 /// Keep a key → value directory beside the entries (`on`), or drop
364 /// it. Views ask a row's value by key alone; an index no view reads
365 /// has no directory and pays nothing for one.
366 ///
367 /// ```
368 /// use kevy_index::{IndexValue, Segment};
369 /// let mut s = Segment::new();
370 /// s.apply(b"k", None, Some(IndexValue::I64(3)));
371 /// s.set_key_dir(true);
372 /// assert_eq!(s.key_dir().and_then(|d| d.get(b"k")), Some(IndexValue::I64(3)));
373 /// ```
374 pub fn set_key_dir(&mut self, on: bool) {
375 if !on {
376 self.key_dir = None;
377 return;
378 }
379 if self.key_dir.is_some() {
380 return;
381 }
382 let mut d = KeyDir::new();
383 self.each_entry(|k, v| d.put(k, v));
384 self.key_dir = Some(d);
385 }
386
387 /// The key → value directory, when [`Segment::set_key_dir`] asked for
388 /// one.
389 ///
390 /// ```
391 /// assert!(kevy_index::Segment::new().key_dir().is_none());
392 /// ```
393 pub fn key_dir(&self) -> Option<&KeyDir> {
394 self.key_dir.as_ref()
395 }
396
397 /// Live counters. The memory term is every byte the segment's
398 /// structures hold: its leaves, its inner nodes and their separators,
399 /// out-of-line entries, and the key directory when there is one.
400 ///
401 /// ```
402 /// use kevy_index::{IndexValue, Segment};
403 /// let mut s = Segment::new();
404 /// s.apply(b"k", None, Some(IndexValue::I64(3)));
405 /// assert_eq!(s.stats().entries, 1);
406 /// assert!(s.stats().approx_bytes >= 1784, "at least one leaf");
407 /// ```
408 pub fn stats(&self) -> SegmentStats {
409 let mut s = self.stats;
410 s.entries = self.tree.len as u64;
411 s.approx_bytes =
412 self.tree.heap_bytes() as u64 + self.key_dir.as_ref().map_or(0, KeyDir::approx_bytes);
413 s
414 }
415
416 /// Repack every entry into full leaves. Entries written in random
417 /// order leave leaves about 69% full; a build ends with this, so what
418 /// stays resident is the packed form.
419 ///
420 /// ```
421 /// use kevy_index::{IndexValue, Segment};
422 /// let mut s = Segment::new();
423 /// for i in 0..10_000 {
424 /// s.apply(format!("k{i}").as_bytes(), None, Some(IndexValue::I64(i * 7919 % 10_000)));
425 /// }
426 /// let before = s.stats().approx_bytes;
427 /// s.repack();
428 /// assert!(s.stats().approx_bytes < before);
429 /// assert_eq!(s.stats().entries, 10_000);
430 /// ```
431 pub fn repack(&mut self) {
432 self.tree.repack();
433 }
434
435 pub(crate) fn note_cut(&mut self, dups: u64, keys: &[(IndexValue, Vec<u8>)]) {
436 self.stats.duplicates -= dups;
437 if let Some(d) = &mut self.key_dir {
438 for (_, k) in keys {
439 d.remove(k);
440 }
441 }
442 }
443}
444
445/// What a segment's leaves carry: its stored values, and each value's
446/// length when values are not a fixed 8 bytes.
447fn shape_of(c: &Codec) -> Shape {
448 Shape { payloads: c.arity > 0, vlens: !matches!(c.form, Form::I64 | Form::F64) }
449}
450
451thread_local! {
452 /// An order key being looked up by a `&self` read, so a point lookup
453 /// allocates nothing.
454 static SCRATCH: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
455}
456
457/// How many entries next to a spot hold the value `vb`, counting no
458/// further than 2: walking left from `left` and right from `right`.
459fn holders(t: &Tree, c: &Codec, vb: &[u8], left: Option<Pos>, right: Option<Pos>) -> usize {
460 let fixed = matches!(c.form, Form::I64 | Form::F64);
461 let head = if fixed { be8(vb) } else { 0 };
462 let same = |p: Pos| {
463 let l = t.leaf(p.leaf);
464 if fixed { l.head(p.slot) == head } else { l.starts_with(p.slot, vb, &t.ov) }
465 };
466 let mut n = 0;
467 let mut at = left;
468 while let Some(p) = at.filter(|_| n < 2) {
469 if !same(p) {
470 break;
471 }
472 n += 1;
473 at = t.prev_pos(p);
474 }
475 let mut at = right;
476 while let Some(p) = at.filter(|_| n < 2) {
477 if !same(p) {
478 break;
479 }
480 n += 1;
481 at = t.next_pos(p);
482 }
483 n
484}
485
486#[path = "segment_read.rs"]
487mod read;
488
489#[cfg(test)]
490#[path = "segment_tests.rs"]
491mod tests;