diff --git a/Cargo.toml b/Cargo.toml index ccfa844..2ca3e34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,3 @@ name = "lmdb" [dependencies.lmdb-sys] path = "lmdb-sys" - diff --git a/README.md b/README.md index 1982bdc..8e2dc88 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ cargo build ## TODO * [x] lmdb-sys. -* [ ] Cursors. +* [x] Cursors. * [ ] Zero-copy put API. * [ ] Nested transactions. * [ ] Database statistics. diff --git a/lmdb-sys/src/lib.rs b/lmdb-sys/src/lib.rs index 01cc6fa..f0fcacf 100644 --- a/lmdb-sys/src/lib.rs +++ b/lmdb-sys/src/lib.rs @@ -52,24 +52,44 @@ pub struct MDB_envinfo { #[repr(C)] pub enum MDB_cursor_op { + /// Position at first key/data item. MDB_FIRST, + /// Position at first data item of current key. Only for `MDB_DUPSORT`. MDB_FIRST_DUP, + /// Position at key/data pair. Only for `MDB_DUPSORT`. MDB_GET_BOTH, + /// position at key, nearest data. Only for `MDB_DUPSORT`. MDB_GET_BOTH_RANGE, + /// Return key/data at current cursor position. MDB_GET_CURRENT, + /// Return key and up to a page of duplicate data items from current cursor position. Move + /// cursor to prepare for `MDB_NEXT_MULTIPLE`. Only for `MDB_DUPFIXED`. MDB_GET_MULTIPLE, + /// Position at last key/data item. MDB_LAST, + /// Position at last data item of current key. Only for `MDB_DUPSORT`. MDB_LAST_DUP, + /// Position at next data item. MDB_NEXT, + /// Position at next data item of current key. Only for `MDB_DUPSORT`. MDB_NEXT_DUP, + /// Return key and up to a page of duplicate data items from next cursor position. Move cursor + /// to prepare for `MDB_NEXT_MULTIPLE`. Only for `MDB_DUPFIXED`. MDB_NEXT_MULTIPLE, + /// Position at first data item of next key. MDB_NEXT_NODUP, + /// Position at previous data item. MDB_PREV, + /// Position at previous data item of current key. Only for `MDB_DUPSORT`. MDB_PREV_DUP, + /// Position at last data item of previous key. MDB_PREV_NODUP, + /// Position at specified key. MDB_SET, + /// Position at specified key, return key + data. MDB_SET_KEY, - MDB_SET_RANGE + /// Position at first key greater than or equal to specified key. + MDB_SET_RANGE, } // Return codes diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 0000000..417520e --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,256 @@ +use libc::{c_void, size_t}; +use std::{mem, ptr, raw}; +use std::kinds::marker; + +use database::Database; +use error::{LmdbResult, lmdb_result}; +use ffi; +use ffi::{MDB_cursor, mdb_cursor_open, MDB_cursor_op, MDB_val}; +use transaction::Transaction; + +/// A cursor for navigating within a database. +pub struct Cursor<'txn> { + cursor: *mut MDB_cursor, + _marker: marker::ContravariantLifetime<'txn>, +} + +unsafe fn slice_to_val(slice: Option<&[u8]>) -> MDB_val { + match slice { + Some(slice) => + MDB_val { mv_size: slice.len() as size_t, + mv_data: slice.as_ptr() as *const c_void }, + None => + MDB_val { mv_size: 0, + mv_data: ptr::null() }, + } +} + +unsafe fn val_to_slice<'a>(val: MDB_val) -> &'a [u8] { + mem::transmute(raw::Slice { + data: val.mv_data as *const u8, + len: val.mv_size as uint + }) +} + +impl <'txn> Cursor<'txn> { + + /// Creates a new cursor into the given database in the given transaction. Prefer using + /// `Transaction::open_cursor()`. + #[doc(hidden)] + pub fn new(txn: &'txn Transaction, db: Database) -> LmdbResult> { + let mut cursor: *mut MDB_cursor = ptr::null_mut(); + unsafe { try!(lmdb_result(mdb_cursor_open(txn.txn(), db.dbi(), &mut cursor))); } + Ok(Cursor { + cursor: cursor, + _marker: marker::ContravariantLifetime::<'txn>, + }) + } + + pub fn cursor(&self) -> *mut MDB_cursor { + self.cursor + } + + /// Retrieves a key/data pair from the cursor. Depending on the cursor op, the current key is + /// returned. + pub fn get(&self, + key: Option<&[u8]>, + data: Option<&[u8]>, + op: MDB_cursor_op) + -> LmdbResult<(Option<&'txn [u8]>, &'txn [u8])> { + unsafe { + let mut key_val = slice_to_val(key); + let mut data_val = slice_to_val(data); + let key_ptr = key_val.mv_data; + try!(lmdb_result(ffi::mdb_cursor_get(self.cursor, + &mut key_val, + &mut data_val, + op))); + let key_out = if key_ptr != key_val.mv_data { Some(val_to_slice(key_val)) } else { None }; + let data_out = val_to_slice(data_val); + Ok((key_out, data_out)) + } + } +} + +#[cfg(test)] +mod test { + + use libc::{c_void, size_t}; + use std::{io, ptr}; + + use environment::*; + use error::{LmdbResult, lmdb_result}; + use flags::*; + use ffi; + use ffi::{MDB_cursor_op, MDB_val}; + use super::*; + + #[test] + fn test_get() { + let dir = io::TempDir::new("test").unwrap(); + let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); + txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap(); + + let cursor = txn.open_cursor(db).unwrap(); + assert_eq!((Some(b"key1"), b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_FIRST).unwrap()); + assert_eq!((Some(b"key1"), b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_GET_CURRENT).unwrap()); + assert_eq!((Some(b"key2"), b"val2"), + cursor.get(None, None, MDB_cursor_op::MDB_NEXT).unwrap()); + assert_eq!((Some(b"key1"), b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_PREV).unwrap()); + assert_eq!((Some(b"key3"), b"val3"), + cursor.get(None, None, MDB_cursor_op::MDB_LAST).unwrap()); + assert_eq!((None, b"val2"), + cursor.get(Some(b"key2"), None, MDB_cursor_op::MDB_SET).unwrap()); + assert_eq!((Some(b"key3"), b"val3"), + cursor.get(Some(b"key3"), None, MDB_cursor_op::MDB_SET_KEY).unwrap()); + assert_eq!((Some(b"key3"), b"val3"), + cursor.get(Some(b"key2\0"), None, MDB_cursor_op::MDB_SET_RANGE).unwrap()); + } + + #[test] + fn test_get_dup() { + let dir = io::TempDir::new("test").unwrap(); + let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let db = txn.open_db(None, MDB_DUPSORT).unwrap(); + txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val2", WriteFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val3", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val1", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val3", WriteFlags::empty()).unwrap(); + + let cursor = txn.open_cursor(db).unwrap(); + assert_eq!((Some(b"key1"), b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_FIRST).unwrap()); + assert_eq!((None, b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_FIRST_DUP).unwrap()); + assert_eq!((Some(b"key1"), b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_GET_CURRENT).unwrap()); + assert_eq!((Some(b"key2"), b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_NEXT_NODUP).unwrap()); + assert_eq!((Some(b"key2"), b"val2"), + cursor.get(None, None, MDB_cursor_op::MDB_NEXT_DUP).unwrap()); + assert_eq!((Some(b"key2"), b"val3"), + cursor.get(None, None, MDB_cursor_op::MDB_NEXT_DUP).unwrap()); + assert!(cursor.get(None, None, MDB_cursor_op::MDB_NEXT_DUP).is_err()); + assert_eq!((Some(b"key2"), b"val2"), + cursor.get(None, None, MDB_cursor_op::MDB_PREV_DUP).unwrap()); + assert_eq!((None, b"val3"), + cursor.get(None, None, MDB_cursor_op::MDB_LAST_DUP).unwrap()); + assert_eq!((Some(b"key1"), b"val3"), + cursor.get(None, None, MDB_cursor_op::MDB_PREV_NODUP).unwrap()); + assert_eq!((None, b"val1"), + cursor.get(Some(b"key1"), None, MDB_cursor_op::MDB_SET).unwrap()); + assert_eq!((Some(b"key2"), b"val1"), + cursor.get(Some(b"key2"), None, MDB_cursor_op::MDB_SET_KEY).unwrap()); + assert_eq!((Some(b"key2"), b"val1"), + cursor.get(Some(b"key1\0"), None, MDB_cursor_op::MDB_SET_RANGE).unwrap()); + assert_eq!((None, b"val3"), + cursor.get(Some(b"key1"), Some(b"val3"), MDB_cursor_op::MDB_GET_BOTH).unwrap()); + assert_eq!((None, b"val1"), + cursor.get(Some(b"key2"), Some(b"val"), MDB_cursor_op::MDB_GET_BOTH_RANGE).unwrap()); + } + + #[test] + fn test_get_dupfixed() { + let dir = io::TempDir::new("test").unwrap(); + let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let db = txn.open_db(None, MDB_DUPSORT | MDB_DUPFIXED).unwrap(); + txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val2", WriteFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val3", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val4", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val5", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val6", WriteFlags::empty()).unwrap(); + + let cursor = txn.open_cursor(db).unwrap(); + assert_eq!((Some(b"key1"), b"val1"), + cursor.get(None, None, MDB_cursor_op::MDB_FIRST).unwrap()); + assert_eq!((None, b"val1val2val3"), + cursor.get(None, None, MDB_cursor_op::MDB_GET_MULTIPLE).unwrap()); + assert!(cursor.get(None, None, MDB_cursor_op::MDB_NEXT_MULTIPLE).is_err()); + } + + /// Checks assumptions about which get operations return keys. + #[test] + fn test_get_keys() { + let dir = io::TempDir::new("test").unwrap(); + let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + + unsafe fn slice_to_val(slice: Option<&[u8]>) -> MDB_val { + match slice { + Some(slice) => + MDB_val { mv_size: slice.len() as size_t, + mv_data: slice.as_ptr() as *const c_void }, + None => + MDB_val { mv_size: 0, + mv_data: ptr::null() }, + } + } + + /// Returns true if the cursor get sets the key. + fn sets_key(cursor: &Cursor, + key: Option<&[u8]>, + data: Option<&[u8]>, + op: MDB_cursor_op) + -> LmdbResult { + unsafe { + let mut key_val = slice_to_val(key); + let mut data_val = slice_to_val(data); + let key_ptr = key_val.mv_data; + try!(lmdb_result(ffi::mdb_cursor_get(cursor.cursor(), + &mut key_val, + &mut data_val, + op))); + Ok(key_ptr != key_val.mv_data) + } + } + + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let db = txn.open_db(None, MDB_DUPSORT | MDB_DUPFIXED).unwrap(); + txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val2", WriteFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val3", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val4", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val5", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val6", WriteFlags::empty()).unwrap(); + txn.put(db, b"key3", b"val7", WriteFlags::empty()).unwrap(); + txn.put(db, b"key3", b"val8", WriteFlags::empty()).unwrap(); + txn.put(db, b"key3", b"val9", WriteFlags::empty()).unwrap(); + + let cursor = txn.open_cursor(db).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST).unwrap()); + assert!(!sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST_DUP).unwrap()); + assert!(!sets_key(&cursor, Some(b"key2"), Some(b"val5"), MDB_cursor_op::MDB_GET_BOTH).unwrap()); + assert!(!sets_key(&cursor, Some(b"key2"), Some(b"val"), MDB_cursor_op::MDB_GET_BOTH_RANGE).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_GET_CURRENT).unwrap()); + assert!(!sets_key(&cursor, None, None, MDB_cursor_op::MDB_GET_MULTIPLE).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_LAST).unwrap()); + assert!(!sets_key(&cursor, None, None, MDB_cursor_op::MDB_LAST_DUP).unwrap()); + sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT_DUP).unwrap()); + sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT_MULTIPLE).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT_NODUP).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_PREV).unwrap()); + sets_key(&cursor, None, None, MDB_cursor_op::MDB_LAST).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_PREV_DUP).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_PREV_NODUP).unwrap()); + assert!(!sets_key(&cursor, Some(b"key2"), None, MDB_cursor_op::MDB_SET).unwrap()); + assert!(sets_key(&cursor, Some(b"key2"), None, MDB_cursor_op::MDB_SET_KEY).unwrap()); + assert!(sets_key(&cursor, Some(b"key2"), None, MDB_cursor_op::MDB_SET_RANGE).unwrap()); + } +} diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..5948ac6 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,45 @@ +use std::kinds::marker; +use std::ptr; + +use error::{LmdbResult, lmdb_result}; +use ffi::{MDB_dbi, mdb_dbi_open}; +use flags::DatabaseFlags; +use transaction::Transaction; + +/// A handle to an individual database in an environment. +/// +/// A database handle denotes the name and parameters of a database in an environment. The database +/// may not exist in the environment (for instance, if the database is opened during a transaction +/// that has not yet committed). +pub struct Database<'env> { + dbi: MDB_dbi, + _marker: marker::ContravariantLifetime<'env>, +} + +impl <'env> Copy for Database<'env> { } + +impl <'env> Database<'env> { + + /// Opens a database in the given transaction. Prefer using `Transaction::open_db`. + #[doc(hidden)] + pub fn new(txn: &Transaction<'env>, + name: Option<&str>, + flags: DatabaseFlags) + -> LmdbResult> { + let c_name = name.map(|n| n.to_c_str()); + let name_ptr = if let Some(ref c_name) = c_name { c_name.as_ptr() } else { ptr::null() }; + let mut dbi: MDB_dbi = 0; + unsafe { + try!(lmdb_result(mdb_dbi_open(txn.txn(), name_ptr, flags.bits(), &mut dbi))); + } + Ok(Database { dbi: dbi, _marker: marker::ContravariantLifetime::<'env> }) + } + + /// Returns the underlying LMDB database handle. + /// + /// The caller **must** ensure that the handle is not used after the lifetime of the + /// environment, or after the database handle has been closed. + pub fn dbi(&self) -> MDB_dbi { + self.dbi + } +} diff --git a/src/environment.rs b/src/environment.rs index 2e95347..01dbc0a 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -2,7 +2,7 @@ use libc::{c_uint, size_t, mode_t}; use std::io::FilePermission; use std::ptr; -use Database; +use database::Database; use error::{LmdbError, LmdbResult, lmdb_result}; use ffi; use ffi::MDB_env; diff --git a/src/error.rs b/src/error.rs index 9485e21..8bd53a1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,7 +57,7 @@ impl Error for LmdbError { LmdbError::Unknown(i) => i, LmdbError::Io(ref io_error) => return io_error.description(), }; - unsafe { str::raw::c_str_to_static_slice(ffi::mdb_strerror(err_code) as *const _) } + unsafe { str::from_c_str(ffi::mdb_strerror(err_code) as *const _) } } } diff --git a/src/lib.rs b/src/lib.rs index ec18e31..a00984f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,12 +8,13 @@ #[phase(plugin, link)] extern crate log; extern crate libc; -extern crate sync; extern crate "lmdb-sys" as ffi; +pub use cursor::Cursor; +pub use database::Database; pub use environment::{Environment, EnvironmentBuilder}; pub use error::{LmdbResult, LmdbError}; -pub use transaction::{Database, Transaction}; +pub use transaction::Transaction; macro_rules! lmdb_try { ($expr:expr) => ({ @@ -36,7 +37,10 @@ macro_rules! lmdb_try_with_cleanup { }) } +mod cursor; +mod database; mod environment; mod error; mod transaction; + pub mod flags; diff --git a/src/transaction.rs b/src/transaction.rs index 7c0620b..667e016 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -2,31 +2,34 @@ use libc::{c_uint, c_void, size_t}; use std::{mem, ptr, raw}; use std::kinds::marker; +use cursor::Cursor; +use database::Database; use environment::Environment; use error::{LmdbResult, lmdb_result}; use ffi; -use ffi::{MDB_txn, MDB_dbi}; +use ffi::MDB_txn; use flags::{DatabaseFlags, EnvironmentFlags, WriteFlags}; /// An LMDB transaction. /// /// All database operations require a transaction. -pub struct Transaction<'a> { +pub struct Transaction<'env> { txn: *mut MDB_txn, - _marker: marker::ContravariantLifetime<'a>, + _marker: marker::ContravariantLifetime<'env>, } #[unsafe_destructor] -impl <'a> Drop for Transaction<'a> { +impl <'env> Drop for Transaction<'env> { fn drop(&mut self) { unsafe { ffi::mdb_txn_abort(self.txn) } } } -impl <'a> Transaction<'a> { +impl <'env> Transaction<'env> { - /// Creates a new transaction in the given environment. - pub fn new(env: &'a Environment, flags: EnvironmentFlags) -> LmdbResult> { + /// Creates a new transaction in the given environment. Prefer using `Environment::begin_txn`. + #[doc(hidden)] + pub fn new(env: &'env Environment, flags: EnvironmentFlags) -> LmdbResult> { let mut txn: *mut MDB_txn = ptr::null_mut(); unsafe { try!(lmdb_result(ffi::mdb_txn_begin(env.env(), @@ -35,7 +38,7 @@ impl <'a> Transaction<'a> { &mut txn))); Ok(Transaction { txn: txn, - _marker: marker::ContravariantLifetime::<'a>, + _marker: marker::ContravariantLifetime::<'env>, }) } } @@ -53,7 +56,7 @@ impl <'a> Transaction<'a> { /// 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 envirnment must be configured to allow named databases through + /// case the environment must be configured to allow named databases through /// `EnvironmentBuilder::set_max_dbs`. /// /// The database handle will be private to the current transaction until the transaction is @@ -63,21 +66,15 @@ impl <'a> Transaction<'a> { /// /// A transaction that uses this function must finish (either commit or abort) before any other /// transaction may use the function. - pub fn open_db(&self, name: Option<&str>, flags: DatabaseFlags) -> LmdbResult> { - let c_name = name.map(|n| n.to_c_str()); - let name_ptr = if let Some(ref c_name) = c_name { c_name.as_ptr() } else { ptr::null() }; - let mut dbi: MDB_dbi = 0; - unsafe { - try!(lmdb_result(ffi::mdb_dbi_open(self.txn, name_ptr, flags.bits(), &mut dbi))); - } - Ok(Database { dbi: dbi, _marker: marker::ContravariantLifetime::<'a> }) + pub fn open_db(&self, name: Option<&str>, flags: DatabaseFlags) -> LmdbResult> { + Database::new(self, name, flags) } /// Gets the option flags for the given database in the transaction. - pub fn db_flags(&self, db: &Database) -> LmdbResult { + pub fn db_flags(&self, db: Database) -> LmdbResult { let mut flags: c_uint = 0; unsafe { - try!(lmdb_result(ffi::mdb_dbi_flags(self.txn, db.dbi, &mut flags))); + try!(lmdb_result(ffi::mdb_dbi_flags(self.txn, db.dbi(), &mut flags))); } Ok(DatabaseFlags::from_bits_truncate(flags)) @@ -106,17 +103,17 @@ impl <'a> Transaction<'a> { /// This function retrieves the data associated with the given key in the database. If the /// database supports duplicate keys (`MDB_DUPSORT`) then the first data item for the key will /// be returned. Retrieval of other items requires the use of `Transaction::cursor_get`. - pub fn get(&self, database: &Database, key: &[u8]) -> LmdbResult<&'a [u8]> { + pub fn get<'txn>(&'txn self, database: Database, key: &[u8]) -> LmdbResult<&'txn [u8]> { let mut key_val: ffi::MDB_val = ffi::MDB_val { mv_size: key.len() as size_t, mv_data: key.as_ptr() as *const c_void }; let mut data_val: ffi::MDB_val = ffi::MDB_val { mv_size: 0, mv_data: ptr::null() }; unsafe { try!(lmdb_result(ffi::mdb_get(self.txn(), - database.dbi, + database.dbi(), &mut key_val, &mut data_val))); - let slice: &'a [u8] = + let slice: &'txn [u8] = mem::transmute(raw::Slice { data: data_val.mv_data as *const u8, len: data_val.mv_size as uint @@ -130,8 +127,8 @@ impl <'a> Transaction<'a> { /// This function stores key/data pairs in the database. The default behavior is to enter the /// new key/data pair, replacing any previously existing key if duplicates are disallowed, or /// adding a duplicate data item if duplicates are allowed (`MDB_DUPSORT`). - pub fn put(&self, - database: &Database, + pub fn put(&mut self, + database: Database, key: &[u8], data: &[u8], flags: WriteFlags) @@ -142,7 +139,7 @@ impl <'a> Transaction<'a> { mv_data: data.as_ptr() as *const c_void }; unsafe { lmdb_result(ffi::mdb_put(self.txn(), - database.dbi, + database.dbi(), &mut key_val, &mut data_val, flags.bits())) @@ -157,8 +154,8 @@ impl <'a> Transaction<'a> { /// for the key will be deleted. Otherwise, if the data parameter is `Some` only the matching /// data item will be deleted. This function will return `MDB_NOTFOUND` if the specified key/data /// pair is not in the database. - pub fn del(&self, - database: &Database, + pub fn del(&mut self, + database: Database, key: &[u8], data: Option<&[u8]>) -> LmdbResult<()> { @@ -169,37 +166,15 @@ impl <'a> Transaction<'a> { mv_data: data.as_ptr() as *const c_void }); unsafe { lmdb_result(ffi::mdb_del(self.txn(), - database.dbi, + database.dbi(), &mut key_val, data_val.map(|mut data_val| &mut data_val as *mut _) .unwrap_or(ptr::null_mut()))) } } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -//// Database -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/// A handle to an individual database in an environment. -/// -/// A database handle denotes the name and parameters of a database. The database may not -/// exist in the environment. -pub struct Database<'a> { - dbi: MDB_dbi, - _marker: marker::ContravariantLifetime<'a>, -} - -impl <'a> Copy for Database<'a> { } - -impl <'a> Database<'a> { - /// Returns the underlying LMDB database handle. - /// - /// The caller **must** ensure that the handle is not used after the lifetime of the - /// environment, or after the database handle has been closed. - pub fn dbi(&self) -> MDB_dbi { - self.dbi + pub fn open_cursor<'txn>(&'txn self, db: Database) -> LmdbResult> { + Cursor::new(self, db) } } @@ -240,21 +215,21 @@ mod test { let dir = io::TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); - txn.put(&db, b"key1", b"val1", WriteFlags::empty()).unwrap(); - txn.put(&db, b"key2", b"val2", WriteFlags::empty()).unwrap(); - txn.put(&db, b"key3", b"val3", WriteFlags::empty()).unwrap(); + txn.put(db, b"key1", b"val1", WriteFlags::empty()).unwrap(); + txn.put(db, b"key2", b"val2", WriteFlags::empty()).unwrap(); + txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap(); txn.commit().unwrap(); - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - assert_eq!(b"val1", txn.get(&db, b"key1").unwrap()); - assert_eq!(b"val2", txn.get(&db, b"key2").unwrap()); - assert_eq!(b"val3", txn.get(&db, b"key3").unwrap()); - assert!(txn.get(&db, b"key").is_err()); + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + assert_eq!(b"val1", txn.get(db, b"key1").unwrap()); + assert_eq!(b"val2", txn.get(db, b"key2").unwrap()); + assert_eq!(b"val3", txn.get(db, b"key3").unwrap()); + assert!(txn.get(db, b"key").is_err()); - txn.del(&db, b"key1", None).unwrap(); - assert!(txn.get(&db, b"key1").is_err()); + txn.del(db, b"key1", None).unwrap(); + assert!(txn.get(db, b"key1").is_err()); } #[test] @@ -280,19 +255,19 @@ mod test { }; // Check that database handles are reused properly - assert!(db1.dbi == db2.dbi); + assert!(db1.dbi() == db2.dbi()); { - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - txn.put(&db1, b"key1", b"val1", WriteFlags::empty()).unwrap(); + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + txn.put(db1, b"key1", b"val1", WriteFlags::empty()).unwrap(); assert!(txn.commit().is_ok()); } unsafe { env.close_db(db1) }; { - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - assert!(txn.put(&db1, b"key2", b"val2", WriteFlags::empty()).is_err()); + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + assert!(txn.put(db1, b"key2", b"val2", WriteFlags::empty()).is_err()); } } @@ -302,7 +277,7 @@ mod test { let env = Arc::new(Environment::new().open(dir.path(), io::USER_RWX).unwrap()); let open_db_txn = env.begin_txn(MDB_RDONLY).unwrap(); - let db = Arc::new(open_db_txn.open_db(None, DatabaseFlags::empty()).unwrap()); + let db = open_db_txn.open_db(None, DatabaseFlags::empty()).unwrap(); open_db_txn.commit().unwrap(); let n = 10u; // Number of concurrent readers @@ -314,27 +289,26 @@ mod test { for _ in range(0, n) { let reader_env = env.clone(); - let reader_db = db.clone(); let reader_barrier = barrier.clone(); futures.push(Future::spawn(proc() { { let txn = reader_env.begin_txn(MDB_RDONLY).unwrap(); - assert!(txn.get(&*reader_db, key).is_err()); + assert!(txn.get(db, key).is_err()); txn.abort(); } reader_barrier.wait(); reader_barrier.wait(); { let txn = reader_env.begin_txn(MDB_RDONLY).unwrap(); - txn.get(&*reader_db, key).unwrap() == val + txn.get(db, key).unwrap() == val } })); } - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); barrier.wait(); - txn.put(&*db, key, val, WriteFlags::empty()).unwrap(); + txn.put(db, key, val, WriteFlags::empty()).unwrap(); txn.commit().unwrap(); barrier.wait(); @@ -347,7 +321,7 @@ mod test { let env = Arc::new(Environment::new().open(dir.path(), io::USER_RWX).unwrap()); let open_db_txn = env.begin_txn(MDB_RDONLY).unwrap(); - let db = Arc::new(open_db_txn.open_db(None, DatabaseFlags::empty()).unwrap()); + let db = open_db_txn.open_db(None, DatabaseFlags::empty()).unwrap(); open_db_txn.commit().unwrap(); let n = 10u; // Number of concurrent writers @@ -358,11 +332,10 @@ mod test { for i in range(0, n) { let writer_env = env.clone(); - let writer_db = db.clone(); futures.push(Future::spawn(proc() { - let txn = writer_env.begin_txn(EnvironmentFlags::empty()).unwrap(); - txn.put(&*writer_db, + let mut txn = writer_env.begin_txn(EnvironmentFlags::empty()).unwrap(); + txn.put(db, format!("{}{}", key, i).as_bytes(), format!("{}{}", val, i).as_bytes(), WriteFlags::empty()) @@ -377,7 +350,7 @@ mod test { for i in range(0, n) { assert_eq!( format!("{}{}", val, i).as_bytes(), - txn.get(&*db, format!("{}{}", key, i).as_bytes()).unwrap()); + txn.get(db, format!("{}{}", key, i).as_bytes()).unwrap()); } } }