use serde::{Deserialize, Serialize, de::DeserializeOwned};
use sled::IVec;
use crate::{
database::{
CompareAndSwapTransaction, Createable, CustomTransactionError, Database, Db, DbIdExt,
Deleteable, Mergeable, TransactionError, deserialize_from_ivec,
},
id::Id,
};
#[derive(Clone)]
pub struct Entry<T: DatabaseEntry> {
entry: T,
id: Id<T>,
}
impl<T: DatabaseEntry> std::ops::Deref for Entry<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
impl<T: DatabaseEntry> std::ops::DerefMut for Entry<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.entry
}
}
impl<T: DatabaseEntry> From<(T, Id<T>)> for Entry<T> {
fn from(value: (T, Id<T>)) -> Self {
Self::new(value.0, value.1)
}
}
impl<T: DatabaseEntry> From<(Id<T>, T)> for Entry<T> {
fn from(value: (Id<T>, T)) -> Self {
Self::new(value.1, value.0)
}
}
impl<T: DatabaseEntry> PartialEq for Entry<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T: DatabaseEntry> Eq for Entry<T> {}
impl<T: DatabaseEntry> std::hash::Hash for Entry<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl<T: DatabaseEntry> Entry<T> {
pub fn new(item: T, id: Id<T>) -> Self {
Self { entry: item, id }
}
pub fn id(&self) -> Id<T> {
self.id
}
pub fn into_item(self) -> T {
self.entry
}
pub(crate) fn from_sled_batch(
value: sled::Result<(IVec, IVec)>,
) -> Result<Self, TransactionError> {
let (k, v) = value.map_err(TransactionError::Sled)?;
let id = Id::try_from(k).expect("All keys should be 32 bytes");
let t: T = deserialize_from_ivec(v)?;
Ok(t.to_entry(id))
}
}
impl<T: DatabaseEntry> Entry<T> {
pub fn tx_upsert(
mut self,
cas_tx: &mut CompareAndSwapTransaction<T::DbInner>,
) -> Result<(), TransactionError> {
T::pre_upsert(&mut self, cas_tx)?;
let request = cas_tx.get_or_new_request();
if let Some(get_mut) = request.get_mut(&self.id) {
get_mut.new = Some(self.entry);
} else {
let old = request.tree().get(self.id)?;
request.insert(self.id, old, Some(self.entry));
}
Ok(())
}
pub fn tx_insert(
self,
cas_tx: &mut CompareAndSwapTransaction<T::DbInner>,
) -> Result<(), TransactionError> {
if self.id.tx_check(cas_tx)? {
return Err(TransactionError::AlreadyInDatabase);
}
self.tx_upsert(cas_tx)?;
Ok(())
}
pub fn tx_patch(
self,
cas_tx: &mut CompareAndSwapTransaction<T::DbInner>,
) -> Result<(), TransactionError>
where
T: Mergeable + std::fmt::Debug,
{
self.id()
.tx_fetch_and_update(
|old, cas_tx| match old {
Some(mut old) => {
T::relink_references(&self, self.id(), cas_tx)?;
old.merge_data(self.entry);
Ok(Some(old))
}
None => Ok(Some(self.entry)),
},
cas_tx,
)
.map_err(TransactionError::from)
}
pub fn tx_merge(
self,
from: Self,
cas_tx: &mut CompareAndSwapTransaction<T::DbInner>,
) -> Result<Self, TransactionError>
where
T: Mergeable + Deleteable,
{
self.tx_merge_batch(std::iter::once(from), cas_tx)
}
pub fn tx_merge_batch(
self,
batch: impl IntoIterator<Item = Self>,
cas_tx: &mut CompareAndSwapTransaction<T::DbInner>,
) -> Result<Self, TransactionError>
where
T: Mergeable + Deleteable,
{
let merged = batch.into_iter().try_fold(
self,
|mut current, from| -> Result<Entry<T>, TransactionError> {
T::relink_references(&from, current.id, cas_tx)?;
if from.id != current.id {
from.id.tx_delete(cas_tx)?;
}
current.merge_data(from.entry);
Ok(current)
},
)?;
merged.clone().tx_upsert(cas_tx)?;
Ok(merged)
}
pub fn tx_create(
args: T::CreateArgs,
cas_tx: &mut CompareAndSwapTransaction<T::DbInner>,
) -> Result<Self, CustomTransactionError<T::Err>>
where
T: Createable,
{
let entry = T::create(args, cas_tx)?;
entry
.clone()
.tx_upsert(cas_tx)
.map_err(CustomTransactionError::from)?;
Ok(entry)
}
}
impl<T: DatabaseEntry> Entry<T> {
pub fn db_upsert(self, db: &Db<T::DbInner>) -> Result<(), TransactionError> {
db.transaction(false, None, |cas_tx| {
self.clone()
.tx_upsert(cas_tx)
.map_err(CustomTransactionError::from)
})
.map_err(TransactionError::from)
}
pub fn db_insert(self, db: &Db<T::DbInner>) -> Result<(), TransactionError> {
db.transaction(false, None, |cas_tx| {
self.clone()
.tx_insert(cas_tx)
.map_err(CustomTransactionError::from)
})
.map_err(TransactionError::from)
}
pub fn db_patch(self, db: &Db<T::DbInner>) -> Result<(), TransactionError>
where
T: Mergeable + std::fmt::Debug,
{
db.transaction(false, None, |cas_tx| {
self.clone()
.tx_patch(cas_tx)
.map_err(CustomTransactionError::from)
})
.map_err(TransactionError::from)
}
pub fn db_merge(self, from: Self, db: &Db<T::DbInner>) -> Result<Self, TransactionError>
where
T: Mergeable + Deleteable,
{
db.transaction(false, None, |cas_tx| {
self.clone()
.tx_merge(from.clone(), cas_tx)
.map_err(CustomTransactionError::from)
})
.map_err(TransactionError::from)
}
pub fn db_merge_batch(
self,
batch: impl IntoIterator<Item = Self>,
db: &Db<T::DbInner>,
) -> Result<Self, TransactionError>
where
T: Mergeable + Deleteable,
{
let batch: Vec<Self> = batch.into_iter().collect();
db.transaction(false, None, |cas_tx| {
self.clone()
.tx_merge_batch(batch.clone(), cas_tx)
.map_err(CustomTransactionError::from)
})
.map_err(TransactionError::from)
}
pub fn db_create(
args: T::CreateArgs,
db: &Db<T::DbInner>,
) -> Result<Self, CustomTransactionError<T::Err>>
where
T: Createable,
{
db.transaction(false, None, |cas_tx| Self::tx_create(args.clone(), cas_tx))
}
pub fn db_get_list(
cursor: Option<Id<T>>,
limit: usize,
db: &Db<T::DbInner>,
) -> Result<Vec<Self>, TransactionError> {
use std::ops::Bound;
let tree = db.entry_tree::<T>();
let start: Bound<Id<T>> = match cursor {
Some(key) => Bound::Excluded(key),
None => Bound::Unbounded,
};
tree.range((start, Bound::Unbounded))
.take(limit)
.map(Entry::from_sled_batch)
.collect()
}
}
pub trait DatabaseEntry:
Serialize + DeserializeOwned + for<'de> Deserialize<'de> + Clone + 'static
{
type DbInner: Database;
const VERSION_NUMBER: u32;
const TREE_NAME: &str;
#[allow(unused_variables)]
fn pre_upsert(
entry: &mut Entry<Self>,
cas_tx: &mut CompareAndSwapTransaction<Self::DbInner>,
) -> Result<(), TransactionError> {
Ok(())
}
#[must_use]
fn to_entry(self, id: Id<Self>) -> Entry<Self> {
Entry::new(self, id)
}
}