use std::path::Path;
use std::sync::Arc;
use leveldb::batch::Batch;
use leveldb::batch::WriteBatch;
use leveldb::compaction::Compaction;
use leveldb::database::comparator::Comparator;
use leveldb::database::Database;
use leveldb::error::Error as DbError;
use leveldb::iterator::Iterable;
use leveldb::iterator::Iterator;
use leveldb::iterator::KeyIterator;
use leveldb::iterator::ValueIterator;
use leveldb::key::IntoLevelDBKey;
use leveldb::options::Options;
use leveldb::options::ReadOptions;
use leveldb::options::WriteOptions;
use leveldb::snapshots::Snapshot;
use leveldb::snapshots::Snapshots;
use rand::distr::Alphanumeric;
use rand::distr::SampleString;
#[derive(Debug, Clone)]
pub struct DbIntMut {
db: Option<Arc<Database>>, path: std::path::PathBuf,
destroy_db_on_drop: bool,
read_options: ReadOptions,
write_options: WriteOptions,
}
impl DbIntMut {
#[inline]
pub fn open(name: &Path, options: &Options) -> Result<Self, DbError> {
let db = Database::open(name, options)?;
Ok(Self {
db: Some(Arc::new(db)),
path: name.into(),
destroy_db_on_drop: false,
read_options: ReadOptions::new(),
write_options: WriteOptions::new(),
})
}
#[inline]
pub fn open_with_options(
name: &Path,
options: &Options,
read_options: ReadOptions,
write_options: WriteOptions,
) -> Result<Self, DbError> {
let db = Database::open(name, options)?;
Ok(Self {
db: Some(Arc::new(db)),
path: name.into(),
destroy_db_on_drop: false,
read_options,
write_options,
})
}
#[inline]
pub fn open_with_comparator<C: Comparator>(
name: &Path,
options: &Options,
comparator: C,
) -> Result<Self, DbError> {
let db = Database::open_with_comparator(name, options, comparator)?;
Ok(Self {
db: Some(Arc::new(db)),
path: name.into(),
destroy_db_on_drop: false,
read_options: ReadOptions::new(),
write_options: WriteOptions::new(),
})
}
pub fn open_new_test_database(
destroy_db_on_drop: bool,
options: Option<Options>,
read_options: Option<ReadOptions>,
write_options: Option<WriteOptions>,
) -> Result<Self, DbError> {
let path = std::env::temp_dir().join(format!(
"test-db-{}",
Alphanumeric.sample_string(&mut rand::rng(), 10)
));
Self::open_test_database(
&path,
destroy_db_on_drop,
options,
read_options,
write_options,
)
}
pub fn open_test_database(
path: &std::path::Path,
destroy_db_on_drop: bool,
options: Option<Options>,
read_options: Option<ReadOptions>,
write_options: Option<WriteOptions>,
) -> Result<Self, DbError> {
let mut opt = options.unwrap_or_else(Options::new);
let read_opt = read_options.unwrap_or_else(ReadOptions::new);
let write_opt = write_options.unwrap_or_else(WriteOptions::new);
opt.create_if_missing = true;
opt.error_if_exists = false;
let mut db = Self::open_with_options(path, &opt, read_opt, write_opt)?;
db.destroy_db_on_drop = destroy_db_on_drop;
Ok(db)
}
#[inline]
pub fn put(&self, key: &dyn IntoLevelDBKey, value: &[u8]) -> Result<(), DbError> {
self.db
.as_ref()
.unwrap()
.put(&self.write_options, key, value)
}
#[inline]
pub fn put_u8(&self, key: &[u8], value: &[u8]) -> Result<(), DbError> {
self.db
.as_ref()
.unwrap()
.put_u8(&self.write_options, key, value)
}
#[inline]
pub fn get(&self, key: &dyn IntoLevelDBKey) -> Result<Option<Vec<u8>>, DbError> {
self.db.as_ref().unwrap().get(&self.read_options, key)
}
#[inline]
pub fn get_u8(&self, key: &[u8]) -> Result<Option<Vec<u8>>, DbError> {
self.db.as_ref().unwrap().get_u8(&self.read_options, key)
}
#[inline]
pub fn delete(&self, key: &dyn IntoLevelDBKey) -> Result<(), DbError> {
self.db.as_ref().unwrap().delete(&self.write_options, key)
}
#[inline]
pub fn delete_u8(&self, key: &[u8]) -> Result<(), DbError> {
self.db
.as_ref()
.unwrap()
.delete_u8(&self.write_options, key)
}
pub fn write(&self, batch: &WriteBatch, sync: bool) -> Result<(), DbError> {
const WO_SYNC: WriteOptions = WriteOptions { sync: true };
const WO_NOSYNC: WriteOptions = WriteOptions { sync: false };
self.db
.as_ref()
.unwrap()
.write(if sync { &WO_SYNC } else { &WO_NOSYNC }, batch)
}
pub fn write_auto(&self, batch: &WriteBatch) -> Result<(), DbError> {
self.db.as_ref().unwrap().write(&self.write_options, batch)
}
#[inline]
pub fn path(&self) -> &std::path::PathBuf {
&self.path
}
#[inline]
pub fn destroy_db_on_drop(&self) -> bool {
self.destroy_db_on_drop
}
fn destroy_db(&self) -> Result<(), std::io::Error> {
match self.path.exists() {
true => std::fs::remove_dir_all(&self.path),
false => Ok(()),
}
}
}
impl Drop for DbIntMut {
#[inline]
fn drop(&mut self) {
if self.destroy_db_on_drop {
{
let db_opt = self.db.take();
if let Some(db_arc) = db_opt {
drop(db_arc);
}
}
let _ = self.destroy_db();
}
}
}
impl Batch for DbIntMut {
#[inline]
fn write(&self, options: &WriteOptions, batch: &WriteBatch) -> Result<(), DbError> {
self.db.as_ref().unwrap().write(options, batch)
}
}
impl<'a> Compaction<'a> for DbIntMut {
#[inline]
fn compact(&self, start: &'a [u8], limit: &'a [u8]) {
self.db.as_ref().unwrap().compact(start, limit)
}
}
impl<'a> Iterable<'a> for DbIntMut {
#[inline]
fn iter(&'a self, options: &ReadOptions) -> Iterator<'a> {
self.db.as_ref().unwrap().iter(options)
}
#[inline]
fn keys_iter(&'a self, options: &ReadOptions) -> KeyIterator<'a> {
self.db.as_ref().unwrap().keys_iter(options)
}
#[inline]
fn value_iter(&'a self, options: &ReadOptions) -> ValueIterator<'a> {
self.db.as_ref().unwrap().value_iter(options)
}
}
impl Snapshots for DbIntMut {
fn snapshot(&self) -> Snapshot<'_> {
self.db.as_ref().unwrap().snapshot()
}
}
#[derive(Debug, Clone)]
pub struct DB(DbIntMut);
impl DB {
#[inline]
pub fn open(name: &Path, options: &Options) -> Result<Self, DbError> {
Ok(Self(DbIntMut::open(name, options)?))
}
#[inline]
pub fn open_with_options(
name: &Path,
options: &Options,
read_options: ReadOptions,
write_options: WriteOptions,
) -> Result<Self, DbError> {
Ok(Self(DbIntMut::open_with_options(
name,
options,
read_options,
write_options,
)?))
}
#[inline]
pub fn open_with_comparator<C: Comparator>(
name: &Path,
options: &Options,
comparator: C,
) -> Result<Self, DbError> {
Ok(Self(DbIntMut::open_with_comparator(
name, options, comparator,
)?))
}
pub fn open_new_test_database(
destroy_db_on_drop: bool,
options: Option<Options>,
read_options: Option<ReadOptions>,
write_options: Option<WriteOptions>,
) -> Result<Self, DbError> {
Ok(Self(DbIntMut::open_new_test_database(
destroy_db_on_drop,
options,
read_options,
write_options,
)?))
}
pub fn open_test_database(
path: &std::path::Path,
destroy_db_on_drop: bool,
options: Option<Options>,
read_options: Option<ReadOptions>,
write_options: Option<WriteOptions>,
) -> Result<Self, DbError> {
Ok(Self(DbIntMut::open_test_database(
path,
destroy_db_on_drop,
options,
read_options,
write_options,
)?))
}
#[inline]
pub fn put(&mut self, key: &dyn IntoLevelDBKey, value: &[u8]) -> Result<(), DbError> {
self.0.put(key, value)
}
#[inline]
pub fn put_u8(&mut self, key: &[u8], value: &[u8]) -> Result<(), DbError> {
self.0.put_u8(key, value)
}
#[inline]
pub fn get(&self, key: &dyn IntoLevelDBKey) -> Result<Option<Vec<u8>>, DbError> {
self.0.get(key)
}
#[inline]
pub fn get_u8(&self, key: &[u8]) -> Result<Option<Vec<u8>>, DbError> {
self.0.get_u8(key)
}
#[inline]
pub fn delete(&mut self, key: &dyn IntoLevelDBKey) -> Result<(), DbError> {
self.0.delete(key)
}
#[inline]
pub fn delete_u8(&mut self, key: &[u8]) -> Result<(), DbError> {
self.0.delete_u8(key)
}
pub fn write(&mut self, batch: &WriteBatch, sync: bool) -> Result<(), DbError> {
self.0.write(batch, sync)
}
pub fn write_batch_iter(
&mut self,
batch: impl IntoIterator<Item = (&'static [u8], &'static [u8])>,
sync: bool,
) -> Result<(), DbError> {
let write_batch = WriteBatch::new();
for (key, value) in batch {
write_batch.put(&key, value);
}
self.0.write(&write_batch, sync)
}
pub fn write_auto(&mut self, batch: &WriteBatch) -> Result<(), DbError> {
self.0.write_auto(batch)
}
pub fn write_batch_iter_auto(
&mut self,
batch: impl IntoIterator<Item = (&'static [u8], &'static [u8])>,
) -> Result<(), DbError> {
let write_batch = WriteBatch::new();
for (key, value) in batch {
write_batch.put(&key, value);
}
self.0.write_auto(&write_batch)
}
#[inline]
pub fn path(&self) -> &std::path::PathBuf {
self.0.path()
}
#[inline]
pub fn destroy_db_on_drop(&self) -> bool {
self.0.destroy_db_on_drop()
}
#[inline]
pub fn compact<'a>(&mut self, start: &'a [u8], limit: &'a [u8]) {
self.0.compact(start, limit)
}
pub fn destroy_db(&mut self) -> Result<(), std::io::Error> {
self.0.destroy_db()
}
}
impl<'a> Iterable<'a> for DB {
#[inline]
fn iter(&'a self, options: &ReadOptions) -> Iterator<'a> {
self.0.iter(options)
}
#[inline]
fn keys_iter(&'a self, options: &ReadOptions) -> KeyIterator<'a> {
self.0.keys_iter(options)
}
#[inline]
fn value_iter(&'a self, options: &ReadOptions) -> ValueIterator<'a> {
self.0.value_iter(options)
}
}
impl Snapshots for DB {
fn snapshot(&self) -> Snapshot<'_> {
self.0.snapshot()
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn level_db_close_and_reload() {
let mut db = DB::open_new_test_database(false, None, None, None).unwrap();
let db_path = db.path().clone();
let key = "answer-to-everything";
let val = vec![42];
let _ = db.put(&key, &val);
drop(db);
assert!(db_path.exists());
let db2 = DbIntMut::open_test_database(&db_path, true, None, None, None).unwrap();
let val2 = db2.get(&key).unwrap().unwrap();
assert_eq!(val, val2);
drop(db2);
assert!(!db_path.exists());
}
}