use libc::{
c_uint,
size_t,
}; use std::ffi::CString; #[cfg(windows)] use std::ffi::OsStr; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::sync::Mutex; use std::{
fmt,
mem,
ptr,
result,
};
use ffi;
use byteorder::{
ByteOrder,
NativeEndian,
};
use cursor::Cursor; use database::Database; use error::{
lmdb_result,
Error,
Result,
}; use flags::{
DatabaseFlags,
EnvironmentFlags,
}; use transaction::{
RoTransaction,
RwTransaction,
Transaction,
};
#[cfg(windows)] /// Adding a 'missing' trait from windows OsStrExt trait OsStrExtLmdb { fn as_bytes(&self) -> &[u8];
} #[cfg(windows)] impl OsStrExtLmdb for OsStr { fn as_bytes(&self) -> &[u8] {
&self.to_str().unwrap().as_bytes()
}
}
/// An LMDB environment. /// /// An environment supports multiple databases, all residing in the same shared-memory map. pubstruct Environment {
env: *mut ffi::MDB_env,
dbi_open_mutex: Mutex<()>,
}
impl Environment { /// Creates a new builder for specifying options for opening an LMDB environment. #[allow(clippy::new_ret_no_self)] pubfn new() -> EnvironmentBuilder {
EnvironmentBuilder {
flags: EnvironmentFlags::empty(),
max_readers: None,
max_dbs: None,
map_size: None,
}
}
/// Returns a raw pointer to the underlying LMDB environment. /// /// The caller **must** ensure that the pointer is not dereferenced after the lifetime of the /// environment. pubfn env(&self) -> *mut ffi::MDB_env { self.env
}
/// Opens a handle to an LMDB database. /// /// If `name` is `None`, then the returned handle will be for the default database. /// /// If `name` is not `None`, then the returned handle will be for a named database. In this /// case the environment must be configured to allow named databases through /// `EnvironmentBuilder::set_max_dbs`. /// /// The returned database handle may be shared among any transaction in the environment. /// /// This function will fail with `Error::BadRslot` if called by a thread which has an ongoing /// transaction. /// /// The database name may not contain the null character. pubfn open_db<'env>(&'env self, name: Option<&str>) -> Result<Database> { let mutex = self.dbi_open_mutex.lock(); let txn = self.begin_ro_txn()?; let db = unsafe { txn.open_db(name)? };
txn.commit()?;
drop(mutex);
Ok(db)
}
/// Opens a handle to an LMDB database, creating the database if necessary. /// /// If the database is already created, the given option flags will be added to it. /// /// If `name` is `None`, then the returned handle will be for the default database. /// /// If `name` is not `None`, then the returned handle will be for a named database. In this /// case the environment must be configured to allow named databases through /// `EnvironmentBuilder::set_max_dbs`. /// /// The returned database handle may be shared among any transaction in the environment. /// /// This function will fail with `Error::BadRslot` if called by a thread with an open /// transaction. pubfn create_db<'env>(&'env self, name: Option<&str>, flags: DatabaseFlags) -> Result<Database> { let mutex = self.dbi_open_mutex.lock(); let txn = self.begin_rw_txn()?; let db = unsafe { txn.create_db(name, flags)? };
txn.commit()?;
drop(mutex);
Ok(db)
}
/// Retrieves the set of flags which the database is opened with. /// /// The database must belong to to this environment. pubfn get_db_flags(&self, db: Database) -> Result<DatabaseFlags> { let txn = self.begin_ro_txn()?; letmut flags: c_uint = 0; unsafe {
lmdb_result(ffi::mdb_dbi_flags(txn.txn(), db.dbi(), &mut flags))?;
}
Ok(DatabaseFlags::from_bits(flags).unwrap())
}
/// Create a read-only transaction for use with the environment. pubfn begin_ro_txn<'env>(&'env self) -> Result<RoTransaction<'env>> {
RoTransaction::new(self)
}
/// Create a read-write transaction for use with the environment. This method will block while /// there are any other read-write transactions open on the environment. pubfn begin_rw_txn<'env>(&'env self) -> Result<RwTransaction<'env>> {
RwTransaction::new(self)
}
/// Flush data buffers to disk. /// /// Data is always written to disk when `Transaction::commit` is called, but the operating /// system may keep it buffered. LMDB always flushes the OS buffers upon commit as well, unless /// the environment was opened with `MDB_NOSYNC` or in part `MDB_NOMETASYNC`. pubfn sync(&self, force: bool) -> Result<()> { unsafe {
lmdb_result(ffi::mdb_env_sync( self.env(), if force { 1
} else { 0
},
))
}
}
/// Closes the database handle. Normally unnecessary. /// /// Closing a database handle is not necessary, but lets `Transaction::open_database` reuse the /// handle value. Usually it's better to set a bigger `EnvironmentBuilder::set_max_dbs`, unless /// that value would be large. /// /// ## Safety /// /// This call is not mutex protected. Databases should only be closed by a single thread, and /// only if no other threads are going to reference the database handle or one of its cursors /// any further. Do not close a handle if an existing transaction has modified its database. /// Doing so can cause misbehavior from database corruption to errors like /// `Error::BadValSize` (since the DB name is gone). pubunsafefn close_db(&mutself, db: Database) {
ffi::mdb_dbi_close(self.env, db.dbi());
}
/// Retrieves statistics about this environment. pubfn stat(&self) -> Result<Stat> { unsafe { letmut stat = Stat::new();
lmdb_try!(ffi::mdb_env_stat(self.env(), stat.mdb_stat()));
Ok(stat)
}
}
/// Retrieves info about this environment. pubfn info(&self) -> Result<Info> { unsafe { letmut info = Info(mem::zeroed());
lmdb_try!(ffi::mdb_env_info(self.env(), &mut info.0));
Ok(info)
}
}
/// Retrieves the total number of pages on the freelist. /// /// Along with `Environment::info()`, this can be used to calculate the exact number /// of used pages as well as free pages in this environment. /// /// ```ignore /// let env = Environment::new().open("/tmp/test").unwrap(); /// let info = env.info().unwrap(); /// let stat = env.stat().unwrap(); /// let freelist = env.freelist().unwrap(); /// let last_pgno = info.last_pgno() + 1; // pgno is 0 based. /// let total_pgs = info.map_size() / stat.page_size() as usize; /// let pgs_in_use = last_pgno - freelist; /// let pgs_free = total_pgs - pgs_in_use; /// ``` /// /// Note: /// /// * LMDB stores all the freelists in the designated database 0 in each environment, /// and the freelist count is stored at the beginning of the value as `libc::size_t` /// in the native byte order. /// /// * It will create a read transaction to traverse the freelist database. pubfn freelist(&self) -> Result<size_t> { letmut freelist: size_t = 0; let db = Database::freelist_db(); let txn = self.begin_ro_txn()?; letmut cursor = txn.open_ro_cursor(db)?;
for result in cursor.iter() { let (_key, value) = result?; if value.len() < mem::size_of::<size_t>() { return Err(Error::Corrupted);
}
let s = &value[..mem::size_of::<size_t>()]; if cfg!(target_pointer_width = "64") {
freelist += NativeEndian::read_u64(s) as size_t;
} else {
freelist += NativeEndian::read_u32(s) as size_t;
}
}
Ok(freelist)
}
/// Sets the size of the memory map to use for the environment. /// /// This could be used to resize the map when the environment is already open. /// /// Note: /// /// * No active transactions allowed when performing resizing in this process. /// /// * The size should be a multiple of the OS page size. Any attempt to set /// a size smaller than the space already consumed by the environment will /// be silently changed to the current size of the used space. /// /// * In the multi-process case, once a process resizes the map, other /// processes need to either re-open the environment, or call set_map_size /// with size 0 to update the environment. Otherwise, new transaction creation /// will fail with `Error::MapResized`. pubfn set_map_size(&self, size: size_t) -> Result<()> { unsafe { lmdb_result(ffi::mdb_env_set_mapsize(self.env(), size)) }
}
}
/// Environment statistics. /// /// Contains information about the size and layout of an LMDB environment or database. pubstruct Stat(ffi::MDB_stat);
impl Stat { /// Create a new Stat with zero'd inner struct `ffi::MDB_stat`. pub(crate) fn new() -> Stat { unsafe { Stat(mem::zeroed()) }
}
/// Returns a mut pointer to `ffi::MDB_stat`. pub(crate) fn mdb_stat(&mutself) -> *mut ffi::MDB_stat {
&mutself.0
}
}
impl Stat { /// Size of a database page. This is the same for all databases in the environment. #[inline] pubfn page_size(&self) -> u32 { self.0.ms_psize
}
/// Depth (height) of the B-tree. #[inline] pubfn depth(&self) -> u32 { self.0.ms_depth
}
/// Number of internal (non-leaf) pages. #[inline] pubfn branch_pages(&self) -> usize { self.0.ms_branch_pages
}
/// Number of leaf pages. #[inline] pubfn leaf_pages(&self) -> usize { self.0.ms_leaf_pages
}
/// Number of overflow pages. #[inline] pubfn overflow_pages(&self) -> usize { self.0.ms_overflow_pages
}
/// Number of data items. #[inline] pubfn entries(&self) -> usize { self.0.ms_entries
}
}
/// Environment information. /// /// Contains environment information about the map size, readers, last txn id etc. pubstruct Info(ffi::MDB_envinfo);
impl Info { /// Size of memory map. #[inline] pubfn map_size(&self) -> usize { self.0.me_mapsize
}
/// Last used page number #[inline] pubfn last_pgno(&self) -> usize { self.0.me_last_pgno
}
/// Last transaction ID #[inline] pubfn last_txnid(&self) -> usize { self.0.me_last_txnid
}
/// Max reader slots in the environment #[inline] pubfn max_readers(&self) -> u32 { self.0.me_maxreaders
}
/// Max reader slots used in the environment #[inline] pubfn num_readers(&self) -> u32 { self.0.me_numreaders
}
}
unsafeimpl Send for Environment {} unsafeimpl Sync for Environment {}
/// Options for opening or creating an environment. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pubstruct EnvironmentBuilder {
flags: EnvironmentFlags,
max_readers: Option<c_uint>,
max_dbs: Option<c_uint>,
map_size: Option<size_t>,
}
impl EnvironmentBuilder { /// Open an environment. /// /// On UNIX, the database files will be opened with 644 permissions. /// /// The path may not contain the null character, Windows UNC (Uniform Naming Convention) /// paths are not supported either. pubfn open(&self, path: &Path) -> Result<Environment> { self.open_with_permissions(path, 0o644)
}
/// Open an environment with the provided UNIX permissions. /// /// On Windows, the permissions will be ignored. /// /// The path may not contain the null character, Windows UNC (Uniform Naming Convention) /// paths are not supported either. pubfn open_with_permissions(&self, path: &Path, mode: ffi::mdb_mode_t) -> Result<Environment> { letmut env: *mut ffi::MDB_env = ptr::null_mut(); unsafe {
lmdb_try!(ffi::mdb_env_create(&mut env)); iflet Some(max_readers) = self.max_readers {
lmdb_try_with_cleanup!(ffi::mdb_env_set_maxreaders(env, max_readers), ffi::mdb_env_close(env))
} iflet Some(max_dbs) = self.max_dbs {
lmdb_try_with_cleanup!(ffi::mdb_env_set_maxdbs(env, max_dbs), ffi::mdb_env_close(env))
} iflet Some(map_size) = self.map_size {
lmdb_try_with_cleanup!(ffi::mdb_env_set_mapsize(env, map_size), ffi::mdb_env_close(env))
} let path = match CString::new(path.as_os_str().as_bytes()) {
Ok(path) => path,
Err(..) => return Err(::Error::Invalid),
};
lmdb_try_with_cleanup!(
ffi::mdb_env_open(env, path.as_ptr(), self.flags.bits(), mode),
ffi::mdb_env_close(env)
);
}
Ok(Environment {
env,
dbi_open_mutex: Mutex::new(()),
})
}
/// Sets the provided options in the environment. pubfn set_flags(&mutself, flags: EnvironmentFlags) -> &='color:red'>mut EnvironmentBuilder { self.flags = flags; self
}
/// Sets the maximum number of threads or reader slots for the environment. /// /// This defines the number of slots in the lock table that is used to track readers in the /// the environment. The default is 126. Starting a read-only transaction normally ties a lock /// table slot to the current thread until the environment closes or the thread exits. If /// `MDB_NOTLS` is in use, `Environment::open_txn` instead ties the slot to the `Transaction` /// object until it or the `Environment` object is destroyed. pubfn set_max_readers(&mutself, max_readers: c_uint) -> &le='color:red'>mut EnvironmentBuilder { self.max_readers = Some(max_readers); self
}
/// Sets the maximum number of named databases for the environment. /// /// This function is only needed if multiple databases will be used in the /// environment. Simpler applications that use the environment as a single /// unnamed database can ignore this option. /// /// Currently a moderate number of slots are cheap but a huge number gets /// expensive: 7-120 words per transaction, and every `Transaction::open_db` /// does a linear search of the opened slots. pubfn set_max_dbs(&mutself, max_dbs: c_uint) -> &mut EnvironmentBuilder { self.max_dbs = Some(max_dbs); self
}
/// Sets the size of the memory map to use for the environment. /// /// The size should be a multiple of the OS page size. The default is /// 1048576 bytes. The size of the memory map is also the maximum size /// of the database. The value should be chosen as large as possible, /// to accommodate future growth of the database. It may be increased at /// later times. /// /// Any attempt to set a size smaller than the space already consumed /// by the environment will be silently changed to the current size of the used space. pubfn set_map_size(&mutself, map_size: size_t) -> &mut EnvironmentBuilder { self.map_size = Some(map_size); self
}
}
#[cfg(test)] mod test {
externcrate byteorder;
useself::byteorder::{
ByteOrder,
LittleEndian,
}; use tempdir::TempDir;
use flags::*;
usesuper::*;
#[test] fn test_open() { let dir = TempDir::new("test").unwrap();
// opening non-existent env with read-only should fail
assert!(Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).is_err());
// opening non-existent env should succeed
assert!(Environment::new().open(dir.path()).is_ok());
// opening env with read-only should succeed
assert!(Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).is_ok());
}
#[test] fn test_begin_txn() { let dir = TempDir::new("test").unwrap();
{ // writable environment let env = Environment::new().open(dir.path()).unwrap();
#[test] fn test_create_db() { let dir = TempDir::new("test").unwrap(); let env = Environment::new().set_max_dbs(11).open(dir.path()).unwrap();
assert!(env.open_db(Some("testdb")).is_err());
assert!(env.create_db(Some("testdb"), DatabaseFlags::empty()).is_ok());
assert!(env.open_db(Some("testdb")).is_ok())
}
#[test] fn test_close_database() { let dir = TempDir::new("test").unwrap(); letmut env = Environment::new().set_max_dbs(10).open(dir.path()).unwrap();
let db = env.create_db(Some("db"), DatabaseFlags::empty()).unwrap(); unsafe {
env.close_db(db);
}
assert!(env.open_db(Some("db")).is_ok());
}
#[test] fn test_sync() { let dir = TempDir::new("test").unwrap();
{ let env = Environment::new().open(dir.path()).unwrap();
assert!(env.sync(true).is_ok());
}
{ let env = Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).unwrap();
assert!(env.sync(true).is_err());
}
}
#[test] fn test_stat() { let dir = TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path()).unwrap();
// Stats should be empty initially. let stat = env.stat().unwrap();
assert_eq!(stat.page_size(), 4096);
assert_eq!(stat.depth(), 0);
assert_eq!(stat.branch_pages(), 0);
assert_eq!(stat.leaf_pages(), 0);
assert_eq!(stat.overflow_pages(), 0);
assert_eq!(stat.entries(), 0);
let db = env.open_db(None).unwrap();
// Write a few small values. for i in0..64 { letmut value = [0u8; 8];
LittleEndian::write_u64(&mut value, i); letmut tx = env.begin_rw_txn().expect("begin_rw_txn");
tx.put(db, &value, &value, WriteFlags::default()).expect("tx.put");
tx.commit().expect("tx.commit")
}
// Stats should now reflect inserted values. let stat = env.stat().unwrap();
assert_eq!(stat.page_size(), 4096);
assert_eq!(stat.depth(), 1);
assert_eq!(stat.branch_pages(), 0);
assert_eq!(stat.leaf_pages(), 1);
assert_eq!(stat.overflow_pages(), 0);
assert_eq!(stat.entries(), 64);
}
#[test] fn test_info() { let map_size = 1024 * 1024; let dir = TempDir::new("test").unwrap(); let env = Environment::new().set_map_size(map_size).open(dir.path()).unwrap();
let info = env.info().unwrap();
assert_eq!(info.map_size(), map_size);
assert_eq!(info.last_pgno(), 1);
assert_eq!(info.last_txnid(), 0); // The default max readers is 126.
assert_eq!(info.max_readers(), 126);
assert_eq!(info.num_readers(), 0);
}
#[test] fn test_freelist() { let dir = TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path()).unwrap();
let db = env.open_db(None).unwrap(); letmut freelist = env.freelist().unwrap();
assert_eq!(freelist, 0);
// Write a few small values. for i in0..64 { letmut value = [0u8; 8];
LittleEndian::write_u64(&mut value, i); letmut tx = env.begin_rw_txn().expect("begin_rw_txn");
tx.put(db, &value, &value, WriteFlags::default()).expect("tx.put");
tx.commit().expect("tx.commit")
} letmut tx = env.begin_rw_txn().expect("begin_rw_txn");
tx.clear_db(db).expect("clear");
tx.commit().expect("tx.commit");
// Freelist should not be empty after clear_db.
freelist = env.freelist().unwrap();
assert!(freelist > 0);
}
#[test] fn test_set_map_size() { let dir = TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path()).unwrap();
letmut info = env.info().unwrap(); let default_size = info.map_size();
// Resizing to 0 merely reloads the map size
env.set_map_size(0).unwrap();
info = env.info().unwrap();
assert_eq!(info.map_size(), default_size);
env.set_map_size(2 * default_size).unwrap();
info = env.info().unwrap();
assert_eq!(info.map_size(), 2 * default_size);
env.set_map_size(4 * default_size).unwrap();
info = env.info().unwrap();
assert_eq!(info.map_size(), 4 * default_size);
// Decreasing is also fine if the space hasn't been consumed.
env.set_map_size(2 * default_size).unwrap();
info = env.info().unwrap();
assert_eq!(info.map_size(), 2 * default_size);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.