use std::collections::{BTreeMap, BTreeSet};
use crate::datatypes::{ActorId, Crdt};
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Tag {
pub actor: ActorId,
pub counter: u64,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct ElementState {
adds: BTreeSet<Tag>,
removes: BTreeSet<Tag>,
}
impl ElementState {
fn is_present(&self) -> bool {
self.adds.iter().any(|t| !self.removes.contains(t))
}
fn merge(&mut self, other: &Self) {
for tag in &other.adds {
self.adds.insert(tag.clone());
}
for tag in &other.removes {
self.removes.insert(tag.clone());
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct OrSet {
elements: BTreeMap<Vec<u8>, ElementState>,
actor_counters: BTreeMap<ActorId, u64>,
}
impl OrSet {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, actor: &ActorId, element: impl Into<Vec<u8>>) -> Tag {
let counter = self.actor_counters.entry(actor.clone()).or_insert(0);
*counter = counter.checked_add(1).expect("or-set counter overflow");
let tag = Tag {
actor: actor.clone(),
counter: *counter,
};
let entry = self.elements.entry(element.into()).or_default();
entry.adds.insert(tag.clone());
tag
}
pub fn remove(&mut self, element: &[u8]) {
if let Some(state) = self.elements.get_mut(element) {
for tag in state.adds.clone() {
state.removes.insert(tag);
}
}
}
#[must_use]
pub fn contains(&self, element: &[u8]) -> bool {
self.elements
.get(element)
.is_some_and(ElementState::is_present)
}
#[must_use]
pub fn raw_elements(&self) -> BTreeMap<Vec<u8>, (BTreeSet<Tag>, BTreeSet<Tag>)> {
self.elements
.iter()
.map(|(e, s)| (e.clone(), (s.adds.clone(), s.removes.clone())))
.collect()
}
#[must_use]
pub fn raw_actor_counters(&self) -> BTreeMap<ActorId, u64> {
self.actor_counters.clone()
}
#[must_use]
pub fn from_raw(
elements: BTreeMap<Vec<u8>, (BTreeSet<Tag>, BTreeSet<Tag>)>,
actor_counters: BTreeMap<ActorId, u64>,
) -> Self {
let elements = elements
.into_iter()
.map(|(e, (adds, removes))| (e, ElementState { adds, removes }))
.collect();
Self {
elements,
actor_counters,
}
}
}
impl Crdt for OrSet {
type Value = BTreeSet<Vec<u8>>;
fn merge(&mut self, other: &Self) {
for (actor, &count) in &other.actor_counters {
let entry = self.actor_counters.entry(actor.clone()).or_insert(0);
if *entry < count {
*entry = count;
}
}
for (element, state) in &other.elements {
let entry = self.elements.entry(element.clone()).or_default();
entry.merge(state);
}
}
fn value(&self) -> BTreeSet<Vec<u8>> {
self.elements
.iter()
.filter_map(|(e, s)| {
if s.is_present() {
Some(e.clone())
} else {
None
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn aid(name: &str) -> ActorId {
ActorId::new("dc1", name)
}
#[test]
fn add_then_value_contains_element() {
let a = aid("a");
let mut s = OrSet::new();
s.add(&a, b"x".to_vec());
let v = s.value();
assert_eq!(v.len(), 1);
assert!(v.contains(b"x".as_slice()));
}
#[test]
fn remove_after_add_clears_element() {
let a = aid("a");
let mut s = OrSet::new();
s.add(&a, b"x".to_vec());
s.remove(b"x");
assert!(!s.contains(b"x"));
}
#[test]
fn concurrent_adds_merge_to_union() {
let actor_a = aid("a");
let actor_b = aid("b");
let mut left = OrSet::new();
left.add(&actor_a, b"foo".to_vec());
let mut right = OrSet::new();
right.add(&actor_b, b"foo".to_vec());
right.add(&actor_b, b"bar".to_vec());
left.merge(&right);
let merged = left.value();
assert!(merged.contains(b"foo".as_slice()));
assert!(merged.contains(b"bar".as_slice()));
assert_eq!(merged.len(), 2);
}
#[test]
fn concurrent_remove_loses_to_concurrent_add() {
let a = aid("a");
let b = aid("b");
let mut shared = OrSet::new();
shared.add(&a, b"x".to_vec());
let mut left = shared.clone();
left.remove(b"x");
let mut right = shared.clone();
right.add(&b, b"x".to_vec());
let mut merged = left.clone();
merged.merge(&right);
assert!(merged.contains(b"x"));
}
#[test]
fn merge_is_commutative() {
let a = aid("a");
let b = aid("b");
let mut x = OrSet::new();
x.add(&a, b"alpha".to_vec());
x.add(&a, b"beta".to_vec());
let mut y = OrSet::new();
y.add(&b, b"beta".to_vec());
y.remove(b"beta");
y.add(&b, b"gamma".to_vec());
let mut left = x.clone();
left.merge(&y);
let mut right = y.clone();
right.merge(&x);
assert_eq!(left.value(), right.value());
}
#[test]
fn merge_is_idempotent() {
let a = aid("a");
let mut x = OrSet::new();
x.add(&a, b"x".to_vec());
let snapshot = x.clone();
x.merge(&snapshot);
assert_eq!(x.value(), snapshot.value());
}
#[test]
fn add_after_remove_resurrects_with_fresh_tag() {
let a = aid("a");
let mut s = OrSet::new();
s.add(&a, b"x".to_vec());
s.remove(b"x");
assert!(!s.contains(b"x"));
s.add(&a, b"x".to_vec());
assert!(s.contains(b"x"));
}
}