First cut at a higher-level api

without.crypto
Dan Burkert 10 years ago
parent 22b7a50ac0
commit 9ab71e29b1
  1. 3
      lmdb-sys/Cargo.toml
  2. 2
      lmdb-sys/build.rs
  3. 2
      lmdb-sys/mdb
  4. 109
      lmdb-sys/src/constants.rs
  5. 204
      lmdb-sys/src/lib.rs
  6. 276
      src/cursor.rs
  7. 30
      src/database.rs
  8. 142
      src/environment.rs
  9. 12
      src/lib.rs
  10. 315
      src/transaction.rs

@ -12,6 +12,9 @@ build = "build.rs"
name = "lmdb-sys" name = "lmdb-sys"
[dependencies.rust-bindgen]
rust-bindgen = "*"
[build-dependencies.pkg-config] [build-dependencies.pkg-config]
pkg-config = "*" pkg-config = "*"

@ -13,6 +13,6 @@ fn main() {
gcc::compile_library("liblmdb.a", gcc::compile_library("liblmdb.a",
&Default::default(), &Default::default(),
&[mdb.join("mdb.c").as_str().unwrap(), &[mdb.join("mdb.c").as_str().unwrap(),
mdb.join("midl.c").as_str().unwrap()]) mdb.join("midl.c").as_str().unwrap()]);
} }
} }

@ -1 +1 @@
Subproject commit 9a8eb95674c7b500cfe5f44d03493ff76c9fc0c1 Subproject commit 9cc04f604f80f033d712f5f1faeb4ed97ca74f40

@ -1,4 +1,4 @@
use libc::c_uint; use libc::{c_int, c_uint};
bitflags! { bitflags! {
#[doc="Environment Options"] #[doc="Environment Options"]
@ -120,39 +120,39 @@ bitflags! {
#[doc="Keys are strings to be compared in reverse order, from the end"] #[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="of the strings to the beginning. By default, Keys are treated as strings and"]
#[doc="compared from beginning to end."] #[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="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 may have multiple data items, stored in sorted order.) By default"]
#[doc="keys must be unique and may have only a single data item."] #[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="Keys are binary integers in native byte order. Setting this option"]
#[doc="requires all keys to be the same size, typically sizeof(int)"] #[doc="requires all keys to be the same size, typically sizeof(int)"]
#[doc="or sizeof(size_t)."] #[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="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="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="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="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."] #[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="This option specifies that duplicate data items are also integers, and"]
#[doc="should be sorted as such."] #[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="This option specifies that duplicate data items should be compared as"]
#[doc="strings in reverse order."] #[doc="strings in reverse order."]
const MDB_REVERSEDUP = 0x40, const MDB_REVERSEDUP = 0x40, // 64
#[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,
} }
} }
/// 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! { bitflags! {
#[doc="Write Options"] #[doc="Write Options"]
#[deriving(Show)] #[deriving(Show)]
@ -180,15 +180,6 @@ bitflags! {
#[doc="record followed by an insert."] #[doc="record followed by an insert."]
const MDB_CURRENT = 0x40, 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="Append the given key/data pair to the end of the"]
#[doc="database. No key comparisons are performed. This option allows"] #[doc="database. No key comparisons are performed. This option allows"]
#[doc="fast bulk loading when keys are already known to be in the"] #[doc="fast bulk loading when keys are already known to be in the"]
@ -200,3 +191,81 @@ bitflags! {
const MDB_APPENDDUP = 0x40000, 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;

@ -1,202 +1,12 @@
//! Provides extern declarations for `liblmdb`. Additionally, this crate provides `liblmdb` as a #![allow(dead_code, uppercase_variables, non_camel_case_types)]
//! native Cargo dependency. #![feature(phase,globs)]
#![allow(non_camel_case_types)] #[phase(plugin)] extern crate bindgen;
extern crate libc; 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; mod constants;
pub type mdb_filehandle_t = libc::c_int;
pub type MDB_dbi = c_uint; bindgen!("../mdb/libraries/liblmdb/lmdb.h", match="lmdb.h", link="lmdb")
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;
}

@ -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::{mem, ptr, raw};
use std::kinds::marker; use std::kinds::marker;
use database::Database; use database::Database;
use error::{LmdbResult, lmdb_result}; use error::{LmdbResult, lmdb_result};
use ffi; use ffi;
use ffi::{MDB_cursor, mdb_cursor_open, MDB_cursor_op, MDB_val}; use ffi::{MDB_cursor, mdb_cursor_open, MDB_val, WriteFlags};
use flags::WriteFlags;
use transaction::Transaction; 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] /// An LMDB cursor.
impl <'txn> Drop for Cursor<'txn> { pub trait Cursor<'txn> {
fn drop(&mut self) { /// Returns a raw pointer to the underlying LMDB cursor.
unsafe { ffi::mdb_cursor_close(self.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> { pub trait ReadCursor<'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,
_no_sync: marker::NoSync,
_no_send: marker::NoSend,
_contravariant: 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 /// Retrieves a key/data pair from the cursor. Depending on the cursor op, the current key is
/// returned. /// returned.
pub fn get(&self, fn get(&self,
key: Option<&[u8]>, key: Option<&[u8]>,
data: Option<&[u8]>, data: Option<&[u8]>,
op: MDB_cursor_op) op: c_uint)
-> LmdbResult<(Option<&'txn [u8]>, &'txn [u8])> { -> LmdbResult<(Option<&'txn [u8]>, &'txn [u8])> {
unsafe { unsafe {
let mut key_val = slice_to_val(key); let mut key_val = slice_to_val(key);
let mut data_val = slice_to_val(data); let mut data_val = slice_to_val(data);
let key_ptr = key_val.mv_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 key_val,
&mut data_val, &mut data_val,
op))); op)));
@ -64,10 +39,13 @@ impl <'txn> Cursor<'txn> {
Ok((key_out, data_out)) 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, /// Puts a key/data pair into the database. The cursor will be positioned at the new data item,
/// or on failure usually near it. /// or on failure usually near it.
pub fn put(&self, fn put(&self,
key: &[u8], key: &[u8],
data: &[u8], data: &[u8],
flags: WriteFlags) flags: WriteFlags)
@ -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 /// `MDB_NODUPDATA` may be used to delete all data items for the current key, if the database
/// was opened with `MDB_DUPSORT`. /// was opened with `MDB_DUPSORT`.
pub fn del(&self, flags: WriteFlags) -> LmdbResult<()> { fn del(&self, flags: WriteFlags) -> LmdbResult<()> {
unsafe { unsafe {
lmdb_result(ffi::mdb_cursor_del(self.cursor(), flags.bits())) 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<RoCursor<'txn>> {
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<RwCursor<'txn>> {
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 { unsafe fn slice_to_val(slice: Option<&[u8]>) -> MDB_val {
match slice { match slice {
Some(slice) => Some(slice) =>
@ -120,53 +179,52 @@ unsafe fn val_to_slice<'a>(val: MDB_val) -> &'a [u8] {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use libc::{c_void, size_t}; use libc::{c_void, size_t, c_uint};
use std::{io, ptr}; use std::{io, ptr};
use transaction::*;
use environment::*; use environment::*;
use error::{LmdbResult, lmdb_result}; use error::{LmdbResult, lmdb_result};
use flags::*; use ffi::*;
use ffi;
use ffi::{MDB_cursor_op, MDB_val};
use super::*; use super::*;
#[test] #[test]
fn test_get() { fn test_get() {
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 db = env.open_db(None).unwrap();
let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_write_txn().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();
let cursor = txn.open_cursor(db).unwrap(); let cursor = txn.open_read_cursor(db).unwrap();
assert_eq!((Some(b"key1"), b"val1"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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] #[test]
fn test_get_dup() { fn test_get_dup() {
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 db = env.create_db(None, MDB_DUPSORT).unwrap();
let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_write_txn().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"val1", WriteFlags::empty()).unwrap();
txn.put(db, b"key1", b"val2", 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"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"val2", WriteFlags::empty()).unwrap();
txn.put(db, b"key2", b"val3", 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"), 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"), 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"), 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"), 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"), 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"), assert_eq!((Some(b"key2"), b"val3"),
cursor.get(None, None, MDB_cursor_op::MDB_NEXT_DUP).unwrap()); cursor.get(None, None, MDB_NEXT_DUP).unwrap());
assert!(cursor.get(None, None, MDB_cursor_op::MDB_NEXT_DUP).is_err()); assert!(cursor.get(None, None, MDB_NEXT_DUP).is_err());
assert_eq!((Some(b"key2"), b"val2"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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] #[test]
fn test_get_dupfixed() { fn test_get_dupfixed() {
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 db = env.create_db(None, MDB_DUPSORT | MDB_DUPFIXED).unwrap();
let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_write_txn().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"val1", WriteFlags::empty()).unwrap();
txn.put(db, b"key1", b"val2", 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"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"val5", WriteFlags::empty()).unwrap();
txn.put(db, b"key2", b"val6", 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"), 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"), assert_eq!((None, b"val1val2val3"),
cursor.get(None, None, MDB_cursor_op::MDB_GET_MULTIPLE).unwrap()); cursor.get(None, None, MDB_GET_MULTIPLE).unwrap());
assert!(cursor.get(None, None, MDB_cursor_op::MDB_NEXT_MULTIPLE).is_err()); assert!(cursor.get(None, None, MDB_NEXT_MULTIPLE).is_err());
} }
/// Checks assumptions about which get operations return keys. /// Checks assumptions about which get operations return keys.
@ -249,22 +307,22 @@ mod test {
fn sets_key(cursor: &Cursor, fn sets_key(cursor: &Cursor,
key: Option<&[u8]>, key: Option<&[u8]>,
data: Option<&[u8]>, data: Option<&[u8]>,
op: MDB_cursor_op) op: c_uint)
-> LmdbResult<bool> { -> LmdbResult<bool> {
unsafe { unsafe {
let mut key_val = slice_to_val(key); let mut key_val = slice_to_val(key);
let mut data_val = slice_to_val(data); let mut data_val = slice_to_val(data);
let key_ptr = key_val.mv_data; let key_ptr = key_val.mv_data;
try!(lmdb_result(ffi::mdb_cursor_get(cursor.cursor(), try!(lmdb_result(mdb_cursor_get(cursor.cursor(),
&mut key_val, &mut key_val,
&mut data_val, &mut data_val,
op))); op)));
Ok(key_ptr != key_val.mv_data) 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 mut txn = env.begin_write_txn().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"val1", WriteFlags::empty()).unwrap();
txn.put(db, b"key1", b"val2", 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"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"val8", WriteFlags::empty()).unwrap();
txn.put(db, b"key3", b"val9", WriteFlags::empty()).unwrap(); txn.put(db, b"key3", b"val9", WriteFlags::empty()).unwrap();
let cursor = txn.open_cursor(db).unwrap(); let cursor = txn.open_read_cursor(db).unwrap();
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST).unwrap()); assert!(sets_key(&cursor, None, None, MDB_FIRST).unwrap());
assert!(!sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST_DUP).unwrap()); assert!(!sets_key(&cursor, None, None, 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"val5"), 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, Some(b"key2"), Some(b"val"), 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_GET_CURRENT).unwrap());
assert!(!sets_key(&cursor, None, None, MDB_cursor_op::MDB_GET_MULTIPLE).unwrap()); assert!(!sets_key(&cursor, None, None, MDB_GET_MULTIPLE).unwrap());
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_LAST).unwrap()); assert!(sets_key(&cursor, None, None, MDB_LAST).unwrap());
assert!(!sets_key(&cursor, None, None, MDB_cursor_op::MDB_LAST_DUP).unwrap()); assert!(!sets_key(&cursor, None, None, MDB_LAST_DUP).unwrap());
sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST).unwrap(); sets_key(&cursor, None, None, MDB_FIRST).unwrap();
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT).unwrap()); assert!(sets_key(&cursor, None, None, MDB_NEXT).unwrap());
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT_DUP).unwrap()); assert!(sets_key(&cursor, None, None, MDB_NEXT_DUP).unwrap());
sets_key(&cursor, None, None, MDB_cursor_op::MDB_FIRST).unwrap(); sets_key(&cursor, None, None, MDB_FIRST).unwrap();
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT_MULTIPLE).unwrap()); assert!(sets_key(&cursor, None, None, MDB_NEXT_MULTIPLE).unwrap());
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_NEXT_NODUP).unwrap()); assert!(sets_key(&cursor, None, None, MDB_NEXT_NODUP).unwrap());
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_PREV).unwrap()); assert!(sets_key(&cursor, None, None, MDB_PREV).unwrap());
sets_key(&cursor, None, None, MDB_cursor_op::MDB_LAST).unwrap(); sets_key(&cursor, None, None, MDB_LAST).unwrap();
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_PREV_DUP).unwrap()); assert!(sets_key(&cursor, None, None, MDB_PREV_DUP).unwrap());
assert!(sets_key(&cursor, None, None, MDB_cursor_op::MDB_PREV_NODUP).unwrap()); assert!(sets_key(&cursor, None, None, 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_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_SET_KEY).unwrap());
assert!(sets_key(&cursor, Some(b"key2"), None, MDB_cursor_op::MDB_SET_RANGE).unwrap()); assert!(sets_key(&cursor, Some(b"key2"), None, MDB_SET_RANGE).unwrap());
} }
#[test] #[test]
fn test_put_del() { fn test_put_del() {
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 db = env.open_db(None).unwrap();
let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_write_txn().unwrap();
let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); let cursor = txn.open_write_cursor(db).unwrap();
let cursor = txn.open_cursor(db).unwrap();
cursor.put(b"key1", b"val1", WriteFlags::empty()).unwrap(); cursor.put(b"key1", b"val1", WriteFlags::empty()).unwrap();
cursor.put(b"key2", b"val2", WriteFlags::empty()).unwrap(); cursor.put(b"key2", b"val2", WriteFlags::empty()).unwrap();
cursor.put(b"key3", b"val3", WriteFlags::empty()).unwrap(); cursor.put(b"key3", b"val3", WriteFlags::empty()).unwrap();
assert_eq!((Some(b"key3"), b"val3"), 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(); cursor.del(WriteFlags::empty()).unwrap();
assert_eq!((Some(b"key2"), b"val2"), assert_eq!((Some(b"key2"), b"val2"),
cursor.get(None, None, MDB_cursor_op::MDB_LAST).unwrap()); cursor.get(None, None, MDB_LAST).unwrap());
} }
} }

@ -2,37 +2,43 @@ use std::kinds::marker;
use std::ptr; use std::ptr;
use error::{LmdbResult, lmdb_result}; use error::{LmdbResult, lmdb_result};
use ffi::{MDB_dbi, mdb_dbi_open}; use ffi::*;
use flags::DatabaseFlags; use transaction::{Transaction, ReadTransaction, WriteTransaction};
use transaction::Transaction;
/// A handle to an individual database in an environment. /// 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 /// 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 /// may not exist in the environment (for instance, if the database is opened during a transaction
/// that has not yet committed). /// that has not yet committed).
#[deriving(Clone, Copy)]
pub struct Database<'env> { pub struct Database<'env> {
dbi: MDB_dbi, dbi: MDB_dbi,
_marker: marker::ContravariantLifetime<'env>, _marker: marker::ContravariantLifetime<'env>,
} }
impl <'env> Copy for Database<'env> { }
impl <'env> Database<'env> { impl <'env> Database<'env> {
/// Opens a database in the given transaction. Prefer using `Transaction::open_db`. pub unsafe fn open<'env>(txn: &ReadTransaction<'env>,
#[doc(hidden)] name: Option<&str>)
pub fn new(txn: &Transaction<'env>, -> 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;
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>, name: Option<&str>,
flags: DatabaseFlags) flags: DatabaseFlags)
-> LmdbResult<Database<'env>> { -> LmdbResult<Database<'env>> {
let c_name = name.map(|n| n.to_c_str()); 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 name_ptr = if let Some(ref c_name) = c_name { c_name.as_ptr() } else { ptr::null() };
let mut dbi: MDB_dbi = 0; let mut dbi: MDB_dbi = 0;
unsafe { try!(lmdb_result(mdb_dbi_open(txn.txn(), name_ptr, flags.bits() | MDB_CREATE, &mut dbi)));
try!(lmdb_result(mdb_dbi_open(txn.txn(), name_ptr, flags.bits(), &mut dbi))); Ok(Database { dbi: dbi,
} _marker: marker::ContravariantLifetime::<'env> })
Ok(Database { dbi: dbi, _marker: marker::ContravariantLifetime::<'env> })
} }
/// Returns the underlying LMDB database handle. /// Returns the underlying LMDB database handle.

@ -1,19 +1,19 @@
use libc::{c_uint, size_t, mode_t}; use libc::{c_uint, size_t, mode_t};
use std::io::FilePermission; use std::io::FilePermission;
use std::ptr; use std::ptr;
use std::sync::Mutex;
use error::{LmdbResult, lmdb_result};
use database::Database; use database::Database;
use error::{LmdbError, LmdbResult, lmdb_result}; use ffi::*;
use ffi; use transaction::{RoTransaction, RwTransaction, Transaction, TransactionExt};
use ffi::MDB_env;
use flags::EnvironmentFlags;
use transaction::Transaction;
/// An LMDB environment. /// An LMDB environment.
/// ///
/// An environment supports multiple databases, all residing in the same shared-memory map. /// An environment supports multiple databases, all residing in the same shared-memory map.
pub struct Environment { pub struct Environment {
env: *mut MDB_env, env: *mut MDB_env,
dbi_open_mutex: Mutex<()>,
} }
impl Environment { impl Environment {
@ -36,11 +36,65 @@ impl Environment {
self.env 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. /// If `name` is `None`, then the returned handle will be for the default database.
pub fn begin_txn<'a>(&'a self, flags: EnvironmentFlags) -> LmdbResult<Transaction<'a>> { ///
Transaction::new(self, flags) /// 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<Database<'env>> {
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<Database<'env>> {
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<DatabaseFlags> {
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<'env>> {
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<'env>> {
RwTransaction::new(self)
} }
/// Flush data buffers to disk. /// Flush data buffers to disk.
@ -50,7 +104,7 @@ impl Environment {
/// the environment was opened with `MDB_NOSYNC` or in part `MDB_NOMETASYNC`. /// the environment was opened with `MDB_NOSYNC` or in part `MDB_NOMETASYNC`.
pub fn sync(&self, force: bool) -> LmdbResult<()> { pub fn sync(&self, force: bool) -> LmdbResult<()> {
unsafe { 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 /// handle value. Usually it's better to set a bigger `EnvironmentBuilder::set_max_dbs`, unless
/// that value would be large. /// that value would be large.
pub unsafe fn close_db(&self, db: Database) { 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 { impl Drop for Environment {
fn drop(&mut self) { 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. /// Options for opening or creating an environment.
#[deriving(Show, PartialEq, Eq)] #[deriving(Show, PartialEq, Eq, Copy, Clone)]
pub struct EnvironmentBuilder { pub struct EnvironmentBuilder {
flags: EnvironmentFlags, flags: EnvironmentFlags,
max_readers: Option<c_uint>, max_readers: Option<c_uint>,
@ -95,26 +149,27 @@ impl EnvironmentBuilder {
pub fn open(&self, path: &Path, mode: FilePermission) -> LmdbResult<Environment> { pub fn open(&self, path: &Path, mode: FilePermission) -> LmdbResult<Environment> {
let mut env: *mut MDB_env = ptr::null_mut(); let mut env: *mut MDB_env = ptr::null_mut();
unsafe { unsafe {
lmdb_try!(ffi::mdb_env_create(&mut env)); lmdb_try!(mdb_env_create(&mut env));
if let Some(max_readers) = self.max_readers { if let Some(max_readers) = self.max_readers {
lmdb_try_with_cleanup!(ffi::mdb_env_set_maxreaders(env, max_readers), lmdb_try_with_cleanup!(mdb_env_set_maxreaders(env, max_readers),
ffi::mdb_env_close(env)) mdb_env_close(env))
} }
if let Some(max_dbs) = self.max_dbs { if let Some(max_dbs) = self.max_dbs {
lmdb_try_with_cleanup!(ffi::mdb_env_set_maxdbs(env, max_dbs), lmdb_try_with_cleanup!(mdb_env_set_maxdbs(env, max_dbs),
ffi::mdb_env_close(env)) mdb_env_close(env))
} }
if let Some(map_size) = self.map_size { if let Some(map_size) = self.map_size {
lmdb_try_with_cleanup!(ffi::mdb_env_set_mapsize(env, map_size), lmdb_try_with_cleanup!(mdb_env_set_mapsize(env, map_size),
ffi::mdb_env_close(env)) 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(), path.to_c_str().as_ptr(),
self.flags.bits(), self.flags.bits(),
mode.bits() as mode_t), 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 { pub fn set_flags(&mut self, flags: EnvironmentFlags) -> &mut EnvironmentBuilder {
@ -169,7 +224,7 @@ mod test {
use std::io; use std::io;
use flags; use ffi::*;
use super::*; use super::*;
#[test] #[test]
@ -177,7 +232,7 @@ mod test {
let dir = io::TempDir::new("test").unwrap(); let dir = io::TempDir::new("test").unwrap();
// opening non-existent env with read-only should fail // 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) .open(dir.path(), io::USER_RWX)
.is_err()); .is_err());
@ -185,7 +240,7 @@ mod test {
assert!(Environment::new().open(dir.path(), io::USER_RWX).is_ok()); assert!(Environment::new().open(dir.path(), io::USER_RWX).is_ok());
// opening env with read-only should not fail // 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) .open(dir.path(), io::USER_RWX)
.is_ok()); .is_ok());
} }
@ -197,23 +252,44 @@ mod test {
{ {
// Mutable env, mutable txn // 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 // 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 // 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) .open(dir.path(), io::USER_RWX)
.unwrap(); .unwrap();
assert!(env.begin_txn(flags::EnvironmentFlags::empty()).is_err()); assert!(env.begin_write_txn().is_err());
} { } {
// Read-only env, read-only txn // 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_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) .open(dir.path(), io::USER_RWX)
.unwrap(); .unwrap();
assert!(env.begin_txn(flags::MDB_RDONLY).is_ok()); 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] #[test]
@ -223,7 +299,7 @@ mod test {
let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap();
assert!(env.sync(true).is_ok()); 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) .open(dir.path(), io::USER_RWX)
.unwrap(); .unwrap();
assert!(env.sync(true).is_ok()); assert!(env.sync(true).is_ok());

@ -14,13 +14,13 @@ pub use cursor::Cursor;
pub use database::Database; 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::Transaction; pub use transaction::{ReadTransaction, WriteTransaction, RoTransaction, RwTransaction};
macro_rules! lmdb_try { macro_rules! lmdb_try {
($expr:expr) => ({ ($expr:expr) => ({
match $expr { match $expr {
ffi::MDB_SUCCESS => (), ::ffi::MDB_SUCCESS => (),
err_code => return Err(::std::error::FromError::from_error(LmdbError::from_err_code(err_code))), 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 { macro_rules! lmdb_try_with_cleanup {
($expr:expr, $cleanup:expr) => ({ ($expr:expr, $cleanup:expr) => ({
match $expr { match $expr {
ffi::MDB_SUCCESS => (), ::ffi::MDB_SUCCESS => (),
err_code => { err_code => {
let _ = $cleanup; 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 environment;
mod error; mod error;
mod transaction; mod transaction;
pub mod flags;

@ -3,94 +3,35 @@ use std::{mem, ptr, raw};
use std::kinds::marker; use std::kinds::marker;
use std::io::BufWriter; use std::io::BufWriter;
use cursor::Cursor; use cursor::{RoCursor, RwCursor};
use database::Database;
use environment::Environment; use environment::Environment;
use database::Database;
use error::{LmdbResult, lmdb_result}; use error::{LmdbResult, lmdb_result};
use ffi; use ffi;
use ffi::MDB_txn; use ffi::MDB_txn;
use flags::{DatabaseFlags, EnvironmentFlags, WriteFlags, MDB_RESERVE}; use ffi::{DatabaseFlags, EnvironmentFlags, WriteFlags, MDB_RDONLY, MDB_RESERVE};
/// An LMDB transaction. /// An LMDB transaction.
/// ///
/// All database operations require a transaction. /// All database operations require a transaction.
pub struct Transaction<'env> { pub trait 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<Transaction<'env>> {
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>,
})
}
}
/// Returns a raw pointer to the underlying LMDB transaction. /// Returns a raw pointer to the underlying LMDB transaction.
/// ///
/// The caller **must** ensure that the pointer is not used after the lifetime of the /// The caller **must** ensure that the pointer is not used after the lifetime of the
/// transaction. /// transaction.
pub fn txn(&self) -> *mut MDB_txn { 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<'env>> {
Database::new(self, name, flags)
} }
/// Gets the option flags for the given database in the transaction. /// Transaction extension methods.
pub fn db_flags(&self, db: Database) -> LmdbResult<DatabaseFlags> { pub trait TransactionExt<'env> : Transaction<'env> {
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))
}
/// Commits the transaction. /// Commits the transaction.
/// ///
/// Any pending operations will be saved. /// Any pending operations will be saved.
pub fn commit(self) -> LmdbResult<()> { fn commit(self) -> LmdbResult<()> {
unsafe { unsafe {
let result = lmdb_result(ffi::mdb_txn_commit(self.txn())); let result = lmdb_result(::ffi::mdb_txn_commit(self.txn()));
mem::forget(self); mem::forget(self);
result result
} }
@ -99,22 +40,30 @@ impl <'env> Transaction<'env> {
/// Aborts the transaction. /// Aborts the transaction.
/// ///
/// Any pending operations will not be saved. /// Any pending operations will not be saved.
pub fn abort(self) { fn abort(self) {
// Abort is called in the destructor // 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. /// Gets an item from a database.
/// ///
/// 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<'txn>(&'txn self, database: Database, key: &[u8]) -> LmdbResult<&'txn [u8]> { 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 *mut c_void }; 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() }; mv_data: ptr::null_mut() };
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)));
@ -127,12 +76,38 @@ 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<'txn>> {
RoCursor::new(self, db)
}
/// Gets the option flags for the given database in the transaction.
fn db_flags(&self, db: Database) -> LmdbResult<DatabaseFlags> {
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<'txn>> {
RwCursor::new(self, db)
}
/// Stores an item into a database. /// Stores an item into a database.
/// ///
/// 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(&mut self, fn put(&mut self,
database: Database, database: Database,
key: &[u8], key: &[u8],
data: &[u8], data: &[u8],
@ -152,8 +127,8 @@ impl <'env> Transaction<'env> {
} }
/// Returns a `BufWriter` which can be used to write a value into the item at the given key /// Returns a `BufWriter` which can be used to write a value into the item at the given key
/// and with the given length. /// and with the given length. The buffer must be completely filled by the caller.
pub fn put_zero_copy<'txn>(&'txn mut self, fn reserve<'txn>(&'txn mut self,
database: Database, database: Database,
key: &[u8], key: &[u8],
len: size_t, len: size_t,
@ -168,7 +143,7 @@ impl <'env> Transaction<'env> {
database.dbi(), database.dbi(),
&mut key_val, &mut key_val,
&mut data_val, &mut data_val,
(flags | MDB_RESERVE).bits()))); flags.bits() | MDB_RESERVE)));
let slice: &'txn mut [u8] = let slice: &'txn mut [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,
@ -187,7 +162,7 @@ impl <'env> Transaction<'env> {
/// 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(&mut self, fn del(&mut self,
database: Database, database: Database,
key: &[u8], key: &[u8],
data: Option<&[u8]>) data: Option<&[u8]>)
@ -205,16 +180,101 @@ impl <'env> Transaction<'env> {
.unwrap_or(ptr::null_mut()))) .unwrap_or(ptr::null_mut())))
} }
} }
}
/// Open a new cursor over the database. /// An LMDB read-only transaction.
/// pub struct RoTransaction<'env> {
/// Takes a mutable reference to the transaction since cursors can mutate the transaction, which txn: *mut MDB_txn,
/// will invalidates values read during the transaction. _no_sync: marker::NoSync,
pub fn open_cursor<'txn>(&'txn mut self, db: Database) -> LmdbResult<Cursor<'txn>> { _no_send: marker::NoSend,
Cursor::new(self, db) _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<RoTransaction<'env>> {
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<RwTransaction<'env>> {
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)] #[cfg(test)]
mod test { mod test {
@ -222,44 +282,23 @@ mod test {
use std::sync::{Arc, Barrier, Future}; use std::sync::{Arc, Barrier, Future};
use environment::*; 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] #[test]
fn test_put_get_del() { fn test_put_get_del() {
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 db = env.open_db(None).unwrap();
let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_write_txn().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 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"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());
@ -270,19 +309,19 @@ mod test {
} }
#[test] #[test]
fn test_put_zero_copy() { fn test_reserve() {
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 db = env.open_db(None).unwrap();
let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let mut txn = env.begin_write_txn().unwrap();
let db = txn.open_db(None, DatabaseFlags::empty()).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(); writer.write(b"val1").unwrap();
} }
txn.commit().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"val1", txn.get(db, b"key1").unwrap());
assert!(txn.get(db, b"key").is_err()); assert!(txn.get(db, b"key").is_err());
@ -299,15 +338,15 @@ mod test {
.unwrap()); .unwrap());
let db1 = { let db1 = {
let txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); let db = env.create_db(Some("db"), DatabaseFlags::empty()).unwrap();
let db = txn.open_db(Some("db"), MDB_CREATE).unwrap(); let txn = env.begin_write_txn().unwrap();
txn.commit().unwrap(); txn.commit().unwrap();
db db
}; };
let db2 = { let db2 = {
let txn = env.begin_txn(MDB_RDONLY).unwrap(); let db = env.open_db(Some("db")).unwrap();
let db = txn.open_db(Some("db"), DatabaseFlags::empty()).unwrap(); let txn = env.begin_read_txn().unwrap();
txn.commit().unwrap(); txn.commit().unwrap();
db db
}; };
@ -316,7 +355,7 @@ mod test {
assert!(db1.dbi() == db2.dbi()); 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(); txn.put(db1, b"key1", b"val1", WriteFlags::empty()).unwrap();
assert!(txn.commit().is_ok()); assert!(txn.commit().is_ok());
} }
@ -324,7 +363,7 @@ mod test {
unsafe { env.close_db(db1) }; 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()); assert!(txn.put(db1, b"key2", b"val2", WriteFlags::empty()).is_err());
} }
} }
@ -332,15 +371,11 @@ mod test {
#[test] #[test]
fn test_concurrent_readers_single_writer() { fn test_concurrent_readers_single_writer() {
let dir = io::TempDir::new("test").unwrap(); let dir = io::TempDir::new("test").unwrap();
let env = Arc::new(Environment::new().open(dir.path(), io::USER_RWX).unwrap()); let env: Arc<Environment> = 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 readers let n = 10u; // Number of concurrent readers
let barrier = Arc::new(Barrier::new(n + 1)); let barrier = Arc::new(Barrier::new(n + 1));
let mut futures = Vec::with_capacity(n); let mut futures: Vec<Future<bool>> = Vec::with_capacity(n);
let key = b"key"; let key = b"key";
let val = b"val"; let val = b"val";
@ -349,22 +384,24 @@ mod test {
let reader_env = env.clone(); let reader_env = env.clone();
let reader_barrier = barrier.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()); 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_read_txn().unwrap();
txn.get(db, key).unwrap() == val 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(); barrier.wait();
txn.put(db, key, val, WriteFlags::empty()).unwrap(); txn.put(db, key, val, WriteFlags::empty()).unwrap();
txn.commit().unwrap(); txn.commit().unwrap();
@ -378,12 +415,8 @@ mod test {
let dir = io::TempDir::new("test").unwrap(); let dir = io::TempDir::new("test").unwrap();
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 db = open_db_txn.open_db(None, DatabaseFlags::empty()).unwrap();
open_db_txn.commit().unwrap();
let n = 10u; // Number of concurrent writers let n = 10u; // Number of concurrent writers
let mut futures = Vec::with_capacity(n); let mut futures: Vec<Future<bool>> = Vec::with_capacity(n);
let key = "key"; let key = "key";
let val = "val"; let val = "val";
@ -391,8 +424,9 @@ mod test {
for i in range(0, n) { for i in range(0, n) {
let writer_env = env.clone(); let writer_env = env.clone();
futures.push(Future::spawn(proc() { futures.push(Future::spawn(move|| {
let mut txn = writer_env.begin_txn(EnvironmentFlags::empty()).unwrap(); let db = writer_env.open_db(None).unwrap();
let mut txn = writer_env.begin_write_txn().unwrap();
txn.put(db, txn.put(db,
format!("{}{}", key, i).as_bytes(), format!("{}{}", key, i).as_bytes(),
format!("{}{}", val, i).as_bytes(), format!("{}{}", val, i).as_bytes(),
@ -403,7 +437,8 @@ mod test {
} }
assert!(futures.iter_mut().all(|b| b.get())); 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) { for i in range(0, n) {
assert_eq!( assert_eq!(

Loading…
Cancel
Save