use lmdb::{Database, RwTransaction, Transaction, WriteFlags};
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum LmdbExtError {
#[error("internal storage corrupted: {0}")]
LmdbCorrupted(lmdb::Error),
#[error("internal data corrupted: {0}")]
DataCorrupted(Box<dyn std::error::Error + Send + Sync>),
#[error("storage exhausted resource (but still intact): {0}")]
ResourceExhausted(lmdb::Error),
#[error("unknown LMDB or serialization error, likely from a bug: {0}")]
Other(Box<dyn std::error::Error + Send + Sync>),
}
impl From<lmdb::Error> for LmdbExtError {
fn from(lmdb_error: lmdb::Error) -> Self {
match lmdb_error {
lmdb::Error::PageNotFound
| lmdb::Error::Corrupted
| lmdb::Error::Panic
| lmdb::Error::VersionMismatch
| lmdb::Error::Invalid
| lmdb::Error::Incompatible => LmdbExtError::LmdbCorrupted(lmdb_error),
lmdb::Error::MapFull
| lmdb::Error::DbsFull
| lmdb::Error::ReadersFull
| lmdb::Error::TlsFull
| lmdb::Error::TxnFull
| lmdb::Error::CursorFull
| lmdb::Error::PageFull
| lmdb::Error::MapResized => LmdbExtError::ResourceExhausted(lmdb_error),
lmdb::Error::NotFound
| lmdb::Error::BadRslot
| lmdb::Error::BadTxn
| lmdb::Error::BadValSize
| lmdb::Error::BadDbi
| lmdb::Error::KeyExist
| lmdb::Error::Other(_) => LmdbExtError::Other(Box::new(lmdb_error)),
}
}
}
pub(super) trait TransactionExt {
fn get_value<K: AsRef<[u8]>, V: DeserializeOwned>(
&mut self,
db: Database,
key: &K,
) -> Result<Option<V>, LmdbExtError>;
}
pub(super) trait WriteTransactionExt {
fn put_value<K: AsRef<[u8]>, V: Serialize>(
&mut self,
db: Database,
key: &K,
value: &V,
overwrite: bool,
) -> Result<bool, LmdbExtError>;
}
impl<T> TransactionExt for T
where
T: Transaction,
{
#[inline]
fn get_value<K: AsRef<[u8]>, V: DeserializeOwned>(
&mut self,
db: Database,
key: &K,
) -> Result<Option<V>, LmdbExtError> {
match self.get(db, key) {
Ok(raw) => deserialize(raw).map(Some),
Err(lmdb::Error::NotFound) => Ok(None),
Err(err) => Err(err.into()),
}
}
}
impl WriteTransactionExt for RwTransaction<'_> {
fn put_value<K: AsRef<[u8]>, V: Serialize>(
&mut self,
db: Database,
key: &K,
value: &V,
overwrite: bool,
) -> Result<bool, LmdbExtError> {
let buffer = serialize(value)?;
let flags = if overwrite {
WriteFlags::empty()
} else {
WriteFlags::NO_OVERWRITE
};
match self.put(db, key, &buffer, flags) {
Ok(()) => Ok(true),
Err(lmdb::Error::KeyExist) => Ok(false),
Err(err) => Err(err.into()),
}
}
}
#[inline(always)]
pub(super) fn deserialize<T: DeserializeOwned>(raw: &[u8]) -> Result<T, LmdbExtError> {
bincode::deserialize(raw).map_err(|err| LmdbExtError::DataCorrupted(Box::new(err)))
}
#[inline(always)]
pub(super) fn serialize<T: Serialize>(value: &T) -> Result<Vec<u8>, LmdbExtError> {
bincode::serialize(value).map_err(|err| LmdbExtError::Other(Box::new(err)))
}