heed/
lib.rs

1#![doc(
2    html_favicon_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon.ico?raw=true"
3)]
4#![doc(
5    html_logo_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon-logo.png?raw=true"
6)]
7
8//! `heed` is a high-level wrapper of [LMDB].
9//!
10//! The [cookbook] will give you a variety of complete Rust programs to use with heed.
11//!
12//! ----
13//!
14//! This crate simply facilitates the use of LMDB by providing a mechanism to store and
15//! retrieve Rust types. It abstracts away some of the complexities of the raw LMDB usage
16//! while retaining its performance characteristics. The functionality is achieved with the help
17//! of the serde library for data serialization concerns.
18//!
19//! LMDB stands for Lightning Memory-Mapped Database, which utilizes memory-mapped files
20//! for efficient data storage and retrieval by mapping file content directly into the virtual
21//! address space. `heed` derives its efficiency from the underlying LMDB without imposing
22//! additional runtime costs.
23//!
24//! [LMDB]: https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database
25//!
26//! # Examples
27//!
28//! Open a database that will support some typed key/data and ensure, at compile time,
29//! that you'll write those types and not others.
30//!
31//! ```
32//! use std::fs;
33//! use std::path::Path;
34//! use heed::{EnvOpenOptions, Database};
35//! use heed::types::*;
36//!
37//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
38//! let dir = tempfile::tempdir()?;
39//! let env = unsafe { EnvOpenOptions::new().open(dir.path())? };
40//!
41//! // we will open the default unnamed database
42//! let mut wtxn = env.write_txn()?;
43//! let db: Database<Str, U32<byteorder::NativeEndian>> = env.create_database(&mut wtxn, None)?;
44//!
45//! // opening a write transaction
46//! db.put(&mut wtxn, "seven", &7)?;
47//! db.put(&mut wtxn, "zero", &0)?;
48//! db.put(&mut wtxn, "five", &5)?;
49//! db.put(&mut wtxn, "three", &3)?;
50//! wtxn.commit()?;
51//!
52//! // opening a read transaction
53//! // to check if those values are now available
54//! let mut rtxn = env.read_txn()?;
55//!
56//! let ret = db.get(&rtxn, "zero")?;
57//! assert_eq!(ret, Some(0));
58//!
59//! let ret = db.get(&rtxn, "five")?;
60//! assert_eq!(ret, Some(5));
61//! # Ok(()) }
62//! ```
63#![warn(missing_docs)]
64
65pub mod cookbook;
66mod cursor;
67mod database;
68mod env;
69pub mod iteration_method;
70mod iterator;
71mod mdb;
72mod reserved_space;
73mod txn;
74
75use std::ffi::CStr;
76use std::{error, fmt, io, mem, result};
77
78use heed_traits as traits;
79pub use {byteorder, heed_types as types};
80
81use self::cursor::{RoCursor, RwCursor};
82pub use self::database::{Database, DatabaseOpenOptions, DatabaseStat};
83pub use self::env::{
84    env_closing_event, CompactionOption, DefaultComparator, Env, EnvClosingEvent, EnvInfo,
85    EnvOpenOptions, FlagSetMode,
86};
87pub use self::iterator::{
88    RoIter, RoPrefix, RoRange, RoRevIter, RoRevPrefix, RoRevRange, RwIter, RwPrefix, RwRange,
89    RwRevIter, RwRevPrefix, RwRevRange,
90};
91pub use self::mdb::error::Error as MdbError;
92use self::mdb::ffi::{from_val, into_val};
93pub use self::mdb::flags::{DatabaseFlags, EnvFlags, PutFlags};
94pub use self::reserved_space::ReservedSpace;
95pub use self::traits::{BoxedError, BytesDecode, BytesEncode, Comparator, LexicographicComparator};
96pub use self::txn::{RoTxn, RwTxn};
97
98/// The underlying LMDB library version information.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
100pub struct LmdbVersion {
101    /// The library version as a string.
102    pub string: &'static str,
103    /// The library major version number.
104    pub major: i32,
105    /// The library minor version number.
106    pub minor: i32,
107    /// The library patch version number.
108    pub patch: i32,
109}
110
111/// Return the LMDB library version information.
112///
113/// ```
114/// use heed::{lmdb_version, LmdbVersion};
115///
116/// let expected = LmdbVersion {
117///     string: "LMDB 0.9.70: (December 19, 2015)",
118///     major: 0,
119///     minor: 9,
120///     patch: 70,
121/// };
122/// assert_eq!(lmdb_version(), expected);
123/// ```
124pub fn lmdb_version() -> LmdbVersion {
125    let mut major = mem::MaybeUninit::uninit();
126    let mut minor = mem::MaybeUninit::uninit();
127    let mut patch = mem::MaybeUninit::uninit();
128
129    unsafe {
130        let string_ptr =
131            mdb::ffi::mdb_version(major.as_mut_ptr(), minor.as_mut_ptr(), patch.as_mut_ptr());
132        LmdbVersion {
133            string: CStr::from_ptr(string_ptr).to_str().unwrap(),
134            major: major.assume_init(),
135            minor: minor.assume_init(),
136            patch: patch.assume_init(),
137        }
138    }
139}
140
141/// An error that encapsulates all possible errors in this crate.
142#[derive(Debug)]
143pub enum Error {
144    /// I/O error: can come from the standard library or be a rewrapped [`MdbError`].
145    Io(io::Error),
146    /// LMDB error.
147    Mdb(MdbError),
148    /// Encoding error.
149    Encoding(BoxedError),
150    /// Decoding error.
151    Decoding(BoxedError),
152    /// Database closing in progress.
153    DatabaseClosing,
154    /// Attempt to open [`Env`] with different options.
155    BadOpenOptions {
156        /// The options that were used to originally open this env.
157        options: EnvOpenOptions,
158        /// The env opened with the original options.
159        env: Env,
160    },
161}
162
163impl fmt::Display for Error {
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        match self {
166            Error::Io(error) => write!(f, "{}", error),
167            Error::Mdb(error) => write!(f, "{}", error),
168            Error::Encoding(error) => write!(f, "error while encoding: {}", error),
169            Error::Decoding(error) => write!(f, "error while decoding: {}", error),
170            Error::DatabaseClosing => {
171                f.write_str("database is in a closing phase, you can't open it at the same time")
172            }
173            Error::BadOpenOptions { .. } => {
174                f.write_str("an environment is already opened with different options")
175            }
176        }
177    }
178}
179
180impl error::Error for Error {}
181
182impl From<MdbError> for Error {
183    fn from(error: MdbError) -> Error {
184        match error {
185            MdbError::Other(e) => Error::Io(io::Error::from_raw_os_error(e)),
186            _ => Error::Mdb(error),
187        }
188    }
189}
190
191impl From<io::Error> for Error {
192    fn from(error: io::Error) -> Error {
193        Error::Io(error)
194    }
195}
196
197/// Either a success or an [`Error`].
198pub type Result<T> = result::Result<T, Error>;
199
200/// An unspecified type.
201///
202/// It is used as placeholders when creating a database.
203/// It does not implement the [`BytesEncode`] and [`BytesDecode`] traits
204/// and therefore can't be used as codecs. You must use the [`Database::remap_types`]
205/// to properly define them.
206pub enum Unspecified {}
207
208macro_rules! assert_eq_env_db_txn {
209    ($database:ident, $txn:ident) => {
210        assert!(
211            $database.env_ident == $txn.env_mut_ptr() as usize,
212            "The database environment doesn't match the transaction's environment"
213        );
214    };
215}
216
217macro_rules! assert_eq_env_txn {
218    ($env:expr, $txn:ident) => {
219        assert!(
220            $env.env_mut_ptr() == $txn.env_mut_ptr(),
221            "The environment doesn't match the transaction's environment"
222        );
223    };
224}
225
226pub(crate) use {assert_eq_env_db_txn, assert_eq_env_txn};
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn error_is_send_sync() {
234        fn give_me_send_sync<T: Send + Sync>(_: T) {}
235
236        let error = Error::Encoding(Box::from("There is an issue, you know?"));
237        give_me_send_sync(error);
238    }
239}