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}