#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(any(feature = "persist", feature = "persist-msgpack", feature = "persist-compressed"))]
pub mod persist;
#[cfg(not(any(feature = "std", feature = "alloc")))]
compile_error!("Either 'std' (default) or 'alloc' feature must be enabled. For no_std environments, use: cargo build --no-default-features --features alloc");
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
hash::Hash,
sync::Arc,
};
#[cfg(not(feature = "std"))]
use alloc::{
string::String,
sync::Arc,
vec::Vec,
};
#[cfg(not(feature = "std"))]
use core::{cmp::Ordering, hash::Hash};
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use hashbrown::{HashMap, HashSet};
#[cfg(all(feature = "sorted-keys", feature = "std"))]
type DataMap<K, V> = std::collections::BTreeMap<K, V>;
#[cfg(all(feature = "sorted-keys", not(feature = "std"), feature = "alloc"))]
type DataMap<K, V> = alloc::collections::BTreeMap<K, V>;
#[cfg(all(not(feature = "sorted-keys"), feature = "std"))]
type DataMap<K, V> = HashMap<K, V>;
#[cfg(all(not(feature = "sorted-keys"), not(feature = "std"), feature = "alloc"))]
type DataMap<K, V> = HashMap<K, V>;
#[cfg(all(feature = "sorted-keys", feature = "std"))]
type DataMapEntry<'a, K, V> = std::collections::btree_map::Entry<'a, K, V>;
#[cfg(all(feature = "sorted-keys", not(feature = "std"), feature = "alloc"))]
type DataMapEntry<'a, K, V> = alloc::collections::btree_map::Entry<'a, K, V>;
#[cfg(all(not(feature = "sorted-keys"), feature = "std"))]
type DataMapEntry<'a, K, V> = std::collections::hash_map::Entry<'a, K, V>;
#[cfg(all(not(feature = "sorted-keys"), not(feature = "std"), feature = "alloc"))]
type DataMapEntry<'a, K, V> = hashbrown::hash_map::Entry<'a, K, V, hashbrown::DefaultHashBuilder>;
#[cfg(feature = "node-id-u128")]
pub type NodeId = u128;
#[cfg(not(feature = "node-id-u128"))]
pub type NodeId = u64;
pub type ColumnKey = String;
const TOMBSTONE_COL_VERSION: u64 = u64::MAX;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "K: serde::Serialize, C: serde::Serialize, V: serde::Serialize")))]
#[cfg_attr(feature = "serde", serde(bound(deserialize = "K: serde::de::DeserializeOwned, C: serde::de::DeserializeOwned, V: serde::de::DeserializeOwned")))]
pub struct Change<K, C, V> {
pub record_id: K,
pub col_name: Option<C>,
pub value: Option<V>,
pub col_version: u64,
pub db_version: u64,
pub node_id: NodeId,
pub local_db_version: u64,
pub flags: u32,
}
impl<K: Eq, C: Eq, V: Eq> Eq for Change<K, C, V> {}
impl<K, C, V> Change<K, C, V> {
#[allow(clippy::too_many_arguments)]
pub fn new(
record_id: K,
col_name: Option<C>,
value: Option<V>,
col_version: u64,
db_version: u64,
node_id: NodeId,
local_db_version: u64,
flags: u32,
) -> Self {
Self {
record_id,
col_name,
value,
col_version,
db_version,
node_id,
local_db_version,
flags,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ColumnVersion {
pub col_version: u64,
pub db_version: u64,
pub node_id: NodeId,
pub local_db_version: u64,
}
impl ColumnVersion {
pub fn new(col_version: u64, db_version: u64, node_id: NodeId, local_db_version: u64) -> Self {
Self {
col_version,
db_version,
node_id,
local_db_version,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TombstoneInfo {
pub db_version: u64,
pub node_id: NodeId,
pub local_db_version: u64,
}
impl TombstoneInfo {
pub fn new(db_version: u64, node_id: NodeId, local_db_version: u64) -> Self {
Self {
db_version,
node_id,
local_db_version,
}
}
pub fn as_column_version(&self) -> ColumnVersion {
ColumnVersion::new(
TOMBSTONE_COL_VERSION,
self.db_version,
self.node_id,
self.local_db_version,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LogicalClock {
time: u64,
}
impl LogicalClock {
pub fn new() -> Self {
Self { time: 0 }
}
pub fn tick(&mut self) -> u64 {
self.time += 1;
self.time
}
pub fn update(&mut self, received_time: u64) -> u64 {
self.time = self.time.max(received_time);
self.time += 1;
self.time
}
pub fn set_time(&mut self, time: u64) {
self.time = time;
}
pub fn current_time(&self) -> u64 {
self.time
}
}
impl Default for LogicalClock {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TombstoneStorage<K: Hash + Eq> {
entries: HashMap<K, TombstoneInfo>,
}
impl<K: Hash + Eq> TombstoneStorage<K> {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
pub fn insert_or_assign(&mut self, key: K, info: TombstoneInfo) {
self.entries.insert(key, info);
}
pub fn find(&self, key: &K) -> Option<TombstoneInfo> {
self.entries.get(key).copied()
}
pub fn erase(&mut self, key: &K) -> bool {
self.entries.remove(key).is_some()
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &TombstoneInfo)> {
self.entries.iter()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn compact(&mut self, min_acknowledged_version: u64) -> usize {
let initial_len = self.entries.len();
self
.entries
.retain(|_, info| info.db_version >= min_acknowledged_version);
initial_len - self.entries.len()
}
}
impl<K: Hash + Eq> Default for TombstoneStorage<K> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "C: serde::Serialize + Hash + Eq, V: serde::Serialize")))]
#[cfg_attr(feature = "serde", serde(bound(deserialize = "C: serde::de::DeserializeOwned + Hash + Eq, V: serde::de::DeserializeOwned")))]
pub struct Record<C, V> {
pub fields: HashMap<C, V>,
pub column_versions: HashMap<C, ColumnVersion>,
pub lowest_local_db_version: u64,
pub highest_local_db_version: u64,
}
impl<C: Hash + Eq, V> Record<C, V> {
pub fn new() -> Self {
Self {
fields: HashMap::new(),
column_versions: HashMap::new(),
lowest_local_db_version: u64::MAX,
highest_local_db_version: 0,
}
}
pub fn from_parts(
fields: HashMap<C, V>,
column_versions: HashMap<C, ColumnVersion>,
) -> Self {
let mut lowest = u64::MAX;
let mut highest = 0;
for ver in column_versions.values() {
if ver.local_db_version < lowest {
lowest = ver.local_db_version;
}
if ver.local_db_version > highest {
highest = ver.local_db_version;
}
}
Self {
fields,
column_versions,
lowest_local_db_version: lowest,
highest_local_db_version: highest,
}
}
}
impl<C: Hash + Eq + PartialEq, V: PartialEq> PartialEq for Record<C, V> {
fn eq(&self, other: &Self) -> bool {
self.fields == other.fields
}
}
impl<C: Hash + Eq, V> Default for Record<C, V> {
fn default() -> Self {
Self::new()
}
}
pub trait MergeRule<K, C, V> {
fn should_accept(
&self,
local_col: u64,
local_db: u64,
local_node: NodeId,
remote_col: u64,
remote_db: u64,
remote_node: NodeId,
) -> bool;
fn should_accept_change(&self, local: &Change<K, C, V>, remote: &Change<K, C, V>) -> bool {
self.should_accept(
local.col_version,
local.db_version,
local.node_id,
remote.col_version,
remote.db_version,
remote.node_id,
)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultMergeRule;
impl<K, C, V> MergeRule<K, C, V> for DefaultMergeRule {
fn should_accept(
&self,
local_col: u64,
local_db: u64,
local_node: NodeId,
remote_col: u64,
remote_db: u64,
remote_node: NodeId,
) -> bool {
match remote_col.cmp(&local_col) {
Ordering::Greater => true,
Ordering::Less => false,
Ordering::Equal => match remote_db.cmp(&local_db) {
Ordering::Greater => true,
Ordering::Less => false,
Ordering::Equal => remote_node > local_node,
},
}
}
}
pub trait ChangeComparator<K, C, V> {
fn compare(&self, a: &Change<K, C, V>, b: &Change<K, C, V>) -> Ordering;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultChangeComparator;
impl<K: Ord, C: Ord, V> ChangeComparator<K, C, V> for DefaultChangeComparator {
fn compare(&self, a: &Change<K, C, V>, b: &Change<K, C, V>) -> Ordering {
match a.record_id.cmp(&b.record_id) {
Ordering::Equal => {}
ord => return ord,
}
match (a.col_name.as_ref(), b.col_name.as_ref()) {
(None, None) => {}
(None, Some(_)) => return Ordering::Greater,
(Some(_), None) => return Ordering::Less,
(Some(a_col), Some(b_col)) => match a_col.cmp(b_col) {
Ordering::Equal => {}
ord => return ord,
},
}
match b.col_version.cmp(&a.col_version) {
Ordering::Equal => {}
ord => return ord,
}
match b.db_version.cmp(&a.db_version) {
Ordering::Equal => {}
ord => return ord,
}
b.node_id.cmp(&a.node_id)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "K: serde::Serialize, C: serde::Serialize, V: serde::Serialize")))]
#[cfg_attr(feature = "serde", serde(bound(deserialize = "K: serde::de::DeserializeOwned + Ord + Hash + Eq + Clone, C: serde::de::DeserializeOwned + Hash + Eq + Clone, V: serde::de::DeserializeOwned + Clone")))]
pub struct CRDT<K: Ord + Hash + Eq + Clone, C: Hash + Eq + Clone, V: Clone> {
node_id: NodeId,
clock: LogicalClock,
data: DataMap<K, Record<C, V>>,
tombstones: TombstoneStorage<K>,
#[cfg_attr(feature = "serde", serde(skip, default))]
parent: Option<Arc<CRDT<K, C, V>>>,
#[allow(dead_code)]
base_version: u64,
}
impl<K: Ord + Hash + Eq + Clone, C: Hash + Eq + Clone, V: Clone> CRDT<K, C, V> {
pub fn new(node_id: NodeId, parent: Option<Arc<CRDT<K, C, V>>>) -> Self {
let (clock, base_version) = if let Some(ref p) = parent {
let parent_clock = p.clock;
let base = parent_clock.current_time();
(parent_clock, base)
} else {
(LogicalClock::new(), 0)
};
Self {
node_id,
clock,
data: DataMap::new(),
tombstones: TombstoneStorage::new(),
parent,
base_version,
}
}
pub fn from_changes(node_id: NodeId, changes: Vec<Change<K, C, V>>) -> Self {
let mut crdt = Self::new(node_id, None);
crdt.apply_changes(changes);
crdt
}
pub fn reset(&mut self, changes: Vec<Change<K, C, V>>) {
self.data.clear();
self.tombstones.clear();
self.clock = LogicalClock::new();
self.apply_changes(changes);
}
fn apply_changes(&mut self, changes: Vec<Change<K, C, V>>) {
let max_db_version = changes
.iter()
.map(|c| c.db_version.max(c.local_db_version))
.max()
.unwrap_or(0);
self.clock.set_time(max_db_version);
for change in changes {
let record_id = change.record_id.clone();
let col_name = change.col_name.clone();
let remote_col_version = change.col_version;
let remote_db_version = change.db_version;
let remote_node_id = change.node_id;
let remote_local_db_version = change.local_db_version;
let remote_value = change.value;
if col_name.is_none() {
self.data.remove(&record_id);
self.tombstones.insert_or_assign(
record_id,
TombstoneInfo::new(remote_db_version, remote_node_id, remote_local_db_version),
);
} else if let Some(col_key) = col_name {
if !self.is_record_tombstoned(&record_id, false) {
let record = self.get_or_create_record_unchecked(&record_id, false);
if let Some(value) = remote_value {
record.fields.insert(col_key.clone(), value);
}
let col_ver = ColumnVersion::new(
remote_col_version,
remote_db_version,
remote_node_id,
remote_local_db_version,
);
record.column_versions.insert(col_key, col_ver);
if remote_local_db_version < record.lowest_local_db_version {
record.lowest_local_db_version = remote_local_db_version;
}
if remote_local_db_version > record.highest_local_db_version {
record.highest_local_db_version = remote_local_db_version;
}
}
}
}
}
#[must_use = "changes should be propagated to other nodes"]
pub fn insert_or_update<I>(&mut self, record_id: &K, fields: I) -> Vec<Change<K, C, V>>
where
I: IntoIterator<Item = (C, V)>,
{
self.insert_or_update_with_flags(record_id, 0, fields)
}
#[must_use = "changes should be propagated to other nodes"]
pub fn insert_or_update_with_flags<I>(
&mut self,
record_id: &K,
flags: u32,
fields: I,
) -> Vec<Change<K, C, V>>
where
I: IntoIterator<Item = (C, V)>,
{
let db_version = self.clock.tick();
if self.is_record_tombstoned(record_id, false) {
return Vec::new();
}
let mut changes = Vec::new();
let node_id = self.node_id; let record = self.get_or_create_record_unchecked(record_id, false);
for (col_name, value) in fields {
let col_version = if let Some(col_info) = record.column_versions.get_mut(&col_name) {
col_info.col_version += 1;
col_info.db_version = db_version;
col_info.node_id = node_id;
col_info.local_db_version = db_version;
col_info.col_version
} else {
record.column_versions.insert(
col_name.clone(),
ColumnVersion::new(1, db_version, node_id, db_version),
);
1
};
if db_version < record.lowest_local_db_version {
record.lowest_local_db_version = db_version;
}
if db_version > record.highest_local_db_version {
record.highest_local_db_version = db_version;
}
record.fields.insert(col_name.clone(), value.clone());
changes.push(Change::new(
record_id.clone(),
Some(col_name),
Some(value),
col_version,
db_version,
node_id,
db_version,
flags,
));
}
changes
}
#[must_use = "changes should be propagated to other nodes"]
pub fn delete_record(&mut self, record_id: &K) -> Option<Change<K, C, V>> {
self.delete_record_with_flags(record_id, 0)
}
#[must_use = "changes should be propagated to other nodes"]
pub fn delete_record_with_flags(&mut self, record_id: &K, flags: u32) -> Option<Change<K, C, V>> {
if self.is_record_tombstoned(record_id, false) {
return None;
}
let db_version = self.clock.tick();
self.data.remove(record_id);
self.tombstones.insert_or_assign(
record_id.clone(),
TombstoneInfo::new(db_version, self.node_id, db_version),
);
Some(Change::new(
record_id.clone(),
None,
None,
TOMBSTONE_COL_VERSION,
db_version,
self.node_id,
db_version,
flags,
))
}
#[must_use = "changes should be propagated to other nodes"]
pub fn delete_field(&mut self, record_id: &K, field_name: &C) -> Option<Change<K, C, V>> {
self.delete_field_with_flags(record_id, field_name, 0)
}
#[must_use = "changes should be propagated to other nodes"]
pub fn delete_field_with_flags(
&mut self,
record_id: &K,
field_name: &C,
flags: u32,
) -> Option<Change<K, C, V>> {
if self.is_record_tombstoned(record_id, false) {
return None;
}
let record = self.data.get_mut(record_id)?;
if !record.fields.contains_key(field_name) {
return None;
}
let db_version = self.clock.tick();
let col_version = if let Some(col_info) = record.column_versions.get_mut(field_name) {
col_info.col_version += 1;
col_info.db_version = db_version;
col_info.node_id = self.node_id;
col_info.local_db_version = db_version;
col_info.col_version
} else {
record.column_versions.insert(
field_name.clone(),
ColumnVersion::new(1, db_version, self.node_id, db_version),
);
1
};
if db_version < record.lowest_local_db_version {
record.lowest_local_db_version = db_version;
}
if db_version > record.highest_local_db_version {
record.highest_local_db_version = db_version;
}
record.fields.remove(field_name);
Some(Change::new(
record_id.clone(),
Some(field_name.clone()),
None, col_version,
db_version,
self.node_id,
db_version,
flags,
))
}
pub fn merge_changes<R: MergeRule<K, C, V>>(
&mut self,
changes: Vec<Change<K, C, V>>,
merge_rule: &R,
) -> Vec<Change<K, C, V>> {
self.merge_changes_impl(changes, false, merge_rule)
}
fn merge_changes_impl<R: MergeRule<K, C, V>>(
&mut self,
changes: Vec<Change<K, C, V>>,
ignore_parent: bool,
merge_rule: &R,
) -> Vec<Change<K, C, V>> {
let mut accepted_changes = Vec::new();
if changes.is_empty() {
return accepted_changes;
}
for change in changes {
let Change {
record_id,
col_name,
value: remote_value,
col_version: remote_col_version,
db_version: remote_db_version,
node_id: remote_node_id,
flags,
..
} = change;
let new_local_db_version = self.clock.update(remote_db_version);
if self.is_record_tombstoned(&record_id, ignore_parent) {
continue;
}
let local_col_info = if col_name.is_none() {
self
.tombstones
.find(&record_id)
.map(|info| info.as_column_version())
} else if let Some(ref col) = col_name {
self
.get_record_ptr(&record_id, ignore_parent)
.and_then(|record| record.column_versions.get(col).copied())
} else {
None
};
let should_accept = if let Some(local_info) = local_col_info {
merge_rule.should_accept(
local_info.col_version,
local_info.db_version,
local_info.node_id,
remote_col_version,
remote_db_version,
remote_node_id,
)
} else {
true
};
if should_accept {
if let Some(col_key) = col_name {
let record = self.get_or_create_record_unchecked(&record_id, ignore_parent);
if let Some(value) = remote_value.clone() {
record.fields.insert(col_key.clone(), value);
} else {
record.fields.remove(&col_key);
}
record.column_versions.insert(
col_key.clone(),
ColumnVersion::new(
remote_col_version,
remote_db_version,
remote_node_id,
new_local_db_version,
),
);
if new_local_db_version < record.lowest_local_db_version {
record.lowest_local_db_version = new_local_db_version;
}
if new_local_db_version > record.highest_local_db_version {
record.highest_local_db_version = new_local_db_version;
}
accepted_changes.push(Change::new(
record_id,
Some(col_key),
remote_value,
remote_col_version,
remote_db_version,
remote_node_id,
new_local_db_version,
flags,
));
} else {
self.data.remove(&record_id);
self.tombstones.insert_or_assign(
record_id.clone(),
TombstoneInfo::new(remote_db_version, remote_node_id, new_local_db_version),
);
accepted_changes.push(Change::new(
record_id,
None,
None,
remote_col_version,
remote_db_version,
remote_node_id,
new_local_db_version,
flags,
));
}
}
}
accepted_changes
}
#[must_use]
pub fn get_changes_since(&self, last_db_version: u64) -> Vec<Change<K, C, V>>
where
K: Ord,
C: Ord,
{
self.get_changes_since_excluding(last_db_version, &HashSet::new())
}
pub fn get_changes_since_excluding(
&self,
last_db_version: u64,
excluding: &HashSet<NodeId>,
) -> Vec<Change<K, C, V>>
where
K: Ord,
C: Ord,
{
let mut changes = Vec::new();
if let Some(ref parent) = self.parent {
let parent_changes = parent.get_changes_since_excluding(last_db_version, excluding);
changes.extend(parent_changes);
}
for (record_id, record) in &self.data {
if record.highest_local_db_version <= last_db_version {
continue;
}
for (col_name, clock_info) in &record.column_versions {
if clock_info.local_db_version > last_db_version && !excluding.contains(&clock_info.node_id)
{
let value = record.fields.get(col_name).cloned();
changes.push(Change::new(
record_id.clone(),
Some(col_name.clone()),
value,
clock_info.col_version,
clock_info.db_version,
clock_info.node_id,
clock_info.local_db_version,
0,
));
}
}
}
for (record_id, tombstone_info) in self.tombstones.iter() {
if tombstone_info.local_db_version > last_db_version
&& !excluding.contains(&tombstone_info.node_id)
{
changes.push(Change::new(
record_id.clone(),
None,
None,
TOMBSTONE_COL_VERSION,
tombstone_info.db_version,
tombstone_info.node_id,
tombstone_info.local_db_version,
0,
));
}
}
if self.parent.is_some() {
Self::compress_changes(&mut changes);
}
changes
}
pub fn compress_changes(changes: &mut Vec<Change<K, C, V>>)
where
K: Ord,
C: Ord,
{
if changes.is_empty() {
return;
}
let comparator = DefaultChangeComparator;
changes.sort_unstable_by(|a, b| comparator.compare(a, b));
let mut write = 0;
for read in 1..changes.len() {
if changes[read].record_id != changes[write].record_id {
write += 1;
if write != read {
changes[write] = changes[read].clone();
}
} else if changes[read].col_name.is_none() && changes[write].col_name.is_some() {
let mut first_pos = write;
while first_pos > 0 && changes[first_pos - 1].record_id == changes[read].record_id {
first_pos -= 1;
}
changes[first_pos] = changes[read].clone();
write = first_pos;
} else if changes[read].col_name != changes[write].col_name
&& changes[write].col_name.is_some()
{
write += 1;
if write != read {
changes[write] = changes[read].clone();
}
}
}
changes.truncate(write + 1);
}
pub fn get_record(&self, record_id: &K) -> Option<&Record<C, V>> {
self.get_record_ptr(record_id, false)
}
pub fn is_tombstoned(&self, record_id: &K) -> bool {
self.is_record_tombstoned(record_id, false)
}
pub fn get_tombstone(&self, record_id: &K) -> Option<TombstoneInfo> {
if let Some(info) = self.tombstones.find(record_id) {
return Some(info);
}
if let Some(ref parent) = self.parent {
return parent.get_tombstone(record_id);
}
None
}
pub fn compact_tombstones(&mut self, min_acknowledged_version: u64) -> usize {
self.tombstones.compact(min_acknowledged_version)
}
pub fn tombstone_count(&self) -> usize {
self.tombstones.len()
}
pub fn get_clock(&self) -> &LogicalClock {
&self.clock
}
pub fn get_data(&self) -> &DataMap<K, Record<C, V>> {
&self.data
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> Result<String, serde_json::Error>
where
K: serde::Serialize,
C: serde::Serialize,
V: serde::Serialize,
{
serde_json::to_string(self)
}
#[cfg(feature = "json")]
pub fn from_json(json: &str) -> Result<Self, serde_json::Error>
where
K: serde::de::DeserializeOwned + Hash + Eq + Clone,
C: serde::de::DeserializeOwned + Hash + Eq + Clone,
V: serde::de::DeserializeOwned + Clone,
{
serde_json::from_str(json)
}
#[cfg(feature = "binary")]
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::error::EncodeError>
where
K: serde::Serialize,
C: serde::Serialize,
V: serde::Serialize,
{
bincode::serde::encode_to_vec(self, bincode::config::standard())
}
#[cfg(feature = "binary")]
pub fn from_bytes(bytes: &[u8]) -> Result<Self, bincode::error::DecodeError>
where
K: serde::de::DeserializeOwned + Hash + Eq + Clone,
C: serde::de::DeserializeOwned + Hash + Eq + Clone,
V: serde::de::DeserializeOwned + Clone,
{
let (result, _len) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
Ok(result)
}
#[cfg(feature = "msgpack")]
pub fn to_msgpack_bytes(&self) -> Result<Vec<u8>, rmp_serde::encode::Error>
where
K: serde::Serialize,
C: serde::Serialize,
V: serde::Serialize,
{
rmp_serde::to_vec(self)
}
#[cfg(feature = "msgpack")]
pub fn from_msgpack_bytes(bytes: &[u8]) -> Result<Self, rmp_serde::decode::Error>
where
K: serde::de::DeserializeOwned + Hash + Eq + Clone,
C: serde::de::DeserializeOwned + Hash + Eq + Clone,
V: serde::de::DeserializeOwned + Clone,
{
rmp_serde::from_slice(bytes)
}
#[cfg(feature = "std")]
pub fn get_changed_since(&self, since_version: u64) -> (
DataMap<K, Record<C, V>>,
HashMap<K, TombstoneInfo>,
) {
let records = self.data
.iter()
.filter(|(_, record)| record.highest_local_db_version > since_version)
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let tombstones = self.tombstones
.iter()
.filter(|(_, info)| info.local_db_version > since_version)
.map(|(k, v)| (k.clone(), *v))
.collect();
(records, tombstones)
}
#[cfg(not(feature = "std"))]
pub fn get_changed_since(&self, since_version: u64) -> (
DataMap<K, Record<C, V>>,
HashMap<K, TombstoneInfo>,
) {
let records = self.data
.iter()
.filter(|(_, record)| record.highest_local_db_version > since_version)
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let tombstones = self.tombstones
.iter()
.filter(|(_, info)| info.local_db_version > since_version)
.map(|(k, v)| (k.clone(), *v))
.collect();
(records, tombstones)
}
fn is_record_tombstoned(&self, record_id: &K, ignore_parent: bool) -> bool {
if self.tombstones.find(record_id).is_some() {
return true;
}
if !ignore_parent {
if let Some(ref parent) = self.parent {
return parent.is_record_tombstoned(record_id, false);
}
}
false
}
fn get_or_create_record_unchecked(
&mut self,
record_id: &K,
ignore_parent: bool,
) -> &mut Record<C, V> {
match self.data.entry(record_id.clone()) {
DataMapEntry::Occupied(e) => e.into_mut(),
DataMapEntry::Vacant(e) => {
let record = if !ignore_parent {
self
.parent
.as_ref()
.and_then(|p| p.get_record_ptr(record_id, false))
.cloned()
.unwrap_or_else(Record::new)
} else {
Record::new()
};
e.insert(record)
}
}
}
fn get_record_ptr(&self, record_id: &K, ignore_parent: bool) -> Option<&Record<C, V>> {
if let Some(record) = self.data.get(record_id) {
return Some(record);
}
if !ignore_parent {
if let Some(ref parent) = self.parent {
return parent.get_record_ptr(record_id, false);
}
}
None
}
}
#[cfg(feature = "sorted-keys")]
impl<K: Ord + Hash + Eq + Clone, C: Hash + Eq + Clone, V: Clone> CRDT<K, C, V> {
pub fn range<R>(&self, range: R) -> impl Iterator<Item = (&K, &Record<C, V>)>
where
R: core::ops::RangeBounds<K>,
{
self.data.range(range)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::{string::ToString, vec};
#[test]
fn test_logical_clock() {
let mut clock = LogicalClock::new();
assert_eq!(clock.current_time(), 0);
let t1 = clock.tick();
assert_eq!(t1, 1);
assert_eq!(clock.current_time(), 1);
let t2 = clock.update(5);
assert_eq!(t2, 6);
assert_eq!(clock.current_time(), 6);
}
#[test]
fn test_tombstone_storage() {
let mut storage = TombstoneStorage::new();
let info = TombstoneInfo::new(10, 1, 10);
storage.insert_or_assign("key1".to_string(), info);
assert_eq!(storage.len(), 1);
assert_eq!(storage.find(&"key1".to_string()), Some(info));
assert_eq!(storage.find(&"key2".to_string()), None);
let removed = storage.compact(15);
assert_eq!(removed, 1);
assert_eq!(storage.len(), 0);
}
#[test]
fn test_basic_insert() {
let mut crdt: CRDT<String, String, String> = CRDT::new(1, None);
let fields = vec![
("name".to_string(), "Alice".to_string()),
("age".to_string(), "30".to_string()),
];
let changes = crdt.insert_or_update(&"user1".to_string(), fields);
assert_eq!(changes.len(), 2);
assert_eq!(crdt.get_data().len(), 1);
let record = crdt.get_record(&"user1".to_string()).unwrap();
assert_eq!(record.fields.get("name").unwrap(), "Alice");
assert_eq!(record.fields.get("age").unwrap(), "30");
}
#[test]
fn test_delete_record() {
let mut crdt: CRDT<String, String, String> = CRDT::new(1, None);
let fields = vec![("name".to_string(), "Bob".to_string())];
let _ = crdt.insert_or_update(&"user2".to_string(), fields);
let delete_change = crdt.delete_record(&"user2".to_string());
assert!(delete_change.is_some());
assert!(crdt.is_tombstoned(&"user2".to_string()));
assert_eq!(crdt.get_data().len(), 0);
}
#[test]
fn test_merge_changes() {
let mut crdt1: CRDT<String, String, String> = CRDT::new(1, None);
let mut crdt2: CRDT<String, String, String> = CRDT::new(2, None);
let fields1 = vec![("tag".to_string(), "Node1".to_string())];
let changes1 = crdt1.insert_or_update(&"record1".to_string(), fields1);
let fields2 = vec![("tag".to_string(), "Node2".to_string())];
let changes2 = crdt2.insert_or_update(&"record1".to_string(), fields2);
let merge_rule = DefaultMergeRule;
crdt1.merge_changes(changes2, &merge_rule);
crdt2.merge_changes(changes1, &merge_rule);
assert_eq!(
crdt1
.get_record(&"record1".to_string())
.unwrap()
.fields
.get("tag")
.unwrap(),
"Node2"
);
assert_eq!(crdt1.get_data(), crdt2.get_data());
}
#[test]
#[cfg(feature = "serde")]
fn test_change_serialization() {
#[allow(unused_variables)]
let change = Change::new(
"record1".to_string(),
Some("name".to_string()),
Some("Alice".to_string()),
1,
10,
1,
10,
0,
);
#[cfg(feature = "json")]
{
let json = serde_json::to_string(&change).unwrap();
let deserialized: Change<String, String, String> = serde_json::from_str(&json).unwrap();
assert_eq!(change, deserialized);
}
#[cfg(feature = "binary")]
{
let bytes = bincode::serde::encode_to_vec(&change, bincode::config::standard()).unwrap();
let (deserialized, _): (Change<String, String, String>, _) =
bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
assert_eq!(change, deserialized);
}
}
#[test]
#[cfg(feature = "serde")]
fn test_record_serialization() {
let mut fields = HashMap::new();
fields.insert("name".to_string(), "Bob".to_string());
fields.insert("age".to_string(), "25".to_string());
let mut column_versions = HashMap::new();
column_versions.insert("name".to_string(), ColumnVersion::new(1, 10, 1, 10));
column_versions.insert("age".to_string(), ColumnVersion::new(1, 11, 1, 11));
#[allow(unused_variables)]
let record = Record::from_parts(fields, column_versions);
#[cfg(feature = "json")]
{
let json = serde_json::to_string(&record).unwrap();
let deserialized: Record<String, String> = serde_json::from_str(&json).unwrap();
assert_eq!(record, deserialized);
}
#[cfg(feature = "binary")]
{
let bytes = bincode::serde::encode_to_vec(&record, bincode::config::standard()).unwrap();
let (deserialized, _): (Record<String, String>, _) =
bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
assert_eq!(record, deserialized);
}
}
#[test]
#[cfg(feature = "json")]
fn test_crdt_json_serialization() {
let mut crdt: CRDT<String, String, String> = CRDT::new(1, None);
let fields = vec![
("name".to_string(), "Alice".to_string()),
("age".to_string(), "30".to_string()),
];
let _ = crdt.insert_or_update(&"user1".to_string(), fields);
let fields2 = vec![("name".to_string(), "Bob".to_string())];
let _ = crdt.insert_or_update(&"user2".to_string(), fields2);
let _ = crdt.delete_record(&"user2".to_string());
let json = crdt.to_json().unwrap();
let deserialized: CRDT<String, String, String> = CRDT::from_json(&json).unwrap();
assert_eq!(crdt.get_data().len(), deserialized.get_data().len());
assert_eq!(
crdt.get_record(&"user1".to_string()).unwrap().fields,
deserialized.get_record(&"user1".to_string()).unwrap().fields
);
assert_eq!(crdt.tombstone_count(), deserialized.tombstone_count());
assert!(deserialized.is_tombstoned(&"user2".to_string()));
assert_eq!(
crdt.get_clock().current_time(),
deserialized.get_clock().current_time()
);
let has_parent = deserialized.parent.is_some();
assert!(!has_parent);
}
#[test]
#[cfg(feature = "binary")]
fn test_crdt_binary_serialization() {
let mut crdt: CRDT<String, String, String> = CRDT::new(1, None);
let fields = vec![
("name".to_string(), "Alice".to_string()),
("age".to_string(), "30".to_string()),
];
let _ = crdt.insert_or_update(&"user1".to_string(), fields);
let bytes = crdt.to_bytes().unwrap();
let deserialized: CRDT<String, String, String> = CRDT::from_bytes(&bytes).unwrap();
assert_eq!(crdt.get_data().len(), deserialized.get_data().len());
assert_eq!(
crdt.get_record(&"user1".to_string()).unwrap().fields,
deserialized.get_record(&"user1".to_string()).unwrap().fields
);
assert_eq!(
crdt.get_clock().current_time(),
deserialized.get_clock().current_time()
);
}
#[test]
#[cfg(feature = "serde")]
fn test_parent_not_serialized() {
let mut parent: CRDT<String, String, String> = CRDT::new(1, None);
let fields = vec![("parent_field".to_string(), "parent_value".to_string())];
let _ = parent.insert_or_update(&"parent_record".to_string(), fields);
let parent_arc = Arc::new(parent);
let mut child = CRDT::new(2, Some(parent_arc.clone()));
let child_fields = vec![("child_field".to_string(), "child_value".to_string())];
let _ = child.insert_or_update(&"child_record".to_string(), child_fields);
#[cfg(feature = "json")]
{
let json = serde_json::to_string(&child).unwrap();
let deserialized: CRDT<String, String, String> = serde_json::from_str(&json).unwrap();
assert!(deserialized.parent.is_none());
assert!(deserialized.get_record(&"child_record".to_string()).is_some());
assert!(deserialized.get_record(&"parent_record".to_string()).is_none());
}
}
#[test]
#[cfg(feature = "sorted-keys")]
fn test_sorted_keys_range_queries() {
let mut crdt: CRDT<String, String, String> = CRDT::new(1, None);
let _ = crdt.insert_or_update(
&String::from("session-abc-001"),
vec![(String::from("data"), String::from("first"))],
);
let _ = crdt.insert_or_update(
&String::from("session-abc-002"),
vec![(String::from("data"), String::from("second"))],
);
let _ = crdt.insert_or_update(
&String::from("session-abc-003"),
vec![(String::from("data"), String::from("third"))],
);
let _ = crdt.insert_or_update(
&String::from("session-xyz-001"),
vec![(String::from("data"), String::from("other"))],
);
let _ = crdt.insert_or_update(
&String::from("user-001"),
vec![(String::from("name"), String::from("Alice"))],
);
let session_abc_records: Vec<_> = crdt
.range(String::from("session-abc-")..String::from("session-abd-"))
.collect();
assert_eq!(session_abc_records.len(), 3);
assert!(session_abc_records
.iter()
.all(|(k, _)| k.starts_with("session-abc-")));
let all_keys: Vec<String> = crdt.get_data().keys().cloned().collect();
let mut sorted_keys = all_keys.clone();
sorted_keys.sort();
assert_eq!(all_keys, sorted_keys, "Keys should be in sorted order");
let range_from_user: Vec<_> = crdt.range(String::from("user-")..).collect();
assert_eq!(range_from_user.len(), 1);
assert_eq!(range_from_user[0].0, "user-001");
}
}