diff --git a/README.md b/README.md index 8e2dc88..da85614 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,6 @@ cargo build * [x] lmdb-sys. * [x] Cursors. -* [ ] Zero-copy put API. +* [x] Zero-copy put API. * [ ] Nested transactions. * [ ] Database statistics. diff --git a/lmdb-sys/src/lib.rs b/lmdb-sys/src/lib.rs index f0fcacf..02fcc99 100644 --- a/lmdb-sys/src/lib.rs +++ b/lmdb-sys/src/lib.rs @@ -18,7 +18,7 @@ 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: *const c_void, + pub mv_data: *mut c_void, } #[repr(C)] diff --git a/src/cursor.rs b/src/cursor.rs index 18167a7..023256a 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -74,9 +74,9 @@ impl <'txn> Cursor<'txn> { -> LmdbResult<()> { let mut key_val: ffi::MDB_val = ffi::MDB_val { mv_size: key.len() as size_t, - mv_data: key.as_ptr() as *const c_void }; + mv_data: key.as_ptr() as *mut c_void }; let mut data_val: ffi::MDB_val = ffi::MDB_val { mv_size: data.len() as size_t, - mv_data: data.as_ptr() as *const c_void }; + mv_data: data.as_ptr() as *mut c_void }; unsafe { lmdb_result(ffi::mdb_cursor_put(self.cursor(), @@ -103,10 +103,10 @@ unsafe fn slice_to_val(slice: Option<&[u8]>) -> MDB_val { match slice { Some(slice) => MDB_val { mv_size: slice.len() as size_t, - mv_data: slice.as_ptr() as *const c_void }, + mv_data: slice.as_ptr() as *mut c_void }, None => MDB_val { mv_size: 0, - mv_data: ptr::null() }, + mv_data: ptr::null_mut() }, } } @@ -238,10 +238,10 @@ mod test { match slice { Some(slice) => MDB_val { mv_size: slice.len() as size_t, - mv_data: slice.as_ptr() as *const c_void }, + mv_data: slice.as_ptr() as *mut c_void }, None => MDB_val { mv_size: 0, - mv_data: ptr::null() }, + mv_data: ptr::null_mut() }, } } diff --git a/src/lib.rs b/src/lib.rs index a00984f..dd4ffe3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! Provides the minimal amount of abstraction necessary to interact with LMDB safely in Rust. In //! general, the API is very similar to the LMDB [C-API](http://symas.com/mdb/doc/). -#![feature(phase, globs, macro_rules, unsafe_destructor, if_let)] +#![feature(phase, globs, macro_rules, unsafe_destructor)] #[phase(plugin, link)] extern crate log; extern crate libc; diff --git a/src/transaction.rs b/src/transaction.rs index 7732474..385084e 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,6 +1,7 @@ use libc::{c_uint, c_void, size_t}; use std::{mem, ptr, raw}; use std::kinds::marker; +use std::io::BufWriter; use cursor::Cursor; use database::Database; @@ -8,7 +9,7 @@ use environment::Environment; use error::{LmdbResult, lmdb_result}; use ffi; use ffi::MDB_txn; -use flags::{DatabaseFlags, EnvironmentFlags, WriteFlags}; +use flags::{DatabaseFlags, EnvironmentFlags, WriteFlags, MDB_RESERVE}; /// An LMDB transaction. /// @@ -109,9 +110,9 @@ impl <'env> Transaction<'env> { /// 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, - mv_data: key.as_ptr() as *const c_void }; + mv_data: key.as_ptr() as *mut c_void }; let mut data_val: ffi::MDB_val = ffi::MDB_val { mv_size: 0, - mv_data: ptr::null() }; + mv_data: ptr::null_mut() }; unsafe { try!(lmdb_result(ffi::mdb_get(self.txn(), database.dbi(), @@ -138,9 +139,9 @@ impl <'env> Transaction<'env> { 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 *const c_void }; + 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, - mv_data: data.as_ptr() as *const c_void }; + mv_data: data.as_ptr() as *mut c_void }; unsafe { lmdb_result(ffi::mdb_put(self.txn(), database.dbi(), @@ -150,6 +151,34 @@ 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> { + 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, + mv_data: ptr::null_mut::() }; + unsafe { + try!(lmdb_result(ffi::mdb_put(self.txn(), + database.dbi(), + &mut key_val, + &mut data_val, + (flags | MDB_RESERVE).bits()))); + let slice: &'txn mut [u8] = + mem::transmute(raw::Slice { + data: data_val.mv_data as *const u8, + len: data_val.mv_size as uint + }); + + Ok(BufWriter::new(slice)) + } + } + /// Deletes an item from a database. /// /// This function removes key/data pairs from the database. If the database does not support @@ -164,10 +193,10 @@ impl <'env> Transaction<'env> { data: Option<&[u8]>) -> LmdbResult<()> { let mut key_val: ffi::MDB_val = ffi::MDB_val { mv_size: key.len() as size_t, - mv_data: key.as_ptr() as *const c_void }; + mv_data: key.as_ptr() as *mut c_void }; let data_val: Option = data.map(|data| ffi::MDB_val { mv_size: data.len() as size_t, - mv_data: data.as_ptr() as *const c_void }); + mv_data: data.as_ptr() as *mut c_void }); unsafe { lmdb_result(ffi::mdb_del(self.txn(), database.dbi(), @@ -240,6 +269,27 @@ mod test { assert!(txn.get(db, b"key1").is_err()); } + #[test] + fn test_put_zero_copy() { + let dir = io::TempDir::new("test").unwrap(); + let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + let db = txn.open_db(None, DatabaseFlags::empty()).unwrap(); + { + let mut writer = txn.put_zero_copy(db, b"key1", 4, WriteFlags::empty()).unwrap(); + writer.write(b"val1").unwrap(); + } + txn.commit().unwrap(); + + let mut txn = env.begin_txn(EnvironmentFlags::empty()).unwrap(); + assert_eq!(b"val1", txn.get(db, b"key1").unwrap()); + assert!(txn.get(db, b"key").is_err()); + + txn.del(db, b"key1", None).unwrap(); + assert!(txn.get(db, b"key1").is_err()); + } + #[test] fn test_close_database() { let dir = io::TempDir::new("test").unwrap();