use serde::{Deserialize, Serialize};
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
#[msgpack(map)]
pub struct RedoRecord {
pub version: u16,
pub ops: Vec<RedoSubRecord>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[msgpack(default)]
pub calvin_stamp: Option<CalvinStamp>,
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
#[msgpack(map)]
pub struct RedoSubRecord {
pub record_type: u32,
pub payload: Vec<u8>,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
#[msgpack(map)]
pub struct CalvinStamp {
pub epoch: u64,
pub position: u32,
pub vshard_id: u32,
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
#[msgpack(map)]
pub struct EdgePutRedo {
pub collection: String,
pub src_id: String,
pub label: String,
pub dst_id: String,
pub properties: Vec<u8>,
pub src_surrogate: u32,
pub dst_surrogate: u32,
#[serde(default)]
#[msgpack(default)]
pub system_from: Option<i64>,
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
#[msgpack(map)]
pub struct EdgeDeleteRedo {
pub collection: String,
pub src_id: String,
pub label: String,
pub dst_id: String,
#[serde(default)]
#[msgpack(default)]
pub system_from: Option<i64>,
}
impl RedoRecord {
pub fn to_bytes(&self) -> crate::Result<Vec<u8>> {
zerompk::to_msgpack_vec(self).map_err(|e| crate::Error::Serialization {
format: "msgpack".into(),
detail: format!("redo record encode: {e}"),
})
}
pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
zerompk::from_msgpack(bytes).map_err(|e| crate::Error::Serialization {
format: "msgpack".into(),
detail: format!("redo record decode: {e}"),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_ops() -> Vec<RedoSubRecord> {
vec![
RedoSubRecord {
record_type: nodedb_wal::record::RecordType::Put as u32,
payload: vec![1, 2, 3, 4],
},
RedoSubRecord {
record_type: nodedb_wal::record::RecordType::VectorPut as u32,
payload: vec![9, 8, 7],
},
]
}
#[test]
fn roundtrip_without_calvin_stamp() {
let record = RedoRecord {
version: 1,
ops: sample_ops(),
calvin_stamp: None,
};
let bytes = record.to_bytes().expect("encode");
let decoded = RedoRecord::from_bytes(&bytes).expect("decode");
assert_eq!(decoded, record);
assert!(decoded.calvin_stamp.is_none());
}
#[test]
fn roundtrip_with_calvin_stamp() {
let record = RedoRecord {
version: 1,
ops: sample_ops(),
calvin_stamp: Some(CalvinStamp {
epoch: 42,
position: 7,
vshard_id: 3,
}),
};
let bytes = record.to_bytes().expect("encode");
let decoded = RedoRecord::from_bytes(&bytes).expect("decode");
assert_eq!(decoded, record);
let stamp = decoded.calvin_stamp.expect("stamp present");
assert_eq!(stamp.epoch, 42);
assert_eq!(stamp.position, 7);
assert_eq!(stamp.vshard_id, 3);
}
#[test]
fn decodes_legacy_bytes_without_calvin_stamp_field() {
#[derive(Serialize, zerompk::ToMessagePack)]
#[msgpack(map)]
struct LegacyRedoRecord {
version: u16,
ops: Vec<RedoSubRecord>,
}
let legacy = LegacyRedoRecord {
version: 1,
ops: sample_ops(),
};
let bytes = zerompk::to_msgpack_vec(&legacy).expect("encode legacy");
let decoded = RedoRecord::from_bytes(&bytes).expect("decode legacy as RedoRecord");
assert_eq!(decoded.version, 1);
assert_eq!(decoded.ops, sample_ops());
assert!(decoded.calvin_stamp.is_none());
}
}