use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct VersionVector {
versions: HashMap<String, u64>,
hlc_timestamp: u64,
hlc_counter: u32,
}
impl VersionVector {
pub fn new() -> Self {
Self {
versions: HashMap::new(),
hlc_timestamp: 0,
hlc_counter: 0,
}
}
pub fn with_node(node_id: impl Into<String>, counter: u64) -> Self {
let mut versions = HashMap::new();
versions.insert(node_id.into(), counter);
Self {
versions,
hlc_timestamp: 0,
hlc_counter: 0,
}
}
pub fn increment(&mut self, node_id: &str) -> u64 {
let counter = self.versions.entry(node_id.to_string()).or_insert(0);
*counter += 1;
*counter
}
pub fn get(&self, node_id: &str) -> u64 {
self.versions.get(node_id).copied().unwrap_or(0)
}
pub fn set_hlc(&mut self, timestamp: u64, counter: u32) {
self.hlc_timestamp = timestamp;
self.hlc_counter = counter;
}
pub fn hlc_timestamp(&self) -> u64 {
self.hlc_timestamp
}
pub fn hlc_counter(&self) -> u32 {
self.hlc_counter
}
pub fn dominates(&self, other: &VersionVector) -> bool {
for (node_id, other_counter) in &other.versions {
let self_counter = self.versions.get(node_id).copied().unwrap_or(0);
if self_counter < *other_counter {
return false;
}
}
true
}
pub fn is_dominated_by(&self, other: &VersionVector) -> bool {
other.dominates(self)
}
pub fn compare(&self, other: &VersionVector) -> VectorComparison {
let self_dominates = self.dominates(other);
let other_dominates = other.dominates(self);
match (self_dominates, other_dominates) {
(true, true) => VectorComparison::Equal,
(true, false) => VectorComparison::Dominates,
(false, true) => VectorComparison::Dominated,
(false, false) => VectorComparison::Concurrent,
}
}
pub fn merge(&mut self, other: &VersionVector) {
for (node_id, other_counter) in &other.versions {
let self_counter = self.versions.entry(node_id.clone()).or_insert(0);
if *other_counter > *self_counter {
*self_counter = *other_counter;
}
}
if other.hlc_timestamp > self.hlc_timestamp {
self.hlc_timestamp = other.hlc_timestamp;
self.hlc_counter = other.hlc_counter;
} else if other.hlc_timestamp == self.hlc_timestamp && other.hlc_counter > self.hlc_counter
{
self.hlc_counter = other.hlc_counter;
}
}
pub fn merged(&self, other: &VersionVector) -> VersionVector {
let mut result = self.clone();
result.merge(other);
result
}
pub fn nodes(&self) -> impl Iterator<Item = &String> {
self.versions.keys()
}
pub fn is_empty(&self) -> bool {
self.versions.is_empty()
}
pub fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).unwrap_or_default()
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
bincode::deserialize(bytes).ok()
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}
impl PartialEq for VersionVector {
fn eq(&self, other: &Self) -> bool {
self.versions == other.versions
}
}
impl Eq for VersionVector {}
impl Hash for VersionVector {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut entries: Vec<_> = self.versions.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (k, v) in entries {
k.hash(state);
v.hash(state);
}
}
}
impl fmt::Display for VersionVector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let entries: Vec<String> = self
.versions
.iter()
.map(|(k, v)| format!("{}:{}", k, v))
.collect();
write!(f, "[{}]", entries.join(", "))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorComparison {
Equal,
Dominates,
Dominated,
Concurrent,
}
impl VectorComparison {
pub fn is_conflict(&self) -> bool {
matches!(self, VectorComparison::Concurrent)
}
pub fn self_wins(&self) -> bool {
matches!(self, VectorComparison::Dominates | VectorComparison::Equal)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct CausalDot {
pub node_id: String,
pub sequence: u64,
}
impl CausalDot {
pub fn new(node_id: impl Into<String>, sequence: u64) -> Self {
Self {
node_id: node_id.into(),
sequence,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConflictInfo {
pub document_key: String,
pub collection: String,
pub local_vector: VersionVector,
pub remote_vector: VersionVector,
pub local_data: Option<serde_json::Value>,
pub remote_data: Option<serde_json::Value>,
pub detected_at: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_vector_increment() {
let mut vv = VersionVector::new();
assert_eq!(vv.increment("node-1"), 1);
assert_eq!(vv.increment("node-1"), 2);
assert_eq!(vv.get("node-1"), 2);
assert_eq!(vv.get("node-2"), 0);
}
#[test]
fn test_version_vector_dominates() {
let mut vv1 = VersionVector::new();
vv1.increment("node-1");
vv1.increment("node-1");
let mut vv2 = VersionVector::new();
vv2.increment("node-1");
assert!(vv1.dominates(&vv2));
assert!(!vv2.dominates(&vv1));
}
#[test]
fn test_version_vector_concurrent() {
let mut vv1 = VersionVector::new();
vv1.increment("node-1");
vv1.increment("node-1");
let mut vv2 = VersionVector::new();
vv2.increment("node-2");
vv2.increment("node-2");
assert!(!vv1.dominates(&vv2));
assert!(!vv2.dominates(&vv1));
let comparison = vv1.compare(&vv2);
assert_eq!(comparison, VectorComparison::Concurrent);
}
#[test]
fn test_version_vector_merge() {
let mut vv1 = VersionVector::new();
vv1.increment("node-1");
vv1.increment("node-1");
let mut vv2 = VersionVector::new();
vv2.increment("node-2");
vv2.increment("node-2");
vv2.increment("node-2");
let merged = vv1.merged(&vv2);
assert_eq!(merged.get("node-1"), 2);
assert_eq!(merged.get("node-2"), 3);
}
#[test]
fn test_version_vector_comparison() {
let mut vv1 = VersionVector::new();
vv1.increment("node-1");
let mut vv2 = VersionVector::new();
vv2.increment("node-1");
vv2.increment("node-1");
assert_eq!(vv2.compare(&vv1), VectorComparison::Dominates);
assert_eq!(vv1.compare(&vv2), VectorComparison::Dominated);
assert_eq!(vv1.compare(&vv1), VectorComparison::Equal);
}
#[test]
fn test_version_vector_serialization() {
let mut vv = VersionVector::new();
vv.increment("node-1");
vv.increment("node-2");
vv.set_hlc(1234567890, 42);
let bytes = vv.to_bytes();
let restored = VersionVector::from_bytes(&bytes).unwrap();
assert_eq!(vv.get("node-1"), restored.get("node-1"));
assert_eq!(vv.get("node-2"), restored.get("node-2"));
assert_eq!(vv.hlc_timestamp(), restored.hlc_timestamp());
}
}