Skip to main content

citadel_buffer/
cursor.rs

1//! B+ tree cursor for range iteration using a root-to-leaf path stack.
2
3use std::collections::HashMap;
4use std::hash::BuildHasher;
5use std::sync::Arc;
6
7use citadel_core::types::{PageId, PageType, ValueType};
8use citadel_core::{Error, Result};
9use citadel_page::leaf_node::LeafCell;
10use citadel_page::page::Page;
11use citadel_page::{branch_node, leaf_node};
12
13pub trait PageMap {
14    fn get_page(&self, id: &PageId) -> Option<&Page>;
15}
16
17/// Extends `PageMap` with on-demand page loading for lazy cursor traversal.
18pub trait PageLoader: PageMap {
19    fn ensure_loaded(&mut self, id: PageId) -> Result<()>;
20}
21
22impl<S: BuildHasher> PageMap for HashMap<PageId, Page, S> {
23    fn get_page(&self, id: &PageId) -> Option<&Page> {
24        self.get(id)
25    }
26}
27
28impl<S: BuildHasher> PageMap for HashMap<PageId, Arc<Page>, S> {
29    fn get_page(&self, id: &PageId) -> Option<&Page> {
30        self.get(id).map(|a| a.as_ref())
31    }
32}
33
34pub struct CursorEntry {
35    pub key: Vec<u8>,
36    pub val_type: ValueType,
37    pub value: Vec<u8>,
38}
39
40/// B+ tree cursor position with root-to-leaf path.
41pub struct Cursor {
42    /// Stack of (page_id, child_index) from root to current leaf's parent.
43    path: Vec<(PageId, usize)>,
44    /// Current leaf page ID.
45    leaf: PageId,
46    /// Current cell index within the leaf.
47    cell_idx: u16,
48    /// Whether the cursor is positioned at a valid entry.
49    valid: bool,
50}
51
52impl Cursor {
53    /// Seek to first key >= `key`. Empty key = first entry.
54    pub fn seek(pages: &impl PageMap, root: PageId, key: &[u8]) -> Result<Self> {
55        let mut path = Vec::new();
56        let mut current = root;
57
58        loop {
59            let page = pages
60                .get_page(&current)
61                .ok_or(Error::PageOutOfBounds(current))?;
62            match page.page_type() {
63                Some(PageType::Leaf) => break,
64                Some(PageType::Branch) => {
65                    let child_idx = branch_node::search_child_index(page, key);
66                    let child = branch_node::get_child(page, child_idx);
67                    path.push((current, child_idx));
68                    current = child;
69                }
70                _ => return Err(Error::InvalidPageType(page.page_type_raw(), current)),
71            }
72        }
73
74        let page = pages.get_page(&current).unwrap();
75        let cell_idx = match leaf_node::search(page, key) {
76            Ok(idx) => idx,
77            Err(idx) => idx,
78        };
79
80        let valid = cell_idx < page.num_cells();
81
82        let mut cursor = Self {
83            path,
84            leaf: current,
85            cell_idx,
86            valid,
87        };
88
89        // Landed past the end of this leaf: advance to next.
90        if !valid && page.num_cells() > 0 {
91            cursor.advance_leaf(pages)?;
92        } else if page.num_cells() == 0 {
93            cursor.valid = false;
94        }
95
96        Ok(cursor)
97    }
98
99    /// Position the cursor at the first entry in the tree.
100    pub fn first(pages: &impl PageMap, root: PageId) -> Result<Self> {
101        let mut path = Vec::new();
102        let mut current = root;
103
104        loop {
105            let page = pages
106                .get_page(&current)
107                .ok_or(Error::PageOutOfBounds(current))?;
108            match page.page_type() {
109                Some(PageType::Leaf) => break,
110                Some(PageType::Branch) => {
111                    let child = branch_node::get_child(page, 0);
112                    path.push((current, 0));
113                    current = child;
114                }
115                _ => return Err(Error::InvalidPageType(page.page_type_raw(), current)),
116            }
117        }
118
119        let page = pages.get_page(&current).unwrap();
120        let valid = page.num_cells() > 0;
121
122        Ok(Self {
123            path,
124            leaf: current,
125            cell_idx: 0,
126            valid,
127        })
128    }
129
130    /// Position the cursor at the last entry in the tree.
131    pub fn last(pages: &impl PageMap, root: PageId) -> Result<Self> {
132        let mut path = Vec::new();
133        let mut current = root;
134
135        loop {
136            let page = pages
137                .get_page(&current)
138                .ok_or(Error::PageOutOfBounds(current))?;
139            match page.page_type() {
140                Some(PageType::Leaf) => break,
141                Some(PageType::Branch) => {
142                    let n = page.num_cells() as usize;
143                    let child = page.right_child();
144                    path.push((current, n));
145                    current = child;
146                }
147                _ => return Err(Error::InvalidPageType(page.page_type_raw(), current)),
148            }
149        }
150
151        let page = pages.get_page(&current).unwrap();
152        let n = page.num_cells();
153        let valid = n > 0;
154        let cell_idx = if valid { n - 1 } else { 0 };
155
156        Ok(Self {
157            path,
158            leaf: current,
159            cell_idx,
160            valid,
161        })
162    }
163
164    /// Whether the cursor is at a valid position.
165    pub fn is_valid(&self) -> bool {
166        self.valid
167    }
168
169    /// Current leaf page ID.
170    pub fn leaf_page_id(&self) -> PageId {
171        self.leaf
172    }
173
174    /// Current cell index within the leaf.
175    pub fn cell_index(&self) -> u16 {
176        self.cell_idx
177    }
178
179    /// Update the current leaf page ID after a CoW operation.
180    pub fn set_leaf_page_id(&mut self, id: PageId) {
181        self.leaf = id;
182    }
183
184    pub fn set_cell_index(&mut self, idx: u16) {
185        self.cell_idx = idx;
186    }
187
188    pub fn advance_to_next_leaf<P: PageLoader + ?Sized>(&mut self, pages: &mut P) -> Result<bool> {
189        self.advance_leaf_lazy(pages)
190    }
191
192    pub fn current(&self, pages: &impl PageMap) -> Option<CursorEntry> {
193        if !self.valid {
194            return None;
195        }
196        let page = pages.get_page(&self.leaf)?;
197        let cell = leaf_node::read_cell(page, self.cell_idx);
198        Some(CursorEntry {
199            key: cell.key.to_vec(),
200            val_type: cell.val_type,
201            value: cell.value.to_vec(),
202        })
203    }
204
205    pub fn current_ref<'a, P: PageMap>(&self, pages: &'a P) -> Option<LeafCell<'a>> {
206        if !self.valid {
207            return None;
208        }
209        let page = pages.get_page(&self.leaf)?;
210        Some(leaf_node::read_cell(page, self.cell_idx))
211    }
212
213    /// Move the cursor to the next entry (forward).
214    pub fn next(&mut self, pages: &impl PageMap) -> Result<bool> {
215        if !self.valid {
216            return Ok(false);
217        }
218
219        let page = pages
220            .get_page(&self.leaf)
221            .ok_or(Error::PageOutOfBounds(self.leaf))?;
222
223        if self.cell_idx + 1 < page.num_cells() {
224            self.cell_idx += 1;
225            return Ok(true);
226        }
227
228        self.advance_leaf(pages)
229    }
230
231    /// Move the cursor to the previous entry (backward).
232    pub fn prev(&mut self, pages: &impl PageMap) -> Result<bool> {
233        if !self.valid {
234            return Ok(false);
235        }
236
237        if self.cell_idx > 0 {
238            self.cell_idx -= 1;
239            return Ok(true);
240        }
241
242        self.retreat_leaf(pages)
243    }
244
245    /// Advance to the first cell of the next leaf.
246    fn advance_leaf(&mut self, pages: &impl PageMap) -> Result<bool> {
247        while let Some((parent_id, child_idx)) = self.path.pop() {
248            let parent = pages
249                .get_page(&parent_id)
250                .ok_or(Error::PageOutOfBounds(parent_id))?;
251            let n = parent.num_cells() as usize;
252
253            if child_idx < n {
254                let next_child_idx = child_idx + 1;
255                let next_child = branch_node::get_child(parent, next_child_idx);
256                self.path.push((parent_id, next_child_idx));
257
258                let mut current = next_child;
259                loop {
260                    let page = pages
261                        .get_page(&current)
262                        .ok_or(Error::PageOutOfBounds(current))?;
263                    match page.page_type() {
264                        Some(PageType::Leaf) => {
265                            self.leaf = current;
266                            self.cell_idx = 0;
267                            self.valid = page.num_cells() > 0;
268                            return Ok(self.valid);
269                        }
270                        Some(PageType::Branch) => {
271                            let child = branch_node::get_child(page, 0);
272                            self.path.push((current, 0));
273                            current = child;
274                        }
275                        _ => return Err(Error::InvalidPageType(page.page_type_raw(), current)),
276                    }
277                }
278            }
279            // child_idx == num_cells (rightmost child) - keep going up
280        }
281
282        // No more siblings - we've exhausted the tree
283        self.valid = false;
284        Ok(false)
285    }
286
287    /// Seek with lazy page loading — only loads the root-to-leaf path.
288    pub fn seek_lazy(pages: &mut impl PageLoader, root: PageId, key: &[u8]) -> Result<Self> {
289        let mut path = Vec::new();
290        let mut current = root;
291
292        loop {
293            pages.ensure_loaded(current)?;
294            let page = pages
295                .get_page(&current)
296                .ok_or(Error::PageOutOfBounds(current))?;
297            match page.page_type() {
298                Some(PageType::Leaf) => break,
299                Some(PageType::Branch) => {
300                    let child_idx = branch_node::search_child_index(page, key);
301                    let child = branch_node::get_child(page, child_idx);
302                    path.push((current, child_idx));
303                    current = child;
304                }
305                _ => return Err(Error::InvalidPageType(page.page_type_raw(), current)),
306            }
307        }
308
309        let page = pages.get_page(&current).unwrap();
310        let cell_idx = match leaf_node::search(page, key) {
311            Ok(idx) => idx,
312            Err(idx) => idx,
313        };
314
315        let valid = cell_idx < page.num_cells();
316
317        let mut cursor = Self {
318            path,
319            leaf: current,
320            cell_idx,
321            valid,
322        };
323
324        if !valid && page.num_cells() > 0 {
325            cursor.advance_leaf_lazy(pages)?;
326        } else if page.num_cells() == 0 {
327            cursor.valid = false;
328        }
329
330        Ok(cursor)
331    }
332
333    /// Read the current entry, loading the leaf page if needed.
334    pub fn current_ref_lazy<'a, P: PageLoader + ?Sized>(
335        &self,
336        pages: &'a mut P,
337    ) -> Option<LeafCell<'a>> {
338        if !self.valid {
339            return None;
340        }
341        pages.ensure_loaded(self.leaf).ok()?;
342        let page = pages.get_page(&self.leaf)?;
343        Some(leaf_node::read_cell(page, self.cell_idx))
344    }
345
346    /// Advance to the next entry, loading pages on demand.
347    pub fn next_lazy<P: PageLoader + ?Sized>(&mut self, pages: &mut P) -> Result<bool> {
348        if !self.valid {
349            return Ok(false);
350        }
351
352        pages.ensure_loaded(self.leaf)?;
353        let page = pages
354            .get_page(&self.leaf)
355            .ok_or(Error::PageOutOfBounds(self.leaf))?;
356
357        if self.cell_idx + 1 < page.num_cells() {
358            self.cell_idx += 1;
359            return Ok(true);
360        }
361
362        self.advance_leaf_lazy(pages)
363    }
364
365    /// Advance to the next leaf, loading child pages on demand.
366    fn advance_leaf_lazy<P: PageLoader + ?Sized>(&mut self, pages: &mut P) -> Result<bool> {
367        while let Some((parent_id, child_idx)) = self.path.pop() {
368            let parent = pages
369                .get_page(&parent_id)
370                .ok_or(Error::PageOutOfBounds(parent_id))?;
371            let n = parent.num_cells() as usize;
372
373            if child_idx < n {
374                let next_child_idx = child_idx + 1;
375                let next_child = branch_node::get_child(parent, next_child_idx);
376                self.path.push((parent_id, next_child_idx));
377
378                let mut current = next_child;
379                loop {
380                    pages.ensure_loaded(current)?;
381                    let page = pages
382                        .get_page(&current)
383                        .ok_or(Error::PageOutOfBounds(current))?;
384                    match page.page_type() {
385                        Some(PageType::Leaf) => {
386                            self.leaf = current;
387                            self.cell_idx = 0;
388                            self.valid = page.num_cells() > 0;
389                            return Ok(self.valid);
390                        }
391                        Some(PageType::Branch) => {
392                            let child = branch_node::get_child(page, 0);
393                            self.path.push((current, 0));
394                            current = child;
395                        }
396                        _ => return Err(Error::InvalidPageType(page.page_type_raw(), current)),
397                    }
398                }
399            }
400        }
401
402        self.valid = false;
403        Ok(false)
404    }
405
406    /// Retreat to the last cell of the previous leaf.
407    fn retreat_leaf(&mut self, pages: &impl PageMap) -> Result<bool> {
408        while let Some((parent_id, child_idx)) = self.path.pop() {
409            if child_idx > 0 {
410                let prev_child_idx = child_idx - 1;
411                let parent = pages
412                    .get_page(&parent_id)
413                    .ok_or(Error::PageOutOfBounds(parent_id))?;
414                let prev_child = branch_node::get_child(parent, prev_child_idx);
415                self.path.push((parent_id, prev_child_idx));
416
417                let mut current = prev_child;
418                loop {
419                    let page = pages
420                        .get_page(&current)
421                        .ok_or(Error::PageOutOfBounds(current))?;
422                    match page.page_type() {
423                        Some(PageType::Leaf) => {
424                            self.leaf = current;
425                            let n = page.num_cells();
426                            if n > 0 {
427                                self.cell_idx = n - 1;
428                                self.valid = true;
429                            } else {
430                                self.valid = false;
431                            }
432                            return Ok(self.valid);
433                        }
434                        Some(PageType::Branch) => {
435                            let n = page.num_cells() as usize;
436                            let child = page.right_child();
437                            self.path.push((current, n));
438                            current = child;
439                        }
440                        _ => return Err(Error::InvalidPageType(page.page_type_raw(), current)),
441                    }
442                }
443            }
444            // child_idx == 0 (leftmost child) - keep going up
445        }
446
447        // No more siblings - we've exhausted the tree
448        self.valid = false;
449        Ok(false)
450    }
451}
452
453#[cfg(test)]
454#[path = "cursor_tests.rs"]
455mod tests;