use libc::c_int;
use std::cmp::{Ord, Ordering};
use std::ffi::CString;
use std::mem;
use std::ptr;
use supercow::Supercow;
use crate::env::{self, Environment};
use crate::error::{Error, Result};
use crate::mdb_vals::mdb_val_as_bytes;
use crate::traits::LmdbOrdKey;
use crate::tx::TxHandle;
pub mod db {
use liblmdb;
bitflags! {
pub struct Flags: u32 {
const REVERSEKEY = liblmdb::MDB_REVERSEKEY;
const DUPSORT = liblmdb::MDB_DUPSORT;
const INTEGERKEY = liblmdb::MDB_INTEGERKEY;
const DUPFIXED = liblmdb::MDB_DUPFIXED;
const INTEGERDUP = liblmdb::MDB_INTEGERDUP;
const REVERSEDUP = liblmdb::MDB_REVERSEDUP;
const CREATE = liblmdb::MDB_CREATE;
}
}
}
#[derive(Debug)]
struct DbHandle<'a> {
env: Supercow<'a, Environment>,
dbi: liblmdb::MDB_dbi,
close_on_drop: bool,
}
impl Drop for DbHandle<'_> {
fn drop(&mut self) {
if self.close_on_drop {
env::dbi_close(&self.env, self.dbi);
}
}
}
#[derive(Debug)]
pub struct Database<'a> {
db: DbHandle<'a>,
}
pub struct DatabaseOptions {
pub flags: db::Flags,
key_cmp: Option<liblmdb::MDB_cmp_func>,
val_cmp: Option<liblmdb::MDB_cmp_func>,
}
impl DatabaseOptions {
#[must_use]
pub fn new(flags: db::Flags) -> DatabaseOptions {
DatabaseOptions {
flags,
key_cmp: None,
val_cmp: None,
}
}
#[must_use]
pub fn defaults() -> DatabaseOptions {
DatabaseOptions::new(db::Flags::empty())
}
pub fn sort_keys_as<K: LmdbOrdKey + ?Sized>(&mut self) {
self.key_cmp = Some(Some(DatabaseOptions::entry_cmp_as::<K>));
}
pub fn sort_values_as<V: LmdbOrdKey + ?Sized>(&mut self) {
self.val_cmp = Some(Some(DatabaseOptions::entry_cmp_as::<V>));
}
#[must_use]
pub fn create_map<K: LmdbOrdKey + ?Sized>() -> Self {
let mut this = DatabaseOptions::new(db::Flags::CREATE);
if K::ordered_as_integer() {
this.flags |= db::Flags::INTEGERKEY;
} else if !K::ordered_by_bytes() {
this.sort_keys_as::<K>();
}
this
}
#[must_use]
pub fn create_multimap_unsized<K: LmdbOrdKey + ?Sized, V: LmdbOrdKey + ?Sized>() -> Self {
let mut this = DatabaseOptions::create_map::<K>();
this.flags |= db::Flags::DUPSORT;
if V::ordered_as_integer() {
this.flags |= db::Flags::INTEGERDUP;
} else if !V::ordered_by_bytes() {
this.sort_values_as::<V>();
}
this
}
#[must_use]
pub fn create_multimap<K: LmdbOrdKey + ?Sized, V: LmdbOrdKey + Sized>() -> Self {
let mut this = DatabaseOptions::create_multimap_unsized::<K, V>();
this.flags |= db::Flags::DUPFIXED;
this
}
extern "C" fn entry_cmp_as<V: LmdbOrdKey + ?Sized>(
ap: *const liblmdb::MDB_val,
bp: *const liblmdb::MDB_val,
) -> c_int {
match unsafe {
V::from_lmdb_bytes(mdb_val_as_bytes(&ap, &*ap))
.cmp(&V::from_lmdb_bytes(mdb_val_as_bytes(&bp, &*bp)))
} {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
}
impl<'a> Database<'a> {
#[allow(clippy::missing_panics_doc)]
pub fn open<E>(env: E, name: Option<&str>, options: &DatabaseOptions) -> Result<Database<'a>>
where
E: Into<Supercow<'a, Environment>>,
{
let env: Supercow<'a, Environment> = env.into();
let mut raw: liblmdb::MDB_dbi = 0;
let name_cstr = match name {
None => None,
Some(s) => Some(CString::new(s)?),
};
let raw = unsafe {
use crate::env;
let mut locked_dbis = env::env_open_dbis(&env).lock();
let mut raw_tx: *mut liblmdb::MDB_txn = ptr::null_mut();
let mut txn_flags = 0;
if env.flags()?.contains(env::open::Flags::RDONLY) {
txn_flags = liblmdb::MDB_RDONLY;
}
lmdb_call!(liblmdb::mdb_txn_begin(
env::env_ptr(&env),
ptr::null_mut(),
txn_flags,
&raw mut raw_tx
));
let mut wrapped_tx = TxHandle(raw_tx); lmdb_call!(liblmdb::mdb_dbi_open(
raw_tx,
name_cstr.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
options.flags.bits(),
&raw mut raw
));
if !locked_dbis.insert(raw) {
return Err(Error::Reopened);
}
if let Some(fun) = options.key_cmp {
lmdb_call!(liblmdb::mdb_set_compare(raw_tx, raw, fun));
}
if let Some(fun) = options.val_cmp {
lmdb_call!(liblmdb::mdb_set_dupsort(raw_tx, raw, fun));
}
wrapped_tx.commit()?;
raw
};
Ok(Database {
db: DbHandle {
env,
dbi: raw,
close_on_drop: true,
},
})
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn from_raw<E>(env: E, raw: liblmdb::MDB_dbi) -> Self
where
E: Into<Supercow<'a, Environment>>,
{
let env: Supercow<'a, Environment> = env.into();
{
use crate::env;
let mut locked_dbis = env::env_open_dbis(&env).lock();
assert!(
locked_dbis.insert(raw),
"DBI {raw} already opened in this environment"
);
}
Database {
db: DbHandle {
env,
dbi: raw,
close_on_drop: true,
},
}
}
pub unsafe fn borrow_raw<E>(env: E, raw: liblmdb::MDB_dbi) -> Self
where
E: Into<Supercow<'a, Environment>>,
{
Database {
db: DbHandle {
env: env.into(),
dbi: raw,
close_on_drop: false,
},
}
}
pub fn delete(self) -> Result<()> {
env::dbi_delete(&self.db.env, self.db.dbi)?;
mem::forget(self.db);
Ok(())
}
#[inline]
#[must_use]
pub fn env(&self) -> &Environment {
&self.db.env
}
pub fn assert_same_env(&self, other_env: &Environment) -> Result<()> {
if &raw const *self.db.env == ptr::from_ref::<Environment>(other_env) {
Ok(())
} else {
Err(Error::Mismatch)
}
}
#[deprecated(since = "0.4.4", note = "use as_raw() instead")]
#[must_use]
pub fn dbi(&self) -> liblmdb::MDB_dbi {
self.db.dbi
}
#[inline]
#[must_use]
pub fn as_raw(&self) -> liblmdb::MDB_dbi {
self.db.dbi
}
#[must_use]
pub fn into_raw(mut self) -> liblmdb::MDB_dbi {
self.disown();
self.db.dbi
}
pub fn disown(&mut self) {
use env;
if self.db.close_on_drop {
let mut locked_dbis = env::env_open_dbis(&self.db.env).lock();
locked_dbis.remove(&self.db.dbi);
self.db.close_on_drop = false;
}
}
}
#[cfg(test)]
mod test {
use crate::dbi::*;
use crate::test_helpers::*;
use crate::tx::*;
#[test]
fn disown_allows_sharing() {
let env = create_env();
let mut db1 = defdb(&env);
db1.disown();
let db2 = defdb(&env);
let tx = WriteTransaction::new(&env).unwrap();
tx.access()
.put(&db1, "foo", "bar", &put::Flags::empty())
.unwrap();
tx.commit().unwrap();
drop(db1);
let tx = ReadTransaction::new(&env).unwrap();
assert_eq!("bar", tx.access().get::<str, str>(&db2, "foo").unwrap());
}
#[test]
fn borrow_raw_allows_sharing() {
let env = create_env();
let db1 = defdb(&env);
let db2 = unsafe { Database::borrow_raw(&env, db1.as_raw()) };
let tx = WriteTransaction::new(&env).unwrap();
tx.access()
.put(&db2, "foo", "bar", &put::Flags::empty())
.unwrap();
tx.commit().unwrap();
drop(db2);
let tx = ReadTransaction::new(&env).unwrap();
assert_eq!("bar", tx.access().get::<str, str>(&db1, "foo").unwrap());
}
}