use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TimestampedValue {
pub value: Vec<u8>,
pub timestamp: u64,
}
impl TimestampedValue {
pub fn new(value: Vec<u8>, timestamp: u64) -> Self {
Self { value, timestamp }
}
pub fn now(value: Vec<u8>) -> Self {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64;
Self { value, timestamp }
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.value.clone();
bytes.extend(&self.timestamp.to_be_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 8 {
return None;
}
let (value, ts_bytes) = bytes.split_at(bytes.len() - 8);
let timestamp = u64::from_be_bytes(ts_bytes.try_into().ok()?);
Some(Self {
value: value.to_vec(),
timestamp,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MultiValueSet {
pub values: Vec<Vec<u8>>,
}
impl MultiValueSet {
pub fn single(value: Vec<u8>) -> Self {
Self {
values: vec![value],
}
}
pub fn from_values(mut values: Vec<Vec<u8>>) -> Self {
values.sort();
values.dedup();
Self { values }
}
pub fn merge(&self, other: &Self) -> Self {
let mut values = Vec::with_capacity(self.values.len().saturating_add(other.values.len()));
values.extend(self.values.iter().cloned());
values.extend(other.values.iter().cloned());
Self::from_values(values)
}
pub fn to_bytes(&self) -> Vec<u8> {
let canonical = Self::from_values(self.values.clone());
let mut bytes = Vec::new();
bytes.extend(&(canonical.values.len() as u32).to_be_bytes());
for v in &canonical.values {
bytes.extend(&(v.len() as u32).to_be_bytes());
bytes.extend(v);
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 4 {
return None;
}
let count = u32::from_be_bytes(bytes[..4].try_into().ok()?) as usize;
if count > (bytes.len() - 4) / 4 {
return None;
}
let mut values = Vec::with_capacity(count);
let mut offset = 4;
for _ in 0..count {
if offset + 4 > bytes.len() {
return None;
}
let len = u32::from_be_bytes(bytes[offset..offset + 4].try_into().ok()?) as usize;
offset += 4;
let end = offset.checked_add(len)?;
if end > bytes.len() {
return None;
}
values.push(bytes[offset..end].to_vec());
offset = end;
}
if offset != bytes.len() {
return None;
}
Some(Self::from_values(values))
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn len(&self) -> usize {
self.values.len()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CrdtResolution {
Value(Vec<u8>),
Delete,
}
impl CrdtResolution {
pub fn value(value: impl Into<Vec<u8>>) -> Self {
Self::Value(value.into())
}
pub fn delete() -> Self {
Self::Delete
}
}
pub type CustomMergeFn = Arc<dyn Fn(&Conflict) -> CrdtResolution + Send + Sync>;
#[derive(Clone, Default)]
pub enum MergeStrategy {
#[default]
LastWriterWins,
MultiValue,
Custom(CustomMergeFn),
}
impl std::fmt::Debug for MergeStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MergeStrategy::LastWriterWins => write!(f, "LastWriterWins"),
MergeStrategy::MultiValue => write!(f, "MultiValue"),
MergeStrategy::Custom(_) => write!(f, "Custom(<fn>)"),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum DeletePolicy {
DeleteWins,
#[default]
UpdateWins,
}
pub type TimestampExtractor = Arc<dyn Fn(&[u8]) -> u64 + Send + Sync>;
#[derive(Clone)]
pub struct CrdtConfig {
pub strategy: MergeStrategy,
pub delete_policy: DeletePolicy,
pub timestamp_extractor: Option<TimestampExtractor>,
}
impl std::fmt::Debug for CrdtConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CrdtConfig")
.field("strategy", &self.strategy)
.field("delete_policy", &self.delete_policy)
.field(
"timestamp_extractor",
&self.timestamp_extractor.as_ref().map(|_| "<fn>"),
)
.finish()
}
}
impl Default for CrdtConfig {
fn default() -> Self {
Self {
strategy: MergeStrategy::LastWriterWins,
delete_policy: DeletePolicy::UpdateWins,
timestamp_extractor: None,
}
}
}
impl CrdtConfig {
pub fn lww() -> Self {
Self {
strategy: MergeStrategy::LastWriterWins,
..Default::default()
}
}
pub fn multi_value() -> Self {
Self {
strategy: MergeStrategy::MultiValue,
..Default::default()
}
}
pub fn custom<F>(merge_fn: F) -> Self
where
F: Fn(&Conflict) -> CrdtResolution + Send + Sync + 'static,
{
Self {
strategy: MergeStrategy::Custom(Arc::new(merge_fn)),
..Default::default()
}
}
pub fn with_delete_policy(mut self, policy: DeletePolicy) -> Self {
self.delete_policy = policy;
self
}
pub fn with_timestamp_extractor<F>(mut self, extractor: F) -> Self
where
F: Fn(&[u8]) -> u64 + Send + Sync + 'static,
{
self.timestamp_extractor = Some(Arc::new(extractor));
self
}
}
use super::error::{Conflict, Error, Resolution, Resolver};
use super::store::Store;
use super::tree::Tree;
use super::Prolly;
pub trait ConflictFreeMerger<S: Store> {
fn crdt_merge(
&self,
store: &S,
base: &Tree,
left: &Tree,
right: &Tree,
config: &CrdtConfig,
) -> Result<Tree, Error>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct DefaultConflictFreeMerger;
impl DefaultConflictFreeMerger {
pub fn new() -> Self {
Self
}
fn extract_timestamp(value: &[u8], config: &CrdtConfig) -> u64 {
if let Some(ref extractor) = config.timestamp_extractor {
extractor(value)
} else {
TimestampedValue::from_bytes(value)
.map(|tv| tv.timestamp)
.unwrap_or(0)
}
}
fn resolve_lww(left: &[u8], right: &[u8], config: &CrdtConfig) -> Vec<u8> {
let left_ts = Self::extract_timestamp(left, config);
let right_ts = Self::extract_timestamp(right, config);
if left_ts > right_ts {
left.to_vec()
} else if right_ts > left_ts {
right.to_vec()
} else {
if left >= right {
left.to_vec()
} else {
right.to_vec()
}
}
}
fn resolve_mv(left: &[u8], right: &[u8]) -> Vec<u8> {
let left_set =
MultiValueSet::from_bytes(left).unwrap_or_else(|| MultiValueSet::single(left.to_vec()));
let right_set = MultiValueSet::from_bytes(right)
.unwrap_or_else(|| MultiValueSet::single(right.to_vec()));
left_set.merge(&right_set).to_bytes()
}
fn resolve_custom(conflict: &Conflict, merge_fn: &CustomMergeFn) -> Option<Vec<u8>> {
match merge_fn(conflict) {
CrdtResolution::Value(value) => Some(value),
CrdtResolution::Delete => None,
}
}
fn resolve_delete_conflict(existing_value: &[u8], policy: &DeletePolicy) -> Option<Vec<u8>> {
match policy {
DeletePolicy::DeleteWins => None,
DeletePolicy::UpdateWins => Some(existing_value.to_vec()),
}
}
fn resolve_conflict(
key: &[u8],
base: &Option<Vec<u8>>,
left: &Option<Vec<u8>>,
right: &Option<Vec<u8>>,
config: &CrdtConfig,
) -> Option<Vec<u8>> {
if let MergeStrategy::Custom(f) = &config.strategy {
let conflict = Conflict {
key: key.to_vec(),
base: base.clone(),
left: left.clone(),
right: right.clone(),
};
return Self::resolve_custom(&conflict, f);
}
match (left, right) {
(Some(l), Some(r)) => {
let resolved = match &config.strategy {
MergeStrategy::LastWriterWins => Self::resolve_lww(l, r, config),
MergeStrategy::MultiValue => Self::resolve_mv(l, r),
MergeStrategy::Custom(_) => unreachable!("custom strategy returned above"),
};
Some(resolved)
}
(Some(l), None) => Self::resolve_delete_conflict(l, &config.delete_policy),
(None, Some(r)) => Self::resolve_delete_conflict(r, &config.delete_policy),
(None, None) => None,
}
}
}
pub(crate) fn resolve_conflict(config: &CrdtConfig, conflict: &Conflict) -> Option<Vec<u8>> {
DefaultConflictFreeMerger::resolve_conflict(
&conflict.key,
&conflict.base,
&conflict.left,
&conflict.right,
config,
)
}
pub(crate) fn resolver(config: &CrdtConfig) -> Resolver {
let config = config.clone();
Box::new(move |conflict| {
resolve_conflict(&config, conflict)
.map(Resolution::value)
.unwrap_or_else(Resolution::delete)
})
}
impl<S: Store> ConflictFreeMerger<S> for DefaultConflictFreeMerger {
fn crdt_merge(
&self,
store: &S,
base: &Tree,
left: &Tree,
right: &Tree,
config: &CrdtConfig,
) -> Result<Tree, Error> {
let prolly = Prolly::new(store, base.config.clone());
prolly.merge(base, left, right, Some(resolver(config)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timestamped_value_new() {
let tv = TimestampedValue::new(b"hello".to_vec(), 12345);
assert_eq!(tv.value, b"hello");
assert_eq!(tv.timestamp, 12345);
}
#[test]
fn test_timestamped_value_now() {
let tv = TimestampedValue::now(b"hello".to_vec());
assert_eq!(tv.value, b"hello");
assert!(tv.timestamp > 0);
}
#[test]
fn test_timestamped_value_roundtrip() {
let original = TimestampedValue::new(b"test value".to_vec(), 1234567890);
let bytes = original.to_bytes();
let restored = TimestampedValue::from_bytes(&bytes).unwrap();
assert_eq!(original, restored);
}
#[test]
fn test_timestamped_value_empty_value() {
let tv = TimestampedValue::new(vec![], 100);
let bytes = tv.to_bytes();
let restored = TimestampedValue::from_bytes(&bytes).unwrap();
assert_eq!(restored.value, Vec::<u8>::new());
assert_eq!(restored.timestamp, 100);
}
#[test]
fn test_timestamped_value_from_bytes_too_short() {
let bytes = vec![1, 2, 3]; assert!(TimestampedValue::from_bytes(&bytes).is_none());
}
#[test]
fn test_multi_value_set_single() {
let mv = MultiValueSet::single(b"value".to_vec());
assert_eq!(mv.values.len(), 1);
assert_eq!(mv.values[0], b"value");
}
#[test]
fn test_multi_value_set_from_values() {
let mv = MultiValueSet::from_values(vec![
b"c".to_vec(),
b"a".to_vec(),
b"b".to_vec(),
b"a".to_vec(), ]);
assert_eq!(mv.values, vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]);
}
#[test]
fn test_multi_value_set_merge() {
let mv1 = MultiValueSet::single(b"a".to_vec());
let mv2 = MultiValueSet::single(b"b".to_vec());
let merged = mv1.merge(&mv2);
assert_eq!(merged.values, vec![b"a".to_vec(), b"b".to_vec()]);
}
#[test]
fn test_multi_value_set_merge_with_overlap() {
let mv1 = MultiValueSet::from_values(vec![b"a".to_vec(), b"b".to_vec()]);
let mv2 = MultiValueSet::from_values(vec![b"b".to_vec(), b"c".to_vec()]);
let merged = mv1.merge(&mv2);
assert_eq!(
merged.values,
vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]
);
}
#[test]
fn test_multi_value_set_roundtrip() {
let original = MultiValueSet::from_values(vec![
b"value1".to_vec(),
b"value2".to_vec(),
b"value3".to_vec(),
]);
let bytes = original.to_bytes();
let restored = MultiValueSet::from_bytes(&bytes).unwrap();
assert_eq!(original, restored);
}
#[test]
fn test_multi_value_set_empty_roundtrip() {
let original = MultiValueSet { values: vec![] };
let bytes = original.to_bytes();
let restored = MultiValueSet::from_bytes(&bytes).unwrap();
assert_eq!(original, restored);
}
#[test]
fn test_multi_value_set_from_bytes_too_short() {
let bytes = vec![1, 2]; assert!(MultiValueSet::from_bytes(&bytes).is_none());
}
#[test]
fn test_multi_value_set_from_bytes_truncated() {
let bytes = vec![0, 0, 0, 1, 0, 0, 0, 5]; assert!(MultiValueSet::from_bytes(&bytes).is_none());
}
#[test]
fn test_multi_value_set_rejects_trailing_bytes() {
let mut bytes = MultiValueSet::single(b"value".to_vec()).to_bytes();
bytes.extend_from_slice(b"trailing");
assert!(MultiValueSet::from_bytes(&bytes).is_none());
}
#[test]
fn test_multi_value_set_rejects_impossible_count_before_allocating() {
let bytes = u32::MAX.to_be_bytes();
assert!(MultiValueSet::from_bytes(&bytes).is_none());
}
#[test]
fn test_multi_value_set_serialization_is_canonical() {
let non_canonical = MultiValueSet {
values: vec![b"b".to_vec(), b"a".to_vec(), b"b".to_vec()],
};
let decoded = MultiValueSet::from_bytes(&non_canonical.to_bytes()).unwrap();
assert_eq!(decoded.values, vec![b"a".to_vec(), b"b".to_vec()]);
}
#[test]
fn test_crdt_config_default() {
let config = CrdtConfig::default();
assert!(matches!(config.strategy, MergeStrategy::LastWriterWins));
assert_eq!(config.delete_policy, DeletePolicy::UpdateWins);
assert!(config.timestamp_extractor.is_none());
}
#[test]
fn test_crdt_config_lww() {
let config = CrdtConfig::lww();
assert!(matches!(config.strategy, MergeStrategy::LastWriterWins));
}
#[test]
fn test_crdt_config_multi_value() {
let config = CrdtConfig::multi_value();
assert!(matches!(config.strategy, MergeStrategy::MultiValue));
}
#[test]
fn test_crdt_config_custom() {
let config = CrdtConfig::custom(|conflict| {
CrdtResolution::value(conflict.left.clone().expect("left value"))
});
assert!(matches!(config.strategy, MergeStrategy::Custom(_)));
}
#[test]
fn test_crdt_config_with_delete_policy() {
let config = CrdtConfig::default().with_delete_policy(DeletePolicy::DeleteWins);
assert_eq!(config.delete_policy, DeletePolicy::DeleteWins);
}
#[test]
fn test_crdt_config_with_timestamp_extractor() {
let config = CrdtConfig::default().with_timestamp_extractor(|bytes| {
if bytes.len() >= 8 {
u64::from_be_bytes(bytes[bytes.len() - 8..].try_into().unwrap())
} else {
0
}
});
assert!(config.timestamp_extractor.is_some());
}
#[test]
fn test_delete_policy_default() {
let policy = DeletePolicy::default();
assert_eq!(policy, DeletePolicy::UpdateWins);
}
#[test]
fn test_merge_strategy_default() {
let strategy = MergeStrategy::default();
assert!(matches!(strategy, MergeStrategy::LastWriterWins));
}
#[test]
fn test_merge_strategy_debug() {
assert_eq!(
format!("{:?}", MergeStrategy::LastWriterWins),
"LastWriterWins"
);
assert_eq!(format!("{:?}", MergeStrategy::MultiValue), "MultiValue");
let custom = MergeStrategy::Custom(Arc::new(|conflict: &Conflict| {
CrdtResolution::value(conflict.left.clone().expect("left value"))
}));
assert_eq!(format!("{:?}", custom), "Custom(<fn>)");
}
#[test]
fn test_resolve_lww_higher_timestamp_wins() {
let config = CrdtConfig::default();
let old_value = TimestampedValue::new(b"old".to_vec(), 100);
let new_value = TimestampedValue::new(b"new".to_vec(), 200);
let result = DefaultConflictFreeMerger::resolve_lww(
&old_value.to_bytes(),
&new_value.to_bytes(),
&config,
);
let restored = TimestampedValue::from_bytes(&result).unwrap();
assert_eq!(restored.value, b"new");
assert_eq!(restored.timestamp, 200);
}
#[test]
fn test_resolve_lww_equal_timestamps_lexicographic_tiebreaker() {
let config = CrdtConfig::default();
let value_a = TimestampedValue::new(b"aaa".to_vec(), 100);
let value_b = TimestampedValue::new(b"bbb".to_vec(), 100);
let result = DefaultConflictFreeMerger::resolve_lww(
&value_a.to_bytes(),
&value_b.to_bytes(),
&config,
);
let restored = TimestampedValue::from_bytes(&result).unwrap();
assert_eq!(restored.value, b"bbb");
}
#[test]
fn test_resolve_lww_with_custom_extractor() {
let config = CrdtConfig::default().with_timestamp_extractor(|bytes| {
if bytes.len() >= 8 {
u64::from_be_bytes(bytes[..8].try_into().unwrap())
} else {
0
}
});
let mut old_value = 100u64.to_be_bytes().to_vec();
old_value.extend(b"old");
let mut new_value = 200u64.to_be_bytes().to_vec();
new_value.extend(b"new");
let result = DefaultConflictFreeMerger::resolve_lww(&old_value, &new_value, &config);
assert_eq!(result, new_value);
}
#[test]
fn test_resolve_mv_combines_values() {
let result = DefaultConflictFreeMerger::resolve_mv(b"left", b"right");
let mv = MultiValueSet::from_bytes(&result).unwrap();
assert_eq!(mv.values.len(), 2);
assert!(mv.values.contains(&b"left".to_vec()));
assert!(mv.values.contains(&b"right".to_vec()));
}
#[test]
fn test_resolve_mv_merges_existing_sets() {
let left_set = MultiValueSet::from_values(vec![b"a".to_vec(), b"b".to_vec()]);
let right_set = MultiValueSet::from_values(vec![b"c".to_vec(), b"d".to_vec()]);
let result =
DefaultConflictFreeMerger::resolve_mv(&left_set.to_bytes(), &right_set.to_bytes());
let mv = MultiValueSet::from_bytes(&result).unwrap();
assert_eq!(mv.values.len(), 4);
assert!(mv.values.contains(&b"a".to_vec()));
assert!(mv.values.contains(&b"b".to_vec()));
assert!(mv.values.contains(&b"c".to_vec()));
assert!(mv.values.contains(&b"d".to_vec()));
}
#[test]
fn test_resolve_custom_function() {
let merge_fn: CustomMergeFn = Arc::new(|conflict| {
let mut result = conflict.left.clone().expect("left value");
result.extend(conflict.right.as_ref().expect("right value"));
CrdtResolution::value(result)
});
let conflict = Conflict {
key: b"key".to_vec(),
base: Some(b"base".to_vec()),
left: Some(b"left".to_vec()),
right: Some(b"right".to_vec()),
};
let result = DefaultConflictFreeMerger::resolve_custom(&conflict, &merge_fn);
assert_eq!(result, Some(b"leftright".to_vec()));
}
#[test]
fn test_resolve_custom_function_can_delete() {
let merge_fn: CustomMergeFn = Arc::new(|_| CrdtResolution::delete());
let conflict = Conflict {
key: b"key".to_vec(),
base: Some(b"base".to_vec()),
left: None,
right: Some(b"right".to_vec()),
};
let result = DefaultConflictFreeMerger::resolve_custom(&conflict, &merge_fn);
assert!(result.is_none());
}
#[test]
fn test_resolve_delete_conflict_delete_wins() {
let result =
DefaultConflictFreeMerger::resolve_delete_conflict(b"value", &DeletePolicy::DeleteWins);
assert!(result.is_none());
}
#[test]
fn test_resolve_delete_conflict_update_wins() {
let result =
DefaultConflictFreeMerger::resolve_delete_conflict(b"value", &DeletePolicy::UpdateWins);
assert_eq!(result, Some(b"value".to_vec()));
}
#[test]
fn test_resolve_conflict_both_values_lww() {
let config = CrdtConfig::lww();
let old_value = TimestampedValue::new(b"old".to_vec(), 100);
let new_value = TimestampedValue::new(b"new".to_vec(), 200);
let result = DefaultConflictFreeMerger::resolve_conflict(
b"key",
&Some(b"base".to_vec()),
&Some(old_value.to_bytes()),
&Some(new_value.to_bytes()),
&config,
);
assert!(result.is_some());
let restored = TimestampedValue::from_bytes(&result.unwrap()).unwrap();
assert_eq!(restored.value, b"new");
}
#[test]
fn test_resolve_conflict_left_deleted_update_wins() {
let config = CrdtConfig::default().with_delete_policy(DeletePolicy::UpdateWins);
let result = DefaultConflictFreeMerger::resolve_conflict(
b"key",
&Some(b"base".to_vec()),
&None,
&Some(b"right_value".to_vec()),
&config,
);
assert_eq!(result, Some(b"right_value".to_vec()));
}
#[test]
fn test_resolve_conflict_left_deleted_delete_wins() {
let config = CrdtConfig::default().with_delete_policy(DeletePolicy::DeleteWins);
let result = DefaultConflictFreeMerger::resolve_conflict(
b"key",
&Some(b"base".to_vec()),
&None,
&Some(b"right_value".to_vec()),
&config,
);
assert!(result.is_none());
}
#[test]
fn test_resolve_conflict_both_deleted() {
let config = CrdtConfig::default();
let result =
DefaultConflictFreeMerger::resolve_conflict(b"key", &None, &None, &None, &config);
assert!(result.is_none());
}
#[test]
fn test_crdt_merge_no_conflicts() {
use crate::prolly::config::Config;
use crate::prolly::store::MemStore;
use crate::prolly::Prolly;
use std::sync::Arc;
let store = Arc::new(MemStore::new());
let config = Config::default();
let prolly = Prolly::new(store.clone(), config);
let base = prolly.create();
let base = prolly.put(&base, b"a".to_vec(), b"1".to_vec()).unwrap();
let left = prolly.put(&base, b"b".to_vec(), b"2".to_vec()).unwrap();
let right = prolly.put(&base, b"c".to_vec(), b"3".to_vec()).unwrap();
let merger = DefaultConflictFreeMerger::new();
let crdt_config = CrdtConfig::default();
let merged = merger
.crdt_merge(store.as_ref(), &base, &left, &right, &crdt_config)
.unwrap();
assert_eq!(prolly.get(&merged, b"a").unwrap(), Some(b"1".to_vec()));
assert_eq!(prolly.get(&merged, b"b").unwrap(), Some(b"2".to_vec()));
assert_eq!(prolly.get(&merged, b"c").unwrap(), Some(b"3".to_vec()));
}
#[test]
fn test_crdt_merge_reuses_production_root_fast_path() {
use crate::prolly::config::Config;
use crate::prolly::store::MemStore;
use crate::prolly::Prolly;
let store = MemStore::new();
let prolly = Prolly::new(&store, Config::default());
let base = prolly
.put(&prolly.create(), b"key".to_vec(), b"base".to_vec())
.unwrap();
let right = prolly
.put(&base, b"key".to_vec(), b"right".to_vec())
.unwrap();
let merged = DefaultConflictFreeMerger::new()
.crdt_merge(&store, &base, &base, &right, &CrdtConfig::lww())
.unwrap();
assert_eq!(merged.root, right.root);
}
#[test]
fn test_crdt_merge_with_lww_conflict() {
use crate::prolly::config::Config;
use crate::prolly::store::MemStore;
use crate::prolly::Prolly;
use std::sync::Arc;
let store = Arc::new(MemStore::new());
let config = Config::default();
let prolly = Prolly::new(store.clone(), config);
let base = prolly.create();
let base_value = TimestampedValue::new(b"base".to_vec(), 100);
let base = prolly
.put(&base, b"key".to_vec(), base_value.to_bytes())
.unwrap();
let left_value = TimestampedValue::new(b"left".to_vec(), 200);
let left = prolly
.put(&base, b"key".to_vec(), left_value.to_bytes())
.unwrap();
let right_value = TimestampedValue::new(b"right".to_vec(), 300);
let right = prolly
.put(&base, b"key".to_vec(), right_value.to_bytes())
.unwrap();
let merger = DefaultConflictFreeMerger::new();
let crdt_config = CrdtConfig::lww();
let merged = merger
.crdt_merge(store.as_ref(), &base, &left, &right, &crdt_config)
.unwrap();
let result = prolly.get(&merged, b"key").unwrap().unwrap();
let restored = TimestampedValue::from_bytes(&result).unwrap();
assert_eq!(restored.value, b"right");
assert_eq!(restored.timestamp, 300);
}
#[test]
fn test_crdt_merge_with_mv_conflict() {
use crate::prolly::config::Config;
use crate::prolly::store::MemStore;
use crate::prolly::Prolly;
use std::sync::Arc;
let store = Arc::new(MemStore::new());
let config = Config::default();
let prolly = Prolly::new(store.clone(), config);
let base = prolly.create();
let base = prolly
.put(&base, b"key".to_vec(), b"base".to_vec())
.unwrap();
let left = prolly
.put(&base, b"key".to_vec(), b"left".to_vec())
.unwrap();
let right = prolly
.put(&base, b"key".to_vec(), b"right".to_vec())
.unwrap();
let merger = DefaultConflictFreeMerger::new();
let crdt_config = CrdtConfig::multi_value();
let merged = merger
.crdt_merge(store.as_ref(), &base, &left, &right, &crdt_config)
.unwrap();
let result = prolly.get(&merged, b"key").unwrap().unwrap();
let mv = MultiValueSet::from_bytes(&result).unwrap();
assert_eq!(mv.values.len(), 2);
assert!(mv.values.contains(&b"left".to_vec()));
assert!(mv.values.contains(&b"right".to_vec()));
}
#[test]
fn test_crdt_merge_with_custom_value_resolution() {
use crate::prolly::config::Config;
use crate::prolly::store::MemStore;
use crate::prolly::Prolly;
use std::sync::Arc;
let store = Arc::new(MemStore::new());
let config = Config::default();
let prolly = Prolly::new(store.clone(), config);
let base = prolly.create();
let base = prolly
.put(&base, b"key".to_vec(), b"base".to_vec())
.unwrap();
let left = prolly
.put(&base, b"key".to_vec(), b"left".to_vec())
.unwrap();
let right = prolly
.put(&base, b"key".to_vec(), b"right".to_vec())
.unwrap();
let crdt_config = CrdtConfig::custom(|conflict| {
assert_eq!(conflict.base.as_deref(), Some(b"base".as_slice()));
let mut value = conflict.left.clone().expect("left value");
value.extend_from_slice(b"+");
value.extend_from_slice(conflict.right.as_ref().expect("right value"));
CrdtResolution::value(value)
});
let merger = DefaultConflictFreeMerger::new();
let merged = merger
.crdt_merge(store.as_ref(), &base, &left, &right, &crdt_config)
.unwrap();
assert_eq!(
prolly.get(&merged, b"key").unwrap(),
Some(b"left+right".to_vec())
);
}
#[test]
fn test_crdt_merge_with_custom_delete_resolution() {
use crate::prolly::config::Config;
use crate::prolly::store::MemStore;
use crate::prolly::Prolly;
use std::sync::Arc;
let store = Arc::new(MemStore::new());
let config = Config::default();
let prolly = Prolly::new(store.clone(), config);
let base = prolly.create();
let base = prolly
.put(&base, b"key".to_vec(), b"base".to_vec())
.unwrap();
let left = prolly.delete(&base, b"key").unwrap();
let right = prolly
.put(&base, b"key".to_vec(), b"right".to_vec())
.unwrap();
let crdt_config = CrdtConfig::custom(|conflict| {
assert!(conflict.left.is_none());
assert_eq!(conflict.right.as_deref(), Some(b"right".as_slice()));
CrdtResolution::delete()
});
let merger = DefaultConflictFreeMerger::new();
let merged = merger
.crdt_merge(store.as_ref(), &base, &left, &right, &crdt_config)
.unwrap();
assert_eq!(prolly.get(&merged, b"key").unwrap(), None);
}
#[test]
fn test_crdt_merge_never_returns_conflict_error() {
use crate::prolly::config::Config;
use crate::prolly::error::Error;
use crate::prolly::store::MemStore;
use crate::prolly::Prolly;
use std::sync::Arc;
let store = Arc::new(MemStore::new());
let config = Config::default();
let prolly = Prolly::new(store.clone(), config);
let base = prolly.create();
let base = prolly
.put(&base, b"key".to_vec(), b"base".to_vec())
.unwrap();
let left = prolly
.put(&base, b"key".to_vec(), b"left".to_vec())
.unwrap();
let right = prolly
.put(&base, b"key".to_vec(), b"right".to_vec())
.unwrap();
let merger = DefaultConflictFreeMerger::new();
let crdt_config = CrdtConfig::default();
let result = merger.crdt_merge(store.as_ref(), &base, &left, &right, &crdt_config);
assert!(result.is_ok());
assert!(!matches!(result, Err(Error::Conflict(_))));
}
}