1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#![doc(
    html_favicon_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon.ico?raw=true"
)]
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/meilisearch/heed/main/assets/heed-pigeon-logo.png?raw=true"
)]

//! `heed` is a high-level wrapper of [LMDB].
//!
//! The [cookbook] will give you a variety of complete Rust programs to use with heed.
//!
//! ----
//!
//! This crate simply facilitates the use of LMDB by providing a mechanism to store and
//! retrieve Rust types. It abstracts away some of the complexities of the raw LMDB usage
//! while retaining its performance characteristics. The functionality is achieved with the help
//! of the serde library for data serialization concerns.
//!
//! LMDB stands for Lightning Memory-Mapped Database, which utilizes memory-mapped files
//! for efficient data storage and retrieval by mapping file content directly into the virtual
//! address space. `heed` derives its efficiency from the underlying LMDB without imposing
//! additional runtime costs.
//!
//! [LMDB]: https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database
//!
//! # Examples
//!
//! Open a database that will support some typed key/data and ensure, at compile time,
//! that you'll write those types and not others.
//!
//! ```
//! use std::fs;
//! use std::path::Path;
//! use heed::{EnvOpenOptions, Database};
//! use heed::types::*;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let dir = tempfile::tempdir()?;
//! let env = unsafe { EnvOpenOptions::new().open(dir.path())? };
//!
//! // we will open the default unnamed database
//! let mut wtxn = env.write_txn()?;
//! let db: Database<Str, U32<byteorder::NativeEndian>> = env.create_database(&mut wtxn, None)?;
//!
//! // opening a write transaction
//! db.put(&mut wtxn, "seven", &7)?;
//! db.put(&mut wtxn, "zero", &0)?;
//! db.put(&mut wtxn, "five", &5)?;
//! db.put(&mut wtxn, "three", &3)?;
//! wtxn.commit()?;
//!
//! // opening a read transaction
//! // to check if those values are now available
//! let mut rtxn = env.read_txn()?;
//!
//! let ret = db.get(&rtxn, "zero")?;
//! assert_eq!(ret, Some(0));
//!
//! let ret = db.get(&rtxn, "five")?;
//! assert_eq!(ret, Some(5));
//! # Ok(()) }
//! ```
#![warn(missing_docs)]

pub mod cookbook;
mod cursor;
mod database;
mod env;
pub mod iteration_method;
mod iterator;
mod mdb;
mod reserved_space;
mod txn;

use std::ffi::CStr;
use std::{error, fmt, io, mem, result};

use heed_traits as traits;
pub use {byteorder, heed_types as types};

use self::cursor::{RoCursor, RwCursor};
pub use self::database::{Database, DatabaseOpenOptions, DatabaseStat};
pub use self::env::{
    env_closing_event, CompactionOption, DefaultComparator, Env, EnvClosingEvent, EnvInfo,
    EnvOpenOptions, FlagSetMode,
};
pub use self::iterator::{
    RoIter, RoPrefix, RoRange, RoRevIter, RoRevPrefix, RoRevRange, RwIter, RwPrefix, RwRange,
    RwRevIter, RwRevPrefix, RwRevRange,
};
pub use self::mdb::error::Error as MdbError;
use self::mdb::ffi::{from_val, into_val};
pub use self::mdb::flags::{DatabaseFlags, EnvFlags, PutFlags};
pub use self::reserved_space::ReservedSpace;
pub use self::traits::{BoxedError, BytesDecode, BytesEncode, Comparator, LexicographicComparator};
pub use self::txn::{RoTxn, RwTxn};

/// The underlying LMDB library version information.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LmdbVersion {
    /// The library version as a string.
    pub string: &'static str,
    /// The library major version number.
    pub major: i32,
    /// The library minor version number.
    pub minor: i32,
    /// The library patch version number.
    pub patch: i32,
}

/// Return the LMDB library version information.
///
/// ```
/// use heed::{lmdb_version, LmdbVersion};
///
/// let expected = LmdbVersion {
///     string: "LMDB 0.9.70: (December 19, 2015)",
///     major: 0,
///     minor: 9,
///     patch: 70,
/// };
/// assert_eq!(lmdb_version(), expected);
/// ```
pub fn lmdb_version() -> LmdbVersion {
    let mut major = mem::MaybeUninit::uninit();
    let mut minor = mem::MaybeUninit::uninit();
    let mut patch = mem::MaybeUninit::uninit();

    unsafe {
        let string_ptr =
            mdb::ffi::mdb_version(major.as_mut_ptr(), minor.as_mut_ptr(), patch.as_mut_ptr());
        LmdbVersion {
            string: CStr::from_ptr(string_ptr).to_str().unwrap(),
            major: major.assume_init(),
            minor: minor.assume_init(),
            patch: patch.assume_init(),
        }
    }
}

/// An error that encapsulates all possible errors in this crate.
#[derive(Debug)]
pub enum Error {
    /// I/O error: can come from the standard library or be a rewrapped [`MdbError`].
    Io(io::Error),
    /// LMDB error.
    Mdb(MdbError),
    /// Encoding error.
    Encoding(BoxedError),
    /// Decoding error.
    Decoding(BoxedError),
    /// Database closing in progress.
    DatabaseClosing,
    /// Attempt to open [`Env`] with different options.
    BadOpenOptions {
        /// The options that were used to originally open this env.
        options: EnvOpenOptions,
        /// The env opened with the original options.
        env: Env,
    },
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Io(error) => write!(f, "{}", error),
            Error::Mdb(error) => write!(f, "{}", error),
            Error::Encoding(error) => write!(f, "error while encoding: {}", error),
            Error::Decoding(error) => write!(f, "error while decoding: {}", error),
            Error::DatabaseClosing => {
                f.write_str("database is in a closing phase, you can't open it at the same time")
            }
            Error::BadOpenOptions { .. } => {
                f.write_str("an environment is already opened with different options")
            }
        }
    }
}

impl error::Error for Error {}

impl From<MdbError> for Error {
    fn from(error: MdbError) -> Error {
        match error {
            MdbError::Other(e) => Error::Io(io::Error::from_raw_os_error(e)),
            _ => Error::Mdb(error),
        }
    }
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Error {
        Error::Io(error)
    }
}

/// Either a success or an [`Error`].
pub type Result<T> = result::Result<T, Error>;

/// An unspecified type.
///
/// It is used as placeholders when creating a database.
/// It does not implement the [`BytesEncode`] and [`BytesDecode`] traits
/// and therefore can't be used as codecs. You must use the [`Database::remap_types`]
/// to properly define them.
pub enum Unspecified {}

macro_rules! assert_eq_env_db_txn {
    ($database:ident, $txn:ident) => {
        assert!(
            $database.env_ident == $txn.env_mut_ptr() as usize,
            "The database environment doesn't match the transaction's environment"
        );
    };
}

macro_rules! assert_eq_env_txn {
    ($env:expr, $txn:ident) => {
        assert!(
            $env.env_mut_ptr() == $txn.env_mut_ptr(),
            "The environment doesn't match the transaction's environment"
        );
    };
}

pub(crate) use {assert_eq_env_db_txn, assert_eq_env_txn};

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn error_is_send_sync() {
        fn give_me_send_sync<T: Send + Sync>(_: T) {}

        let error = Error::Encoding(Box::from("There is an issue, you know?"));
        give_me_send_sync(error);
    }
}