From 9ab71e29b166eca702ee16f7d81c6bdbf10eaf82 Mon Sep 17 00:00:00 2001 From: Dan Burkert Date: Sun, 7 Dec 2014 14:23:00 -0800 Subject: [PATCH] First cut at a higher-level api --- lmdb-sys/Cargo.toml | 3 + lmdb-sys/build.rs | 2 +- lmdb-sys/mdb | 2 +- src/flags.rs => lmdb-sys/src/constants.rs | 109 +++++-- lmdb-sys/src/lib.rs | 204 +------------ src/cursor.rs | 290 +++++++++++-------- src/database.rs | 36 ++- src/environment.rs | 142 ++++++--- src/lib.rs | 12 +- src/transaction.rs | 337 ++++++++++++---------- 10 files changed, 596 insertions(+), 541 deletions(-) rename src/flags.rs => lmdb-sys/src/constants.rs (77%) diff --git a/lmdb-sys/Cargo.toml b/lmdb-sys/Cargo.toml index 807f983..e97f562 100644 --- a/lmdb-sys/Cargo.toml +++ b/lmdb-sys/Cargo.toml @@ -12,6 +12,9 @@ build = "build.rs" name = "lmdb-sys" +[dependencies.rust-bindgen] +rust-bindgen = "*" + [build-dependencies.pkg-config] pkg-config = "*" diff --git a/lmdb-sys/build.rs b/lmdb-sys/build.rs index 2a29122..dee22ab 100644 --- a/lmdb-sys/build.rs +++ b/lmdb-sys/build.rs @@ -13,6 +13,6 @@ fn main() { gcc::compile_library("liblmdb.a", &Default::default(), &[mdb.join("mdb.c").as_str().unwrap(), - mdb.join("midl.c").as_str().unwrap()]) + mdb.join("midl.c").as_str().unwrap()]); } } diff --git a/lmdb-sys/mdb b/lmdb-sys/mdb index 9a8eb95..9cc04f6 160000 --- a/lmdb-sys/mdb +++ b/lmdb-sys/mdb @@ -1 +1 @@ -Subproject commit 9a8eb95674c7b500cfe5f44d03493ff76c9fc0c1 +Subproject commit 9cc04f604f80f033d712f5f1faeb4ed97ca74f40 diff --git a/src/flags.rs b/lmdb-sys/src/constants.rs similarity index 77% rename from src/flags.rs rename to lmdb-sys/src/constants.rs index aa1e241..6fd7e7c 100644 --- a/src/flags.rs +++ b/lmdb-sys/src/constants.rs @@ -1,4 +1,4 @@ -use libc::c_uint; +use libc::{c_int, c_uint}; bitflags! { #[doc="Environment Options"] @@ -120,39 +120,39 @@ bitflags! { #[doc="Keys are strings to be compared in reverse order, from the end"] #[doc="of the strings to the beginning. By default, Keys are treated as strings and"] #[doc="compared from beginning to end."] - const MDB_REVERSEKEY = 0x02, + const MDB_REVERSEKEY = 0x02, // 2 #[doc="Duplicate keys may be used in the database. (Or, from another perspective,"] #[doc="keys may have multiple data items, stored in sorted order.) By default"] #[doc="keys must be unique and may have only a single data item."] - const MDB_DUPSORT = 0x04, + const MDB_DUPSORT = 0x04, // 4 #[doc="Keys are binary integers in native byte order. Setting this option"] #[doc="requires all keys to be the same size, typically sizeof(int)"] #[doc="or sizeof(size_t)."] - const MDB_INTEGERKEY = 0x08, + const MDB_INTEGERKEY = 0x08, // 8 #[doc="This flag may only be used in combination with `MDB_DUPSORT`. This option"] #[doc="tells the library that the data items for this database are all the same"] #[doc="size, which allows further optimizations in storage and retrieval. When"] #[doc="all data items are the same size, the `MDB_GET_MULTIPLE` and `MDB_NEXT_MULTIPLE`"] #[doc="cursor operations may be used to retrieve multiple items at once."] - const MDB_DUPFIXED = 0x10, + const MDB_DUPFIXED = 0x10, // 16 #[doc="This option specifies that duplicate data items are also integers, and"] #[doc="should be sorted as such."] - const MDB_INTEGERDUP = 0x20, + const MDB_INTEGERDUP = 0x20, // 32 #[doc="This option specifies that duplicate data items should be compared as"] #[doc="strings in reverse order."] - const MDB_REVERSEDUP = 0x40, - - #[doc="Create the named database if it doesn't exist. This option is not"] - #[doc="allowed in a read-only transaction or a read-only environment."] - const MDB_CREATE = 0x40000, + const MDB_REVERSEDUP = 0x40, // 64 } } +/// Create the named database if it doesn't exist. This option is not +/// allowed in a read-only transaction or a read-only environment. +pub const MDB_CREATE: c_uint = 0x40000; + bitflags! { #[doc="Write Options"] #[deriving(Show)] @@ -180,15 +180,6 @@ bitflags! { #[doc="record followed by an insert."] const MDB_CURRENT = 0x40, - #[doc="Reserve space for data of the given size, but"] - #[doc="don't copy the given data. Instead, return a pointer to the"] - #[doc="reserved space, which the caller can fill in later - before"] - #[doc="the next update operation or the transaction ends. This saves"] - #[doc="an extra memcpy if the data is being generated later."] - #[doc="LMDB does nothing else with this memory, the caller is expected"] - #[doc="to modify all of the space requested."] - const MDB_RESERVE = 0x10000, - #[doc="Append the given key/data pair to the end of the"] #[doc="database. No key comparisons are performed. This option allows"] #[doc="fast bulk loading when keys are already known to be in the"] @@ -200,3 +191,81 @@ bitflags! { const MDB_APPENDDUP = 0x40000, } } + +/// Reserve space for data of the given size, but don't copy the given data. Instead, return a +/// pointer to the reserved space, which the caller can fill in later - before the next update +/// operation or the transaction ends. This saves an extra memcpy if the data is being generated +/// later. LMDB does nothing else with this memory, the caller is expected to modify all of the +/// space requested. +pub const MDB_RESERVE: c_uint = 0x10000; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +//// Return Codes +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Successful result. +pub const MDB_SUCCESS: c_int = 0; + +/// key/data pair already exists. +pub const MDB_KEYEXIST: c_int = -30799; + +/// key/data pair not found (EOF). +pub const MDB_NOTFOUND: c_int = -30798; + +/// Requested page not found - this usually indicates corruption. +pub const MDB_PAGE_NOTFOUND: c_int = -30797; + +/// Located page was wrong type. +pub const MDB_CORRUPTED: c_int = -30796; + +/// Update of meta page failed or environment had fatal error. +pub const MDB_PANIC : c_int = -30795; + +/// Environment version mismatch. +pub const MDB_VERSION_MISMATCH: c_int = -30794; + +/// File is not a valid LMDB file. +pub const MDB_INVALID: c_int = -30793; + +/// Environment mapsize reached. +pub const MDB_MAP_FULL: c_int = -30792; + +/// Environment maxdbs reached. +pub const MDB_DBS_FULL: c_int = -30791; + +/// Environment maxreaders reached. +pub const MDB_READERS_FULL: c_int = -30790; + +/// Too many TLS keys in use - Windows only. +pub const MDB_TLS_FULL: c_int = -30789; + +/// Txn has too many dirty pages. +pub const MDB_TXN_FULL: c_int = -30788; + +/// Cursor stack too deep - internal error. +pub const MDB_CURSOR_FULL: c_int = -30787; + +/// Page has not enough space - internal error. +pub const MDB_PAGE_FULL: c_int = -30786; + +/// Database contents grew beyond environment mapsize. +pub const MDB_MAP_RESIZED: c_int = -30785; + +/// MDB_INCOMPATIBLE: Operation and DB incompatible, or DB flags changed. +pub const MDB_INCOMPATIBLE: c_int = -30784; + +/// Invalid reuse of reader locktable slot. +pub const MDB_BAD_RSLOT: c_int = -30783; + +/// Transaction cannot recover - it must be aborted. +pub const MDB_BAD_TXN: c_int = -30782; + +/// Unsupported size of key/DB name/data, or wrong DUPFIXED size. +pub const MDB_BAD_VALSIZE: c_int = -30781; + +/// The specified DBI was changed unexpectedly. +pub const MDB_BAD_DBI: c_int = -30780; + +/// The last defined error code. +pub const MDB_LAST_ERRCODE: c_int = MDB_BAD_DBI; diff --git a/lmdb-sys/src/lib.rs b/lmdb-sys/src/lib.rs index 02fcc99..31c461d 100644 --- a/lmdb-sys/src/lib.rs +++ b/lmdb-sys/src/lib.rs @@ -1,202 +1,12 @@ -//! Provides extern declarations for `liblmdb`. Additionally, this crate provides `liblmdb` as a -//! native Cargo dependency. +#![allow(dead_code, uppercase_variables, non_camel_case_types)] +#![feature(phase,globs)] -#![allow(non_camel_case_types)] +#[phase(plugin)] extern crate bindgen; extern crate libc; -use libc::{c_int, c_uint, c_void, c_char, size_t}; +use libc::{size_t, mode_t}; +pub use constants::*; -pub type mdb_mode_t = libc::mode_t; -pub type mdb_filehandle_t = libc::c_int; +mod constants; -pub type MDB_dbi = c_uint; - -pub type MDB_rel_func = extern fn(*mut MDB_val, *mut c_void, *mut c_void, *mut c_void); -pub type MDB_msg_func = extern fn(*const c_char, *mut c_void) -> c_int; -pub type MDB_cmp_func = extern fn(*const MDB_val, *const MDB_val) -> c_int; - -#[repr(C)] -pub struct MDB_val { - pub mv_size: size_t, - pub mv_data: *mut c_void, -} - -#[repr(C)] -pub struct MDB_env; - -#[repr(C)] -pub struct MDB_txn; - -#[repr(C)] -pub struct MDB_cursor; - -#[repr(C)] -pub struct MDB_stat { - ms_psize: c_uint, - ms_depth: c_uint, - ms_branch_pages: size_t, - ms_leaf_pages: size_t, - ms_overflow_pages: size_t, - ms_entries: size_t -} - -#[repr(C)] -pub struct MDB_envinfo { - me_mapaddr: *const c_void, - me_mapsize: size_t, - me_last_pgno: size_t, - me_last_txnid: size_t, - me_maxreaders: c_uint, - me_numreaders: c_uint -} - -#[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, - /// Position at first key greater than or equal to specified key. - MDB_SET_RANGE, -} - -// Return codes -pub const MDB_SUCCESS: c_int = 0; -pub const MDB_KEYEXIST: c_int = -30799; -pub const MDB_NOTFOUND: c_int = -30798; -pub const MDB_PAGE_NOTFOUND: c_int = -30797; -pub const MDB_CORRUPTED: c_int = -30796; -pub const MDB_PANIC: c_int = -30795; -pub const MDB_VERSION_MISMATCH: c_int = -30794; -pub const MDB_INVALID: c_int = -30793; -pub const MDB_MAP_FULL: c_int = -30792; -pub const MDB_DBS_FULL: c_int = -30791; -pub const MDB_READERS_FULL: c_int = -30790; -pub const MDB_TLS_FULL: c_int = -30789; -pub const MDB_TXN_FULL: c_int = -30788; -pub const MDB_CURSOR_FULL: c_int = -30787; -pub const MDB_PAGE_FULL: c_int = -30786; -pub const MDB_MAP_RESIZED: c_int = -30785; -pub const MDB_INCOMPATIBLE: c_int = -30784; -pub const MDB_BAD_RSLOT: c_int = -30783; -pub const MDB_BAD_TXN: c_int = -30782; -pub const MDB_BAD_VALSIZE: c_int = -30781; -pub const MDB_BAD_DBI: c_int = -30780; - -// Write flags -pub const MDB_NOOVERWRITE: c_uint = 0x10; -pub const MDB_NODUPDATA: c_uint = 0x20; -pub const MDB_CURRENT: c_uint = 0x40; -pub const MDB_RESERVE: c_uint = 0x10000; -pub const MDB_APPEND: c_uint = 0x20000; -pub const MDB_APPENDDUP: c_uint = 0x40000; -pub const MDB_MULTIPLE: c_uint = 0x80000; - -// Database flags -pub const MDB_REVERSEKEY: c_uint = 0x02; -pub const MDB_DUPSORT: c_uint = 0x04; -pub const MDB_INTEGERKEY: c_uint = 0x08; -pub const MDB_DUPFIXED: c_uint = 0x10; -pub const MDB_INTEGERDUP: c_uint = 0x20; -pub const MDB_REVERSEDUP: c_uint = 0x40; -pub const MDB_CREATE: c_uint = 0x40000; - -// Environment flags -pub const MDB_FIXEDMAP: c_uint = 0x01; -pub const MDB_NOSUBDIR: c_uint = 0x4000; -pub const MDB_NOSYNC: c_uint = 0x10000; -pub const MDB_RDONLY: c_uint = 0x20000; -pub const MDB_NOMETASYNC: c_uint = 0x40000; -pub const MDB_WRITEMAP: c_uint = 0x80000; -pub const MDB_MAPASYNC: c_uint = 0x100000; -pub const MDB_NOTLS: c_uint = 0x200000; -pub const MDB_NOLOCK: c_uint = 0x400000; -pub const MDB_NORDAHEAD: c_uint = 0x800000; -pub const MDB_NOMEMINIT: c_uint = 0x1000000; - -extern { - pub fn mdb_version(major: *mut c_int, minor: *mut c_int, patch: *mut c_int) -> *mut c_char; - pub fn mdb_strerror(err: c_int) -> *mut c_char; - pub fn mdb_env_create(env: *mut *mut MDB_env) -> c_int; - pub fn mdb_env_open(env: *mut MDB_env, path: *const c_char, flags: c_uint, mode: mdb_mode_t) -> c_int; - pub fn mdb_env_copy(env: *mut MDB_env, path: *const c_char) -> c_int; - pub fn mdb_env_copyfd(env: *mut MDB_env, fd: mdb_filehandle_t) -> c_int; - pub fn mdb_env_copy2(env: *mut MDB_env, path: *const c_char, flags: c_uint) -> c_int; - pub fn mdb_env_copyfd2(env: *mut MDB_env, fd: mdb_filehandle_t, flags: c_uint) -> c_int; - pub fn mdb_env_stat(env: *mut MDB_env, stat: *mut MDB_stat) -> c_int; - pub fn mdb_env_info(env: *mut MDB_env, stat: *mut MDB_envinfo) -> c_int; - pub fn mdb_env_sync(env: *mut MDB_env, force: c_int) -> c_int; - pub fn mdb_env_close(env: *mut MDB_env); - pub fn mdb_env_set_flags(env: *mut MDB_env, flags: c_uint, onoff: c_int) -> c_int; - pub fn mdb_env_get_flags(env: *mut MDB_env, flags: *mut c_uint) -> c_int; - pub fn mdb_env_get_path(env: *mut MDB_env, path: *const *const c_char) -> c_int; - pub fn mdb_env_get_fd(env: *mut MDB_env, fd: *mut mdb_filehandle_t) -> c_int; - pub fn mdb_env_set_mapsize(env: *mut MDB_env, size: size_t) -> c_int; - pub fn mdb_env_set_maxreaders(env: *mut MDB_env, readers: c_uint) -> c_int; - pub fn mdb_env_get_maxreaders(env: *mut MDB_env, readers: *mut c_uint) -> c_int; - pub fn mdb_env_set_maxdbs(env: *mut MDB_env, dbs: MDB_dbi) -> c_int; - pub fn mdb_env_get_maxkeysize(env: *mut MDB_env) -> c_int; - pub fn mdb_txn_begin(env: *mut MDB_env, parent: *mut MDB_txn, flags: c_uint, txn: *mut *mut MDB_txn) -> c_int; - pub fn mdb_txn_env(txn: *mut MDB_txn) -> *mut MDB_env; - pub fn mdb_txn_commit(txn: *mut MDB_txn) -> c_int; - pub fn mdb_txn_abort(txn: *mut MDB_txn); - pub fn mdb_txn_reset(txn: *mut MDB_txn); - pub fn mdb_txn_renew(txn: *mut MDB_txn) -> c_int; - pub fn mdb_dbi_open(txn: *mut MDB_txn, name: *const c_char, flags: c_uint, dbi: *mut MDB_dbi) -> c_int; - pub fn mdb_stat(txn: *mut MDB_txn, dbi: MDB_dbi, stat: *mut MDB_stat) -> c_int; - pub fn mdb_dbi_flags(txn: *mut MDB_txn, dbi: MDB_dbi, flags: *mut c_uint) -> c_int; - pub fn mdb_dbi_close(txn: *mut MDB_env, dbi: MDB_dbi); - pub fn mdb_drop(txn: *mut MDB_txn, dbi: MDB_dbi, del: c_int) -> c_int; - pub fn mdb_set_compare(txn: *mut MDB_txn, dbi: MDB_dbi, cmp: *mut MDB_cmp_func) -> c_int; - pub fn mdb_set_dupsort(txn: *mut MDB_txn, dbi: MDB_dbi, cmp: *mut MDB_cmp_func) -> c_int; - pub fn mdb_set_relfunc(txn: *mut MDB_txn, dbi: MDB_dbi, rel: *mut MDB_rel_func) -> c_int; - pub fn mdb_set_relctx(txn: *mut MDB_txn, dbi: MDB_dbi, ctx: *mut c_void) -> c_int; - pub fn mdb_get(txn: *mut MDB_txn, dbi: MDB_dbi, key: *mut MDB_val, data: *mut MDB_val) -> c_int; - pub fn mdb_put(txn: *mut MDB_txn, dbi: MDB_dbi, key: *mut MDB_val, data: *mut MDB_val, flags: c_uint) -> c_int; - pub fn mdb_del(txn: *mut MDB_txn, dbi: MDB_dbi, key: *mut MDB_val, data: *mut MDB_val) -> c_int; - pub fn mdb_cursor_open(txn: *mut MDB_txn, dbi: MDB_dbi, cursor: *mut *mut MDB_cursor) -> c_int; - pub fn mdb_cursor_close(cursor: *mut MDB_cursor); - pub fn mdb_cursor_renew(txn: *mut MDB_txn, cursor: *mut MDB_cursor) -> c_int; - pub fn mdb_cursor_txn(cursor: *mut MDB_cursor) -> *mut MDB_txn; - pub fn mdb_cursor_dbi(cursor: *mut MDB_cursor) -> MDB_dbi; - pub fn mdb_cursor_get(cursor: *mut MDB_cursor, key: *mut MDB_val, data: *mut MDB_val, op: MDB_cursor_op) -> c_int; - pub fn mdb_cursor_put(cursor: *mut MDB_cursor, key: *mut MDB_val, data: *mut MDB_val, flags: c_uint) -> c_int; - pub fn mdb_cursor_del(cursor: *mut MDB_cursor, flags: c_uint) -> c_int; - pub fn mdb_cursor_count(cursor: *mut MDB_cursor, countp: *mut size_t) -> c_int; - pub fn mdb_cmp(txn: *mut MDB_txn, dbi: MDB_dbi, a: *const MDB_val, b: *const MDB_val) -> c_int; - pub fn mdb_dcmp(txn: *mut MDB_txn, dbi: MDB_dbi, a: *const MDB_val, b: *const MDB_val) -> c_int; - pub fn mdb_reader_list(env: *mut MDB_env, func: *mut MDB_msg_func, ctx: *mut c_void) -> c_int; - pub fn mdb_reader_check(env: *mut MDB_env, dead: *mut c_int) -> c_int; -} +bindgen!("../mdb/libraries/liblmdb/lmdb.h", match="lmdb.h", link="lmdb") diff --git a/src/cursor.rs b/src/cursor.rs index 023256a..11a09b0 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,61 +1,36 @@ -use libc::{c_void, size_t}; +use libc::{c_void, size_t, c_uint}; 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 flags::WriteFlags; +use ffi::{MDB_cursor, mdb_cursor_open, MDB_val, WriteFlags}; use transaction::Transaction; -/// A cursor for navigating within a database. -pub struct Cursor<'txn> { - cursor: *mut MDB_cursor, - _no_sync: marker::NoSync, - _no_send: marker::NoSend, - _contravariant: marker::ContravariantLifetime<'txn>, -} -#[unsafe_destructor] -impl <'txn> Drop for Cursor<'txn> { - fn drop(&mut self) { - unsafe { ffi::mdb_cursor_close(self.cursor) } - } +/// An LMDB cursor. +pub trait Cursor<'txn> { + /// Returns a raw pointer to the underlying LMDB cursor. + /// + /// The caller **must** ensure that the pointer is not used after the lifetime of the cursor. + fn cursor(&self) -> *mut MDB_cursor; } -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, - _no_sync: marker::NoSync, - _no_send: marker::NoSend, - _contravariant: marker::ContravariantLifetime::<'txn>, - }) - } - - pub fn cursor(&self) -> *mut MDB_cursor { - self.cursor - } +pub trait ReadCursor<'txn> : Cursor<'txn> { /// Retrieves a key/data pair from the cursor. Depending on the cursor op, the current key is /// returned. - pub fn get(&self, + fn get(&self, key: Option<&[u8]>, data: Option<&[u8]>, - op: MDB_cursor_op) + op: c_uint) -> 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, + try!(lmdb_result(ffi::mdb_cursor_get(self.cursor(), &mut key_val, &mut data_val, op))); @@ -64,14 +39,17 @@ impl <'txn> Cursor<'txn> { Ok((key_out, data_out)) } } +} + +pub trait WriteCursor<'txn> : ReadCursor<'txn> { /// Puts a key/data pair into the database. The cursor will be positioned at the new data item, /// or on failure usually near it. - pub fn put(&self, - key: &[u8], - data: &[u8], - flags: WriteFlags) - -> LmdbResult<()> { + fn put(&self, + key: &[u8], + data: &[u8], + flags: WriteFlags) + -> LmdbResult<()> { let mut key_val: ffi::MDB_val = ffi::MDB_val { mv_size: key.len() as size_t, mv_data: key.as_ptr() as *mut c_void }; @@ -92,13 +70,94 @@ impl <'txn> Cursor<'txn> { /// /// `MDB_NODUPDATA` may be used to delete all data items for the current key, if the database /// was opened with `MDB_DUPSORT`. - pub fn del(&self, flags: WriteFlags) -> LmdbResult<()> { + fn del(&self, flags: WriteFlags) -> LmdbResult<()> { unsafe { lmdb_result(ffi::mdb_cursor_del(self.cursor(), flags.bits())) } } } +/// A read-only cursor for navigating items within a database. +pub struct RoCursor<'txn> { + cursor: *mut MDB_cursor, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime<'txn>, +} + +impl <'txn> Cursor<'txn> for RoCursor<'txn> { + fn cursor(&self) -> *mut MDB_cursor { + self.cursor + } +} + +impl <'txn> ReadCursor<'txn> for RoCursor<'txn> { } + +#[unsafe_destructor] +impl <'txn> Drop for RoCursor<'txn> { + fn drop(&mut self) { + unsafe { ffi::mdb_cursor_close(self.cursor) } + } +} + +impl <'txn> RoCursor<'txn> { + + /// Creates a new read-only cursor in the given database and 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(RoCursor { + cursor: cursor, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime::<'txn>, + }) + } +} + +/// A read-only cursor for navigating items within a database. +pub struct RwCursor<'txn> { + cursor: *mut MDB_cursor, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime<'txn>, +} + +impl <'txn> Cursor<'txn> for RwCursor<'txn> { + fn cursor(&self) -> *mut MDB_cursor { + self.cursor + } +} + +impl <'txn> ReadCursor<'txn> for RwCursor<'txn> { } +impl <'txn> WriteCursor<'txn> for RwCursor<'txn> { } + +#[unsafe_destructor] +impl <'txn> Drop for RwCursor<'txn> { + fn drop(&mut self) { + unsafe { ffi::mdb_cursor_close(self.cursor) } + } +} + +impl <'txn> RwCursor<'txn> { + + /// Creates a new read-only cursor in the given database and transaction. Prefer using + /// `WriteTransaction::open_write_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(RwCursor { + cursor: cursor, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime::<'txn>, + }) + } +} + unsafe fn slice_to_val(slice: Option<&[u8]>) -> MDB_val { match slice { Some(slice) => @@ -120,53 +179,52 @@ unsafe fn val_to_slice<'a>(val: MDB_val) -> &'a [u8] { #[cfg(test)] mod test { - use libc::{c_void, size_t}; + use libc::{c_void, size_t, c_uint}; use std::{io, ptr}; + use transaction::*; use environment::*; use error::{LmdbResult, lmdb_result}; - use flags::*; - use ffi; - use ffi::{MDB_cursor_op, MDB_val}; + use ffi::*; 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 db = env.open_db(None).unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); + let mut txn = env.begin_write_txn().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(); + let cursor = txn.open_read_cursor(db).unwrap(); assert_eq!((Some(b"key1"), b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_FIRST).unwrap()); + cursor.get(None, None, MDB_FIRST).unwrap()); assert_eq!((Some(b"key1"), b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_GET_CURRENT).unwrap()); + cursor.get(None, None, MDB_GET_CURRENT).unwrap()); assert_eq!((Some(b"key2"), b"val2"), - cursor.get(None, None, MDB_cursor_op::MDB_NEXT).unwrap()); + cursor.get(None, None, MDB_NEXT).unwrap()); assert_eq!((Some(b"key1"), b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_PREV).unwrap()); + cursor.get(None, None, MDB_PREV).unwrap()); assert_eq!((Some(b"key3"), b"val3"), - cursor.get(None, None, MDB_cursor_op::MDB_LAST).unwrap()); + cursor.get(None, None, MDB_LAST).unwrap()); assert_eq!((None, b"val2"), - cursor.get(Some(b"key2"), None, MDB_cursor_op::MDB_SET).unwrap()); + cursor.get(Some(b"key2"), None, MDB_SET).unwrap()); assert_eq!((Some(b"key3"), b"val3"), - cursor.get(Some(b"key3"), None, MDB_cursor_op::MDB_SET_KEY).unwrap()); + cursor.get(Some(b"key3"), None, 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()); + cursor.get(Some(b"key2\0"), None, 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 db = env.create_db(None, MDB_DUPSORT).unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(None, MDB_DUPSORT).unwrap(); + let mut txn = env.begin_write_txn().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(); @@ -174,45 +232,45 @@ mod test { 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(); + let cursor = txn.open_read_cursor(db).unwrap(); assert_eq!((Some(b"key1"), b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_FIRST).unwrap()); + cursor.get(None, None, MDB_FIRST).unwrap()); assert_eq!((None, b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_FIRST_DUP).unwrap()); + cursor.get(None, None, MDB_FIRST_DUP).unwrap()); assert_eq!((Some(b"key1"), b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_GET_CURRENT).unwrap()); + cursor.get(None, None, MDB_GET_CURRENT).unwrap()); assert_eq!((Some(b"key2"), b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_NEXT_NODUP).unwrap()); + cursor.get(None, None, MDB_NEXT_NODUP).unwrap()); assert_eq!((Some(b"key2"), b"val2"), - cursor.get(None, None, MDB_cursor_op::MDB_NEXT_DUP).unwrap()); + cursor.get(None, None, 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()); + cursor.get(None, None, MDB_NEXT_DUP).unwrap()); + assert!(cursor.get(None, None, MDB_NEXT_DUP).is_err()); assert_eq!((Some(b"key2"), b"val2"), - cursor.get(None, None, MDB_cursor_op::MDB_PREV_DUP).unwrap()); + cursor.get(None, None, MDB_PREV_DUP).unwrap()); assert_eq!((None, b"val3"), - cursor.get(None, None, MDB_cursor_op::MDB_LAST_DUP).unwrap()); + cursor.get(None, None, MDB_LAST_DUP).unwrap()); assert_eq!((Some(b"key1"), b"val3"), - cursor.get(None, None, MDB_cursor_op::MDB_PREV_NODUP).unwrap()); + cursor.get(None, None, MDB_PREV_NODUP).unwrap()); assert_eq!((None, b"val1"), - cursor.get(Some(b"key1"), None, MDB_cursor_op::MDB_SET).unwrap()); + cursor.get(Some(b"key1"), None, MDB_SET).unwrap()); assert_eq!((Some(b"key2"), b"val1"), - cursor.get(Some(b"key2"), None, MDB_cursor_op::MDB_SET_KEY).unwrap()); + cursor.get(Some(b"key2"), None, 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()); + cursor.get(Some(b"key1\0"), None, MDB_SET_RANGE).unwrap()); assert_eq!((None, b"val3"), - cursor.get(Some(b"key1"), Some(b"val3"), MDB_cursor_op::MDB_GET_BOTH).unwrap()); + cursor.get(Some(b"key1"), Some(b"val3"), 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()); + cursor.get(Some(b"key2"), Some(b"val"), 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 db = env.create_db(None, MDB_DUPSORT | MDB_DUPFIXED).unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(None, MDB_DUPSORT | MDB_DUPFIXED).unwrap(); + let mut txn = env.begin_write_txn().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(); @@ -220,12 +278,12 @@ mod test { 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(); + let cursor = txn.open_read_cursor(db).unwrap(); assert_eq!((Some(b"key1"), b"val1"), - cursor.get(None, None, MDB_cursor_op::MDB_FIRST).unwrap()); + cursor.get(None, None, 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()); + cursor.get(None, None, MDB_GET_MULTIPLE).unwrap()); + assert!(cursor.get(None, None, MDB_NEXT_MULTIPLE).is_err()); } /// Checks assumptions about which get operations return keys. @@ -249,22 +307,22 @@ mod test { fn sets_key(cursor: &Cursor, key: Option<&[u8]>, data: Option<&[u8]>, - op: MDB_cursor_op) + op: c_uint) -> 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))); + try!(lmdb_result(mdb_cursor_get(cursor.cursor(), + &mut key_val, + &mut data_val, + op))); Ok(key_ptr != key_val.mv_data) } } + let db = env.create_db(None, MDB_DUPSORT | MDB_DUPFIXED).unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(None, MDB_DUPSORT | MDB_DUPFIXED).unwrap(); + let mut txn = env.begin_write_txn().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(); @@ -275,48 +333,48 @@ mod test { 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()); + let cursor = txn.open_read_cursor(db).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_FIRST).unwrap()); + assert!(!sets_key(&cursor, None, None, MDB_FIRST_DUP).unwrap()); + assert!(!sets_key(&cursor, Some(b"key2"), Some(b"val5"), MDB_GET_BOTH).unwrap()); + assert!(!sets_key(&cursor, Some(b"key2"), Some(b"val"), MDB_GET_BOTH_RANGE).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_GET_CURRENT).unwrap()); + assert!(!sets_key(&cursor, None, None, MDB_GET_MULTIPLE).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_LAST).unwrap()); + assert!(!sets_key(&cursor, None, None, MDB_LAST_DUP).unwrap()); + sets_key(&cursor, None, None, MDB_FIRST).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_NEXT).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_NEXT_DUP).unwrap()); + sets_key(&cursor, None, None, MDB_FIRST).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_NEXT_MULTIPLE).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_NEXT_NODUP).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_PREV).unwrap()); + sets_key(&cursor, None, None, MDB_LAST).unwrap(); + assert!(sets_key(&cursor, None, None, MDB_PREV_DUP).unwrap()); + assert!(sets_key(&cursor, None, None, MDB_PREV_NODUP).unwrap()); + assert!(!sets_key(&cursor, Some(b"key2"), None, MDB_SET).unwrap()); + assert!(sets_key(&cursor, Some(b"key2"), None, MDB_SET_KEY).unwrap()); + assert!(sets_key(&cursor, Some(b"key2"), None, MDB_SET_RANGE).unwrap()); } #[test] fn test_put_del() { let dir = io::TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + let db = env.open_db(None).unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); - let cursor = txn.open_cursor(db).unwrap(); + let mut txn = env.begin_write_txn().unwrap(); + let cursor = txn.open_write_cursor(db).unwrap(); cursor.put(b"key1", b"val1", WriteFlags::empty()).unwrap(); cursor.put(b"key2", b"val2", WriteFlags::empty()).unwrap(); cursor.put(b"key3", b"val3", WriteFlags::empty()).unwrap(); assert_eq!((Some(b"key3"), b"val3"), - cursor.get(None, None, MDB_cursor_op::MDB_GET_CURRENT).unwrap()); + cursor.get(None, None, MDB_GET_CURRENT).unwrap()); cursor.del(WriteFlags::empty()).unwrap(); assert_eq!((Some(b"key2"), b"val2"), - cursor.get(None, None, MDB_cursor_op::MDB_LAST).unwrap()); + cursor.get(None, None, MDB_LAST).unwrap()); } } diff --git a/src/database.rs b/src/database.rs index 5948ac6..0e86df7 100644 --- a/src/database.rs +++ b/src/database.rs @@ -2,37 +2,43 @@ 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; +use ffi::*; +use transaction::{Transaction, ReadTransaction, WriteTransaction}; /// 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). +#[deriving(Clone, Copy)] 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> { + pub unsafe fn open<'env>(txn: &ReadTransaction<'env>, + name: Option<&str>) + -> 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; + try!(lmdb_result(mdb_dbi_open(txn.txn(), name_ptr, 0, &mut dbi))); + Ok(Database { dbi: dbi, + _marker: marker::ContravariantLifetime::<'env> }) + } + + pub unsafe fn create<'env>(txn: &WriteTransaction<'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> }) + try!(lmdb_result(mdb_dbi_open(txn.txn(), name_ptr, flags.bits() | MDB_CREATE, &mut dbi))); + Ok(Database { dbi: dbi, + _marker: marker::ContravariantLifetime::<'env> }) } /// Returns the underlying LMDB database handle. diff --git a/src/environment.rs b/src/environment.rs index 01dbc0a..285cc73 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -1,19 +1,19 @@ use libc::{c_uint, size_t, mode_t}; use std::io::FilePermission; use std::ptr; +use std::sync::Mutex; +use error::{LmdbResult, lmdb_result}; use database::Database; -use error::{LmdbError, LmdbResult, lmdb_result}; -use ffi; -use ffi::MDB_env; -use flags::EnvironmentFlags; -use transaction::Transaction; +use ffi::*; +use transaction::{RoTransaction, RwTransaction, Transaction, TransactionExt}; /// An LMDB environment. /// /// An environment supports multiple databases, all residing in the same shared-memory map. pub struct Environment { env: *mut MDB_env, + dbi_open_mutex: Mutex<()>, } impl Environment { @@ -36,11 +36,65 @@ impl Environment { self.env } - /// Create a transaction for use with the environment. + /// Opens a handle to an LMDB database. /// - /// `flags` must either be empty, or `MDB_RDONLY` in order to specify a read-only transaction. - pub fn begin_txn<'a>(&'a self, flags: EnvironmentFlags) -> LmdbResult> { - Transaction::new(self, flags) + /// 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 transactions. + pub fn open_db<'env>(&'env self, name: Option<&str>) -> LmdbResult> { + let mutex = self.dbi_open_mutex.lock(); + let txn = try!(self.begin_read_txn()); + let db = unsafe { try!(Database::open(&txn, name)) }; + try!(txn.commit()); + drop(mutex); + Ok(db) + } + + /// Opens a handle to an LMDB database, opening 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 transactions. + pub fn create_db<'env>(&'env self, + name: Option<&str>, + flags: DatabaseFlags) + -> LmdbResult> { + let mutex = self.dbi_open_mutex.lock(); + let txn = try!(self.begin_write_txn()); + let db = unsafe { try!(Database::create(&txn, name, flags)) }; + try!(txn.commit()); + drop(mutex); + Ok(db) + } + + pub fn get_db_flags<'env>(&'env self, db: Database<'env>) -> LmdbResult { + let txn = try!(self.begin_read_txn()); + let mut flags: c_uint = 0; + unsafe { + try!(lmdb_result(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. + pub fn begin_read_txn<'env>(&'env self) -> LmdbResult> { + 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. + pub fn begin_write_txn<'env>(&'env self) -> LmdbResult> { + RwTransaction::new(self) } /// Flush data buffers to disk. @@ -50,7 +104,7 @@ impl Environment { /// the environment was opened with `MDB_NOSYNC` or in part `MDB_NOMETASYNC`. pub fn sync(&self, force: bool) -> LmdbResult<()> { unsafe { - lmdb_result(ffi::mdb_env_sync(self.env(), if force { 1 } else { 0 })) + lmdb_result(mdb_env_sync(self.env(), if force { 1 } else { 0 })) } } @@ -66,13 +120,13 @@ impl Environment { /// handle value. Usually it's better to set a bigger `EnvironmentBuilder::set_max_dbs`, unless /// that value would be large. pub unsafe fn close_db(&self, db: Database) { - ffi::mdb_dbi_close(self.env, db.dbi()) + mdb_dbi_close(self.env, db.dbi()) } } impl Drop for Environment { fn drop(&mut self) { - unsafe { ffi::mdb_env_close(self.env) } + unsafe { mdb_env_close(self.env) } } } @@ -81,7 +135,7 @@ impl Drop for Environment { /////////////////////////////////////////////////////////////////////////////////////////////////// /// Options for opening or creating an environment. -#[deriving(Show, PartialEq, Eq)] +#[deriving(Show, PartialEq, Eq, Copy, Clone)] pub struct EnvironmentBuilder { flags: EnvironmentFlags, max_readers: Option, @@ -95,26 +149,27 @@ impl EnvironmentBuilder { pub fn open(&self, path: &Path, mode: FilePermission) -> LmdbResult { let mut env: *mut MDB_env = ptr::null_mut(); unsafe { - lmdb_try!(ffi::mdb_env_create(&mut env)); + lmdb_try!(mdb_env_create(&mut env)); if let Some(max_readers) = self.max_readers { - lmdb_try_with_cleanup!(ffi::mdb_env_set_maxreaders(env, max_readers), - ffi::mdb_env_close(env)) + lmdb_try_with_cleanup!(mdb_env_set_maxreaders(env, max_readers), + mdb_env_close(env)) } if let Some(max_dbs) = self.max_dbs { - lmdb_try_with_cleanup!(ffi::mdb_env_set_maxdbs(env, max_dbs), - ffi::mdb_env_close(env)) + lmdb_try_with_cleanup!(mdb_env_set_maxdbs(env, max_dbs), + mdb_env_close(env)) } if let Some(map_size) = self.map_size { - lmdb_try_with_cleanup!(ffi::mdb_env_set_mapsize(env, map_size), - ffi::mdb_env_close(env)) + lmdb_try_with_cleanup!(mdb_env_set_mapsize(env, map_size), + mdb_env_close(env)) } - lmdb_try_with_cleanup!(ffi::mdb_env_open(env, + lmdb_try_with_cleanup!(mdb_env_open(env, path.to_c_str().as_ptr(), self.flags.bits(), mode.bits() as mode_t), - ffi::mdb_env_close(env)); + mdb_env_close(env)); } - Ok(Environment { env: env }) + Ok(Environment { env: env, + dbi_open_mutex: Mutex::new(()) }) } pub fn set_flags(&mut self, flags: EnvironmentFlags) -> &mut EnvironmentBuilder { @@ -169,7 +224,7 @@ mod test { use std::io; - use flags; + use ffi::*; use super::*; #[test] @@ -177,7 +232,7 @@ mod test { let dir = io::TempDir::new("test").unwrap(); // opening non-existent env with read-only should fail - assert!(Environment::new().set_flags(flags::MDB_RDONLY) + assert!(Environment::new().set_flags(MDB_RDONLY) .open(dir.path(), io::USER_RWX) .is_err()); @@ -185,7 +240,7 @@ mod test { assert!(Environment::new().open(dir.path(), io::USER_RWX).is_ok()); // opening env with read-only should not fail - assert!(Environment::new().set_flags(flags::MDB_RDONLY) + assert!(Environment::new().set_flags(MDB_RDONLY) .open(dir.path(), io::USER_RWX) .is_ok()); } @@ -197,25 +252,46 @@ mod test { { // Mutable env, mutable txn - assert!(env.begin_txn(flags::EnvironmentFlags::empty()).is_ok()); + assert!(env.begin_write_txn().is_ok()); } { // Mutable env, read-only txn - assert!(env.begin_txn(flags::MDB_RDONLY).is_ok()); + assert!(env.begin_read_txn().is_ok()); } { // Read-only env, mutable txn - let env = Environment::new().set_flags(flags::MDB_RDONLY) + let env = Environment::new().set_flags(MDB_RDONLY) .open(dir.path(), io::USER_RWX) .unwrap(); - assert!(env.begin_txn(flags::EnvironmentFlags::empty()).is_err()); + assert!(env.begin_write_txn().is_err()); } { // Read-only env, read-only txn - let env = Environment::new().set_flags(flags::MDB_RDONLY) + let env = Environment::new().set_flags(MDB_RDONLY) .open(dir.path(), io::USER_RWX) .unwrap(); - assert!(env.begin_txn(flags::MDB_RDONLY).is_ok()); + assert!(env.begin_read_txn().is_ok()); } } + #[test] + fn test_open_db() { + let dir = io::TempDir::new("test").unwrap(); + let env = Environment::new().set_max_dbs(10) + .open(dir.path(), io::USER_RWX) + .unwrap(); + assert!(env.open_db(None).is_ok()); + assert!(env.open_db(Some("testdb")).is_err()); + } + + #[test] + fn test_create_db() { + let dir = io::TempDir::new("test").unwrap(); + let env = Environment::new().set_max_dbs(10) + .open(dir.path(), io::USER_RWX) + .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_sync() { let dir = io::TempDir::new("test").unwrap(); @@ -223,7 +299,7 @@ mod test { let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); assert!(env.sync(true).is_ok()); } { - let env = Environment::new().set_flags(flags::MDB_RDONLY) + let env = Environment::new().set_flags(MDB_RDONLY) .open(dir.path(), io::USER_RWX) .unwrap(); assert!(env.sync(true).is_ok()); diff --git a/src/lib.rs b/src/lib.rs index dd4ffe3..d9c61d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,13 +14,13 @@ pub use cursor::Cursor; pub use database::Database; pub use environment::{Environment, EnvironmentBuilder}; pub use error::{LmdbResult, LmdbError}; -pub use transaction::Transaction; +pub use transaction::{ReadTransaction, WriteTransaction, RoTransaction, RwTransaction}; macro_rules! lmdb_try { ($expr:expr) => ({ match $expr { - ffi::MDB_SUCCESS => (), - err_code => return Err(::std::error::FromError::from_error(LmdbError::from_err_code(err_code))), + ::ffi::MDB_SUCCESS => (), + err_code => return Err(::std::error::FromError::from_error(::LmdbError::from_err_code(err_code))), } }) } @@ -28,10 +28,10 @@ macro_rules! lmdb_try { macro_rules! lmdb_try_with_cleanup { ($expr:expr, $cleanup:expr) => ({ match $expr { - ffi::MDB_SUCCESS => (), + ::ffi::MDB_SUCCESS => (), err_code => { let _ = $cleanup; - return Err(::std::error::FromError::from_error(LmdbError::from_err_code(err_code))) + return Err(::std::error::FromError::from_error(::LmdbError::from_err_code(err_code))) }, } }) @@ -42,5 +42,3 @@ mod database; mod environment; mod error; mod transaction; - -pub mod flags; diff --git a/src/transaction.rs b/src/transaction.rs index 385084e..ef9d5b1 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -3,94 +3,35 @@ use std::{mem, ptr, raw}; use std::kinds::marker; use std::io::BufWriter; -use cursor::Cursor; -use database::Database; +use cursor::{RoCursor, RwCursor}; use environment::Environment; +use database::Database; use error::{LmdbResult, lmdb_result}; use ffi; use ffi::MDB_txn; -use flags::{DatabaseFlags, EnvironmentFlags, WriteFlags, MDB_RESERVE}; +use ffi::{DatabaseFlags, EnvironmentFlags, WriteFlags, MDB_RDONLY, MDB_RESERVE}; /// An LMDB transaction. /// /// All database operations require a transaction. -pub struct Transaction<'env> { - txn: *mut MDB_txn, - _no_sync: marker::NoSync, - _no_send: marker::NoSend, - _contravariant: marker::ContravariantLifetime<'env>, -} - -#[unsafe_destructor] -impl <'env> Drop for Transaction<'env> { - fn drop(&mut self) { - unsafe { ffi::mdb_txn_abort(self.txn) } - } -} - -impl <'env> Transaction<'env> { - - /// 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(), - ptr::null_mut(), - flags.bits(), - &mut txn))); - Ok(Transaction { - txn: txn, - _no_sync: marker::NoSync, - _no_send: marker::NoSend, - _contravariant: marker::ContravariantLifetime::<'env>, - }) - } - } +pub trait Transaction<'env> { /// Returns a raw pointer to the underlying LMDB transaction. /// /// The caller **must** ensure that the pointer is not used after the lifetime of the /// transaction. - pub fn txn(&self) -> *mut MDB_txn { - self.txn - } - - /// Opens a handle to a 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 database handle will be private to the current transaction until the transaction is - /// successfully committed. If the transaction is aborted the database handle will be closed - /// automatically. After a successful commit the database handle will reside in the shared - /// environment, and may be used by other transactions. - /// - /// 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> { - Database::new(self, name, flags) - } - - /// Gets the option flags for the given database in the transaction. - 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))); - } + fn txn(&self) -> *mut MDB_txn; +} - Ok(DatabaseFlags::from_bits_truncate(flags)) - } +/// Transaction extension methods. +pub trait TransactionExt<'env> : Transaction<'env> { /// Commits the transaction. /// /// Any pending operations will be saved. - pub fn commit(self) -> LmdbResult<()> { + fn commit(self) -> LmdbResult<()> { unsafe { - let result = lmdb_result(ffi::mdb_txn_commit(self.txn())); + let result = lmdb_result(::ffi::mdb_txn_commit(self.txn())); mem::forget(self); result } @@ -99,22 +40,30 @@ impl <'env> Transaction<'env> { /// Aborts the transaction. /// /// Any pending operations will not be saved. - pub fn abort(self) { - // Abort is called in the destructor + fn abort(self) { + // Abort should be performed in transaction destructors. } +} + +impl<'env, T> TransactionExt<'env> for T where T: Transaction<'env> {} + +/// A read-only LMDB transaction. +/// +/// All database read operations require a transaction. +pub trait ReadTransaction<'env> : Transaction<'env> { /// Gets an item from a database. /// /// 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<'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, + 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 *mut 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_mut() }; unsafe { - try!(lmdb_result(ffi::mdb_get(self.txn(), + try!(lmdb_result(::ffi::mdb_get(self.txn(), database.dbi(), &mut key_val, &mut data_val))); @@ -127,17 +76,43 @@ impl <'env> Transaction<'env> { } } + /// Open a new read-only cursor on the given database. + fn open_read_cursor<'txn>(&'txn self, db: Database) -> LmdbResult> { + RoCursor::new(self, db) + } + + /// Gets the option flags for the given database in the transaction. + 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))); + } + + Ok(DatabaseFlags::from_bits_truncate(flags)) + } +} + +/// A read-write LMDB transaction. +/// +/// All database operations require a transaction. +pub trait WriteTransaction<'env> : ReadTransaction<'env> { + + /// Open a new read-write cursor on the given database. + fn open_write_cursor<'txn>(&'txn mut self, db: Database) -> LmdbResult> { + RwCursor::new(self, db) + } + /// Stores an item into a database. /// /// 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(&mut self, - database: Database, - key: &[u8], - data: &[u8], - flags: WriteFlags) - -> LmdbResult<()> { + fn put(&mut self, + database: Database, + key: &[u8], + data: &[u8], + flags: WriteFlags) + -> LmdbResult<()> { let mut key_val: ffi::MDB_val = ffi::MDB_val { mv_size: key.len() as size_t, mv_data: key.as_ptr() as *mut c_void }; let mut data_val: ffi::MDB_val = ffi::MDB_val { mv_size: data.len() as size_t, @@ -152,13 +127,13 @@ impl <'env> Transaction<'env> { } /// Returns a `BufWriter` which can be used to write a value into the item at the given key - /// and with the given length. - pub fn put_zero_copy<'txn>(&'txn mut self, - database: Database, - key: &[u8], - len: size_t, - flags: WriteFlags) - -> LmdbResult> { + /// and with the given length. The buffer must be completely filled by the caller. + fn reserve<'txn>(&'txn mut self, + database: Database, + key: &[u8], + len: size_t, + flags: WriteFlags) + -> LmdbResult> { let mut key_val: ffi::MDB_val = ffi::MDB_val { mv_size: key.len() as size_t, mv_data: key.as_ptr() as *mut c_void }; let mut data_val: ffi::MDB_val = ffi::MDB_val { mv_size: len, @@ -168,7 +143,7 @@ impl <'env> Transaction<'env> { database.dbi(), &mut key_val, &mut data_val, - (flags | MDB_RESERVE).bits()))); + flags.bits() | MDB_RESERVE))); let slice: &'txn mut [u8] = mem::transmute(raw::Slice { data: data_val.mv_data as *const u8, @@ -187,7 +162,7 @@ impl <'env> Transaction<'env> { /// 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(&mut self, + fn del(&mut self, database: Database, key: &[u8], data: Option<&[u8]>) @@ -205,16 +180,101 @@ impl <'env> Transaction<'env> { .unwrap_or(ptr::null_mut()))) } } +} - /// Open a new cursor over the database. - /// - /// Takes a mutable reference to the transaction since cursors can mutate the transaction, which - /// will invalidates values read during the transaction. - pub fn open_cursor<'txn>(&'txn mut self, db: Database) -> LmdbResult> { - Cursor::new(self, db) +/// An LMDB read-only transaction. +pub struct RoTransaction<'env> { + txn: *mut MDB_txn, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime<'env>, +} + +#[unsafe_destructor] +impl <'env> Drop for RoTransaction<'env> { + fn drop(&mut self) { + unsafe { ffi::mdb_txn_abort(self.txn) } + } +} + +impl <'env> RoTransaction<'env> { + + /// Creates a new read-only transaction in the given environment. Prefer using + /// `Environment::begin_ro_txn`. + #[doc(hidden)] + pub fn new(env: &'env Environment) -> LmdbResult> { + let mut txn: *mut MDB_txn = ptr::null_mut(); + unsafe { + try!(lmdb_result(ffi::mdb_txn_begin(env.env(), + ptr::null_mut(), + MDB_RDONLY.bits(), + &mut txn))); + Ok(RoTransaction { + txn: txn, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime::<'env>, + }) + } + } +} + +impl <'env> Transaction<'env> for RoTransaction<'env> { + fn txn(&self) -> *mut MDB_txn { + self.txn } } +//impl <'env> TransactionExt<'env> for RoTransaction<'env> { } +impl <'env> ReadTransaction<'env> for RoTransaction<'env> { } + +/// An LMDB read-write transaction. +pub struct RwTransaction<'env> { + txn: *mut MDB_txn, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime<'env>, +} + +#[unsafe_destructor] +impl <'env> Drop for RwTransaction<'env> { + fn drop(&mut self) { + unsafe { ffi::mdb_txn_abort(self.txn) } + } +} + +impl <'env> RwTransaction<'env> { + + /// Creates a new read-write transaction in the given environment. Prefer using + /// `Environment::begin_ro_txn`. + #[doc(hidden)] + pub fn new(env: &'env Environment) -> LmdbResult> { + let mut txn: *mut MDB_txn = ptr::null_mut(); + unsafe { + try!(lmdb_result(ffi::mdb_txn_begin(env.env(), + ptr::null_mut(), + EnvironmentFlags::empty().bits(), + &mut txn))); + Ok(RwTransaction { + txn: txn, + _no_sync: marker::NoSync, + _no_send: marker::NoSend, + _contravariant: marker::ContravariantLifetime::<'env>, + }) + } + } +} + +impl <'env> Transaction<'env> for RwTransaction<'env> { + fn txn(&self) -> *mut MDB_txn { + self.txn + } +} + +//impl <'env> TransactionExt<'env> for RwTransaction<'env> { } +impl <'env> ReadTransaction<'env> for RwTransaction<'env> { } +impl <'env> WriteTransaction<'env> for RwTransaction<'env> { } + #[cfg(test)] mod test { @@ -222,44 +282,23 @@ mod test { use std::sync::{Arc, Barrier, Future}; use environment::*; - use flags::*; + use super::*; + use ffi::*; - #[test] - fn test_open_db() { - let dir = io::TempDir::new("test").unwrap(); - let env = Environment::new().set_max_dbs(10) - .open(dir.path(), io::USER_RWX) - .unwrap(); - { - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - assert!(txn.open_db(None, DatabaseFlags::empty()).is_ok()); - assert!(txn.commit().is_ok()); - } { - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - assert!(txn.open_db(Some("testdb"), DatabaseFlags::empty()).is_err()) - } { - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - txn.open_db(Some("testdb"), MDB_CREATE).unwrap(); - assert!(txn.commit().is_ok()); - } { - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - assert!(txn.open_db(Some("testdb"), DatabaseFlags::empty()).is_ok()) - } - } #[test] fn test_put_get_del() { let dir = io::TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + let db = env.open_db(None).unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); + let mut txn = env.begin_write_txn().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 mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let mut txn = env.begin_write_txn().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()); @@ -270,19 +309,19 @@ mod test { } #[test] - fn test_put_zero_copy() { + fn test_reserve() { let dir = io::TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + let db = env.open_db(None).unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); + let mut txn = env.begin_write_txn().unwrap(); { - let mut writer = txn.put_zero_copy(db, b"key1", 4, WriteFlags::empty()).unwrap(); + let mut writer = txn.reserve(db, b"key1", 4, WriteFlags::empty()).unwrap(); writer.write(b"val1").unwrap(); } txn.commit().unwrap(); - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let mut txn = env.begin_write_txn().unwrap(); assert_eq!(b"val1", txn.get(db, b"key1").unwrap()); assert!(txn.get(db, b"key").is_err()); @@ -299,15 +338,15 @@ mod test { .unwrap()); let db1 = { - let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); - let db = txn.open_db(Some("db"), MDB_CREATE).unwrap(); + let db = env.create_db(Some("db"), DatabaseFlags::empty()).unwrap(); + let txn = env.begin_write_txn().unwrap(); txn.commit().unwrap(); db }; let db2 = { - let txn = env.begin_txn(MDB_RDONLY).unwrap(); - let db = txn.open_db(Some("db"), DatabaseFlags::empty()).unwrap(); + let db = env.open_db(Some("db")).unwrap(); + let txn = env.begin_read_txn().unwrap(); txn.commit().unwrap(); db }; @@ -316,7 +355,7 @@ mod test { assert!(db1.dbi() == db2.dbi()); { - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let mut txn = env.begin_write_txn().unwrap(); txn.put(db1, b"key1", b"val1", WriteFlags::empty()).unwrap(); assert!(txn.commit().is_ok()); } @@ -324,7 +363,7 @@ mod test { unsafe { env.close_db(db1) }; { - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let mut txn = env.begin_write_txn().unwrap(); assert!(txn.put(db1, b"key2", b"val2", WriteFlags::empty()).is_err()); } } @@ -332,15 +371,11 @@ mod test { #[test] fn test_concurrent_readers_single_writer() { let dir = io::TempDir::new("test").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 db = open_db_txn.open_db(None, DatabaseFlags::empty()).unwrap(); - open_db_txn.commit().unwrap(); + let env: Arc = Arc::new(Environment::new().open(dir.path(), io::USER_RWX).unwrap()); let n = 10u; // Number of concurrent readers let barrier = Arc::new(Barrier::new(n + 1)); - let mut futures = Vec::with_capacity(n); + let mut futures: Vec> = Vec::with_capacity(n); let key = b"key"; let val = b"val"; @@ -349,22 +384,24 @@ mod test { let reader_env = env.clone(); let reader_barrier = barrier.clone(); - futures.push(Future::spawn(proc() { + futures.push(Future::spawn(move|| { + let db = reader_env.open_db(None).unwrap(); { - let txn = reader_env.begin_txn(MDB_RDONLY).unwrap(); + let txn = reader_env.begin_read_txn().unwrap(); assert!(txn.get(db, key).is_err()); txn.abort(); } reader_barrier.wait(); reader_barrier.wait(); { - let txn = reader_env.begin_txn(MDB_RDONLY).unwrap(); + let txn = reader_env.begin_read_txn().unwrap(); txn.get(db, key).unwrap() == val } })); } - let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let db = env.open_db(None).unwrap(); + let mut txn = env.begin_write_txn().unwrap(); barrier.wait(); txn.put(db, key, val, WriteFlags::empty()).unwrap(); txn.commit().unwrap(); @@ -378,12 +415,8 @@ mod test { let dir = io::TempDir::new("test").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 db = open_db_txn.open_db(None, DatabaseFlags::empty()).unwrap(); - open_db_txn.commit().unwrap(); - let n = 10u; // Number of concurrent writers - let mut futures = Vec::with_capacity(n); + let mut futures: Vec> = Vec::with_capacity(n); let key = "key"; let val = "val"; @@ -391,8 +424,9 @@ mod test { for i in range(0, n) { let writer_env = env.clone(); - futures.push(Future::spawn(proc() { - let mut txn = writer_env.begin_txn(EnvironmentFlags::empty()).unwrap(); + futures.push(Future::spawn(move|| { + let db = writer_env.open_db(None).unwrap(); + let mut txn = writer_env.begin_write_txn().unwrap(); txn.put(db, format!("{}{}", key, i).as_bytes(), format!("{}{}", val, i).as_bytes(), @@ -403,7 +437,8 @@ mod test { } assert!(futures.iter_mut().all(|b| b.get())); - let txn = env.begin_txn(MDB_RDONLY).unwrap(); + let db = env.open_db(None).unwrap(); + let txn = env.begin_read_txn().unwrap(); for i in range(0, n) { assert_eq!(