hydracache 0.59.0

User-facing HydraCache runtime crate.
Documentation
use std::collections::BTreeMap;

use hydracache::{
    merge_split_brain_records, split_brain_winner, ClusterEpoch, DiscardLoser, HigherVersionWins,
    PartitionId, PutIfAbsent, ReplicatedValueRecord,
};

#[test]
fn split_brain_lower_epoch_side_loses_topology() {
    let (winner, loser) = split_brain_winner(ClusterEpoch::new(7), ClusterEpoch::new(5));

    assert_eq!(winner, ClusterEpoch::new(7));
    assert_eq!(loser, ClusterEpoch::new(5));
}

#[test]
fn split_brain_higher_version_wins_merges_values() {
    let mut winner = BTreeMap::new();
    winner.insert(
        "user:42".to_owned(),
        ReplicatedValueRecord::value(
            PartitionId::new(1),
            4,
            ClusterEpoch::new(4),
            b"old".to_vec(),
        ),
    );
    let mut loser = BTreeMap::new();
    loser.insert(
        "user:42".to_owned(),
        ReplicatedValueRecord::value(
            PartitionId::new(1),
            6,
            ClusterEpoch::new(3),
            b"new".to_vec(),
        ),
    );

    let outcome = merge_split_brain_records(
        ClusterEpoch::new(4),
        ClusterEpoch::new(3),
        winner,
        loser,
        &HigherVersionWins,
    );

    assert_eq!(outcome.records["user:42"].version, 6);
    assert_eq!(outcome.report.merged_entries, 1);
}

#[test]
fn split_brain_tombstone_on_winner_beats_value_on_loser() {
    let mut winner = BTreeMap::new();
    winner.insert(
        "user:42".to_owned(),
        ReplicatedValueRecord::tombstone(PartitionId::new(1), 9, ClusterEpoch::new(5), None),
    );
    let mut loser = BTreeMap::new();
    loser.insert(
        "user:42".to_owned(),
        ReplicatedValueRecord::value(
            PartitionId::new(1),
            9,
            ClusterEpoch::new(5),
            b"stale".to_vec(),
        ),
    );

    let outcome = merge_split_brain_records(
        ClusterEpoch::new(5),
        ClusterEpoch::new(4),
        winner,
        loser,
        &HigherVersionWins,
    );

    assert!(outcome.records["user:42"].is_tombstone());
}

#[test]
fn split_brain_merge_runs_as_topology_op_not_hot_path() {
    let winner = BTreeMap::new();
    let mut loser = BTreeMap::new();
    loser.insert(
        "user:42".to_owned(),
        ReplicatedValueRecord::value(
            PartitionId::new(1),
            1,
            ClusterEpoch::new(1),
            b"value".to_vec(),
        ),
    );

    let outcome = merge_split_brain_records(
        ClusterEpoch::new(2),
        ClusterEpoch::new(1),
        winner,
        loser,
        &PutIfAbsent,
    );

    assert_eq!(outcome.records.len(), 1);
    assert_eq!(outcome.report.merged_entries, 1);
}

#[test]
fn split_brain_discard_policy_counts_loser_entries() {
    let winner = BTreeMap::new();
    let mut loser = BTreeMap::new();
    loser.insert(
        "user:42".to_owned(),
        ReplicatedValueRecord::value(
            PartitionId::new(1),
            1,
            ClusterEpoch::new(1),
            b"value".to_vec(),
        ),
    );

    let outcome = merge_split_brain_records(
        ClusterEpoch::new(2),
        ClusterEpoch::new(1),
        winner,
        loser,
        &DiscardLoser,
    );

    assert!(outcome.records.is_empty());
    assert_eq!(outcome.report.discarded_entries, 1);
}