heed/envs/mod.rs
1use std::cmp::Ordering;
2use std::collections::HashMap;
3use std::ffi::c_void;
4use std::fs::File;
5#[cfg(unix)]
6use std::os::unix::io::{AsRawFd, RawFd};
7use std::panic::catch_unwind;
8use std::path::{Path, PathBuf};
9use std::process::abort;
10use std::sync::{Arc, LazyLock, RwLock};
11use std::time::Duration;
12#[cfg(windows)]
13use std::{
14 ffi::OsStr,
15 os::windows::io::{AsRawHandle as _, RawHandle},
16};
17use std::{fmt, io};
18
19use heed_traits::{Comparator, LexicographicComparator};
20use synchronoise::event::SignalEvent;
21
22use crate::mdb::ffi;
23#[allow(unused)] // for cargo auto doc links
24use crate::{Database, DatabaseFlags};
25
26#[cfg(master3)]
27mod encrypted_env;
28mod env;
29mod env_open_options;
30
31#[cfg(master3)]
32pub use encrypted_env::EncryptedEnv;
33pub use env::Env;
34pub(crate) use env::EnvInner;
35pub use env_open_options::EnvOpenOptions;
36
37/// Records the current list of opened environments for tracking purposes. The canonical
38/// path of an environment is removed when either an `Env` or `EncryptedEnv` is closed.
39static OPENED_ENV: LazyLock<RwLock<HashMap<PathBuf, Arc<SignalEvent>>>> =
40 LazyLock::new(RwLock::default);
41
42/// Returns a struct that allows to wait for the effective closing of an environment.
43pub fn env_closing_event<P: AsRef<Path>>(path: P) -> Option<EnvClosingEvent> {
44 let lock = OPENED_ENV.read().unwrap();
45 lock.get(path.as_ref()).map(|signal_event| EnvClosingEvent(signal_event.clone()))
46}
47
48/// Contains information about the environment.
49#[derive(Debug, Clone, Copy)]
50pub struct EnvInfo {
51 /// Address of the map, if fixed.
52 pub map_addr: *mut c_void,
53 /// Size of the data memory map.
54 pub map_size: usize,
55 /// ID of the last used page.
56 pub last_page_number: usize,
57 /// ID of the last committed transaction.
58 pub last_txn_id: usize,
59 /// Maximum number of reader slots in the environment.
60 pub maximum_number_of_readers: u32,
61 /// Number of reader slots used in the environment.
62 pub number_of_readers: u32,
63}
64
65/// Statistics for an environment.
66#[derive(Debug, Clone, Copy)]
67pub struct EnvStat {
68 /// Size of a database page.
69 /// This is currently the same for all databases.
70 pub page_size: u32,
71 /// Depth (height) of the B-tree.
72 pub depth: u32,
73 /// Number of internal (non-leaf) pages
74 pub branch_pages: usize,
75 /// Number of leaf pages.
76 pub leaf_pages: usize,
77 /// Number of overflow pages.
78 pub overflow_pages: usize,
79 /// Number of data items.
80 pub entries: usize,
81}
82
83/// A structure that can be used to wait for the closing event.
84/// Multiple threads can wait on this event.
85#[derive(Clone)]
86pub struct EnvClosingEvent(Arc<SignalEvent>);
87
88impl EnvClosingEvent {
89 /// Blocks this thread until the environment is effectively closed.
90 ///
91 /// # Safety
92 ///
93 /// Make sure that you don't have any copy of the environment in the thread
94 /// that is waiting for a close event. If you do, you will have a deadlock.
95 pub fn wait(&self) {
96 self.0.wait()
97 }
98
99 /// Blocks this thread until either the environment has been closed
100 /// or until the timeout elapses. Returns `true` if the environment
101 /// has been effectively closed.
102 pub fn wait_timeout(&self, timeout: Duration) -> bool {
103 self.0.wait_timeout(timeout)
104 }
105}
106
107impl fmt::Debug for EnvClosingEvent {
108 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109 f.debug_struct("EnvClosingEvent").finish()
110 }
111}
112
113// Thanks to the mozilla/rkv project
114// Workaround the UNC path on Windows, see https://github.com/rust-lang/rust/issues/42869.
115// Otherwise, `Env::from_env()` will panic with error_no(123).
116#[cfg(not(windows))]
117fn canonicalize_path(path: &Path) -> io::Result<PathBuf> {
118 path.canonicalize()
119}
120
121#[cfg(windows)]
122fn canonicalize_path(path: &Path) -> io::Result<PathBuf> {
123 let canonical = path.canonicalize()?;
124 let url = url::Url::from_file_path(&canonical)
125 .map_err(|_e| io::Error::new(io::ErrorKind::Other, "URL passing error"))?;
126 url.to_file_path()
127 .map_err(|_e| io::Error::new(io::ErrorKind::Other, "path canonicalization error"))
128}
129
130#[cfg(windows)]
131/// Adding a 'missing' trait from windows OsStrExt
132trait OsStrExtLmdb {
133 fn as_bytes(&self) -> &[u8];
134}
135#[cfg(windows)]
136impl OsStrExtLmdb for OsStr {
137 fn as_bytes(&self) -> &[u8] {
138 &self.to_str().unwrap().as_bytes()
139 }
140}
141
142#[cfg(unix)]
143fn get_file_fd(file: &File) -> RawFd {
144 file.as_raw_fd()
145}
146
147#[cfg(windows)]
148fn get_file_fd(file: &File) -> RawHandle {
149 file.as_raw_handle()
150}
151
152/// A helper function that transforms the LMDB types into Rust types (`MDB_val` into slices)
153/// and vice versa, the Rust types into C types (`Ordering` into an integer).
154///
155/// # Safety
156///
157/// `a` and `b` should both properly aligned, valid for reads and should point to a valid
158/// [`MDB_val`][ffi::MDB_val]. An [`MDB_val`][ffi::MDB_val] (consists of a pointer and size) is
159/// valid when its pointer (`mv_data`) is valid for reads of `mv_size` bytes and is not null.
160unsafe extern "C" fn custom_key_cmp_wrapper<C: Comparator>(
161 a: *const ffi::MDB_val,
162 b: *const ffi::MDB_val,
163) -> i32 {
164 let a = unsafe { ffi::from_val(*a) };
165 let b = unsafe { ffi::from_val(*b) };
166 match catch_unwind(|| C::compare(a, b)) {
167 Ok(Ordering::Less) => -1,
168 Ok(Ordering::Equal) => 0,
169 Ok(Ordering::Greater) => 1,
170 Err(_) => abort(),
171 }
172}
173
174/// A representation of LMDB's default comparator behavior.
175///
176/// This enum is used to indicate the absence of a custom comparator for an LMDB
177/// database instance. When a [`Database`] is created or opened with
178/// [`DefaultComparator`], it signifies that the comparator should not be explicitly
179/// set via [`ffi::mdb_set_compare`]. Consequently, the database
180/// instance utilizes LMDB's built-in default comparator, which inherently performs
181/// lexicographic comparison of keys.
182///
183/// This comparator's lexicographic implementation is employed in scenarios involving
184/// prefix iterators. Specifically, methods other than [`Comparator::compare`] are utilized
185/// to determine the lexicographic successors and predecessors of byte sequences, which
186/// is essential for these iterators' operation.
187///
188/// When a custom comparator is provided, the wrapper is responsible for setting
189/// it with the [`ffi::mdb_set_compare`] function, which overrides the default comparison
190/// behavior of LMDB with the user-defined logic.
191#[derive(Debug)]
192pub enum DefaultComparator {}
193
194impl LexicographicComparator for DefaultComparator {
195 #[inline]
196 fn compare_elem(a: u8, b: u8) -> Ordering {
197 a.cmp(&b)
198 }
199
200 #[inline]
201 fn successor(elem: u8) -> Option<u8> {
202 match elem {
203 u8::MAX => None,
204 elem => Some(elem + 1),
205 }
206 }
207
208 #[inline]
209 fn predecessor(elem: u8) -> Option<u8> {
210 match elem {
211 u8::MIN => None,
212 elem => Some(elem - 1),
213 }
214 }
215
216 #[inline]
217 fn max_elem() -> u8 {
218 u8::MAX
219 }
220
221 #[inline]
222 fn min_elem() -> u8 {
223 u8::MIN
224 }
225}
226
227/// A representation of LMDB's `MDB_INTEGERKEY` and `MDB_INTEGERDUP` comparator behavior.
228///
229/// This enum is used to indicate a table should be sorted by the keys numeric
230/// value in native byte order. When a [`Database`] is created or opened with
231/// [`IntegerComparator`], it signifies that the comparator should not be explicitly
232/// set via [`ffi::mdb_set_compare`], instead the flag [`DatabaseFlags::INTEGER_KEY`]
233/// or [`DatabaseFlags::INTEGER_DUP`] is set on the table.
234///
235/// This can only be used on certain types: either `u32` or `usize`.
236/// The keys must all be of the same size.
237#[derive(Debug)]
238pub enum IntegerComparator {}
239
240impl Comparator for IntegerComparator {
241 fn compare(a: &[u8], b: &[u8]) -> Ordering {
242 #[cfg(target_endian = "big")]
243 return a.cmp(b);
244
245 #[cfg(target_endian = "little")]
246 {
247 let len = a.len();
248
249 for i in (0..len).rev() {
250 match a[i].cmp(&b[i]) {
251 Ordering::Equal => continue,
252 other => return other,
253 }
254 }
255
256 Ordering::Equal
257 }
258 }
259}
260
261/// Whether to perform compaction while copying an environment.
262#[derive(Debug, Copy, Clone)]
263pub enum CompactionOption {
264 /// Omit free pages and sequentially renumber all pages in output.
265 ///
266 /// This option consumes more CPU and runs more slowly than the default.
267 /// Currently it fails if the environment has suffered a page leak.
268 Enabled,
269
270 /// Copy everything without taking any special action about free pages.
271 Disabled,
272}
273
274/// Whether to enable or disable flags in [`Env::set_flags`].
275#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum FlagSetMode {
277 /// Enable the flags.
278 Enable,
279 /// Disable the flags.
280 Disable,
281}
282
283impl FlagSetMode {
284 /// Convert the enum into the `i32` required by LMDB.
285 /// "A non-zero value sets the flags, zero clears them."
286 /// <http://www.lmdb.tech/doc/group__mdb.html#ga83f66cf02bfd42119451e9468dc58445>
287 fn as_mdb_env_set_flags_input(self) -> i32 {
288 match self {
289 Self::Enable => 1,
290 Self::Disable => 0,
291 }
292 }
293}