Skip to main content

mnem_core/prolly/
cursor.rs

1//! Ordered cursor over a Prolly tree.
2//!
3//! [`Cursor`] yields `(ProllyKey, Cid)` pairs in ascending key order by
4//! walking the Merkle DAG depth-first along the left spine. Usable as a
5//! standard Rust [`Iterator`] (`for (k, v) in cursor { ... }`), so range
6//! scans and full enumeration both compose naturally.
7//!
8//! Memory: the cursor's stack holds at most one decoded [`Internal`] per
9//! tree level, plus the current leaf. For mnem's branching factor and
10//! typical graph scale, that's kilobytes - not megabytes.
11
12use crate::error::Error;
13use crate::id::Cid;
14use crate::prolly::constants::ProllyKey;
15use crate::prolly::tree::{Internal, Leaf, TreeChunk, load_tree_chunk};
16use crate::store::Blockstore;
17
18/// A forward-only cursor over the `(key, value)` entries of a Prolly tree.
19///
20/// Use [`Cursor::new`] to open, then drive via [`Iterator::next`] or a
21/// `for` loop.
22pub struct Cursor<'a, B: Blockstore + ?Sized> {
23    store: &'a B,
24    /// Open internal-node frames: `(chunk, next_child_idx)`.
25    /// `next_child_idx` is the index of the next unvisited child.
26    stack: Vec<(Internal, usize)>,
27    /// Currently loaded leaf and the index of the next unread entry.
28    current: Option<(Leaf, usize)>,
29}
30
31impl<'a, B: Blockstore + ?Sized> Cursor<'a, B> {
32    /// Open a cursor at the leftmost leaf of the tree rooted at `root`.
33    ///
34    /// # Errors
35    ///
36    /// Propagates store and codec errors.
37    pub fn new(store: &'a B, root: &Cid) -> Result<Self, Error> {
38        let mut c = Self {
39            store,
40            stack: Vec::new(),
41            current: None,
42        };
43        c.descend(root)?;
44        Ok(c)
45    }
46
47    /// Descend from `start` along the left spine until a leaf is reached,
48    /// pushing every internal node encountered onto the stack with
49    /// `next_child_idx = 1` (child 0 is the one we descended into).
50    fn descend(&mut self, start: &Cid) -> Result<(), Error> {
51        let mut cid = start.clone();
52        loop {
53            match load_tree_chunk(self.store, &cid)? {
54                TreeChunk::Leaf(leaf) => {
55                    self.current = Some((leaf, 0));
56                    return Ok(());
57                }
58                TreeChunk::Internal(internal) => {
59                    let next = internal.children[0].clone();
60                    self.stack.push((internal, 1));
61                    cid = next;
62                }
63            }
64        }
65    }
66
67    /// Move to the next leaf in key order, loading it into `self.current`.
68    /// Returns `Ok(false)` when the tree is fully traversed.
69    fn advance_leaf(&mut self) -> Result<bool, Error> {
70        loop {
71            let (internal, next_idx) = match self.stack.last_mut() {
72                Some(f) => f,
73                None => return Ok(false),
74            };
75            if *next_idx < internal.children.len() {
76                let next_cid = internal.children[*next_idx].clone();
77                *next_idx += 1;
78                self.descend(&next_cid)?;
79                return Ok(true);
80            }
81            self.stack.pop();
82        }
83    }
84}
85
86impl<B: Blockstore + ?Sized> Iterator for Cursor<'_, B> {
87    type Item = Result<(ProllyKey, Cid), Error>;
88
89    fn next(&mut self) -> Option<Self::Item> {
90        loop {
91            if let Some((leaf, idx)) = &mut self.current {
92                if *idx < leaf.entries.len() {
93                    let (k, v) = leaf.entries[*idx].clone();
94                    *idx += 1;
95                    return Some(Ok((k, v)));
96                }
97                self.current = None;
98            }
99            match self.advance_leaf() {
100                Ok(true) => continue, // outer loop reads from new leaf
101                Ok(false) => return None,
102                Err(e) => return Some(Err(e)),
103            }
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::id::{CODEC_RAW, Multihash};
112    use crate::prolly::tree::build_tree;
113    use crate::store::MemoryBlockstore;
114
115    fn keyed(i: u32) -> ProllyKey {
116        let mut k = [0u8; 16];
117        k[12..16].copy_from_slice(&i.to_be_bytes());
118        ProllyKey(k)
119    }
120
121    fn val(i: u32) -> Cid {
122        Cid::new(CODEC_RAW, Multihash::sha2_256(&i.to_be_bytes()))
123    }
124
125    #[test]
126    fn cursor_iterates_empty_tree_to_nothing() {
127        let store = MemoryBlockstore::new();
128        let root = build_tree(&store, std::iter::empty()).unwrap();
129        let cursor = Cursor::new(&store, &root).unwrap();
130        let collected: Vec<_> = cursor.map(|r| r.unwrap()).collect();
131        assert!(collected.is_empty());
132    }
133
134    #[test]
135    fn cursor_iterates_single_entry() {
136        let store = MemoryBlockstore::new();
137        let root = build_tree(&store, [(keyed(7), val(42))]).unwrap();
138        let cursor = Cursor::new(&store, &root).unwrap();
139        let collected: Vec<_> = cursor.map(|r| r.unwrap()).collect();
140        assert_eq!(collected, vec![(keyed(7), val(42))]);
141    }
142
143    #[test]
144    fn cursor_iterates_single_leaf_in_key_order() {
145        let store = MemoryBlockstore::new();
146        let entries: Vec<_> = (0..10u32).map(|i| (keyed(i), val(i))).collect();
147        let root = build_tree(&store, entries.clone()).unwrap();
148        let cursor = Cursor::new(&store, &root).unwrap();
149        let collected: Vec<_> = cursor.map(|r| r.unwrap()).collect();
150        assert_eq!(collected, entries);
151    }
152
153    #[test]
154    fn cursor_iterates_multi_level_tree_in_key_order() {
155        let store = MemoryBlockstore::new();
156        let entries: Vec<_> = (0..5_000u32).map(|i| (keyed(i), val(i))).collect();
157        let root = build_tree(&store, entries.clone()).unwrap();
158        let cursor = Cursor::new(&store, &root).unwrap();
159        let collected: Vec<_> = cursor.map(|r| r.unwrap()).collect();
160        assert_eq!(collected.len(), 5_000);
161        // Check sort order.
162        for w in collected.windows(2) {
163            assert!(
164                w[0].0 < w[1].0,
165                "out-of-order: {:?} then {:?}",
166                w[0].0,
167                w[1].0
168            );
169        }
170        assert_eq!(collected, entries);
171    }
172}