heed/
env.rs

1use std::any::TypeId;
2use std::cmp::Ordering;
3use std::collections::hash_map::{Entry, HashMap};
4use std::ffi::{c_void, CString};
5use std::fs::{File, Metadata};
6use std::io::ErrorKind::NotFound;
7#[cfg(unix)]
8use std::os::unix::{
9    ffi::OsStrExt,
10    io::{AsRawFd, BorrowedFd, RawFd},
11};
12use std::panic::catch_unwind;
13use std::path::{Path, PathBuf};
14use std::process::abort;
15use std::sync::{Arc, RwLock};
16use std::time::Duration;
17#[cfg(windows)]
18use std::{
19    ffi::OsStr,
20    os::windows::io::{AsRawHandle, BorrowedHandle, RawHandle},
21};
22use std::{fmt, io, mem, ptr};
23
24use heed_traits::{Comparator, LexicographicComparator};
25use once_cell::sync::Lazy;
26use synchronoise::event::SignalEvent;
27
28use crate::cursor::MoveOperation;
29use crate::database::DatabaseOpenOptions;
30use crate::mdb::error::mdb_result;
31use crate::mdb::ffi;
32use crate::mdb::lmdb_flags::AllDatabaseFlags;
33use crate::{Database, EnvFlags, Error, Result, RoCursor, RoTxn, RwTxn, Unspecified};
34
35/// The list of opened environments, the value is an optional environment, it is None
36/// when someone asks to close the environment, closing is a two-phase step, to make sure
37/// noone tries to open the same environment between these two phases.
38///
39/// Trying to open a None marked environment returns an error to the user trying to open it.
40static OPENED_ENV: Lazy<RwLock<HashMap<PathBuf, EnvEntry>>> = Lazy::new(RwLock::default);
41
42struct EnvEntry {
43    env: Option<Env>,
44    signal_event: Arc<SignalEvent>,
45    options: EnvOpenOptions,
46}
47
48// Thanks to the mozilla/rkv project
49// Workaround the UNC path on Windows, see https://github.com/rust-lang/rust/issues/42869.
50// Otherwise, `Env::from_env()` will panic with error_no(123).
51#[cfg(not(windows))]
52fn canonicalize_path(path: &Path) -> io::Result<PathBuf> {
53    path.canonicalize()
54}
55
56#[cfg(windows)]
57fn canonicalize_path(path: &Path) -> io::Result<PathBuf> {
58    let canonical = path.canonicalize()?;
59    let url = url::Url::from_file_path(&canonical)
60        .map_err(|_e| io::Error::new(io::ErrorKind::Other, "URL passing error"))?;
61    url.to_file_path()
62        .map_err(|_e| io::Error::new(io::ErrorKind::Other, "path canonicalization error"))
63}
64
65#[cfg(windows)]
66/// Adding a 'missing' trait from windows OsStrExt
67trait OsStrExtLmdb {
68    fn as_bytes(&self) -> &[u8];
69}
70#[cfg(windows)]
71impl OsStrExtLmdb for OsStr {
72    fn as_bytes(&self) -> &[u8] {
73        &self.to_str().unwrap().as_bytes()
74    }
75}
76
77#[cfg(unix)]
78fn get_file_fd(file: &File) -> RawFd {
79    file.as_raw_fd()
80}
81
82#[cfg(windows)]
83fn get_file_fd(file: &File) -> RawHandle {
84    file.as_raw_handle()
85}
86
87#[cfg(unix)]
88/// Get metadata from a file descriptor.
89unsafe fn metadata_from_fd(raw_fd: RawFd) -> io::Result<Metadata> {
90    let fd = BorrowedFd::borrow_raw(raw_fd);
91    let owned = fd.try_clone_to_owned()?;
92    File::from(owned).metadata()
93}
94
95#[cfg(windows)]
96/// Get metadata from a file descriptor.
97unsafe fn metadata_from_fd(raw_fd: RawHandle) -> io::Result<Metadata> {
98    let fd = BorrowedHandle::borrow_raw(raw_fd);
99    let owned = fd.try_clone_to_owned()?;
100    File::from(owned).metadata()
101}
102
103/// Options and flags which can be used to configure how an environment is opened.
104#[derive(Clone, Debug, PartialEq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct EnvOpenOptions {
107    map_size: Option<usize>,
108    max_readers: Option<u32>,
109    max_dbs: Option<u32>,
110    flags: EnvFlags,
111}
112
113impl Default for EnvOpenOptions {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl EnvOpenOptions {
120    /// Creates a blank new set of options ready for configuration.
121    pub fn new() -> EnvOpenOptions {
122        EnvOpenOptions {
123            map_size: None,
124            max_readers: None,
125            max_dbs: None,
126            flags: EnvFlags::empty(),
127        }
128    }
129
130    /// Set the size of the memory map to use for this environment.
131    pub fn map_size(&mut self, size: usize) -> &mut Self {
132        self.map_size = Some(size);
133        self
134    }
135
136    /// Set the maximum number of threads/reader slots for the environment.
137    pub fn max_readers(&mut self, readers: u32) -> &mut Self {
138        self.max_readers = Some(readers);
139        self
140    }
141
142    /// Set the maximum number of named databases for the environment.
143    pub fn max_dbs(&mut self, dbs: u32) -> &mut Self {
144        self.max_dbs = Some(dbs);
145        self
146    }
147
148    /// Set one or more [LMDB flags](http://www.lmdb.tech/doc/group__mdb__env.html).
149    /// ```
150    /// use std::fs;
151    /// use std::path::Path;
152    /// use heed::{EnvOpenOptions, Database, EnvFlags};
153    /// use heed::types::*;
154    ///
155    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
156    /// fs::create_dir_all(Path::new("target").join("database.mdb"))?;
157    /// let mut env_builder = EnvOpenOptions::new();
158    /// unsafe { env_builder.flags(EnvFlags::NO_TLS | EnvFlags::NO_META_SYNC); }
159    /// let dir = tempfile::tempdir().unwrap();
160    /// let env = unsafe { env_builder.open(dir.path())? };
161    ///
162    /// // we will open the default unnamed database
163    /// let mut wtxn = env.write_txn()?;
164    /// let db: Database<Str, U32<byteorder::NativeEndian>> = env.create_database(&mut wtxn, None)?;
165    ///
166    /// // opening a write transaction
167    /// db.put(&mut wtxn, "seven", &7)?;
168    /// db.put(&mut wtxn, "zero", &0)?;
169    /// db.put(&mut wtxn, "five", &5)?;
170    /// db.put(&mut wtxn, "three", &3)?;
171    /// wtxn.commit()?;
172    ///
173    /// // force the OS to flush the buffers (see Flag::NoSync and Flag::NoMetaSync).
174    /// env.force_sync();
175    ///
176    /// // opening a read transaction
177    /// // to check if those values are now available
178    /// let mut rtxn = env.read_txn()?;
179    ///
180    /// let ret = db.get(&rtxn, "zero")?;
181    /// assert_eq!(ret, Some(0));
182    ///
183    /// let ret = db.get(&rtxn, "five")?;
184    /// assert_eq!(ret, Some(5));
185    /// # Ok(()) }
186    /// ```
187    ///
188    /// # Safety
189    ///
190    /// It is unsafe to use unsafe LMDB flags such as `NO_SYNC`, `NO_META_SYNC`, or `NO_LOCK`.
191    pub unsafe fn flags(&mut self, flags: EnvFlags) -> &mut Self {
192        self.flags |= flags;
193        self
194    }
195
196    /// Open an environment that will be located at the specified path.
197    ///
198    /// # Safety
199    /// LMDB is backed by a memory map [^1] which comes with some safety precautions.
200    ///
201    /// Memory map constructors are marked `unsafe` because of the potential
202    /// for Undefined Behavior (UB) using the map if the underlying file is
203    /// subsequently modified, in or out of process.
204    ///
205    /// LMDB itself has a locking system that solves this problem,
206    /// but it will not save you from making mistakes yourself.
207    ///
208    /// These are some things to take note of:
209    ///
210    /// - Avoid long-lived transactions, they will cause the database to grow quickly [^2]
211    /// - Avoid aborting your process with an active transaction [^3]
212    /// - Do not use LMDB on remote filesystems, even between processes on the same host [^4]
213    /// - You must manage concurrent accesses yourself if using [`EnvFlags::NO_LOCK`] [^5]
214    /// - Anything that causes LMDB's lock file to be broken will cause synchronization issues and may introduce UB [^6]
215    ///
216    /// `heed` itself upholds some safety invariants, including but not limited to:
217    /// - Calling [`EnvOpenOptions::open`] twice in the same process, at the same time is OK [^7]
218    ///
219    /// For more details, it is highly recommended to read LMDB's official documentation. [^8]
220    ///
221    /// [^1]: <https://en.wikipedia.org/wiki/Memory_map>
222    ///
223    /// [^2]: <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L107-L114>
224    ///
225    /// [^3]: <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L118-L121>
226    ///
227    /// [^4]: <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L129>
228    ///
229    /// [^5]: <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L129>
230    ///
231    /// [^6]: <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L49-L52>
232    ///
233    /// [^7]: <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L102-L105>
234    ///
235    /// [^8]: <http://www.lmdb.tech/doc/index.html>
236    pub unsafe fn open<P: AsRef<Path>>(&self, path: P) -> Result<Env> {
237        let mut lock = OPENED_ENV.write().unwrap();
238
239        let path = match canonicalize_path(path.as_ref()) {
240            Err(err) => {
241                if err.kind() == NotFound && self.flags.contains(EnvFlags::NO_SUB_DIR) {
242                    let path = path.as_ref();
243                    match path.parent().zip(path.file_name()) {
244                        Some((dir, file_name)) => canonicalize_path(dir)?.join(file_name),
245                        None => return Err(err.into()),
246                    }
247                } else {
248                    return Err(err.into());
249                }
250            }
251            Ok(path) => path,
252        };
253
254        match lock.entry(path) {
255            Entry::Occupied(entry) => {
256                let env = entry.get().env.clone().ok_or(Error::DatabaseClosing)?;
257                let options = entry.get().options.clone();
258                if &options == self {
259                    Ok(env)
260                } else {
261                    Err(Error::BadOpenOptions { env, options })
262                }
263            }
264            Entry::Vacant(entry) => {
265                let path = entry.key();
266                let path_str = CString::new(path.as_os_str().as_bytes()).unwrap();
267
268                unsafe {
269                    let mut env: *mut ffi::MDB_env = ptr::null_mut();
270                    mdb_result(ffi::mdb_env_create(&mut env))?;
271
272                    if let Some(size) = self.map_size {
273                        if size % page_size::get() != 0 {
274                            let msg = format!(
275                                "map size ({}) must be a multiple of the system page size ({})",
276                                size,
277                                page_size::get()
278                            );
279                            return Err(Error::Io(io::Error::new(
280                                io::ErrorKind::InvalidInput,
281                                msg,
282                            )));
283                        }
284                        mdb_result(ffi::mdb_env_set_mapsize(env, size))?;
285                    }
286
287                    if let Some(readers) = self.max_readers {
288                        mdb_result(ffi::mdb_env_set_maxreaders(env, readers))?;
289                    }
290
291                    if let Some(dbs) = self.max_dbs {
292                        mdb_result(ffi::mdb_env_set_maxdbs(env, dbs))?;
293                    }
294
295                    // When the `read-txn-no-tls` feature is enabled, we must force LMDB
296                    // to avoid using the thread local storage, this way we allow users
297                    // to use references of RoTxn between threads safely.
298                    let flags = if cfg!(feature = "read-txn-no-tls") {
299                        self.flags | EnvFlags::NO_TLS
300                    } else {
301                        self.flags
302                    };
303
304                    let result =
305                        mdb_result(ffi::mdb_env_open(env, path_str.as_ptr(), flags.bits(), 0o600));
306
307                    match result {
308                        Ok(()) => {
309                            let signal_event = Arc::new(SignalEvent::manual(false));
310                            let inner = EnvInner { env, path: path.clone() };
311                            let env = Env(Arc::new(inner));
312                            let cache_entry = EnvEntry {
313                                env: Some(env.clone()),
314                                options: self.clone(),
315                                signal_event,
316                            };
317                            entry.insert(cache_entry);
318                            Ok(env)
319                        }
320                        Err(e) => {
321                            ffi::mdb_env_close(env);
322                            Err(e.into())
323                        }
324                    }
325                }
326            }
327        }
328    }
329}
330
331/// Returns a struct that allows to wait for the effective closing of an environment.
332pub fn env_closing_event<P: AsRef<Path>>(path: P) -> Option<EnvClosingEvent> {
333    let lock = OPENED_ENV.read().unwrap();
334    lock.get(path.as_ref()).map(|e| EnvClosingEvent(e.signal_event.clone()))
335}
336
337/// An environment handle constructed by using [`EnvOpenOptions`].
338#[derive(Clone)]
339pub struct Env(Arc<EnvInner>);
340
341impl fmt::Debug for Env {
342    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
343        let EnvInner { env: _, path } = self.0.as_ref();
344        f.debug_struct("Env").field("path", &path.display()).finish_non_exhaustive()
345    }
346}
347
348struct EnvInner {
349    env: *mut ffi::MDB_env,
350    path: PathBuf,
351}
352
353unsafe impl Send for EnvInner {}
354
355unsafe impl Sync for EnvInner {}
356
357impl Drop for EnvInner {
358    fn drop(&mut self) {
359        let mut lock = OPENED_ENV.write().unwrap();
360
361        match lock.remove(&self.path) {
362            None => panic!("It seems another env closed this env before"),
363            Some(EnvEntry { signal_event, .. }) => {
364                unsafe {
365                    ffi::mdb_env_close(self.env);
366                }
367                // We signal to all the waiters that the env is closed now.
368                signal_event.signal();
369            }
370        }
371    }
372}
373
374/// A helper function that transforms the LMDB types into Rust types (`MDB_val` into slices)
375/// and vice versa, the Rust types into C types (`Ordering` into an integer).
376///
377/// # Safety
378///
379/// `a` and `b` should both properly aligned, valid for reads and should point to a valid
380/// [`MDB_val`][ffi::MDB_val]. An [`MDB_val`][ffi::MDB_val] (consists of a pointer and size) is
381/// valid when its pointer (`mv_data`) is valid for reads of `mv_size` bytes and is not null.
382unsafe extern "C" fn custom_key_cmp_wrapper<C: Comparator>(
383    a: *const ffi::MDB_val,
384    b: *const ffi::MDB_val,
385) -> i32 {
386    let a = unsafe { ffi::from_val(*a) };
387    let b = unsafe { ffi::from_val(*b) };
388    match catch_unwind(|| C::compare(a, b)) {
389        Ok(Ordering::Less) => -1,
390        Ok(Ordering::Equal) => 0,
391        Ok(Ordering::Greater) => 1,
392        Err(_) => abort(),
393    }
394}
395
396/// A representation of LMDB's default comparator behavior.
397///
398/// This enum is used to indicate the absence of a custom comparator for an LMDB
399/// database instance. When a [`Database`] is created or opened with
400/// [`DefaultComparator`], it signifies that the comparator should not be explicitly
401/// set via [`ffi::mdb_set_compare`]. Consequently, the database
402/// instance utilizes LMDB's built-in default comparator, which inherently performs
403/// lexicographic comparison of keys.
404///
405/// This comparator's lexicographic implementation is employed in scenarios involving
406/// prefix iterators. Specifically, methods other than [`Comparator::compare`] are utilized
407/// to determine the lexicographic successors and predecessors of byte sequences, which
408/// is essential for these iterators' operation.
409///
410/// When a custom comparator is provided, the wrapper is responsible for setting
411/// it with the [`ffi::mdb_set_compare`] function, which overrides the default comparison
412/// behavior of LMDB with the user-defined logic.
413pub enum DefaultComparator {}
414
415impl LexicographicComparator for DefaultComparator {
416    #[inline]
417    fn compare_elem(a: u8, b: u8) -> Ordering {
418        a.cmp(&b)
419    }
420
421    #[inline]
422    fn successor(elem: u8) -> Option<u8> {
423        match elem {
424            u8::MAX => None,
425            elem => Some(elem + 1),
426        }
427    }
428
429    #[inline]
430    fn predecessor(elem: u8) -> Option<u8> {
431        match elem {
432            u8::MIN => None,
433            elem => Some(elem - 1),
434        }
435    }
436
437    #[inline]
438    fn max_elem() -> u8 {
439        u8::MAX
440    }
441
442    #[inline]
443    fn min_elem() -> u8 {
444        u8::MIN
445    }
446}
447
448/// Whether to perform compaction while copying an environment.
449#[derive(Debug, Copy, Clone)]
450pub enum CompactionOption {
451    /// Omit free pages and sequentially renumber all pages in output.
452    ///
453    /// This option consumes more CPU and runs more slowly than the default.
454    /// Currently it fails if the environment has suffered a page leak.
455    Enabled,
456
457    /// Copy everything without taking any special action about free pages.
458    Disabled,
459}
460
461/// Whether to enable or disable flags in [`Env::set_flags`].
462#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
463pub enum FlagSetMode {
464    /// Enable the flags.
465    Enable,
466    /// Disable the flags.
467    Disable,
468}
469
470impl FlagSetMode {
471    /// Convert the enum into the `i32` required by LMDB.
472    /// "A non-zero value sets the flags, zero clears them."
473    /// <http://www.lmdb.tech/doc/group__mdb.html#ga83f66cf02bfd42119451e9468dc58445>
474    fn as_mdb_env_set_flags_input(self) -> i32 {
475        match self {
476            Self::Enable => 1,
477            Self::Disable => 0,
478        }
479    }
480}
481
482impl Env {
483    pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env {
484        self.0.env
485    }
486
487    /// The size of the data file on disk.
488    ///
489    /// # Example
490    ///
491    /// ```
492    /// use heed::EnvOpenOptions;
493    ///
494    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
495    /// let dir = tempfile::tempdir()?;
496    /// let size_in_bytes = 1024 * 1024;
497    /// let env = unsafe { EnvOpenOptions::new().map_size(size_in_bytes).open(dir.path())? };
498    ///
499    /// let actual_size = env.real_disk_size()? as usize;
500    /// assert!(actual_size < size_in_bytes);
501    /// # Ok(()) }
502    /// ```
503    pub fn real_disk_size(&self) -> Result<u64> {
504        let mut fd = mem::MaybeUninit::uninit();
505        unsafe { mdb_result(ffi::mdb_env_get_fd(self.env_mut_ptr(), fd.as_mut_ptr()))? };
506        let fd = unsafe { fd.assume_init() };
507        let metadata = unsafe { metadata_from_fd(fd)? };
508        Ok(metadata.len())
509    }
510
511    /// Return the raw flags the environment was opened with.
512    ///
513    /// Returns `None` if the environment flags are different from the [`EnvFlags`] set.
514    pub fn flags(&self) -> Result<Option<EnvFlags>> {
515        self.get_flags().map(EnvFlags::from_bits)
516    }
517
518    /// Enable or disable the environment's currently active [`EnvFlags`].
519    ///
520    /// ```
521    /// use std::fs;
522    /// use std::path::Path;
523    /// use heed::{EnvOpenOptions, Database, EnvFlags, FlagSetMode};
524    /// use heed::types::*;
525    ///
526    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
527    /// fs::create_dir_all(Path::new("target").join("database.mdb"))?;
528    /// let mut env_builder = EnvOpenOptions::new();
529    /// let dir = tempfile::tempdir().unwrap();
530    /// let env = unsafe { env_builder.open(dir.path())? };
531    ///
532    /// // Env was opened without flags.
533    /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits());
534    ///
535    /// // Enable a flag after opening.
536    /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Enable).unwrap(); }
537    /// assert_eq!(env.get_flags().unwrap(), EnvFlags::NO_SYNC.bits());
538    ///
539    /// // Disable a flag after opening.
540    /// unsafe { env.set_flags(EnvFlags::NO_SYNC, FlagSetMode::Disable).unwrap(); }
541    /// assert_eq!(env.get_flags().unwrap(), EnvFlags::empty().bits());
542    /// # Ok(()) }
543    /// ```
544    ///
545    /// # Safety
546    ///
547    /// It is unsafe to use unsafe LMDB flags such as `NO_SYNC`, `NO_META_SYNC`, or `NO_LOCK`.
548    ///
549    /// LMDB also requires that only 1 thread calls this function at any given moment.
550    /// Neither `heed` or LMDB check for this condition, so the caller must ensure it explicitly.
551    pub unsafe fn set_flags(&self, flags: EnvFlags, mode: FlagSetMode) -> Result<()> {
552        // safety: caller must ensure no other thread is calling this function.
553        // <http://www.lmdb.tech/doc/group__mdb.html#ga83f66cf02bfd42119451e9468dc58445>
554        mdb_result(unsafe {
555            ffi::mdb_env_set_flags(
556                self.env_mut_ptr(),
557                flags.bits(),
558                mode.as_mdb_env_set_flags_input(),
559            )
560        })
561        .map_err(Into::into)
562    }
563
564    /// Return the raw flags the environment is currently set with.
565    pub fn get_flags(&self) -> Result<u32> {
566        let mut flags = mem::MaybeUninit::uninit();
567        unsafe { mdb_result(ffi::mdb_env_get_flags(self.env_mut_ptr(), flags.as_mut_ptr()))? };
568        let flags = unsafe { flags.assume_init() };
569        Ok(flags)
570    }
571
572    /// Returns some basic informations about this environment.
573    pub fn info(&self) -> EnvInfo {
574        let mut raw_info = mem::MaybeUninit::uninit();
575        unsafe { ffi::mdb_env_info(self.0.env, raw_info.as_mut_ptr()) };
576        let raw_info = unsafe { raw_info.assume_init() };
577
578        EnvInfo {
579            map_addr: raw_info.me_mapaddr,
580            map_size: raw_info.me_mapsize,
581            last_page_number: raw_info.me_last_pgno,
582            last_txn_id: raw_info.me_last_txnid,
583            maximum_number_of_readers: raw_info.me_maxreaders,
584            number_of_readers: raw_info.me_numreaders,
585        }
586    }
587
588    /// Returns the size used by all the databases in the environment without the free pages.
589    ///
590    /// It is crucial to configure [`EnvOpenOptions::max_dbs`] with a sufficiently large value
591    /// before invoking this function. All databases within the environment will be opened
592    /// and remain so.
593    pub fn non_free_pages_size(&self) -> Result<u64> {
594        let compute_size = |stat: ffi::MDB_stat| {
595            (stat.ms_leaf_pages + stat.ms_branch_pages + stat.ms_overflow_pages) as u64
596                * stat.ms_psize as u64
597        };
598
599        let mut size = 0;
600
601        let mut stat = mem::MaybeUninit::uninit();
602        unsafe { mdb_result(ffi::mdb_env_stat(self.env_mut_ptr(), stat.as_mut_ptr()))? };
603        let stat = unsafe { stat.assume_init() };
604        size += compute_size(stat);
605
606        let rtxn = self.read_txn()?;
607        // Open the main database
608        let dbi = self.raw_open_dbi::<DefaultComparator>(rtxn.txn, None, 0)?;
609
610        // We're going to iterate on the unnamed database
611        let mut cursor = RoCursor::new(&rtxn, dbi)?;
612
613        while let Some((key, _value)) = cursor.move_on_next(MoveOperation::NoDup)? {
614            if key.contains(&0) {
615                continue;
616            }
617
618            let key = String::from_utf8(key.to_vec()).unwrap();
619            // Calling `ffi::db_stat` on a database instance does not involve key comparison
620            // in LMDB, so it's safe to specify a noop key compare function for it.
621            if let Ok(dbi) = self.raw_open_dbi::<DefaultComparator>(rtxn.txn, Some(&key), 0) {
622                let mut stat = mem::MaybeUninit::uninit();
623                unsafe { mdb_result(ffi::mdb_stat(rtxn.txn, dbi, stat.as_mut_ptr()))? };
624                let stat = unsafe { stat.assume_init() };
625                size += compute_size(stat);
626            }
627        }
628
629        Ok(size)
630    }
631
632    /// Options and flags which can be used to configure how a [`Database`] is opened.
633    pub fn database_options(&self) -> DatabaseOpenOptions<Unspecified, Unspecified> {
634        DatabaseOpenOptions::new(self)
635    }
636
637    /// Opens a typed database that already exists in this environment.
638    ///
639    /// If the database was previously opened in this program run, types will be checked.
640    ///
641    /// ## Important Information
642    ///
643    /// LMDB has an important restriction on the unnamed database when named ones are opened.
644    /// The names of the named databases are stored as keys in the unnamed one and are immutable,
645    /// and these keys can only be read and not written.
646    ///
647    /// ## LMDB read-only access of existing database
648    ///
649    /// In the case of accessing a database in a read-only manner from another process
650    /// where you wrote, you might need to manually call [`RoTxn::commit`] to get metadata
651    /// and the database handles opened and shared with the global [`Env`] handle.
652    ///
653    /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })`
654    /// known as `EINVAL`.
655    pub fn open_database<KC, DC>(
656        &self,
657        rtxn: &RoTxn,
658        name: Option<&str>,
659    ) -> Result<Option<Database<KC, DC>>>
660    where
661        KC: 'static,
662        DC: 'static,
663    {
664        let mut options = self.database_options().types::<KC, DC>();
665        if let Some(name) = name {
666            options.name(name);
667        }
668        options.open(rtxn)
669    }
670
671    /// Creates a typed database that can already exist in this environment.
672    ///
673    /// If the database was previously opened during this program run, types will be checked.
674    ///
675    /// ## Important Information
676    ///
677    /// LMDB has an important restriction on the unnamed database when named ones are opened.
678    /// The names of the named databases are stored as keys in the unnamed one and are immutable,
679    /// and these keys can only be read and not written.
680    pub fn create_database<KC, DC>(
681        &self,
682        wtxn: &mut RwTxn,
683        name: Option<&str>,
684    ) -> Result<Database<KC, DC>>
685    where
686        KC: 'static,
687        DC: 'static,
688    {
689        let mut options = self.database_options().types::<KC, DC>();
690        if let Some(name) = name {
691            options.name(name);
692        }
693        options.create(wtxn)
694    }
695
696    pub(crate) fn raw_init_database<C: Comparator + 'static>(
697        &self,
698        raw_txn: *mut ffi::MDB_txn,
699        name: Option<&str>,
700        flags: AllDatabaseFlags,
701    ) -> Result<u32> {
702        match self.raw_open_dbi::<C>(raw_txn, name, flags.bits()) {
703            Ok(dbi) => Ok(dbi),
704            Err(e) => Err(e.into()),
705        }
706    }
707
708    fn raw_open_dbi<C: Comparator + 'static>(
709        &self,
710        raw_txn: *mut ffi::MDB_txn,
711        name: Option<&str>,
712        flags: u32,
713    ) -> std::result::Result<u32, crate::mdb::lmdb_error::Error> {
714        let mut dbi = 0;
715        let name = name.map(|n| CString::new(n).unwrap());
716        let name_ptr = match name {
717            Some(ref name) => name.as_bytes_with_nul().as_ptr() as *const _,
718            None => ptr::null(),
719        };
720
721        // safety: The name cstring is cloned by LMDB, we can drop it after.
722        //         If a read-only is used with the MDB_CREATE flag, LMDB will throw an error.
723        unsafe {
724            mdb_result(ffi::mdb_dbi_open(raw_txn, name_ptr, flags, &mut dbi))?;
725            if TypeId::of::<C>() != TypeId::of::<DefaultComparator>() {
726                mdb_result(ffi::mdb_set_compare(raw_txn, dbi, Some(custom_key_cmp_wrapper::<C>)))?;
727            }
728        };
729
730        Ok(dbi)
731    }
732
733    /// Create a transaction with read and write access for use with the environment.
734    ///
735    /// ## LMDB Limitations
736    ///
737    /// Only one [`RwTxn`] may exist simultaneously in the current environment.
738    /// If another write transaction is initiated, while another write transaction exists
739    /// the thread initiating the new one will wait on a mutex upon completion of the previous
740    /// transaction.
741    pub fn write_txn(&self) -> Result<RwTxn> {
742        RwTxn::new(self)
743    }
744
745    /// Create a nested transaction with read and write access for use with the environment.
746    ///
747    /// The new transaction will be a nested transaction, with the transaction indicated by parent
748    /// as its parent. Transactions may be nested to any level.
749    ///
750    /// A parent transaction and its cursors may not issue any other operations than _commit_ and
751    /// _abort_ while it has active child transactions.
752    pub fn nested_write_txn<'p>(&'p self, parent: &'p mut RwTxn) -> Result<RwTxn<'p>> {
753        RwTxn::nested(self, parent)
754    }
755
756    /// Create a transaction with read-only access for use with the environment.
757    ///
758    /// You can make this transaction `Send`able between threads by
759    /// using the `read-txn-no-tls` crate feature.
760    /// See [`Self::static_read_txn`] if you want the txn to own the environment.
761    ///
762    /// ## LMDB Limitations
763    ///
764    /// It's possible to have multiple read transactions in the same environment
765    /// while there is a write transaction ongoing.
766    ///
767    /// But read transactions prevent reuse of pages freed by newer write transactions,
768    /// thus the database can grow quickly. Write transactions prevent other write transactions,
769    /// since writes are serialized.
770    ///
771    /// So avoid long-lived read transactions.
772    ///
773    /// ## Errors
774    ///
775    /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down
776    /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env
777    ///   map must be resized
778    /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is
779    ///   full
780    pub fn read_txn(&self) -> Result<RoTxn> {
781        RoTxn::new(self)
782    }
783
784    /// Create a transaction with read-only access for use with the environment.
785    /// Contrary to [`Self::read_txn`], this version **owns** the environment, which
786    /// means you won't be able to close the environment while this transaction is alive.
787    ///
788    /// You can make this transaction `Send`able between threads by
789    /// using the `read-txn-no-tls` crate feature.
790    ///
791    /// ## LMDB Limitations
792    ///
793    /// It's possible to have multiple read transactions in the same environment
794    /// while there is a write transaction ongoing.
795    ///
796    /// But read transactions prevent reuse of pages freed by newer write transactions,
797    /// thus the database can grow quickly. Write transactions prevent other write transactions,
798    /// since writes are serialized.
799    ///
800    /// So avoid long-lived read transactions.
801    ///
802    /// ## Errors
803    ///
804    /// * [`crate::MdbError::Panic`]: A fatal error occurred earlier, and the environment must be shut down
805    /// * [`crate::MdbError::MapResized`]: Another process wrote data beyond this [`Env`] mapsize and this env
806    ///   map must be resized
807    /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is
808    ///   full
809    pub fn static_read_txn(self) -> Result<RoTxn<'static>> {
810        RoTxn::static_read_txn(self)
811    }
812
813    /// Copy an LMDB environment to the specified path, with options.
814    ///
815    /// This function may be used to make a backup of an existing environment.
816    /// No lockfile is created, since it gets recreated at need.
817    pub fn copy_to_file<P: AsRef<Path>>(&self, path: P, option: CompactionOption) -> Result<File> {
818        let file = File::options().create_new(true).write(true).open(&path)?;
819        let fd = get_file_fd(&file);
820
821        unsafe { self.copy_to_fd(fd, option)? };
822
823        // We reopen the file to make sure the cursor is at the start,
824        // even a seek to start doesn't work properly.
825        let file = File::open(path)?;
826
827        Ok(file)
828    }
829
830    /// Copy an LMDB environment to the specified file descriptor, with compaction option.
831    ///
832    /// This function may be used to make a backup of an existing environment.
833    /// No lockfile is created, since it gets recreated at need.
834    ///
835    /// # Safety
836    ///
837    /// The [`ffi::mdb_filehandle_t`] must have already been opened for Write access.
838    pub unsafe fn copy_to_fd(
839        &self,
840        fd: ffi::mdb_filehandle_t,
841        option: CompactionOption,
842    ) -> Result<()> {
843        let flags = if let CompactionOption::Enabled = option { ffi::MDB_CP_COMPACT } else { 0 };
844        mdb_result(ffi::mdb_env_copyfd2(self.0.env, fd, flags))?;
845        Ok(())
846    }
847
848    /// Flush the data buffers to disk.
849    pub fn force_sync(&self) -> Result<()> {
850        unsafe { mdb_result(ffi::mdb_env_sync(self.0.env, 1))? }
851        Ok(())
852    }
853
854    /// Returns the canonicalized path where this env lives.
855    pub fn path(&self) -> &Path {
856        &self.0.path
857    }
858
859    /// Returns an `EnvClosingEvent` that can be used to wait for the closing event,
860    /// multiple threads can wait on this event.
861    ///
862    /// Make sure that you drop all the copies of `Env`s you have, env closing are triggered
863    /// when all references are dropped, the last one will eventually close the environment.
864    pub fn prepare_for_closing(self) -> EnvClosingEvent {
865        let mut lock = OPENED_ENV.write().unwrap();
866        match lock.get_mut(self.path()) {
867            None => panic!("cannot find the env that we are trying to close"),
868            Some(EnvEntry { env, signal_event, .. }) => {
869                // We remove the env from the global list and replace it with a None.
870                let _env = env.take();
871                let signal_event = signal_event.clone();
872
873                // we must make sure we release the lock before we drop the env
874                // as the drop of the EnvInner also tries to lock the OPENED_ENV
875                // global and we don't want to trigger a dead-lock.
876                drop(lock);
877
878                EnvClosingEvent(signal_event)
879            }
880        }
881    }
882
883    /// Check for stale entries in the reader lock table and clear them.
884    ///
885    /// Returns the number of stale readers cleared.
886    pub fn clear_stale_readers(&self) -> Result<usize> {
887        let mut dead: i32 = 0;
888        unsafe { mdb_result(ffi::mdb_reader_check(self.0.env, &mut dead))? }
889        // safety: The reader_check function asks for an i32, initialize it to zero
890        //         and never decrements it. It is safe to use either an u32 or u64 (usize).
891        Ok(dead as usize)
892    }
893
894    /// Resize the memory map to a new size.
895    ///
896    /// # Safety
897    ///
898    /// According to the [LMDB documentation](http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5),
899    /// it is okay to call `mdb_env_set_mapsize` for an open environment as long as no transactions are active,
900    /// but the library does not check for this condition, so the caller must ensure it explicitly.
901    pub unsafe fn resize(&self, new_size: usize) -> Result<()> {
902        if new_size % page_size::get() != 0 {
903            let msg = format!(
904                "map size ({}) must be a multiple of the system page size ({})",
905                new_size,
906                page_size::get()
907            );
908            return Err(Error::Io(io::Error::new(io::ErrorKind::InvalidInput, msg)));
909        }
910        mdb_result(unsafe { ffi::mdb_env_set_mapsize(self.env_mut_ptr(), new_size) })
911            .map_err(Into::into)
912    }
913
914    /// Get the maximum size of keys and MDB_DUPSORT data we can write.
915    ///
916    /// Depends on the compile-time constant MDB_MAXKEYSIZE. Default 511
917    pub fn max_key_size(&self) -> usize {
918        let maxsize: i32 = unsafe { ffi::mdb_env_get_maxkeysize(self.env_mut_ptr()) };
919        maxsize as usize
920    }
921}
922
923/// Contains information about the environment.
924#[derive(Debug, Clone, Copy)]
925pub struct EnvInfo {
926    /// Address of the map, if fixed.
927    pub map_addr: *mut c_void,
928    /// Size of the data memory map.
929    pub map_size: usize,
930    /// ID of the last used page.
931    pub last_page_number: usize,
932    /// ID of the last committed transaction.
933    pub last_txn_id: usize,
934    /// Maximum number of reader slots in the environment.
935    pub maximum_number_of_readers: u32,
936    /// Number of reader slots used in the environment.
937    pub number_of_readers: u32,
938}
939
940/// A structure that can be used to wait for the closing event.
941/// Multiple threads can wait on this event.
942#[derive(Clone)]
943pub struct EnvClosingEvent(Arc<SignalEvent>);
944
945impl EnvClosingEvent {
946    /// Blocks this thread until the environment is effectively closed.
947    ///
948    /// # Safety
949    ///
950    /// Make sure that you don't have any copy of the environment in the thread
951    /// that is waiting for a close event. If you do, you will have a deadlock.
952    pub fn wait(&self) {
953        self.0.wait()
954    }
955
956    /// Blocks this thread until either the environment has been closed
957    /// or until the timeout elapses. Returns `true` if the environment
958    /// has been effectively closed.
959    pub fn wait_timeout(&self, timeout: Duration) -> bool {
960        self.0.wait_timeout(timeout)
961    }
962}
963
964impl fmt::Debug for EnvClosingEvent {
965    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
966        f.debug_struct("EnvClosingEvent").finish()
967    }
968}
969
970#[cfg(test)]
971mod tests {
972    use std::io::ErrorKind;
973    use std::time::Duration;
974    use std::{fs, thread};
975
976    use crate::types::*;
977    use crate::{env_closing_event, EnvOpenOptions, Error};
978
979    #[test]
980    fn close_env() {
981        let dir = tempfile::tempdir().unwrap();
982        let env = unsafe {
983            EnvOpenOptions::new()
984                .map_size(10 * 1024 * 1024) // 10MB
985                .max_dbs(30)
986                .open(dir.path())
987                .unwrap()
988        };
989
990        // Force a thread to keep the env for 1 second.
991        let env_cloned = env.clone();
992        thread::spawn(move || {
993            let _env = env_cloned;
994            thread::sleep(Duration::from_secs(1));
995        });
996
997        let mut wtxn = env.write_txn().unwrap();
998        let db = env.create_database::<Str, Str>(&mut wtxn, None).unwrap();
999        wtxn.commit().unwrap();
1000
1001        // Create an ordered list of keys...
1002        let mut wtxn = env.write_txn().unwrap();
1003        db.put(&mut wtxn, "hello", "hello").unwrap();
1004        db.put(&mut wtxn, "world", "world").unwrap();
1005
1006        let mut iter = db.iter(&wtxn).unwrap();
1007        assert_eq!(iter.next().transpose().unwrap(), Some(("hello", "hello")));
1008        assert_eq!(iter.next().transpose().unwrap(), Some(("world", "world")));
1009        assert_eq!(iter.next().transpose().unwrap(), None);
1010        drop(iter);
1011
1012        wtxn.commit().unwrap();
1013
1014        let signal_event = env.prepare_for_closing();
1015
1016        eprintln!("waiting for the env to be closed");
1017        signal_event.wait();
1018        eprintln!("env closed successfully");
1019
1020        // Make sure we don't have a reference to the env
1021        assert!(env_closing_event(dir.path()).is_none());
1022    }
1023
1024    #[test]
1025    fn reopen_env_with_different_options_is_err() {
1026        let dir = tempfile::tempdir().unwrap();
1027        let _env = unsafe {
1028            EnvOpenOptions::new()
1029                .map_size(10 * 1024 * 1024) // 10MB
1030                .open(dir.path())
1031                .unwrap()
1032        };
1033
1034        let result = unsafe {
1035            EnvOpenOptions::new()
1036                .map_size(12 * 1024 * 1024) // 12MB
1037                .open(dir.path())
1038        };
1039
1040        assert!(matches!(result, Err(Error::BadOpenOptions { .. })));
1041    }
1042
1043    #[test]
1044    fn open_env_with_named_path() {
1045        let dir = tempfile::tempdir().unwrap();
1046        fs::create_dir_all(dir.path().join("babar.mdb")).unwrap();
1047        let _env = unsafe {
1048            EnvOpenOptions::new()
1049                .map_size(10 * 1024 * 1024) // 10MB
1050                .open(dir.path().join("babar.mdb"))
1051                .unwrap()
1052        };
1053
1054        let _env = unsafe {
1055            EnvOpenOptions::new()
1056                .map_size(10 * 1024 * 1024) // 10MB
1057                .open(dir.path().join("babar.mdb"))
1058                .unwrap()
1059        };
1060    }
1061
1062    #[test]
1063    #[cfg(not(windows))]
1064    fn open_database_with_writemap_flag() {
1065        let dir = tempfile::tempdir().unwrap();
1066        let mut envbuilder = EnvOpenOptions::new();
1067        envbuilder.map_size(10 * 1024 * 1024); // 10MB
1068        envbuilder.max_dbs(10);
1069        unsafe { envbuilder.flags(crate::EnvFlags::WRITE_MAP) };
1070        let env = unsafe { envbuilder.open(dir.path()).unwrap() };
1071
1072        let mut wtxn = env.write_txn().unwrap();
1073        let _db = env.create_database::<Str, Str>(&mut wtxn, Some("my-super-db")).unwrap();
1074        wtxn.commit().unwrap();
1075    }
1076
1077    #[test]
1078    fn open_database_with_nosubdir() {
1079        let dir = tempfile::tempdir().unwrap();
1080        let mut envbuilder = EnvOpenOptions::new();
1081        unsafe { envbuilder.flags(crate::EnvFlags::NO_SUB_DIR) };
1082        let _env = unsafe { envbuilder.open(dir.path().join("data.mdb")).unwrap() };
1083    }
1084
1085    #[test]
1086    fn create_database_without_commit() {
1087        let dir = tempfile::tempdir().unwrap();
1088        let env = unsafe {
1089            EnvOpenOptions::new()
1090                .map_size(10 * 1024 * 1024) // 10MB
1091                .max_dbs(10)
1092                .open(dir.path())
1093                .unwrap()
1094        };
1095
1096        let mut wtxn = env.write_txn().unwrap();
1097        let _db = env.create_database::<Str, Str>(&mut wtxn, Some("my-super-db")).unwrap();
1098        wtxn.abort();
1099
1100        let rtxn = env.read_txn().unwrap();
1101        let option = env.open_database::<Str, Str>(&rtxn, Some("my-super-db")).unwrap();
1102        assert!(option.is_none());
1103    }
1104
1105    #[test]
1106    fn open_already_existing_database() {
1107        let dir = tempfile::tempdir().unwrap();
1108        let env = unsafe {
1109            EnvOpenOptions::new()
1110                .map_size(10 * 1024 * 1024) // 10MB
1111                .max_dbs(10)
1112                .open(dir.path())
1113                .unwrap()
1114        };
1115
1116        // we first create a database
1117        let mut wtxn = env.write_txn().unwrap();
1118        let _db = env.create_database::<Str, Str>(&mut wtxn, Some("my-super-db")).unwrap();
1119        wtxn.commit().unwrap();
1120
1121        // Close the environement and reopen it, databases must not be loaded in memory.
1122        env.prepare_for_closing().wait();
1123        let env = unsafe {
1124            EnvOpenOptions::new()
1125                .map_size(10 * 1024 * 1024) // 10MB
1126                .max_dbs(10)
1127                .open(dir.path())
1128                .unwrap()
1129        };
1130
1131        let rtxn = env.read_txn().unwrap();
1132        let option = env.open_database::<Str, Str>(&rtxn, Some("my-super-db")).unwrap();
1133        assert!(option.is_some());
1134    }
1135
1136    #[test]
1137    fn resize_database() {
1138        let dir = tempfile::tempdir().unwrap();
1139        let page_size = page_size::get();
1140        let env = unsafe {
1141            EnvOpenOptions::new().map_size(9 * page_size).max_dbs(1).open(dir.path()).unwrap()
1142        };
1143
1144        let mut wtxn = env.write_txn().unwrap();
1145        let db = env.create_database::<Str, Str>(&mut wtxn, Some("my-super-db")).unwrap();
1146        wtxn.commit().unwrap();
1147
1148        let mut wtxn = env.write_txn().unwrap();
1149        for i in 0..64 {
1150            db.put(&mut wtxn, &i.to_string(), "world").unwrap();
1151        }
1152        wtxn.commit().unwrap();
1153
1154        let mut wtxn = env.write_txn().unwrap();
1155        for i in 64..128 {
1156            db.put(&mut wtxn, &i.to_string(), "world").unwrap();
1157        }
1158        wtxn.commit().expect_err("cannot commit a transaction that would reach the map size limit");
1159
1160        unsafe {
1161            env.resize(10 * page_size).unwrap();
1162        }
1163        let mut wtxn = env.write_txn().unwrap();
1164        for i in 64..128 {
1165            db.put(&mut wtxn, &i.to_string(), "world").unwrap();
1166        }
1167        wtxn.commit().expect("transaction should commit after resizing the map size");
1168
1169        assert_eq!(10 * page_size, env.info().map_size);
1170    }
1171
1172    /// Non-regression test for
1173    /// <https://github.com/meilisearch/heed/issues/183>
1174    ///
1175    /// We should be able to open database Read-Only Env with
1176    /// no prior Read-Write Env opening. And query data.
1177    #[test]
1178    fn open_read_only_without_no_env_opened_before() {
1179        let expected_data0 = "Data Expected db0";
1180        let dir = tempfile::tempdir().unwrap();
1181
1182        {
1183            // We really need this env to be dropped before the read-only access.
1184            let env = unsafe {
1185                EnvOpenOptions::new()
1186                    .map_size(16 * 1024 * 1024 * 1024) // 10MB
1187                    .max_dbs(32)
1188                    .open(dir.path())
1189                    .unwrap()
1190            };
1191            let mut wtxn = env.write_txn().unwrap();
1192            let database0 = env.create_database::<Str, Str>(&mut wtxn, Some("shared0")).unwrap();
1193
1194            wtxn.commit().unwrap();
1195            let mut wtxn = env.write_txn().unwrap();
1196            database0.put(&mut wtxn, "shared0", expected_data0).unwrap();
1197            wtxn.commit().unwrap();
1198            // We also really need that no other env reside in memory in other thread doing tests.
1199            env.prepare_for_closing().wait();
1200        }
1201
1202        {
1203            // Open now we do a read-only opening
1204            let env = unsafe {
1205                EnvOpenOptions::new()
1206                    .map_size(16 * 1024 * 1024 * 1024) // 10MB
1207                    .max_dbs(32)
1208                    .open(dir.path())
1209                    .unwrap()
1210            };
1211            let database0 = {
1212                let rtxn = env.read_txn().unwrap();
1213                let database0 =
1214                    env.open_database::<Str, Str>(&rtxn, Some("shared0")).unwrap().unwrap();
1215                // This commit is mandatory if not committed you might get
1216                // Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })
1217                rtxn.commit().unwrap();
1218                database0
1219            };
1220
1221            {
1222                // If we didn't committed the opening it might fail with EINVAL.
1223                let rtxn = env.read_txn().unwrap();
1224                let value = database0.get(&rtxn, "shared0").unwrap().unwrap();
1225                assert_eq!(value, expected_data0);
1226            }
1227
1228            env.prepare_for_closing().wait();
1229        }
1230
1231        // To avoid reintroducing the bug let's try to open again but without the commit
1232        {
1233            // Open now we do a read-only opening
1234            let env = unsafe {
1235                EnvOpenOptions::new()
1236                    .map_size(16 * 1024 * 1024 * 1024) // 10MB
1237                    .max_dbs(32)
1238                    .open(dir.path())
1239                    .unwrap()
1240            };
1241            let database0 = {
1242                let rtxn = env.read_txn().unwrap();
1243                let database0 =
1244                    env.open_database::<Str, Str>(&rtxn, Some("shared0")).unwrap().unwrap();
1245                // No commit it's important, dropping explicitly
1246                drop(rtxn);
1247                database0
1248            };
1249
1250            {
1251                // We didn't committed the opening we will get EINVAL.
1252                let rtxn = env.read_txn().unwrap();
1253                // The dbg!() is intentional in case of a change in rust-std or in lmdb related
1254                // to the windows error.
1255                let err = dbg!(database0.get(&rtxn, "shared0"));
1256
1257                // The error kind is still ErrorKind Uncategorized on windows.
1258                // Behind it's a ERROR_BAD_COMMAND code 22 like EINVAL.
1259                if cfg!(windows) {
1260                    assert!(err.is_err());
1261                } else {
1262                    assert!(
1263                        matches!(err, Err(Error::Io(ref e)) if e.kind() == ErrorKind::InvalidInput)
1264                    );
1265                }
1266            }
1267
1268            env.prepare_for_closing().wait();
1269        }
1270    }
1271
1272    #[test]
1273    fn max_key_size() {
1274        let dir = tempfile::tempdir().unwrap();
1275        let env = unsafe { EnvOpenOptions::new().open(dir.path().join(dir.path())).unwrap() };
1276        let maxkeysize = env.max_key_size();
1277
1278        eprintln!("maxkeysize: {}", maxkeysize);
1279
1280        if cfg!(feature = "longer-keys") {
1281            // Should be larger than the default of 511
1282            assert!(maxkeysize > 511);
1283        } else {
1284            // Should be the default of 511
1285            assert_eq!(maxkeysize, 511);
1286        }
1287    }
1288}