signet_libmdbx/lib.rs
1//! Idiomatic and safe Rust bindings for [libmdbx].
2//!
3//! # Overview
4//!
5//! [libmdbx] is a high-performance embedded key-value database based on
6//! LMDB, with additional features like nested transactions, automatic
7//! compaction, and improved durability options.
8//!
9//! This crate provides a safe, idiomatic Rust interface for:
10//! - Creating and managing memory-mapped database environments
11//! - Performing transactional read and write operations
12//! - Iterating over key-value pairs with cursors
13//! - Custom deserialization via the [`TableObject`] trait
14//!
15//! # Quick Start
16//!
17//! Databases are stored in a directory on disk. The following example
18//! demonstrates creating an environment, writing a key-value pair, and
19//! reading it back.
20//!
21//! ```no_run
22//! use signet_libmdbx::{
23//! Environment, DatabaseFlags, WriteFlags, Geometry, MdbxResult,
24//! };
25//! use std::path::Path;
26//!
27//! fn main() -> MdbxResult<()> {
28//! // Open an environment (creates directory if needed)
29//! let env = Environment::builder()
30//! .set_geometry(Geometry {
31//! size: Some(0..(1024 * 1024 * 1024)), // up to 1GB
32//! ..Default::default()
33//! })
34//! .open(Path::new("/tmp/my_database"))?;
35//!
36//! // Write data in a read-write transaction
37//! let txn = env.begin_rw_sync()?;
38//! let db = txn.create_db(None, DatabaseFlags::empty())?;
39//! txn.put(db, b"hello", b"world", WriteFlags::empty())?;
40//! txn.commit()?;
41//!
42//! // Read data in a read-only transaction
43//! let txn = env.begin_ro_sync()?;
44//! let db = txn.open_db(None)?;
45//! let value: Option<Vec<u8>> = txn.get(db.dbi(), b"hello").expect("read failed");
46//! assert_eq!(value.as_deref(), Some(b"world".as_slice()));
47//!
48//! Ok(())
49//! }
50//! ```
51//! # Imports
52//!
53//! For most use cases, import from the crate root:
54//! ```rust,ignore
55//! use signet_libmdbx::{Environment, DatabaseFlags, WriteFlags, Geometry, MdbxResult};
56//! ```
57//!
58//! Transaction and cursor types are returned from `Environment` and transaction
59//! methods - you rarely need to import them directly.
60//!
61//! For advanced usage, import from submodules:
62//! - [`tx`] - Transaction type aliases (`RoTxSync`, `RwTxUnsync`, etc.) and
63//! cursor type aliases
64//! - [`tx::iter`] - Iterator types for cursor iteration
65//! - [`sys`] - Environment internals (`EnvironmentKind`, `PageSize`, etc.)
66//!
67//! # Key Concepts
68//!
69//! - [`Environment`] - A directory containing one or more databases. Created
70//! via [`Environment::builder()`].
71//! - [`TxSync`] and [`TxUnsync`] - Transactions for performing database
72//! operations.
73//! - Synchronized transactions (`TxSync`) can be shared between
74//! threads.
75//! - Unsynchronized transactions (`TxUnsync`) offer better
76//! performance for single-threaded use cases.
77//! - [`Ro`] and [`Rw`] - Marker types indicating read-only (`Ro`) or
78//! read-write (`Rw`) transactions.
79//! - These also exist in sync flavors: [`RoSync`] and [`RwSync`].
80//! - [`Database`] - A named or unnamed key-value store within an environment.
81//! - Accessed with [`Tx::open_db`].
82//! - or created via [`Tx::create_db`].
83//! - [`Cursor`]: Enables iteration and positioned access within a database.
84//! Created via [`TxSync::cursor()`] or [`TxUnsync::cursor()`].
85//!
86//! [`Tx::open_db`]: crate::tx::Tx::open_db
87//! [`Tx::create_db`]: crate::tx::Tx::create_db
88//!
89//! # Cursor Iterators
90//!
91//! Cursors provide several iterator types for traversing databases. The
92//! iterator to use depends on your database flags and access pattern.
93//!
94//! | Iterator | Cursor Methods | Yields | Description |
95//! |----------|----------------|--------|-------------|
96//! | [`Iter`] | `iter_start`, `iter_from` | `(Key, Value)` | Forward iteration over all key-value pairs. |
97//! | [`IterDup`] | `iter_dup_start`, `iter_dup_from` | [`DupItem`] | Flat iteration over DUPSORT tables. Yields `NewKey` for first value of each key, `SameKey` for subsequent. |
98//! | [`IterDupOfKey`] | `iter_dup_of` | `Value` | Single-key iteration over DUPSORT duplicate values. |
99//! | [`IterDupFixed`] | `iter_dupfixed_start`, `iter_dupfixed_from` | [`DupItem`] | Flat iteration over DUPFIXED tables using page-based access. |
100//! | [`IterDupFixedOfKey`] | `iter_dupfixed_of` | `Value` | Single-key iteration over DUPFIXED values. Exact `size_hint()`. |
101//!
102//! [`Iter`]: crate::tx::iter::Iter
103//! [`IterDup`]: crate::tx::iter::IterDup
104//! [`IterDupOfKey`]: crate::tx::iter::IterDupOfKey
105//! [`IterDupFixed`]: crate::tx::iter::IterDupFixed
106//! [`IterDupFixedOfKey`]: crate::tx::iter::IterDupFixedOfKey
107//! [`DupItem`]: crate::tx::iter::DupItem
108//!
109//! # Custom Zero-copy Deserialization with [`TableObject`]
110//!
111//! Implement [`TableObject`] to decode custom types directly from the
112//! database:
113//!
114//! ```
115//! # use std::borrow::Cow;
116//! use signet_libmdbx::{TableObject, ReadResult, MdbxError};
117//!
118//! struct MyKey([u8; 32]);
119//!
120//! impl TableObject<'_> for MyKey {
121//! fn decode_borrow(data: Cow<'_, [u8]>) -> ReadResult<Self> {
122//! let arr: [u8; 32] = data.as_ref().try_into()
123//! .map_err(|_| MdbxError::DecodeErrorLenDiff)?;
124//! Ok(Self(arr))
125//! }
126//! }
127//! ```
128//!
129//! See the [`TableObject`] docs for more examples.
130//!
131//! # Debug assertions
132//!
133//! When compiled with debug assertions enabled (the default for
134//! `cargo build`), this crate performs additional runtime checks to
135//! catch common mistakes.
136//!
137//! 1. Key sizes are checked against the database's configured
138//! `pagesize` and `DatabaseFlags` (e.g. `INTEGERKEY`).
139//! 2. Value sizes are checked against the database's configured
140//! `pagesize` and `DatabaseFlags` (e.g. `INTEGERDUP`).
141//! 3. For `append` operations, it checks that the key being appended is
142//! greater than the current last key using lexicographic comparison.
143//! This check is skipped for `REVERSE_KEY` and `REVERSE_DUP` databases
144//! since they use different comparison semantics (comparing bytes from
145//! end to beginning).
146//!
147//! # Provenance
148//!
149//! Forked from [reth-libmdbx], which was forked from an earlier Apache
150//! licensed version of the `libmdbx-rs` crate. Original LMDB bindings from
151//! [lmdb-rs].
152//!
153//! [libmdbx]: https://github.com/erthink/libmdbx
154//! [reth-libmdbx]: https://github.com/paradigmxyz/reth
155//! [lmdb-rs]: https://github.com/mozilla/lmdb-rs
156
157#![warn(
158 missing_copy_implementations,
159 missing_debug_implementations,
160 missing_docs,
161 unreachable_pub,
162 clippy::missing_const_for_fn,
163 rustdoc::all
164)]
165#![cfg_attr(not(test), warn(unused_crate_dependencies))]
166#![deny(unused_must_use, rust_2018_idioms)]
167#![cfg_attr(docsrs, feature(doc_cfg))]
168
169pub extern crate signet_mdbx_sys as ffi;
170
171mod codec;
172pub use codec::{ObjectLength, TableObject, TableObjectOwned};
173mod error;
174pub use error::{MdbxError, MdbxResult, ReadError, ReadResult};
175
176mod flags;
177pub use flags::{DatabaseFlags, EnvironmentFlags, Mode, SyncMode, WriteFlags};
178
179pub mod sys;
180pub use sys::{Environment, EnvironmentBuilder, Geometry, Info, Stat};
181
182pub mod tx;
183pub use tx::aliases::{TxSync, TxUnsync};
184pub use tx::iter::DupItem;
185pub use tx::{CommitLatency, Cursor, Database, Ro, RoSync, Rw, RwSync, TransactionKind};
186
187#[cfg(test)]
188mod test {
189 use super::*;
190 use byteorder::{ByteOrder, LittleEndian};
191 use tempfile::tempdir;
192
193 /// Regression test for <https://github.com/danburkert/lmdb-rs/issues/21>.
194 /// This test reliably segfaults when run against lmdb compiled with opt
195 /// level -O3 and newer GCC compilers.
196 #[test]
197 fn issue_21_regression() {
198 const HEIGHT_KEY: [u8; 1] = [0];
199
200 let dir = tempdir().unwrap();
201
202 let env = {
203 let mut builder = Environment::builder();
204 builder.set_max_dbs(2);
205 builder
206 .set_geometry(Geometry { size: Some(1_000_000..1_000_000), ..Default::default() });
207 builder.open(dir.path()).expect("open mdbx env")
208 };
209
210 for height in 0..1000 {
211 let mut value = [0u8; 8];
212 LittleEndian::write_u64(&mut value, height);
213 let tx = env.begin_rw_sync().expect("begin_rw_sync");
214 let index = tx.create_db(None, DatabaseFlags::DUP_SORT).expect("open index db");
215 tx.put(index, HEIGHT_KEY, value, WriteFlags::empty()).expect("tx.put");
216 tx.commit().expect("tx.commit");
217 }
218 }
219}