use std::convert::TryInto;
use std::marker::PhantomData;
use std::path::PathBuf;
pub use pallet_macros::DocumentLike;
pub mod ext {
pub use sled;
pub use tantivy;
}
#[cfg(feature = "bincode")]
mod serialize {
pub fn serialize<T: ?Sized>(value: &T) -> crate::err::Result<Vec<u8>>
where
T: serde::Serialize,
{
bincode::serialize(value).map_err(crate::err::Error::Bincode)
}
pub fn deserialize<'a, T>(bytes: &'a [u8]) -> crate::err::Result<T>
where
T: serde::de::Deserialize<'a>,
{
bincode::deserialize(bytes).map_err(crate::err::Error::Bincode)
}
}
#[cfg(feature = "serde_cbor")]
mod serialize {
pub fn serialize<T: Sized>(value: &T) -> crate::err::Result<Vec<u8>>
where
T: serde::Serialize,
{
serde_cbor::to_vec(value).map_err(crate::err::Error::CBOR)
}
pub fn deserialize<'a, T>(bytes: &'a [u8]) -> crate::err::Result<T>
where
T: serde::Deserialize<'a>,
{
serde_cbor::from_slice(bytes).map_err(crate::err::Error::CBOR)
}
}
pub mod err {
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Search error: `{0}`")]
Tantivy(tantivy::TantivyError),
#[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<tantivy::TantivyError> for Error {
fn from(t: tantivy::TantivyError) -> Self {
Error::Tantivy(t)
}
}
impl From<tantivy::query::QueryParserError> for Error {
fn from(t: tantivy::query::QueryParserError) -> Self {
Error::Tantivy(t.into())
}
}
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>;
}
pub mod db;
pub mod search;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Document<T> {
pub id: u64,
pub inner: T,
}
impl<T> std::ops::Deref for Document<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> std::ops::DerefMut for Document<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub struct Store<T: DocumentLike> {
tree: db::Tree,
marker: PhantomData<fn(T)>,
pub index: search::Index<T::IndexFieldsType>,
}
impl<T: DocumentLike> Store<T> {
pub fn builder() -> StoreBuilder<T> {
StoreBuilder::default()
}
pub fn create(&self, inner: &T) -> err::Result<u64> {
let id = self.tree.transaction(
|tree| -> sled::transaction::ConflictableTransactionResult<u64, err::Error> {
self.index.with_writer(|index_writer| {
let id = tree.generate_id()?;
let serialized_inner = crate::serialize::serialize(inner)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
let mut search_doc = inner
.as_index_document(&self.index.fields)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
search_doc.add_u64(self.index.id_field, id);
index_writer.add_document(search_doc);
tree.insert(&id.to_le_bytes(), serialized_inner)?;
index_writer
.commit()
.map_err(err::Error::Tantivy)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
Ok(id)
})
},
)?;
Ok(id)
}
pub fn create_multi(&self, inners: &[T]) -> err::Result<Vec<u64>> {
let ids = self.tree.transaction(
|tree| -> sled::transaction::ConflictableTransactionResult<_, err::Error> {
self.index.with_writer(|index_writer| {
let mut out = Vec::with_capacity(inners.len());
for inner in inners {
let id = tree.generate_id()?;
let serialized_inner = crate::serialize::serialize(inner)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
let mut search_doc = inner
.as_index_document(&self.index.fields)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
search_doc.add_u64(self.index.id_field, id);
index_writer.add_document(search_doc);
tree.insert(&id.to_le_bytes(), serialized_inner)?;
out.push(id);
}
index_writer
.commit()
.map_err(err::Error::Tantivy)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
Ok(out)
})
},
)?;
Ok(ids)
}
pub fn update(&self, doc: &Document<T>) -> err::Result<()> {
self.update_multi(std::slice::from_ref(doc))
}
pub fn update_multi(&self, docs: &[Document<T>]) -> err::Result<()> {
self.tree.transaction(
|tree| -> sled::transaction::ConflictableTransactionResult<_, err::Error> {
self.index.with_writer(|index_writer| {
for Document { id, inner } in docs {
let serialized_inner = crate::serialize::serialize(inner)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
let mut search_doc = inner
.as_index_document(&self.index.fields)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
search_doc.add_u64(self.index.id_field, *id);
index_writer
.delete_term(tantivy::Term::from_field_u64(self.index.id_field, *id));
index_writer.add_document(search_doc);
tree.insert(&id.to_le_bytes(), serialized_inner)?;
}
index_writer
.commit()
.map_err(err::Error::Tantivy)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
Ok(())
})
},
)?;
Ok(())
}
pub fn delete(&self, id: u64) -> err::Result<()> {
self.delete_multi(&[id])
}
pub fn delete_multi(&self, ids: &[u64]) -> err::Result<()> {
self.tree.transaction(
|tree| -> sled::transaction::ConflictableTransactionResult<_, err::Error> {
self.index.with_writer(|index_writer| {
for id in ids {
index_writer
.delete_term(tantivy::Term::from_field_u64(self.index.id_field, *id));
tree.remove(&id.to_le_bytes())?;
}
index_writer
.commit()
.map_err(err::Error::Tantivy)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
Ok(())
})
},
)?;
Ok(())
}
pub fn search<I: search::Searcher<T>>(&self, searcher: I) -> Result<I::Item, I::Error> {
searcher.search(self)
}
pub fn all(&self) -> err::Result<Vec<Document<T>>> {
Ok(self
.tree
.iter()
.flatten()
.map(|(k, v)| {
Ok(Document {
id: u64::from_le_bytes(k.as_ref().try_into().map_err(err::custom)?),
inner: crate::serialize::deserialize(&v)?,
})
})
.collect::<err::Result<Vec<_>>>()?)
}
pub fn index_all(&self) -> err::Result<()> {
self.index.with_writer(|index_writer| {
let docs = self.all()?;
for Document { id, inner } in docs {
let mut search_doc = inner.as_index_document(&self.index.fields)?;
search_doc.add_u64(self.index.id_field, id);
index_writer.delete_term(tantivy::Term::from_field_u64(self.index.id_field, id));
index_writer.add_document(search_doc);
}
index_writer.commit()?;
Ok(())
})
}
pub fn find(&self, id: u64) -> err::Result<Option<Document<T>>> {
Ok(self
.tree
.get(id.to_le_bytes())?
.map(|bytes| crate::serialize::deserialize(&bytes))
.transpose()?
.map(|inner| Document { id, inner }))
}
pub fn delete_all(&self) -> err::Result<()> {
let keys = self
.tree
.iter()
.keys()
.map(|x| x.map_err(err::Error::from))
.collect::<err::Result<Vec<_>>>()?;
self.tree.transaction(
|tree| -> sled::transaction::ConflictableTransactionResult<_, err::Error> {
for key in &keys {
tree.remove(key)?;
}
self.index.with_writer(|index_writer| {
index_writer
.delete_all_documents()
.map_err(err::Error::Tantivy)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
index_writer
.commit()
.map_err(err::Error::Tantivy)
.map_err(sled::transaction::ConflictableTransactionError::Abort)?;
Ok(())
})
},
)?;
Ok(())
}
}
pub struct StoreBuilder<T: DocumentLike> {
tree_builder: db::TreeBuilder,
index_builder: search::IndexBuilder<T::IndexFieldsType>,
marker: PhantomData<fn(T)>,
}
impl<T: DocumentLike> Default for StoreBuilder<T> {
fn default() -> Self {
StoreBuilder {
tree_builder: db::TreeBuilder::default(),
index_builder: search::IndexBuilder::default(),
marker: PhantomData,
}
}
}
impl<T: DocumentLike> StoreBuilder<T> {
pub fn with_db(mut self, db: sled::Db) -> Self {
self.tree_builder = self.tree_builder.with_db(db);
self
}
pub fn with_index_dir<I: Into<PathBuf>>(mut self, index_dir: I) -> Self {
self.index_builder = self.index_builder.with_index_dir(index_dir);
self
}
pub fn with_tree_builder(mut self, tree_builder: db::TreeBuilder) -> Self {
self.tree_builder = tree_builder;
self
}
pub fn with_index_builder(
mut self,
index_builder: search::IndexBuilder<T::IndexFieldsType>,
) -> Self {
self.index_builder = index_builder;
self
}
pub fn finish(self) -> err::Result<Store<T>> {
let tree = self.tree_builder.merge(T::tree_builder()).finish()?;
let index = self.index_builder.merge(T::index_builder()).finish()?;
Ok(Store { tree, index, marker: PhantomData })
}
}
pub trait DocumentLike: serde::Serialize + serde::de::DeserializeOwned {
type IndexFieldsType;
fn as_index_document(
&self,
index_fields: &Self::IndexFieldsType,
) -> err::Result<tantivy::Document>;
fn tree_builder() -> db::TreeBuilder {
db::TreeBuilder::default()
}
fn index_builder() -> search::IndexBuilder<Self::IndexFieldsType> {
search::IndexBuilder::default()
}
}