#![warn(missing_docs)]
extern crate bincode;
extern crate libc;
extern crate serde;
#[cfg(test)]
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
extern crate tempdir;
mod gdbm_sys;
mod error;
use std::ops::Drop;
use std::default::Default;
use std::path::Path;
use std::slice;
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
use std::os::raw::c_void as os_c_void;
use std::mem;
use serde::{Deserialize, Serialize};
use error::last_errno;
pub use error::{Error, GdbmError, GdbmResult};
const DEFAULT_MODE: i32 = 0o666;
#[derive(Debug)]
pub struct RwHandle {
handle: gdbm_sys::GDBM_FILE,
}
#[derive(Debug)]
pub struct ReadHandle(RwHandle);
#[derive(Debug, Default)]
pub struct GdbmOpener {
sync: bool,
no_lock: bool,
no_mmap: bool,
create: bool,
overwrite: bool,
readonly: bool,
block_size: i32,
}
#[derive(Debug)]
pub struct Entry<'a> {
datum: gdbm_sys::datum,
slice: &'a [u8],
}
#[derive(Debug)]
pub struct Key<'a>(Entry<'a>);
#[derive(Debug)]
pub struct Iter<'a> {
db: &'a RwHandle,
nxt_key: Option<gdbm_sys::datum>,
}
impl RwHandle {
pub fn store<K, V>(&mut self, key: K, value: &V) -> GdbmResult<()>
where
K: AsRef<[u8]>,
V: ?Sized + Serialize,
{
self.store_impl(key, value, true).map(|_| ())
}
pub fn store_checked<K, V>(&mut self, key: K, value: &V) -> GdbmResult<()>
where
K: AsRef<[u8]>,
V: ?Sized + Serialize,
{
let r = self.store_impl(key, value, false)?;
if r == 1 {
Err(Error::KeyExists)
} else {
Ok(())
}
}
fn store_impl<K, V>(&mut self, key: K, value: &V, replace: bool) -> GdbmResult<i32>
where
K: AsRef<[u8]>,
V: ?Sized + Serialize,
{
let bytes = bincode::serialize(value)?;
let key_d: gdbm_sys::datum = key.as_ref().into();
let value_d = gdbm_sys::datum {
dptr: bytes.as_ptr() as *mut i8,
dsize: bytes.len() as i32,
};
let flag = if replace {
gdbm_sys::GDBM_REPLACE
} else {
gdbm_sys::GDBM_INSERT
};
let result = unsafe { gdbm_sys::gdbm_store(self.handle, key_d, value_d, flag as i32) };
if result == -1 {
Err(GdbmError::from_last().into())
} else {
Ok(result)
}
}
pub fn fetch<K>(&self, key: K) -> GdbmResult<Entry>
where K: AsRef<[u8]>,
{
let key_d = key.as_ref().into();
let result = unsafe { gdbm_sys::gdbm_fetch(self.handle, key_d) };
if result.dptr.is_null() {
Err(GdbmError::from_last().into())
} else {
Ok(Entry::new(result))
}
}
pub fn remove<K>(&self, key: K) -> GdbmResult<bool>
where K: AsRef<[u8]>,
{
let key_d = key.as_ref().into();
let result = unsafe { gdbm_sys::gdbm_delete(self.handle, key_d) };
if result != 0 {
match Error::from_last() {
ref e if e.is_no_record() => Ok(false),
e => Err(e)
}
} else {
Ok(true)
}
}
pub fn count(&self) -> GdbmResult<usize> {
let mut count = 0_u64;
let count_ptr: *mut u64 = &mut count;
let r = unsafe { gdbm_sys::gdbm_count(self.handle, count_ptr) };
if r == -1 {
Err(GdbmError::from_last().into())
} else {
Ok(count as usize)
}
}
pub fn iter<'a>(&'a self) -> Iter<'a> {
Iter::new(self)
}
pub fn contains_key(&self, key: &[u8]) -> GdbmResult<bool> {
let key_d: gdbm_sys::datum = key.into();
let result = unsafe { gdbm_sys::gdbm_exists(self.handle, key_d) };
if result == 0 {
let errno = last_errno();
if errno != gdbm_sys::GDBM_NO_ERROR {
Err(errno.into())
} else {
Ok(false)
}
} else {
Ok(true)
}
}
pub fn sync(&self) {
unsafe { gdbm_sys::gdbm_sync(self.handle) }
}
pub fn reorganize(&mut self) -> GdbmResult<()> {
let result = unsafe { gdbm_sys::gdbm_reorganize(self.handle) };
if result != 0 {
Err(Error::from_last())
} else {
Ok(())
}
}
pub fn set_cache_size(&mut self, size: usize) -> GdbmResult<()> {
self.set_opt(gdbm_sys::GDBM_SETCACHESIZE, size);
Ok(())
}
pub fn get_cache_size(&self) -> GdbmResult<usize> {
Ok(self.get_opt(gdbm_sys::GDBM_GETCACHESIZE))
}
pub fn set_sync_mode(&mut self, mode: bool) -> GdbmResult<()> {
let mode = if mode { 1i32 } else { 0 };
self.set_opt(gdbm_sys::GDBM_SETSYNCMODE, mode);
Ok(())
}
pub fn get_sync_mode(&self) -> GdbmResult<bool> {
let r: i32 = self.get_opt(gdbm_sys::GDBM_GETSYNCMODE);
match r {
0 => Ok(false),
1 => Ok(true),
_ => Err(Error::from_last()),
}
}
pub fn set_max_mmap_size(&mut self, size: usize) -> GdbmResult<()> {
self.set_opt(gdbm_sys::GDBM_SETMAXMAPSIZE, size);
Ok(())
}
pub fn get_max_mmap_size(&self) -> GdbmResult<usize> {
Ok(self.get_opt(gdbm_sys::GDBM_GETMAXMAPSIZE))
}
pub fn set_mmap_enabled(&mut self, mode: bool) -> GdbmResult<()> {
let mode = if mode { 1i32 } else { 0 };
self.set_opt(gdbm_sys::GDBM_SETMMAP, mode);
Ok(())
}
pub fn get_mmap_enabled(&self) -> GdbmResult<bool> {
let r: i32 = self.get_opt(gdbm_sys::GDBM_GETMMAP);
match r {
0 => Ok(false),
1 => Ok(true),
_ => Err(Error::from_last()),
}
}
pub fn get_block_size(&self) -> GdbmResult<usize> {
Ok(self.get_opt(gdbm_sys::GDBM_GETBLOCKSIZE))
}
fn set_opt<T>(&self, opt: u32, value: T) {
let mut value = value;
let ptr = &mut value as *mut T;
let ptr = ptr as *mut os_c_void;
let size = mem::size_of::<T>() as i32;
unsafe { gdbm_sys::gdbm_setopt(self.handle, opt as i32, ptr, size) };
}
fn get_opt<T: Default>(&self, opt: u32) -> T {
let mut value = T::default();
let ptr = &mut value as *mut T;
let ptr = ptr as *mut os_c_void;
let size = mem::size_of::<T>() as i32;
unsafe { gdbm_sys::gdbm_setopt(self.handle, opt as i32, ptr, size) };
value
}
#[allow(dead_code)]
#[doc(hidden)]
pub fn dummy() -> RwHandle {
use std::ptr;
RwHandle { handle: ptr::null_mut() }
}
}
#[doc(hidden)]
impl Drop for RwHandle {
fn drop(&mut self) {
unsafe { gdbm_sys::gdbm_close(self.handle) }
}
}
impl ReadHandle {
pub fn fetch<K>(&self, key: K) -> GdbmResult<Entry>
where K: AsRef<[u8]>,
{
self.0.fetch(key)
}
pub fn count(&self) -> GdbmResult<usize> {
self.0.count()
}
pub fn iter<'a>(&'a self) -> Iter<'a> {
self.0.iter()
}
}
impl GdbmOpener {
pub fn new() -> Self {
Self::default()
}
pub fn create(&mut self, create: bool) -> &mut Self {
self.create = create;
self
}
pub fn overwrite(&mut self, overwrite: bool) -> &mut Self {
self.overwrite = overwrite;
self
}
pub fn sync(&mut self, sync: bool) -> &mut Self {
self.sync = sync;
self
}
pub fn no_lock(&mut self, no_lock: bool) -> &mut Self {
self.no_lock = no_lock;
self
}
pub fn no_mmap(&mut self, no_mmap: bool) -> &mut Self {
self.no_mmap = no_mmap;
self
}
pub fn readwrite<P: AsRef<Path>>(&self, path: P) -> GdbmResult<RwHandle> {
let path = path.as_ref();
let handle = self.gdbm_open(&path)?;
Ok(RwHandle { handle })
}
pub fn readonly<P: AsRef<Path>>(&mut self, path: P) -> GdbmResult<ReadHandle> {
self.readonly = true;
let db = self.readwrite(path)?;
Ok(ReadHandle(db))
}
fn gdbm_open(&self, path: &Path) -> GdbmResult<gdbm_sys::GDBM_FILE> {
let path = CString::new(path.as_os_str().as_bytes())?;
let path_ptr = path.as_ptr() as *mut i8;
let mut flags = gdbm_sys::GDBM_WRITER as i32;
if self.readonly {
flags = gdbm_sys::GDBM_READER as i32;
} else if self.overwrite {
flags = gdbm_sys::GDBM_NEWDB as i32;
} else if self.create {
flags = gdbm_sys::GDBM_WRCREAT as i32;
}
if self.sync {
flags |= gdbm_sys::GDBM_SYNC as i32
}
if self.no_lock {
flags |= gdbm_sys::GDBM_NOLOCK as i32
}
if self.no_mmap {
flags |= gdbm_sys::GDBM_NOMMAP as i32
}
eprintln!("opening with flags {}", flags);
let handle =
unsafe { gdbm_sys::gdbm_open(path_ptr, self.block_size, flags, DEFAULT_MODE, None) };
if handle.is_null() {
Err(GdbmError::from_last().into())
} else {
Ok(handle)
}
}
}
impl<'a> Entry<'a> {
fn new(datum: gdbm_sys::datum) -> Self {
let slice = unsafe { slice::from_raw_parts(datum.dptr as *const u8, datum.dsize as usize) };
Entry { datum, slice }
}
pub fn as_bytes(&self) -> &[u8] {
self.slice
}
pub fn deserialize<'de, T>(&'de self) -> Result<T, bincode::Error>
where
T: Deserialize<'de>,
{
bincode::deserialize(self.slice)
}
}
#[doc(hidden)]
impl<'a> Drop for Entry<'a> {
fn drop(&mut self) {
if self.datum.dptr.is_null() { return };
unsafe {
libc::free(self.datum.dptr as *mut libc::c_void);
}
}
}
impl<'a> Key<'a> {
pub fn as_bytes(&self) -> &[u8] {
self.0.slice
}
}
impl<'a> Iter<'a> {
fn new(db: &'a RwHandle) -> Self {
let firstkey = unsafe { gdbm_sys::gdbm_firstkey(db.handle) };
let nxt_key = if firstkey.dptr.is_null() {
None
} else {
Some(firstkey)
};
Iter { db, nxt_key }
}
}
#[doc(hidden)]
impl<'a> Iterator for Iter<'a> {
type Item = (Key<'a>, Entry<'a>);
fn next(&mut self) -> Option<Self::Item> {
if let Some(d) = self.nxt_key.take() {
let value_d = unsafe { gdbm_sys::gdbm_fetch(self.db.handle, d) };
let nxt = unsafe { gdbm_sys::gdbm_nextkey(self.db.handle, d) };
if value_d.dptr.is_null() {
return None;
}
if !nxt.dptr.is_null() {
self.nxt_key = Some(nxt);
} else {
}
Some((Key(Entry::new(d)), Entry::new(value_d)))
} else {
None
}
}
}
#[doc(hidden)]
impl<'a> Drop for Iter<'a> {
fn drop(&mut self) {
if let Some(datum) = self.nxt_key {
if !datum.dptr.is_null() {
unsafe {
libc::free(datum.dptr as *mut libc::c_void);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempdir::TempDir;
fn create_db(path: &Path) -> RwHandle {
assert!(!path.exists());
let db = GdbmOpener::new()
.create(true)
.readwrite(&path)
.expect("db creation failed");
assert!(path.exists());
db
}
#[test]
fn smoke_test() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("create.db");
let mut db = create_db(&db_path);
assert!(db_path.exists());
db.store("my key".as_bytes(), "my value").unwrap();
{
let entry = db.fetch("my key".as_bytes()).unwrap();
let s: &str = entry.deserialize().unwrap();
assert_eq!(s, "my value");
}
}
#[test]
fn test_readonly() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("readonly.db");
create_db(&db_path);
assert!(db_path.exists());
let _ = GdbmOpener::new()
.readonly(&db_path)
.expect("db read failed");
let _ = GdbmOpener::new()
.readonly(&db_path)
.expect("db 2nd read failed");
}
#[test]
fn test_modes() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("read.db");
assert!(!db_path.exists());
{
let mut db = GdbmOpener::new()
.create(true)
.readwrite(&db_path)
.expect("db creation failed");
db.store("read key".as_bytes(), "read value").unwrap();
assert!(db_path.exists());
}
{
assert!(db_path.exists());
let db = GdbmOpener::new()
.readonly(&db_path)
.expect("db open failed");
{
let entry = db.fetch("read key".as_bytes()).unwrap();
let s: String = entry.deserialize().unwrap();
assert_eq!(s, "read value");
}
}
{
let mut db = GdbmOpener::new()
.readwrite(&db_path)
.expect("db open for write failed");
db.store("write key".as_bytes(), "write value").unwrap();
{
let entry = db.fetch("write key".as_bytes()).unwrap();
let s: String = entry.deserialize().unwrap();
assert_eq!(s, "write value");
}
}
let db = GdbmOpener::new()
.overwrite(true)
.readwrite(&db_path)
.expect("db new failed");
let r = db.fetch("write key".as_bytes());
assert!(r.is_err());
}
#[test]
fn count() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("count.db");
let mut db = create_db(&db_path);
assert_eq!(db.count().unwrap(), 0);
for i in 0..5 {
db.store(format!("key {}", i).as_bytes(), &format!("value {}", i))
.unwrap();
}
assert_eq!(db.count().unwrap(), 5);
for i in 5..10 {
db.store(format!("key {}", i).as_bytes(), &format!("value {}", i))
.unwrap();
}
assert_eq!(db.count().unwrap(), 10);
}
#[test]
fn entry() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("entry.db");
let mut db = create_db(&db_path);
db.store("an int".as_bytes(), &6usize).unwrap();
{
let entry = db.fetch("an int".as_bytes()).unwrap();
let s: usize = entry.deserialize().unwrap();
assert_eq!(6, s);
}
let v = vec![4, 2, 0];
db.store("a vec".as_bytes(), &v).unwrap();
{
let entry = db.fetch("a vec".as_bytes()).unwrap();
let s: Vec<i32> = entry.deserialize().unwrap();
assert_eq!(s, v);
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct MyStruct<'a> {
name: &'a str,
count: usize,
maybe: Option<bool>,
}
let n = "me".to_string();
let s = MyStruct {
name: &n,
count: 808,
maybe: None,
};
db.store("a struct".as_bytes(), &s).unwrap();
let entry = db.fetch("a struct".as_bytes()).unwrap();
let r: MyStruct = entry.deserialize().unwrap();
assert_eq!(r, s);
}
#[test]
fn test_iter() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("iter.db");
let mut db = create_db(&db_path);
for i in 0..5 {
db.store(&vec![i as u8], &i).unwrap();
}
{
let iter = db.iter();
assert_eq!(5, iter.count());
}
let iter = db.iter();
let sum = iter.fold(0, |acc, (_, ent)| acc + ent.deserialize::<i32>().unwrap());
assert_eq!(sum, (0..5).sum());
}
#[test]
fn contains_delete() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("contains_del.db");
let mut db = create_db(&db_path);
for i in 0..5 {
db.store(format!("key {}", i).as_bytes(), &format!("value {}", i))
.unwrap();
}
assert!(db.contains_key("key 1".as_bytes()).unwrap());
assert!(!db.contains_key("key x".as_bytes()).unwrap());
db.remove("key 1".as_bytes()).unwrap();
assert!(!db.contains_key("key 1".as_bytes()).unwrap());
assert!(db.contains_key("key 2".as_bytes()).unwrap());
}
#[test]
#[allow(unused_must_use)]
fn set_cache_size() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("cache_sizes.db");
let mut db = create_db(&db_path);
let initial = db.get_cache_size().unwrap();
db.set_cache_size(420);
let after = db.get_cache_size().unwrap();
assert_ne!(initial, after);
db.set_cache_size(182);
let again = db.get_cache_size().unwrap();
assert_eq!(again, 420);
}
#[test]
#[allow(unused_must_use)]
fn sync_opts() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("sync_opt.db");
{
let db = create_db(&db_path);
assert_eq!(db.get_sync_mode().unwrap(), false);
}
let mut db = GdbmOpener::new()
.create(true)
.sync(true)
.readwrite(&db_path)
.expect("create for sync opt failed");
assert_eq!(db.get_sync_mode().unwrap(), true);
db.set_sync_mode(false);
assert_eq!(db.get_sync_mode().unwrap(), false);
db.set_sync_mode(true);
assert_eq!(db.get_sync_mode().unwrap(), true);
}
#[test]
#[allow(unused_must_use)]
fn mmap_opts() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("mmap_opt.db");
{
let db = create_db(&db_path);
assert_eq!(db.get_mmap_enabled().unwrap(), true);
}
let mut db = GdbmOpener::new()
.create(true)
.no_mmap(true)
.readwrite(&db_path)
.expect("create for sync opt failed");
assert_eq!(db.get_mmap_enabled().unwrap(), false);
db.set_mmap_enabled(true);
assert_eq!(db.get_mmap_enabled().unwrap(), true);
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
db.set_max_mmap_size(page_size * 2);
assert_eq!(db.get_max_mmap_size().unwrap(), page_size * 2);
db.set_mmap_enabled(false);
assert_eq!(db.get_mmap_enabled().unwrap(), false);
}
#[test]
fn deletion() {
let dir = TempDir::new("rust_gdbm").unwrap();
let db_path = dir.path().join("delete.db");
let mut db = create_db(&db_path);
db.store("key", "value").unwrap();
assert!(db.remove("key").unwrap());
assert!(!db.remove("non-key").unwrap());
}
}