Skip to main content

noxu_db/
db_iter.rs

1//! Lazy iterator adapters for [`crate::Database`].
2//!
3//! Provides [`DbIter`] (full-scan, forward) and [`DbRange`] (key-range scan)
4//! as convenience wrappers around the underlying [`crate::Cursor`] API.
5//!
6//! # Design
7//!
8//! Both types implement `Iterator<Item = Result<(Vec<u8>, Vec<u8>)>>` and
9//! advance the cursor **lazily** — one record per `next()` call.  They do
10//! NOT eagerly materialise the scan into a `Vec` (that is the `StoredMap`
11//! anti-pattern flagged in audit-2026-05-jonhoo.md finding 2.2).
12//!
13//! # Example
14//!
15//! ```no_run
16//! use noxu_db::{DatabaseConfig, DatabaseEntry, Environment, EnvironmentConfig};
17//! use std::path::PathBuf;
18//!
19//! let env = Environment::open(
20//!     EnvironmentConfig::new(PathBuf::from("/tmp/iter_demo"))
21//!         .with_allow_create(true)
22//!         .with_transactional(true),
23//! )?;
24//! let db = env.open_database(
25//!     None,
26//!     "demo",
27//!     &DatabaseConfig::new().with_allow_create(true).with_transactional(true),
28//! )?;
29//!
30//! // Insert some records.
31//! for i in 0u32..5 {
32//!     db.put(None, &DatabaseEntry::from_bytes(&i.to_be_bytes()), &DatabaseEntry::from_bytes(b"v"))?;
33//! }
34//!
35//! // Forward scan — lazy.
36//! for result in db.iter(None)? {
37//!     let (key, val) = result?;
38//!     println!("{:?} => {:?}", key, val);
39//! }
40//!
41//! // Range scan — lazy.
42//! let lo = 1u32.to_be_bytes();
43//! let hi = 3u32.to_be_bytes();
44//! for result in db.range(None, lo.as_ref()..=hi.as_ref())? {
45//!     let (key, _val) = result?;
46//!     assert!(key.as_slice() >= lo.as_slice() && key.as_slice() <= hi.as_slice());
47//! }
48//!
49//! db.close()?;
50//! env.close()?;
51//! # Ok::<(), noxu_db::NoxuError>(())
52//! ```
53
54use crate::cursor::Cursor;
55use crate::database_entry::DatabaseEntry;
56use crate::error::{NoxuError, Result};
57use crate::get::Get;
58use crate::operation_status::OperationStatus;
59use crate::transaction::Transaction;
60use std::marker::PhantomData;
61use std::ops::Bound;
62
63// ── DbIter ────────────────────────────────────────────────────────────────────
64
65/// A forward-scanning iterator over all records in a database.
66///
67/// Returned by [`crate::Database::iter`].  Holds a live [`crate::Cursor`]; records are
68/// fetched one at a time (lazy) — the full database is **not** materialised
69/// into memory.
70///
71/// The lifetime `'txn` ensures the iterator cannot outlive the transaction
72/// it was opened against.  This prevents use-after-commit bugs at compile
73/// time: the borrow checker rejects any code that commits or drops the
74/// transaction while `DbIter` is still alive.
75///
76/// # Drop behaviour
77///
78/// Dropping the iterator closes the underlying cursor.  For transactional
79/// cursors this releases any shared read locks the cursor holds.
80pub struct DbIter<'txn> {
81    cursor: Cursor,
82    started: bool,
83    done: bool,
84    _txn: PhantomData<&'txn Transaction>,
85}
86
87impl<'txn> DbIter<'txn> {
88    pub(crate) fn new(cursor: Cursor) -> Self {
89        Self { cursor, started: false, done: false, _txn: PhantomData }
90    }
91}
92
93impl<'txn> Iterator for DbIter<'txn> {
94    type Item = Result<(Vec<u8>, Vec<u8>)>;
95
96    fn next(&mut self) -> Option<Self::Item> {
97        if self.done {
98            return None;
99        }
100        let get_type = if self.started { Get::Next } else { Get::First };
101        self.started = true;
102
103        let mut key = DatabaseEntry::new();
104        let mut val = DatabaseEntry::new();
105        match self.cursor.get(&mut key, &mut val, get_type, None) {
106            Err(e) => {
107                self.done = true;
108                Some(Err(e))
109            }
110            Ok(OperationStatus::Success) => {
111                let k = key.get_data().unwrap_or(&[]).to_vec();
112                let v = val.get_data().unwrap_or(&[]).to_vec();
113                Some(Ok((k, v)))
114            }
115            Ok(_) => {
116                self.done = true;
117                None
118            }
119        }
120    }
121}
122
123// ── DbRange ───────────────────────────────────────────────────────────────────
124
125/// A lazy key-range iterator over a database.
126///
127/// Returned by [`crate::Database::range`].  Holds a live [`crate::Cursor`] positioned at
128/// the first key ≥ `start_bound` and stops when the current key exceeds
129/// `end_bound`.  Records are fetched lazily — one per `next()` call.
130///
131/// The lifetime `'txn` ensures the iterator cannot outlive the transaction
132/// it was opened against.  See [`DbIter`] for the rationale.
133pub struct DbRange<'txn> {
134    cursor: Cursor,
135    end_bound: Bound<Vec<u8>>,
136    done: bool,
137    /// Whether the cursor has been positioned at the start yet.
138    positioned: bool,
139    start_key: Option<Vec<u8>>,
140    /// When true, skip a record whose key exactly equals `start_key` (Excluded bound).
141    exclude_start: bool,
142    _txn: PhantomData<&'txn Transaction>,
143}
144
145impl<'txn> DbRange<'txn> {
146    pub(crate) fn new(
147        cursor: Cursor,
148        start_bound: Bound<Vec<u8>>,
149        end_bound: Bound<Vec<u8>>,
150    ) -> Self {
151        let (start_key, exclude_start) = match start_bound {
152            Bound::Included(k) => (Some(k), false),
153            Bound::Excluded(k) => (Some(k), true),
154            Bound::Unbounded => (None, false),
155        };
156        Self {
157            cursor,
158            end_bound,
159            done: false,
160            positioned: false,
161            start_key,
162            exclude_start,
163            _txn: PhantomData,
164        }
165    }
166
167    fn past_end(&self, key: &[u8]) -> bool {
168        match &self.end_bound {
169            Bound::Unbounded => false,
170            Bound::Included(end) => key > end.as_slice(),
171            Bound::Excluded(end) => key >= end.as_slice(),
172        }
173    }
174}
175
176impl<'txn> Iterator for DbRange<'txn> {
177    type Item = Result<(Vec<u8>, Vec<u8>)>;
178
179    fn next(&mut self) -> Option<Self::Item> {
180        if self.done {
181            return None;
182        }
183
184        let mut key_entry = DatabaseEntry::new();
185        let mut val_entry = DatabaseEntry::new();
186
187        if !self.positioned {
188            self.positioned = true;
189            // Position the cursor at the start of the range.
190            let status = if let Some(ref sk) = self.start_key {
191                key_entry.set_data(sk);
192                self.cursor.get(
193                    &mut key_entry,
194                    &mut val_entry,
195                    Get::SearchGte,
196                    None,
197                )
198            } else {
199                self.cursor.get(
200                    &mut key_entry,
201                    &mut val_entry,
202                    Get::First,
203                    None,
204                )
205            };
206
207            match status {
208                Err(e) => {
209                    self.done = true;
210                    return Some(Err(e));
211                }
212                Ok(OperationStatus::Success) => {
213                    let k = key_entry.get_data().unwrap_or(&[]).to_vec();
214                    let v = val_entry.get_data().unwrap_or(&[]).to_vec();
215                    if self.past_end(&k) {
216                        self.done = true;
217                        return None;
218                    }
219                    // Excluded start: skip the exact start key.
220                    if self.exclude_start
221                        && self
222                            .start_key
223                            .as_ref()
224                            .is_some_and(|sk| k.as_slice() == sk.as_slice())
225                    {
226                        // Fall through to the Get::Next block below.
227                        self.positioned = true;
228                    } else {
229                        return Some(Ok((k, v)));
230                    }
231                }
232                Ok(_) => {
233                    self.done = true;
234                    return None;
235                }
236            }
237        }
238
239        // Subsequent calls: advance forward.
240        match self.cursor.get(&mut key_entry, &mut val_entry, Get::Next, None) {
241            Err(e) => {
242                self.done = true;
243                Some(Err(e))
244            }
245            Ok(OperationStatus::Success) => {
246                let k = key_entry.get_data().unwrap_or(&[]).to_vec();
247                let v = val_entry.get_data().unwrap_or(&[]).to_vec();
248                if self.past_end(&k) {
249                    self.done = true;
250                    None
251                } else {
252                    Some(Ok((k, v)))
253                }
254            }
255            Ok(_) => {
256                self.done = true;
257                None
258            }
259        }
260    }
261}