use crate::config::FileConfig;
use crate::error::NetabaseError;
use crate::traits::backend_store::{BackendStore, PathBasedBackend};
use crate::traits::convert::ToIVec;
use crate::traits::definition::NetabaseDefinitionTrait;
use crate::traits::model::NetabaseModelTrait;
use std::marker::PhantomData;
use std::path::Path;
use std::str::FromStr;
use strum::{IntoDiscriminant, IntoEnumIterator};
use super::transaction::SledTransactionalTree;
use super::tree::SledStoreTree;
use super::types::SecondaryKeyOp;
pub struct SledStore<D>
where
D: NetabaseDefinitionTrait,
<D as IntoDiscriminant>::Discriminant: crate::traits::definition::NetabaseDiscriminant,
{
pub(crate) db: sled::Db,
pub trees: Vec<D::Discriminant>,
}
impl<D> SledStore<D>
where
D: NetabaseDefinitionTrait,
<D as IntoDiscriminant>::Discriminant: crate::traits::definition::NetabaseDiscriminant,
{
pub fn db(&self) -> &sled::Db {
&self.db
}
}
impl<D> SledStore<D>
where
D: NetabaseDefinitionTrait + ToIVec,
<D as IntoDiscriminant>::Discriminant: crate::traits::definition::NetabaseDiscriminant,
{
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, NetabaseError> {
let db = sled::open(path)?;
Ok(Self {
db,
trees: D::Discriminant::iter().collect(),
})
}
pub fn temp() -> Result<Self, NetabaseError> {
let config = sled::Config::new().temporary(true);
let db = config.open()?;
Ok(Self {
db,
trees: D::Discriminant::iter().collect(),
})
}
pub fn open_tree<M>(&self) -> SledStoreTree<'_, D, M>
where
M: NetabaseModelTrait<D> + TryFrom<D> + Into<D>,
D: TryFrom<M> + ToIVec,
{
SledStoreTree::new(&self.db, M::DISCRIMINANT)
}
pub fn open_t(&self, _key: D::Keys) {
}
pub fn tree_names(&self) -> Vec<D::Discriminant> {
D::Discriminant::iter().collect()
}
pub fn flush(&self) -> Result<usize, NetabaseError> {
Ok(self.db.flush()?)
}
pub fn transaction<M, F, R>(&self, f: F) -> Result<R, NetabaseError>
where
M: NetabaseModelTrait<D> + TryFrom<D> + Into<D>,
D: TryFrom<M>,
F: Fn(&SledTransactionalTree<D, M>) -> Result<R, Box<dyn std::error::Error>>,
{
let tree = self.db.open_tree(M::DISCRIMINANT.to_string())?;
let sec_tree_name = format!("{}_secondary", M::discriminant_name());
let secondary_tree = self.db.open_tree(sec_tree_name)?;
let pending_secondary_ops = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let pending_ops_clone = pending_secondary_ops.clone();
let result = tree
.transaction(|txn_tree| {
let wrapper = SledTransactionalTree {
tree: txn_tree.clone(),
secondary_tree: secondary_tree.clone(),
pending_secondary_keys: Some(pending_ops_clone.clone()),
_phantom_d: PhantomData,
_phantom_m: PhantomData,
};
f(&wrapper).map_err(sled::transaction::ConflictableTransactionError::Abort)
})
.map_err(|e| match e {
sled::transaction::TransactionError::Abort(e) => {
NetabaseError::Transaction(format!("Transaction aborted: {}", e))
}
sled::transaction::TransactionError::Storage(e) => e.into(),
})?;
let ops = pending_secondary_ops.lock().unwrap();
for op in ops.iter() {
match op {
SecondaryKeyOp::Insert(key) => {
secondary_tree.insert(key, &[] as &[u8])?;
}
SecondaryKeyOp::Remove(key) => {
secondary_tree.remove(key)?;
}
}
}
Ok(result)
}
}
impl<D> BackendStore<D> for SledStore<D>
where
D: NetabaseDefinitionTrait,
<D as IntoDiscriminant>::Discriminant: Clone
+ Copy
+ std::fmt::Debug
+ std::fmt::Display
+ PartialEq
+ Eq
+ std::hash::Hash
+ strum::IntoEnumIterator
+ 'static
+ FromStr,
{
type Config = FileConfig;
fn new(config: Self::Config) -> Result<Self, NetabaseError> {
if config.truncate && config.path.exists() {
std::fs::remove_dir_all(&config.path)?;
}
let db = if config.create_if_missing {
sled::open(&config.path)?
} else {
sled::Config::new()
.path(&config.path)
.cache_capacity((config.cache_size_mb * 1024 * 1024) as u64)
.use_compression(true)
.open()?
};
Ok(Self {
db,
trees: D::Discriminant::iter().collect(),
})
}
fn open(config: Self::Config) -> Result<Self, NetabaseError> {
let db = sled::Config::new()
.path(&config.path)
.cache_capacity((config.cache_size_mb * 1024 * 1024) as u64)
.use_compression(true)
.open()?;
Ok(Self {
db,
trees: D::Discriminant::iter().collect(),
})
}
fn temp() -> Result<Self, NetabaseError> {
let config = FileConfig::temp();
<Self as BackendStore<D>>::new(config)
}
}
impl<D> PathBasedBackend<D> for SledStore<D>
where
D: NetabaseDefinitionTrait,
<D as IntoDiscriminant>::Discriminant: Clone
+ Copy
+ std::fmt::Debug
+ std::fmt::Display
+ PartialEq
+ Eq
+ std::hash::Hash
+ strum::IntoEnumIterator
+ 'static
+ FromStr,
{
fn at_path<P: AsRef<Path>>(path: P) -> Result<Self, NetabaseError> {
let config = FileConfig::new(path.as_ref());
<Self as BackendStore<D>>::open(config)
}
}