Cursor get support.

without.crypto
Dan Burkert 10 years ago
parent ef8f09ae52
commit bed03ebff6
  1. 1
      Cargo.toml
  2. 2
      README.md
  3. 22
      lmdb-sys/src/lib.rs
  4. 256
      src/cursor.rs
  5. 45
      src/database.rs
  6. 2
      src/environment.rs
  7. 2
      src/error.rs
  8. 8
      src/lib.rs
  9. 131
      src/transaction.rs

@ -16,4 +16,3 @@ name = "lmdb"
[dependencies.lmdb-sys] [dependencies.lmdb-sys]
path = "lmdb-sys" path = "lmdb-sys"

@ -22,7 +22,7 @@ cargo build
## TODO ## TODO
* [x] lmdb-sys. * [x] lmdb-sys.
* [ ] Cursors. * [x] Cursors.
* [ ] Zero-copy put API. * [ ] Zero-copy put API.
* [ ] Nested transactions. * [ ] Nested transactions.
* [ ] Database statistics. * [ ] Database statistics.

@ -52,24 +52,44 @@ pub struct MDB_envinfo {
#[repr(C)] #[repr(C)]
pub enum MDB_cursor_op { pub enum MDB_cursor_op {
/// Position at first key/data item.
MDB_FIRST, MDB_FIRST,
/// Position at first data item of current key. Only for `MDB_DUPSORT`.
MDB_FIRST_DUP, MDB_FIRST_DUP,
/// Position at key/data pair. Only for `MDB_DUPSORT`.
MDB_GET_BOTH, MDB_GET_BOTH,
/// position at key, nearest data. Only for `MDB_DUPSORT`.
MDB_GET_BOTH_RANGE, MDB_GET_BOTH_RANGE,
/// Return key/data at current cursor position.
MDB_GET_CURRENT, 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, MDB_GET_MULTIPLE,
/// Position at last key/data item.
MDB_LAST, MDB_LAST,
/// Position at last data item of current key. Only for `MDB_DUPSORT`.
MDB_LAST_DUP, MDB_LAST_DUP,
/// Position at next data item.
MDB_NEXT, MDB_NEXT,
/// Position at next data item of current key. Only for `MDB_DUPSORT`.
MDB_NEXT_DUP, 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, MDB_NEXT_MULTIPLE,
/// Position at first data item of next key.
MDB_NEXT_NODUP, MDB_NEXT_NODUP,
/// Position at previous data item.
MDB_PREV, MDB_PREV,
/// Position at previous data item of current key. Only for `MDB_DUPSORT`.
MDB_PREV_DUP, MDB_PREV_DUP,
/// Position at last data item of previous key.
MDB_PREV_NODUP, MDB_PREV_NODUP,
/// Position at specified key.
MDB_SET, MDB_SET,
/// Position at specified key, return key + data.
MDB_SET_KEY, MDB_SET_KEY,
MDB_SET_RANGE /// Position at first key greater than or equal to specified key.
MDB_SET_RANGE,
} }
// Return codes // Return codes

@ -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<Cursor<'txn>> {
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<bool> {
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());
}
}

@ -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<Database<'env>> {
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
}
}

@ -2,7 +2,7 @@ use libc::{c_uint, size_t, mode_t};
use std::io::FilePermission; use std::io::FilePermission;
use std::ptr; use std::ptr;
use Database; use database::Database;
use error::{LmdbError, LmdbResult, lmdb_result}; use error::{LmdbError, LmdbResult, lmdb_result};
use ffi; use ffi;
use ffi::MDB_env; use ffi::MDB_env;

@ -57,7 +57,7 @@ impl Error for LmdbError {
LmdbError::Unknown(i) => i, LmdbError::Unknown(i) => i,
LmdbError::Io(ref io_error) => return io_error.description(), 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 _) }
} }
} }

@ -8,12 +8,13 @@
#[phase(plugin, link)] extern crate log; #[phase(plugin, link)] extern crate log;
extern crate libc; extern crate libc;
extern crate sync;
extern crate "lmdb-sys" as ffi; extern crate "lmdb-sys" as ffi;
pub use cursor::Cursor;
pub use database::Database;
pub use environment::{Environment, EnvironmentBuilder}; pub use environment::{Environment, EnvironmentBuilder};
pub use error::{LmdbResult, LmdbError}; pub use error::{LmdbResult, LmdbError};
pub use transaction::{Database, Transaction}; pub use transaction::Transaction;
macro_rules! lmdb_try { macro_rules! lmdb_try {
($expr:expr) => ({ ($expr:expr) => ({
@ -36,7 +37,10 @@ macro_rules! lmdb_try_with_cleanup {
}) })
} }
mod cursor;
mod database;
mod environment; mod environment;
mod error; mod error;
mod transaction; mod transaction;
pub mod flags; pub mod flags;

@ -2,31 +2,34 @@ use libc::{c_uint, c_void, size_t};
use std::{mem, ptr, raw}; use std::{mem, ptr, raw};
use std::kinds::marker; use std::kinds::marker;
use cursor::Cursor;
use database::Database;
use environment::Environment; use environment::Environment;
use error::{LmdbResult, lmdb_result}; use error::{LmdbResult, lmdb_result};
use ffi; use ffi;
use ffi::{MDB_txn, MDB_dbi}; use ffi::MDB_txn;
use flags::{DatabaseFlags, EnvironmentFlags, WriteFlags}; use flags::{DatabaseFlags, EnvironmentFlags, WriteFlags};
/// An LMDB transaction. /// An LMDB transaction.
/// ///
/// All database operations require a transaction. /// All database operations require a transaction.
pub struct Transaction<'a> { pub struct Transaction<'env> {
txn: *mut MDB_txn, txn: *mut MDB_txn,
_marker: marker::ContravariantLifetime<'a>, _marker: marker::ContravariantLifetime<'env>,
} }
#[unsafe_destructor] #[unsafe_destructor]
impl <'a> Drop for Transaction<'a> { impl <'env> Drop for Transaction<'env> {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { ffi::mdb_txn_abort(self.txn) } unsafe { ffi::mdb_txn_abort(self.txn) }
} }
} }
impl <'a> Transaction<'a> { impl <'env> Transaction<'env> {
/// Creates a new transaction in the given environment. /// Creates a new transaction in the given environment. Prefer using `Environment::begin_txn`.
pub fn new(env: &'a Environment, flags: EnvironmentFlags) -> LmdbResult<Transaction<'a>> { #[doc(hidden)]
pub fn new(env: &'env Environment, flags: EnvironmentFlags) -> LmdbResult<Transaction<'env>> {
let mut txn: *mut MDB_txn = ptr::null_mut(); let mut txn: *mut MDB_txn = ptr::null_mut();
unsafe { unsafe {
try!(lmdb_result(ffi::mdb_txn_begin(env.env(), try!(lmdb_result(ffi::mdb_txn_begin(env.env(),
@ -35,7 +38,7 @@ impl <'a> Transaction<'a> {
&mut txn))); &mut txn)));
Ok(Transaction { Ok(Transaction {
txn: txn, 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 `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 /// 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`. /// `EnvironmentBuilder::set_max_dbs`.
/// ///
/// The database handle will be private to the current transaction until the transaction is /// 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 /// A transaction that uses this function must finish (either commit or abort) before any other
/// transaction may use the function. /// transaction may use the function.
pub fn open_db(&self, name: Option<&str>, flags: DatabaseFlags) -> LmdbResult<Database<'a>> { pub fn open_db(&self, name: Option<&str>, flags: DatabaseFlags) -> LmdbResult<Database<'env>> {
let c_name = name.map(|n| n.to_c_str()); Database::new(self, name, flags)
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> })
} }
/// Gets the option flags for the given database in the transaction. /// Gets the option flags for the given database in the transaction.
pub fn db_flags(&self, db: &Database) -> LmdbResult<DatabaseFlags> { pub fn db_flags(&self, db: Database) -> LmdbResult<DatabaseFlags> {
let mut flags: c_uint = 0; let mut flags: c_uint = 0;
unsafe { 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)) 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 /// 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 /// 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`. /// 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, 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 }; mv_data: key.as_ptr() as *const c_void };
let mut data_val: ffi::MDB_val = ffi::MDB_val { mv_size: 0, let mut data_val: ffi::MDB_val = ffi::MDB_val { mv_size: 0,
mv_data: ptr::null() }; mv_data: ptr::null() };
unsafe { unsafe {
try!(lmdb_result(ffi::mdb_get(self.txn(), try!(lmdb_result(ffi::mdb_get(self.txn(),
database.dbi, database.dbi(),
&mut key_val, &mut key_val,
&mut data_val))); &mut data_val)));
let slice: &'a [u8] = let slice: &'txn [u8] =
mem::transmute(raw::Slice { mem::transmute(raw::Slice {
data: data_val.mv_data as *const u8, data: data_val.mv_data as *const u8,
len: data_val.mv_size as uint 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 /// 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 /// 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`). /// adding a duplicate data item if duplicates are allowed (`MDB_DUPSORT`).
pub fn put(&self, pub fn put(&mut self,
database: &Database, database: Database,
key: &[u8], key: &[u8],
data: &[u8], data: &[u8],
flags: WriteFlags) flags: WriteFlags)
@ -142,7 +139,7 @@ impl <'a> Transaction<'a> {
mv_data: data.as_ptr() as *const c_void }; mv_data: data.as_ptr() as *const c_void };
unsafe { unsafe {
lmdb_result(ffi::mdb_put(self.txn(), lmdb_result(ffi::mdb_put(self.txn(),
database.dbi, database.dbi(),
&mut key_val, &mut key_val,
&mut data_val, &mut data_val,
flags.bits())) 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 /// 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 /// data item will be deleted. This function will return `MDB_NOTFOUND` if the specified key/data
/// pair is not in the database. /// pair is not in the database.
pub fn del(&self, pub fn del(&mut self,
database: &Database, database: Database,
key: &[u8], key: &[u8],
data: Option<&[u8]>) data: Option<&[u8]>)
-> LmdbResult<()> { -> LmdbResult<()> {
@ -169,37 +166,15 @@ impl <'a> Transaction<'a> {
mv_data: data.as_ptr() as *const c_void }); mv_data: data.as_ptr() as *const c_void });
unsafe { unsafe {
lmdb_result(ffi::mdb_del(self.txn(), lmdb_result(ffi::mdb_del(self.txn(),
database.dbi, database.dbi(),
&mut key_val, &mut key_val,
data_val.map(|mut data_val| &mut data_val as *mut _) data_val.map(|mut data_val| &mut data_val as *mut _)
.unwrap_or(ptr::null_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. pub fn open_cursor<'txn>(&'txn self, db: Database) -> LmdbResult<Cursor<'txn>> {
/// Cursor::new(self, db)
/// 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
} }
} }
@ -240,21 +215,21 @@ mod test {
let dir = io::TempDir::new("test").unwrap(); let dir = io::TempDir::new("test").unwrap();
let env = Environment::new().open(dir.path(), io::USER_RWX).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(); let db = txn.open_db(None, DatabaseFlags::empty()).unwrap();
txn.put(&db, b"key1", b"val1", 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"key2", b"val2", WriteFlags::empty()).unwrap();
txn.put(&db, b"key3", b"val3", WriteFlags::empty()).unwrap(); txn.put(db, b"key3", b"val3", WriteFlags::empty()).unwrap();
txn.commit().unwrap(); txn.commit().unwrap();
let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap();
assert_eq!(b"val1", txn.get(&db, b"key1").unwrap()); assert_eq!(b"val1", txn.get(db, b"key1").unwrap());
assert_eq!(b"val2", txn.get(&db, b"key2").unwrap()); assert_eq!(b"val2", txn.get(db, b"key2").unwrap());
assert_eq!(b"val3", txn.get(&db, b"key3").unwrap()); assert_eq!(b"val3", txn.get(db, b"key3").unwrap());
assert!(txn.get(&db, b"key").is_err()); assert!(txn.get(db, b"key").is_err());
txn.del(&db, b"key1", None).unwrap(); txn.del(db, b"key1", None).unwrap();
assert!(txn.get(&db, b"key1").is_err()); assert!(txn.get(db, b"key1").is_err());
} }
#[test] #[test]
@ -280,19 +255,19 @@ mod test {
}; };
// Check that database handles are reused properly // 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(); let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap();
txn.put(&db1, b"key1", b"val1", WriteFlags::empty()).unwrap(); txn.put(db1, b"key1", b"val1", WriteFlags::empty()).unwrap();
assert!(txn.commit().is_ok()); assert!(txn.commit().is_ok());
} }
unsafe { env.close_db(db1) }; unsafe { env.close_db(db1) };
{ {
let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap();
assert!(txn.put(&db1, b"key2", b"val2", WriteFlags::empty()).is_err()); 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 env = Arc::new(Environment::new().open(dir.path(), io::USER_RWX).unwrap());
let open_db_txn = env.begin_txn(MDB_RDONLY).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(); open_db_txn.commit().unwrap();
let n = 10u; // Number of concurrent readers let n = 10u; // Number of concurrent readers
@ -314,27 +289,26 @@ mod test {
for _ in range(0, n) { for _ in range(0, n) {
let reader_env = env.clone(); let reader_env = env.clone();
let reader_db = db.clone();
let reader_barrier = barrier.clone(); let reader_barrier = barrier.clone();
futures.push(Future::spawn(proc() { futures.push(Future::spawn(proc() {
{ {
let txn = reader_env.begin_txn(MDB_RDONLY).unwrap(); 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(); txn.abort();
} }
reader_barrier.wait(); reader_barrier.wait();
reader_barrier.wait(); reader_barrier.wait();
{ {
let txn = reader_env.begin_txn(MDB_RDONLY).unwrap(); 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(); barrier.wait();
txn.put(&*db, key, val, WriteFlags::empty()).unwrap(); txn.put(db, key, val, WriteFlags::empty()).unwrap();
txn.commit().unwrap(); txn.commit().unwrap();
barrier.wait(); barrier.wait();
@ -347,7 +321,7 @@ mod test {
let env = Arc::new(Environment::new().open(dir.path(), io::USER_RWX).unwrap()); let env = Arc::new(Environment::new().open(dir.path(), io::USER_RWX).unwrap());
let open_db_txn = env.begin_txn(MDB_RDONLY).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(); open_db_txn.commit().unwrap();
let n = 10u; // Number of concurrent writers let n = 10u; // Number of concurrent writers
@ -358,11 +332,10 @@ mod test {
for i in range(0, n) { for i in range(0, n) {
let writer_env = env.clone(); let writer_env = env.clone();
let writer_db = db.clone();
futures.push(Future::spawn(proc() { futures.push(Future::spawn(proc() {
let txn = writer_env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = writer_env.begin_txn(EnvironmentFlags::empty()).unwrap();
txn.put(&*writer_db, txn.put(db,
format!("{}{}", key, i).as_bytes(), format!("{}{}", key, i).as_bytes(),
format!("{}{}", val, i).as_bytes(), format!("{}{}", val, i).as_bytes(),
WriteFlags::empty()) WriteFlags::empty())
@ -377,7 +350,7 @@ mod test {
for i in range(0, n) { for i in range(0, n) {
assert_eq!( assert_eq!(
format!("{}{}", val, i).as_bytes(), format!("{}{}", val, i).as_bytes(),
txn.get(&*db, format!("{}{}", key, i).as_bytes()).unwrap()); txn.get(db, format!("{}{}", key, i).as_bytes()).unwrap());
} }
} }
} }

Loading…
Cancel
Save