Add TypedTree

This commit is contained in:
Arthur Carcano
2026-06-10 17:09:33 +02:00
committed by Alex
parent 22e003f0d0
commit eb91f463f5
7 changed files with 509 additions and 203 deletions
+56 -50
View File
@@ -12,7 +12,7 @@ use fjall::{
use crate::{
open::{Engine, OpenOpt},
Db, Error, IDb, ITx, ITxFn, OnCommit, Result, TxError, TxFnResult, TxOpError, TxOpResult,
Db, DbError, DbResult, Error, IDb, ITx, ITxFn, OnCommit, TxError, TxFnResult, TxOpError,
TxResult, TxValueIter, Value, ValueIter,
};
@@ -20,10 +20,10 @@ pub use fjall;
// --
pub(crate) fn open_db(path: &Path, opt: &OpenOpt) -> Result<Db> {
pub(crate) fn open_db(path: &Path, opt: &OpenOpt) -> DbResult<Db> {
info!("Opening Fjall database at: {}", path.display());
if opt.fsync {
return Err(Error(
return Err(DbError(
"metadata_fsync is not supported with the Fjall database engine".into(),
));
}
@@ -37,21 +37,27 @@ pub(crate) fn open_db(path: &Path, opt: &OpenOpt) -> Result<Db> {
// -- err
impl From<fjall::Error> for Error {
fn from(e: fjall::Error) -> Error {
Error(format!("fjall: {}", e).into())
impl From<fjall::Error> for DbError {
fn from(e: fjall::Error) -> DbError {
DbError(format!("fjall: {}", e).into())
}
}
impl From<fjall::LsmError> for Error {
fn from(e: fjall::LsmError) -> Error {
Error(format!("fjall lsm_tree: {}", e).into())
impl From<fjall::LsmError> for DbError {
fn from(e: fjall::LsmError) -> DbError {
DbError(format!("fjall lsm_tree: {}", e).into())
}
}
impl From<fjall::Error> for Error {
fn from(e: fjall::Error) -> Error {
Error::Db(DbError::from(e))
}
}
impl From<fjall::Error> for TxOpError {
fn from(e: fjall::Error) -> TxOpError {
TxOpError(e.into())
DbError::from(e).into()
}
}
@@ -76,11 +82,11 @@ impl FjallDb {
fn get_tree(
&self,
i: usize,
) -> Result<MappedRwLockReadGuard<'_, TransactionalPartitionHandle>> {
) -> DbResult<MappedRwLockReadGuard<'_, TransactionalPartitionHandle>> {
RwLockReadGuard::try_map(self.trees.read(), |trees: &Vec<_>| {
trees.get(i).map(|tup| &tup.1)
})
.map_err(|_| Error("invalid tree id".into()))
.map_err(|_| DbError("invalid tree id".into()))
}
}
@@ -89,7 +95,7 @@ impl IDb for FjallDb {
"Fjall (EXPERIMENTAL!)".into()
}
fn open_tree(&self, name: &str) -> Result<usize> {
fn open_tree(&self, name: &str) -> DbResult<usize> {
let mut trees = self.trees.write();
let safe_name = encode_name(name)?;
if let Some(i) = trees.iter().position(|(name, _)| *name == safe_name) {
@@ -104,15 +110,15 @@ impl IDb for FjallDb {
}
}
fn list_trees(&self) -> Result<Vec<String>> {
fn list_trees(&self) -> DbResult<Vec<String>> {
self.keyspace
.list_partitions()
.iter()
.map(|n| decode_name(n))
.collect::<Result<Vec<_>>>()
.collect::<DbResult<Vec<_>>>()
}
fn snapshot(&self, base_path: &Path) -> Result<()> {
fn snapshot(&self, base_path: &Path) -> DbResult<()> {
std::fs::create_dir_all(base_path)?;
let path = Engine::Fjall.db_path(base_path);
@@ -138,7 +144,7 @@ impl IDb for FjallDb {
// ----
fn get(&self, tree_idx: usize, key: &[u8]) -> Result<Option<Value>> {
fn get(&self, tree_idx: usize, key: &[u8]) -> DbResult<Option<Value>> {
let tree = self.get_tree(tree_idx)?;
let tx = self.keyspace.read_tx();
let val = tx.get(&tree, key)?;
@@ -148,17 +154,17 @@ impl IDb for FjallDb {
}
}
fn approximate_len(&self, tree_idx: usize) -> Result<usize> {
fn approximate_len(&self, tree_idx: usize) -> DbResult<usize> {
let tree = self.get_tree(tree_idx)?;
Ok(tree.approximate_len())
}
fn is_empty(&self, tree_idx: usize) -> Result<bool> {
fn is_empty(&self, tree_idx: usize) -> DbResult<bool> {
let tree = self.get_tree(tree_idx)?;
let tx = self.keyspace.read_tx();
Ok(tx.is_empty(&tree)?)
}
fn insert(&self, tree_idx: usize, key: &[u8], value: &[u8]) -> Result<()> {
fn insert(&self, tree_idx: usize, key: &[u8], value: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree_idx)?;
let mut tx = self.keyspace.write_tx();
tx.insert(&tree, key, value);
@@ -166,7 +172,7 @@ impl IDb for FjallDb {
Ok(())
}
fn remove(&self, tree_idx: usize, key: &[u8]) -> Result<()> {
fn remove(&self, tree_idx: usize, key: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree_idx)?;
let mut tx = self.keyspace.write_tx();
tx.remove(&tree, key);
@@ -174,11 +180,11 @@ impl IDb for FjallDb {
Ok(())
}
fn clear(&self, tree_idx: usize) -> Result<()> {
fn clear(&self, tree_idx: usize) -> DbResult<()> {
let mut trees = self.trees.write();
if tree_idx >= trees.len() {
return Err(Error("invalid tree id".into()));
return Err(DbError("invalid tree id".into()));
}
let (name, tree) = trees.remove(tree_idx);
@@ -191,13 +197,13 @@ impl IDb for FjallDb {
Ok(())
}
fn iter(&self, tree_idx: usize) -> Result<ValueIter<'_>> {
fn iter(&self, tree_idx: usize) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree_idx)?;
let tx = self.keyspace.read_tx();
Ok(Box::new(tx.iter(&tree).map(iterator_remap)))
}
fn iter_rev(&self, tree_idx: usize) -> Result<ValueIter<'_>> {
fn iter_rev(&self, tree_idx: usize) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree_idx)?;
let tx = self.keyspace.read_tx();
Ok(Box::new(tx.iter(&tree).rev().map(iterator_remap)))
@@ -208,7 +214,7 @@ impl IDb for FjallDb {
tree_idx: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>> {
) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree_idx)?;
let tx = self.keyspace.read_tx();
Ok(Box::new(
@@ -221,7 +227,7 @@ impl IDb for FjallDb {
tree_idx: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>> {
) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree_idx)?;
let tx = self.keyspace.read_tx();
Ok(Box::new(
@@ -252,9 +258,9 @@ impl IDb for FjallDb {
}
TxFnResult::DbErr => {
tx.tx.rollback();
Err(TxError::Db(Error(
"(this message will be discarded)".into(),
)))
Err(TxError::Db(
DbError("(this message will be discarded)".into()).into(),
))
}
}
}
@@ -268,47 +274,47 @@ struct FjallTx<'a> {
}
impl<'a> FjallTx<'a> {
fn get_tree(&self, i: usize) -> TxOpResult<&TransactionalPartitionHandle> {
fn get_tree(&self, i: usize) -> DbResult<&TransactionalPartitionHandle> {
self.trees.get(i).map(|tup| &tup.1).ok_or_else(|| {
TxOpError(Error(
DbError(
"invalid tree id (it might have been opened after the transaction started)".into(),
))
)
})
}
}
impl<'a> ITx for FjallTx<'a> {
fn get(&self, tree_idx: usize, key: &[u8]) -> TxOpResult<Option<Value>> {
fn get(&self, tree_idx: usize, key: &[u8]) -> DbResult<Option<Value>> {
let tree = self.get_tree(tree_idx)?;
match self.tx.get(tree, key)? {
Some(v) => Ok(Some(v.to_vec())),
None => Ok(None),
}
}
fn len(&self, tree_idx: usize) -> TxOpResult<usize> {
fn len(&self, tree_idx: usize) -> DbResult<usize> {
let tree = self.get_tree(tree_idx)?;
Ok(self.tx.len(tree)?)
}
fn insert(&mut self, tree_idx: usize, key: &[u8], value: &[u8]) -> TxOpResult<()> {
fn insert(&mut self, tree_idx: usize, key: &[u8], value: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree_idx)?.clone();
self.tx.insert(&tree, key, value);
Ok(())
}
fn remove(&mut self, tree_idx: usize, key: &[u8]) -> TxOpResult<()> {
fn remove(&mut self, tree_idx: usize, key: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree_idx)?.clone();
self.tx.remove(&tree, key);
Ok(())
}
fn clear(&mut self, _tree_idx: usize) -> TxOpResult<()> {
fn clear(&mut self, _tree_idx: usize) -> DbResult<()> {
unimplemented!("LSM tree clearing in cross-partition transaction is not supported")
}
fn iter(&self, tree_idx: usize) -> TxOpResult<TxValueIter<'_>> {
fn iter(&self, tree_idx: usize) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree_idx)?.clone();
Ok(Box::new(self.tx.iter(&tree).map(iterator_remap_tx)))
}
fn iter_rev(&self, tree_idx: usize) -> TxOpResult<TxValueIter<'_>> {
fn iter_rev(&self, tree_idx: usize) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree_idx)?.clone();
Ok(Box::new(self.tx.iter(&tree).rev().map(iterator_remap_tx)))
}
@@ -318,7 +324,7 @@ impl<'a> ITx for FjallTx<'a> {
tree_idx: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>> {
) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree_idx)?;
let low = clone_bound(low);
let high = clone_bound(high);
@@ -333,7 +339,7 @@ impl<'a> ITx for FjallTx<'a> {
tree_idx: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>> {
) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree_idx)?;
let low = clone_bound(low);
let high = clone_bound(high);
@@ -348,14 +354,14 @@ impl<'a> ITx for FjallTx<'a> {
// -- maps fjall's (k, v) to ours
fn iterator_remap(r: fjall::Result<(fjall::Slice, fjall::Slice)>) -> Result<(Value, Value)> {
fn iterator_remap(r: fjall::Result<(fjall::Slice, fjall::Slice)>) -> DbResult<(Value, Value)> {
r.map(|(k, v)| (k.to_vec(), v.to_vec()))
.map_err(|e| e.into())
.map_err(DbError::from)
}
fn iterator_remap_tx(r: fjall::Result<(fjall::Slice, fjall::Slice)>) -> TxOpResult<(Value, Value)> {
fn iterator_remap_tx(r: fjall::Result<(fjall::Slice, fjall::Slice)>) -> DbResult<(Value, Value)> {
r.map(|(k, v)| (k.to_vec(), v.to_vec()))
.map_err(|e| e.into())
.map_err(DbError::from)
}
// -- utils to deal with Garage's tightness on Bound lifetimes
@@ -378,7 +384,7 @@ fn clone_bound(bound: Bound<&[u8]>) -> ByteVecBound {
// -- utils to encode table names --
fn encode_name(s: &str) -> Result<String> {
fn encode_name(s: &str) -> DbResult<String> {
let base = 'A' as u32;
let mut ret = String::with_capacity(s.len() + 10);
@@ -392,7 +398,7 @@ fn encode_name(s: &str) -> Result<String> {
ret.push(char::from_u32(base + c_hi).unwrap());
ret.push(char::from_u32(base + c_lo).unwrap());
} else {
return Err(Error(
return Err(DbError(
format!("table name {} could not be safely encoded", s).into(),
));
}
@@ -400,10 +406,10 @@ fn encode_name(s: &str) -> Result<String> {
Ok(ret)
}
fn decode_name(s: &str) -> Result<String> {
fn decode_name(s: &str) -> DbResult<String> {
use std::convert::TryFrom;
let errfn = || Error(format!("encoded table name {} is invalid", s).into());
let errfn = || DbError(format!("encoded table name {} is invalid", s).into());
let c_map = |c: char| {
let c = c as u32;
let base = 'A' as u32;
+97 -57
View File
@@ -9,6 +9,7 @@ pub mod lmdb_adapter;
pub mod sqlite_adapter;
pub mod open;
pub mod typed;
#[cfg(test)]
pub mod test;
@@ -23,6 +24,7 @@ use std::sync::Arc;
use thiserror::Error;
pub use open::*;
pub use typed::{DbBytes, DbOrdKey, TypedIter, TypedTree, TypedTxIter};
pub(crate) type OnCommit = Vec<Box<dyn FnOnce()>>;
@@ -38,21 +40,34 @@ pub struct Transaction<'a> {
pub struct Tree(Arc<dyn IDb>, usize);
pub type Value = Vec<u8>;
pub type ValueIter<'a> = Box<dyn std::iter::Iterator<Item = Result<(Value, Value)>> + 'a>;
pub type TxValueIter<'a> = Box<dyn std::iter::Iterator<Item = TxOpResult<(Value, Value)>> + 'a>;
pub type ValueIter<'a> = Box<dyn std::iter::Iterator<Item = DbResult<(Value, Value)>> + 'a>;
pub type TxValueIter<'a> = Box<dyn std::iter::Iterator<Item = DbResult<(Value, Value)>> + 'a>;
// ----
#[derive(Debug, Error)]
#[error("{0}")]
pub struct Error(pub Cow<'static, str>);
#[error("database error: {0}")]
pub struct DbError(pub Cow<'static, str>);
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error {
Error(format!("IO: {}", e).into())
#[derive(Debug, Error)]
#[error("decode error: {0}")]
pub struct DecodeError(pub Cow<'static, str>);
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Db(#[from] DbError),
#[error(transparent)]
Decode(#[from] DecodeError),
}
impl From<std::io::Error> for DbError {
fn from(e: std::io::Error) -> DbError {
DbError(format!("IO: {}", e).into())
}
}
pub type DbResult<T> = std::result::Result<T, DbError>;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
@@ -60,6 +75,18 @@ pub type Result<T> = std::result::Result<T, Error>;
pub struct TxOpError(pub(crate) Error);
pub type TxOpResult<T> = std::result::Result<T, TxOpError>;
impl From<DbError> for TxOpError {
fn from(e: DbError) -> TxOpError {
TxOpError(e.into())
}
}
impl From<DecodeError> for TxOpError {
fn from(e: DecodeError) -> TxOpError {
TxOpError(e.into())
}
}
#[derive(Debug)]
pub enum TxError<E> {
Abort(E),
@@ -73,6 +100,12 @@ impl<E> From<TxOpError> for TxError<E> {
}
}
impl<E> From<DbError> for TxError<E> {
fn from(e: DbError) -> TxError<E> {
TxError::Db(e.into())
}
}
pub fn unabort<R, E>(res: TxResult<R, E>) -> TxOpResult<std::result::Result<R, E>> {
match res {
Ok(v) => Ok(Ok(v)),
@@ -88,12 +121,12 @@ impl Db {
self.0.engine()
}
pub fn open_tree<S: AsRef<str>>(&self, name: S) -> Result<Tree> {
pub fn open_tree<S: AsRef<str>>(&self, name: S) -> DbResult<Tree> {
let tree_id = self.0.open_tree(name.as_ref())?;
Ok(Tree(self.0.clone(), tree_id))
}
pub fn list_trees(&self) -> Result<Vec<String>> {
pub fn list_trees(&self) -> DbResult<Vec<String>> {
self.0.list_trees()
}
@@ -147,27 +180,28 @@ impl Db {
}
}
pub fn snapshot(&self, path: &Path) -> Result<()> {
pub fn snapshot(&self, path: &Path) -> DbResult<()> {
self.0.snapshot(path)
}
pub fn import(&self, other: &Db) -> Result<()> {
let existing_trees = self.list_trees()?;
if !existing_trees.is_empty() {
return Err(Error(
return Err(DbError(
format!(
"destination database already contains data: {:?}",
existing_trees
)
.into(),
));
)
.into());
}
let tree_names = other.list_trees()?;
for name in tree_names {
let tree = self.open_tree(&name)?;
if !tree.is_empty()? {
return Err(Error(format!("tree {} already contains data", name).into()));
return Err(DbError(format!("tree {} already contains data", name).into()).into());
}
let ex_tree = other.open_tree(&name)?;
@@ -186,7 +220,7 @@ impl Db {
});
let total = match tx_res {
Err(TxError::Db(e)) => return Err(e),
Err(TxError::Abort(e)) => return Err(e),
Err(TxError::Abort(e)) => return Err(e.into()),
Ok(x) => x,
};
@@ -204,24 +238,24 @@ impl Tree {
}
#[inline]
pub fn get<T: AsRef<[u8]>>(&self, key: T) -> Result<Option<Value>> {
pub fn get<T: AsRef<[u8]>>(&self, key: T) -> DbResult<Option<Value>> {
self.0.get(self.1, key.as_ref())
}
#[inline]
pub fn approximate_len(&self) -> Result<usize> {
pub fn approximate_len(&self) -> DbResult<usize> {
self.0.approximate_len(self.1)
}
#[inline]
pub fn is_empty(&self) -> Result<bool> {
pub fn is_empty(&self) -> DbResult<bool> {
self.0.is_empty(self.1)
}
#[inline]
pub fn first(&self) -> Result<Option<(Value, Value)>> {
pub fn first(&self) -> DbResult<Option<(Value, Value)>> {
self.iter()?.next().transpose()
}
#[inline]
pub fn get_gt<T: AsRef<[u8]>>(&self, from: T) -> Result<Option<(Value, Value)>> {
pub fn get_gt<T: AsRef<[u8]>>(&self, from: T) -> DbResult<Option<(Value, Value)>> {
if from.as_ref().is_empty() {
self.iter()?.next().transpose()
} else {
@@ -233,31 +267,31 @@ impl Tree {
/// Returns the old value if there was one
#[inline]
pub fn insert<T: AsRef<[u8]>, U: AsRef<[u8]>>(&self, key: T, value: U) -> Result<()> {
pub fn insert<T: AsRef<[u8]>, U: AsRef<[u8]>>(&self, key: T, value: U) -> DbResult<()> {
self.0.insert(self.1, key.as_ref(), value.as_ref())
}
/// Returns the old value if there was one
#[inline]
pub fn remove<T: AsRef<[u8]>>(&self, key: T) -> Result<()> {
pub fn remove<T: AsRef<[u8]>>(&self, key: T) -> DbResult<()> {
self.0.remove(self.1, key.as_ref())
}
/// Clears all values from the tree
#[inline]
pub fn clear(&self) -> Result<()> {
pub fn clear(&self) -> DbResult<()> {
self.0.clear(self.1)
}
#[inline]
pub fn iter(&self) -> Result<ValueIter<'_>> {
pub fn iter(&self) -> DbResult<ValueIter<'_>> {
self.0.iter(self.1)
}
#[inline]
pub fn iter_rev(&self) -> Result<ValueIter<'_>> {
pub fn iter_rev(&self) -> DbResult<ValueIter<'_>> {
self.0.iter_rev(self.1)
}
#[inline]
pub fn range<K, R>(&self, range: R) -> Result<ValueIter<'_>>
pub fn range<K, R>(&self, range: R) -> DbResult<ValueIter<'_>>
where
K: AsRef<[u8]>,
R: RangeBounds<K>,
@@ -267,7 +301,7 @@ impl Tree {
self.0.range(self.1, get_bound(sb), get_bound(eb))
}
#[inline]
pub fn range_rev<K, R>(&self, range: R) -> Result<ValueIter<'_>>
pub fn range_rev<K, R>(&self, range: R) -> DbResult<ValueIter<'_>>
where
K: AsRef<[u8]>,
R: RangeBounds<K>,
@@ -282,11 +316,11 @@ impl Tree {
impl<'a> Transaction<'a> {
#[inline]
pub fn get<T: AsRef<[u8]>>(&self, tree: &Tree, key: T) -> TxOpResult<Option<Value>> {
self.tx.get(tree.1, key.as_ref())
self.tx.get(tree.1, key.as_ref()).map_err(Into::into)
}
#[inline]
pub fn len(&self, tree: &Tree) -> TxOpResult<usize> {
self.tx.len(tree.1)
self.tx.len(tree.1).map_err(Into::into)
}
/// Returns the old value if there was one
@@ -297,26 +331,28 @@ impl<'a> Transaction<'a> {
key: T,
value: U,
) -> TxOpResult<()> {
self.tx.insert(tree.1, key.as_ref(), value.as_ref())
self.tx
.insert(tree.1, key.as_ref(), value.as_ref())
.map_err(Into::into)
}
/// Returns the old value if there was one
#[inline]
pub fn remove<T: AsRef<[u8]>>(&mut self, tree: &Tree, key: T) -> TxOpResult<()> {
self.tx.remove(tree.1, key.as_ref())
self.tx.remove(tree.1, key.as_ref()).map_err(Into::into)
}
/// Clears all values in a tree
#[inline]
pub fn clear(&mut self, tree: &Tree) -> TxOpResult<()> {
self.tx.clear(tree.1)
self.tx.clear(tree.1).map_err(Into::into)
}
#[inline]
pub fn iter(&self, tree: &Tree) -> TxOpResult<TxValueIter<'_>> {
self.tx.iter(tree.1)
self.tx.iter(tree.1).map_err(Into::into)
}
#[inline]
pub fn iter_rev(&self, tree: &Tree) -> TxOpResult<TxValueIter<'_>> {
self.tx.iter_rev(tree.1)
self.tx.iter_rev(tree.1).map_err(Into::into)
}
#[inline]
@@ -327,7 +363,9 @@ impl<'a> Transaction<'a> {
{
let sb = range.start_bound();
let eb = range.end_bound();
self.tx.range(tree.1, get_bound(sb), get_bound(eb))
self.tx
.range(tree.1, get_bound(sb), get_bound(eb))
.map_err(Into::into)
}
#[inline]
pub fn range_rev<K, R>(&self, tree: &Tree, range: R) -> TxOpResult<TxValueIter<'_>>
@@ -337,7 +375,9 @@ impl<'a> Transaction<'a> {
{
let sb = range.start_bound();
let eb = range.end_bound();
self.tx.range_rev(tree.1, get_bound(sb), get_bound(eb))
self.tx
.range_rev(tree.1, get_bound(sb), get_bound(eb))
.map_err(Into::into)
}
#[inline]
@@ -350,60 +390,60 @@ impl<'a> Transaction<'a> {
pub(crate) trait IDb: Send + Sync {
fn engine(&self) -> String;
fn open_tree(&self, name: &str) -> Result<usize>;
fn list_trees(&self) -> Result<Vec<String>>;
fn snapshot(&self, path: &Path) -> Result<()>;
fn open_tree(&self, name: &str) -> DbResult<usize>;
fn list_trees(&self) -> DbResult<Vec<String>>;
fn snapshot(&self, path: &Path) -> DbResult<()>;
fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>>;
fn approximate_len(&self, tree: usize) -> Result<usize>;
fn is_empty(&self, tree: usize) -> Result<bool>;
fn get(&self, tree: usize, key: &[u8]) -> DbResult<Option<Value>>;
fn approximate_len(&self, tree: usize) -> DbResult<usize>;
fn is_empty(&self, tree: usize) -> DbResult<bool>;
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<()>;
fn remove(&self, tree: usize, key: &[u8]) -> Result<()>;
fn clear(&self, tree: usize) -> Result<()>;
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> DbResult<()>;
fn remove(&self, tree: usize, key: &[u8]) -> DbResult<()>;
fn clear(&self, tree: usize) -> DbResult<()>;
fn iter(&self, tree: usize) -> Result<ValueIter<'_>>;
fn iter_rev(&self, tree: usize) -> Result<ValueIter<'_>>;
fn iter(&self, tree: usize) -> DbResult<ValueIter<'_>>;
fn iter_rev(&self, tree: usize) -> DbResult<ValueIter<'_>>;
fn range<'r>(
&self,
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>>;
) -> DbResult<ValueIter<'_>>;
fn range_rev<'r>(
&self,
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>>;
) -> DbResult<ValueIter<'_>>;
fn transaction(&self, f: &dyn ITxFn) -> TxResult<OnCommit, ()>;
}
pub(crate) trait ITx {
fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>>;
fn len(&self, tree: usize) -> TxOpResult<usize>;
fn get(&self, tree: usize, key: &[u8]) -> DbResult<Option<Value>>;
fn len(&self, tree: usize) -> DbResult<usize>;
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<()>;
fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<()>;
fn clear(&mut self, tree: usize) -> TxOpResult<()>;
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> DbResult<()>;
fn remove(&mut self, tree: usize, key: &[u8]) -> DbResult<()>;
fn clear(&mut self, tree: usize) -> DbResult<()>;
fn iter(&self, tree: usize) -> TxOpResult<TxValueIter<'_>>;
fn iter_rev(&self, tree: usize) -> TxOpResult<TxValueIter<'_>>;
fn iter(&self, tree: usize) -> DbResult<TxValueIter<'_>>;
fn iter_rev(&self, tree: usize) -> DbResult<TxValueIter<'_>>;
fn range<'r>(
&self,
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>>;
) -> DbResult<TxValueIter<'_>>;
fn range_rev<'r>(
&self,
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>>;
) -> DbResult<TxValueIter<'_>>;
}
pub(crate) trait ITxFn {
+49 -43
View File
@@ -14,7 +14,7 @@ type Database = heed::Database<Bytes, Bytes>;
use crate::{
open::{Engine, OpenOpt},
Db, Error, IDb, ITx, ITxFn, OnCommit, Result, TxError, TxFnResult, TxOpError, TxOpResult,
Db, DbError, DbResult, Error, IDb, ITx, ITxFn, OnCommit, TxError, TxFnResult, TxOpError,
TxResult, TxValueIter, Value, ValueIter,
};
@@ -22,10 +22,10 @@ pub use heed;
// ---- top-level open function
pub(crate) fn open_db(path: &PathBuf, opt: &OpenOpt) -> Result<Db> {
pub(crate) fn open_db(path: &PathBuf, opt: &OpenOpt) -> DbResult<Db> {
info!("Opening LMDB database at: {}", path.display());
if let Err(e) = std::fs::create_dir_all(path) {
return Err(Error(
return Err(DbError(
format!("Unable to create LMDB data directory: {}", e).into(),
));
}
@@ -48,7 +48,7 @@ pub(crate) fn open_db(path: &PathBuf, opt: &OpenOpt) -> Result<Db> {
env_builder.open(path)
};
match open_res {
Err(heed::Error::Io(e)) if e.kind() == std::io::ErrorKind::OutOfMemory => Err(Error(
Err(heed::Error::Io(e)) if e.kind() == std::io::ErrorKind::OutOfMemory => Err(DbError(
"OutOfMemory error while trying to open LMDB database. This can happen \
if your operating system is not allowing you to use sufficient virtual \
memory address space. Please check that no limit is set (ulimit -v). \
@@ -56,22 +56,28 @@ pub(crate) fn open_db(path: &PathBuf, opt: &OpenOpt) -> Result<Db> {
On 32-bit machines, you should probably switch to another database engine."
.into(),
)),
Err(e) => Err(Error(format!("Cannot open LMDB database: {}", e).into())),
Err(e) => Err(DbError(format!("Cannot open LMDB database: {}", e).into())),
Ok(db) => Ok(LmdbDb::init(db)),
}
}
// -- err
impl From<heed::Error> for DbError {
fn from(e: heed::Error) -> DbError {
DbError(format!("LMDB: {}", e).into())
}
}
impl From<heed::Error> for Error {
fn from(e: heed::Error) -> Error {
Error(format!("LMDB: {}", e).into())
DbError::from(e).into()
}
}
impl From<heed::Error> for TxOpError {
fn from(e: heed::Error) -> TxOpError {
TxOpError(e.into())
DbError::from(e).into()
}
}
@@ -91,14 +97,14 @@ impl LmdbDb {
Db(Arc::new(s))
}
fn get_tree(&self, i: usize) -> Result<Database> {
fn get_tree(&self, i: usize) -> DbResult<Database> {
self.trees
.read()
.unwrap()
.0
.get(i)
.cloned()
.ok_or_else(|| Error("invalid tree id".into()))
.ok_or_else(|| DbError("invalid tree id".into()))
}
}
@@ -107,7 +113,7 @@ impl IDb for LmdbDb {
"LMDB (using Heed crate)".into()
}
fn open_tree(&self, name: &str) -> Result<usize> {
fn open_tree(&self, name: &str) -> DbResult<usize> {
let mut trees = self.trees.write().unwrap();
if let Some(i) = trees.1.get(name) {
Ok(*i)
@@ -122,7 +128,7 @@ impl IDb for LmdbDb {
}
}
fn list_trees(&self) -> Result<Vec<String>> {
fn list_trees(&self) -> DbResult<Vec<String>> {
let rtxn = self.db.read_txn()?;
let tree0 = match self
.db
@@ -153,7 +159,7 @@ impl IDb for LmdbDb {
Ok(ret2)
}
fn snapshot(&self, base_path: &Path) -> Result<()> {
fn snapshot(&self, base_path: &Path) -> DbResult<()> {
std::fs::create_dir_all(base_path)?;
let path = Engine::Lmdb.db_path(base_path);
self.db
@@ -163,7 +169,7 @@ impl IDb for LmdbDb {
// ----
fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> {
fn get(&self, tree: usize, key: &[u8]) -> DbResult<Option<Value>> {
let tree = self.get_tree(tree)?;
let tx = self.db.read_txn()?;
@@ -174,18 +180,18 @@ impl IDb for LmdbDb {
}
}
fn approximate_len(&self, tree: usize) -> Result<usize> {
fn approximate_len(&self, tree: usize) -> DbResult<usize> {
let tree = self.get_tree(tree)?;
let tx = self.db.read_txn()?;
Ok(tree.len(&tx)?.try_into().unwrap())
}
fn is_empty(&self, tree: usize) -> Result<bool> {
fn is_empty(&self, tree: usize) -> DbResult<bool> {
let tree = self.get_tree(tree)?;
let tx = self.db.read_txn()?;
Ok(tree.is_empty(&tx)?)
}
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<()> {
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree)?;
let mut tx = self.db.write_txn()?;
tree.put(&mut tx, key, value)?;
@@ -193,7 +199,7 @@ impl IDb for LmdbDb {
Ok(())
}
fn remove(&self, tree: usize, key: &[u8]) -> Result<()> {
fn remove(&self, tree: usize, key: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree)?;
let mut tx = self.db.write_txn()?;
tree.delete(&mut tx, key)?;
@@ -201,7 +207,7 @@ impl IDb for LmdbDb {
Ok(())
}
fn clear(&self, tree: usize) -> Result<()> {
fn clear(&self, tree: usize) -> DbResult<()> {
let tree = self.get_tree(tree)?;
let mut tx = self.db.write_txn()?;
tree.clear(&mut tx)?;
@@ -209,14 +215,14 @@ impl IDb for LmdbDb {
Ok(())
}
fn iter(&self, tree: usize) -> Result<ValueIter<'_>> {
fn iter(&self, tree: usize) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let tx = self.db.read_txn()?;
// Safety: the cloture does not store its argument anywhere,
unsafe { TxAndIterator::make(tx, |tx| Ok(tree.iter(tx)?)) }
}
fn iter_rev(&self, tree: usize) -> Result<ValueIter<'_>> {
fn iter_rev(&self, tree: usize) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let tx = self.db.read_txn()?;
// Safety: the cloture does not store its argument anywhere,
@@ -228,7 +234,7 @@ impl IDb for LmdbDb {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>> {
) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let tx = self.db.read_txn()?;
// Safety: the cloture does not store its argument anywhere,
@@ -239,7 +245,7 @@ impl IDb for LmdbDb {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>> {
) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let tx = self.db.read_txn()?;
// Safety: the cloture does not store its argument anywhere,
@@ -271,9 +277,9 @@ impl IDb for LmdbDb {
}
TxFnResult::DbErr => {
tx.tx.abort();
Err(TxError::Db(Error(
"(this message will be discarded)".into(),
)))
Err(TxError::Db(
DbError("(this message will be discarded)".into()).into(),
))
}
}
}
@@ -287,49 +293,49 @@ struct LmdbTx<'a> {
}
impl<'a> LmdbTx<'a> {
fn get_tree(&self, i: usize) -> TxOpResult<&Database> {
fn get_tree(&self, i: usize) -> DbResult<&Database> {
self.trees.get(i).ok_or_else(|| {
TxOpError(Error(
DbError(
"invalid tree id (it might have been opened after the transaction started)".into(),
))
)
})
}
}
impl<'a> ITx for LmdbTx<'a> {
fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> {
fn get(&self, tree: usize, key: &[u8]) -> DbResult<Option<Value>> {
let tree = self.get_tree(tree)?;
match tree.get(&self.tx, key)? {
Some(v) => Ok(Some(v.to_vec())),
None => Ok(None),
}
}
fn len(&self, tree: usize) -> TxOpResult<usize> {
fn len(&self, tree: usize) -> DbResult<usize> {
let tree = self.get_tree(tree)?;
Ok(tree.len(&self.tx)? as usize)
}
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<()> {
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> DbResult<()> {
let tree = *self.get_tree(tree)?;
tree.put(&mut self.tx, key, value)?;
Ok(())
}
fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<()> {
fn remove(&mut self, tree: usize, key: &[u8]) -> DbResult<()> {
let tree = *self.get_tree(tree)?;
tree.delete(&mut self.tx, key)?;
Ok(())
}
fn clear(&mut self, tree: usize) -> TxOpResult<()> {
fn clear(&mut self, tree: usize) -> DbResult<()> {
let tree = *self.get_tree(tree)?;
tree.clear(&mut self.tx)?;
Ok(())
}
fn iter(&self, tree: usize) -> TxOpResult<TxValueIter<'_>> {
fn iter(&self, tree: usize) -> DbResult<TxValueIter<'_>> {
let tree = *self.get_tree(tree)?;
Ok(Box::new(tree.iter(&self.tx)?.map(tx_iter_item)))
}
fn iter_rev(&self, tree: usize) -> TxOpResult<TxValueIter<'_>> {
fn iter_rev(&self, tree: usize) -> DbResult<TxValueIter<'_>> {
let tree = *self.get_tree(tree)?;
Ok(Box::new(tree.rev_iter(&self.tx)?.map(tx_iter_item)))
}
@@ -339,7 +345,7 @@ impl<'a> ITx for LmdbTx<'a> {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>> {
) -> DbResult<TxValueIter<'_>> {
let tree = *self.get_tree(tree)?;
Ok(Box::new(
tree.range(&self.tx, &(low, high))?.map(tx_iter_item),
@@ -350,7 +356,7 @@ impl<'a> ITx for LmdbTx<'a> {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>> {
) -> DbResult<TxValueIter<'_>> {
let tree = *self.get_tree(tree)?;
Ok(Box::new(
tree.rev_range(&self.tx, &(low, high))?.map(tx_iter_item),
@@ -386,9 +392,9 @@ where
}
/// Safety: iterfun must not store its argument anywhere but in its result.
unsafe fn make<F>(tx: RoTxn<'a, WithTls>, iterfun: F) -> Result<ValueIter<'a>>
unsafe fn make<F>(tx: RoTxn<'a, WithTls>, iterfun: F) -> DbResult<ValueIter<'a>>
where
F: FnOnce(&'a RoTxn<'a>) -> Result<I>,
F: FnOnce(&'a RoTxn<'a>) -> DbResult<I>,
{
let res = TxAndIterator {
tx,
@@ -436,13 +442,13 @@ impl<'a, I> Iterator for TxAndIteratorPin<'a, I>
where
I: Iterator<Item = IteratorItem<'a>> + 'a,
{
type Item = Result<(Value, Value)>;
type Item = DbResult<(Value, Value)>;
fn next(&mut self) -> Option<Self::Item> {
let mut_ref = Pin::as_mut(&mut self.0);
let next = mut_ref.iter().as_mut()?.next()?;
let res = match next {
Err(e) => Err(e.into()),
Err(e) => Err(DbError::from(e)),
Ok((k, v)) => Ok((k.to_vec(), v.to_vec())),
};
Some(res)
@@ -453,9 +459,9 @@ where
fn tx_iter_item<'a>(
item: std::result::Result<(&'a [u8], &'a [u8]), heed::Error>,
) -> TxOpResult<(Vec<u8>, Vec<u8>)> {
) -> DbResult<(Vec<u8>, Vec<u8>)> {
item.map(|(k, v)| (k.to_vec(), v.to_vec()))
.map_err(|e| TxOpError(Error::from(e)))
.map_err(DbError::from)
}
// ---- utility ----
+10 -9
View File
@@ -1,6 +1,6 @@
use std::path::{Path, PathBuf};
use crate::{Db, Error, Result};
use crate::{Db, DbError, Error, Result};
/// List of supported database engine types
///
@@ -49,14 +49,14 @@ impl std::str::FromStr for Engine {
"lmdb" | "heed" => Ok(Self::Lmdb),
"sqlite" | "sqlite3" | "rusqlite" => Ok(Self::Sqlite),
"fjall" => Ok(Self::Fjall),
"sled" => Err(Error("Sled is no longer supported as a database engine. Converting your old metadata db can be done using an older Garage binary (e.g. v0.9.4).".into())),
kind => Err(Error(
"sled" => Err(DbError("Sled is no longer supported as a database engine. Converting your old metadata db can be done using an older Garage binary (e.g. v0.9.4).".into()).into()),
kind => Err(DbError(
format!(
"Invalid DB engine: {} (options are: lmdb, sqlite, fjall)",
kind
)
.into(),
)),
).into()),
}
}
}
@@ -72,22 +72,23 @@ pub fn open_db(path: &PathBuf, engine: Engine, opt: &OpenOpt) -> Result<Db> {
match engine {
// ---- Sqlite DB ----
#[cfg(feature = "sqlite")]
Engine::Sqlite => crate::sqlite_adapter::open_db(path, opt),
Engine::Sqlite => Ok(crate::sqlite_adapter::open_db(path, opt)?),
// ---- LMDB DB ----
#[cfg(feature = "lmdb")]
Engine::Lmdb => crate::lmdb_adapter::open_db(path, opt),
Engine::Lmdb => Ok(crate::lmdb_adapter::open_db(path, opt)?),
// ---- Fjall DB ----
#[cfg(feature = "fjall")]
Engine::Fjall => crate::fjall_adapter::open_db(path, opt),
Engine::Fjall => Ok(crate::fjall_adapter::open_db(path, opt)?),
// Pattern is unreachable when all supported DB engines are compiled into binary. The allow
// attribute is added so that we won't have to change this match in case stop building
// support for one or more engines by default.
#[allow(unreachable_patterns)]
engine => Err(Error(
engine => Err(DbError(
format!("DB engine support not available in this build: {}", engine).into(),
)),
)
.into()),
}
}
+55 -43
View File
@@ -12,7 +12,7 @@ use rusqlite::{params, Rows, Statement, Transaction};
use crate::{
open::{Engine, OpenOpt},
Db, Error, IDb, ITx, ITxFn, OnCommit, Result, TxError, TxFnResult, TxOpError, TxOpResult,
Db, DbError, DbResult, Error, IDb, ITx, ITxFn, OnCommit, TxError, TxFnResult, TxOpError,
TxResult, TxValueIter, Value, ValueIter,
};
@@ -20,7 +20,7 @@ pub use rusqlite;
// ---- top-level open function
pub(crate) fn open_db(path: &PathBuf, opt: &OpenOpt) -> Result<Db> {
pub(crate) fn open_db(path: &PathBuf, opt: &OpenOpt) -> DbResult<Db> {
info!("Opening Sqlite database at: {}", path.display());
let manager = r2d2_sqlite::SqliteConnectionManager::file(path);
SqliteDb::open(manager, opt.fsync)
@@ -32,21 +32,33 @@ type Connection = r2d2::PooledConnection<SqliteConnectionManager>;
// --- err
impl From<rusqlite::Error> for DbError {
fn from(e: rusqlite::Error) -> DbError {
DbError(format!("Sqlite: {}", e).into())
}
}
impl From<rusqlite::Error> for Error {
fn from(e: rusqlite::Error) -> Error {
Error(format!("Sqlite: {}", e).into())
DbError::from(e).into()
}
}
impl From<r2d2::Error> for DbError {
fn from(e: r2d2::Error) -> DbError {
DbError(format!("Sqlite: {}", e).into())
}
}
impl From<r2d2::Error> for Error {
fn from(e: r2d2::Error) -> Error {
Error(format!("Sqlite: {}", e).into())
DbError::from(e).into()
}
}
impl From<rusqlite::Error> for TxOpError {
fn from(e: rusqlite::Error) -> TxOpError {
TxOpError(e.into())
DbError::from(e).into()
}
}
@@ -62,7 +74,7 @@ pub struct SqliteDb {
}
impl SqliteDb {
pub fn open(manager: SqliteConnectionManager, sync_mode: bool) -> Result<Db> {
pub fn open(manager: SqliteConnectionManager, sync_mode: bool) -> DbResult<Db> {
let manager = manager.with_init(move |db| {
db.pragma_update(None, "journal_mode", "WAL")?;
if sync_mode {
@@ -82,16 +94,16 @@ impl SqliteDb {
}
impl SqliteDb {
fn get_tree(&self, i: usize) -> Result<Arc<str>> {
fn get_tree(&self, i: usize) -> DbResult<Arc<str>> {
self.trees
.read()
.unwrap()
.get(i)
.cloned()
.ok_or_else(|| Error("invalid tree id".into()))
.ok_or_else(|| DbError("invalid tree id".into()))
}
fn internal_get(&self, db: &Connection, tree: &str, key: &[u8]) -> Result<Option<Value>> {
fn internal_get(&self, db: &Connection, tree: &str, key: &[u8]) -> DbResult<Option<Value>> {
let mut stmt = db.prepare(&format!("SELECT v FROM {} WHERE k = ?1", tree))?;
let mut res_iter = stmt.query([key])?;
match res_iter.next()? {
@@ -106,7 +118,7 @@ impl IDb for SqliteDb {
format!("sqlite3 v{} (using rusqlite crate)", rusqlite::version())
}
fn open_tree(&self, name: &str) -> Result<usize> {
fn open_tree(&self, name: &str) -> DbResult<usize> {
let name = format!("tree_{}", name.replace(':', "_COLON_"));
let mut trees = self.trees.write().unwrap();
@@ -133,7 +145,7 @@ impl IDb for SqliteDb {
}
}
fn list_trees(&self) -> Result<Vec<String>> {
fn list_trees(&self) -> DbResult<Vec<String>> {
let mut trees = vec![];
let db = self.db.get()?;
@@ -150,13 +162,13 @@ impl IDb for SqliteDb {
Ok(trees)
}
fn snapshot(&self, base_path: &Path) -> Result<()> {
fn snapshot(&self, base_path: &Path) -> DbResult<()> {
std::fs::create_dir_all(base_path)?;
let path = Engine::Sqlite
.db_path(base_path)
.into_os_string()
.into_string()
.map_err(|_| Error("invalid sqlite path string".into()))?;
.map_err(|_| DbError("invalid sqlite path string".into()))?;
info!("Start sqlite VACUUM INTO `{}`", path);
self.db.get()?.execute("VACUUM INTO ?1", params![path])?;
@@ -167,12 +179,12 @@ impl IDb for SqliteDb {
// ----
fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> {
fn get(&self, tree: usize, key: &[u8]) -> DbResult<Option<Value>> {
let tree = self.get_tree(tree)?;
self.internal_get(&self.db.get()?, &tree, key)
}
fn approximate_len(&self, tree: usize) -> Result<usize> {
fn approximate_len(&self, tree: usize) -> DbResult<usize> {
let tree = self.get_tree(tree)?;
let db = self.db.get()?;
@@ -184,11 +196,11 @@ impl IDb for SqliteDb {
}
}
fn is_empty(&self, tree: usize) -> Result<bool> {
fn is_empty(&self, tree: usize) -> DbResult<bool> {
Ok(self.approximate_len(tree)? == 0)
}
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<()> {
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree)?;
let db = self.db.get()?;
let lock = self.write_lock.lock();
@@ -206,7 +218,7 @@ impl IDb for SqliteDb {
Ok(())
}
fn remove(&self, tree: usize, key: &[u8]) -> Result<()> {
fn remove(&self, tree: usize, key: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree)?;
let db = self.db.get()?;
let lock = self.write_lock.lock();
@@ -217,7 +229,7 @@ impl IDb for SqliteDb {
Ok(())
}
fn clear(&self, tree: usize) -> Result<()> {
fn clear(&self, tree: usize) -> DbResult<()> {
let tree = self.get_tree(tree)?;
let db = self.db.get()?;
let lock = self.write_lock.lock();
@@ -228,13 +240,13 @@ impl IDb for SqliteDb {
Ok(())
}
fn iter(&self, tree: usize) -> Result<ValueIter<'_>> {
fn iter(&self, tree: usize) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let sql = format!("SELECT k, v FROM {} ORDER BY k ASC", tree);
DbValueIterator::make(self.db.get()?, &sql, [])
}
fn iter_rev(&self, tree: usize) -> Result<ValueIter<'_>> {
fn iter_rev(&self, tree: usize) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let sql = format!("SELECT k, v FROM {} ORDER BY k DESC", tree);
DbValueIterator::make(self.db.get()?, &sql, [])
@@ -245,7 +257,7 @@ impl IDb for SqliteDb {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>> {
) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let (bounds_sql, params) = bounds_sql(low, high);
@@ -263,7 +275,7 @@ impl IDb for SqliteDb {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> Result<ValueIter<'_>> {
) -> DbResult<ValueIter<'_>> {
let tree = self.get_tree(tree)?;
let (bounds_sql, params) = bounds_sql(low, high);
@@ -300,9 +312,9 @@ impl IDb for SqliteDb {
}
TxFnResult::DbErr => {
tx.tx.rollback().map_err(Error::from).map_err(TxError::Db)?;
Err(TxError::Db(Error(
"(this message will be discarded)".into(),
)))
Err(TxError::Db(
DbError("(this message will be discarded)".into()).into(),
))
}
};
@@ -320,15 +332,15 @@ struct SqliteTx<'a> {
}
impl<'a> SqliteTx<'a> {
fn get_tree(&self, i: usize) -> TxOpResult<&'_ str> {
fn get_tree(&self, i: usize) -> DbResult<&'_ str> {
self.trees.get(i).map(Arc::as_ref).ok_or_else(|| {
TxOpError(Error(
DbError(
"invalid tree id (it might have been opened after the transaction started)".into(),
))
)
})
}
fn internal_get(&self, tree: &str, key: &[u8]) -> TxOpResult<Option<Value>> {
fn internal_get(&self, tree: &str, key: &[u8]) -> DbResult<Option<Value>> {
let mut stmt = self
.tx
.prepare(&format!("SELECT v FROM {} WHERE k = ?1", tree))?;
@@ -341,11 +353,11 @@ impl<'a> SqliteTx<'a> {
}
impl<'a> ITx for SqliteTx<'a> {
fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> {
fn get(&self, tree: usize, key: &[u8]) -> DbResult<Option<Value>> {
let tree = self.get_tree(tree)?;
self.internal_get(tree, key)
}
fn len(&self, tree: usize) -> TxOpResult<usize> {
fn len(&self, tree: usize) -> DbResult<usize> {
let tree = self.get_tree(tree)?;
let mut stmt = self.tx.prepare(&format!("SELECT COUNT(*) FROM {}", tree))?;
let mut res_iter = stmt.query([])?;
@@ -355,30 +367,30 @@ impl<'a> ITx for SqliteTx<'a> {
}
}
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<()> {
fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree)?;
let sql = format!("INSERT OR REPLACE INTO {} (k, v) VALUES (?1, ?2)", tree);
self.tx.execute(&sql, params![key, value])?;
Ok(())
}
fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<()> {
fn remove(&mut self, tree: usize, key: &[u8]) -> DbResult<()> {
let tree = self.get_tree(tree)?;
self.tx
.execute(&format!("DELETE FROM {} WHERE k = ?1", tree), params![key])?;
Ok(())
}
fn clear(&mut self, tree: usize) -> TxOpResult<()> {
fn clear(&mut self, tree: usize) -> DbResult<()> {
let tree = self.get_tree(tree)?;
self.tx.execute(&format!("DELETE FROM {}", tree), [])?;
Ok(())
}
fn iter(&self, tree: usize) -> TxOpResult<TxValueIter<'_>> {
fn iter(&self, tree: usize) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree)?;
let sql = format!("SELECT k, v FROM {} ORDER BY k ASC", tree);
TxValueIterator::make(self, &sql, [])
}
fn iter_rev(&self, tree: usize) -> TxOpResult<TxValueIter<'_>> {
fn iter_rev(&self, tree: usize) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree)?;
let sql = format!("SELECT k, v FROM {} ORDER BY k DESC", tree);
TxValueIterator::make(self, &sql, [])
@@ -389,7 +401,7 @@ impl<'a> ITx for SqliteTx<'a> {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>> {
) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree)?;
let (bounds_sql, params) = bounds_sql(low, high);
@@ -407,7 +419,7 @@ impl<'a> ITx for SqliteTx<'a> {
tree: usize,
low: Bound<&'r [u8]>,
high: Bound<&'r [u8]>,
) -> TxOpResult<TxValueIter<'_>> {
) -> DbResult<TxValueIter<'_>> {
let tree = self.get_tree(tree)?;
let (bounds_sql, params) = bounds_sql(low, high);
@@ -439,7 +451,7 @@ impl DbValueIterator {
db: Connection,
sql: &str,
args: P,
) -> Result<ValueIter<'res>> {
) -> DbResult<ValueIter<'res>> {
let res = DbValueIterator {
db,
stmt: None,
@@ -484,7 +496,7 @@ impl Drop for DbValueIterator {
struct DbValueIteratorPin(Pin<Box<DbValueIterator>>);
impl Iterator for DbValueIteratorPin {
type Item = Result<(Value, Value)>;
type Item = DbResult<(Value, Value)>;
fn next(&mut self) -> Option<Self::Item> {
let mut_ref = Pin::as_mut(&mut self.0);
@@ -509,7 +521,7 @@ impl<'a> TxValueIterator<'a> {
tx: &'a SqliteTx<'a>,
sql: &str,
args: P,
) -> TxOpResult<TxValueIter<'a>> {
) -> DbResult<TxValueIter<'a>> {
let stmt = tx.tx.prepare(sql)?;
let res = TxValueIterator {
stmt,
@@ -543,7 +555,7 @@ impl<'a> Drop for TxValueIterator<'a> {
struct TxValueIteratorPin<'a>(Pin<Box<TxValueIterator<'a>>>);
impl<'a> Iterator for TxValueIteratorPin<'a> {
type Item = TxOpResult<(Value, Value)>;
type Item = DbResult<(Value, Value)>;
fn next(&mut self) -> Option<Self::Item> {
let mut_ref = Pin::as_mut(&mut self.0);
+241
View File
@@ -0,0 +1,241 @@
use std::marker::PhantomData;
use std::ops::{Bound, RangeBounds};
// Todo: some parts of this code, notably around ranges are never used but are here to prepare
// the migration of the parts of the codebase that still use untyped trees. At one point, this
// migration should be done or these functions deleted.
use super::{
DbResult, DecodeError, Error, Result, Transaction, Tree, TxOpError, TxOpResult, TxValueIter,
ValueIter,
};
pub use super::Db;
pub trait DbBytes: Sized {
fn encode(&self) -> Vec<u8>;
fn decode(bytes: &[u8]) -> std::result::Result<Self, DecodeError>;
}
/// Subtrait of [`DbBytes`] for types used as tree keys with operations where order matters
/// (`get_gt`, range, etc...).
///
/// Implementors must guarantee that the byte encoding is order-preserving:
/// for any `a, b: Self`, `a.cmp(&b) == a.encode().cmp(&b.encode())`.
pub trait DbOrdKey: DbBytes + Ord {}
#[derive(Clone)]
pub struct TypedTree<K, V> {
inner: Tree,
_phantom: PhantomData<[(K, V)]>,
}
impl<K: DbBytes, V: DbBytes> TypedTree<K, V> {
pub fn new(tree: Tree) -> Self {
Self {
inner: tree,
_phantom: PhantomData,
}
}
pub fn db(&self) -> Db {
self.inner.db()
}
pub fn untyped(&self) -> &Tree {
&self.inner
}
pub fn get(&self, key: &K) -> Result<Option<V>> {
self.inner
.get(key.encode())?
.map(|v| V::decode(&v).map_err(Error::from))
.transpose()
}
pub fn approximate_len(&self) -> DbResult<usize> {
self.inner.approximate_len()
}
pub fn is_empty(&self) -> DbResult<bool> {
self.inner.is_empty()
}
pub fn insert(&self, key: &K, value: &V) -> DbResult<()> {
self.inner.insert(key.encode(), value.encode())
}
pub fn remove(&self, key: &K) -> DbResult<()> {
self.inner.remove(key.encode())
}
pub fn clear(&self) -> DbResult<()> {
self.inner.clear()
}
pub fn tx_get(&self, tx: &Transaction<'_>, key: &K) -> TxOpResult<Option<V>> {
tx.get(&self.inner, key.encode())?
.map(|v| V::decode(&v).map_err(TxOpError::from))
.transpose()
}
pub fn tx_insert(&self, tx: &mut Transaction<'_>, key: &K, value: &V) -> TxOpResult<()> {
tx.insert(&self.inner, key.encode(), value.encode())
}
pub fn tx_remove(&self, tx: &mut Transaction<'_>, key: &K) -> TxOpResult<()> {
tx.remove(&self.inner, key.encode())
}
pub fn tx_clear(&self, tx: &mut Transaction<'_>) -> TxOpResult<()> {
tx.clear(&self.inner)
}
}
impl<K: DbOrdKey, V: DbBytes> TypedTree<K, V> {
pub fn first(&self) -> Result<Option<(K, V)>> {
self.iter()?.next().transpose()
}
pub fn get_gt(&self, from: &K) -> Result<Option<(K, V)>> {
self.inner
.get_gt(from.encode())?
.map(|(k, v)| {
Ok((
K::decode(&k).map_err(Error::from)?,
V::decode(&v).map_err(Error::from)?,
))
})
.transpose()
}
pub fn iter(&self) -> Result<TypedIter<'_, K, V>> {
Ok(TypedIter::new(self.inner.iter()?))
}
pub fn iter_rev(&self) -> Result<TypedIter<'_, K, V>> {
Ok(TypedIter::new(self.inner.iter_rev()?))
}
pub fn range<R: RangeBounds<K>>(&self, range: R) -> Result<TypedIter<'_, K, V>> {
Ok(TypedIter::new(self.inner.range(encode_range(range))?))
}
pub fn range_rev<R: RangeBounds<K>>(&self, range: R) -> Result<TypedIter<'_, K, V>> {
Ok(TypedIter::new(self.inner.range_rev(encode_range(range))?))
}
pub fn tx_iter<'t>(&self, tx: &'t Transaction<'_>) -> TxOpResult<TypedTxIter<'t, K, V>> {
Ok(TypedTxIter::new(tx.iter(&self.inner)?))
}
pub fn tx_iter_rev<'t>(&self, tx: &'t Transaction<'_>) -> TxOpResult<TypedTxIter<'t, K, V>> {
Ok(TypedTxIter::new(tx.iter_rev(&self.inner)?))
}
pub fn tx_range<'t, R: RangeBounds<K>>(
&self,
tx: &'t Transaction<'_>,
range: R,
) -> TxOpResult<TypedTxIter<'t, K, V>> {
Ok(TypedTxIter::new(
tx.range(&self.inner, encode_range(range))?,
))
}
pub fn tx_range_rev<'t, R: RangeBounds<K>>(
&self,
tx: &'t Transaction<'_>,
range: R,
) -> TxOpResult<TypedTxIter<'t, K, V>> {
Ok(TypedTxIter::new(
tx.range_rev(&self.inner, encode_range(range))?,
))
}
}
impl<K: DbBytes, V: DbBytes> From<Tree> for TypedTree<K, V> {
fn from(tree: Tree) -> Self {
Self::new(tree)
}
}
impl Db {
pub fn open_typed_tree<K: DbBytes, V: DbBytes, S: AsRef<str>>(
&self,
name: S,
) -> DbResult<TypedTree<K, V>> {
Ok(TypedTree::new(self.open_tree(name)?))
}
}
pub struct TypedIter<'a, K, V> {
inner: ValueIter<'a>,
_phantom: PhantomData<(K, V)>,
}
impl<'a, K, V> TypedIter<'a, K, V> {
fn new(inner: ValueIter<'a>) -> Self {
Self {
inner,
_phantom: PhantomData,
}
}
}
impl<K: DbOrdKey, V: DbBytes> Iterator for TypedIter<'_, K, V> {
type Item = Result<(K, V)>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|res| {
let (k, v) = res?;
Ok((
K::decode(&k).map_err(Error::from)?,
V::decode(&v).map_err(Error::from)?,
))
})
}
}
pub struct TypedTxIter<'a, K, V> {
inner: TxValueIter<'a>,
_phantom: PhantomData<(K, V)>,
}
impl<'a, K, V> TypedTxIter<'a, K, V> {
fn new(inner: TxValueIter<'a>) -> Self {
Self {
inner,
_phantom: PhantomData,
}
}
}
impl<K: DbOrdKey, V: DbBytes> Iterator for TypedTxIter<'_, K, V> {
type Item = TxOpResult<(K, V)>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|res| {
let (k, v) = res?;
Ok((
K::decode(&k).map_err(TxOpError::from)?,
V::decode(&v).map_err(TxOpError::from)?,
))
})
}
}
fn encode_range<K: DbOrdKey, R: RangeBounds<K>>(range: R) -> (Bound<Vec<u8>>, Bound<Vec<u8>>) {
(
encode_bound(range.start_bound()),
encode_bound(range.end_bound()),
)
}
fn encode_bound<K: DbOrdKey>(bound: Bound<&K>) -> Bound<Vec<u8>> {
match bound {
Bound::Included(k) => Bound::Included(k.encode()),
Bound::Excluded(k) => Bound::Excluded(k.encode()),
Bound::Unbounded => Bound::Unbounded,
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ pub struct OpenLmdbOpt {
pub(crate) fn do_conversion(args: ConvertDbOpt) -> Result<()> {
if args.input_engine == args.output_engine {
return Err(Error("input and output database engine must differ".into()));
return Err(DbError("input and output database engine must differ".into()).into());
}
let opt = OpenOpt {