use std::cmp::{Ord, Ordering};
use std::ffi::CString;
use std::mem;
use std::ptr;
use libc::c_int;
use ffi;
use supercow::Supercow;
use env::{self, Environment};
use error::{Error, Result};
use mdb_vals::*;
use traits::*;
use tx::TxHandle;
pub mod db {
use ffi;
use libc;
bitflags! {
pub struct Flags : libc::c_uint {
const REVERSEKEY = ffi::MDB_REVERSEKEY;
const DUPSORT = ffi::MDB_DUPSORT;
const INTEGERKEY = ffi::MDB_INTEGERKEY;
const DUPFIXED = ffi::MDB_DUPFIXED;
const INTEGERDUP = ffi::MDB_INTEGERDUP;
const REVERSEDUP = ffi::MDB_REVERSEDUP;
const CREATE = ffi::MDB_CREATE;
}
}
}
#[derive(Debug)]
struct DbHandle<'a> {
env: Supercow<'a, Environment>,
dbi: ffi::MDB_dbi,
close_on_drop: bool,
}
impl<'a> Drop for DbHandle<'a> {
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>,
}
#[derive(Clone,Debug)]
pub struct DatabaseOptions {
pub flags: db::Flags,
key_cmp: Option<ffi::MDB_cmp_func>,
val_cmp: Option<ffi::MDB_cmp_func>,
}
impl DatabaseOptions {
pub fn new(flags: db::Flags) -> DatabaseOptions {
DatabaseOptions {
flags: flags,
key_cmp: None,
val_cmp: None,
}
}
pub fn defaults() -> DatabaseOptions {
DatabaseOptions::new(db::Flags::empty())
}
pub fn sort_keys_as<K : LmdbOrdKey + ?Sized>(&mut self) {
self.key_cmp = Some(DatabaseOptions::entry_cmp_as::<K>);
}
pub fn sort_values_as<V : LmdbOrdKey + ?Sized>(&mut self) {
self.val_cmp = Some(DatabaseOptions::entry_cmp_as::<V>);
}
pub fn create_map<K : LmdbOrdKey + ?Sized>() -> Self {
let mut this = DatabaseOptions::new(db::CREATE);
if K::ordered_as_integer() {
this.flags |= db::INTEGERKEY;
} else if !K::ordered_by_bytes() {
this.sort_keys_as::<K>();
}
this
}
pub fn create_multimap_unsized<K : LmdbOrdKey + ?Sized,
V : LmdbOrdKey + ?Sized>
() -> Self
{
let mut this = DatabaseOptions::create_map::<K>();
this.flags |= db::DUPSORT;
if V::ordered_as_integer() {
this.flags |= db::INTEGERDUP;
} else if !V::ordered_by_bytes() {
this.sort_values_as::<V>();
}
this
}
pub fn create_multimap<K : LmdbOrdKey + ?Sized,
V : LmdbOrdKey + Sized>
() -> Self
{
let mut this = DatabaseOptions::create_multimap_unsized::<K, V>();
this.flags |= db::DUPFIXED;
this
}
extern fn entry_cmp_as<V : LmdbOrdKey + ?Sized>(
ap: *const ffi::MDB_val, bp: *const ffi::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> {
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: ffi::MDB_dbi = 0;
let name_cstr = match name {
None => None,
Some(s) => Some(try!(CString::new(s))),
};
let raw = unsafe {
use env;
let mut locked_dbis = env::env_open_dbis(&env).lock()
.expect("open_dbis lock poisoned");
let mut raw_tx: *mut ffi::MDB_txn = ptr::null_mut();
let mut txn_flags = 0;
if env.flags().unwrap().contains(env::open::RDONLY) {
txn_flags = ffi::MDB_RDONLY;
}
lmdb_call!(ffi::mdb_txn_begin(
env::env_ptr(&env), ptr::null_mut(), txn_flags, &mut raw_tx));
let mut wrapped_tx = TxHandle(raw_tx); lmdb_call!(ffi::mdb_dbi_open(
raw_tx, name_cstr.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
options.flags.bits(), &mut raw));
if !locked_dbis.insert(raw) {
return Err(Error::Reopened)
}
if let Some(fun) = options.key_cmp {
lmdb_call!(ffi::mdb_set_compare(raw_tx, raw, fun));
}
if let Some(fun) = options.val_cmp {
lmdb_call!(ffi::mdb_set_dupsort(raw_tx, raw, fun));
}
try!(wrapped_tx.commit());
raw
};
Ok(Database {
db: DbHandle {
env: env,
dbi: raw,
close_on_drop: true,
}
})
}
pub unsafe fn from_raw<E>(env: E, raw: ffi::MDB_dbi) -> Self
where E : Into<Supercow<'a, Environment>> {
let env: Supercow<'a, Environment> = env.into();
use env;
{
let mut locked_dbis = env::env_open_dbis(&env).lock()
.expect("open_dbis lock poisoned");
if !locked_dbis.insert(raw) {
panic!("DBI {} already opened in this environment", raw);
}
}
Database {
db: DbHandle {
env: env,
dbi: raw,
close_on_drop: true,
}
}
}
pub unsafe fn borrow_raw<E>(env: E, raw: ffi::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<()> {
try!(env::dbi_delete(&self.db.env, self.db.dbi));
mem::forget(self.db);
Ok(())
}
#[inline]
pub fn env(&self) -> &Environment {
&*self.db.env
}
pub fn assert_same_env(&self, other_env: &Environment)
-> Result<()> {
if &*self.db.env as *const Environment !=
other_env as *const Environment
{
Err(Error::Mismatch)
} else {
Ok(())
}
}
#[deprecated(since = "0.4.4", note = "use as_raw() instead")]
pub fn dbi(&self) -> ffi::MDB_dbi {
self.db.dbi
}
#[inline]
pub fn as_raw(&self) -> ffi::MDB_dbi {
self.db.dbi
}
pub fn into_raw(mut self) -> ffi::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()
.expect("open_dbis lock poisoned");
locked_dbis.remove(&self.db.dbi);
self.db.close_on_drop = false;
}
}
}
#[cfg(test)]
mod test {
use test_helpers::*;
use dbi::*;
use 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());
}
}