#![deny(missing_docs)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
mod generated {
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/tdb_sys.rs"));
#[repr(C)]
pub struct TDB_DATA {
pub dptr: *mut std::os::raw::c_uchar,
pub dsize: usize,
}
}
use generated::TDB_DATA;
use bitflags::bitflags;
use std::ffi::CStr;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, RawFd};
pub use libc::{O_CREAT, O_RDONLY, O_RDWR, O_TRUNC};
pub struct Tdb(*mut generated::tdb_context);
#[derive(Debug)]
pub enum Error {
Corrupt,
IO,
Lock,
OOM,
Exists,
NoLock,
LockTimeout,
ReadOnly,
NoExist,
Invalid,
Nesting,
}
bitflags! {
pub struct Flags: u32 {
const ClearIfFirst = generated::TDB_CLEAR_IF_FIRST;
const Internal = generated::TDB_INTERNAL;
const NoMmap = generated::TDB_NOMMAP;
const NoLock = generated::TDB_NOLOCK;
const NoSync = generated::TDB_SEQNUM;
const Seqnum = generated::TDB_SEQNUM;
const Volatile = generated::TDB_VOLATILE;
const AllowNesting = generated::TDB_ALLOW_NESTING;
const DisallowNesting = generated::TDB_DISALLOW_NESTING;
const IncompatibleHash = generated::TDB_INCOMPATIBLE_HASH;
const MutexLocking = generated::TDB_MUTEX_LOCKING;
}
}
impl Default for Flags {
fn default() -> Self {
Flags::empty()
}
}
#[repr(C)]
pub enum StoreFlags {
Insert = generated::TDB_INSERT as isize,
Replace = generated::TDB_REPLACE as isize,
Modify = generated::TDB_MODIFY as isize,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::Corrupt => f.write_str("Database is corrupt"),
Error::IO => f.write_str("I/O error"),
Error::Lock => f.write_str("Locked"),
Error::OOM => f.write_str("OOM"),
Error::Exists => f.write_str("Exists"),
Error::NoLock => f.write_str("NoLock"),
Error::LockTimeout => f.write_str("Lock timeout expired"),
Error::ReadOnly => f.write_str("Database is read-only"),
Error::NoExist => f.write_str("NoExist"),
Error::Invalid => f.write_str("Invalid"),
Error::Nesting => f.write_str("Nesting"),
}
}
}
impl std::error::Error for Error {}
impl From<u32> for Error {
fn from(e: u32) -> Self {
match e {
generated::TDB_ERROR_TDB_ERR_CORRUPT => Error::Corrupt,
generated::TDB_ERROR_TDB_ERR_IO => Error::IO,
generated::TDB_ERROR_TDB_ERR_LOCK => Error::Lock,
generated::TDB_ERROR_TDB_ERR_OOM => Error::OOM,
generated::TDB_ERROR_TDB_ERR_EXISTS => Error::Exists,
generated::TDB_ERROR_TDB_ERR_NOLOCK => Error::NoLock,
generated::TDB_ERROR_TDB_ERR_LOCK_TIMEOUT => Error::LockTimeout,
generated::TDB_ERROR_TDB_ERR_RDONLY => Error::ReadOnly,
generated::TDB_ERROR_TDB_ERR_NOEXIST => Error::NoExist,
generated::TDB_ERROR_TDB_ERR_EINVAL => Error::Invalid,
generated::TDB_ERROR_TDB_ERR_NESTING => Error::Nesting,
_ => panic!("Unknown error code: {}", e),
}
}
}
impl From<i32> for Error {
fn from(e: i32) -> Self {
From::<u32>::from(e as u32)
}
}
impl From<Vec<u8>> for TDB_DATA {
fn from(mut data: Vec<u8>) -> Self {
let ptr = data.as_mut_ptr() as *mut std::os::raw::c_uchar;
let len = data.len();
let cap = data.capacity();
std::mem::forget(data);
debug_assert_eq!(len, cap, "Vector should be at exact capacity");
TDB_DATA {
dptr: ptr,
dsize: len,
}
}
}
impl Drop for TDB_DATA {
fn drop(&mut self) {
if !self.dptr.is_null() {
unsafe {
libc::free(self.dptr as *mut libc::c_void);
}
}
}
}
impl Clone for TDB_DATA {
fn clone(&self) -> Self {
if self.dsize == 0 || self.dptr.is_null() {
return TDB_DATA {
dptr: std::ptr::null_mut(),
dsize: 0,
};
}
unsafe {
let ptr = libc::malloc(self.dsize) as *mut std::os::raw::c_uchar;
if ptr.is_null() {
panic!("Failed to allocate memory for TDB_DATA clone");
}
std::ptr::copy_nonoverlapping(self.dptr, ptr, self.dsize);
TDB_DATA {
dptr: ptr,
dsize: self.dsize,
}
}
}
}
impl From<TDB_DATA> for Vec<u8> {
fn from(mut data: TDB_DATA) -> Self {
let ret = unsafe { Vec::from_raw_parts(data.dptr, data.dsize, data.dsize) };
data.dptr = std::ptr::null_mut();
ret
}
}
#[repr(C)]
struct CONST_TDB_DATA {
pub dptr: *const std::os::raw::c_uchar,
pub dsize: usize,
}
impl From<&[u8]> for CONST_TDB_DATA {
fn from(data: &[u8]) -> Self {
CONST_TDB_DATA {
dptr: data.as_ptr(),
dsize: data.len(),
}
}
}
extern "C" {
fn tdb_fetch(tdb: *mut generated::tdb_context, key: CONST_TDB_DATA) -> TDB_DATA;
fn tdb_store(
tdb: *mut generated::tdb_context,
key: CONST_TDB_DATA,
dbuf: CONST_TDB_DATA,
flag: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
fn tdb_append(
tdb: *mut generated::tdb_context,
key: CONST_TDB_DATA,
new_dbuf: CONST_TDB_DATA,
) -> ::std::os::raw::c_int;
fn tdb_exists(tdb: *mut generated::tdb_context, key: CONST_TDB_DATA) -> bool;
fn tdb_delete(tdb: *mut generated::tdb_context, key: CONST_TDB_DATA) -> ::std::os::raw::c_int;
fn tdb_nextkey(tdb: *mut generated::tdb_context, key: CONST_TDB_DATA) -> TDB_DATA;
}
impl Tdb {
pub fn open<P: AsRef<std::path::Path>>(
name: P,
hash_size: Option<u32>,
tdb_flags: Flags,
open_flags: i32,
mode: generated::mode_t,
) -> Option<Tdb> {
let name = name.as_ref();
let hash_size = hash_size.unwrap_or(0);
let c_name = std::ffi::CString::new(name.as_os_str().as_bytes()).ok()?;
let ret = unsafe {
generated::tdb_open(
c_name.as_ptr(),
hash_size as i32,
tdb_flags.bits() as i32,
open_flags,
mode,
)
};
if ret.is_null() {
None
} else {
Some(Tdb(ret))
}
}
pub fn memory(hash_size: Option<u32>, mut tdb_flags: Flags) -> Option<Tdb> {
let hash_size = hash_size.unwrap_or(0);
tdb_flags.insert(Flags::Internal);
let ret = unsafe {
generated::tdb_open(
c":memory:".as_ptr(),
hash_size as i32,
tdb_flags.bits() as i32,
O_RDWR | O_CREAT,
0,
)
};
if ret.is_null() {
None
} else {
Some(Tdb(ret))
}
}
fn error(&self) -> Result<(), Error> {
let err = unsafe { generated::tdb_error(self.0) };
if err == 0 {
Ok(())
} else {
Err(err.into())
}
}
pub fn set_max_dead(&mut self, max_dead: u32) {
unsafe { generated::tdb_set_max_dead(self.0, max_dead as i32) };
}
pub fn reopen(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_reopen(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn fetch(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let ret = unsafe { tdb_fetch(self.0, key.into()) };
if ret.dptr.is_null() {
match self.error() {
Err(Error::NoExist) => Ok(None),
Err(e) => Err(e),
Ok(_) => panic!("error but no error?"),
}
} else {
Ok(Some(ret.into()))
}
}
pub fn store(
&mut self,
key: &[u8],
val: &[u8],
flags: Option<StoreFlags>,
) -> Result<(), Error> {
let flags = flags.map_or(0, |f| f as i32);
let ret = unsafe { tdb_store(self.0, key.into(), val.into(), flags) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn delete(&mut self, key: &[u8]) -> Result<(), Error> {
let ret = unsafe { tdb_delete(self.0, key.into()) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn append(&mut self, key: &[u8], val: &[u8]) -> Result<(), Error> {
let ret = unsafe { tdb_append(self.0, key.into(), val.into()) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn keys(&self) -> impl Iterator<Item = Vec<u8>> + '_ {
TdbKeys(self, None)
}
pub fn iter(&self) -> impl Iterator<Item = (Vec<u8>, Vec<u8>)> + '_ {
TdbIter(self, TdbKeys(self, None))
}
pub fn exists(&self, key: &[u8]) -> bool {
unsafe { tdb_exists(self.0, key.into()) }
}
pub fn lockall(&self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_lockall(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn unlockall(&self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_unlockall(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn lockall_nonblock(&self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_lockall_nonblock(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn lockall_read(&self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_lockall_read(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn lockall_read_nonblock(&self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_lockall_read_nonblock(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn name(&self) -> &str {
unsafe { CStr::from_ptr(generated::tdb_name(self.0)) }
.to_str()
.unwrap()
}
pub fn hash_size(&self) -> u32 {
unsafe { generated::tdb_hash_size(self.0) as u32 }
}
pub fn map_size(&self) -> u32 {
unsafe { generated::tdb_map_size(self.0) as u32 }
}
pub fn get_seqnum(&self) -> u64 {
unsafe { generated::tdb_get_seqnum(self.0) as u64 }
}
pub fn get_flags(&self) -> Flags {
Flags::from_bits_truncate(unsafe { generated::tdb_get_flags(self.0) as u32 })
}
pub fn add_flags(&mut self, flags: Flags) {
unsafe { generated::tdb_add_flags(self.0, flags.bits()) };
}
pub fn remove_flags(&mut self, flags: Flags) {
unsafe { generated::tdb_remove_flags(self.0, flags.bits()) };
}
pub fn enable_seqnum(&mut self) {
unsafe { generated::tdb_enable_seqnum(self.0) };
}
pub fn increment_seqnum_nonblock(&mut self) {
unsafe { generated::tdb_increment_seqnum_nonblock(self.0) };
}
pub fn repack(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_repack(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn wipe_all(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_wipe_all(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn summary(&self) -> String {
let buf = unsafe { generated::tdb_summary(self.0) };
unsafe { CStr::from_ptr(buf) }.to_str().unwrap().to_owned()
}
pub fn freelist_size(&self) -> u32 {
unsafe { generated::tdb_freelist_size(self.0) as u32 }
}
pub fn transaction_start(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_transaction_start(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn transaction_active(&self) -> bool {
unsafe { generated::tdb_transaction_active(self.0) }
}
pub fn transaction_start_nonblock(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_transaction_start_nonblock(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn transaction_prepare_commit(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_transaction_prepare_commit(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn transaction_commit(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_transaction_commit(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
pub fn transaction_cancel(&mut self) -> Result<(), Error> {
let ret = unsafe { generated::tdb_transaction_cancel(self.0) };
if ret == -1 {
self.error()
} else {
Ok(())
}
}
}
impl AsRawFd for Tdb {
fn as_raw_fd(&self) -> RawFd {
unsafe { generated::tdb_fd(self.0) }
}
}
struct TdbKeys<'a>(&'a Tdb, Option<TDB_DATA>);
impl Iterator for TdbKeys<'_> {
type Item = Vec<u8>;
fn next(&mut self) -> Option<Vec<u8>> {
let key = if let Some(prev_key) = self.1.take() {
let const_key = CONST_TDB_DATA {
dptr: prev_key.dptr as *const _,
dsize: prev_key.dsize,
};
let result = unsafe { tdb_nextkey(self.0 .0, const_key) };
drop(prev_key);
result
} else {
unsafe { generated::tdb_firstkey(self.0 .0) }
};
if key.dptr.is_null() {
match self.0.error() {
Err(Error::NoExist) | Ok(_) => None,
Err(e) => panic!("TDB iterator error: {}", e),
}
} else {
self.1 = Some(key.clone());
Some(key.into())
}
}
}
struct TdbIter<'a>(&'a Tdb, TdbKeys<'a>);
impl Iterator for TdbIter<'_> {
type Item = (Vec<u8>, Vec<u8>);
fn next(&mut self) -> Option<(Vec<u8>, Vec<u8>)> {
let key = self.1.next()?;
match self.0.fetch(&key) {
Ok(Some(val)) => Some((key, val)),
Ok(None) => {
self.next()
}
Err(_) => {
self.next()
}
}
}
}
impl Drop for Tdb {
fn drop(&mut self) {
unsafe { generated::tdb_close(self.0) };
}
}
pub fn jenkins_hash(key: &[u8]) -> u32 {
let mut tdb_key = CONST_TDB_DATA::from(key);
unsafe { generated::tdb_jenkins_hash(&mut tdb_key as *mut _ as *mut TDB_DATA) }
}
#[cfg(test)]
mod test {
use super::*;
use std::os::unix::io::AsRawFd;
fn testtdb() -> super::Tdb {
let tmppath = tempfile::tempdir().unwrap();
let path = tmppath.path().join("test.tdb");
super::Tdb::open(
path.as_path(),
None,
super::Flags::empty(),
libc::O_RDWR | libc::O_CREAT,
0o600,
)
.unwrap()
}
#[test]
fn test_memory() {
let mut tdb = super::Tdb::memory(None, super::Flags::empty()).unwrap();
assert!(!tdb.exists(b"foo"));
tdb.store(b"foo", b"bar", None).unwrap();
assert!(tdb.exists(b"foo"));
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"bar");
tdb.delete(b"foo").unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap(), None);
}
#[test]
fn test_simple() {
let mut tdb = testtdb();
assert!(!tdb.exists(b"foo"));
tdb.store(b"foo", b"bar", None).unwrap();
assert!(tdb.exists(b"foo"));
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"bar");
tdb.delete(b"foo").unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap(), None);
}
#[test]
fn test_iter() {
let mut tdb = testtdb();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.store(b"blah", b"bloe", None).unwrap();
let mut iter = tdb.iter();
assert_eq!(iter.next().unwrap(), (b"foo".to_vec(), b"bar".to_vec()));
assert_eq!(iter.next().unwrap(), (b"blah".to_vec(), b"bloe".to_vec()));
assert_eq!(iter.next(), None);
}
#[test]
fn test_keys() {
let mut tdb = testtdb();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.store(b"blah", b"bloe", None).unwrap();
let mut keys = tdb.keys();
assert_eq!(keys.next().unwrap(), b"foo");
assert_eq!(keys.next().unwrap(), b"blah");
assert_eq!(keys.next(), None);
}
#[test]
fn test_transaction() {
let mut tdb = testtdb();
tdb.transaction_start().unwrap();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.transaction_cancel().unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap(), None);
tdb.transaction_start().unwrap();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.transaction_prepare_commit().unwrap();
tdb.transaction_commit().unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"bar");
}
#[test]
fn test_fetch_nonexistent() {
let tdb = testtdb();
assert_eq!(tdb.fetch(b"foo").unwrap(), None);
}
#[test]
fn test_store_overwrite() {
let mut tdb = testtdb();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.store(b"foo", b"blah", None).unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"blah");
}
#[test]
fn test_transaction_active() {
let mut tdb = testtdb();
assert!(!tdb.transaction_active());
tdb.transaction_start().unwrap();
assert!(tdb.transaction_active());
tdb.transaction_cancel().unwrap();
assert!(!tdb.transaction_active());
}
#[test]
fn test_transaction_start_nonblock() {
let mut tdb = testtdb();
tdb.transaction_start_nonblock().unwrap();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.transaction_commit().unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"bar");
}
#[test]
fn test_locking() {
let tdb = testtdb();
tdb.lockall().unwrap();
tdb.unlockall().unwrap();
tdb.lockall_nonblock().unwrap();
tdb.unlockall().unwrap();
}
#[test]
fn test_read_locking() {
let tdb = testtdb();
tdb.lockall_read().unwrap();
let tdb2 = testtdb();
tdb2.lockall_read_nonblock().unwrap();
}
#[test]
fn test_metadata() {
let tdb = testtdb();
let name = tdb.name();
assert!(name.contains("test.tdb"));
let hash_size = tdb.hash_size();
assert!(hash_size > 0);
let map_size = tdb.map_size();
assert!(map_size > 0);
let summary = tdb.summary();
assert!(!summary.is_empty());
let _freelist_size = tdb.freelist_size();
}
#[test]
fn test_flags() {
let mut tdb = testtdb();
let _initial_flags = tdb.get_flags();
tdb.add_flags(Flags::NoSync);
let flags_after_add = tdb.get_flags();
assert!(flags_after_add.contains(Flags::NoSync));
tdb.remove_flags(Flags::NoSync);
let flags_after_remove = tdb.get_flags();
assert!(!flags_after_remove.contains(Flags::NoSync));
}
#[test]
fn test_sequence_numbers() {
let mut tdb = testtdb();
tdb.enable_seqnum();
let initial_seqnum = tdb.get_seqnum();
tdb.increment_seqnum_nonblock();
let new_seqnum = tdb.get_seqnum();
assert!(new_seqnum >= initial_seqnum);
}
#[test]
fn test_reopen() {
let mut tdb = testtdb();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.reopen().unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"bar");
}
#[test]
fn test_append() {
let mut tdb = testtdb();
tdb.append(b"foo", b"bar").unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"bar");
tdb.append(b"foo", b"baz").unwrap();
assert_eq!(tdb.fetch(b"foo").unwrap().unwrap(), b"barbaz");
}
#[test]
fn test_wipe_all() {
let mut tdb = testtdb();
tdb.store(b"foo", b"bar", None).unwrap();
tdb.store(b"baz", b"qux", None).unwrap();
tdb.wipe_all().unwrap();
assert!(!tdb.exists(b"foo"));
assert!(!tdb.exists(b"baz"));
}
#[test]
fn test_repack() {
let mut tdb = testtdb();
for i in 0..10 {
let key = format!("key{}", i);
let value = format!("value{}", i);
tdb.store(key.as_bytes(), value.as_bytes(), None).unwrap();
}
for i in 0..5 {
let key = format!("key{}", i);
tdb.delete(key.as_bytes()).unwrap();
}
tdb.repack().unwrap();
for i in 5..10 {
let key = format!("key{}", i);
let value = format!("value{}", i);
assert_eq!(
tdb.fetch(key.as_bytes()).unwrap().unwrap(),
value.as_bytes()
);
}
}
#[test]
fn test_set_max_dead() {
let mut tdb = testtdb();
tdb.set_max_dead(10);
}
#[test]
fn test_jenkins_hash() {
let hash1 = jenkins_hash(b"");
assert!(hash1 != 0);
let hash2 = jenkins_hash(b"hello");
assert!(hash2 != 0);
assert!(hash2 != hash1);
let hash3 = jenkins_hash(b"hello");
assert_eq!(hash2, hash3);
}
#[test]
fn test_as_raw_fd() {
let tdb = testtdb();
let fd = tdb.as_raw_fd();
assert!(fd > 0);
}
#[test]
fn test_tdb_data_clone() {
let empty_data = TDB_DATA {
dptr: std::ptr::null_mut(),
dsize: 0,
};
let cloned_empty = empty_data.clone();
assert!(cloned_empty.dptr.is_null());
assert_eq!(cloned_empty.dsize, 0);
let vec = vec![1, 2, 3, 4, 5];
let data: TDB_DATA = vec.into();
let cloned = data.clone();
assert!(!cloned.dptr.is_null());
assert_eq!(cloned.dsize, 5);
let vec_back: Vec<u8> = cloned.into();
assert_eq!(vec_back, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_error_display() {
assert_eq!(format!("{}", Error::Corrupt), "Database is corrupt");
assert_eq!(format!("{}", Error::IO), "I/O error");
assert_eq!(format!("{}", Error::Lock), "Locked");
assert_eq!(format!("{}", Error::OOM), "OOM");
assert_eq!(format!("{}", Error::Exists), "Exists");
assert_eq!(format!("{}", Error::NoLock), "NoLock");
assert_eq!(format!("{}", Error::LockTimeout), "Lock timeout expired");
assert_eq!(format!("{}", Error::ReadOnly), "Database is read-only");
assert_eq!(format!("{}", Error::NoExist), "NoExist");
assert_eq!(format!("{}", Error::Invalid), "Invalid");
assert_eq!(format!("{}", Error::Nesting), "Nesting");
}
#[test]
fn test_store_flags() {
let mut tdb = testtdb();
tdb.store(b"foo", b"bar", None).unwrap();
let result = tdb.store(b"foo", b"baz", Some(StoreFlags::Insert));
assert!(result.is_err());
tdb.store(b"existing", b"old", None).unwrap();
tdb.store(b"existing", b"new", Some(StoreFlags::Replace))
.unwrap();
assert_eq!(tdb.fetch(b"existing").unwrap().unwrap(), b"new");
tdb.store(b"newkey", b"value", Some(StoreFlags::Insert))
.unwrap();
assert_eq!(tdb.fetch(b"newkey").unwrap().unwrap(), b"value");
}
#[test]
fn test_memory_with_hash_size() {
let tdb = Tdb::memory(Some(1024), Flags::empty()).unwrap();
assert!(tdb.hash_size() >= 1024);
}
}