mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-06 04:47:42 +00:00
replace Option CRDT by explicit CancelingOption and MergingOption types
This commit is contained in:
@@ -26,28 +26,6 @@ pub trait Crdt {
|
||||
fn merge(&mut self, other: &Self);
|
||||
}
|
||||
|
||||
/// `Option<T>` implements Crdt for any type T, even if T doesn't implement CRDT itself: when
|
||||
/// different values are detected, they are always merged to None. This can be used for value
|
||||
/// types which shoulnd't be merged, instead of trying to merge things when we know we don't want
|
||||
/// to merge them (which is what the `AutoCrdt` trait is used for most of the time). This cases
|
||||
/// arises very often, for example with a Lww or a `LwwMap`: the value type has to be a CRDT so that
|
||||
/// we have a rule for what to do when timestamps aren't enough to disambiguate (in a distributed
|
||||
/// system, anything can happen!), and with `AutoCrdt` the rule is to make an arbitrary (but
|
||||
/// deterministic) choice between the two. When using an `Option<T>` instead with this impl, ambiguity
|
||||
/// cases are explicitly stored as None, which allows us to detect the ambiguity and handle it in
|
||||
/// the way we want. (this can only work if we are happy with losing the value when an ambiguity
|
||||
/// arises)
|
||||
impl<T> Crdt for Option<T>
|
||||
where
|
||||
T: Eq,
|
||||
{
|
||||
fn merge(&mut self, other: &Self) {
|
||||
if self != other {
|
||||
*self = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All types that implement `Ord` (a total order) can also implement a trivial CRDT
|
||||
/// defined by the merge rule: `a ⊔ b = max(a, b)`. Implement this trait for your type
|
||||
/// to enable this behavior.
|
||||
|
||||
@@ -16,6 +16,7 @@ mod deletable;
|
||||
mod lww;
|
||||
mod lww_map;
|
||||
mod map;
|
||||
mod option;
|
||||
|
||||
pub use self::bool::*;
|
||||
pub use crdt::*;
|
||||
@@ -23,3 +24,4 @@ pub use deletable::*;
|
||||
pub use lww::*;
|
||||
pub use lww_map::*;
|
||||
pub use map::*;
|
||||
pub use option::*;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::crdt::Crdt;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Default, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[serde(transparent)]
|
||||
pub struct CancelingOption<T>(pub Option<T>);
|
||||
|
||||
/// `CancelingOption<T>` implements Crdt for any type T, even if T doesn't implement CRDT itself: when
|
||||
/// different values are detected, they are always merged to None. This can be used for value
|
||||
/// types which shoulnd't be merged, instead of trying to merge things when we know we don't want
|
||||
/// to merge them (which is what the `AutoCrdt` trait is used for most of the time). This cases
|
||||
/// arises very often, for example with a Lww or a `LwwMap`: the value type has to be a CRDT so that
|
||||
/// we have a rule for what to do when timestamps aren't enough to disambiguate (in a distributed
|
||||
/// system, anything can happen!), and with `AutoCrdt` the rule is to make an arbitrary (but
|
||||
/// deterministic) choice between the two. When using an `CancelingOption<T>` instead with this impl, ambiguity
|
||||
/// cases are explicitly stored as None, which allows us to detect the ambiguity and handle it in
|
||||
/// the way we want. (this can only work if we are happy with losing the value when an ambiguity
|
||||
/// arises)
|
||||
impl<T> Crdt for CancelingOption<T>
|
||||
where
|
||||
T: Eq + Clone,
|
||||
{
|
||||
fn merge(&mut self, other: &Self) {
|
||||
match (self.0.as_ref(), other.0.as_ref()) {
|
||||
(Some(a), Some(b)) if a != b => {
|
||||
self.0 = None;
|
||||
}
|
||||
(None, Some(b)) => {
|
||||
self.0 = Some(b.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CancelingOption<T> {
|
||||
pub fn inner(&self) -> Option<&T> {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Option<T> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> CancelingOption<U> {
|
||||
CancelingOption(self.0.map(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for CancelingOption<T> {
|
||||
fn from(x: Option<T>) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy, Default)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[serde(transparent)]
|
||||
pub struct MergingOption<T>(pub Option<T>);
|
||||
|
||||
/// `MergingOption<T>` implements `Crdt` when `T` implements `Crdt`:
|
||||
/// None is a bottom value, and different Some values get merged according
|
||||
/// to their Crdt operator.
|
||||
impl<T> Crdt for MergingOption<T>
|
||||
where
|
||||
T: Crdt + Clone,
|
||||
{
|
||||
fn merge(&mut self, other: &Self) {
|
||||
if let (Some(a), Some(b)) = (self.0.as_mut(), other.0.as_ref()) {
|
||||
a.merge(b);
|
||||
} else {
|
||||
self.0 = self.0.take().or_else(|| other.0.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MergingOption<T> {
|
||||
pub fn inner(&self) -> Option<&T> {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Option<T> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> MergingOption<U> {
|
||||
MergingOption(self.0.map(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for MergingOption<T> {
|
||||
fn from(x: Option<T>) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user