Skip to main content

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;