pg_replica 0.1.0

Consensus-driven failover for PostgreSQL (Raft control plane)
pub const KIND_RAFT: u8 = 0;
pub const KIND_GOSSIP: u8 = 1;

pub struct Gossip {
    pub lsn: u64,
    pub in_recovery: bool,
    pub reconfirm: bool,
}

pub fn encode_gossip(lsn: u64, in_recovery: bool, reconfirm: bool) -> Vec<u8> {
    let mut buf = Vec::with_capacity(10);
    buf.extend_from_slice(&lsn.to_be_bytes());
    buf.push(in_recovery as u8);
    buf.push(reconfirm as u8);
    buf
}

pub fn decode_gossip(bytes: &[u8]) -> Option<Gossip> {
    if bytes.len() < 9 {
        return None;
    }
    let mut lsn = [0u8; 8];
    lsn.copy_from_slice(&bytes[..8]);
    Some(Gossip {
        lsn: u64::from_be_bytes(lsn),
        in_recovery: bytes[8] != 0,
        reconfirm: bytes.get(9).map_or(false, |byte| *byte != 0),
    })
}

#[derive(Clone, Copy)]
pub struct Decision {
    pub seq: u64,
    pub primary: u64,
}

pub fn encode_decision(decision: Decision) -> Vec<u8> {
    let mut buf = Vec::with_capacity(16);
    buf.extend_from_slice(&decision.seq.to_be_bytes());
    buf.extend_from_slice(&decision.primary.to_be_bytes());
    buf
}

pub fn decode_decision(bytes: &[u8]) -> Option<Decision> {
    if bytes.len() < 16 {
        return None;
    }
    let mut seq = [0u8; 8];
    let mut primary = [0u8; 8];
    seq.copy_from_slice(&bytes[..8]);
    primary.copy_from_slice(&bytes[8..16]);
    Some(Decision {
        seq: u64::from_be_bytes(seq),
        primary: u64::from_be_bytes(primary),
    })
}

pub fn choose_primary(candidates: &[(u64, u64, bool)]) -> Option<u64> {
    candidates
        .iter()
        .max_by(|a, b| {
            a.1.cmp(&b.1)
                .then((!a.2 as u8).cmp(&(!b.2 as u8)))
                .then(b.0.cmp(&a.0))
        })
        .map(|candidate| candidate.0)
}