borderless_kv_store/backend/lmdb.rs
1use crate::{CursorOp, Error, KvDatabase, KvHandle, RoCursor, RoTx, RwCursor, RwTx, ToCursorOp};
2use lmdb::EnvironmentFlags;
3use lmdb_sys::{MDB_FIRST, MDB_GET_CURRENT, MDB_LAST, MDB_NEXT, MDB_PREV};
4use std::{path::Path, sync::Arc};
5
6use crate::{Db, RawRead, RawWrite, Tx};
7
8pub use lmdb::Database as DbPtr;
9
10/// Converts LMDB-specific errors (`lmdb::Error`) into the database interface `Error` type.
11///
12/// This implementation maps LMDB errors to a meaningful representation in the library context,
13/// allowing better handling of database errors.
14///
15/// ### Notes:
16/// - The `NotFound` error is deliberately not converted to the custom `Error` type because it is
17/// represented by the `Option` type in the interface itself.
18/// - For errors like `Other`, additional contextual information is included for debugging purposes.
19impl From<lmdb::Error> for Error {
20 fn from(value: lmdb::Error) -> Error {
21 match value {
22 lmdb::Error::KeyExist => Error::KeyExist,
23 lmdb::Error::NotFound => panic!("Wrong implementation, the key not found error is pictured by the option type in the interface iteself."),
24 lmdb::Error::PageNotFound => Error::Other("Database page not found".to_string()),
25 lmdb::Error::Corrupted => Error::Corrupted,
26 lmdb::Error::Panic => Error::Other("panic occured".to_string()),
27 lmdb::Error::VersionMismatch => Error::Other("version mismatch".to_string()),
28 lmdb::Error::Invalid => Error::InvalidArgument,
29 lmdb::Error::MapResized => Error::Other("map resized".to_string()),
30 lmdb::Error::Incompatible => Error::Other("incopatible format".to_string()),
31 lmdb::Error::BadRslot => Error::Other("bad reader slot".to_string()),
32 lmdb::Error::BadTxn => Error::Other("bad transaction".to_string()),
33 lmdb::Error::BadValSize => Error::Other("bad value size".to_string()),
34 lmdb::Error::BadDbi => Error::Other("bad database index".to_string()),
35 lmdb::Error::Other(code) => Error::Other(format!("unknown lmdb error code: {}", code)),
36 e => Error::Busy(e.to_string())
37 }
38 }
39}
40
41impl ToCursorOp<lmdb::Database> for CursorOp {
42 fn to_op(&self) -> u32 {
43 match self {
44 CursorOp::First => MDB_FIRST,
45 CursorOp::Last => MDB_LAST,
46 CursorOp::Next => MDB_NEXT,
47 CursorOp::Prev => MDB_PREV,
48 CursorOp::Current => MDB_GET_CURRENT,
49 }
50 }
51}
52
53/// Marks LMDB's `Database` type as implementing the `KvDatabase` trait.
54///
55/// This allows LMDB's native database type to integrate with the `KvDatabase` trait, enabling
56/// it to be used seamlessly with the broader key-value store interface.
57impl KvDatabase for lmdb::Database {}
58
59/// Implements the `KvHandle` trait for `lmdb::Database`, providing database access.
60///
61/// This implementation ensures compatibility with the key-value interface by exposing the
62/// underlying LMDB database.
63impl KvHandle<lmdb::Database> for lmdb::Database {
64 /// Returns a reference to the underlying LMDB database.
65 ///
66 /// ### Returns:
67 /// - A reference to the database managed by this handle.
68 fn db(&self) -> &lmdb::Database {
69 self
70 }
71}
72
73/// Represents an LMDB environment for managing databases and transactions.
74///
75/// The `Lmdb` provides a high-level abstraction over the LMDB `Environment`,
76/// enabling the creation and management of databases, as well as read-only and
77/// read-write transactions.
78#[derive(Clone)]
79pub struct Lmdb {
80 env: Arc<lmdb::Environment>,
81}
82
83impl Lmdb {
84 /// Initializes a new LMDB environment at the specified path with a given maximum number of databases.
85 ///
86 /// ### Parameters:
87 /// - `path`: The filesystem path where the LMDB environment will be created or accessed.
88 /// - `max_dbs`: The maximum number of named databases that can be created within this environment.
89 ///
90 /// ### Returns:
91 /// - `Ok(Lmdb)` if the environment was successfully initialized.
92 /// - `Err(Error)` if an error occurred during initialization.
93 pub fn new(path: &Path, max_dbs: u32) -> Result<Lmdb, Error> {
94 // We can do some further optimizations, if we want to increase the performance:
95 let flags = EnvironmentFlags::default()
96 | EnvironmentFlags::WRITE_MAP // Faster writes, backed by OS memory mapping. Safe on modern OSes. (incompatible with nested transactions !)
97 | EnvironmentFlags::NO_META_SYNC; // Skips metadata sync — tiny risk on power loss, big write speed boost.
98
99 let env = lmdb::Environment::new()
100 .set_max_dbs(max_dbs)
101 // NOTE: we have to maintain the map size
102 // in future. A mechanism to increase this size
103 // is a good idea.
104 .set_map_size(1_099_511_627_776) // 1 TB
105 .set_max_readers(2048) // allow more concurrent readers
106 .set_flags(flags)
107 .open(path)?;
108
109 Ok(Lmdb { env: Arc::new(env) })
110 }
111}
112
113/// Implements the `Db` trait for `Lmdb`.
114///
115/// This allows `Lmdb` to serve as a fully functional key-value environment
116/// compatible with the application's interface.
117impl Db for Lmdb {
118 type DB = lmdb::Database;
119 type Handle = lmdb::Database;
120 type RoTx<'env> = lmdb::RoTransaction<'env>;
121 type RwTx<'env> = lmdb::RwTransaction<'env>;
122
123 /// Opens a database by name or the default database if the name is "default".
124 ///
125 /// ### Parameters:
126 /// - `name`: The name of the database to open, or "default" to open the unnamed default database.
127 ///
128 /// ### Returns:
129 /// - `Ok(Handle<'env>)` containing a handle to the opened database.
130 /// - `Err(Error)` if the database could not be opened.
131 fn open_sub_db(&self, name: &str) -> Result<Self::Handle, Error> {
132 // NOTE: This can also cause Error::NotFound
133 let res = if name.to_ascii_lowercase() == "default" {
134 self.env.open_db(None)
135 } else {
136 self.env.open_db(Some(name))
137 };
138
139 let handle = match res {
140 Ok(db) => db,
141 Err(lmdb::Error::NotFound) => return Err(Error::DbNotFound(name.to_string())),
142 Err(e) => return Err(Error::from(e)),
143 };
144
145 Ok(handle)
146 }
147
148 /// Create a database by name or the default database if the name is "default".
149 ///
150 /// ### Parameters:
151 /// - `name`: The name of the database to open, or "default" to open the unnamed default database.
152 ///
153 /// ### Returns:
154 /// - `Ok(Handle<'env>)` containing a handle to the opened database.
155 /// - `Err(Error)` if the database could not be opened.
156 fn create_sub_db(&self, name: &str) -> Result<Self::Handle, Error> {
157 let handle = if name == "default" {
158 self.env.create_db(None, lmdb::DatabaseFlags::empty())?
159 } else {
160 self.env
161 .create_db(Some(name), lmdb::DatabaseFlags::empty())?
162 };
163
164 Ok(handle)
165 }
166
167 /// Begins a new read-only transaction within the environment.
168 ///
169 /// ### Returns:
170 /// - `Ok(RoTx<'env>)` containing the transaction object.
171 /// - `Err(Error)` if the transaction could not be started.
172 fn begin_ro_txn(&self) -> Result<Self::RoTx<'_>, Error> {
173 let txn = self.env.begin_ro_txn()?;
174 Ok(txn)
175 }
176
177 /// Begins a new read-write transaction within the environment.
178 ///
179 /// ### Returns:
180 /// - `Ok(RwTx<'env>)` containing the transaction object.
181 /// - `Err(Error)` if the transaction could not be started.
182 fn begin_rw_txn(&self) -> Result<Self::RwTx<'_>, Error> {
183 let txn = self.env.begin_rw_txn()?;
184 Ok(txn)
185 }
186}
187
188/// Implements the `Tx` trait for LMDB's read-only transactions (`RoTransaction`).
189///
190/// This trait provides methods to manage the lifecycle of read-only transactions,
191/// including committing and aborting.
192impl<'env> Tx for lmdb::RoTransaction<'env> {
193 /// Commits the transaction, making all changes visible to other transactions.
194 ///
195 /// ### Returns:
196 /// - `Ok(())` if the transaction was successfully committed.
197 /// - `Err(Error)` if an error occurred during the commit operation.
198 fn commit(self) -> Result<(), Error> {
199 <Self as lmdb::Transaction>::commit(self)?;
200 Ok(())
201 }
202
203 /// Aborts the transaction, discarding all changes made during its lifetime.
204 ///
205 /// ### Notes:
206 /// - This method ensures that the transaction is cleanly terminated without affecting the database.
207 fn abort(self) {
208 <Self as lmdb::Transaction>::abort(self);
209 }
210}
211
212/// Implements the `RawRead` trait for LMDB's read-only transaction (`RoTransaction`).
213///
214/// This allows performing read operations within the context of a read-only transaction.
215/// It retrieves data from the database based on a specified key.
216///
217/// ### Methods:
218/// - `read`: Fetches the value associated with a given key in the specified database.
219impl<'env> RawRead<'env, lmdb::Database> for lmdb::RoTransaction<'env> {
220 /// Reads a value from the database associated with the provided key.
221 ///
222 /// ### Parameters:
223 /// - `db`: A handle to the database to query.
224 /// - `key`: The key to search for in the database.
225 ///
226 /// ### Returns:
227 /// - `Ok(Some(&[u8]))`: If the key exists, returns a reference to the value.
228 /// - `Ok(None)`: If the key is not found.
229 /// - `Err(Error)`: If an error occurs during the operation.
230 fn read(
231 &self,
232 db: &impl KvHandle<lmdb::Database>,
233 key: &impl AsRef<[u8]>,
234 ) -> Result<Option<&[u8]>, Error> {
235 let res = <Self as lmdb::Transaction>::get(self, *db.db(), key);
236
237 let data = match res {
238 Ok(buf) => Some(buf),
239 Err(lmdb::Error::NotFound) => None,
240 Err(e) => return Err(Error::from(e)),
241 };
242
243 Ok(data)
244 }
245}
246
247/// Implements the `RoTx` trait for LMDB's read-only transactions.
248///
249/// This provides additional methods specific to read-only transactions, such as creating cursors.
250impl<'env> RoTx<'env, lmdb::Database> for lmdb::RoTransaction<'env> {
251 /// Type definition for the cursor used in read-only transactions.
252 type Cursor<'txn> = lmdb::RoCursor<'txn>
253 where
254 Self: 'txn;
255
256 /// Creates a cursor for iterating over the database in the context of a read-only transaction.
257 ///
258 /// ### Parameters:
259 /// - `db`: A handle to the database for which the cursor is created.
260 ///
261 /// ### Returns:
262 /// - `Ok(Cursor)`: If the cursor is successfully created.
263 /// - `Err(Error)`: If an error occurs.
264 fn ro_cursor<'txn>(
265 &'txn self,
266 db: &impl KvHandle<lmdb::Database>,
267 ) -> Result<Self::Cursor<'txn>, Error> {
268 let cursor = <Self as lmdb::Transaction>::open_ro_cursor(self, *db.db())?;
269 Ok(cursor)
270 }
271}
272
273/// Implements the `RoCursor` trait for LMDB's read-only cursors.
274///
275/// Provides methods to iterate over the key-value pairs in the database.
276impl<'txn> RoCursor<'txn, lmdb::Database> for lmdb::RoCursor<'txn> {
277 /// Type definition for the iterator used in the cursor.
278 type Iter = Iter<'txn>;
279
280 fn get<K, V>(
281 &self,
282 key: Option<&K>,
283 value: Option<&V>,
284 op: impl ToCursorOp<lmdb::Database>,
285 ) -> Result<(Option<&'txn [u8]>, &'txn [u8]), Error>
286 where
287 K: AsRef<[u8]>,
288 V: AsRef<[u8]>,
289 {
290 let res = <Self as lmdb::Cursor>::get(
291 self,
292 key.map(|k| k.as_ref()),
293 value.map(|v| v.as_ref()),
294 op.to_op(),
295 )?;
296
297 Ok(res)
298 }
299
300 /// Creates an iterator over the database starting from the current cursor position.
301 ///
302 /// ### Returns:
303 /// - An iterator over key-value pairs in the database.
304 fn iter(&mut self) -> Self::Iter {
305 Iter(<Self as lmdb::Cursor>::iter(self))
306 }
307
308 fn iter_start(&mut self) -> Self::Iter {
309 Iter(<Self as lmdb::Cursor>::iter_start(self))
310 }
311
312 fn iter_from<K>(&mut self, key: &K) -> Self::Iter
313 where
314 K: AsRef<[u8]>,
315 {
316 Iter(<Self as lmdb::Cursor>::iter_from(self, key))
317 }
318}
319
320/// Implements the `Tx` trait for LMDB's read-write transactions.
321///
322/// Provides methods to manage the lifecycle of read-write transactions, such as committing or aborting.
323impl<'env> Tx for lmdb::RwTransaction<'env> {
324 /// Commits the transaction, applying all changes to the database.
325 ///
326 /// ### Returns:
327 /// - `Ok(())`: If the transaction was successfully committed.
328 /// - `Err(Error)`: If an error occurs during the commit operation.
329 fn commit(self) -> Result<(), Error> {
330 <Self as lmdb::Transaction>::commit(self)?;
331 Ok(())
332 }
333
334 /// Aborts the transaction, discarding all changes made within it.
335 ///
336 /// ### Notes:
337 /// - This ensures no changes from the transaction are persisted in the database.
338 fn abort(self) {
339 <Self as lmdb::Transaction>::abort(self);
340 }
341}
342
343/// Implements the `RawRead` trait for LMDB's read-write transaction (`RwTransaction`).
344///
345/// Enables reading data within the context of a read-write transaction.
346impl<'env> RawRead<'env, lmdb::Database> for lmdb::RwTransaction<'env> {
347 /// Reads a value from the database associated with the provided key.
348 ///
349 /// ### Parameters:
350 /// - `db`: A handle to the database to query.
351 /// - `key`: The key to search for in the database.
352 ///
353 /// ### Returns:
354 /// - `Ok(Some(&[u8]))`: If the key exists, returns a reference to the value.
355 /// - `Ok(None)`: If the key is not found.
356 /// - `Err(Error)`: If an error occurs during the operation.
357 fn read(
358 &self,
359 db: &impl KvHandle<lmdb::Database>,
360 key: &impl AsRef<[u8]>,
361 ) -> Result<Option<&[u8]>, Error> {
362 let res = <Self as lmdb::Transaction>::get(self, *db.db(), key);
363
364 let data = match res {
365 Ok(buf) => Some(buf),
366 Err(lmdb::Error::NotFound) => None,
367 Err(e) => return Err(Error::from(e)),
368 };
369
370 Ok(data)
371 }
372}
373
374/// Implements the `RawWrite` trait for LMDB's read-write transaction (`RwTransaction`).
375///
376/// Enables writing and deleting data within the context of a read-write transaction.
377impl<'env> RawWrite<'env, lmdb::Database> for lmdb::RwTransaction<'env> {
378 /// Writes a key-value pair to the database.
379 ///
380 /// ### Parameters:
381 /// - `db`: A handle to the database where the key-value pair will be stored.
382 /// - `key`: The key to store.
383 /// - `data`: The value associated with the key.
384 ///
385 /// ### Returns:
386 /// - `Ok(())`: If the write operation was successful.
387 /// - `Err(Error)`: If an error occurs during the operation.
388 fn write(
389 &mut self,
390 db: &impl KvHandle<lmdb::Database>,
391 key: &impl AsRef<[u8]>,
392 data: &impl AsRef<[u8]>,
393 ) -> Result<(), Error> {
394 self.put(*db.db(), key, &data, lmdb::WriteFlags::empty())?;
395 Ok(())
396 }
397
398 /// Deletes a key-value pair from the database.
399 ///
400 /// ### Parameters:
401 /// - `db`: A handle to the database from which the key-value pair will be deleted.
402 /// - `key`: The key to delete.
403 ///
404 /// ### Returns:
405 /// - `Ok(())`: If the deletion was successful.
406 /// - `Err(Error)`: If an error occurs during the operation.
407 fn delete(
408 &mut self,
409 db: &impl KvHandle<lmdb::Database>,
410 key: &impl AsRef<[u8]>,
411 ) -> Result<(), Error> {
412 let res = self.del(*db.db(), key, None);
413
414 match res {
415 Ok(_) => Ok(()),
416 Err(lmdb::Error::NotFound) => Ok(()),
417 Err(e) => Err(Error::from(e)),
418 }
419 }
420}
421
422/// Implements the `RwTx` trait for LMDB's read-write transactions.
423///
424/// Provides methods to create cursors and nested transactions within the context of a read-write transaction.
425impl<'env> RwTx<'env, lmdb::Database> for lmdb::RwTransaction<'env> {
426 /// Type definition for cursors used in read-write transactions.
427 type Cursor<'txn> = lmdb::RwCursor<'txn>
428 where
429 Self: 'txn;
430
431 /// Type definition for nested read-write transactions.
432 type RwTx<'txn> = lmdb::RwTransaction<'txn>
433 where
434 Self: 'txn;
435
436 /// Creates a cursor for iterating over the database in the context of a read-write transaction.
437 ///
438 /// ### Parameters:
439 /// - `db`: A handle to the database for which the cursor is created.
440 ///
441 /// ### Returns:
442 /// - `Ok(Cursor)`: If the cursor is successfully created.
443 /// - `Err(Error)`: If an error occurs.
444 fn rw_cursor<'txn>(
445 &'txn mut self,
446 db: &impl KvHandle<lmdb::Database>,
447 ) -> Result<Self::Cursor<'txn>, Error> {
448 let cursor = self.open_rw_cursor(*db.db())?;
449 Ok(cursor)
450 }
451
452 /// Begins a nested transaction within the current transaction.
453 ///
454 /// ### Returns:
455 /// - `Ok(RwTx<'txn>)`: If the nested transaction is successfully started.
456 /// - `Err(Error)`: If an error occurs during the operation.
457 fn nested_txn(&mut self) -> Result<Self::RwTx<'_>, Error> {
458 let ntx = self.begin_nested_txn()?;
459 Ok(ntx)
460 }
461}
462
463/// Implements the `RwCursor` trait for LMDB's read-write cursors.
464///
465/// Provides methods to iterate over the key-value pairs in the database.
466impl<'txn> RwCursor<'txn, lmdb::Database> for lmdb::RwCursor<'txn> {
467 /// Type definition for the iterator used in the cursor.
468 type Iter = Iter<'txn>;
469
470 fn get<K, V>(
471 &self,
472 key: Option<&K>,
473 value: Option<&V>,
474 op: impl ToCursorOp<lmdb::Database>,
475 ) -> Result<(Option<&'txn [u8]>, &'txn [u8]), Error>
476 where
477 K: AsRef<[u8]>,
478 V: AsRef<[u8]>,
479 {
480 let res = <Self as lmdb::Cursor>::get(
481 self,
482 key.map(|k| k.as_ref()),
483 value.map(|v| v.as_ref()),
484 op.to_op(),
485 )?;
486
487 Ok(res)
488 }
489
490 fn put<K, V>(&mut self, key: &K, value: &V) -> Result<(), Error>
491 where
492 K: AsRef<[u8]>,
493 V: AsRef<[u8]>,
494 {
495 self.put(key, value, lmdb::WriteFlags::empty())?;
496 Ok(())
497 }
498
499 fn del(&mut self) -> Result<(), Error> {
500 self.del(lmdb::WriteFlags::empty())?;
501 Ok(())
502 }
503
504 /// Creates an iterator over the database starting from the current cursor position.
505 ///
506 /// ### Returns:
507 /// - An iterator over key-value pairs in the database.
508 fn iter(&mut self) -> Self::Iter {
509 Iter(<Self as lmdb::Cursor>::iter(self))
510 }
511
512 fn iter_start(&mut self) -> Self::Iter {
513 Iter(<Self as lmdb::Cursor>::iter_start(self))
514 }
515
516 fn iter_from<K>(&mut self, key: &K) -> Self::Iter
517 where
518 K: AsRef<[u8]>,
519 {
520 Iter(<Self as lmdb::Cursor>::iter_from(self, key))
521 }
522}
523
524pub struct Iter<'txn>(lmdb::Iter<'txn>);
525
526impl<'txn> Iterator for Iter<'txn> {
527 type Item = (&'txn [u8], &'txn [u8]);
528
529 fn next(&mut self) -> Option<(&'txn [u8], &'txn [u8])> {
530 // NOTE: This swallows possible errors, when the iterator fails to get a value.
531 // BUT I don't want to change the interface of the traits to match the Option<Result<(k, v)>> pattern.
532 // This change here was necessary, when we switched from lmdb to the lmdb-rkv crate,
533 // as lmdb simply panics in some iterator functions (because someone thought putting an unwrap there was smart).
534 // Anyway; I think the chances that the database fails *after* obtaining the iterator are low.
535 // Obtaining the iterator has a Result<T>, so if e.g. we cannot read from disk, this operation would fail.
536 // In case there really is a severe db error, all iterators will stop iterating, and the next read/write
537 // will fail with a normal error - for me this behaviour is fine.
538 flatten_opt_result(self.0.next())
539 }
540}
541
542/// A sligtly more efficient variant of `.transpose().ok().flatten()`
543///
544/// This approach avoids the allocation of intermediate `Result<Option<T>, E>` and
545/// `Option<Option<T>>` structures that happen with `.transpose().ok().flatten()`.
546/// It's unclear if those allocations would every happen in real life,
547/// as the compiler might optimize them out, and it's also unclear if this really makes a measurable
548/// difference in most use-cases..
549/// But if this variant is never *less* efficient, and *might* be more efficient, I am happy to roll with it.
550#[inline]
551fn flatten_opt_result<T, E>(opt_res: Option<Result<T, E>>) -> Option<T> {
552 match opt_res {
553 Some(Ok(value)) => Some(value),
554 _ => None,
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561 use rand::Rng;
562 use tempfile::tempdir;
563
564 const TEST_REPEATS: usize = 4;
565
566 fn open_tmp_lmdb() -> Lmdb {
567 let temp_dir = tempdir().unwrap();
568 let env = Lmdb::new(temp_dir.path(), 1).unwrap();
569 env
570 }
571
572 fn create_test_handle<DB: KvDatabase, Env: Db>(env: &Env) -> impl KvHandle<DB>
573 where
574 <Env as Db>::Handle: KvHandle<DB>,
575 {
576 env.create_sub_db("test").unwrap()
577 }
578
579 fn write_with_rw_txn<'env, DB: KvDatabase, Txn: RwTx<'env, DB>>(
580 txn: &mut Txn,
581 handle: &impl KvHandle<DB>,
582 key: &impl AsRef<[u8]>,
583 data: &impl AsRef<[u8]>,
584 ) -> Result<(), Error> {
585 txn.write(handle, &key, &data)?;
586 Ok(())
587 }
588
589 fn read_with_ro_txn<'env, DB: KvDatabase, Txn: RoTx<'env, DB>>(
590 txn: &Txn,
591 handle: &impl KvHandle<DB>,
592 key: &impl AsRef<[u8]>,
593 ) -> Result<Option<Vec<u8>>, Error> {
594 let buf = txn.read(handle, &key)?;
595 let out: Option<Vec<u8>> = buf.map(|v| v.to_vec());
596 Ok(out)
597 }
598
599 fn delete_with_rw_txn<'env, DB: KvDatabase, Txn: RwTx<'env, DB>>(
600 txn: &mut Txn,
601 handle: &impl KvHandle<DB>,
602 key: &impl AsRef<[u8]>,
603 ) -> Result<(), Error> {
604 txn.delete(handle, &key)?;
605 Ok(())
606 }
607
608 #[test]
609 fn read_write_delete() -> Result<(), Box<dyn std::error::Error>> {
610 let env = open_tmp_lmdb();
611 let handle = create_test_handle(&env);
612
613 for _ in 0..TEST_REPEATS {
614 let mut rng = rand::rng();
615 let test_key: Vec<u8> = (0..256).map(|_| rng.random()).collect(); // 32 byte random key
616 let test_data: Vec<u8> = (0..1048576).map(|_| rng.random()).collect(); // 1 MiB random data
617 {
618 // write test value to db
619 let mut txn = env.begin_rw_txn()?;
620 write_with_rw_txn(&mut txn, &handle, &test_key, &test_data)?;
621 Tx::commit(txn)?;
622 }
623
624 {
625 // read test value from db
626 let txn = env.begin_ro_txn()?;
627 let res = read_with_ro_txn(&txn, &handle, &test_key)?;
628 assert!(res.is_some(), "failed to read value from db");
629 assert_eq!(test_data, res.unwrap(), "data read is corrupted");
630 }
631
632 {
633 // delete test value from db
634 let mut txn = env.begin_rw_txn()?;
635 delete_with_rw_txn(&mut txn, &handle, &test_key)?;
636 Tx::commit(txn)?;
637
638 // try to read deleted value
639 let txn = env.begin_ro_txn()?;
640 let res = read_with_ro_txn(&txn, &handle, &test_key)?;
641 assert!(res.is_none(), "could read deleted value");
642 }
643 }
644 Ok(())
645 }
646
647 #[test]
648 fn not_found_is_none() -> Result<(), Box<dyn std::error::Error>> {
649 let env = open_tmp_lmdb();
650 let handle = create_test_handle(&env);
651
652 let txn = env.begin_ro_txn()?;
653 let not_found = txn.read(&handle, &[0, 0, 0, 0])?;
654 assert!(not_found.is_none());
655 Ok(())
656 }
657
658 #[test]
659 fn non_existing_db() {
660 let env = open_tmp_lmdb();
661 let db_name = "does-not-exist";
662 let res = env.open_sub_db(db_name);
663 assert!(res.is_err());
664 if let Err(e) = res {
665 match e {
666 Error::DbNotFound(name) => {
667 assert_eq!(name, db_name, "error should include db-name")
668 }
669 _ => panic!("expected error 'DbNotFound'"),
670 }
671 }
672 }
673}