Skip to main content

basalt/
btree.rs

1/// A hand-written B+tree keyed on (Value, row_id), with linked leaves for
2/// ordered range scans. No std BTreeMap — split/merge logic is ours.
3///
4/// Keys order by `Value::cmp_value`, tie-broken by row id, so duplicate values
5/// on a non-unique index are all stored and all returned by range scans.
6use crate::types::Value;
7use std::cmp::Ordering;
8
9pub const MAX_KEYS: usize = 64;
10
11#[derive(Debug, Clone)]
12struct Node {
13    leaf: bool,
14    keys: Vec<Value>,     // internal: separators; leaf: entry keys
15    values: Vec<u64>,     // leaf: row ids aligned with keys
16    children: Vec<usize>, // internal: child ids, len == keys.len()+1
17    next: Option<usize>,  // leaf: next leaf for range scans
18}
19
20impl Node {
21    fn new(leaf: bool) -> Node {
22        Node {
23            leaf,
24            keys: Vec::new(),
25            values: Vec::new(),
26            children: Vec::new(),
27            next: None,
28        }
29    }
30}
31
32fn cmp_entry(k: &Value, id: u64, key: &Value, key_id: u64) -> Ordering {
33    match k.cmp_value(key) {
34        Ordering::Equal => id.cmp(&key_id),
35        other => other,
36    }
37}
38
39#[derive(Debug, Clone)]
40pub struct BTree {
41    nodes: Vec<Node>,
42    root: usize,
43    pub size: usize,
44}
45
46impl Default for BTree {
47    fn default() -> Self {
48        let nodes = vec![Node::new(true)];
49        BTree {
50            nodes,
51            root: 0,
52            size: 0,
53        }
54    }
55}
56
57impl BTree {
58    fn new_node(&mut self, leaf: bool) -> usize {
59        let id = self.nodes.len();
60        self.nodes.push(Node::new(leaf));
61        id
62    }
63
64    pub fn is_empty(&self) -> bool {
65        self.size == 0
66    }
67
68    /// Find the first leaf that can contain `value`.  Internal separators are
69    /// the minimum key of their right child, so equality must choose the
70    /// child on the left; callers then walk the linked leaves to cover all
71    /// duplicate keys.
72    fn find_first_leaf(&self, value: &Value) -> usize {
73        let mut node = self.root;
74        while !self.nodes[node].leaf {
75            let n = &self.nodes[node];
76            let idx = n
77                .keys
78                .iter()
79                .position(|k| k.cmp_value(value) != Ordering::Less)
80                .unwrap_or(n.keys.len());
81            node = n.children[idx];
82        }
83        node
84    }
85
86    fn leftmost_leaf(&self) -> usize {
87        let mut node = self.root;
88        while !self.nodes[node].leaf {
89            node = self.nodes[node].children[0];
90        }
91        node
92    }
93
94    fn find_entry_leaf(&self, value: &Value, row_id: u64) -> Option<usize> {
95        let mut leaf = self.find_first_leaf(value);
96        loop {
97            let node = &self.nodes[leaf];
98            for (key, id) in node.keys.iter().zip(&node.values) {
99                match cmp_entry(key, *id, value, row_id) {
100                    Ordering::Less => {}
101                    Ordering::Equal => return Some(leaf),
102                    Ordering::Greater => return None,
103                }
104            }
105            leaf = node.next?;
106        }
107    }
108
109    /// Locate the leaf where a new `(value, row_id)` entry belongs.  The
110    /// linked-leaf walk is important because internal separators contain the
111    /// value but not the row-id tie breaker.
112    fn insertion_leaf(&self, value: &Value, row_id: u64) -> usize {
113        let mut leaf = self.find_first_leaf(value);
114        loop {
115            let node = &self.nodes[leaf];
116            if node
117                .keys
118                .iter()
119                .zip(&node.values)
120                .any(|(key, id)| cmp_entry(key, *id, value, row_id) != Ordering::Less)
121            {
122                return leaf;
123            }
124            match node.next {
125                Some(next) => leaf = next,
126                None => return leaf,
127            }
128        }
129    }
130
131    fn path_to_leaf(&self, node: usize, target: usize, path: &mut Vec<usize>) -> bool {
132        path.push(node);
133        if node == target {
134            return self.nodes[node].leaf;
135        }
136        if self.nodes[node].leaf {
137            path.pop();
138            return false;
139        }
140        for child in self.nodes[node].children.iter().copied() {
141            if self.path_to_leaf(child, target, path) {
142                return true;
143            }
144        }
145        path.pop();
146        false
147    }
148
149    fn leaf_pos(&self, leaf: usize, value: &Value, row_id: u64) -> Option<usize> {
150        let n = &self.nodes[leaf];
151        let mut lo = 0usize;
152        let mut hi = n.keys.len();
153        while lo < hi {
154            let mid = (lo + hi) / 2;
155            match cmp_entry(&n.keys[mid], n.values[mid], value, row_id) {
156                Ordering::Less => lo = mid + 1,
157                Ordering::Greater => hi = mid,
158                Ordering::Equal => return Some(mid),
159            }
160        }
161        None
162    }
163
164    pub fn contains(&self, value: &Value, row_id: u64) -> bool {
165        if self.is_empty() {
166            return false;
167        }
168        self.find_entry_leaf(value, row_id).is_some()
169    }
170
171    /// All row ids whose key compares equal to `value`, in order.
172    pub fn lookup_eq(&self, value: &Value) -> Vec<u64> {
173        let mut out = Vec::new();
174        if self.is_empty() {
175            return out;
176        }
177        let mut node = self.find_first_leaf(value);
178        loop {
179            let n = &self.nodes[node];
180            for (k, v) in n.keys.iter().zip(n.values.iter()) {
181                match k.cmp_value(value) {
182                    Ordering::Greater => return out,
183                    Ordering::Equal => out.push(*v),
184                    Ordering::Less => {}
185                }
186            }
187            match n.next {
188                Some(next) => node = next,
189                None => return out,
190            }
191        }
192    }
193
194    /// All row ids with value in [low, high] inclusive, in key order.
195    pub fn range_scan(&self, low: &Value, high: &Value) -> Vec<u64> {
196        let mut out = Vec::new();
197        if self.is_empty() {
198            return out;
199        }
200        if low.cmp_value(high) == Ordering::Greater {
201            return out;
202        }
203        let mut node = self.find_first_leaf(low);
204        loop {
205            let n = &self.nodes[node];
206            for (k, v) in n.keys.iter().zip(n.values.iter()) {
207                if k.cmp_value(high) == Ordering::Greater {
208                    return out;
209                }
210                if k.cmp_value(low) != Ordering::Less {
211                    out.push(*v);
212                }
213            }
214            match n.next {
215                Some(next) => node = next,
216                None => return out,
217            }
218        }
219    }
220
221    /// All (key, row_id) in order — full index scan.
222    pub fn scan_all(&self) -> Vec<(Value, u64)> {
223        let mut out = Vec::new();
224        if self.is_empty() {
225            return out;
226        }
227        let mut node = self.leftmost_leaf();
228        loop {
229            let n = &self.nodes[node];
230            for i in 0..n.keys.len() {
231                out.push((n.keys[i].clone(), n.values[i]));
232            }
233            match n.next {
234                Some(next) => node = next,
235                None => return out,
236            }
237        }
238    }
239
240    pub fn insert(&mut self, value: Value, row_id: u64) {
241        if self.contains(&value, row_id) {
242            return;
243        }
244        let target = self.insertion_leaf(&value, row_id);
245        let mut path = Vec::new();
246        assert!(self.path_to_leaf(self.root, target, &mut path));
247        let split = self.insert_rec_at(&path, 0, value, row_id);
248        self.size += 1;
249        if let Some((median, right)) = split {
250            let left = self.root;
251            let new_root = self.new_node(false);
252            self.nodes[new_root].keys.push(median);
253            self.nodes[new_root].children.push(left);
254            self.nodes[new_root].children.push(right);
255            self.root = new_root;
256        }
257    }
258
259    fn insert_rec_at(
260        &mut self,
261        path: &[usize],
262        depth: usize,
263        value: Value,
264        row_id: u64,
265    ) -> Option<(Value, usize)> {
266        let node = path[depth];
267        if self.nodes[node].leaf {
268            let pos = self.nodes[node]
269                .keys
270                .iter()
271                .zip(&self.nodes[node].values)
272                .position(|(key, id)| cmp_entry(key, *id, &value, row_id) == Ordering::Greater)
273                .unwrap_or(self.nodes[node].keys.len());
274            self.nodes[node].keys.insert(pos, value);
275            self.nodes[node].values.insert(pos, row_id);
276            if self.nodes[node].keys.len() > MAX_KEYS {
277                Some(self.split_node(node))
278            } else {
279                None
280            }
281        } else {
282            let child = path[depth + 1];
283            let child_index = self.nodes[node]
284                .children
285                .iter()
286                .position(|candidate| *candidate == child)
287                .expect("path child belongs to parent");
288            if let Some((median, right)) = self.insert_rec_at(path, depth + 1, value, row_id) {
289                let n = &mut self.nodes[node];
290                n.keys.insert(child_index, median);
291                n.children.insert(child_index + 1, right);
292                if n.keys.len() > MAX_KEYS {
293                    Some(self.split_node(node))
294                } else {
295                    None
296                }
297            } else {
298                None
299            }
300        }
301    }
302
303    fn split_node(&mut self, node: usize) -> (Value, usize) {
304        let mid = MAX_KEYS / 2;
305        let right = self.new_node(self.nodes[node].leaf);
306        if self.nodes[node].leaf {
307            let median = self.nodes[node].keys[mid].clone();
308            self.nodes[right].keys = self.nodes[node].keys.split_off(mid);
309            self.nodes[right].values = self.nodes[node].values.split_off(mid);
310            self.nodes[right].next = self.nodes[node].next;
311            self.nodes[node].next = Some(right);
312            (median, right)
313        } else {
314            let mut keys_right = self.nodes[node].keys.split_off(mid);
315            let sep = keys_right.remove(0);
316            self.nodes[right].keys = keys_right;
317            self.nodes[right].children = self.nodes[node].children.split_off(mid + 1);
318            (sep, right)
319        }
320    }
321
322    /// Remove one entry; returns true if found.  Underflow is lazy: empty
323    /// leaves remain in the linked structure until the tree itself becomes
324    /// empty.  This keeps separator maintenance simple while preserving
325    /// ordered scans and allowing those leaves to be reused by later inserts.
326    pub fn delete(&mut self, value: &Value, row_id: u64) -> bool {
327        if self.is_empty() {
328            return false;
329        }
330        let Some(leaf) = self.find_entry_leaf(value, row_id) else {
331            return false;
332        };
333        let pos = self
334            .leaf_pos(leaf, value, row_id)
335            .expect("located entry exists");
336        self.nodes[leaf].keys.remove(pos);
337        self.nodes[leaf].values.remove(pos);
338        self.size = self.size.saturating_sub(1);
339        if self.size == 0 {
340            *self = Self::default();
341        }
342        true
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    fn v(n: i64) -> Value {
351        Value::Integer(n)
352    }
353
354    #[test]
355    fn insert_and_lookup() {
356        let mut t = BTree::default();
357        for i in 0..1000 {
358            t.insert(v(i), i as u64);
359        }
360        assert_eq!(t.size, 1000);
361        for i in (0..1000).rev() {
362            assert!(t.contains(&v(i), i as u64));
363            assert_eq!(t.lookup_eq(&v(i)), vec![i as u64]);
364        }
365    }
366
367    #[test]
368    fn range_scan_order() {
369        let mut t = BTree::default();
370        for i in 0..5000 {
371            t.insert(v((i * 7919) % 5000), i as u64); // pseudo-randomized
372        }
373        let res = t.range_scan(&v(100), &v(200));
374        assert_eq!(res.len(), 101);
375        // keys [100,200] each appear once
376    }
377
378    #[test]
379    fn duplicates_by_row_id() {
380        let mut t = BTree::default();
381        t.insert(v(5), 1);
382        t.insert(v(5), 2);
383        t.insert(v(5), 3);
384        assert_eq!(t.lookup_eq(&v(5)), vec![1, 2, 3]);
385        assert_eq!(t.size, 3);
386        t.delete(&v(5), 2);
387        assert_eq!(t.lookup_eq(&v(5)), vec![1, 3]);
388        assert_eq!(t.size, 2);
389    }
390
391    #[test]
392    fn duplicate_keys_survive_leaf_splits_and_out_of_order_ids() {
393        let mut t = BTree::default();
394        for row_id in (0..256u64).rev() {
395            t.insert(v(5), row_id);
396        }
397        assert_eq!(t.lookup_eq(&v(5)), (0..256u64).collect::<Vec<_>>());
398        assert!(t.contains(&v(5), 127));
399        assert!(t.delete(&v(5), 127));
400        assert!(!t.contains(&v(5), 127));
401        assert_eq!(t.lookup_eq(&v(5)).len(), 255);
402    }
403
404    #[test]
405    fn delete_leaves_and_reuses_tombstone_order() {
406        let mut t = BTree::default();
407        for row_id in 0..512u64 {
408            t.insert(v(row_id as i64), row_id);
409        }
410        for row_id in 0..512u64 {
411            assert!(t.delete(&v(row_id as i64), row_id));
412        }
413        for row_id in (0..128u64).rev() {
414            t.insert(v(9), row_id);
415        }
416        assert_eq!(t.lookup_eq(&v(9)), (0..128u64).collect::<Vec<_>>());
417    }
418
419    #[test]
420    fn delete_and_shrink() {
421        let mut t = BTree::default();
422        for i in 0..200 {
423            t.insert(v(i), i as u64);
424        }
425        for i in 0..200 {
426            assert!(t.delete(&v(i), i as u64));
427        }
428        assert!(t.is_empty());
429        t.insert(v(1), 1);
430        assert_eq!(t.lookup_eq(&v(1)), vec![1]);
431    }
432
433    #[test]
434    fn scan_all_order() {
435        let mut t = BTree::default();
436        let mut expect = Vec::new();
437        for i in 0..300 {
438            t.insert(v(i), i as u64);
439            expect.push((v(i), i as u64));
440        }
441        assert_eq!(t.scan_all(), expect);
442    }
443
444    #[test]
445    fn text_and_null_keys() {
446        let mut t = BTree::default();
447        t.insert(Value::Text("b".into()), 1);
448        t.insert(Value::Text("a".into()), 2);
449        t.insert(Value::Null, 3);
450        let all = t.scan_all();
451        assert_eq!(all[0].1, 3); // NULL sorts lowest
452        assert_eq!(all[1].1, 2);
453        assert_eq!(all[2].1, 1);
454    }
455
456    #[test]
457    fn randomized_delete_preserves_order_and_lookup() {
458        let mut tree = BTree::default();
459        for id in 0..2000u64 {
460            let key = ((id * 1103515245 + 12345) % 10000) as i64;
461            tree.insert(v(key), id);
462        }
463        let mut expected = tree.scan_all();
464        for id in (0..2000u64).step_by(3) {
465            let key = ((id * 1103515245 + 12345) % 10000) as i64;
466            assert!(tree.delete(&v(key), id));
467            expected.retain(|(_, row_id)| *row_id != id);
468        }
469        expected.sort_by(|left, right| cmp_entry(&left.0, left.1, &right.0, right.1));
470        assert_eq!(tree.scan_all(), expected);
471        for (key, id) in expected {
472            assert!(tree.contains(&key, id));
473        }
474    }
475
476    #[test]
477    fn mixed_insert_delete_matches_sorted_model() {
478        let mut tree = BTree::default();
479        let mut model: Vec<(Value, u64)> = Vec::new();
480        for step in 0..3000u64 {
481            let row_id = (step * 37) % 701;
482            let key = v(((step * 97) % 23) as i64);
483            if step % 3 == 0 {
484                let removed = tree.delete(&key, row_id);
485                let before = model.len();
486                model.retain(|entry| entry != &(key.clone(), row_id));
487                assert_eq!(removed, model.len() != before);
488            } else {
489                tree.insert(key.clone(), row_id);
490                if !model.contains(&(key, row_id)) {
491                    model.push((v(((step * 97) % 23) as i64), row_id));
492                }
493            }
494            model.sort_by(|left, right| cmp_entry(&left.0, left.1, &right.0, right.1));
495            assert_eq!(tree.scan_all(), model);
496        }
497    }
498}