hydracache 0.60.0

User-facing HydraCache runtime crate.
Documentation
use hydracache::{
    tombstone_crdt_decision, ClusterEpoch, ClusterNodeId, ConflictFreeValue, CrdtMergeStats,
    GCounter, HigherVersionWins, HybridLogicalClock, LwwRegister, MergePolicy, OrSet, OrSetTag,
    PartitionId, PnCounter, ReplicatedValueRecord, TombstoneCrdtDecision,
};

fn merged<T: ConflictFreeValue>(mut left: T, right: &T) -> T {
    left.merge(right);
    left
}

#[test]
fn crdt_gcounter_merge_is_associative_commutative_idempotent() {
    let mut a = GCounter::new();
    a.increment("a", 2);
    let mut b = GCounter::new();
    b.increment("b", 3);
    let mut c = GCounter::new();
    c.increment("a", 5);

    let left_assoc = merged(merged(a.clone(), &b), &c);
    let right_assoc = merged(a.clone(), &merged(b.clone(), &c));
    assert_eq!(left_assoc, right_assoc);

    assert_eq!(merged(a.clone(), &b), merged(b.clone(), &a));
    assert_eq!(merged(a.clone(), &a), a);
    assert_eq!(left_assoc.value(), 8);
}

#[test]
fn crdt_pn_counter_converges_across_regions() {
    let mut eu = PnCounter::new();
    eu.increment("eu", 10);
    let mut us = PnCounter::new();
    us.decrement("us", 3);

    eu.merge(&us);
    us.merge(&eu);

    assert_eq!(eu, us);
    assert_eq!(eu.value(), 7);
}

#[test]
fn crdt_or_set_add_remove_converges() {
    let mut first = OrSet::new();
    let mut second = OrSet::new();
    let add_v1 = OrSetTag::new("eu", 1);
    let add_v2 = OrSetTag::new("us", 1);

    first.add("profile:42", add_v1.clone());
    second.merge(&first);
    second.remove(&"profile:42");
    first.add("profile:42", add_v2);

    first.merge(&second);
    second.merge(&first);

    assert_eq!(first, second);
    assert!(first.contains(&"profile:42"));
    assert_eq!(first.values(), vec!["profile:42"]);
    assert!(first.metadata_bytes() >= 2 * std::mem::size_of::<u64>() as u64);
}

#[test]
fn crdt_lww_register_uses_hlc_not_wall_clock() {
    let mut left = LwwRegister::new("old", HybridLogicalClock::new(1000, 0), "eu");
    let right = LwwRegister::new("new", HybridLogicalClock::new(1, 2), "us");

    left.merge(&right);
    assert_eq!(left.value(), &"old");

    let newer = LwwRegister::new("newer", HybridLogicalClock::new(1000, 1), "us");
    left.merge(&newer);
    assert_eq!(left.value(), &"newer");
}

#[test]
fn crdt_tombstone_beats_concurrent_crdt_update() {
    let tombstone =
        ReplicatedValueRecord::tombstone(PartitionId::new(1), 9, ClusterEpoch::new(4), None);

    assert_eq!(
        tombstone_crdt_decision(&tombstone, 9, ClusterEpoch::new(4)),
        TombstoneCrdtDecision::KeepTombstone
    );
    assert_eq!(
        tombstone_crdt_decision(&tombstone, 10, ClusterEpoch::new(4)),
        TombstoneCrdtDecision::ApplyUpdate
    );
}

#[test]
fn crdt_non_crdt_value_still_uses_merge_policy() {
    let winner = ReplicatedValueRecord::value(
        PartitionId::new(1),
        3,
        ClusterEpoch::new(1),
        b"winner".to_vec(),
    );
    let loser = ReplicatedValueRecord::value(
        PartitionId::new(1),
        4,
        ClusterEpoch::new(1),
        b"loser".to_vec(),
    );
    let merged = HigherVersionWins
        .merge(Some(&winner), &loser)
        .expect("merged");

    assert_eq!(merged.version, 4);
}

#[test]
fn crdt_merge_stats_use_bounded_type_labels() {
    let mut stats = CrdtMergeStats::default();
    let mut counter = GCounter::new();
    counter.increment("eu", 1);

    stats.record_merge("gcounter", true, counter.metadata_bytes());
    stats.record_merge("gcounter", false, counter.metadata_bytes());

    assert_eq!(stats.merge_total["gcounter"], 2);
    assert_eq!(stats.conflict_resolved_total["gcounter"], 1);
    assert_eq!(stats.metadata_bytes["gcounter"], counter.metadata_bytes());
}

#[test]
fn crdt_lww_register_ties_break_by_writer() {
    let mut left = LwwRegister::new(
        "left",
        HybridLogicalClock::new(5, 1),
        ClusterNodeId::from("a"),
    );
    let right = LwwRegister::new(
        "right",
        HybridLogicalClock::new(5, 1),
        ClusterNodeId::from("z"),
    );

    left.merge(&right);

    assert_eq!(left.value(), &"right");
}