2024-04-21 09:49:02 +02:00
|
|
|
use bincode::{Decode, Encode};
|
2024-04-23 23:11:11 +02:00
|
|
|
/// Derive a [`Mixin`] implementation.
|
|
|
|
pub use r#macro::Mixin;
|
2024-04-21 09:49:02 +02:00
|
|
|
|
2024-04-23 23:11:11 +02:00
|
|
|
use super::{
|
|
|
|
types::{MixinSpec, Value},
|
|
|
|
Batch, Store, Transaction,
|
|
|
|
};
|
|
|
|
use crate::{Error, Key, Result};
|
2024-04-21 09:49:02 +02:00
|
|
|
|
2024-04-23 23:11:11 +02:00
|
|
|
/// Mixins are the simplest pieces of data in the store.
|
|
|
|
pub trait Mixin: Value<Type = MixinSpec> + Encode + Decode {}
|
2024-04-21 09:49:02 +02:00
|
|
|
|
2024-04-23 23:11:11 +02:00
|
|
|
impl Store {
|
|
|
|
/// Get the value!
|
|
|
|
pub fn get_mixin<M>(&self, node: Key) -> Result<Option<M>>
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
op::get_mixin(self, node)
|
|
|
|
}
|
|
|
|
/// Check if `node` has a mixin `M`.
|
|
|
|
pub fn has_mixin<M>(&self, node: Key) -> Result<bool>
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
op::has_mixin::<M>(self, node)
|
|
|
|
}
|
2024-04-21 09:49:02 +02:00
|
|
|
}
|
|
|
|
|
2024-04-23 23:11:11 +02:00
|
|
|
impl Transaction<'_> {
|
|
|
|
/// Apply an update function to the mixin `M` of `node`.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// - [`Error::Missing`]: if `node` does not have a mixin of this type.
|
|
|
|
///
|
|
|
|
/// [`Error::Missing`]: crate::Error::Missing
|
|
|
|
pub fn update<M>(&self, node: Key, update: impl FnOnce(M) -> M) -> Result<()>
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
op::update(self, node, update)
|
|
|
|
}
|
|
|
|
/// Get the mixin of the specified type associated with `node`.
|
|
|
|
pub fn get_mixin<M>(&self, node: Key) -> Result<Option<M>>
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
op::get_mixin(self, node)
|
|
|
|
}
|
|
|
|
/// Add a mixin to `node`.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// - [`Error::Conflict`]: if `node` already has a mixin of type `M`.
|
|
|
|
///
|
|
|
|
/// [`Error::Conflict`]: crate::Error::Missing
|
|
|
|
pub fn add_mixin<M>(&self, node: Key, mixin: M) -> Result<()>
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
if op::has_mixin::<M>(self, node)? {
|
|
|
|
return Err(Error::Conflict);
|
|
|
|
} else {
|
|
|
|
op::add_mixin::<M>(self, node, mixin)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// Check whether `node` has an `M` defined for it.
|
|
|
|
pub fn has_mixin<M>(&self, node: Key) -> Result<bool>
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
op::has_mixin::<M>(self, node)
|
|
|
|
}
|
2024-04-21 09:49:02 +02:00
|
|
|
}
|
|
|
|
|
2024-04-23 23:11:11 +02:00
|
|
|
impl Batch {
|
|
|
|
/// Add a mixin to the `node`.
|
|
|
|
///
|
|
|
|
/// **Note**: unlike [`Transaction::add_mixin`], this will *not* return an error if the key already has a mixin
|
|
|
|
/// of this type. This *should* not cause inconsistency.
|
|
|
|
pub fn put_mixin<M>(&mut self, node: Key, mixin: M)
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
op::add_mixin(self, node, mixin).unwrap()
|
|
|
|
}
|
2024-04-21 09:49:02 +02:00
|
|
|
}
|
|
|
|
|
2024-04-23 23:11:11 +02:00
|
|
|
mod op {
|
|
|
|
use super::Mixin;
|
|
|
|
use crate::{internal::*, Error, Key, Result};
|
|
|
|
|
|
|
|
pub fn update<M>(
|
|
|
|
cx: &(impl Query + Write),
|
|
|
|
node: Key,
|
|
|
|
update: impl FnOnce(M) -> M,
|
|
|
|
) -> Result<()>
|
|
|
|
where
|
|
|
|
M: Mixin,
|
|
|
|
{
|
|
|
|
// TODO: implement in terms of a merge operator instead of separate query and write ops.
|
|
|
|
// this would let us remove the `Query` bound, which would in turn let us update from within
|
|
|
|
// a batch.
|
|
|
|
//
|
|
|
|
// See https://github.com/facebook/rocksdb/wiki/Merge-Operator
|
|
|
|
//
|
|
|
|
// It looks like rocksdb allows you to specify a merge operator per column family.[^1]
|
|
|
|
// This means we can construct our column families with a merge operator that knows how to encode and decode mixins.
|
|
|
|
//
|
|
|
|
// [^1]: https://github.com/facebook/rocksdb/blob/9d37408f9af15c7a1ae42f9b94d06b27d98a011a/include/rocksdb/options.h#L128
|
|
|
|
let tree = cx.open(M::SPEC.keyspace);
|
|
|
|
match tree.get(node.as_ref())? {
|
|
|
|
None => Err(Error::Missing),
|
|
|
|
Some(buf) => {
|
|
|
|
let new = decode(buf).map(update).and_then(encode)?;
|
|
|
|
tree.set(node, new)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_mixin<M: Mixin>(cx: &impl Query, node: Key) -> Result<Option<M>> {
|
|
|
|
cx.open(M::SPEC.keyspace).get(node)?.map(decode).transpose()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_mixin<M: Mixin>(cx: &impl Write, node: Key, mixin: M) -> Result<()> {
|
|
|
|
cx.open(M::SPEC.keyspace).set(node, encode(mixin)?)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn has_mixin<M: Mixin>(cx: &impl Query, node: Key) -> Result<bool> {
|
|
|
|
cx.open(M::SPEC.keyspace).has(node)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn encode(data: impl bincode::Encode) -> Result<Vec<u8>> {
|
|
|
|
bincode::encode_to_vec(data, bincode::config::standard()).map_err(Error::Encoding)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decode<T>(data: impl AsRef<[u8]>) -> Result<T>
|
|
|
|
where
|
|
|
|
T: bincode::Decode,
|
|
|
|
{
|
|
|
|
bincode::decode_from_slice(data.as_ref(), bincode::config::standard())
|
|
|
|
.map_err(Error::Decoding)
|
|
|
|
.map(|(v, _)| v)
|
|
|
|
}
|
2024-04-21 09:49:02 +02:00
|
|
|
}
|