diff --git a/.appveyor.yml b/.appveyor.yml index 93d9a7c..ad89544 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,26 +2,22 @@ environment: matrix: - TARGET: x86_64-pc-windows-msvc - TARGET: i686-pc-windows-msvc - # Temporarily disable MinGW builds due to https://github.com/rust-lang/rust/issues/47048. - # Once that issue is fixed, presumably by https://github.com/rust-lang/rust/pull/51989, - # we should reenable them. - # - TARGET: x86_64-pc-windows-gnu - # MSYS_BITS: 64 - # - TARGET: i686-pc-windows-gnu - # MSYS_BITS: 32 install: - - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" - - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" - - set PATH=%PATH%;C:\Program Files (x86)\Rust\bin - - if defined MSYS_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS_BITS%\bin - - rustc -V + - curl -sSf -o rustup-init.exe https://win.rustup.rs/ + - rustup-init.exe -y --default-host %TARGET% --default-toolchain nightly + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -Vv - cargo -V build_script: - git submodule -q update --init - - cargo build --target %TARGET% --all -v test_script: - SET RUST_BACKTRACE=1 - cargo test --target %TARGET% --all -v + - cargo test --release --target %TARGET% --all -v + +cache: + - C:\Users\appveyor\.cargo\registry + - target diff --git a/lmdb-sys/Cargo.toml b/lmdb-sys/Cargo.toml index 5c8f433..e7377e0 100644 --- a/lmdb-sys/Cargo.toml +++ b/lmdb-sys/Cargo.toml @@ -19,5 +19,5 @@ build = "build.rs" libc = "0.2" [build-dependencies] -pkg-config = "0.3" +pkg-config = "0.3.2" cc = "1" diff --git a/lmdb-sys/build.rs b/lmdb-sys/build.rs index 8ae769a..b1bd80e 100644 --- a/lmdb-sys/build.rs +++ b/lmdb-sys/build.rs @@ -11,11 +11,16 @@ fn main() { lmdb.push("liblmdb"); if !pkg_config::find_library("liblmdb").is_ok() { - cc::Build::new() - .file(lmdb.join("mdb.c")) - .file(lmdb.join("midl.c")) - // https://github.com/LMDB/lmdb/blob/LMDB_0.9.21/libraries/liblmdb/Makefile#L25 - .opt_level(2) - .compile("liblmdb.a") + let target = env::var("TARGET").expect("No TARGET found"); + let mut build = cc::Build::new(); + if target.contains("android") { + build.define("ANDROID", "1"); + } + build + .file(lmdb.join("mdb.c")) + .file(lmdb.join("midl.c")) + // https://github.com/LMDB/lmdb/blob/LMDB_0.9.21/libraries/liblmdb/Makefile#L25 + .opt_level(2) + .compile("liblmdb.a") } } diff --git a/src/cursor.rs b/src/cursor.rs index 1f0dc63..f50eef4 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,6 +1,7 @@ -use libc::{c_void, size_t, c_uint}; -use std::{fmt, ptr, result, slice}; use std::marker::PhantomData; +use std::{fmt, mem, ptr, result, slice}; + +use libc::{EINVAL, c_void, size_t, c_uint}; use database::Database; use error::{Error, Result, lmdb_result}; @@ -19,11 +20,7 @@ pub trait Cursor<'txn> { /// Retrieves a key/data pair from the cursor. Depending on the cursor op, /// the current key may be returned. - fn get(&self, - key: Option<&[u8]>, - data: Option<&[u8]>, - op: c_uint) - -> Result<(Option<&'txn [u8]>, &'txn [u8])> { + fn get(&self, key: Option<&[u8]>, data: Option<&[u8]>, op: c_uint) -> Result<(Option<&'txn [u8]>, &'txn [u8])> { unsafe { let mut key_val = slice_to_val(key); let mut data_val = slice_to_val(data); @@ -52,8 +49,7 @@ pub trait Cursor<'txn> { /// duplicate data items of each key will be returned before moving on to /// the next key. fn iter_start(&mut self) -> Iter<'txn> { - self.get(None, None, ffi::MDB_FIRST).unwrap(); - Iter::new(self.cursor(), ffi::MDB_GET_CURRENT, ffi::MDB_NEXT) + Iter::new(self.cursor(), ffi::MDB_FIRST, ffi::MDB_NEXT) } /// Iterate over database items starting from the given key. @@ -63,10 +59,9 @@ pub trait Cursor<'txn> { /// the next key. fn iter_from(&mut self, key: K) -> Iter<'txn> where K: AsRef<[u8]> { match self.get(Some(key.as_ref()), None, ffi::MDB_SET_RANGE) { - Err(Error::NotFound) => Ok(()), - Err(error) => Err(error), - Ok(_) => Ok(()), - }.unwrap(); + Ok(_) | Err(Error::NotFound) => (), + Err(error) => panic!("mdb_cursor_get returned an unexpected error: {}", error), + }; Iter::new(self.cursor(), ffi::MDB_GET_CURRENT, ffi::MDB_NEXT) } @@ -80,27 +75,26 @@ pub trait Cursor<'txn> { /// Iterate over duplicate database items starting from the beginning of the /// database. Each item will be returned as an iterator of its duplicates. fn iter_dup_start(&mut self) -> IterDup<'txn> { - self.get(None, None, ffi::MDB_FIRST).unwrap(); - IterDup::new(self.cursor(), ffi::MDB_GET_CURRENT) + IterDup::new(self.cursor(), ffi::MDB_FIRST) } /// Iterate over duplicate items in the database starting from the given /// key. Each item will be returned as an iterator of its duplicates. fn iter_dup_from(&mut self, key: &K) -> IterDup<'txn> where K: AsRef<[u8]> { match self.get(Some(key.as_ref()), None, ffi::MDB_SET_RANGE) { - Err(Error::NotFound) => Ok(()), - Err(error) => Err(error), - Ok(_) => Ok(()), - }.unwrap(); + Ok(_) | Err(Error::NotFound) => (), + Err(error) => panic!("mdb_cursor_get returned an unexpected error: {}", error), + }; IterDup::new(self.cursor(), ffi::MDB_GET_CURRENT) } - /// Iterate over the duplicates of the item in the database with the given - /// key. - fn iter_dup_of(&mut self, key: &K) -> Result> where K: - AsRef<[u8]> { - self.get(Some(key.as_ref()), None, ffi::MDB_SET)?; - Ok(Iter::new(self.cursor(), ffi::MDB_GET_CURRENT, ffi::MDB_NEXT_DUP)) + /// Iterate over the duplicates of the item in the database with the given key. + fn iter_dup_of(&mut self, key: &K) -> Iter<'txn> where K: AsRef<[u8]> { + match self.get(Some(key.as_ref()), None, ffi::MDB_SET) { + Ok(_) | Err(Error::NotFound) => (), + Err(error) => panic!("mdb_cursor_get returned an unexpected error: {}", error), + }; + Iter::new(self.cursor(), ffi::MDB_GET_CURRENT, ffi::MDB_NEXT_DUP) } } @@ -249,20 +243,14 @@ impl <'txn> Iterator for Iter<'txn> { fn next(&mut self) -> Option<(&'txn [u8], &'txn [u8])> { let mut key = ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }; let mut data = ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }; - + let op = mem::replace(&mut self.op, self.next_op); unsafe { - let err_code = ffi::mdb_cursor_get(self.cursor, &mut key, &mut data, self.op); - // Set the operation for the next get - self.op = self.next_op; - if err_code == ffi::MDB_SUCCESS { - Some((val_to_slice(key), val_to_slice(data))) - } else { - // The documentation for mdb_cursor_get specifies that it may fail with MDB_NOTFOUND - // and MDB_EINVAL (and we shouldn't be passing in invalid parameters). - // TODO: validate that these are the only failures possible. - debug_assert!(err_code == ffi::MDB_NOTFOUND, - "Unexpected LMDB error {:?}.", Error::from_err_code(err_code)); - None + match ffi::mdb_cursor_get(self.cursor, &mut key, &mut data, op) { + ffi::MDB_SUCCESS => Some((val_to_slice(key), val_to_slice(data))), + // EINVAL can occur when the cursor was previously seeked to a non-existent value, + // e.g. iter_from with a key greater than all values in the database. + ffi::MDB_NOTFOUND | EINVAL => None, + error => panic!("mdb_cursor_get returned an unexpected error: {}", error), } } } @@ -299,12 +287,12 @@ impl <'txn> Iterator for IterDup<'txn> { fn next(&mut self) -> Option> { let mut key = ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }; let mut data = ffi::MDB_val { mv_size: 0, mv_data: ptr::null_mut() }; + let op = mem::replace(&mut self.op, ffi::MDB_NEXT_NODUP); let err_code = unsafe { - ffi::mdb_cursor_get(self.cursor, &mut key, &mut data, self.op) + ffi::mdb_cursor_get(self.cursor, &mut key, &mut data, op) }; if err_code == ffi::MDB_SUCCESS { - self.op = ffi::MDB_NEXT; Some(Iter::new(self.cursor, ffi::MDB_GET_CURRENT, ffi::MDB_NEXT_DUP)) } else { None @@ -463,6 +451,36 @@ mod test { cursor.iter_from(b"key6").collect::>()); } + #[test] + fn test_iter_empty_database() { + let dir = TempDir::new("test").unwrap(); + let env = Environment::new().open(dir.path()).unwrap(); + let db = env.open_db(None).unwrap(); + let txn = env.begin_ro_txn().unwrap(); + let mut cursor = txn.open_ro_cursor(db).unwrap(); + + assert_eq!(0, cursor.iter().count()); + assert_eq!(0, cursor.iter_start().count()); + assert_eq!(0, cursor.iter_from(b"foo").count()); + } + + #[test] + fn test_iter_empty_dup_database() { + let dir = TempDir::new("test").unwrap(); + let env = Environment::new().open(dir.path()).unwrap(); + let db = env.create_db(None, DatabaseFlags::DUP_SORT).unwrap(); + let txn = env.begin_ro_txn().unwrap(); + let mut cursor = txn.open_ro_cursor(db).unwrap(); + + assert_eq!(0, cursor.iter().count()); + assert_eq!(0, cursor.iter_start().count()); + assert_eq!(0, cursor.iter_from(b"foo").count()); + assert_eq!(0, cursor.iter_dup().count()); + assert_eq!(0, cursor.iter_dup_start().count()); + assert_eq!(0, cursor.iter_dup_from(b"foo").count()); + assert_eq!(0, cursor.iter_dup_of(b"foo").count()); + } + #[test] fn test_iter_dup() { let dir = TempDir::new("test").unwrap(); @@ -514,9 +532,9 @@ mod test { cursor.iter_dup_from(b"f").flat_map(|x| x).collect::>()); assert_eq!(items.clone().into_iter().skip(3).take(3).collect::>(), - cursor.iter_dup_of(b"b").unwrap().collect::>()); + cursor.iter_dup_of(b"b").collect::>()); - assert!(cursor.iter_dup_of(b"foo").is_err()); + assert_eq!(0, cursor.iter_dup_of(b"foo").count()); } #[test] diff --git a/src/environment.rs b/src/environment.rs index db31f7a..9ac51c0 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -242,7 +242,8 @@ impl EnvironmentBuilder { /// /// On UNIX, the database files will be opened with 644 permissions. /// - /// The path may not contain the null character. + /// The path may not contain the null character, Windows UNC (Uniform Naming Convention) + /// paths are not supported either. pub fn open(&self, path: &Path) -> Result { self.open_with_permissions(path, 0o644) } @@ -251,7 +252,8 @@ impl EnvironmentBuilder { /// /// On Windows, the permissions will be ignored. /// - /// The path may not contain the null character. + /// The path may not contain the null character, Windows UNC (Uniform Naming Convention) + /// paths are not supported either. pub fn open_with_permissions(&self, path: &Path, mode: ffi::mode_t) -> Result { let mut env: *mut ffi::MDB_env = ptr::null_mut(); unsafe {