Skip to main content

noxu_db/
cursor.rs

1//! Cursor handle for Noxu DB.
2//!
3
4use crate::database_entry::DatabaseEntry;
5use crate::error::{NoxuError, Result};
6use crate::get::Get;
7use crate::lock_mode::LockMode;
8use crate::operation_status::OperationStatus;
9use crate::put::Put;
10use noxu_dbi::{CursorImpl, DbiError, GetMode, PutMode, SearchMode};
11
12/// Map a `DbiError` from a cursor inner operation to the appropriate
13/// public `NoxuError`.
14///
15/// `EnvironmentFailure` propagates as-is (via `From<DbiError>`) so the
16/// caller sees `NoxuError::EnvironmentFailure` rather than
17/// `NoxuError::OperationNotAllowed`. All other errors are wrapped as
18/// `OperationNotAllowed` to preserve backward compatibility.  X-13 fix.
19#[inline]
20fn map_cursor_err(e: DbiError) -> NoxuError {
21    match e {
22        DbiError::EnvironmentFailure { .. } => NoxuError::from(e),
23        _ => NoxuError::OperationNotAllowed(e.to_string()),
24    }
25}
26
27/// Cursor state.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum CursorState {
30    /// Cursor has not been positioned yet.
31    NotInitialized,
32    /// Cursor is positioned on a record.
33    Initialized,
34    /// Cursor has been closed.
35    Closed,
36}
37
38/// A database cursor for iterating over records.
39///
40///
41///
42/// Cursors are used for operating on collections of records,
43/// for iterating over a database, and for saving handles to individual
44/// records so they can be modified after reading.
45///
46/// # Example
47/// ```ignore
48/// use noxu_db::{Database, DatabaseEntry, Get};
49///
50/// # fn example(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
51/// let mut cursor = db.open_cursor(None, None)?;
52/// let mut key = DatabaseEntry::new();
53/// let mut data = DatabaseEntry::new();
54///
55/// // Iterate through all records
56/// while cursor.get(&mut key, &mut data, Get::Next, None)? == OperationStatus::Success {
57///     // Process key and data
58/// }
59///
60/// cursor.close()?;
61/// # Ok(())
62/// # }
63/// ```
64pub struct Cursor {
65    /// Underlying CursorImpl from the dbi layer.
66    inner: CursorImpl,
67    /// Current cursor state.
68    state: CursorState,
69    /// Whether this cursor is read-only.
70    read_only: bool,
71}
72
73impl Cursor {
74    /// Creates a Cursor wrapping a `CursorImpl`.
75    ///
76    /// Called by `Database::open_cursor`.
77    pub(crate) fn from_impl(inner: CursorImpl, read_only: bool) -> Self {
78        Self { inner, state: CursorState::NotInitialized, read_only }
79    }
80
81    /// Retrieve a record using the cursor.
82    ///
83    /// # Arguments
84    /// * `key` — search input (for `Get::Search` / `Get::SearchGte`) or
85    ///   the output buffer that receives the discovered key (for
86    ///   iteration / range search variants).
87    /// * `data` — output buffer for the record's value.
88    /// * `get_type` — selects the navigation primitive (see [`Get`]).
89    /// * `lock_mode` — reserved for per-operation isolation overrides
90    ///   (e.g., dirty reads inside an otherwise-serializable txn).
91    ///   The current implementation ignores this argument and uses the
92    ///   surrounding transaction's isolation level (set on
93    ///   [`crate::transaction_config::TransactionConfig`]).  Per-call
94    ///   read-uncommitted is not yet implemented; pass `None` for now.
95    ///
96    /// # Returns
97    /// * `OperationStatus::Success` if the cursor positioned on a record.
98    /// * `OperationStatus::NotFound` if no record satisfied the request.
99    ///
100    /// # Errors
101    /// * [`NoxuError::DatabaseClosed`] if the underlying database has
102    ///   been closed.
103    /// * [`NoxuError::OperationNotAllowed`] if the cursor was passed a
104    ///   `Get::Current` request before being positioned by an earlier
105    ///   call, or if an underlying B-tree operation rejected the
106    ///   request.
107    pub fn get(
108        &mut self,
109        key: &mut DatabaseEntry,
110        data: &mut DatabaseEntry,
111        get_type: Get,
112        _lock_mode: Option<LockMode>,
113    ) -> Result<OperationStatus> {
114        self.check_open()?;
115
116        if matches!(get_type, Get::Current) {
117            self.check_initialized()?;
118        }
119
120        let status = match get_type {
121            Get::Search => {
122                // Audit cursor F10 (Wave 2C-4): empty keys are accepted as a
123                // first-class input and forwarded to the inner search,
124                // which simply reports `NotFound` because no record can
125                // exist under an empty key on a writable database.
126                let key_bytes = key.get_data().unwrap_or(&[]);
127                self.inner
128                    .search(key_bytes, None, SearchMode::Set)
129                    .map_err(map_cursor_err)?
130            }
131            Get::SearchGte | Get::SearchRange => {
132                let key_bytes = key.get_data().unwrap_or(&[]);
133                self.inner
134                    .search(key_bytes, None, SearchMode::SetRange)
135                    .map_err(map_cursor_err)?
136            }
137            Get::First => self.inner.get_first().map_err(map_cursor_err)?,
138            Get::Last => self.inner.get_last().map_err(map_cursor_err)?,
139            Get::Next => {
140                if self.state == CursorState::NotInitialized {
141                    // Next from uninitialized positions at the first record.
142                    self.inner.get_first().map_err(map_cursor_err)?
143                } else {
144                    self.inner
145                        .retrieve_next(GetMode::Next)
146                        .map_err(map_cursor_err)?
147                }
148            }
149            Get::Prev => {
150                if self.state == CursorState::NotInitialized {
151                    // Prev from uninitialized positions at the last record.
152                    self.inner.get_last().map_err(map_cursor_err)?
153                } else {
154                    self.inner
155                        .retrieve_next(GetMode::Prev)
156                        .map_err(map_cursor_err)?
157                }
158            }
159            Get::NextDup => {
160                self.check_initialized()?;
161                self.inner
162                    .retrieve_next(GetMode::NextDup)
163                    .map_err(map_cursor_err)?
164            }
165            Get::PrevDup => {
166                self.check_initialized()?;
167                self.inner
168                    .retrieve_next(GetMode::PrevDup)
169                    .map_err(map_cursor_err)?
170            }
171            Get::NextNoDup => {
172                if self.state == CursorState::NotInitialized {
173                    self.inner.get_first().map_err(map_cursor_err)?
174                } else {
175                    self.inner
176                        .retrieve_next(GetMode::NextNoDup)
177                        .map_err(map_cursor_err)?
178                }
179            }
180            Get::PrevNoDup => {
181                if self.state == CursorState::NotInitialized {
182                    self.inner.get_last().map_err(map_cursor_err)?
183                } else {
184                    self.inner
185                        .retrieve_next(GetMode::PrevNoDup)
186                        .map_err(map_cursor_err)?
187                }
188            }
189            Get::SearchBoth => {
190                let key_bytes = key.get_data().unwrap_or(&[]);
191                let data_bytes = data.get_data();
192                self.inner
193                    .search(key_bytes, data_bytes, SearchMode::Both)
194                    .map_err(map_cursor_err)?
195            }
196            // Audit cursor F12 (Wave 2C-4): expose `SearchBothRange` to the
197            // public API; the inner `SearchMode::BothRange` was already
198            // implemented in `noxu-dbi` but unreachable from `Cursor::get`.
199            Get::SearchBothRange => {
200                let key_bytes = key.get_data().unwrap_or(&[]);
201                let data_bytes = data.get_data();
202                self.inner
203                    .search(key_bytes, data_bytes, SearchMode::BothRange)
204                    .map_err(map_cursor_err)?
205            }
206            Get::Current => {
207                // Already checked initialized above.
208                // : re-check for deletion — Cursor.getCurrentLN() returns
209                // KEYEMPTY when the record at the cursor position was deleted
210                // after the cursor was positioned.
211                if self.inner.is_current_slot_deleted() {
212                    return Ok(OperationStatus::NotFound);
213                }
214                let (k, v) =
215                    self.inner.get_current().map_err(map_cursor_err)?;
216                data.set_data(&v);
217                key.set_data(&k);
218                self.state = CursorState::Initialized;
219                return Ok(OperationStatus::Success);
220            }
221            // Audit Finding 3: these three Get variants are recognised by the
222            // public API and have rustdoc claiming working semantics, but no
223            // implementation has been wired up.  Pre-fix they fell through to
224            // a wildcard `_ => Ok(NotFound)` arm, silently misleading users
225            // who could not distinguish "no such record" from "this operator
226            // is not implemented".  Surface a loud, typed error instead.
227            // See `docs/src/internal/api-audit-2026-05-cursor.md`.
228            //
229            // Implementing these properly is tracked for a later sprint.
230            Get::SearchLte => {
231                return Err(NoxuError::Unsupported(
232                    "Get::SearchLte".to_string(),
233                ));
234            }
235            Get::FirstDup => {
236                return Err(NoxuError::Unsupported(
237                    "Get::FirstDup".to_string(),
238                ));
239            }
240            Get::LastDup => {
241                return Err(NoxuError::Unsupported("Get::LastDup".to_string()));
242            }
243        };
244
245        match status {
246            noxu_dbi::OperationStatus::Success => {
247                let (k, v) =
248                    self.inner.get_current().map_err(map_cursor_err)?;
249                data.set_data(&v);
250                // Write back the current key for navigation operations.
251                // `key` is always an output parameter for positioning ops.
252                key.set_data(&k);
253                self.state = CursorState::Initialized;
254                Ok(OperationStatus::Success)
255            }
256            _ => {
257                if matches!(
258                    get_type,
259                    Get::First
260                        | Get::Last
261                        | Get::Search
262                        | Get::SearchGte
263                        | Get::SearchRange
264                        | Get::SearchBoth
265                        | Get::SearchBothRange
266                ) {
267                    self.state = CursorState::NotInitialized;
268                }
269                Ok(OperationStatus::NotFound)
270            }
271        }
272    }
273
274    /// Store a record using the cursor.
275    ///
276    /// # Arguments
277    /// * `key` - Key to store
278    /// * `data` - Data to store
279    /// * `put_type` - Type of put operation
280    ///
281    /// Store a record using the cursor.
282    ///
283    /// # Arguments
284    /// * `key` — the record's key.  Empty keys (`get_data()` returns
285    ///   `None` or an empty slice) are forwarded to the underlying
286    ///   B-tree which rejects them on writable databases.
287    /// * `data` — the record's value.
288    /// * `put_type` — see [`Put`] for the per-mode semantics.
289    ///
290    /// # Returns
291    /// * `OperationStatus::Success` if the record was inserted or updated.
292    /// * `OperationStatus::KeyExists` for `Put::NoOverwrite` /
293    ///   `Put::NoDupData` when the key (or `(key, data)` pair under
294    ///   sorted-dup) already exists.
295    ///
296    /// # Errors
297    /// * [`NoxuError::DatabaseClosed`] if the underlying database has
298    ///   been closed.
299    /// * [`NoxuError::OperationNotAllowed`] if the cursor was opened
300    ///   read-only, or if the call uses
301    ///   `Put::Current` before the cursor was positioned.
302    pub fn put(
303        &mut self,
304        key: &DatabaseEntry,
305        data: &DatabaseEntry,
306        put_type: Put,
307    ) -> Result<OperationStatus> {
308        self.check_open()?;
309
310        if self.read_only {
311            return Err(NoxuError::OperationNotAllowed(
312                "Cannot write with a read-only cursor".to_string(),
313            ));
314        }
315
316        let key_bytes = key.get_data().unwrap_or(&[]);
317        let data_bytes = data.get_data().unwrap_or(&[]);
318
319        let put_mode = match put_type {
320            Put::Overwrite => PutMode::Overwrite,
321            Put::NoOverwrite => PutMode::NoOverwrite,
322            // NoDupData inserts only if the exact (key, data) pair does not
323            // already exist.  For sorted-dup databases this checks the full
324            // two-part composite key; for non-dup databases it behaves
325            // identically to NoOverwrite.
326            Put::NoDupData => PutMode::NoDupData,
327            Put::Current => {
328                self.check_initialized()?;
329                PutMode::Current
330            }
331        };
332
333        match self
334            .inner
335            .put(key_bytes, data_bytes, put_mode)
336            .map_err(map_cursor_err)?
337        {
338            noxu_dbi::OperationStatus::KeyExist => {
339                Ok(OperationStatus::KeyExists)
340            }
341            _ => {
342                self.state = CursorState::Initialized;
343                Ok(OperationStatus::Success)
344            }
345        }
346    }
347
348    /// Delete the record at the current cursor position.
349    ///
350    /// # Returns
351    /// `OperationStatus::Success` if the record was deleted,
352    /// `OperationStatus::NotFound` if the cursor is not positioned.
353    pub fn delete(&mut self) -> Result<OperationStatus> {
354        self.check_open()?;
355        self.check_initialized()?;
356
357        if self.read_only {
358            return Err(NoxuError::OperationNotAllowed(
359                "Cannot delete with a read-only cursor".to_string(),
360            ));
361        }
362
363        self.inner.delete().map_err(map_cursor_err)?;
364        self.state = CursorState::NotInitialized;
365        Ok(OperationStatus::Success)
366    }
367
368    /// Count the number of records with the same key.
369    ///
370    /// For non-duplicate databases this returns `1` when positioned and
371    /// `0` otherwise.  For sorted-dup databases it returns the number
372    /// of duplicate values stored under the cursor's current key.
373    ///
374    /// # Returns
375    /// The count of records, or `0` if the cursor is not currently
376    /// positioned on a record.
377    ///
378    /// # Errors
379    /// * [`NoxuError::DatabaseClosed`] if the underlying database has
380    ///   been closed.
381    /// * [`NoxuError::OperationNotAllowed`] if the underlying B-tree
382    ///   count operation fails (e.g., the cursor was invalidated by a
383    ///   concurrent close).
384    pub fn count(&self) -> Result<u64> {
385        self.check_open()?;
386
387        if self.state != CursorState::Initialized {
388            return Ok(0);
389        }
390
391        // Audit cursor F16 (Wave 2C-4): drop the previous `.max(1)`
392        // clamp.  The inner `count()` always returns at least 1 when the
393        // cursor is positioned (one record at minimum); a 0 from the
394        // inner is therefore a real bug and must surface, not be silently
395        // promoted to 1.
396        let n = self.inner.count().map_err(map_cursor_err)?;
397        if n < 1 {
398            return Err(NoxuError::OperationNotAllowed(format!(
399                "cursor count() returned {n} while positioned (invariant violated)",
400            )));
401        }
402        Ok(n as u64)
403    }
404
405    /// Close the cursor.
406    ///
407    /// The cursor handle may not be used again after this call.  Any
408    /// subsequent navigation / mutation operation returns
409    /// [`NoxuError::OperationNotAllowed`].
410    ///
411    /// `close()` itself is idempotent: calling it more than once is a
412    /// no-op and returns `Ok(())`.  This matches BDB-JE's
413    /// `Cursor.close()` contract — calling it more than once is safe.
414    ///
415    /// # Errors
416    /// Returns the inner-cursor close error if the underlying
417    /// `CursorImpl::close` fails — currently the inner close is
418    /// infallible after the first call, so this only surfaces internal
419    /// invariant violations.
420    pub fn close(&mut self) -> Result<()> {
421        if self.state == CursorState::Closed {
422            return Ok(());
423        }
424        self.state = CursorState::Closed;
425        // Audit cursor F14 (Wave 2C-4): propagate close to the inner
426        // CursorImpl so its BIN pin is released immediately rather than
427        // at outer-`Cursor::Drop` time.
428        self.inner.close().map_err(map_cursor_err)
429    }
430
431    /// Check if the cursor is valid (not closed).
432    pub fn is_valid(&self) -> bool {
433        self.state != CursorState::Closed
434    }
435
436    /// Get the current cursor state.
437    pub fn get_state(&self) -> CursorState {
438        self.state
439    }
440
441    /// Check if the cursor is read-only.
442    pub fn is_read_only(&self) -> bool {
443        self.read_only
444    }
445
446    /// Check that the cursor is not closed.
447    fn check_open(&self) -> Result<()> {
448        if self.state == CursorState::Closed {
449            Err(NoxuError::OperationNotAllowed(
450                "Cursor has been closed".to_string(),
451            ))
452        } else {
453            Ok(())
454        }
455    }
456
457    /// Check that the cursor is initialized (positioned on a record).
458    fn check_initialized(&self) -> Result<()> {
459        if self.state != CursorState::Initialized {
460            Err(NoxuError::OperationNotAllowed(
461                "Cursor is not positioned on a record".to_string(),
462            ))
463        } else {
464            Ok(())
465        }
466    }
467}
468
469impl Drop for Cursor {
470    fn drop(&mut self) {
471        // Audit cursor F15 (Wave 2C-4): only warn for genuinely-leaked
472        // cursors that were positioned on a record at drop time.
473        // `NotInitialized` covers freshly-opened cursors and cursors
474        // that just had their record deleted (Finding 7); warning in
475        // those cases is noise that masks real leaks.
476        if self.state == CursorState::Initialized {
477            log::warn!("Cursor dropped without close (still positioned)");
478        }
479        // Best-effort close of the inner CursorImpl — its own Drop will
480        // also release the BIN pin, but this gives us symmetric cleanup
481        // semantics for cursors that were never explicitly closed.
482        if self.state != CursorState::Closed {
483            let _ = self.inner.close();
484            self.state = CursorState::Closed;
485        }
486    }
487}
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492    use noxu_dbi::{
493        DatabaseConfig as DbiDatabaseConfig, DatabaseId, DatabaseImpl, DbType,
494    };
495    use noxu_sync::RwLock;
496    use std::sync::Arc;
497
498    /// Creates a fresh in-memory DatabaseImpl and wraps it in a Cursor.
499    fn make_cursor(read_only: bool) -> Cursor {
500        let db_id = DatabaseId::new(1);
501        let config = DbiDatabaseConfig::default();
502        let db_impl =
503            DatabaseImpl::new(db_id, "test".to_string(), DbType::User, &config);
504        let db_arc = Arc::new(RwLock::new(db_impl));
505        let inner = CursorImpl::new(db_arc, 0);
506        Cursor::from_impl(inner, read_only)
507    }
508
509    /// Creates a cursor backed by a DatabaseImpl pre-populated with records.
510    fn make_cursor_with(records: Vec<(&[u8], &[u8])>) -> Cursor {
511        let db_id = DatabaseId::new(1);
512        let config = DbiDatabaseConfig::default();
513        let db_impl =
514            DatabaseImpl::new(db_id, "test".to_string(), DbType::User, &config);
515        let db_arc = Arc::new(RwLock::new(db_impl));
516
517        {
518            let mut tmp = CursorImpl::new(Arc::clone(&db_arc), 0);
519            for (k, v) in &records {
520                tmp.put(k, v, PutMode::Overwrite).unwrap();
521            }
522        }
523
524        let inner = CursorImpl::new(db_arc, 0);
525        Cursor::from_impl(inner, false)
526    }
527
528    #[test]
529    fn test_new_cursor() {
530        let cursor = make_cursor(false);
531        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
532        assert!(cursor.is_valid());
533        assert!(!cursor.is_read_only());
534    }
535
536    #[test]
537    fn test_read_only_cursor() {
538        let cursor = make_cursor(true);
539        assert!(cursor.is_read_only());
540    }
541
542    #[test]
543    fn test_search() {
544        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
545        let mut key = DatabaseEntry::from_bytes(b"key1");
546        let mut data = DatabaseEntry::new();
547
548        let status =
549            cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
550        assert_eq!(status, OperationStatus::Success);
551        assert_eq!(data.get_data().unwrap(), b"value1");
552        assert_eq!(cursor.get_state(), CursorState::Initialized);
553    }
554
555    #[test]
556    fn test_search_not_found() {
557        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
558        let mut key = DatabaseEntry::from_bytes(b"key2");
559        let mut data = DatabaseEntry::new();
560
561        let status =
562            cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
563        assert_eq!(status, OperationStatus::NotFound);
564    }
565
566    #[test]
567    fn test_first() {
568        let mut cursor = make_cursor_with(vec![
569            (b"key3", b"value3"),
570            (b"key1", b"value1"),
571            (b"key2", b"value2"),
572        ]);
573        let mut key = DatabaseEntry::new();
574        let mut data = DatabaseEntry::new();
575
576        let status = cursor.get(&mut key, &mut data, Get::First, None).unwrap();
577        assert_eq!(status, OperationStatus::Success);
578        assert_eq!(data.get_data().unwrap(), b"value1");
579        assert_eq!(cursor.get_state(), CursorState::Initialized);
580    }
581
582    #[test]
583    fn test_last() {
584        let mut cursor = make_cursor_with(vec![
585            (b"key3", b"value3"),
586            (b"key1", b"value1"),
587            (b"key2", b"value2"),
588        ]);
589        let mut key = DatabaseEntry::new();
590        let mut data = DatabaseEntry::new();
591
592        let status = cursor.get(&mut key, &mut data, Get::Last, None).unwrap();
593        assert_eq!(status, OperationStatus::Success);
594        assert_eq!(data.get_data().unwrap(), b"value3");
595    }
596
597    #[test]
598    fn test_next_iteration() {
599        let mut cursor = make_cursor_with(vec![
600            (b"key3", b"value3"),
601            (b"key1", b"value1"),
602            (b"key2", b"value2"),
603        ]);
604        let mut key = DatabaseEntry::new();
605        let mut data = DatabaseEntry::new();
606
607        let status = cursor.get(&mut key, &mut data, Get::First, None).unwrap();
608        assert_eq!(status, OperationStatus::Success);
609        assert_eq!(data.get_data().unwrap(), b"value1");
610
611        let status = cursor.get(&mut key, &mut data, Get::Next, None).unwrap();
612        assert_eq!(status, OperationStatus::Success);
613        assert_eq!(data.get_data().unwrap(), b"value2");
614
615        let status = cursor.get(&mut key, &mut data, Get::Next, None).unwrap();
616        assert_eq!(status, OperationStatus::Success);
617        assert_eq!(data.get_data().unwrap(), b"value3");
618
619        let status = cursor.get(&mut key, &mut data, Get::Next, None).unwrap();
620        assert_eq!(status, OperationStatus::NotFound);
621    }
622
623    #[test]
624    fn test_prev_iteration() {
625        let mut cursor = make_cursor_with(vec![
626            (b"key3", b"value3"),
627            (b"key1", b"value1"),
628            (b"key2", b"value2"),
629        ]);
630        let mut key = DatabaseEntry::new();
631        let mut data = DatabaseEntry::new();
632
633        let status = cursor.get(&mut key, &mut data, Get::Last, None).unwrap();
634        assert_eq!(status, OperationStatus::Success);
635        assert_eq!(data.get_data().unwrap(), b"value3");
636
637        let status = cursor.get(&mut key, &mut data, Get::Prev, None).unwrap();
638        assert_eq!(status, OperationStatus::Success);
639        assert_eq!(data.get_data().unwrap(), b"value2");
640
641        let status = cursor.get(&mut key, &mut data, Get::Prev, None).unwrap();
642        assert_eq!(status, OperationStatus::Success);
643        assert_eq!(data.get_data().unwrap(), b"value1");
644
645        let status = cursor.get(&mut key, &mut data, Get::Prev, None).unwrap();
646        assert_eq!(status, OperationStatus::NotFound);
647    }
648
649    #[test]
650    fn test_current() {
651        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
652        let mut key = DatabaseEntry::from_bytes(b"key1");
653        let mut data = DatabaseEntry::new();
654
655        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
656
657        let status =
658            cursor.get(&mut key, &mut data, Get::Current, None).unwrap();
659        assert_eq!(status, OperationStatus::Success);
660        assert_eq!(data.get_data().unwrap(), b"value1");
661    }
662
663    #[test]
664    fn test_current_not_initialized() {
665        let mut cursor = make_cursor(false);
666        let mut key = DatabaseEntry::new();
667        let mut data = DatabaseEntry::new();
668
669        let result = cursor.get(&mut key, &mut data, Get::Current, None);
670        assert!(result.is_err());
671    }
672
673    #[test]
674    fn test_put_overwrite() {
675        let mut cursor = make_cursor(false);
676
677        let mut key = DatabaseEntry::from_bytes(b"key1");
678        let data = DatabaseEntry::from_bytes(b"value1");
679
680        let status = cursor.put(&key, &data, Put::Overwrite).unwrap();
681        assert_eq!(status, OperationStatus::Success);
682        assert_eq!(cursor.get_state(), CursorState::Initialized);
683
684        // Verify by reading back
685        let mut out = DatabaseEntry::new();
686        let s = cursor.get(&mut key, &mut out, Get::Search, None).unwrap();
687        assert_eq!(s, OperationStatus::Success);
688        assert_eq!(out.get_data().unwrap(), b"value1");
689    }
690
691    #[test]
692    fn test_put_no_overwrite() {
693        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
694
695        let key = DatabaseEntry::from_bytes(b"key1");
696        let data = DatabaseEntry::from_bytes(b"value2");
697
698        let status = cursor.put(&key, &data, Put::NoOverwrite).unwrap();
699        assert_eq!(status, OperationStatus::KeyExists);
700    }
701
702    #[test]
703    fn test_put_no_overwrite_new_key() {
704        let mut cursor = make_cursor(false);
705
706        let mut key = DatabaseEntry::from_bytes(b"key1");
707        let data = DatabaseEntry::from_bytes(b"value1");
708
709        let status = cursor.put(&key, &data, Put::NoOverwrite).unwrap();
710        assert_eq!(status, OperationStatus::Success);
711
712        // Verify by reading back
713        let mut out = DatabaseEntry::new();
714        let s = cursor.get(&mut key, &mut out, Get::Search, None).unwrap();
715        assert_eq!(s, OperationStatus::Success);
716        assert_eq!(out.get_data().unwrap(), b"value1");
717    }
718
719    #[test]
720    fn test_put_current() {
721        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
722
723        let mut key = DatabaseEntry::from_bytes(b"key1");
724        let mut data = DatabaseEntry::new();
725        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
726
727        let new_data = DatabaseEntry::from_bytes(b"value2");
728        let status = cursor.put(&key, &new_data, Put::Current).unwrap();
729        assert_eq!(status, OperationStatus::Success);
730
731        // Verify updated
732        let mut out = DatabaseEntry::new();
733        cursor.get(&mut key, &mut out, Get::Search, None).unwrap();
734        assert_eq!(out.get_data().unwrap(), b"value2");
735    }
736
737    #[test]
738    fn test_put_read_only() {
739        let mut cursor = make_cursor(true);
740
741        let key = DatabaseEntry::from_bytes(b"key1");
742        let data = DatabaseEntry::from_bytes(b"value1");
743
744        let result = cursor.put(&key, &data, Put::Overwrite);
745        assert!(result.is_err());
746    }
747
748    #[test]
749    fn test_delete() {
750        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
751
752        let mut key = DatabaseEntry::from_bytes(b"key1");
753        let mut data = DatabaseEntry::new();
754        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
755
756        let status = cursor.delete().unwrap();
757        assert_eq!(status, OperationStatus::Success);
758        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
759
760        // Verify deleted
761        let s = cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
762        assert_eq!(s, OperationStatus::NotFound);
763    }
764
765    #[test]
766    fn test_delete_not_positioned() {
767        let mut cursor = make_cursor(false);
768        let result = cursor.delete();
769        assert!(result.is_err());
770    }
771
772    #[test]
773    fn test_delete_read_only() {
774        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
775
776        let mut key = DatabaseEntry::from_bytes(b"key1");
777        let mut data = DatabaseEntry::new();
778        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
779
780        // Simulate read-only after positioning
781        cursor.read_only = true;
782        let result = cursor.delete();
783        assert!(result.is_err());
784    }
785
786    #[test]
787    fn test_count() {
788        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
789
790        // Not positioned
791        assert_eq!(cursor.count().unwrap(), 0);
792
793        // Position cursor
794        let mut key = DatabaseEntry::from_bytes(b"key1");
795        let mut data = DatabaseEntry::new();
796        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
797
798        // Count should be 1 for non-dup DB
799        assert_eq!(cursor.count().unwrap(), 1);
800    }
801
802    #[test]
803    fn test_close() {
804        let mut cursor = make_cursor(false);
805
806        assert!(cursor.is_valid());
807        cursor.close().unwrap();
808        assert!(!cursor.is_valid());
809        assert_eq!(cursor.get_state(), CursorState::Closed);
810    }
811
812    #[test]
813    fn test_close_twice() {
814        // Audit cursor F13/F14 (Wave 2C-4): close() is idempotent.
815        let mut cursor = make_cursor(false);
816
817        cursor.close().unwrap();
818        // Second close must succeed silently.
819        cursor.close().expect("close() must be idempotent");
820    }
821
822    #[test]
823    fn test_close_propagates_to_inner() {
824        // Audit cursor F14 (Wave 2C-4): outer close() must close the
825        // inner CursorImpl so the BIN pin is released eagerly.
826        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
827
828        let mut key = DatabaseEntry::from_bytes(b"key1");
829        let mut data = DatabaseEntry::new();
830        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
831        // Inner state is Initialized at this point.
832        cursor.close().unwrap();
833
834        // Calling any operation on the inner cursor must now report it
835        // as closed.
836        let result = cursor.inner.get_first();
837        assert!(
838            result.is_err(),
839            "inner cursor must be closed after outer close()",
840        );
841    }
842
843    #[test]
844    fn test_operations_after_close() {
845        let mut cursor = make_cursor(false);
846
847        cursor.close().unwrap();
848
849        let mut key = DatabaseEntry::new();
850        let mut data = DatabaseEntry::new();
851
852        let result = cursor.get(&mut key, &mut data, Get::First, None);
853        assert!(result.is_err());
854    }
855
856    #[test]
857    fn test_empty_database_iteration() {
858        let mut cursor = make_cursor(false);
859
860        let mut key = DatabaseEntry::new();
861        let mut data = DatabaseEntry::new();
862
863        let status = cursor.get(&mut key, &mut data, Get::First, None).unwrap();
864        assert_eq!(status, OperationStatus::NotFound);
865    }
866
867    #[test]
868    fn test_sorted_iteration() {
869        let mut cursor = make_cursor_with(vec![
870            (b"zebra", b"z"),
871            (b"apple", b"a"),
872            (b"mango", b"m"),
873        ]);
874        let mut key = DatabaseEntry::new();
875        let mut data = DatabaseEntry::new();
876
877        let mut values = Vec::new();
878
879        let mut status =
880            cursor.get(&mut key, &mut data, Get::First, None).unwrap();
881        while status == OperationStatus::Success {
882            values.push(data.get_data().unwrap().to_vec());
883            status = cursor.get(&mut key, &mut data, Get::Next, None).unwrap();
884        }
885
886        assert_eq!(values, vec![b"a".to_vec(), b"m".to_vec(), b"z".to_vec()]);
887    }
888
889    #[test]
890    fn test_next_from_uninitialized() {
891        let mut cursor =
892            make_cursor_with(vec![(b"key1", b"value1"), (b"key2", b"value2")]);
893        let mut key = DatabaseEntry::new();
894        let mut data = DatabaseEntry::new();
895
896        // Next from uninitialized should return first
897        let status = cursor.get(&mut key, &mut data, Get::Next, None).unwrap();
898        assert_eq!(status, OperationStatus::Success);
899        assert_eq!(data.get_data().unwrap(), b"value1");
900    }
901
902    #[test]
903    fn test_prev_from_uninitialized() {
904        let mut cursor =
905            make_cursor_with(vec![(b"key1", b"value1"), (b"key2", b"value2")]);
906        let mut key = DatabaseEntry::new();
907        let mut data = DatabaseEntry::new();
908
909        // Prev from uninitialized should return last
910        let status = cursor.get(&mut key, &mut data, Get::Prev, None).unwrap();
911        assert_eq!(status, OperationStatus::Success);
912        assert_eq!(data.get_data().unwrap(), b"value2");
913    }
914
915    #[test]
916    fn test_cursor_state_transitions() {
917        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
918        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
919
920        let mut key = DatabaseEntry::from_bytes(b"key1");
921        let mut data = DatabaseEntry::new();
922        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
923        assert_eq!(cursor.get_state(), CursorState::Initialized);
924
925        cursor.delete().unwrap();
926        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
927
928        cursor.close().unwrap();
929        assert_eq!(cursor.get_state(), CursorState::Closed);
930    }
931
932    // ========================================================================
933    // Additional branch-coverage tests
934    // ========================================================================
935
936    /// Get::SearchGte with empty key positions at the first record.
937    ///
938    /// Empty keys are valid input under
939    /// the unified contract — SearchGte with the empty key starts the
940    /// range scan at the smallest record, matching BDB-JE.
941    #[test]
942    fn test_search_gte_empty_key_returns_not_found() {
943        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
944        let mut key = DatabaseEntry::new(); // no data
945        let mut data = DatabaseEntry::new();
946
947        let status =
948            cursor.get(&mut key, &mut data, Get::SearchGte, None).unwrap();
949        // Empty < any non-empty key → SearchGte positions at "key1".
950        assert_eq!(status, OperationStatus::Success);
951        assert_eq!(data.get_data().unwrap(), b"value1");
952    }
953
954    /// Get::Search with empty key returns NotFound.
955    #[test]
956    fn test_search_empty_key_returns_not_found() {
957        // Audit cursor F10 (Wave 2C-4): empty keys are valid input;
958        // the search simply yields NotFound because no record uses an
959        // empty key in this fixture.
960        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
961        let mut key = DatabaseEntry::new(); // no data
962        let mut data = DatabaseEntry::new();
963
964        let status =
965            cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
966        assert_eq!(status, OperationStatus::NotFound);
967    }
968
969    /// Get::NextDup on a non-dup DB returns NotFound (audit Finding 5).
970    ///
971    /// Pre-fix this fell through to a wildcard `_ =>` arm; post-fix it is
972    /// handled by an explicit early-return in `CursorImpl::retrieve_next`.
973    #[test]
974    fn test_get_other_variant_returns_not_found() {
975        let mut cursor = make_cursor_with(vec![(b"key1", b"value1")]);
976        let mut key = DatabaseEntry::from_bytes(b"key1");
977        let mut data = DatabaseEntry::new();
978
979        // Position cursor first.
980        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
981
982        // Get::NextDup on a non-dup DB must always return NotFound.
983        let status =
984            cursor.get(&mut key, &mut data, Get::NextDup, None).unwrap();
985        assert_eq!(status, OperationStatus::NotFound);
986    }
987
988    /// Put::NoDupData on a non-dup database inserts when the key is new.
989    ///
990    /// `Cursor.putNoDupData()`: for a non-dup database NoDupData
991    /// behaves like NoOverwrite (returns KeyExists if the key is already
992    /// present, Success otherwise).
993    #[test]
994    fn test_put_no_dup_data_inserts_new_key() {
995        let mut cursor = make_cursor(false);
996
997        let mut key = DatabaseEntry::from_bytes(b"k");
998        let data = DatabaseEntry::from_bytes(b"v");
999
1000        let status = cursor.put(&key, &data, Put::NoDupData).unwrap();
1001        assert_eq!(status, OperationStatus::Success);
1002
1003        // Verify the record is readable.
1004        let mut out = DatabaseEntry::new();
1005        let s = cursor.get(&mut key, &mut out, Get::Search, None).unwrap();
1006        assert_eq!(s, OperationStatus::Success);
1007        assert_eq!(out.get_data().unwrap(), b"v");
1008    }
1009
1010    /// Put::NoDupData returns KeyExists when the key already exists (non-dup DB).
1011    #[test]
1012    fn test_put_no_dup_data_key_exists() {
1013        let mut cursor = make_cursor_with(vec![(b"k", b"v")]);
1014
1015        let key = DatabaseEntry::from_bytes(b"k");
1016        let data = DatabaseEntry::from_bytes(b"v2");
1017
1018        let status = cursor.put(&key, &data, Put::NoDupData).unwrap();
1019        assert_eq!(status, OperationStatus::KeyExists);
1020    }
1021
1022    /// Put::Current when cursor is not initialized returns an error.
1023    #[test]
1024    fn test_put_current_not_initialized_returns_error() {
1025        let mut cursor = make_cursor(false);
1026
1027        let key = DatabaseEntry::from_bytes(b"k");
1028        let data = DatabaseEntry::from_bytes(b"v");
1029
1030        let result = cursor.put(&key, &data, Put::Current);
1031        assert!(result.is_err());
1032    }
1033
1034    /// Get::First on empty DB resets state to NotInitialized.
1035    #[test]
1036    fn test_first_not_found_resets_state() {
1037        let mut cursor = make_cursor(false); // empty DB
1038        let mut key = DatabaseEntry::new();
1039        let mut data = DatabaseEntry::new();
1040
1041        let status = cursor.get(&mut key, &mut data, Get::First, None).unwrap();
1042        assert_eq!(status, OperationStatus::NotFound);
1043        // After a failed First the state must be NotInitialized.
1044        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
1045    }
1046
1047    /// Get::Last on empty DB resets state to NotInitialized.
1048    #[test]
1049    fn test_last_not_found_resets_state() {
1050        let mut cursor = make_cursor(false); // empty DB
1051        let mut key = DatabaseEntry::new();
1052        let mut data = DatabaseEntry::new();
1053
1054        let status = cursor.get(&mut key, &mut data, Get::Last, None).unwrap();
1055        assert_eq!(status, OperationStatus::NotFound);
1056        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
1057    }
1058
1059    /// Get::Search not-found resets state to NotInitialized.
1060    #[test]
1061    fn test_search_not_found_resets_state() {
1062        let mut cursor = make_cursor_with(vec![(b"key1", b"v1")]);
1063        let mut key = DatabaseEntry::from_bytes(b"key1");
1064        let mut data = DatabaseEntry::new();
1065
1066        // Position first.
1067        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
1068        assert_eq!(cursor.get_state(), CursorState::Initialized);
1069
1070        // Now search for a missing key — state must go back to NotInitialized.
1071        let mut key_miss = DatabaseEntry::from_bytes(b"missing");
1072        let status =
1073            cursor.get(&mut key_miss, &mut data, Get::Search, None).unwrap();
1074        assert_eq!(status, OperationStatus::NotFound);
1075        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
1076    }
1077
1078    /// Get::SearchGte not-found resets state.
1079    #[test]
1080    fn test_search_gte_not_found_resets_state() {
1081        let mut cursor = make_cursor_with(vec![(b"key1", b"v1")]);
1082        let mut key = DatabaseEntry::from_bytes(b"key1");
1083        let mut data = DatabaseEntry::new();
1084
1085        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
1086
1087        let mut key_big = DatabaseEntry::from_bytes(b"zzz");
1088        let status =
1089            cursor.get(&mut key_big, &mut data, Get::SearchGte, None).unwrap();
1090        assert_eq!(status, OperationStatus::NotFound);
1091        assert_eq!(cursor.get_state(), CursorState::NotInitialized);
1092    }
1093
1094    /// count() on a closed cursor returns an error.
1095    #[test]
1096    fn test_count_on_closed_cursor_returns_error() {
1097        let mut cursor = make_cursor(false);
1098        cursor.close().unwrap();
1099        let result = cursor.count();
1100        assert!(result.is_err());
1101    }
1102
1103    /// Delete on a read-only cursor that is NOT positioned (check_initialized
1104    /// fires before check read_only).
1105    #[test]
1106    fn test_delete_not_positioned_check_fires_before_read_only_check() {
1107        let mut cursor = make_cursor(true); // read-only, not initialized
1108        let result = cursor.delete();
1109        // check_initialized fires first → error about "not positioned".
1110        assert!(result.is_err());
1111    }
1112
1113    /// SearchGte success path: search for a key that is less than the first
1114    /// key in the database — should position at the first key (range semantics).
1115    #[test]
1116    fn test_search_gte_positions_at_ge_key() {
1117        let mut cursor = make_cursor_with(vec![(b"mango", b"yellow")]);
1118        let mut key = DatabaseEntry::from_bytes(b"apple"); // < "mango"
1119        let mut data = DatabaseEntry::new();
1120
1121        let status =
1122            cursor.get(&mut key, &mut data, Get::SearchGte, None).unwrap();
1123        assert_eq!(status, OperationStatus::Success);
1124        assert_eq!(data.get_data().unwrap(), b"yellow");
1125        assert_eq!(cursor.get_state(), CursorState::Initialized);
1126    }
1127
1128    // ========================================================================
1129    // map_err closure coverage: close the inner CursorImpl so that operations
1130    // on it return DbiError::CursorClosed.  The outer Cursor::state is kept
1131    // at NotInitialized so that check_open() passes, but the underlying
1132    // CursorImpl returns an error, exercising every `map_err(|e| ...)` closure.
1133    // ========================================================================
1134
1135    /// Helper: cursor whose outer state is NotInitialized but whose inner
1136    /// CursorImpl has been closed, so that any CursorImpl call returns an error.
1137    fn make_inner_closed_cursor() -> Cursor {
1138        let mut c = make_cursor(false);
1139        c.inner.close().unwrap(); // CursorImpl is now Closed
1140        // outer state stays NotInitialized — check_open() will pass
1141        c
1142    }
1143
1144    /// Helper: cursor whose outer state is Initialized but whose inner
1145    /// CursorImpl has been closed (simulate a mid-flight error scenario).
1146    fn make_inner_closed_cursor_initialized() -> Cursor {
1147        let mut c = make_cursor(false);
1148        // Manually set outer state to Initialized so check_initialized() passes.
1149        c.state = CursorState::Initialized;
1150        c.inner.close().unwrap();
1151        c
1152    }
1153
1154    /// Get::Search map_err closure: CursorImpl::search returns an error when
1155    /// the inner cursor is closed.
1156    #[test]
1157    fn test_search_map_err_closure_covered() {
1158        let mut cursor = make_inner_closed_cursor();
1159        let mut key = DatabaseEntry::from_bytes(b"k");
1160        let mut data = DatabaseEntry::new();
1161        let result = cursor.get(&mut key, &mut data, Get::Search, None);
1162        assert!(result.is_err());
1163    }
1164
1165    /// Get::SearchGte map_err closure.
1166    #[test]
1167    fn test_search_gte_map_err_closure_covered() {
1168        let mut cursor = make_inner_closed_cursor();
1169        let mut key = DatabaseEntry::from_bytes(b"k");
1170        let mut data = DatabaseEntry::new();
1171        let result = cursor.get(&mut key, &mut data, Get::SearchGte, None);
1172        assert!(result.is_err());
1173    }
1174
1175    /// Get::First map_err closure.
1176    #[test]
1177    fn test_first_map_err_closure_covered() {
1178        let mut cursor = make_inner_closed_cursor();
1179        let mut key = DatabaseEntry::new();
1180        let mut data = DatabaseEntry::new();
1181        let result = cursor.get(&mut key, &mut data, Get::First, None);
1182        assert!(result.is_err());
1183    }
1184
1185    /// Get::Last map_err closure.
1186    #[test]
1187    fn test_last_map_err_closure_covered() {
1188        let mut cursor = make_inner_closed_cursor();
1189        let mut key = DatabaseEntry::new();
1190        let mut data = DatabaseEntry::new();
1191        let result = cursor.get(&mut key, &mut data, Get::Last, None);
1192        assert!(result.is_err());
1193    }
1194
1195    /// Get::Next (uninitialized path, calls get_first) map_err closure.
1196    #[test]
1197    fn test_next_uninit_map_err_closure_covered() {
1198        let mut cursor = make_inner_closed_cursor();
1199        let mut key = DatabaseEntry::new();
1200        let mut data = DatabaseEntry::new();
1201        // state is NotInitialized → takes the get_first branch
1202        let result = cursor.get(&mut key, &mut data, Get::Next, None);
1203        assert!(result.is_err());
1204    }
1205
1206    /// Get::Next (initialized path, calls retrieve_next) map_err closure.
1207    #[test]
1208    fn test_next_init_map_err_closure_covered() {
1209        let mut cursor = make_inner_closed_cursor_initialized();
1210        let mut key = DatabaseEntry::new();
1211        let mut data = DatabaseEntry::new();
1212        // state is Initialized → takes the retrieve_next branch
1213        let result = cursor.get(&mut key, &mut data, Get::Next, None);
1214        assert!(result.is_err());
1215    }
1216
1217    /// Get::Prev (uninitialized path, calls get_last) map_err closure.
1218    #[test]
1219    fn test_prev_uninit_map_err_closure_covered() {
1220        let mut cursor = make_inner_closed_cursor();
1221        let mut key = DatabaseEntry::new();
1222        let mut data = DatabaseEntry::new();
1223        let result = cursor.get(&mut key, &mut data, Get::Prev, None);
1224        assert!(result.is_err());
1225    }
1226
1227    /// Get::Prev (initialized path, calls retrieve_next(Prev)) map_err closure.
1228    #[test]
1229    fn test_prev_init_map_err_closure_covered() {
1230        let mut cursor = make_inner_closed_cursor_initialized();
1231        let mut key = DatabaseEntry::new();
1232        let mut data = DatabaseEntry::new();
1233        let result = cursor.get(&mut key, &mut data, Get::Prev, None);
1234        assert!(result.is_err());
1235    }
1236
1237    /// Get::Current map_err closure (get_current on inner closed cursor).
1238    #[test]
1239    fn test_current_map_err_closure_covered() {
1240        let mut cursor = make_inner_closed_cursor_initialized();
1241        let mut key = DatabaseEntry::new();
1242        let mut data = DatabaseEntry::new();
1243        // check_initialized passes (outer state = Initialized),
1244        // but inner.get_current() returns CursorClosed → map_err fires.
1245        let result = cursor.get(&mut key, &mut data, Get::Current, None);
1246        assert!(result.is_err());
1247    }
1248
1249    /// After a successful get_first/search, the get_current() call inside the
1250    /// success arm also goes through map_err — exercise it by making the inner
1251    /// cursor report "closed" after the search position has been set.
1252    /// We do this by directly calling the success-arm get_current via the
1253    /// Cursor::get flow: position outer state via Search success, then call
1254    /// CursorImpl::close() and call Get::Current.
1255    #[test]
1256    fn test_get_success_branch_get_current_map_err() {
1257        let mut cursor = make_cursor_with(vec![(b"key1", b"val1")]);
1258        // First do a real search so inner state = Initialized
1259        let mut key = DatabaseEntry::from_bytes(b"key1");
1260        let mut data = DatabaseEntry::new();
1261        cursor.get(&mut key, &mut data, Get::Search, None).unwrap();
1262        // Now close the inner cursor; outer state remains Initialized
1263        cursor.inner.close().unwrap();
1264        // Get::First triggers get_first() on the closed inner cursor → error
1265        let mut key2 = DatabaseEntry::new();
1266        let mut data2 = DatabaseEntry::new();
1267        let result = cursor.get(&mut key2, &mut data2, Get::First, None);
1268        assert!(result.is_err());
1269    }
1270
1271    /// Put map_err closure: CursorImpl::put returns an error when inner is closed.
1272    #[test]
1273    fn test_put_map_err_closure_covered() {
1274        let mut cursor = make_inner_closed_cursor();
1275        let key = DatabaseEntry::from_bytes(b"k");
1276        let data = DatabaseEntry::from_bytes(b"v");
1277        let result = cursor.put(&key, &data, Put::Overwrite);
1278        assert!(result.is_err());
1279    }
1280
1281    /// Get::SearchBothRange wires through to `SearchMode::BothRange` on
1282    /// a sorted-dup database.
1283    #[test]
1284    fn test_search_both_range_on_dup_db() {
1285        use noxu_dbi::{
1286            DatabaseConfig as DbiCfg, DatabaseId, DatabaseImpl, DbType,
1287        };
1288        use noxu_sync::RwLock;
1289        use std::sync::Arc;
1290
1291        let db_id = DatabaseId::new(7);
1292        let config = DbiCfg { sorted_duplicates: true, ..DbiCfg::default() };
1293        let db_impl = DatabaseImpl::new(
1294            db_id,
1295            "dup_test".to_string(),
1296            DbType::User,
1297            &config,
1298        );
1299        let db_arc = Arc::new(RwLock::new(db_impl));
1300
1301        // Insert three duplicates of "k": "a", "b", "d".
1302        {
1303            let mut tmp = CursorImpl::new(Arc::clone(&db_arc), 0);
1304            tmp.put(b"k", b"a", PutMode::Overwrite).unwrap();
1305            tmp.put(b"k", b"b", PutMode::Overwrite).unwrap();
1306            tmp.put(b"k", b"d", PutMode::Overwrite).unwrap();
1307        }
1308
1309        let inner = CursorImpl::new(db_arc, 0);
1310        let mut cursor = Cursor::from_impl(inner, false);
1311
1312        // SearchBothRange for (key="k", data="c") must position at the
1313        // first duplicate >= "c", i.e. "d".
1314        let mut k = DatabaseEntry::from_bytes(b"k");
1315        let mut d = DatabaseEntry::from_bytes(b"c");
1316        let s = cursor.get(&mut k, &mut d, Get::SearchBothRange, None).unwrap();
1317        assert_eq!(s, OperationStatus::Success);
1318        assert_eq!(d.get_data().unwrap(), b"d");
1319    }
1320
1321    /// Delete map_err closure: CursorImpl::delete returns an error when inner is closed.
1322    #[test]
1323    fn test_delete_map_err_closure_covered() {
1324        let mut cursor = make_inner_closed_cursor_initialized();
1325        let result = cursor.delete();
1326        assert!(result.is_err());
1327    }
1328}