├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── appveyor.yml ├── circle.yml ├── examples └── simple.rs ├── liblmdb-sys ├── Cargo.toml ├── build.rs └── src │ └── lib.rs ├── src ├── core.rs ├── lib.rs ├── tests.rs ├── traits.rs └── utils.rs └── up_doc.sh /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | doc 3 | target 4 | /Cargo.lock 5 | /liblmdb-sys/Cargo.lock 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "liblmdb-sys/mdb"] 2 | path = liblmdb-sys/mdb 3 | url = https://github.com/LMDB/lmdb.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | rust: 4 | - nightly 5 | - beta 6 | - stable 7 | 8 | env: 9 | global: 10 | - secure: C6dtZjIXukGFq+dV0/w6INZX1dps6kDfONDzubEqT+0UjsrGgXSugBNspuDE4kTv8yJ9qFNv/69LfvjuYpERRaJZuv66nS2MDpBWugXwZ/4mdsaBfho/Zhuajl4ff4Dn0hvYs9olrZpJK6L/byTLd1w4ugw77ngTyc+rwR+rB4U= 11 | - secure: C770POllPujSfOExiD3xqXlJkhKPM3G9rkzEfQZtAFhR6RcfI893mAqEGmq2IyGz2IoZs48qmBuukt4d9mLwdSAEEOCMJ0pgjYwqPMRa70rvnxZw+JFGZbkmZjgA62JWIOvk2OX7+DXECFxMVO9abYPohToNyjmkz1Tw1jz6/98= 12 | 13 | script: 14 | - cargo test --verbose && cargo doc --verbose --no-deps 15 | 16 | after_script: 17 | - ./up_doc.sh 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lmdb-rs" 3 | version = "0.7.6" 4 | authors = ["Valerii Hiora "] 5 | license = "MIT" 6 | description = "LMDB bindings" 7 | repository = "https://github.com/vhbit/lmdb-rs" 8 | documentation = "https://vhbit.github.io/lmdb-rs/lmdb_rs" 9 | readme = "README.md" 10 | keywords = ["lmdb", "db", "key-value", "kvs", "kv"] 11 | exclude = [ 12 | ".gitignore", 13 | ".travis.yml", 14 | "up_doc.sh", 15 | ] 16 | 17 | [dependencies.liblmdb-sys] 18 | path = "liblmdb-sys" 19 | version = "0.2.2" 20 | 21 | [dependencies] 22 | log = "0.3" 23 | libc = "0.2" 24 | bitflags = "0.7" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Valerii Hiora 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lmdb-rs 2 | ======= 3 | 4 | [![Build status (master)](https://travis-ci.org/vhbit/lmdb-rs.svg?branch=master)](https://travis-ci.org/vhbit/lmdb-rs) 5 | [![Latest version](http://meritbadge.herokuapp.com/lmdb-rs)](https://crates.io/crates/lmdb-rs) 6 | 7 | Rust bindings for [LMDB](http://symas.com/mdb/) 8 | 9 | [Documentation (master branch)](http://vhbit.github.io/lmdb-rs/lmdb_rs/) 10 | 11 | Building 12 | ======== 13 | 14 | LMDB is bundled as submodule so update submodules first: 15 | 16 | `git submodule update --init` 17 | 18 | And then 19 | 20 | `cargo build` 21 | 22 | Feedback 23 | ======== 24 | 25 | Feel free to ping me if you have a question or a suggestion how to 26 | make it better and idiomatic. 27 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - TARGET: nightly-x86_64-pc-windows-msvc 4 | - TARGET: nightly-i686-pc-windows-msvc 5 | - TARGET: stable-x86_64-pc-windows-msvc 6 | - TARGET: stable-i686-pc-windows-msvc 7 | #- TARGET: nightly-i686-pc-windows-gnu 8 | 9 | install: 10 | - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe" 11 | - ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null 12 | - ps: $env:PATH="$env:PATH;C:\MinGW\bin;C:\rust\bin" 13 | - rustc -vV 14 | - cargo -vV 15 | 16 | build_script: 17 | - git submodule -q update --init 18 | - cargo build -v 19 | 20 | test_script: 21 | - SET RUST_BACKTRACE=1 22 | - cargo test -v 23 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | general: 4 | branches: 5 | ignore: 6 | - gh-pages 7 | 8 | checkout: 9 | post: 10 | - git submodule sync 11 | - git submodule update --init --recursive 12 | 13 | dependencies: 14 | pre: 15 | - curl -sS https://static.rust-lang.org/rustup.sh > rustup.sh 16 | - chmod +x ./rustup.sh 17 | - ./rustup.sh --yes 18 | 19 | test: 20 | override: 21 | - cargo test -j4 --verbose 22 | post: 23 | - cargo doc -j4 --verbose --no-deps 24 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate lmdb_rs as lmdb; 2 | 3 | use lmdb::{EnvBuilder, DbFlags}; 4 | 5 | fn main() { 6 | let env = EnvBuilder::new().open("test-lmdb", 0o777).unwrap(); 7 | 8 | let db_handle = env.get_default_db(DbFlags::empty()).unwrap(); 9 | let txn = env.new_transaction().unwrap(); 10 | { 11 | let db = txn.bind(&db_handle); // get a database bound to this transaction 12 | 13 | let pairs = vec![("Albert", "Einstein",), 14 | ("Joe", "Smith",), 15 | ("Jack", "Daniels")]; 16 | 17 | for &(name, surname) in pairs.iter() { 18 | db.set(&surname, &name).unwrap(); 19 | } 20 | } 21 | 22 | // Note: `commit` is choosen to be explicit as 23 | // in case of failure it is responsibility of 24 | // the client to handle the error 25 | match txn.commit() { 26 | Err(_) => panic!("failed to commit!"), 27 | Ok(_) => () 28 | } 29 | 30 | let reader = env.get_reader().unwrap(); 31 | let db = reader.bind(&db_handle); 32 | let name = db.get::<&str>(&"Smith").unwrap(); 33 | println!("It's {} Smith", name); 34 | } 35 | -------------------------------------------------------------------------------- /liblmdb-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "liblmdb-sys" 3 | version = "0.2.2" 4 | authors = ["Valerii Hiora "] 5 | links = "lmdb" 6 | license = "MIT" 7 | description = "LMDB native lib" 8 | repository = "https://github.com/vhbit/lmdb-rs" 9 | build = "build.rs" 10 | 11 | [dependencies] 12 | libc = "0.2" 13 | [build-dependencies] 14 | gcc = "0.3" 15 | -------------------------------------------------------------------------------- /liblmdb-sys/build.rs: -------------------------------------------------------------------------------- 1 | extern crate gcc; 2 | 3 | fn main() { 4 | let target = std::env::var("TARGET").unwrap(); 5 | 6 | let mut config = gcc::Config::new(); 7 | config.file("mdb/libraries/liblmdb/mdb.c") 8 | .file("mdb/libraries/liblmdb/midl.c"); 9 | config.opt_level(2); 10 | 11 | if target.contains("dragonfly") { 12 | config.flag("-DMDB_DSYNC=O_SYNC"); 13 | config.flag("-DMDB_FDATASYNC=fsync"); 14 | } 15 | 16 | config.compile("liblmdb.a"); 17 | } 18 | -------------------------------------------------------------------------------- /liblmdb-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, non_camel_case_types)] 2 | 3 | extern crate libc; 4 | 5 | pub use self::os::{mdb_mode_t, mdb_filehandle_t}; 6 | use libc::{c_int, c_uint, c_void, c_char, size_t}; 7 | 8 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", 9 | target_os = "freebsd", target_os = "dragonfly", 10 | target_os = "android", target_os = "openbsd"))] 11 | mod os { 12 | use libc; 13 | 14 | 15 | pub type mdb_mode_t = libc::mode_t; 16 | 17 | pub type mdb_filehandle_t = libc::c_int; 18 | } 19 | 20 | #[cfg(target_os = "windows")] 21 | mod os { 22 | use libc; 23 | 24 | pub type mdb_mode_t = libc::c_int; 25 | 26 | pub type mdb_filehandle_t = libc::c_int; 27 | } 28 | 29 | pub type MDB_dbi = c_uint; 30 | 31 | pub type MDB_rel_func = extern fn(*const MDB_val, *const c_void, *const c_void, *const c_void); 32 | pub type MDB_msg_func = extern fn(*const c_char, *const c_void) -> c_int; 33 | pub type MDB_cmp_func = extern fn(*const MDB_val, *const MDB_val) -> c_int; 34 | 35 | #[derive(Copy, Clone)] 36 | #[repr(C)] 37 | pub struct MDB_val { 38 | pub mv_size: size_t, 39 | pub mv_data: *const c_void, 40 | } 41 | 42 | #[allow(missing_copy_implementations)] 43 | pub enum MDB_env {} 44 | 45 | #[allow(missing_copy_implementations)] 46 | pub enum MDB_txn {} 47 | 48 | #[allow(missing_copy_implementations)] 49 | pub enum MDB_cursor {} 50 | 51 | #[repr(C)] 52 | #[derive(Copy, Clone)] 53 | pub struct MDB_stat { 54 | pub ms_psize: c_uint, 55 | pub ms_depth: c_uint, 56 | pub ms_branch_pages: size_t, 57 | pub ms_leaf_pages: size_t, 58 | pub ms_overflow_pages: size_t, 59 | pub ms_entries: size_t 60 | } 61 | 62 | #[repr(C)] 63 | #[allow(missing_copy_implementations)] 64 | pub struct MDB_envinfo { 65 | pub me_mapaddr: *const c_void, 66 | pub me_mapsize: size_t, 67 | pub me_last_pgno: size_t, 68 | pub me_last_txnid: size_t, 69 | pub me_maxreaders: c_uint, 70 | pub me_numreaders: c_uint 71 | } 72 | 73 | #[repr(C)] 74 | #[derive(Copy, Clone, Eq, PartialEq)] 75 | pub enum MDB_cursor_op { 76 | MDB_FIRST, 77 | MDB_FIRST_DUP, 78 | MDB_GET_BOTH, 79 | MDB_GET_BOTH_RANGE, 80 | MDB_GET_CURRENT, 81 | MDB_GET_MULTIPLE, 82 | MDB_LAST, 83 | MDB_LAST_DUP, 84 | MDB_NEXT, 85 | MDB_NEXT_DUP, 86 | MDB_NEXT_MULTIPLE, 87 | MDB_NEXT_NODUP, 88 | MDB_PREV, 89 | MDB_PREV_DUP, 90 | MDB_PREV_NODUP, 91 | MDB_SET, 92 | MDB_SET_KEY, 93 | MDB_SET_RANGE 94 | } 95 | 96 | // Return codes 97 | pub const MDB_SUCCESS: c_int = 0; 98 | pub const MDB_KEYEXIST: c_int = -30799; 99 | pub const MDB_NOTFOUND: c_int = -30798; 100 | pub const MDB_PAGE_NOTFOUND: c_int = -30797; 101 | pub const MDB_CORRUPTED: c_int = -30796; 102 | pub const MDB_PANIC: c_int = -30795; 103 | pub const MDB_VERSION_MISMATCH: c_int = -30794; 104 | pub const MDB_INVALID: c_int = -30793; 105 | pub const MDB_MAP_FULL: c_int = -30792; 106 | pub const MDB_DBS_FULL: c_int = -30791; 107 | pub const MDB_READERS_FULL: c_int = -30790; 108 | pub const MDB_TLS_FULL: c_int = -30789; 109 | pub const MDB_TXN_FULL: c_int = -30788; 110 | pub const MDB_CURSOR_FULL: c_int = -30787; 111 | pub const MDB_PAGE_FULL: c_int = -30786; 112 | pub const MDB_MAP_RESIZED: c_int = -30785; 113 | pub const MDB_INCOMPATIBLE: c_int = -30784; 114 | pub const MDB_BAD_RSLOT: c_int = -30783; 115 | pub const MDB_BAD_TXN: c_int = -30782; 116 | pub const MDB_BAD_VALSIZE: c_int = -30781; 117 | 118 | // Write flags 119 | pub const MDB_NOOVERWRITE: c_uint = 0x10; 120 | pub const MDB_NODUPDATA: c_uint = 0x20; 121 | pub const MDB_CURRENT: c_uint = 0x40; 122 | pub const MDB_RESERVE: c_uint = 0x10000; 123 | pub const MDB_APPEND: c_uint = 0x20000; 124 | pub const MDB_APPENDDUP: c_uint = 0x40000; 125 | pub const MDB_MULTIPLE: c_uint = 0x80000; 126 | 127 | // Database flags 128 | pub const MDB_REVERSEKEY: c_uint = 0x02; 129 | pub const MDB_DUPSORT: c_uint = 0x04; 130 | pub const MDB_INTEGERKEY: c_uint = 0x08; 131 | pub const MDB_DUPFIXED: c_uint = 0x10; 132 | pub const MDB_INTEGERDUP: c_uint = 0x20; 133 | pub const MDB_REVERSEDUP: c_uint = 0x40; 134 | pub const MDB_CREATE: c_uint = 0x40000; 135 | 136 | // Environment flags 137 | pub const MDB_FIXEDMAP: c_uint = 0x01; 138 | pub const MDB_NOSUBDIR: c_uint = 0x4000; 139 | pub const MDB_NOSYNC: c_uint = 0x10000; 140 | pub const MDB_RDONLY: c_uint = 0x20000; 141 | pub const MDB_NOMETASYNC: c_uint = 0x40000; 142 | pub const MDB_WRITEMAP: c_uint = 0x80000; 143 | pub const MDB_MAPASYNC: c_uint = 0x100000; 144 | pub const MDB_NOTLS: c_uint = 0x200000; 145 | pub const MDB_NOLOCK: c_uint = 0x400000; 146 | pub const MDB_NORDAHEAD: c_uint = 0x800000; 147 | pub const MDB_NOMEMINIT: c_uint = 0x1000000; 148 | 149 | // Embedding should work better for now 150 | extern "C" { 151 | pub fn mdb_version(major: *mut c_int, minor: *mut c_int, patch: *mut c_int) -> *const c_char; 152 | pub fn mdb_strerror(err: c_int) -> *const c_char; 153 | pub fn mdb_env_create(env: *mut *mut MDB_env) -> c_int; 154 | pub fn mdb_env_open(env: *mut MDB_env, path: *const c_char, flags: c_uint, mode: mdb_mode_t) -> c_int; 155 | pub fn mdb_env_copy(env: *mut MDB_env, path: *const c_char) -> c_int; 156 | pub fn mdb_env_copyfd(env: *mut MDB_env, fd: mdb_filehandle_t) -> c_int; 157 | pub fn mdb_env_stat(env: *mut MDB_env, stat: *mut MDB_stat) -> c_int; 158 | pub fn mdb_env_info(env: *mut MDB_env, info: *mut MDB_envinfo) -> c_int; 159 | pub fn mdb_env_sync(env: *mut MDB_env, force: c_int) -> c_int; 160 | pub fn mdb_env_close(env: *mut MDB_env); 161 | pub fn mdb_env_set_flags(env: *mut MDB_env, flags: c_uint, onoff: c_int) -> c_int; 162 | pub fn mdb_env_get_flags(env: *mut MDB_env, flags: *mut c_uint) -> c_int; 163 | pub fn mdb_env_get_path(env: *mut MDB_env, path: *mut *mut c_char) -> c_int; 164 | pub fn mdb_env_get_fd(env: *mut MDB_env, fd: *mut mdb_filehandle_t) -> c_int; 165 | pub fn mdb_env_set_mapsize(env: *mut MDB_env, size: size_t) -> c_int; 166 | pub fn mdb_env_set_maxreaders(env: *mut MDB_env, readers: c_uint) -> c_int; 167 | pub fn mdb_env_get_maxreaders(env: *mut MDB_env, readers: *mut c_uint) -> c_int; 168 | pub fn mdb_env_set_maxdbs(env: *mut MDB_env, dbs: MDB_dbi) -> c_int; 169 | pub fn mdb_env_get_maxkeysize(env: *mut MDB_env) -> c_int; 170 | pub fn mdb_txn_begin(env: *mut MDB_env, parent: *mut MDB_txn, flags: c_uint, txn: *mut *mut MDB_txn) -> c_int; 171 | pub fn mdb_txn_env(txn: *mut MDB_txn) -> *mut MDB_env; 172 | pub fn mdb_txn_commit(txn: *mut MDB_txn) -> c_int; 173 | pub fn mdb_txn_abort(txn: *mut MDB_txn); 174 | pub fn mdb_txn_reset(txn: *mut MDB_txn); 175 | pub fn mdb_txn_renew(txn: *mut MDB_txn) -> c_int; 176 | pub fn mdb_dbi_open(txn: *mut MDB_txn, name: *const c_char, flags: c_uint, dbi: *mut MDB_dbi) -> c_int; 177 | pub fn mdb_stat(txn: *mut MDB_txn, dbi: MDB_dbi, stat: *mut MDB_stat) -> c_int; 178 | pub fn mdb_dbi_flags(txn: *mut MDB_txn, dbi: MDB_dbi, flags: *mut c_uint) -> c_int; 179 | pub fn mdb_dbi_close(txn: *mut MDB_env, dbi: MDB_dbi); 180 | pub fn mdb_drop(txn: *mut MDB_txn, dbi: MDB_dbi, del: c_int) -> c_int; 181 | pub fn mdb_set_compare(txn: *mut MDB_txn, dbi: MDB_dbi, cmp: MDB_cmp_func) -> c_int; 182 | pub fn mdb_set_dupsort(txn: *mut MDB_txn, dbi: MDB_dbi, cmp: MDB_cmp_func) -> c_int; 183 | pub fn mdb_set_relfunc(txn: *mut MDB_txn, dbi: MDB_dbi, rel: MDB_rel_func) -> c_int; 184 | pub fn mdb_set_relctx(txn: *mut MDB_txn, dbi: MDB_dbi, ctx: *const c_void) -> c_int; 185 | pub fn mdb_get(txn: *mut MDB_txn, dbi: MDB_dbi, key: *mut MDB_val, data: *mut MDB_val) -> c_int; 186 | pub fn mdb_put(txn: *mut MDB_txn, dbi: MDB_dbi, key: *mut MDB_val, data: *mut MDB_val, flags: c_uint) -> c_int; 187 | pub fn mdb_del(txn: *mut MDB_txn, dbi: MDB_dbi, key: *mut MDB_val, data: *mut MDB_val) -> c_int; 188 | pub fn mdb_cursor_open(txn: *mut MDB_txn, dbi: MDB_dbi, cursor: *mut *mut MDB_cursor) -> c_int; 189 | pub fn mdb_cursor_close(cursor: *mut MDB_cursor) -> c_int; 190 | pub fn mdb_cursor_renew(txn: *mut MDB_txn, cursor: *mut MDB_cursor) -> c_int; 191 | pub fn mdb_cursor_txn(cursor: *mut MDB_cursor) -> *mut MDB_txn; 192 | pub fn mdb_cursor_dbi(cursor: *mut MDB_cursor) -> *mut MDB_dbi; 193 | pub fn mdb_cursor_get(cursor: *mut MDB_cursor, key: *mut MDB_val, data: *mut MDB_val, op: MDB_cursor_op) -> c_int; 194 | pub fn mdb_cursor_put(cursor: *mut MDB_cursor, key: *mut MDB_val, data: *mut MDB_val, flags: c_uint) -> c_int; 195 | pub fn mdb_cursor_del(cursor: *mut MDB_cursor, flags: c_uint) -> c_int; 196 | pub fn mdb_cursor_count(cursor: *mut MDB_cursor, countp: *mut size_t) -> c_int; 197 | pub fn mdb_cmp(txn: *mut MDB_txn, dbi: MDB_dbi, a: *mut MDB_val, b: *mut MDB_val) -> c_int; 198 | pub fn mdb_dcmp(txn: *mut MDB_txn, dbi: MDB_dbi, a: *mut MDB_val, b: *mut MDB_val) -> c_int; 199 | pub fn mdb_reader_list(env: *mut MDB_env, func: MDB_msg_func, ctx: *const c_void) -> c_int; 200 | pub fn mdb_reader_check(env: *mut MDB_env, dead: *mut c_int) -> c_int; 201 | } 202 | 203 | impl std::fmt::Debug for MDB_val { 204 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 205 | unsafe { 206 | let buf: &[u8] = std::slice::from_raw_parts(std::mem::transmute(self.mv_data), 207 | self.mv_size as usize); 208 | write!(fmt, "{:?}@{:?}", buf, self.mv_data) 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | //! High level wrapper of LMDB APIs 2 | //! 3 | //! Requires knowledge of LMDB terminology 4 | //! 5 | //! # Environment 6 | //! 7 | //! Environment is actually the center point of LMDB, it's a container 8 | //! of everything else. As some settings couldn't be adjusted after 9 | //! opening, `Environment` is constructed using `EnvBuilder`, which 10 | //! sets up maximum size, maximum count of named databases, maximum 11 | //! readers which could be used from different threads without locking 12 | //! and so on. 13 | //! 14 | //! # Database 15 | //! 16 | //! Actual key-value store. The most crucial aspect is whether a database 17 | //! allows duplicates or not. It is specified on creation and couldn't be 18 | //! changed later. Entries for the same key are called `items`. 19 | //! 20 | //! There are a couple of optmizations to use, like marking 21 | //! keys or data as integer, allowing sorting using reverse key, marking 22 | //! keys/data as fixed size. 23 | //! 24 | //! # Transaction 25 | //! 26 | //! Absolutely every db operation happens in a transaction. It could 27 | //! be a read-only transaction (reader), which is lockless and therefore 28 | //! cheap. Or it could be a read-write transaction, which is unique, i.e. 29 | //! there could be only one writer at a time. 30 | //! 31 | //! While readers are cheap and lockless, they work better being short-lived 32 | //! as in other case they may lock pages from being reused. Readers have 33 | //! a special API for marking as finished and renewing. 34 | //! 35 | //! It is perfectly fine to create nested transactions. 36 | //! 37 | //! 38 | //! # Example 39 | //! 40 | 41 | #![allow(non_upper_case_globals)] 42 | 43 | use libc::{c_int, c_uint, size_t, c_void}; 44 | use std; 45 | use std::borrow::ToOwned; 46 | use std::cell::{UnsafeCell}; 47 | use std::cmp::{Ordering}; 48 | use std::collections::HashMap; 49 | use std::error::Error; 50 | use std::ffi::{CString}; 51 | use std::path::Path; 52 | use std::mem; 53 | use std::ptr; 54 | use std::result::Result; 55 | use std::sync::{Arc, Mutex}; 56 | 57 | use ffi::{self, MDB_val}; 58 | pub use MdbError::{NotFound, KeyExists, Other, StateError, Corrupted, Panic}; 59 | pub use MdbError::{InvalidPath, TxnFull, CursorFull, PageFull, CacheError}; 60 | use traits::{ToMdbValue, FromMdbValue}; 61 | use utils::{error_msg}; 62 | 63 | 64 | macro_rules! lift_mdb { 65 | ($e:expr) => (lift_mdb!($e, ())); 66 | ($e:expr, $r:expr) => ( 67 | { 68 | let t = $e; 69 | match t { 70 | ffi::MDB_SUCCESS => Ok($r), 71 | _ => return Err(MdbError::new_with_code(t)) 72 | } 73 | }) 74 | } 75 | 76 | macro_rules! try_mdb { 77 | ($e:expr) => ( 78 | { 79 | let t = $e; 80 | match t { 81 | ffi::MDB_SUCCESS => (), 82 | _ => return Err(MdbError::new_with_code(t)) 83 | } 84 | }) 85 | } 86 | 87 | macro_rules! assert_state_eq { 88 | ($log:ident, $cur:expr, $exp:expr) => 89 | ({ 90 | let c = $cur; 91 | let e = $exp; 92 | if c == e { 93 | () 94 | } else { 95 | let msg = format!("{} requires {:?}, is in {:?}", stringify!($log), c, e); 96 | return Err(StateError(msg)) 97 | }}) 98 | } 99 | 100 | macro_rules! assert_state_not { 101 | ($log:ident, $cur:expr, $exp:expr) => 102 | ({ 103 | let c = $cur; 104 | let e = $exp; 105 | if c != e { 106 | () 107 | } else { 108 | let msg = format!("{} shouldn't be in {:?}", stringify!($log), e); 109 | return Err(StateError(msg)) 110 | }}) 111 | } 112 | 113 | /// MdbError wraps information about LMDB error 114 | #[derive(Debug)] 115 | pub enum MdbError { 116 | NotFound, 117 | KeyExists, 118 | TxnFull, 119 | CursorFull, 120 | PageFull, 121 | Corrupted, 122 | Panic, 123 | InvalidPath, 124 | StateError(String), 125 | CacheError, 126 | Other(c_int, String) 127 | } 128 | 129 | 130 | impl MdbError { 131 | pub fn new_with_code(code: c_int) -> MdbError { 132 | match code { 133 | ffi::MDB_NOTFOUND => NotFound, 134 | ffi::MDB_KEYEXIST => KeyExists, 135 | ffi::MDB_TXN_FULL => TxnFull, 136 | ffi::MDB_CURSOR_FULL => CursorFull, 137 | ffi::MDB_PAGE_FULL => PageFull, 138 | ffi::MDB_CORRUPTED => Corrupted, 139 | ffi::MDB_PANIC => Panic, 140 | _ => Other(code, error_msg(code)) 141 | } 142 | } 143 | } 144 | 145 | 146 | impl std::fmt::Display for MdbError { 147 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 148 | match self { 149 | &NotFound | &KeyExists | &TxnFull | 150 | &CursorFull | &PageFull | &Corrupted | 151 | &Panic | &InvalidPath | &CacheError => write!(fmt, "{}", self.description()), 152 | &StateError(ref msg) => write!(fmt, "{}", msg), 153 | &Other(code, ref msg) => write!(fmt, "{}: {}", code, msg) 154 | } 155 | } 156 | } 157 | 158 | impl Error for MdbError { 159 | fn description(&self) -> &'static str { 160 | match self { 161 | &NotFound => "not found", 162 | &KeyExists => "key exists", 163 | &TxnFull => "txn full", 164 | &CursorFull => "cursor full", 165 | &PageFull => "page full", 166 | &Corrupted => "corrupted", 167 | &Panic => "panic", 168 | &InvalidPath => "invalid path for database", 169 | &StateError(_) => "state error", 170 | &CacheError => "db cache error", 171 | &Other(_, _) => "other error", 172 | } 173 | } 174 | } 175 | 176 | 177 | pub type MdbResult = Result; 178 | 179 | bitflags! { 180 | #[doc = "A set of environment flags which could be changed after opening"] 181 | 182 | pub flags EnvFlags: c_uint { 183 | 184 | #[doc="Don't flush system buffers to disk when committing a 185 | transaction. This optimization means a system crash can 186 | corrupt the database or lose the last transactions if buffers 187 | are not yet flushed to disk. The risk is governed by how 188 | often the system flushes dirty buffers to disk and how often 189 | mdb_env_sync() is called. However, if the filesystem 190 | preserves write order and the MDB_WRITEMAP flag is not used, 191 | transactions exhibit ACI (atomicity, consistency, isolation) 192 | properties and only lose D (durability). I.e. database 193 | integrity is maintained, but a system crash may undo the 194 | final transactions. Note that (MDB_NOSYNC | MDB_WRITEMAP) 195 | leaves the system with no hint for when to write transactions 196 | to disk, unless mdb_env_sync() is called. (MDB_MAPASYNC | 197 | MDB_WRITEMAP) may be preferable. This flag may be changed at 198 | any time using mdb_env_set_flags()."] 199 | const EnvNoSync = ffi::MDB_NOSYNC, 200 | 201 | #[doc="Flush system buffers to disk only once per transaction, 202 | omit the metadata flush. Defer that until the system flushes 203 | files to disk, or next non-MDB_RDONLY commit or 204 | mdb_env_sync(). This optimization maintains database 205 | integrity, but a system crash may undo the last committed 206 | transaction. I.e. it preserves the ACI (atomicity, 207 | consistency, isolation) but not D (durability) database 208 | property. This flag may be changed at any time using 209 | mdb_env_set_flags()."] 210 | const EnvNoMetaSync = ffi::MDB_NOMETASYNC, 211 | 212 | #[doc="When using MDB_WRITEMAP, use asynchronous flushes to 213 | disk. As with MDB_NOSYNC, a system crash can then corrupt the 214 | database or lose the last transactions. Calling 215 | mdb_env_sync() ensures on-disk database integrity until next 216 | commit. This flag may be changed at any time using 217 | mdb_env_set_flags()."] 218 | const EnvMapAsync = ffi::MDB_MAPASYNC, 219 | 220 | #[doc="Don't initialize malloc'd memory before writing to 221 | unused spaces in the data file. By default, memory for pages 222 | written to the data file is obtained using malloc. While 223 | these pages may be reused in subsequent transactions, freshly 224 | malloc'd pages will be initialized to zeroes before use. This 225 | avoids persisting leftover data from other code (that used 226 | the heap and subsequently freed the memory) into the data 227 | file. Note that many other system libraries may allocate and 228 | free memory from the heap for arbitrary uses. E.g., stdio may 229 | use the heap for file I/O buffers. This initialization step 230 | has a modest performance cost so some applications may want 231 | to disable it using this flag. This option can be a problem 232 | for applications which handle sensitive data like passwords, 233 | and it makes memory checkers like Valgrind noisy. This flag 234 | is not needed with MDB_WRITEMAP, which writes directly to the 235 | mmap instead of using malloc for pages. The initialization is 236 | also skipped if MDB_RESERVE is used; the caller is expected 237 | to overwrite all of the memory that was reserved in that 238 | case. This flag may be changed at any time using 239 | mdb_env_set_flags()."] 240 | const EnvNoMemInit = ffi::MDB_NOMEMINIT 241 | } 242 | } 243 | 244 | bitflags! { 245 | #[doc = "A set of all environment flags"] 246 | 247 | pub flags EnvCreateFlags: c_uint { 248 | #[doc="Use a fixed address for the mmap region. This flag must be"] 249 | #[doc=" specified when creating the environment, and is stored persistently"] 250 | #[doc=" in the environment. If successful, the memory map will always reside"] 251 | #[doc=" at the same virtual address and pointers used to reference data items"] 252 | #[doc=" in the database will be constant across multiple invocations. This "] 253 | #[doc="option may not always work, depending on how the operating system has"] 254 | #[doc=" allocated memory to shared libraries and other uses. The feature is highly experimental."] 255 | const EnvCreateFixedMap = ffi::MDB_FIXEDMAP, 256 | #[doc="By default, LMDB creates its environment in a directory whose"] 257 | #[doc=" pathname is given in path, and creates its data and lock files"] 258 | #[doc=" under that directory. With this option, path is used as-is"] 259 | #[doc=" for the database main data file. The database lock file is"] 260 | #[doc=" the path with \"-lock\" appended."] 261 | const EnvCreateNoSubDir = ffi::MDB_NOSUBDIR, 262 | #[doc="Don't flush system buffers to disk when committing a"] 263 | #[doc=" transaction. This optimization means a system crash can corrupt"] 264 | #[doc=" the database or lose the last transactions if buffers are not"] 265 | #[doc=" yet flushed to disk. The risk is governed by how often the"] 266 | #[doc=" system flushes dirty buffers to disk and how often"] 267 | #[doc=" mdb_env_sync() is called. However, if the filesystem preserves"] 268 | #[doc=" write order and the MDB_WRITEMAP flag is not used, transactions"] 269 | #[doc=" exhibit ACI (atomicity, consistency, isolation) properties and"] 270 | #[doc=" only lose D (durability). I.e. database integrity is"] 271 | #[doc=" maintained, but a system crash may undo the final"] 272 | #[doc=" transactions. Note that (MDB_NOSYNC | MDB_WRITEMAP) leaves"] 273 | #[doc=" the system with no hint for when to write transactions to"] 274 | #[doc=" disk, unless mdb_env_sync() is called."] 275 | #[doc=" (MDB_MAPASYNC | MDB_WRITEMAP) may be preferable. This flag"] 276 | #[doc=" may be changed at any time using mdb_env_set_flags()."] 277 | const EnvCreateNoSync = ffi::MDB_NOSYNC, 278 | #[doc="Open the environment in read-only mode. No write operations"] 279 | #[doc=" will be allowed. LMDB will still modify the lock file - except"] 280 | #[doc=" on read-only filesystems, where LMDB does not use locks."] 281 | const EnvCreateReadOnly = ffi::MDB_RDONLY, 282 | #[doc="Flush system buffers to disk only once per transaction,"] 283 | #[doc=" omit the metadata flush. Defer that until the system flushes"] 284 | #[doc=" files to disk, or next non-MDB_RDONLY commit or mdb_env_sync()."] 285 | #[doc=" This optimization maintains database integrity, but a system"] 286 | #[doc=" crash may undo the last committed transaction. I.e. it"] 287 | #[doc=" preserves the ACI (atomicity, consistency, isolation) but"] 288 | #[doc=" not D (durability) database property. This flag may be changed"] 289 | #[doc=" at any time using mdb_env_set_flags()."] 290 | const EnvCreateNoMetaSync = ffi::MDB_NOMETASYNC, 291 | #[doc="Use a writeable memory map unless MDB_RDONLY is set. This is"] 292 | #[doc="faster and uses fewer mallocs, but loses protection from"] 293 | #[doc="application bugs like wild pointer writes and other bad updates"] 294 | #[doc="into the database. Incompatible with nested"] 295 | #[doc="transactions. Processes with and without MDB_WRITEMAP on the"] 296 | #[doc="same environment do not cooperate well."] 297 | const EnvCreateWriteMap = ffi::MDB_WRITEMAP, 298 | #[doc="When using MDB_WRITEMAP, use asynchronous flushes to disk. As"] 299 | #[doc="with MDB_NOSYNC, a system crash can then corrupt the database or"] 300 | #[doc="lose the last transactions. Calling mdb_env_sync() ensures"] 301 | #[doc="on-disk database integrity until next commit. This flag may be"] 302 | #[doc="changed at any time using mdb_env_set_flags()."] 303 | const EnvCreataMapAsync = ffi::MDB_MAPASYNC, 304 | #[doc="Don't use Thread-Local Storage. Tie reader locktable slots to"] 305 | #[doc="ffi::MDB_txn objects instead of to threads. I.e. mdb_txn_reset()"] 306 | #[doc="keeps the slot reseved for the ffi::MDB_txn object. A thread may"] 307 | #[doc="use parallel read-only transactions. A read-only transaction may"] 308 | #[doc="span threads if the user synchronizes its use. Applications that"] 309 | #[doc="multiplex many user threads over individual OS threads need this"] 310 | #[doc="option. Such an application must also serialize the write"] 311 | #[doc="transactions in an OS thread, since LMDB's write locking is"] 312 | #[doc="unaware of the user threads."] 313 | const EnvCreateNoTls = ffi::MDB_NOTLS, 314 | #[doc="Don't do any locking. If concurrent access is anticipated, the"] 315 | #[doc="caller must manage all concurrency itself. For proper operation"] 316 | #[doc="the caller must enforce single-writer semantics, and must ensure"] 317 | #[doc="that no readers are using old transactions while a writer is"] 318 | #[doc="active. The simplest approach is to use an exclusive lock so"] 319 | #[doc="that no readers may be active at all when a writer begins. "] 320 | const EnvCreateNoLock = ffi::MDB_NOLOCK, 321 | #[doc="Turn off readahead. Most operating systems perform readahead on"] 322 | #[doc="read requests by default. This option turns it off if the OS"] 323 | #[doc="supports it. Turning it off may help random read performance"] 324 | #[doc="when the DB is larger than RAM and system RAM is full. The"] 325 | #[doc="option is not implemented on Windows."] 326 | const EnvCreateNoReadAhead = ffi::MDB_NORDAHEAD, 327 | #[doc="Don't initialize malloc'd memory before writing to unused spaces"] 328 | #[doc="in the data file. By default, memory for pages written to the"] 329 | #[doc="data file is obtained using malloc. While these pages may be"] 330 | #[doc="reused in subsequent transactions, freshly malloc'd pages will"] 331 | #[doc="be initialized to zeroes before use. This avoids persisting"] 332 | #[doc="leftover data from other code (that used the heap and"] 333 | #[doc="subsequently freed the memory) into the data file. Note that"] 334 | #[doc="many other system libraries may allocate and free memory from"] 335 | #[doc="the heap for arbitrary uses. E.g., stdio may use the heap for"] 336 | #[doc="file I/O buffers. This initialization step has a modest"] 337 | #[doc="performance cost so some applications may want to disable it"] 338 | #[doc="using this flag. This option can be a problem for applications"] 339 | #[doc="which handle sensitive data like passwords, and it makes memory"] 340 | #[doc="checkers like Valgrind noisy. This flag is not needed with"] 341 | #[doc="MDB_WRITEMAP, which writes directly to the mmap instead of using"] 342 | #[doc="malloc for pages. The initialization is also skipped if"] 343 | #[doc="MDB_RESERVE is used; the caller is expected to overwrite all of"] 344 | #[doc="the memory that was reserved in that case. This flag may be"] 345 | #[doc="changed at any time using mdb_env_set_flags()."] 346 | const EnvCreateNoMemInit = ffi::MDB_NOMEMINIT 347 | } 348 | } 349 | 350 | bitflags! { 351 | #[doc = "A set of database flags"] 352 | 353 | pub flags DbFlags: c_uint { 354 | #[doc="Keys are strings to be compared in reverse order, from the"] 355 | #[doc=" end of the strings to the beginning. By default, Keys are"] 356 | #[doc=" treated as strings and compared from beginning to end."] 357 | const DbReverseKey = ffi::MDB_REVERSEKEY, 358 | #[doc="Duplicate keys may be used in the database. (Or, from another"] 359 | #[doc="perspective, keys may have multiple data items, stored in sorted"] 360 | #[doc="order.) By default keys must be unique and may have only a"] 361 | #[doc="single data item."] 362 | const DbAllowDups = ffi::MDB_DUPSORT, 363 | #[doc="Keys are binary integers in native byte order. Setting this"] 364 | #[doc="option requires all keys to be the same size, typically"] 365 | #[doc="sizeof(int) or sizeof(size_t)."] 366 | const DbIntKey = ffi::MDB_INTEGERKEY, 367 | #[doc="This flag may only be used in combination with"] 368 | #[doc="ffi::MDB_DUPSORT. This option tells the library that the data"] 369 | #[doc="items for this database are all the same size, which allows"] 370 | #[doc="further optimizations in storage and retrieval. When all data"] 371 | #[doc="items are the same size, the ffi::MDB_GET_MULTIPLE and"] 372 | #[doc="ffi::MDB_NEXT_MULTIPLE cursor operations may be used to retrieve"] 373 | #[doc="multiple items at once."] 374 | const DbDupFixed = ffi::MDB_DUPFIXED, 375 | #[doc="This option specifies that duplicate data items are also"] 376 | #[doc="integers, and should be sorted as such."] 377 | const DbAllowIntDups = ffi::MDB_INTEGERDUP, 378 | #[doc="This option specifies that duplicate data items should be"] 379 | #[doc=" compared as strings in reverse order."] 380 | const DbReversedDups = ffi::MDB_REVERSEDUP, 381 | #[doc="Create the named database if it doesn't exist. This option"] 382 | #[doc=" is not allowed in a read-only transaction or a read-only"] 383 | #[doc=" environment."] 384 | const DbCreate = ffi::MDB_CREATE, 385 | } 386 | } 387 | 388 | /// Database 389 | #[derive(Debug)] 390 | pub struct Database<'a> { 391 | handle: ffi::MDB_dbi, 392 | txn: &'a NativeTransaction<'a>, 393 | } 394 | 395 | // FIXME: provide different interfaces for read-only/read-write databases 396 | // FIXME: provide different interfaces for simple KV and storage with duplicates 397 | 398 | impl<'a> Database<'a> { 399 | fn new_with_handle(handle: ffi::MDB_dbi, txn: &'a NativeTransaction<'a>) -> Database<'a> { 400 | Database { handle: handle, txn: txn } 401 | } 402 | 403 | /// Retrieves current db's statistics. 404 | pub fn stat(&'a self) -> MdbResult { 405 | self.txn.stat(self.handle) 406 | } 407 | 408 | /// Retrieves a value by key. In case of DbAllowDups it will be the first value 409 | pub fn get(&'a self, key: &ToMdbValue) -> MdbResult { 410 | self.txn.get(self.handle, key) 411 | } 412 | 413 | /// Sets value for key. In case of DbAllowDups it will add a new item 414 | pub fn set(&self, key: &ToMdbValue, value: &ToMdbValue) -> MdbResult<()> { 415 | self.txn.set(self.handle, key, value) 416 | } 417 | 418 | /// Appends new key-value pair to database, starting a new page instead of splitting an 419 | /// existing one if necessary. Requires that key be >= all existing keys in the database 420 | /// (or will return KeyExists error). 421 | pub fn append(&self, key: &K, value: &V) -> MdbResult<()> { 422 | self.txn.append(self.handle, key, value) 423 | } 424 | 425 | /// Appends new value for the given key (requires DbAllowDups), starting a new page instead 426 | /// of splitting an existing one if necessary. Requires that value be >= all existing values 427 | /// for the given key (or will return KeyExists error). 428 | pub fn append_duplicate(&self, key: &K, value: &V) -> MdbResult<()> { 429 | self.txn.append_duplicate(self.handle, key, value) 430 | } 431 | 432 | /// Set value for key. Fails if key already exists, even when duplicates are allowed. 433 | pub fn insert(&self, key: &ToMdbValue, value: &ToMdbValue) -> MdbResult<()> { 434 | self.txn.insert(self.handle, key, value) 435 | } 436 | 437 | /// Deletes value for key. 438 | pub fn del(&self, key: &ToMdbValue) -> MdbResult<()> { 439 | self.txn.del(self.handle, key) 440 | } 441 | 442 | /// Should be used only with DbAllowDups. Deletes corresponding (key, value) 443 | pub fn del_item(&self, key: &ToMdbValue, data: &ToMdbValue) -> MdbResult<()> { 444 | self.txn.del_item(self.handle, key, data) 445 | } 446 | 447 | /// Returns a new cursor 448 | pub fn new_cursor(&'a self) -> MdbResult> { 449 | self.txn.new_cursor(self.handle) 450 | } 451 | 452 | /// Deletes current db, also moves it out 453 | pub fn del_db(self) -> MdbResult<()> { 454 | self.txn.del_db(self) 455 | } 456 | 457 | /// Removes all key/values from db 458 | pub fn clear(&self) -> MdbResult<()> { 459 | self.txn.clear_db(self.handle) 460 | } 461 | 462 | /// Returns an iterator for all values in database 463 | pub fn iter(&'a self) -> MdbResult> { 464 | self.txn.new_cursor(self.handle) 465 | .and_then(|c| Ok(CursorIterator::wrap(c, CursorIter))) 466 | } 467 | 468 | /// Returns an iterator through keys starting with start_key (>=), start_key is included 469 | pub fn keyrange_from<'c, K: ToMdbValue + 'c>(&'c self, start_key: &'c K) -> MdbResult> { 470 | let cursor = try!(self.txn.new_cursor(self.handle)); 471 | let key_range = CursorFromKeyIter::new(start_key); 472 | let wrap = CursorIterator::wrap(cursor, key_range); 473 | Ok(wrap) 474 | } 475 | 476 | /// Returns an iterator through keys less than end_key, end_key is not included 477 | pub fn keyrange_to<'c, K: ToMdbValue + 'c>(&'c self, end_key: &'c K) -> MdbResult> { 478 | let cursor = try!(self.txn.new_cursor(self.handle)); 479 | let key_range = CursorToKeyIter::new(end_key); 480 | let wrap = CursorIterator::wrap(cursor, key_range); 481 | Ok(wrap) 482 | } 483 | 484 | /// Returns an iterator through keys `start_key <= x < end_key`. This is, start_key is 485 | /// included in the iteration, while end_key is kept excluded. 486 | pub fn keyrange_from_to<'c, K: ToMdbValue + 'c>(&'c self, start_key: &'c K, end_key: &'c K) 487 | -> MdbResult> 488 | { 489 | let cursor = try!(self.txn.new_cursor(self.handle)); 490 | let key_range = CursorKeyRangeIter::new(start_key, end_key, false); 491 | let wrap = CursorIterator::wrap(cursor, key_range); 492 | Ok(wrap) 493 | } 494 | 495 | /// Returns an iterator for values between start_key and end_key (included). 496 | /// Currently it works only for unique keys (i.e. it will skip 497 | /// multiple items when DB created with ffi::MDB_DUPSORT). 498 | /// Iterator is valid while cursor is valid 499 | pub fn keyrange<'c, K: ToMdbValue + 'c>(&'c self, start_key: &'c K, end_key: &'c K) 500 | -> MdbResult> 501 | { 502 | let cursor = try!(self.txn.new_cursor(self.handle)); 503 | let key_range = CursorKeyRangeIter::new(start_key, end_key, true); 504 | let wrap = CursorIterator::wrap(cursor, key_range); 505 | Ok(wrap) 506 | } 507 | 508 | /// Returns an iterator for all items (i.e. values with same key) 509 | pub fn item_iter<'c, 'db: 'c, K: ToMdbValue>(&'db self, key: &'c K) -> MdbResult>> { 510 | let cursor = try!(self.txn.new_cursor(self.handle)); 511 | let inner_iter = CursorItemIter::<'c>::new(key); 512 | Ok(CursorIterator::<'c>::wrap(cursor, inner_iter)) 513 | } 514 | 515 | /// Sets the key compare function for this database. 516 | /// 517 | /// Warning: This function must be called before any data access functions 518 | /// are used, otherwise data corruption may occur. The same comparison 519 | /// function must be used by every program accessing the database, every 520 | /// time the database is used. 521 | /// 522 | /// If not called, keys are compared lexically, with shorter keys collating 523 | /// before longer keys. 524 | /// 525 | /// Setting lasts for the lifetime of the underlying db handle. 526 | pub fn set_compare(&self, cmp_fn: extern "C" fn(*const MDB_val, *const MDB_val) -> c_int) -> MdbResult<()> { 527 | lift_mdb!(unsafe { 528 | ffi::mdb_set_compare(self.txn.handle, self.handle, cmp_fn) 529 | }) 530 | } 531 | 532 | /// Sets the value comparison function for values of the same key in this database. 533 | /// 534 | /// Warning: This function must be called before any data access functions 535 | /// are used, otherwise data corruption may occur. The same dupsort 536 | /// function must be used by every program accessing the database, every 537 | /// time the database is used. 538 | /// 539 | /// If not called, values are compared lexically, with shorter values collating 540 | /// before longer values. 541 | /// 542 | /// Only used when DbAllowDups is true. 543 | /// Setting lasts for the lifetime of the underlying db handle. 544 | pub fn set_dupsort(&self, cmp_fn: extern "C" fn(*const MDB_val, *const MDB_val) -> c_int) -> MdbResult<()> { 545 | lift_mdb!(unsafe { 546 | ffi::mdb_set_dupsort(self.txn.handle, self.handle, cmp_fn) 547 | }) 548 | } 549 | } 550 | 551 | 552 | /// Constructs environment with settigs which couldn't be 553 | /// changed after opening. By default it tries to create 554 | /// corresponding dir if it doesn't exist, use `autocreate_dir()` 555 | /// to override that behavior 556 | #[derive(Copy, Clone, Debug)] 557 | pub struct EnvBuilder { 558 | flags: EnvCreateFlags, 559 | max_readers: Option, 560 | max_dbs: Option, 561 | map_size: Option, 562 | autocreate_dir: bool, 563 | } 564 | 565 | impl EnvBuilder { 566 | pub fn new() -> EnvBuilder { 567 | EnvBuilder { 568 | flags: EnvCreateFlags::empty(), 569 | max_readers: None, 570 | max_dbs: None, 571 | map_size: None, 572 | autocreate_dir: true, 573 | } 574 | } 575 | 576 | /// Sets environment flags 577 | pub fn flags(mut self, flags: EnvCreateFlags) -> EnvBuilder { 578 | self.flags = flags; 579 | self 580 | } 581 | 582 | /// Sets max concurrent readers operating on environment 583 | pub fn max_readers(mut self, max_readers: usize) -> EnvBuilder { 584 | self.max_readers = Some(max_readers); 585 | self 586 | } 587 | 588 | /// Set max number of databases 589 | pub fn max_dbs(mut self, max_dbs: usize) -> EnvBuilder { 590 | self.max_dbs = Some(max_dbs); 591 | self 592 | } 593 | 594 | /// Sets max environment size, i.e. size in memory/disk of 595 | /// all data 596 | pub fn map_size(mut self, map_size: u64) -> EnvBuilder { 597 | self.map_size = Some(map_size); 598 | self 599 | } 600 | 601 | /// Sets whetever `lmdb-rs` should try to autocreate dir with default 602 | /// permissions on opening (default is true) 603 | pub fn autocreate_dir(mut self, autocreate_dir: bool) -> EnvBuilder { 604 | self.autocreate_dir = autocreate_dir; 605 | self 606 | } 607 | 608 | /// Opens environment in specified path 609 | pub fn open>(self, path: P, perms: u32) -> MdbResult { 610 | let changeable_flags: EnvCreateFlags = EnvCreataMapAsync | EnvCreateNoMemInit | EnvCreateNoSync | EnvCreateNoMetaSync; 611 | 612 | let env: *mut ffi::MDB_env = ptr::null_mut(); 613 | unsafe { 614 | let p_env: *mut *mut ffi::MDB_env = std::mem::transmute(&env); 615 | let _ = try_mdb!(ffi::mdb_env_create(p_env)); 616 | } 617 | 618 | // Enable only flags which can be changed, otherwise it'll fail 619 | try_mdb!(unsafe { ffi::mdb_env_set_flags(env, self.flags.bits() & changeable_flags.bits(), 1)}); 620 | 621 | if let Some(map_size) = self.map_size { 622 | try_mdb!(unsafe { ffi::mdb_env_set_mapsize(env, map_size as size_t)}); 623 | } 624 | 625 | if let Some(max_readers) = self.max_readers { 626 | try_mdb!(unsafe { ffi::mdb_env_set_maxreaders(env, max_readers as u32)}); 627 | } 628 | 629 | if let Some(max_dbs) = self.max_dbs { 630 | try_mdb!(unsafe { ffi::mdb_env_set_maxdbs(env, max_dbs as u32)}); 631 | } 632 | 633 | if self.autocreate_dir { 634 | let _ = try!(EnvBuilder::check_path(&path, self.flags)); 635 | } 636 | 637 | let is_readonly = self.flags.contains(EnvCreateReadOnly); 638 | 639 | let res = unsafe { 640 | // FIXME: revert back once `convert` is stable 641 | // let c_path = path.as_os_str().to_cstring().unwrap(); 642 | let path_str = try!(path.as_ref().to_str().ok_or(MdbError::InvalidPath)); 643 | let c_path = try!(CString::new(path_str).map_err(|_| MdbError::InvalidPath)); 644 | 645 | ffi::mdb_env_open(mem::transmute(env), c_path.as_ref().as_ptr(), self.flags.bits(), 646 | perms as ffi::mdb_mode_t) 647 | }; 648 | 649 | drop(self); 650 | match res { 651 | ffi::MDB_SUCCESS => { 652 | Ok(Environment::from_raw(env, is_readonly)) 653 | }, 654 | _ => { 655 | unsafe { ffi::mdb_env_close(mem::transmute(env)); } 656 | Err(MdbError::new_with_code(res)) 657 | } 658 | } 659 | 660 | } 661 | 662 | fn check_path>(path: P, flags: EnvCreateFlags) -> MdbResult<()> { 663 | use std::{fs, io}; 664 | 665 | if flags.contains(EnvCreateNoSubDir) { 666 | // FIXME: check parent dir existence/absence 667 | warn!("checking for path in NoSubDir mode isn't implemented yet"); 668 | return Ok(()); 669 | } 670 | 671 | // There should be a directory before open 672 | match fs::metadata(&path) { 673 | Ok(meta) => { 674 | if meta.is_dir() { 675 | Ok(()) 676 | } else { 677 | Err(MdbError::InvalidPath) 678 | } 679 | }, 680 | Err(e) => { 681 | if e.kind() == io::ErrorKind::NotFound { 682 | fs::create_dir_all(path.as_ref().clone()).map_err(|e| { 683 | error!("failed to auto create dir: {}", e); 684 | MdbError::InvalidPath 685 | }) 686 | } else { 687 | Err(MdbError::InvalidPath) 688 | } 689 | } 690 | } 691 | } 692 | } 693 | 694 | #[derive(Debug)] 695 | struct EnvHandle(*mut ffi::MDB_env); 696 | 697 | impl Drop for EnvHandle { 698 | fn drop(&mut self) { 699 | unsafe { 700 | if self.0 != ptr::null_mut() { 701 | ffi::mdb_env_close(self.0); 702 | } 703 | } 704 | } 705 | } 706 | 707 | /// Represents LMDB Environment. Should be opened using `EnvBuilder` 708 | #[derive(Debug)] 709 | pub struct Environment { 710 | env: Arc, 711 | db_cache: Arc>>>, 712 | is_readonly: bool, // true if opened in 'read-only' mode 713 | } 714 | 715 | impl Environment { 716 | pub fn new() -> EnvBuilder { 717 | EnvBuilder::new() 718 | } 719 | 720 | fn from_raw(env: *mut ffi::MDB_env, is_readonly: bool) -> Environment { 721 | Environment { 722 | env: Arc::new(EnvHandle(env)), 723 | db_cache: Arc::new(Mutex::new(UnsafeCell::new(HashMap::new()))), 724 | is_readonly: is_readonly, 725 | } 726 | } 727 | 728 | /// Check for stale entries in the reader lock table. 729 | /// 730 | /// Returns the number of stale slots that were cleared. 731 | pub fn reader_check(&self) -> MdbResult { 732 | let mut dead: c_int = 0; 733 | lift_mdb!(unsafe { ffi::mdb_reader_check(self.env.0, &mut dead as *mut c_int)}, dead) 734 | } 735 | 736 | /// Retrieve environment statistics 737 | pub fn stat(&self) -> MdbResult { 738 | let mut tmp: ffi::MDB_stat = unsafe { std::mem::zeroed() }; 739 | lift_mdb!(unsafe { ffi::mdb_env_stat(self.env.0, &mut tmp)}, tmp) 740 | } 741 | 742 | pub fn info(&self) -> MdbResult { 743 | let mut tmp: ffi::MDB_envinfo = unsafe { std::mem::zeroed() }; 744 | lift_mdb!(unsafe { ffi::mdb_env_info(self.env.0, &mut tmp)}, tmp) 745 | } 746 | 747 | /// Sync environment to disk 748 | pub fn sync(&self, force: bool) -> MdbResult<()> { 749 | lift_mdb!(unsafe { ffi::mdb_env_sync(self.env.0, if force {1} else {0})}) 750 | } 751 | 752 | /// Sets map size. 753 | /// This can be called after [open](struct.EnvBuilder.html#method.open) if no transactions are active in this process. 754 | pub fn set_mapsize(&self, map_size: usize) -> MdbResult<()> { 755 | lift_mdb!(unsafe { ffi::mdb_env_set_mapsize(self.env.0, map_size as size_t)}) 756 | } 757 | 758 | /// This one sets only flags which are available for change even 759 | /// after opening, see also [get_flags](#method.get_flags) and [get_all_flags](#method.get_all_flags) 760 | pub fn set_flags(&mut self, flags: EnvFlags, turn_on: bool) -> MdbResult<()> { 761 | lift_mdb!(unsafe { 762 | ffi::mdb_env_set_flags(self.env.0, flags.bits(), if turn_on {1} else {0}) 763 | }) 764 | } 765 | 766 | /// Get flags of environment, which could be changed after it was opened 767 | /// use [get_all_flags](#method.get_all_flags) if you need also creation time flags 768 | pub fn get_flags(&self) -> MdbResult { 769 | let tmp = try!(self.get_all_flags()); 770 | Ok(EnvFlags::from_bits_truncate(tmp.bits())) 771 | } 772 | 773 | /// Get all flags of environment, including which were specified on creation 774 | /// See also [get_flags](#method.get_flags) if you're interested only in modifiable flags 775 | pub fn get_all_flags(&self) -> MdbResult { 776 | let mut flags: c_uint = 0; 777 | lift_mdb!(unsafe {ffi::mdb_env_get_flags(self.env.0, &mut flags)}, EnvCreateFlags::from_bits_truncate(flags)) 778 | } 779 | 780 | pub fn get_maxreaders(&self) -> MdbResult { 781 | let mut max_readers: c_uint = 0; 782 | lift_mdb!(unsafe { 783 | ffi::mdb_env_get_maxreaders(self.env.0, &mut max_readers) 784 | }, max_readers) 785 | } 786 | 787 | pub fn get_maxkeysize(&self) -> c_int { 788 | unsafe {ffi::mdb_env_get_maxkeysize(self.env.0)} 789 | } 790 | 791 | /// Creates a backup copy in specified file descriptor 792 | pub fn copy_to_fd(&self, fd: ffi::mdb_filehandle_t) -> MdbResult<()> { 793 | lift_mdb!(unsafe { ffi::mdb_env_copyfd(self.env.0, fd) }) 794 | } 795 | 796 | /// Gets file descriptor of this environment 797 | pub fn get_fd(&self) -> MdbResult { 798 | let mut fd = 0; 799 | lift_mdb!({ unsafe { ffi::mdb_env_get_fd(self.env.0, &mut fd) }}, fd) 800 | } 801 | 802 | /// Creates a backup copy in specified path 803 | // FIXME: check who is responsible for creating path: callee or caller 804 | pub fn copy_to_path>(&self, path: P) -> MdbResult<()> { 805 | // FIXME: revert back once `convert` is stable 806 | // let c_path = path.as_os_str().to_cstring().unwrap(); 807 | let path_str = try!(path.as_ref().to_str().ok_or(MdbError::InvalidPath)); 808 | let c_path = try!(CString::new(path_str).map_err(|_| MdbError::InvalidPath)); 809 | 810 | unsafe { 811 | lift_mdb!(ffi::mdb_env_copy(self.env.0, c_path.as_ref().as_ptr())) 812 | } 813 | } 814 | 815 | fn create_transaction(&self, parent: Option, flags: c_uint) -> MdbResult { 816 | let mut handle: *mut ffi::MDB_txn = ptr::null_mut(); 817 | let parent_handle = match parent { 818 | Some(t) => t.handle, 819 | _ => ptr::null_mut() 820 | }; 821 | 822 | lift_mdb!(unsafe { ffi::mdb_txn_begin(self.env.0, parent_handle, flags, &mut handle) }, 823 | NativeTransaction::new_with_handle(handle, flags as usize, self)) 824 | } 825 | 826 | /// Creates a new read-write transaction 827 | /// 828 | /// Use `get_reader` to get much faster lock-free alternative 829 | pub fn new_transaction(&self) -> MdbResult { 830 | if self.is_readonly { 831 | return Err(MdbError::StateError("Error: creating read-write transaction in read-only environment".to_owned())) 832 | } 833 | self.create_transaction(None, 0) 834 | .and_then(|txn| Ok(Transaction::new_with_native(txn))) 835 | } 836 | 837 | /// Creates a readonly transaction 838 | pub fn get_reader(&self) -> MdbResult { 839 | self.create_transaction(None, ffi::MDB_RDONLY) 840 | .and_then(|txn| Ok(ReadonlyTransaction::new_with_native(txn))) 841 | } 842 | 843 | fn _open_db(&self, db_name: & str, flags: DbFlags, force_creation: bool) -> MdbResult { 844 | debug!("Opening {} (create={}, read_only={})", db_name, force_creation, self.is_readonly); 845 | // From LMDB docs for mdb_dbi_open: 846 | // 847 | // This function must not be called from multiple concurrent 848 | // transactions. A transaction that uses this function must finish 849 | // (either commit or abort) before any other transaction may use 850 | // this function 851 | match self.db_cache.lock() { 852 | Err(_) => Err(MdbError::CacheError), 853 | Ok(guard) => { 854 | let ref cell = *guard; 855 | let cache = cell.get(); 856 | 857 | unsafe { 858 | if let Some(db) = (*cache).get(db_name) { 859 | debug!("Cached value for {}: {}", db_name, *db); 860 | return Ok(*db); 861 | } 862 | } 863 | 864 | let mut txn = { 865 | let txflags = if self.is_readonly { ffi::MDB_RDONLY } else { 0 }; 866 | try!(self.create_transaction(None, txflags)) 867 | }; 868 | let opt_name = if db_name.len() > 0 {Some(db_name)} else {None}; 869 | let flags = if force_creation {flags | DbCreate} else {flags - DbCreate}; 870 | 871 | let mut db: ffi::MDB_dbi = 0; 872 | let db_res = match opt_name { 873 | None => unsafe { ffi::mdb_dbi_open(txn.handle, ptr::null(), flags.bits(), &mut db) }, 874 | Some(db_name) => { 875 | let db_name = CString::new(db_name.as_bytes()).unwrap(); 876 | unsafe { 877 | ffi::mdb_dbi_open(txn.handle, db_name.as_ptr(), flags.bits(), &mut db) 878 | } 879 | } 880 | }; 881 | 882 | try_mdb!(db_res); 883 | try!(txn.commit()); 884 | 885 | debug!("Caching: {} -> {}", db_name, db); 886 | unsafe { 887 | (*cache).insert(db_name.to_owned(), db); 888 | }; 889 | 890 | Ok(db) 891 | } 892 | } 893 | } 894 | 895 | /// Opens existing DB 896 | pub fn get_db(& self, db_name: &str, flags: DbFlags) -> MdbResult { 897 | let db = try!(self._open_db(db_name, flags, false)); 898 | Ok(DbHandle {handle: db, flags: flags}) 899 | } 900 | 901 | /// Opens or creates a DB 902 | pub fn create_db(&self, db_name: &str, flags: DbFlags) -> MdbResult { 903 | let db = try!(self._open_db(db_name, flags, true)); 904 | Ok(DbHandle {handle: db, flags: flags}) 905 | } 906 | 907 | /// Opens default DB with specified flags 908 | pub fn get_default_db(&self, flags: DbFlags) -> MdbResult { 909 | self.get_db("", flags) 910 | } 911 | 912 | fn drop_db_from_cache(&self, handle: ffi::MDB_dbi) { 913 | match self.db_cache.lock() { 914 | Err(_) => (), 915 | Ok(guard) => { 916 | let ref cell = *guard; 917 | 918 | unsafe { 919 | let cache = cell.get(); 920 | 921 | let mut key = None; 922 | for (k, v) in (*cache).iter() { 923 | if *v == handle { 924 | key = Some(k); 925 | break; 926 | } 927 | } 928 | 929 | if let Some(key) = key { 930 | (*cache).remove(key); 931 | } 932 | } 933 | } 934 | } 935 | } 936 | } 937 | 938 | unsafe impl Sync for Environment {} 939 | unsafe impl Send for Environment {} 940 | 941 | impl Clone for Environment { 942 | fn clone(&self) -> Environment { 943 | Environment { 944 | env: self.env.clone(), 945 | db_cache: self.db_cache.clone(), 946 | is_readonly: self.is_readonly, 947 | } 948 | } 949 | } 950 | 951 | #[allow(dead_code)] 952 | #[derive(Copy, Clone, Debug)] 953 | /// A handle to a database 954 | /// 955 | /// It can be cached to avoid opening db on every access 956 | /// In the current state it is unsafe as other thread 957 | /// can ask to drop it. 958 | pub struct DbHandle { 959 | handle: ffi::MDB_dbi, 960 | flags: DbFlags 961 | } 962 | 963 | unsafe impl Sync for DbHandle {} 964 | unsafe impl Send for DbHandle {} 965 | 966 | #[derive(Copy, PartialEq, Debug, Eq, Clone)] 967 | enum TransactionState { 968 | Normal, // Normal, any operation possible 969 | Released, // Released (reset on readonly), has to be renewed 970 | Invalid, // Invalid, no further operation possible 971 | } 972 | 973 | #[derive(Debug)] 974 | struct NativeTransaction<'a> { 975 | handle: *mut ffi::MDB_txn, 976 | env: &'a Environment, 977 | flags: usize, 978 | state: TransactionState, 979 | } 980 | 981 | impl<'a> NativeTransaction<'a> { 982 | fn new_with_handle(h: *mut ffi::MDB_txn, flags: usize, env: &Environment) -> NativeTransaction { 983 | // debug!("new native txn"); 984 | NativeTransaction { 985 | handle: h, 986 | flags: flags, 987 | state: TransactionState::Normal, 988 | env: env, 989 | } 990 | } 991 | 992 | fn is_readonly(&self) -> bool { 993 | (self.flags as u32 & ffi::MDB_RDONLY) == ffi::MDB_RDONLY 994 | } 995 | 996 | fn commit(&mut self) -> MdbResult<()> { 997 | assert_state_eq!(txn, self.state, TransactionState::Normal); 998 | debug!("commit txn"); 999 | self.state = if self.is_readonly() { 1000 | TransactionState::Released 1001 | } else { 1002 | TransactionState::Invalid 1003 | }; 1004 | try_mdb!(unsafe { ffi::mdb_txn_commit(self.handle) } ); 1005 | Ok(()) 1006 | } 1007 | 1008 | fn abort(&mut self) { 1009 | if self.state != TransactionState::Normal { 1010 | debug!("Can't abort transaction: current state {:?}", self.state) 1011 | } else { 1012 | debug!("abort txn"); 1013 | unsafe { ffi::mdb_txn_abort(self.handle); } 1014 | self.state = if self.is_readonly() { 1015 | TransactionState::Released 1016 | } else { 1017 | TransactionState::Invalid 1018 | }; 1019 | } 1020 | } 1021 | 1022 | /// Resets read only transaction, handle is kept. Must be followed 1023 | /// by a call to `renew` 1024 | fn reset(&mut self) { 1025 | if self.state != TransactionState::Normal { 1026 | debug!("Can't reset transaction: current state {:?}", self.state); 1027 | } else { 1028 | unsafe { ffi::mdb_txn_reset(self.handle); } 1029 | self.state = TransactionState::Released; 1030 | } 1031 | } 1032 | 1033 | /// Acquires a new reader lock after it was released by reset 1034 | fn renew(&mut self) -> MdbResult<()> { 1035 | assert_state_eq!(txn, self.state, TransactionState::Released); 1036 | try_mdb!(unsafe {ffi::mdb_txn_renew(self.handle)}); 1037 | self.state = TransactionState::Normal; 1038 | Ok(()) 1039 | } 1040 | 1041 | fn new_child(&self, flags: c_uint) -> MdbResult { 1042 | let mut out: *mut ffi::MDB_txn = ptr::null_mut(); 1043 | try_mdb!(unsafe { ffi::mdb_txn_begin(ffi::mdb_txn_env(self.handle), self.handle, flags, &mut out) }); 1044 | Ok(NativeTransaction::new_with_handle(out, flags as usize, self.env)) 1045 | } 1046 | 1047 | /// Used in Drop to switch state 1048 | fn silent_abort(&mut self) { 1049 | if self.state == TransactionState::Normal { 1050 | debug!("silent abort"); 1051 | unsafe {ffi::mdb_txn_abort(self.handle);} 1052 | self.state = TransactionState::Invalid; 1053 | } 1054 | } 1055 | 1056 | fn get_value(&'a self, db: ffi::MDB_dbi, key: &ToMdbValue) -> MdbResult { 1057 | let mut key_val = key.to_mdb_value(); 1058 | unsafe { 1059 | let mut data_val: MdbValue = std::mem::zeroed(); 1060 | try_mdb!(ffi::mdb_get(self.handle, db, &mut key_val.value, &mut data_val.value)); 1061 | Ok(FromMdbValue::from_mdb_value(&data_val)) 1062 | } 1063 | } 1064 | 1065 | fn get(&'a self, db: ffi::MDB_dbi, key: &ToMdbValue) -> MdbResult { 1066 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1067 | self.get_value(db, key) 1068 | } 1069 | 1070 | fn set_value(&self, db: ffi::MDB_dbi, key: &ToMdbValue, value: &ToMdbValue) -> MdbResult<()> { 1071 | self.set_value_with_flags(db, key, value, 0) 1072 | } 1073 | 1074 | fn set_value_with_flags(&self, db: ffi::MDB_dbi, key: &ToMdbValue, value: &ToMdbValue, flags: c_uint) -> MdbResult<()> { 1075 | unsafe { 1076 | let mut key_val = key.to_mdb_value(); 1077 | let mut data_val = value.to_mdb_value(); 1078 | 1079 | lift_mdb!(ffi::mdb_put(self.handle, db, &mut key_val.value, &mut data_val.value, flags)) 1080 | } 1081 | } 1082 | 1083 | /// Sets a new value for key, in case of enabled duplicates 1084 | /// it actually appends a new value 1085 | // FIXME: think about creating explicit separation of 1086 | // all traits for databases with dup keys 1087 | fn set(&self, db: ffi::MDB_dbi, key: &ToMdbValue, value: &ToMdbValue) -> MdbResult<()> { 1088 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1089 | self.set_value(db, key, value) 1090 | } 1091 | 1092 | fn append(&self, db: ffi::MDB_dbi, key: &ToMdbValue, value: &ToMdbValue) -> MdbResult<()> { 1093 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1094 | self.set_value_with_flags(db, key, value, ffi::MDB_APPEND) 1095 | } 1096 | 1097 | fn append_duplicate(&self, db: ffi::MDB_dbi, key: &ToMdbValue, value: &ToMdbValue) -> MdbResult<()> { 1098 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1099 | self.set_value_with_flags(db, key, value, ffi::MDB_APPENDDUP) 1100 | } 1101 | 1102 | /// Set the value for key only if the key does not exist in the database, 1103 | /// even if the database supports duplicates. 1104 | fn insert(&self, db: ffi::MDB_dbi, key: &ToMdbValue, value: &ToMdbValue) -> MdbResult<()> { 1105 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1106 | self.set_value_with_flags(db, key, value, ffi::MDB_NOOVERWRITE) 1107 | } 1108 | 1109 | /// Deletes all values by key 1110 | fn del_value(&self, db: ffi::MDB_dbi, key: &ToMdbValue) -> MdbResult<()> { 1111 | unsafe { 1112 | let mut key_val = key.to_mdb_value(); 1113 | lift_mdb!(ffi::mdb_del(self.handle, db, &mut key_val.value, ptr::null_mut())) 1114 | } 1115 | } 1116 | 1117 | /// If duplicate keys are allowed deletes value for key which is equal to data 1118 | fn del_item(&self, db: ffi::MDB_dbi, key: &ToMdbValue, data: &ToMdbValue) -> MdbResult<()> { 1119 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1120 | unsafe { 1121 | let mut key_val = key.to_mdb_value(); 1122 | let mut data_val = data.to_mdb_value(); 1123 | 1124 | lift_mdb!(ffi::mdb_del(self.handle, db, &mut key_val.value, &mut data_val.value)) 1125 | } 1126 | } 1127 | 1128 | /// Deletes all values for key 1129 | fn del(&self, db: ffi::MDB_dbi, key: &ToMdbValue) -> MdbResult<()> { 1130 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1131 | self.del_value(db, key) 1132 | } 1133 | 1134 | /// Creates a new cursor in current transaction tied to db 1135 | fn new_cursor(&'a self, db: ffi::MDB_dbi) -> MdbResult> { 1136 | Cursor::new(self, db) 1137 | } 1138 | 1139 | /// Deletes provided database completely 1140 | fn del_db(&self, db: Database) -> MdbResult<()> { 1141 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1142 | unsafe { 1143 | self.env.drop_db_from_cache(db.handle); 1144 | lift_mdb!(ffi::mdb_drop(self.handle, db.handle, 1)) 1145 | } 1146 | } 1147 | 1148 | /// Empties provided database 1149 | fn clear_db(&self, db: ffi::MDB_dbi) -> MdbResult<()> { 1150 | assert_state_eq!(txn, self.state, TransactionState::Normal); 1151 | unsafe { 1152 | lift_mdb!(ffi::mdb_drop(self.handle, db, 0)) 1153 | } 1154 | } 1155 | 1156 | /// Retrieves provided database's statistics 1157 | fn stat(&self, db: ffi::MDB_dbi) -> MdbResult { 1158 | let mut tmp: ffi::MDB_stat = unsafe { std::mem::zeroed() }; 1159 | lift_mdb!(unsafe { ffi::mdb_stat(self.handle, db, &mut tmp)}, tmp) 1160 | } 1161 | 1162 | /* 1163 | fn get_db(&self, name: &str, flags: DbFlags) -> MdbResult { 1164 | self.env.get_db(name, flags) 1165 | .and_then(|db| Ok(Database::new_with_handle(db.handle, self))) 1166 | } 1167 | */ 1168 | 1169 | /* 1170 | fn get_or_create_db(&self, name: &str, flags: DbFlags) -> MdbResult { 1171 | self.get_db(name, flags | DbCreate) 1172 | } 1173 | */ 1174 | } 1175 | 1176 | impl<'a> Drop for NativeTransaction<'a> { 1177 | fn drop(&mut self) { 1178 | //debug!("Dropping native transaction!"); 1179 | self.silent_abort(); 1180 | } 1181 | } 1182 | 1183 | #[derive(Debug)] 1184 | pub struct Transaction<'a> { 1185 | inner: NativeTransaction<'a>, 1186 | } 1187 | 1188 | impl<'a> Transaction<'a> { 1189 | fn new_with_native(txn: NativeTransaction<'a>) -> Transaction<'a> { 1190 | Transaction { 1191 | inner: txn 1192 | } 1193 | } 1194 | 1195 | pub fn new_child(&self) -> MdbResult { 1196 | self.inner.new_child(0) 1197 | .and_then(|txn| Ok(Transaction::new_with_native(txn))) 1198 | } 1199 | 1200 | pub fn new_ro_child(&self) -> MdbResult { 1201 | self.inner.new_child(ffi::MDB_RDONLY) 1202 | .and_then(|txn| Ok(ReadonlyTransaction::new_with_native(txn))) 1203 | } 1204 | 1205 | /// Commits transaction, moves it out 1206 | pub fn commit(self) -> MdbResult<()> { 1207 | //self.inner.commit() 1208 | let mut t = self; 1209 | t.inner.commit() 1210 | } 1211 | 1212 | /// Aborts transaction, moves it out 1213 | pub fn abort(self) { 1214 | let mut t = self; 1215 | t.inner.abort(); 1216 | } 1217 | 1218 | pub fn bind(&self, db_handle: &DbHandle) -> Database { 1219 | Database::new_with_handle(db_handle.handle, &self.inner) 1220 | } 1221 | } 1222 | 1223 | 1224 | #[derive(Debug)] 1225 | pub struct ReadonlyTransaction<'a> { 1226 | inner: NativeTransaction<'a>, 1227 | } 1228 | 1229 | 1230 | impl<'a> ReadonlyTransaction<'a> { 1231 | fn new_with_native(txn: NativeTransaction<'a>) -> ReadonlyTransaction<'a> { 1232 | ReadonlyTransaction { 1233 | inner: txn, 1234 | } 1235 | } 1236 | 1237 | pub fn new_ro_child(&self) -> MdbResult { 1238 | self.inner.new_child(ffi::MDB_RDONLY) 1239 | .and_then(|txn| Ok(ReadonlyTransaction::new_with_native(txn))) 1240 | 1241 | } 1242 | 1243 | /// Aborts transaction. But readonly transaction could be 1244 | /// reused later by calling `renew` 1245 | pub fn abort(&mut self) { 1246 | self.inner.abort(); 1247 | } 1248 | 1249 | /// Resets read only transaction, handle is kept. Must be followed 1250 | /// by call to `renew` 1251 | pub fn reset(&mut self) { 1252 | self.inner.reset(); 1253 | } 1254 | 1255 | /// Acquires a new reader lock after transaction 1256 | /// `abort` or `reset` 1257 | pub fn renew(&mut self) -> MdbResult<()> { 1258 | self.inner.renew() 1259 | } 1260 | 1261 | pub fn bind(&self, db_handle: &DbHandle) -> Database { 1262 | Database::new_with_handle(db_handle.handle, &self.inner) 1263 | } 1264 | } 1265 | 1266 | /// Helper to determine the property of "less than or equal to" where 1267 | /// the "equal to" part is to be specified at runtime. 1268 | trait IsLess { 1269 | fn is_less(&self, or_equal: bool) -> bool; 1270 | } 1271 | 1272 | impl IsLess for Ordering { 1273 | fn is_less(&self, or_equal: bool) -> bool { 1274 | match (*self, or_equal) { 1275 | (Ordering::Less, _) => true, 1276 | (Ordering::Equal, true) => true, 1277 | _ => false, 1278 | } 1279 | } 1280 | } 1281 | 1282 | impl IsLess for MdbResult { 1283 | fn is_less(&self, or_equal: bool) -> bool { 1284 | match *self { 1285 | Ok(ord) => ord.is_less(or_equal), 1286 | Err(_) => false, 1287 | } 1288 | } 1289 | } 1290 | 1291 | #[derive(Debug)] 1292 | pub struct Cursor<'txn> { 1293 | handle: *mut ffi::MDB_cursor, 1294 | data_val: ffi::MDB_val, 1295 | key_val: ffi::MDB_val, 1296 | txn: &'txn NativeTransaction<'txn>, 1297 | db: ffi::MDB_dbi, 1298 | valid_key: bool, 1299 | } 1300 | 1301 | 1302 | impl<'txn> Cursor<'txn> { 1303 | fn new(txn: &'txn NativeTransaction, db: ffi::MDB_dbi) -> MdbResult> { 1304 | debug!("Opening cursor in {}", db); 1305 | let mut tmp: *mut ffi::MDB_cursor = std::ptr::null_mut(); 1306 | try_mdb!(unsafe { ffi::mdb_cursor_open(txn.handle, db, &mut tmp) }); 1307 | Ok(Cursor { 1308 | handle: tmp, 1309 | data_val: unsafe { std::mem::zeroed() }, 1310 | key_val: unsafe { std::mem::zeroed() }, 1311 | txn: txn, 1312 | db: db, 1313 | valid_key: false, 1314 | }) 1315 | } 1316 | 1317 | fn navigate(&mut self, op: ffi::MDB_cursor_op) -> MdbResult<()> { 1318 | self.valid_key = false; 1319 | 1320 | let res = unsafe { 1321 | ffi::mdb_cursor_get(self.handle, &mut self.key_val, &mut self.data_val, op) 1322 | }; 1323 | match res { 1324 | ffi::MDB_SUCCESS => { 1325 | // MDB_SET is the only cursor operation which doesn't 1326 | // writes back a new value. In this case any access to 1327 | // cursor key value should cause a cursor retrieval 1328 | // to get back pointer to database owned memory instead 1329 | // of value used to set the cursor as it might be 1330 | // already destroyed and there is no need to borrow it 1331 | self.valid_key = op != ffi::MDB_cursor_op::MDB_SET; 1332 | Ok(()) 1333 | }, 1334 | e => Err(MdbError::new_with_code(e)) 1335 | } 1336 | } 1337 | 1338 | fn move_to(&mut self, key: &K, value: Option<&V>, op: ffi::MDB_cursor_op) -> MdbResult<()> 1339 | where K: ToMdbValue, V: ToMdbValue { 1340 | self.key_val = key.to_mdb_value().value; 1341 | self.data_val = match value { 1342 | Some(v) => v.to_mdb_value().value, 1343 | _ => unsafe {std::mem::zeroed() } 1344 | }; 1345 | 1346 | self.navigate(op) 1347 | } 1348 | 1349 | /// Moves cursor to first entry 1350 | pub fn to_first(&mut self) -> MdbResult<()> { 1351 | self.navigate(ffi::MDB_cursor_op::MDB_FIRST) 1352 | } 1353 | 1354 | /// Moves cursor to last entry 1355 | pub fn to_last(&mut self) -> MdbResult<()> { 1356 | self.navigate(ffi::MDB_cursor_op::MDB_LAST) 1357 | } 1358 | 1359 | /// Moves cursor to first entry for key if it exists 1360 | pub fn to_key<'k, K: ToMdbValue>(&mut self, key: &'k K) -> MdbResult<()> { 1361 | self.move_to(key, None::<&MdbValue<'k>>, ffi::MDB_cursor_op::MDB_SET_KEY) 1362 | } 1363 | 1364 | /// Moves cursor to first entry for key greater than 1365 | /// or equal to ke 1366 | pub fn to_gte_key<'k, K: ToMdbValue>(&mut self, key: &'k K) -> MdbResult<()> { 1367 | self.move_to(key, None::<&MdbValue<'k>>, ffi::MDB_cursor_op::MDB_SET_RANGE) 1368 | } 1369 | 1370 | /// Moves cursor to specific item (for example, if cursor 1371 | /// already points to a correct key and you need to delete 1372 | /// a specific item through cursor) 1373 | pub fn to_item(&mut self, key: &K, value: & V) -> MdbResult<()> where K: ToMdbValue, V: ToMdbValue { 1374 | self.move_to(key, Some(value), ffi::MDB_cursor_op::MDB_GET_BOTH) 1375 | } 1376 | 1377 | /// Moves cursor to nearest item. 1378 | pub fn to_gte_item(&mut self, key: &K, value: & V) -> MdbResult<()> where K: ToMdbValue, V: ToMdbValue { 1379 | self.move_to(key, Some(value), ffi::MDB_cursor_op::MDB_GET_BOTH_RANGE) 1380 | } 1381 | 1382 | /// Moves cursor to next key, i.e. skip items 1383 | /// with duplicate keys 1384 | pub fn to_next_key(&mut self) -> MdbResult<()> { 1385 | self.navigate(ffi::MDB_cursor_op::MDB_NEXT_NODUP) 1386 | } 1387 | 1388 | /// Moves cursor to next item with the same key as current 1389 | pub fn to_next_item(&mut self) -> MdbResult<()> { 1390 | self.navigate(ffi::MDB_cursor_op::MDB_NEXT_DUP) 1391 | } 1392 | 1393 | /// Moves cursor to prev entry, i.e. skips items 1394 | /// with duplicate keys 1395 | pub fn to_prev_key(&mut self) -> MdbResult<()> { 1396 | self.navigate(ffi::MDB_cursor_op::MDB_PREV_NODUP) 1397 | } 1398 | 1399 | /// Moves cursor to prev item with the same key as current 1400 | pub fn to_prev_item(&mut self) -> MdbResult<()> { 1401 | self.navigate(ffi::MDB_cursor_op::MDB_PREV_DUP) 1402 | } 1403 | 1404 | /// Moves cursor to first item with the same key as current 1405 | pub fn to_first_item(&mut self) -> MdbResult<()> { 1406 | self.navigate(ffi::MDB_cursor_op::MDB_FIRST_DUP) 1407 | } 1408 | 1409 | /// Moves cursor to last item with the same key as current 1410 | pub fn to_last_item(&mut self) -> MdbResult<()> { 1411 | self.navigate(ffi::MDB_cursor_op::MDB_LAST_DUP) 1412 | } 1413 | 1414 | /// Retrieves current key/value as tuple 1415 | pub fn get<'a, T: FromMdbValue + 'a, U: FromMdbValue + 'a>(&'a mut self) -> MdbResult<(T, U)> { 1416 | let (k, v) = try!(self.get_plain()); 1417 | 1418 | unsafe { 1419 | Ok((FromMdbValue::from_mdb_value(mem::transmute(&k)), 1420 | FromMdbValue::from_mdb_value(mem::transmute(&v)))) 1421 | } 1422 | } 1423 | 1424 | /// Retrieves current value 1425 | pub fn get_value<'a, V: FromMdbValue + 'a>(&'a mut self) -> MdbResult { 1426 | let (_, v) = try!(self.get_plain()); 1427 | 1428 | unsafe { 1429 | Ok(FromMdbValue::from_mdb_value(mem::transmute(&v))) 1430 | } 1431 | } 1432 | 1433 | /// Retrieves current key 1434 | pub fn get_key<'a, K: FromMdbValue + 'a>(&'a mut self) -> MdbResult { 1435 | let (k, _) = try!(self.get_plain()); 1436 | 1437 | unsafe { 1438 | Ok(FromMdbValue::from_mdb_value(mem::transmute(&k))) 1439 | } 1440 | } 1441 | 1442 | /// Compares the cursor's current key with the specified other one. 1443 | #[inline] 1444 | fn cmp_key(&mut self, other: &MdbValue) -> MdbResult { 1445 | let (k, _) = try!(self.get_plain()); 1446 | let mut kval = k.value; 1447 | let cmp = unsafe { 1448 | ffi::mdb_cmp(self.txn.handle, self.db, &mut kval, mem::transmute(other)) 1449 | }; 1450 | Ok(match cmp { 1451 | n if n < 0 => Ordering::Less, 1452 | n if n > 0 => Ordering::Greater, 1453 | _ => Ordering::Equal, 1454 | }) 1455 | } 1456 | 1457 | #[inline] 1458 | fn ensure_key_valid(&mut self) -> MdbResult<()> { 1459 | // If key might be invalid simply perform cursor get to be sure 1460 | // it points to database memory instead of user one 1461 | if !self.valid_key { 1462 | unsafe { 1463 | try_mdb!(ffi::mdb_cursor_get(self.handle, &mut self.key_val, 1464 | ptr::null_mut(), 1465 | ffi::MDB_cursor_op::MDB_GET_CURRENT)); 1466 | } 1467 | self.valid_key = true; 1468 | } 1469 | Ok(()) 1470 | } 1471 | 1472 | #[inline] 1473 | fn get_plain(&mut self) -> MdbResult<(MdbValue<'txn>, MdbValue<'txn>)> { 1474 | try!(self.ensure_key_valid()); 1475 | let k = MdbValue {value: self.key_val, marker: ::std::marker::PhantomData}; 1476 | let v = MdbValue {value: self.data_val, marker: ::std::marker::PhantomData}; 1477 | 1478 | Ok((k, v)) 1479 | } 1480 | 1481 | #[allow(dead_code)] 1482 | // This one is used for debugging, so it's to OK to leave it for a while 1483 | fn dump_value(&self, prefix: &str) { 1484 | if self.valid_key { 1485 | println!("{}: key {:?}, data {:?}", prefix, 1486 | self.key_val, 1487 | self.data_val); 1488 | } 1489 | } 1490 | 1491 | fn set_value(&mut self, value: &V, flags: c_uint) -> MdbResult<()> { 1492 | try!(self.ensure_key_valid()); 1493 | self.data_val = value.to_mdb_value().value; 1494 | lift_mdb!(unsafe {ffi::mdb_cursor_put(self.handle, &mut self.key_val, &mut self.data_val, flags)}) 1495 | } 1496 | 1497 | pub fn set(&mut self, key: &K, value: &V, flags: c_uint) -> MdbResult<()> { 1498 | self.key_val = key.to_mdb_value().value; 1499 | self.valid_key = true; 1500 | let res = self.set_value(value, flags); 1501 | self.valid_key = false; 1502 | res 1503 | } 1504 | 1505 | /// Overwrites value for current item 1506 | /// Note: overwrites max cur_value.len() bytes 1507 | pub fn replace(&mut self, value: &V) -> MdbResult<()> { 1508 | let res = self.set_value(value, ffi::MDB_CURRENT); 1509 | self.valid_key = false; 1510 | res 1511 | } 1512 | 1513 | /// Adds a new item when created with allowed duplicates 1514 | pub fn add_item(&mut self, value: &V) -> MdbResult<()> { 1515 | let res = self.set_value(value, 0); 1516 | self.valid_key = false; 1517 | res 1518 | } 1519 | 1520 | fn del_value(&mut self, flags: c_uint) -> MdbResult<()> { 1521 | lift_mdb!(unsafe { ffi::mdb_cursor_del(self.handle, flags) }) 1522 | } 1523 | 1524 | /// Deletes current key 1525 | pub fn del(&mut self) -> MdbResult<()> { 1526 | self.del_all() 1527 | } 1528 | 1529 | /// Deletes only current item 1530 | /// 1531 | /// Note that it doesn't check anything so it is caller responsibility 1532 | /// to make sure that correct item is deleted if, for example, caller 1533 | /// wants to delete only items of current key 1534 | pub fn del_item(&mut self) -> MdbResult<()> { 1535 | let res = self.del_value(0); 1536 | self.valid_key = false; 1537 | res 1538 | } 1539 | 1540 | /// Deletes all items with same key as current 1541 | pub fn del_all(&mut self) -> MdbResult<()> { 1542 | self.del_value(ffi::MDB_NODUPDATA) 1543 | } 1544 | 1545 | /// Returns count of items with the same key as current 1546 | pub fn item_count(&self) -> MdbResult { 1547 | let mut tmp: size_t = 0; 1548 | lift_mdb!(unsafe {ffi::mdb_cursor_count(self.handle, &mut tmp)}, tmp) 1549 | } 1550 | 1551 | pub fn get_item<'k, K: ToMdbValue>(self, k: &'k K) -> CursorItemAccessor<'txn, 'k, K> { 1552 | CursorItemAccessor { 1553 | cursor: self, 1554 | key: k 1555 | } 1556 | } 1557 | } 1558 | 1559 | impl<'txn> Drop for Cursor<'txn> { 1560 | fn drop(&mut self) { 1561 | unsafe { ffi::mdb_cursor_close(self.handle) }; 1562 | } 1563 | } 1564 | 1565 | #[derive(Debug)] 1566 | pub struct CursorItemAccessor<'c, 'k, K: 'k> { 1567 | cursor: Cursor<'c>, 1568 | key: &'k K, 1569 | } 1570 | 1571 | impl<'k, 'c: 'k, K: ToMdbValue> CursorItemAccessor<'c, 'k, K> { 1572 | pub fn get<'a, V: FromMdbValue + 'a>(&'a mut self) -> MdbResult { 1573 | try!(self.cursor.to_key(self.key)); 1574 | self.cursor.get_value() 1575 | } 1576 | 1577 | pub fn add(&mut self, v: &V) -> MdbResult<()> { 1578 | self.cursor.set(self.key, v, 0) 1579 | } 1580 | 1581 | pub fn del(&mut self, v: &V) -> MdbResult<()> { 1582 | try!(self.cursor.to_item(self.key, v)); 1583 | self.cursor.del_item() 1584 | } 1585 | 1586 | pub fn del_all(&mut self) -> MdbResult<()> { 1587 | try!(self.cursor.to_key(self.key)); 1588 | self.cursor.del_all() 1589 | } 1590 | 1591 | pub fn into_inner(self) -> Cursor<'c> { 1592 | let tmp = self; 1593 | tmp.cursor 1594 | } 1595 | } 1596 | 1597 | 1598 | #[derive(Debug)] 1599 | pub struct CursorValue<'cursor> { 1600 | key: MdbValue<'cursor>, 1601 | value: MdbValue<'cursor>, 1602 | marker: ::std::marker::PhantomData<&'cursor ()>, 1603 | } 1604 | 1605 | /// CursorValue performs lazy data extraction from iterator 1606 | /// avoiding any data conversions and memory copy. Lifetime 1607 | /// is limited to iterator lifetime 1608 | impl<'cursor> CursorValue<'cursor> { 1609 | pub fn get_key(&'cursor self) -> T { 1610 | FromMdbValue::from_mdb_value(&self.key) 1611 | } 1612 | 1613 | pub fn get_value(&'cursor self) -> T { 1614 | FromMdbValue::from_mdb_value(&self.value) 1615 | } 1616 | 1617 | pub fn get(&'cursor self) -> (T, U) { 1618 | (FromMdbValue::from_mdb_value(&self.key), 1619 | FromMdbValue::from_mdb_value(&self.value)) 1620 | } 1621 | } 1622 | 1623 | /// Allows the cration of custom cursor iteration behaviours. 1624 | pub trait IterateCursor { 1625 | /// Returns true if initialization successful, for example that 1626 | /// the key exists. 1627 | fn init_cursor<'a, 'b: 'a>(&'a self, cursor: &mut Cursor<'b>) -> bool; 1628 | 1629 | /// Returns true if there is still data and iterator is in correct range 1630 | fn move_to_next<'iter, 'cursor: 'iter>(&'iter self, cursor: &'cursor mut Cursor<'cursor>) -> bool; 1631 | 1632 | /// Returns size hint considering current state of cursor 1633 | fn get_size_hint(&self, _cursor: &Cursor) -> (usize, Option) { 1634 | (0, None) 1635 | } 1636 | } 1637 | 1638 | 1639 | #[derive(Debug)] 1640 | pub struct CursorIterator<'c, I> { 1641 | inner: I, 1642 | has_data: bool, 1643 | cursor: Cursor<'c>, 1644 | marker: ::std::marker::PhantomData<&'c ()>, 1645 | } 1646 | 1647 | impl<'c, I: IterateCursor + 'c> CursorIterator<'c, I> { 1648 | fn wrap(cursor: Cursor<'c>, inner: I) -> CursorIterator<'c, I> { 1649 | let mut cursor = cursor; 1650 | let has_data = inner.init_cursor(&mut cursor); 1651 | CursorIterator { 1652 | inner: inner, 1653 | has_data: has_data, 1654 | cursor: cursor, 1655 | marker: ::std::marker::PhantomData, 1656 | } 1657 | } 1658 | 1659 | #[allow(dead_code)] 1660 | fn unwrap(self) -> Cursor<'c> { 1661 | self.cursor 1662 | } 1663 | } 1664 | 1665 | impl<'c, I: IterateCursor + 'c> Iterator for CursorIterator<'c, I> { 1666 | type Item = CursorValue<'c>; 1667 | 1668 | fn next(&mut self) -> Option> { 1669 | if !self.has_data { 1670 | None 1671 | } else { 1672 | match self.cursor.get_plain() { 1673 | Err(_) => None, 1674 | Ok((k, v)) => { 1675 | self.has_data = unsafe { self.inner.move_to_next(mem::transmute(&mut self.cursor)) }; 1676 | Some(CursorValue { 1677 | key: k, 1678 | value: v, 1679 | marker: ::std::marker::PhantomData 1680 | }) 1681 | } 1682 | } 1683 | } 1684 | } 1685 | 1686 | fn size_hint(&self) -> (usize, Option) { 1687 | self.inner.get_size_hint(&self.cursor) 1688 | } 1689 | } 1690 | 1691 | #[derive(Debug)] 1692 | pub struct CursorKeyRangeIter<'a> { 1693 | start_key: MdbValue<'a>, 1694 | end_key: MdbValue<'a>, 1695 | end_inclusive: bool, 1696 | marker: ::std::marker::PhantomData<&'a ()>, 1697 | } 1698 | 1699 | impl<'a> CursorKeyRangeIter<'a> { 1700 | pub fn new(start_key: &'a K, end_key: &'a K, end_inclusive: bool) -> CursorKeyRangeIter<'a> { 1701 | CursorKeyRangeIter { 1702 | start_key: start_key.to_mdb_value(), 1703 | end_key: end_key.to_mdb_value(), 1704 | end_inclusive: end_inclusive, 1705 | marker: ::std::marker::PhantomData, 1706 | } 1707 | } 1708 | } 1709 | 1710 | impl<'iter> IterateCursor for CursorKeyRangeIter<'iter> { 1711 | fn init_cursor<'a, 'b: 'a>(&'a self, cursor: & mut Cursor<'b>) -> bool { 1712 | let ok = unsafe { 1713 | cursor.to_gte_key(mem::transmute::<&'a MdbValue<'a>, &'b MdbValue<'b>>(&self.start_key)).is_ok() 1714 | }; 1715 | ok && cursor.cmp_key(&self.end_key).is_less(self.end_inclusive) 1716 | } 1717 | 1718 | fn move_to_next<'i, 'c: 'i>(&'i self, cursor: &'c mut Cursor<'c>) -> bool { 1719 | let moved = cursor.to_next_key().is_ok(); 1720 | if !moved { 1721 | false 1722 | } else { 1723 | cursor.cmp_key(&self.end_key).is_less(self.end_inclusive) 1724 | } 1725 | } 1726 | } 1727 | 1728 | #[derive(Debug)] 1729 | pub struct CursorFromKeyIter<'a> { 1730 | start_key: MdbValue<'a>, 1731 | marker: ::std::marker::PhantomData<&'a ()>, 1732 | } 1733 | 1734 | 1735 | impl<'a> CursorFromKeyIter<'a> { 1736 | pub fn new(start_key: &'a K) -> CursorFromKeyIter<'a> { 1737 | CursorFromKeyIter { 1738 | start_key: start_key.to_mdb_value(), 1739 | marker: ::std::marker::PhantomData 1740 | } 1741 | } 1742 | } 1743 | 1744 | impl<'iter> IterateCursor for CursorFromKeyIter<'iter> { 1745 | fn init_cursor<'a, 'b: 'a>(&'a self, cursor: & mut Cursor<'b>) -> bool { 1746 | unsafe { 1747 | cursor.to_gte_key(mem::transmute::<&'a MdbValue<'a>, &'b MdbValue<'b>>(&self.start_key)).is_ok() 1748 | } 1749 | } 1750 | 1751 | fn move_to_next<'i, 'c: 'i>(&'i self, cursor: &'c mut Cursor<'c>) -> bool { 1752 | cursor.to_next_key().is_ok() 1753 | } 1754 | } 1755 | 1756 | 1757 | #[derive(Debug)] 1758 | pub struct CursorToKeyIter<'a> { 1759 | end_key: MdbValue<'a>, 1760 | marker: ::std::marker::PhantomData<&'a ()>, 1761 | } 1762 | 1763 | 1764 | impl<'a> CursorToKeyIter<'a> { 1765 | pub fn new(end_key: &'a K) -> CursorToKeyIter<'a> { 1766 | CursorToKeyIter { 1767 | end_key: end_key.to_mdb_value(), 1768 | marker: ::std::marker::PhantomData, 1769 | } 1770 | } 1771 | } 1772 | 1773 | impl<'iter> IterateCursor for CursorToKeyIter<'iter> { 1774 | fn init_cursor<'a, 'b: 'a>(&'a self, cursor: & mut Cursor<'b>) -> bool { 1775 | let ok = cursor.to_first().is_ok(); 1776 | ok && cursor.cmp_key(&self.end_key).is_less(false) 1777 | } 1778 | 1779 | fn move_to_next<'i, 'c: 'i>(&'i self, cursor: &'c mut Cursor<'c>) -> bool { 1780 | let moved = cursor.to_next_key().is_ok(); 1781 | if !moved { 1782 | false 1783 | } else { 1784 | cursor.cmp_key(&self.end_key).is_less(false) 1785 | } 1786 | } 1787 | } 1788 | 1789 | #[allow(missing_copy_implementations)] 1790 | #[derive(Debug)] 1791 | pub struct CursorIter; 1792 | 1793 | 1794 | impl<'iter> IterateCursor for CursorIter { 1795 | fn init_cursor<'a, 'b: 'a>(&'a self, cursor: & mut Cursor<'b>) -> bool { 1796 | cursor.to_first().is_ok() 1797 | } 1798 | 1799 | fn move_to_next<'i, 'c: 'i>(&'i self, cursor: &'c mut Cursor<'c>) -> bool { 1800 | cursor.to_next_key().is_ok() 1801 | } 1802 | } 1803 | 1804 | 1805 | #[derive(Debug)] 1806 | pub struct CursorItemIter<'a> { 1807 | key: MdbValue<'a>, 1808 | marker: ::std::marker::PhantomData<&'a ()>, 1809 | } 1810 | 1811 | 1812 | impl<'a> CursorItemIter<'a> { 1813 | pub fn new(key: &'a K) -> CursorItemIter<'a> { 1814 | CursorItemIter { 1815 | key: key.to_mdb_value(), 1816 | marker: ::std::marker::PhantomData 1817 | } 1818 | } 1819 | } 1820 | 1821 | impl<'iter> IterateCursor for CursorItemIter<'iter> { 1822 | fn init_cursor<'a, 'b: 'a>(&'a self, cursor: & mut Cursor<'b>) -> bool { 1823 | unsafe { 1824 | cursor.to_key(mem::transmute::<&MdbValue, &'b MdbValue<'b>>(&self.key)).is_ok() 1825 | } 1826 | } 1827 | 1828 | fn move_to_next<'i, 'c: 'i>(&'i self, cursor: &'c mut Cursor<'c>) -> bool { 1829 | cursor.to_next_item().is_ok() 1830 | } 1831 | 1832 | fn get_size_hint(&self, c: &Cursor) -> (usize, Option) { 1833 | match c.item_count() { 1834 | Err(_) => (0, None), 1835 | Ok(cnt) => (0, Some(cnt as usize)) 1836 | } 1837 | } 1838 | } 1839 | 1840 | 1841 | #[derive(Copy, Clone, Debug)] 1842 | pub struct MdbValue<'a> { 1843 | value: MDB_val, 1844 | marker: ::std::marker::PhantomData<&'a ()>, 1845 | } 1846 | 1847 | impl<'a> MdbValue<'a> { 1848 | #[inline] 1849 | pub unsafe fn new(data: *const c_void, len: usize) -> MdbValue<'a> { 1850 | MdbValue { 1851 | value: MDB_val { 1852 | mv_data: data, 1853 | mv_size: len as size_t 1854 | }, 1855 | marker: ::std::marker::PhantomData 1856 | } 1857 | } 1858 | 1859 | #[inline] 1860 | pub unsafe fn from_raw(mdb_val: *const ffi::MDB_val) -> MdbValue<'a> { 1861 | MdbValue::new((*mdb_val).mv_data, (*mdb_val).mv_size as usize) 1862 | } 1863 | 1864 | #[inline] 1865 | pub fn new_from_sized(data: &'a T) -> MdbValue<'a> { 1866 | unsafe { 1867 | MdbValue::new(mem::transmute(data), mem::size_of::()) 1868 | } 1869 | } 1870 | 1871 | #[inline] 1872 | pub unsafe fn get_ref(&'a self) -> *const c_void { 1873 | self.value.mv_data 1874 | } 1875 | 1876 | #[inline] 1877 | pub fn get_size(&self) -> usize { 1878 | self.value.mv_size as usize 1879 | } 1880 | } 1881 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(trivial_casts)] 2 | #![allow(trivial_numeric_casts)] 3 | 4 | extern crate libc; 5 | 6 | #[macro_use] extern crate bitflags; 7 | #[macro_use] extern crate log; 8 | 9 | extern crate liblmdb_sys as ffi; 10 | 11 | pub use libc::c_int; 12 | pub use ffi::{mdb_filehandle_t, MDB_stat, MDB_envinfo, MDB_val}; 13 | pub use core::{EnvBuilder, Environment, EnvFlags, EnvCreateFlags}; 14 | pub use core::{Database, DbFlags, DbHandle}; 15 | pub use core::{Transaction, ReadonlyTransaction, MdbError, MdbValue}; 16 | pub use core::{Cursor, CursorValue, CursorIter, CursorKeyRangeIter}; 17 | pub use traits::{FromMdbValue, ToMdbValue}; 18 | 19 | pub mod core; 20 | pub mod traits; 21 | mod utils; 22 | 23 | #[cfg(test)] 24 | mod tests; 25 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::{self}; 3 | use std::path::{PathBuf}; 4 | use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; 5 | use std::sync::{Once, ONCE_INIT}; 6 | use std::thread; 7 | 8 | use libc::c_int; 9 | 10 | use core::{self, EnvBuilder, DbFlags, MdbValue, EnvNoMemInit, EnvNoMetaSync, KeyExists, MdbError}; 11 | use ffi::MDB_val; 12 | use traits::FromMdbValue; 13 | 14 | const USER_DIR: u32 = 0o777; 15 | static TEST_ROOT_DIR: &'static str = "test-dbs"; 16 | static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT; 17 | static INIT_DIR_ONCE: Once = ONCE_INIT; 18 | 19 | fn global_root() -> PathBuf { 20 | let mut path = env::current_exe().unwrap(); 21 | path.pop(); // chop off exe name 22 | path.pop(); // chop off 'debug' 23 | 24 | // If `cargo test` is run manually then our path looks like 25 | // `target/debug/foo`, in which case our `path` is already pointing at 26 | // `target`. If, however, `cargo test --target $target` is used then the 27 | // output is `target/$target/debug/foo`, so our path is pointing at 28 | // `target/$target`. Here we conditionally pop the `$target` name. 29 | if path.file_name().and_then(|s| s.to_str()) != Some("target") { 30 | path.pop(); 31 | } 32 | 33 | path.join(TEST_ROOT_DIR) 34 | } 35 | 36 | fn next_path() -> PathBuf { 37 | let root_dir = global_root(); 38 | 39 | INIT_DIR_ONCE.call_once(|| { 40 | if let Ok(root_meta) = fs::metadata(root_dir.clone()) { 41 | if root_meta.is_dir() { 42 | let _ = fs::remove_dir_all(&root_dir); 43 | } 44 | } 45 | assert!(fs::create_dir_all(&root_dir).is_ok()); 46 | }); 47 | 48 | let cur_id = NEXT_ID.fetch_add(1, Ordering::SeqCst); 49 | let res = root_dir.join(&format!("db-{}", cur_id)); 50 | println!("Testing db in {}", res.display()); 51 | res 52 | } 53 | 54 | #[test] 55 | fn test_environment() { 56 | let mut env = EnvBuilder::new() 57 | .max_readers(33) 58 | .open(&next_path(), USER_DIR).unwrap(); 59 | 60 | env.sync(true).unwrap(); 61 | 62 | let test_flags = EnvNoMemInit | EnvNoMetaSync; 63 | 64 | env.set_flags(test_flags, true).unwrap(); 65 | let new_flags = env.get_flags().unwrap(); 66 | assert!((new_flags & test_flags) == test_flags, "Get flags != set flags"); 67 | 68 | let db = env.get_default_db(DbFlags::empty()).unwrap(); 69 | let txn = env.new_transaction().unwrap(); 70 | let db = txn.bind(&db); 71 | 72 | let key = "hello"; 73 | let value = "world"; 74 | 75 | db.set(&key, &value).unwrap(); 76 | 77 | let v = db.get::<&str>(&key).unwrap(); 78 | assert!(v == value, "Written {} and read {}", &value, &v); 79 | } 80 | 81 | #[test] 82 | fn test_single_values() { 83 | let env = EnvBuilder::new() 84 | .max_dbs(5) 85 | .open(&next_path(), USER_DIR) 86 | .unwrap(); 87 | 88 | let db = env.get_default_db(DbFlags::empty()).unwrap(); 89 | let txn = env.new_transaction().unwrap(); 90 | let db = txn.bind(&db); 91 | 92 | let test_key1 = "key1"; 93 | let test_data1 = "value1"; 94 | let test_data2 = "value2"; 95 | 96 | assert!(db.get::<()>(&test_key1).is_err(), "Key shouldn't exist yet"); 97 | 98 | assert!(db.set(&test_key1, &test_data1).is_ok()); 99 | let v = db.get::<&str>(&test_key1).unwrap(); 100 | assert!(v == test_data1, "Data written differs from data read"); 101 | 102 | assert!(db.set(&test_key1, &test_data2).is_ok()); 103 | let v = db.get::<&str>(&test_key1).unwrap(); 104 | assert!(v == test_data2, "Data written differs from data read"); 105 | 106 | assert!(db.del(&test_key1).is_ok()); 107 | assert!(db.get::<()>(&test_key1).is_err(), "Key should be deleted"); 108 | } 109 | 110 | #[test] 111 | fn test_multiple_values() { 112 | let env = EnvBuilder::new() 113 | .max_dbs(5) 114 | .open(&next_path(), USER_DIR) 115 | .unwrap(); 116 | 117 | let db = env.get_default_db(core::DbAllowDups).unwrap(); 118 | let txn = env.new_transaction().unwrap(); 119 | let db = txn.bind(&db); 120 | 121 | let test_key1 = "key1"; 122 | let test_data1 = "value1"; 123 | let test_data2 = "value2"; 124 | 125 | assert!(db.get::<()>(&test_key1).is_err(), "Key shouldn't exist yet"); 126 | 127 | assert!(db.set(&test_key1, &test_data1).is_ok()); 128 | let v = db.get::<&str>(&test_key1).unwrap(); 129 | assert!(v == test_data1, "Data written differs from data read"); 130 | 131 | assert!(db.set(&test_key1, &test_data2).is_ok()); 132 | let v = db.get::<&str>(&test_key1).unwrap(); 133 | assert!(v == test_data1, "It should still return first value"); 134 | 135 | assert!(db.del_item(&test_key1, &test_data1).is_ok()); 136 | 137 | let v = db.get::<&str>(&test_key1).unwrap(); 138 | assert!(v == test_data2, "It should return second value"); 139 | assert!(db.del(&test_key1).is_ok()); 140 | 141 | assert!(db.get::<()>(&test_key1).is_err(), "Key shouldn't exist anymore!"); 142 | } 143 | 144 | #[test] 145 | fn test_append_duplicate() { 146 | let env = EnvBuilder::new() 147 | .max_dbs(5) 148 | .open(&next_path(), USER_DIR) 149 | .unwrap(); 150 | 151 | let db = env.get_default_db(core::DbAllowDups).unwrap(); 152 | let txn = env.new_transaction().unwrap(); 153 | let db = txn.bind(&db); 154 | 155 | let test_key1 = "key1"; 156 | let test_data1 = "value1"; 157 | let test_data2 = "value2"; 158 | 159 | assert!(db.append(&test_key1, &test_data1).is_ok()); 160 | let v = db.get::<&str>(&test_key1).unwrap(); 161 | assert!(v == test_data1, "Data written differs from data read"); 162 | 163 | assert!(db.append_duplicate(&test_key1, &test_data2).is_ok()); 164 | let v = db.get::<&str>(&test_key1).unwrap(); 165 | assert!(v == test_data1, "It should still return first value"); 166 | 167 | assert!(db.del_item(&test_key1, &test_data1).is_ok()); 168 | 169 | let v = db.get::<&str>(&test_key1).unwrap(); 170 | assert!(v == test_data2, "It should return second value"); 171 | 172 | match db.append_duplicate(&test_key1, &test_data1).err().unwrap() { 173 | KeyExists => (), 174 | _ => panic!("Expected KeyExists error") 175 | } 176 | } 177 | 178 | #[test] 179 | fn test_insert_values() { 180 | let env = EnvBuilder::new() 181 | .max_dbs(5) 182 | .open(&next_path(), USER_DIR) 183 | .unwrap(); 184 | 185 | let db = env.get_default_db(DbFlags::empty()).unwrap(); 186 | let txn = env.new_transaction().unwrap(); 187 | let db = txn.bind(&db); 188 | 189 | let test_key1 = "key1"; 190 | let test_data1 = "value1"; 191 | let test_data2 = "value2"; 192 | 193 | assert!(db.get::<()>(&test_key1).is_err(), "Key shouldn't exist yet"); 194 | 195 | assert!(db.set(&test_key1, &test_data1).is_ok()); 196 | let v = db.get::<&str>(&test_key1).unwrap(); 197 | assert!(v == test_data1, "Data written differs from data read"); 198 | 199 | assert!(db.insert(&test_key1, &test_data2).is_err(), "Inserting should fail if key exists"); 200 | 201 | assert!(db.del(&test_key1).is_ok()); 202 | assert!(db.get::<()>(&test_key1).is_err(), "Key should be deleted"); 203 | 204 | assert!(db.insert(&test_key1, &test_data2).is_ok(), "Inserting should succeed"); 205 | } 206 | 207 | #[test] 208 | fn test_resize_map() { 209 | use ffi::MDB_MAP_FULL; 210 | 211 | let env = EnvBuilder::new() 212 | .max_dbs(5) 213 | .map_size(0x1000u64) 214 | .open(&next_path(), USER_DIR) 215 | .unwrap(); 216 | 217 | let db = env.get_default_db(DbFlags::empty()).unwrap(); 218 | 219 | let mut key_idx = 0u64; 220 | let test_data: [u8; 0xFF] = [0x5A; 0xFF]; 221 | 222 | let mut write_closure = || { 223 | let txn = env.new_transaction().unwrap(); 224 | { 225 | let db = txn.bind(&db); 226 | let test_key = format!("key_{}", key_idx); 227 | try!(db.set(&test_key, &(&test_data[..]))); 228 | } 229 | key_idx += 1; 230 | txn.commit() 231 | }; 232 | // write data until running into 'MDB_MAP_FULL' error 233 | loop { 234 | match write_closure() { 235 | Err(MdbError::Other(MDB_MAP_FULL, _)) => { break; } 236 | Err(_) => panic!("unexpected db error"), 237 | _ => {} // continue 238 | } 239 | } 240 | 241 | // env should be still ok and resizable 242 | assert!(env.set_mapsize(0x100000usize).is_ok(), "Couldn't resize map"); 243 | 244 | // next write after resize should not fail 245 | let txn = env.new_transaction().unwrap(); 246 | { 247 | let db = txn.bind(&db); 248 | let test_key = "different_key"; 249 | assert!(db.set(&test_key, &(&test_data[..])).is_ok(), "set after resize failed"); 250 | } 251 | assert!(txn.commit().is_ok(), "Commit failed after resizing map"); 252 | } 253 | 254 | #[test] 255 | fn test_stat() { 256 | let env = EnvBuilder::new() 257 | .max_dbs(5) 258 | .open(&next_path(), USER_DIR) 259 | .unwrap(); 260 | 261 | // ~ the two dataset; each to end up in its own database 262 | let dss = [ 263 | // ~ keep the "default db" dataset here at the beginning (see 264 | // the assertion at the end of this test) 265 | ("", vec![("default", "db"), ("has", "some"), ("extras", "prepared")]), 266 | ("db1", vec![("foo", "bar"), ("quux", "qak")]), 267 | ("db2", vec![("a", "abc"), ("b", "bcd"), ("c", "cde"), ("d", "def")]), 268 | ("db3", vec![("hip", "hop")])]; 269 | 270 | // ~ create each db, populate it, and assert db.stat() for each seperately 271 | for &(name, ref ds) in &dss { 272 | let db = env.create_db(name, DbFlags::empty()).unwrap(); 273 | let tx = env.new_transaction().unwrap(); 274 | { 275 | let db = tx.bind(&db); 276 | for &(k, v) in ds { 277 | assert!(db.set(&k, &v).is_ok()); 278 | } 279 | // ~ verify the expected number of entries (key/value pairs) in the db 280 | let stat = db.stat().unwrap(); 281 | assert_eq!(ds.len() as usize, stat.ms_entries); 282 | } 283 | tx.commit().unwrap(); 284 | } 285 | 286 | // ~ now verify the number of data items in this _environment_ (this 287 | // is the number key/value pairs in the default database plus the 288 | // number of other databases) 289 | let stat = env.stat().unwrap(); 290 | assert_eq!(dss[0].1.len() + dss[1..].len(), stat.ms_entries); 291 | } 292 | 293 | 294 | #[test] 295 | fn test_cursors() { 296 | let env = EnvBuilder::new() 297 | .max_dbs(5) 298 | .open(&next_path(), USER_DIR) 299 | .unwrap(); 300 | 301 | let db = env.get_default_db(core::DbAllowDups).unwrap(); 302 | let txn = env.new_transaction().unwrap(); 303 | let db = txn.bind(&db); 304 | 305 | let test_key1 = "key1"; 306 | let test_key2 = "key2"; 307 | let test_values: Vec<&str> = vec!("value1", "value2", "value3", "value4"); 308 | 309 | assert!(db.get::<()>(&test_key1).is_err(), "Key shouldn't exist yet"); 310 | 311 | for t in test_values.iter() { 312 | let _ = db.set(&test_key1, t); 313 | let _ = db.set(&test_key2, t); 314 | } 315 | 316 | let mut cursor = db.new_cursor().unwrap(); 317 | assert!(cursor.to_first().is_ok()); 318 | 319 | assert!(cursor.to_key(&test_key1).is_ok()); 320 | assert!(cursor.item_count().unwrap() == 4); 321 | 322 | assert!(cursor.del_item().is_ok()); 323 | assert!(cursor.item_count().unwrap() == 3); 324 | 325 | assert!(cursor.to_key(&test_key1).is_ok()); 326 | let new_value = "testme"; 327 | 328 | assert!(cursor.replace(&new_value).is_ok()); 329 | { 330 | let (_, v) = cursor.get::<(), &str>().unwrap(); 331 | // NOTE: this asserting will work once new_value is 332 | // of the same length as it is inplace change 333 | assert!(v == new_value); 334 | } 335 | 336 | assert!(cursor.del_all().is_ok()); 337 | assert!(cursor.to_key(&test_key1).is_err()); 338 | 339 | assert!(cursor.to_key(&test_key2).is_ok()); 340 | } 341 | 342 | 343 | #[test] 344 | fn test_cursor_item_manip() { 345 | let env = EnvBuilder::new() 346 | .max_dbs(5) 347 | .open(&next_path(), USER_DIR) 348 | .unwrap(); 349 | 350 | let db = env.get_default_db(core::DbAllowDups | core::DbAllowIntDups).unwrap(); 351 | let txn = env.new_transaction().unwrap(); 352 | let db = txn.bind(&db); 353 | 354 | let test_key1 = "key1"; 355 | 356 | assert!(db.set(&test_key1, &3u64).is_ok()); 357 | 358 | let mut cursor = db.new_cursor().unwrap(); 359 | assert!(cursor.to_key(&test_key1).is_ok()); 360 | 361 | let values: Vec = db.item_iter(&test_key1).unwrap() 362 | .map(|cv| cv.get_value::()) 363 | .collect(); 364 | assert_eq!(values, vec![3u64]); 365 | 366 | assert!(cursor.add_item(&4u64).is_ok()); 367 | assert!(cursor.add_item(&5u64).is_ok()); 368 | 369 | let values: Vec = db.item_iter(&test_key1).unwrap() 370 | .map(|cv| cv.get_value::()) 371 | .collect(); 372 | assert_eq!(values, vec![3u64, 4, 5]); 373 | 374 | assert!(cursor.replace(&6u64).is_ok()); 375 | let values: Vec = db.item_iter(&test_key1).unwrap() 376 | .map(|cv| cv.get_value::()) 377 | .collect(); 378 | 379 | assert_eq!(values, vec![3u64, 4, 6]); 380 | } 381 | 382 | fn as_slices(v: &Vec) -> Vec<&str> { 383 | v.iter().map(|s| &s[..]).collect::>() 384 | } 385 | 386 | #[test] 387 | fn test_item_iter() { 388 | let env = EnvBuilder::new() 389 | .max_dbs(5) 390 | .open(&next_path(), USER_DIR) 391 | .unwrap(); 392 | 393 | let db = env.get_default_db(core::DbAllowDups).unwrap(); 394 | let txn = env.new_transaction().unwrap(); 395 | let db = txn.bind(&db); 396 | 397 | let test_key1 = "key1"; 398 | let test_data1 = "value1"; 399 | let test_data2 = "value2"; 400 | let test_key2 = "key2"; 401 | let test_key3 = "key3"; 402 | 403 | assert!(db.set(&test_key1, &test_data1).is_ok()); 404 | assert!(db.set(&test_key1, &test_data2).is_ok()); 405 | assert!(db.set(&test_key2, &test_data1).is_ok()); 406 | 407 | let iter = db.item_iter(&test_key1).unwrap(); 408 | let values: Vec = iter.map(|cv| cv.get_value::()).collect(); 409 | assert_eq!(as_slices(&values), vec![test_data1, test_data2]); 410 | 411 | let iter = db.item_iter(&test_key2).unwrap(); 412 | let values: Vec = iter.map(|cv| cv.get_value::()).collect(); 413 | assert_eq!(as_slices(&values), vec![test_data1]); 414 | 415 | let iter = db.item_iter(&test_key3).unwrap(); 416 | let values: Vec = iter.map(|cv| cv.get_value::()).collect(); 417 | assert_eq!(values.len(), 0); 418 | } 419 | 420 | #[test] 421 | fn test_db_creation() { 422 | let env = EnvBuilder::new() 423 | .max_dbs(5) 424 | .open(&next_path(), USER_DIR) 425 | .unwrap(); 426 | assert!(env.create_db("test-db", DbFlags::empty()).is_ok()); 427 | } 428 | 429 | #[test] 430 | fn test_read_only_txn() { 431 | let env = EnvBuilder::new() 432 | .max_dbs(5) 433 | .open(&next_path(), USER_DIR) 434 | .unwrap(); 435 | env.get_reader().unwrap(); 436 | } 437 | 438 | #[test] 439 | fn test_cursor_in_txns() { 440 | let env = EnvBuilder::new() 441 | .max_dbs(5) 442 | .open(&next_path(), USER_DIR) 443 | .unwrap(); 444 | 445 | { 446 | let db = env.create_db("test1", core::DbAllowDups | core::DbAllowIntDups).unwrap(); 447 | let txn = env.new_transaction().unwrap(); 448 | { 449 | let db = txn.bind(&db); 450 | 451 | let cursor = db.new_cursor(); 452 | assert!(cursor.is_ok()); 453 | } 454 | assert!(txn.commit().is_ok()); 455 | } 456 | 457 | { 458 | let db = env.create_db("test1", core::DbAllowDups | core::DbAllowIntDups).unwrap(); 459 | let txn = env.new_transaction().unwrap(); 460 | { 461 | let db = txn.bind(&db); 462 | 463 | let cursor = db.new_cursor(); 464 | assert!(cursor.is_ok()); 465 | } 466 | assert!(txn.commit().is_ok()); 467 | } 468 | } 469 | 470 | #[test] 471 | fn test_multithread_env() { 472 | let env = EnvBuilder::new() 473 | .max_dbs(5) 474 | .open(&next_path(), USER_DIR) 475 | .unwrap(); 476 | 477 | let shared_env = env.clone(); 478 | let key = "key"; 479 | let value = "value"; 480 | 481 | let _ = thread::spawn(move || { 482 | let db = shared_env.create_db("test1", DbFlags::empty()).unwrap(); 483 | let txn = shared_env.new_transaction().unwrap(); 484 | { 485 | let db = txn.bind(&db); 486 | assert!(db.set(&key, &value).is_ok()); 487 | } 488 | assert!(txn.commit().is_ok()); 489 | }).join(); 490 | 491 | let db = env.create_db("test1", DbFlags::empty()).unwrap(); 492 | let txn = env.get_reader().unwrap(); 493 | let db = txn.bind(&db); 494 | let value2: String = db.get(&key).unwrap(); 495 | assert_eq!(value, value2); 496 | } 497 | 498 | #[test] 499 | fn test_keyrange_to() { 500 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 501 | let db = env.get_default_db(core::DbIntKey).unwrap(); 502 | let keys: Vec = vec![1, 2, 3]; 503 | let values: Vec = vec![5, 6, 7]; 504 | 505 | // to avoid problems caused by updates 506 | assert_eq!(keys.len(), values.len()); 507 | 508 | let txn = env.new_transaction().unwrap(); 509 | { 510 | let db = txn.bind(&db); 511 | for (k, v) in keys.iter().zip(values.iter()) { 512 | assert!(db.set(k, v).is_ok()); 513 | } 514 | } 515 | assert!(txn.commit().is_ok()); 516 | 517 | let txn = env.get_reader().unwrap(); 518 | { 519 | let db = txn.bind(&db); 520 | 521 | let last_idx = keys.len() - 1; 522 | let last_key: u64 = keys[last_idx]; 523 | // last key is excluded 524 | let iter = db.keyrange_to(&last_key).unwrap(); 525 | 526 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 527 | assert_eq!(res, &values[..last_idx]); 528 | } 529 | } 530 | 531 | /// Test that selecting a key range with an upper bound smaller than 532 | /// the smallest key in the db yields an empty range. 533 | #[test] 534 | fn test_keyrange_to_init_cursor() { 535 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 536 | let db = env.get_default_db(core::DbIntKey).unwrap(); 537 | let recs: Vec<(u64, u64)> = vec![(10, 50), (11, 60), (12, 70)]; 538 | 539 | let txn = env.new_transaction().unwrap(); 540 | { 541 | let db = txn.bind(&db); 542 | for &(k, v) in recs.iter() { 543 | assert!(db.set(&k, &v).is_ok()); 544 | } 545 | } 546 | assert!(txn.commit().is_ok()); 547 | 548 | let txn = env.get_reader().unwrap(); 549 | { 550 | let db = txn.bind(&db); 551 | 552 | // last key is excluded 553 | let upper_bound: u64 = 1; 554 | let iter = db.keyrange_to(&upper_bound).unwrap(); 555 | 556 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 557 | assert_eq!(res, &[]); 558 | } 559 | } 560 | 561 | #[test] 562 | fn test_keyrange_from() { 563 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 564 | let db = env.get_default_db(core::DbIntKey).unwrap(); 565 | let keys: Vec = vec![1, 2, 3]; 566 | let values: Vec = vec![5, 6, 7]; 567 | 568 | // to avoid problems caused by updates 569 | assert_eq!(keys.len(), values.len()); 570 | 571 | let txn = env.new_transaction().unwrap(); 572 | { 573 | let db = txn.bind(&db); 574 | for (k, v) in keys.iter().zip(values.iter()) { 575 | assert!(db.set(k, v).is_ok()); 576 | } 577 | } 578 | assert!(txn.commit().is_ok()); 579 | 580 | let txn = env.get_reader().unwrap(); 581 | { 582 | let db = txn.bind(&db); 583 | 584 | let start_idx = 1; // second key 585 | let last_key: u64 = keys[start_idx]; 586 | let iter = db.keyrange_from(&last_key).unwrap(); 587 | 588 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 589 | assert_eq!(res, &values[start_idx..]); 590 | } 591 | } 592 | 593 | /// Test that selecting a key range with a lower bound greater than 594 | /// the biggest key in the db yields an empty range. 595 | #[test] 596 | fn test_keyrange_from_init_cursor() { 597 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 598 | let db = env.get_default_db(core::DbIntKey).unwrap(); 599 | let recs: Vec<(u64, u64)> = vec![(10, 50), (11, 60), (12, 70)]; 600 | 601 | let txn = env.new_transaction().unwrap(); 602 | { 603 | let db = txn.bind(&db); 604 | for &(k, v) in recs.iter() { 605 | assert!(db.set(&k, &v).is_ok()); 606 | } 607 | } 608 | assert!(txn.commit().is_ok()); 609 | 610 | let txn = env.get_reader().unwrap(); 611 | { 612 | let db = txn.bind(&db); 613 | 614 | // last key is excluded 615 | let lower_bound = recs[recs.len()-1].0 + 1; 616 | let iter = db.keyrange_from(&lower_bound).unwrap(); 617 | 618 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 619 | assert_eq!(res, &[]); 620 | } 621 | } 622 | 623 | #[test] 624 | fn test_keyrange() { 625 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 626 | let db = env.get_default_db(core::DbAllowDups | core::DbIntKey).unwrap(); 627 | let keys: Vec = vec![ 1, 2, 3, 4, 5, 6]; 628 | let values: Vec = vec![10, 11, 12, 13, 14, 15]; 629 | 630 | // to avoid problems caused by updates 631 | assert_eq!(keys.len(), values.len()); 632 | 633 | let txn = env.new_transaction().unwrap(); 634 | { 635 | let db = txn.bind(&db); 636 | for (k, v) in keys.iter().zip(values.iter()) { 637 | assert!(db.set(k, v).is_ok()); 638 | } 639 | } 640 | assert!(txn.commit().is_ok()); 641 | 642 | let txn = env.get_reader().unwrap(); 643 | { 644 | let db = txn.bind(&db); 645 | 646 | let start_idx = 1; 647 | let end_idx = 3; 648 | let iter = db.keyrange(&keys[start_idx], &keys[end_idx]).unwrap(); 649 | 650 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 651 | 652 | // +1 as Rust slices do not include end 653 | assert_eq!(res, &values[start_idx.. end_idx + 1]); 654 | } 655 | } 656 | 657 | /// Test that select a key range outside the available data correctly 658 | /// yields an empty range. 659 | #[test] 660 | fn test_keyrange_init_cursor() { 661 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 662 | let db = env.get_default_db(core::DbAllowDups | core::DbIntKey).unwrap(); 663 | let keys: Vec = vec![ 1, 2, 3, 4, 5, 6]; 664 | let values: Vec = vec![10, 11, 12, 13, 14, 15]; 665 | 666 | // to avoid problems caused by updates 667 | assert_eq!(keys.len(), values.len()); 668 | 669 | let txn = env.new_transaction().unwrap(); 670 | { 671 | let db = txn.bind(&db); 672 | for (k, v) in keys.iter().zip(values.iter()) { 673 | assert!(db.set(k, v).is_ok()); 674 | } 675 | } 676 | assert!(txn.commit().is_ok()); 677 | 678 | // test the cursor initialization before the available data range 679 | let txn = env.get_reader().unwrap(); 680 | { 681 | let db = txn.bind(&db); 682 | 683 | let start_key = 0u64; 684 | let end_key = 0u64; 685 | let iter = db.keyrange(&start_key, &end_key).unwrap(); 686 | 687 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 688 | assert_eq!(res, &[]); 689 | } 690 | 691 | // test the cursor initialization after the available data range 692 | { 693 | let db = txn.bind(&db); 694 | 695 | let start_key = 10; 696 | let end_key = 20; 697 | let iter = db.keyrange(&start_key, &end_key).unwrap(); 698 | 699 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 700 | assert!(res.is_empty()); 701 | } 702 | } 703 | 704 | #[test] 705 | fn test_keyrange_from_to() { 706 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 707 | let db = env.get_default_db(core::DbAllowDups | core::DbIntKey).unwrap(); 708 | let recs: Vec<(u64, u64)> = vec![(10, 11), (20, 21), (30, 31), (40, 41), (50, 51)]; 709 | 710 | let txn = env.new_transaction().unwrap(); 711 | { 712 | let db = txn.bind(&db); 713 | for &(k, v) in recs.iter() { 714 | assert!(db.set(&k, &v).is_ok()); 715 | } 716 | } 717 | assert!(txn.commit().is_ok()); 718 | 719 | let txn = env.get_reader().unwrap(); 720 | { 721 | let db = txn.bind(&db); 722 | 723 | let start_idx = 1; 724 | let end_idx = 3; 725 | let iter = db.keyrange_from_to(&recs[start_idx].0, &recs[end_idx].0).unwrap(); 726 | 727 | let res: Vec<_> = iter.map(|cv| cv.get_value::()).collect(); 728 | // ~ end_key must be excluded here 729 | let exp: Vec<_> = recs[start_idx .. end_idx].iter().map(|x| x.1).collect(); 730 | assert_eq!(res, exp); 731 | } 732 | } 733 | 734 | #[test] 735 | fn test_readonly_env() { 736 | let recs: Vec<(u32,u32)> = vec![(10, 11), (11, 12), (12, 13), (13,14)]; 737 | 738 | // ~ first create a new read-write environment with its default 739 | // database containing a few entries 740 | let path = next_path(); 741 | { 742 | let rw_env = EnvBuilder::new().open(&path, USER_DIR).unwrap(); 743 | let dbh = rw_env.get_default_db(core::DbIntKey).unwrap(); 744 | let tx = rw_env.new_transaction().unwrap(); 745 | { 746 | let db = tx.bind(&dbh); 747 | for &rec in recs.iter() { 748 | db.set(&rec.0, &rec.1).unwrap(); 749 | } 750 | } 751 | tx.commit().unwrap(); 752 | } 753 | 754 | // ~ now re-open the previously created database in read-only mode 755 | // and iterate the key/value pairs 756 | let ro_env = EnvBuilder::new() 757 | .flags(core::EnvCreateReadOnly) 758 | .open(&path, USER_DIR).unwrap(); 759 | let dbh = ro_env.get_default_db(core::DbIntKey).unwrap(); 760 | assert!(ro_env.new_transaction().is_err()); 761 | let mut tx = ro_env.get_reader().unwrap(); 762 | { 763 | let db = tx.bind(&dbh); 764 | let kvs: Vec<(u32,u32)> = db.iter().unwrap().map(|c| c.get()).collect(); 765 | assert_eq!(recs, kvs); 766 | } 767 | tx.abort(); 768 | } 769 | 770 | unsafe fn negative_if_odd_i32_val(val: *const MDB_val) -> i32 { 771 | let v = MdbValue::from_raw(val); 772 | let i = i32::from_mdb_value(&v); 773 | if i % 2 == 0 { 774 | i 775 | } else { 776 | -i 777 | } 778 | } 779 | 780 | // A nonsensical comparison function that sorts differently that byte-by-byte comparison 781 | extern "C" fn negative_odd_cmp_fn(lhs_val: *const MDB_val, rhs_val: *const MDB_val) -> c_int { 782 | unsafe { 783 | let lhs = negative_if_odd_i32_val(lhs_val); 784 | let rhs = negative_if_odd_i32_val(rhs_val); 785 | lhs - rhs 786 | } 787 | } 788 | 789 | #[test] 790 | fn test_compare() { 791 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 792 | let db_handle = env.get_default_db(DbFlags::empty()).unwrap(); 793 | let txn = env.new_transaction().unwrap(); 794 | let val: i32 = 0; 795 | { 796 | let db = txn.bind(&db_handle); 797 | assert!(db.set_compare(negative_odd_cmp_fn).is_ok()); 798 | 799 | let i: i32 = 2; 800 | db.set(&i, &val).unwrap(); 801 | let i: i32 = 3; 802 | db.set(&i, &val).unwrap(); 803 | } 804 | assert!(txn.commit().is_ok()); 805 | 806 | let txn = env.new_transaction().unwrap(); 807 | { 808 | let db = txn.bind(&db_handle); 809 | let i: i32 = 4; 810 | db.set(&i, &val).unwrap(); 811 | let i: i32 = 5; 812 | db.set(&i, &val).unwrap(); 813 | } 814 | assert!(txn.commit().is_ok()); 815 | 816 | let txn = env.new_transaction().unwrap(); 817 | { 818 | let db = txn.bind(&db_handle); 819 | let keys: Vec<_> = db.iter().unwrap().map(|cv| cv.get_key::()).collect(); 820 | assert_eq!(keys, [5, 3, 2, 4]); 821 | } 822 | assert!(txn.commit().is_ok()); 823 | } 824 | 825 | #[test] 826 | fn test_dupsort() { 827 | let env = EnvBuilder::new().open(&next_path(), USER_DIR).unwrap(); 828 | let db_handle = env.get_default_db(core::DbAllowDups).unwrap(); 829 | let txn = env.new_transaction().unwrap(); 830 | let key: i32 = 0; 831 | { 832 | let db = txn.bind(&db_handle); 833 | assert!(db.set_dupsort(negative_odd_cmp_fn).is_ok()); 834 | 835 | let i: i32 = 2; 836 | db.set(&key, &i).unwrap(); 837 | let i: i32 = 3; 838 | db.set(&key, &i).unwrap(); 839 | } 840 | assert!(txn.commit().is_ok()); 841 | 842 | let txn = env.new_transaction().unwrap(); 843 | { 844 | let db = txn.bind(&db_handle); 845 | let i: i32 = 4; 846 | db.set(&key, &i).unwrap(); 847 | let i: i32 = 5; 848 | db.set(&key, &i).unwrap(); 849 | } 850 | assert!(txn.commit().is_ok()); 851 | 852 | let txn = env.new_transaction().unwrap(); 853 | { 854 | let db = txn.bind(&db_handle); 855 | let vals: Vec<_> = db.item_iter(&key).unwrap().map(|cv| cv.get_value::()).collect(); 856 | assert_eq!(vals, [5, 3, 2, 4]); 857 | } 858 | assert!(txn.commit().is_ok()); 859 | } 860 | 861 | // ~ see #29 862 | #[test] 863 | fn test_conversion_to_vecu8() { 864 | let rec: (u32, Vec) = (10, vec![1,2,3,4,5]); 865 | 866 | let path = next_path(); 867 | let env = EnvBuilder::new().open(&path, USER_DIR).unwrap(); 868 | let db = env.get_default_db(core::DbIntKey).unwrap(); 869 | 870 | // ~ add our test record 871 | { 872 | let tx = env.new_transaction().unwrap(); 873 | { 874 | let db = tx.bind(&db); 875 | db.set(&rec.0, &rec.1).unwrap(); 876 | } 877 | tx.commit().unwrap(); 878 | } 879 | 880 | // ~ validate the behavior 881 | let tx = env.new_transaction().unwrap(); 882 | { 883 | let db = tx.bind(&db); 884 | { 885 | // ~ now retrieve a Vec and make sure it is dropped 886 | // earlier than our database handle 887 | let xs: Vec = db.get(&rec.0).unwrap(); 888 | assert_eq!(rec.1, xs); 889 | } 890 | } 891 | tx.abort(); 892 | } 893 | 894 | // ~ see #29 895 | #[test] 896 | fn test_conversion_to_string() { 897 | let rec: (u32, String) = (10, "hello, world".to_owned()); 898 | 899 | let path = next_path(); 900 | let env = EnvBuilder::new().open(&path, USER_DIR).unwrap(); 901 | let db = env.get_default_db(core::DbIntKey).unwrap(); 902 | 903 | // ~ add our test record 904 | { 905 | let tx = env.new_transaction().unwrap(); 906 | { 907 | let db = tx.bind(&db); 908 | db.set(&rec.0, &rec.1).unwrap(); 909 | } 910 | tx.commit().unwrap(); 911 | } 912 | 913 | // ~ validate the behavior 914 | let tx = env.new_transaction().unwrap(); 915 | { 916 | let db = tx.bind(&db); 917 | { 918 | // ~ now retrieve a String and make sure it is dropped 919 | // earlier than our database handle 920 | let xs: String = db.get(&rec.0).unwrap(); 921 | assert_eq!(rec.1, xs); 922 | } 923 | } 924 | tx.abort(); 925 | } 926 | 927 | /* 928 | #[test] 929 | fn test_compilation_of_moved_items() { 930 | let path = Path::new("dbcom"); 931 | test_db_in_path(&next_path(), || { 932 | let mut env = EnvBuilder::new() 933 | .max_dbs(5) 934 | .open(&next_path(), USER_DIR) 935 | .unwrap(); 936 | 937 | let db = env.get_default_db(DbFlags::empty()).unwrap(); 938 | let mut txn = env.new_transaction().unwrap(); 939 | 940 | txn.commit(); 941 | 942 | let test_key1 = "key1"; 943 | let test_data1 = "value1"; 944 | 945 | assert!(db.get::<()>(&txn, &test_key1).is_err(), "Key shouldn't exist yet"); // ~ERROR: use of moved value 946 | }) 947 | } 948 | */ 949 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | //! Conversion of data structures to and from MDB_val 2 | //! 3 | //! Since MDB_val is valid through whole transaction, it is kind of safe 4 | //! to keep plain data, i.e. to keep raw pointers and transmute them back 5 | //! and forward into corresponding data structures to avoid any unnecessary 6 | //! copying. 7 | //! 8 | //! `MdbValue` is a simple wrapper with bounded lifetime which should help 9 | //! keep it sane, i.e. provide compile errors when data retrieved outlives 10 | //! transaction. 11 | //! 12 | //! It would be extremely helpful to create `compile-fail` tests to ensure 13 | //! this, but unfortunately there is no way yet. 14 | 15 | 16 | use std::{self, mem, slice}; 17 | 18 | use core::MdbValue; 19 | use ffi::MDB_val; 20 | 21 | /// `ToMdbValue` is supposed to convert a value to a memory 22 | /// slice which `lmdb` uses to prevent multiple copying data 23 | /// multiple times. May be unsafe. 24 | 25 | pub trait ToMdbValue { 26 | fn to_mdb_value<'a>(&'a self) -> MdbValue<'a>; 27 | } 28 | 29 | /// `FromMdbValue` is supposed to reconstruct a value from 30 | /// memory slice. It allows to use zero copy where it is 31 | /// required. 32 | 33 | pub trait FromMdbValue { 34 | fn from_mdb_value(value: &MdbValue) -> Self; 35 | } 36 | 37 | impl ToMdbValue for Vec { 38 | fn to_mdb_value<'a>(&'a self) -> MdbValue<'a> { 39 | unsafe { 40 | MdbValue::new(std::mem::transmute(self.as_ptr()), self.len()) 41 | } 42 | } 43 | } 44 | 45 | impl ToMdbValue for String { 46 | fn to_mdb_value<'a>(&'a self) -> MdbValue<'a> { 47 | unsafe { 48 | let t: &'a str = self; 49 | MdbValue::new(std::mem::transmute(t.as_ptr()), t.len()) 50 | } 51 | } 52 | } 53 | 54 | impl<'a> ToMdbValue for &'a str { 55 | fn to_mdb_value<'b>(&'b self) -> MdbValue<'b> { 56 | unsafe { 57 | MdbValue::new(mem::transmute(self.as_ptr()), 58 | self.len()) 59 | } 60 | } 61 | } 62 | 63 | impl<'a> ToMdbValue for &'a [u8] { 64 | fn to_mdb_value<'b>(&'b self) -> MdbValue<'b> { 65 | unsafe { 66 | MdbValue::new(std::mem::transmute(self.as_ptr()), 67 | self.len()) 68 | } 69 | } 70 | } 71 | 72 | impl ToMdbValue for MDB_val { 73 | fn to_mdb_value<'a>(&'a self) -> MdbValue<'a> { 74 | unsafe { 75 | MdbValue::from_raw(self) 76 | } 77 | } 78 | } 79 | 80 | impl<'a> ToMdbValue for MdbValue<'a> { 81 | fn to_mdb_value<'b>(&'b self) -> MdbValue<'b> { 82 | *self 83 | } 84 | } 85 | 86 | 87 | impl FromMdbValue for String { 88 | fn from_mdb_value(value: &MdbValue) -> String { 89 | unsafe { 90 | let ptr = mem::transmute(value.get_ref()); 91 | let data: Vec = slice::from_raw_parts(ptr, value.get_size()).to_vec(); 92 | String::from_utf8(data).unwrap() 93 | } 94 | } 95 | } 96 | 97 | impl FromMdbValue for Vec { 98 | fn from_mdb_value(value: &MdbValue) -> Vec { 99 | unsafe { 100 | let ptr = mem::transmute(value.get_ref()); 101 | slice::from_raw_parts(ptr, value.get_size()).to_vec() 102 | } 103 | } 104 | } 105 | 106 | impl FromMdbValue for () { 107 | fn from_mdb_value(_: &MdbValue) { 108 | } 109 | } 110 | 111 | impl<'b> FromMdbValue for &'b str { 112 | fn from_mdb_value(value: &MdbValue) -> &'b str { 113 | unsafe { 114 | std::mem::transmute(slice::from_raw_parts(value.get_ref(), value.get_size())) 115 | } 116 | } 117 | } 118 | 119 | impl<'b> FromMdbValue for &'b [u8] { 120 | fn from_mdb_value(value: &MdbValue) -> &'b [u8] { 121 | unsafe { 122 | std::mem::transmute(slice::from_raw_parts(value.get_ref(), value.get_size())) 123 | } 124 | } 125 | } 126 | 127 | macro_rules! mdb_for_primitive { 128 | ($t:ty) => ( 129 | impl ToMdbValue for $t { 130 | fn to_mdb_value<'a>(&'a self) -> MdbValue<'a> { 131 | MdbValue::new_from_sized(self) 132 | } 133 | } 134 | 135 | impl FromMdbValue for $t { 136 | fn from_mdb_value(value: &MdbValue) -> $t { 137 | unsafe { 138 | let t: *mut $t = mem::transmute(value.get_ref()); 139 | *t 140 | } 141 | } 142 | } 143 | 144 | ) 145 | } 146 | 147 | mdb_for_primitive!(u8); 148 | mdb_for_primitive!(i8); 149 | mdb_for_primitive!(u16); 150 | mdb_for_primitive!(i16); 151 | mdb_for_primitive!(u32); 152 | mdb_for_primitive!(i32); 153 | mdb_for_primitive!(u64); 154 | mdb_for_primitive!(i64); 155 | mdb_for_primitive!(f32); 156 | mdb_for_primitive!(f64); 157 | mdb_for_primitive!(bool); 158 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use libc::c_int; 2 | use std::ffi::{CStr}; 3 | 4 | use ffi::mdb_strerror; 5 | 6 | pub fn error_msg(code: c_int) -> String { 7 | unsafe { 8 | String::from_utf8(CStr::from_ptr(mdb_strerror(code)).to_bytes().to_vec()).unwrap() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /up_doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | [ $TRAVIS_BRANCH = master ] 5 | [ $TRAVIS_PULL_REQUEST = false ] 6 | echo '' > target/doc/index.html 7 | pip install ghp-import --user $USER 8 | $HOME/.local/bin/ghp-import -n target/doc -m "Updated documentation [ci skip]" 9 | git push -q -f https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages > /dev/null 2>&1 10 | echo 'Pushed to gh-pages succesfully' 11 | rm target/doc/index.html 12 | mv target/doc . 13 | curl http://www.rust-ci.org/artifacts/put?t=$RUSTCI_TOKEN | sh 14 | --------------------------------------------------------------------------------