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}