treehouse 0.1.0

A searchable typed datastore built with sled
Documentation
/// Error management
pub mod err {

    /// Error container
    #[derive(thiserror::Error, Debug)]
    pub enum Error {
        #[error("I/O error: `{0}`")]
        Io(#[from] std::io::Error),
        #[error("Database error: `{0}`")]
        Sled(#[from] sled::Error),
        #[cfg(feature = "bincode")]
        #[error("De/serialization error: `{0}`")]
        Bincode(#[from] bincode::Error),
        #[cfg(feature = "serde_cbor")]
        #[error("De/serialization error: `{0}`")]
        CBOR(#[from] serde_cbor::Error),
        #[error("Error: `{0}`")]
        Custom(Box<str>),
    }

    impl From<sled::transaction::TransactionError<Error>> for Error {
        fn from(t: sled::transaction::TransactionError<Error>) -> Self {
            match t {
                sled::transaction::TransactionError::Abort(t) => t,
                sled::transaction::TransactionError::Storage(t) => Error::Sled(t),
            }
        }
    }

    impl From<Error> for sled::transaction::ConflictableTransactionError<Error> {
        fn from(t: Error) -> Self {
            sled::transaction::ConflictableTransactionError::Abort(t)
        }
    }

    pub fn custom<T: std::fmt::Display>(t: T) -> Error {
        Error::Custom(t.to_string().into_boxed_str())
    }

    pub type Result<T> = std::result::Result<T, Error>;
}

mod utils {

    use std::convert::TryInto;
    use sled::transaction::TransactionalTree;

    #[cfg(feature = "bincode")]
    pub fn serialize<T: ?Sized + serde::Serialize>(value: &T) -> crate::err::Result<Vec<u8>> {
        Ok(bincode::serialize(value)?)
    }

    #[cfg(feature = "bincode")]
    pub fn deserialize<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> crate::err::Result<T> {
        Ok(bincode::deserialize(bytes)?)
    }

    pub fn u64_to_bytes(value: u64) -> [u8; 8] {
        u64::to_be_bytes(value)
    }

    pub fn bytes_to_u64(value: &[u8]) -> crate::err::Result<u64> {
        Ok(u64::from_be_bytes(value.try_into().map_err(crate::err::custom)?))
    }
}

pub mod query;
mod ext;

use sled::transaction::Transactional;
use sled::transaction::ConflictableTransactionError;
use std::marker::PhantomData;
use std::collections::BTreeSet;

pub struct StoreBuilder<T> {
    db: Option<sled::Db>,
    tree_name: Option<Vec<u8>>,
    meta_name: Option<Vec<u8>>,
    marker: PhantomData<fn(T)>,
}

impl<T> Default for StoreBuilder<T> {
    fn default() -> Self {
        Self {
            db: None,
            tree_name: None,
            meta_name: None,
            marker: PhantomData,
        }
    }
}

impl<T> StoreBuilder<T> {
    pub fn with_db(mut self, db: sled::Db) -> Self {
        self.db = Some(db);
        self
    }

    pub fn with_tree_name<S: Into<Vec<u8>>>(mut self, tree_name: S) -> Self {
        self.tree_name = Some(tree_name.into());
        self
    }

    pub fn with_meta_name<S: Into<Vec<u8>>>(mut self, meta_name: S) -> Self {
        self.meta_name = Some(meta_name.into());
        self
    }

    pub fn finish(self) -> err::Result<Store<T>> {
        let Self { db, tree_name, meta_name, marker } = self;

        let db = match db {
            Some(db) => db,
            None => { return Err(err::custom("No `db` provided")); }
        };

        let tree_name = match tree_name {
            Some(tree_name) => tree_name,
            None => { return Err(err::custom("No `tree_name` specified")); }
        };

        let meta_name = meta_name.unwrap_or_else(|| {
            tree_name.iter().chain(b"\0meta").copied().collect()
        });

        Ok(Store {
            tree: db.open_tree(&tree_name)?,
            meta: db.open_tree(&meta_name)?,
            db,
            marker
        })
    }
}

pub struct Store<T> {
    db: sled::Db,
    tree: sled::Tree,
    meta: sled::Tree,
    marker: PhantomData<fn(T)>,
}

pub struct TransactionalStore<'a, T> {
    tree: &'a sled::transaction::TransactionalTree,
    meta: ext::TransactionalMeta<'a>,
    marker: PhantomData<fn(T)>,
}

impl<'a, T: query::Queryable + serde::Serialize + serde::de::DeserializeOwned> TransactionalStore<'a, T> {
    pub fn create(&self, id: u64, inner: &T) -> Result<u64, ConflictableTransactionError<err::Error>> {

        let id_bytes = utils::u64_to_bytes(id);

        let serialized_inner = utils::serialize(inner)?;

        self.tree.insert(&id_bytes, serialized_inner)?;

        let new_terms =
            inner.query_terms().into_iter().map(|t| t.flatten()).collect::<BTreeSet<_>>();

        self.meta.add(Some(new_terms), id, &id_bytes)?;

        Ok(id)
    }

    pub fn create_multi(&self, ids: Vec<u64>, inners: &[T]) -> Result<Vec<u64>, ConflictableTransactionError<err::Error>> {

        for (id, inner) in ids.clone().into_iter().zip(inners) {

            let id_bytes = utils::u64_to_bytes(id);

            let serialized_inner = utils::serialize(inner)?;

            self.tree.insert(&id_bytes, serialized_inner)?;

            let new_terms =
                inner.query_terms().into_iter().map(|t| t.flatten()).collect::<BTreeSet<_>>();

            self.meta.add(Some(new_terms), id, &id_bytes)?;
        }

        Ok(ids)
    }

    pub fn update(&self, object: &Object<T>) -> Result<(), ConflictableTransactionError<err::Error>> {
        self.update_multi(std::slice::from_ref(object))
    }

    pub fn update_multi(&self, objects: &[Object<T>]) -> Result<(), ConflictableTransactionError<err::Error>> {

        for Object { id, inner } in objects {

            let id_bytes = utils::u64_to_bytes(*id);

            let serialized_inner = utils::serialize(inner)?;

            self.tree.insert(&id_bytes, serialized_inner)?;

            let new_terms =
                inner.query_terms().into_iter().map(|t| t.flatten()).collect::<BTreeSet<_>>();

            self.meta.add(Some(new_terms), *id, &id_bytes)?;
        }

        Ok(())
    }

    pub fn delete(&self, id: u64) -> Result<(), ConflictableTransactionError<err::Error>> {
        self.delete_multi(&[id])
    }

    pub fn delete_multi(&self, ids: &[u64]) -> Result<(), ConflictableTransactionError<err::Error>> {
        for id in ids {

            let id_bytes = utils::u64_to_bytes(*id);

            self.meta.add(None, *id, &id_bytes)?;

            self.tree.remove(&id_bytes)?;
        }

        Ok(())
    }

    pub fn find(&self, id: u64) -> Result<Option<Object<T>>, ConflictableTransactionError<err::Error>> {
        Ok(self
            .tree
            .get(utils::u64_to_bytes(id))?
            .map(|bytes| utils::deserialize(&bytes))
            .transpose()?
            .map(|inner| Object { id, inner }))
    }

    pub fn filter<Q: query::TransactionalQuery<sled::transaction::TransactionalTree>>(&self, query: Q) -> Result<query::TransactionalResults<T>, ConflictableTransactionError<err::Error>> {
        let matching_ids = query.matching_ids(&self.meta.0)?;
        Ok(query::TransactionalResults { matching_ids, store: self })
    }

}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Object<T> {
    pub id: u64,
    pub inner: T,
}

impl<T> std::ops::Deref for Object<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<T> std::ops::DerefMut for Object<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

impl<T: query::Queryable + serde::Serialize + serde::de::DeserializeOwned> Store<T> {

    pub fn transaction<A, F>(&self, f: F) -> sled::transaction::TransactionResult<A, err::Error>
        where F: Fn(&TransactionalStore<T>) -> sled::transaction::ConflictableTransactionResult<A, err::Error>,
    {
        [&self.tree, &self.meta].transaction(|trees| {

            let tree = &trees[0];
            let meta = &trees[1];

            let transactional_store = TransactionalStore { tree, meta: ext::TransactionalMeta(meta), marker: PhantomData };

            f(&transactional_store)
        })
    }

    pub fn new(&self, inner: T) -> err::Result<Object<T>> {
        let id = self.db.generate_id()?;
        Ok(Object { id, inner })
    }

    pub fn create(&self, inner: &T) -> err::Result<u64> {
        let id = self.db.generate_id()?;
        Ok(self.transaction(|store| store.create(id, inner))?)
    }

    pub fn create_multi(&self, inners: &[T]) -> err::Result<Vec<u64>> {
        let ids = (0..inners.len())
            .map(|_| self.db.generate_id() )
            .collect::<Result<Vec<_>, _>>()?;
        Ok(self.transaction(|store| store.create_multi(ids.clone(), inners))?)
    }

    pub fn update(&self, object: &Object<T>) -> err::Result<()> {
        self.update_multi(std::slice::from_ref(object))
    }

    pub fn update_multi(&self, objects: &[Object<T>]) -> err::Result<()> {
        Ok(self.transaction(|store| store.update_multi(objects))?)
    }

    pub fn delete(&self, id: u64) -> err::Result<()> {
        self.delete_multi(&[id])
    }

    pub fn delete_multi(&self, ids: &[u64]) -> err::Result<()> {
        Ok(self.transaction(|store| store.delete_multi(ids))?)
    }

    pub fn all(&self) -> err::Result<Vec<Object<T>>> {
        
        Ok(self
            .tree
            .iter()
            .flatten()
            .map(|(k, v)| {
                Ok(Object {
                    id: utils::bytes_to_u64(k.as_ref())?,
                    inner: utils::deserialize(&v)?,
                })
            })
            .collect::<err::Result<Vec<_>>>()?)
    }

    pub fn find(&self, id: u64) -> err::Result<Option<Object<T>>> {
        Ok(self
            .tree
            .get(utils::u64_to_bytes(id))?
            .map(|bytes| utils::deserialize(&bytes))
            .transpose()?
            .map(|inner| Object { id, inner }))
    }

    pub fn filter<Q: query::Query>(&self, query: Q) -> err::Result<query::Results<T>> {
        let matching_ids = query.matching_ids(&self.meta)?;
        Ok(query::Results { matching_ids, store: self })
    }

    pub fn delete_all(&self) -> err::Result<()> {

        let tree_keys = self.tree.iter().keys().collect::<Vec<_>>();
        let meta_keys = self.meta.iter().keys().collect::<Vec<_>>();

        [&self.tree, &self.meta].transaction(|trees| {
            let tree = &trees[0];
            let meta = &trees[1];

            for key in tree_keys.clone() {
                let key = key?;
                tree.remove(key)?;
            }

            for key in meta_keys.clone() {
                let key = key?;
                meta.remove(key)?;
            }


            Ok(())
        })?;

        Ok(())
    }

}