use crate::core::{
id::{EdgeId, NodeId, VersionId},
interning::InternedString,
property::PropertyMap,
temporal::{Timestamp, time},
};
pub const MAX_WAL_ENTRY_SIZE: usize = 64 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LSN(pub u64);
impl LSN {
pub fn initial() -> Self {
LSN(1)
}
pub fn next(&self) -> Self {
LSN(self.0 + 1)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum WalOperation {
CreateNode {
node_id: NodeId,
label: InternedString,
properties: PropertyMap,
valid_from: Timestamp,
},
CreateEdge {
edge_id: EdgeId,
source: NodeId,
target: NodeId,
label: InternedString,
properties: PropertyMap,
valid_from: Timestamp,
},
UpdateNode {
node_id: NodeId,
version_id: VersionId,
label: InternedString,
properties: PropertyMap,
valid_from: Timestamp,
},
UpdateEdge {
edge_id: EdgeId,
version_id: VersionId,
label: InternedString,
properties: PropertyMap,
valid_from: Timestamp,
},
DeleteNode {
node_id: NodeId,
valid_from: Timestamp,
},
DeleteEdge {
edge_id: EdgeId,
valid_from: Timestamp,
},
Checkpoint {
lsn: LSN,
timestamp: Timestamp,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct WalEntry {
pub lsn: LSN,
pub timestamp: Timestamp,
pub operation: WalOperation,
pub checksum: u32,
}
impl WalEntry {
pub fn new(lsn: LSN, operation: WalOperation) -> Self {
let timestamp = time::now();
WalEntry {
lsn,
timestamp,
operation,
checksum: 0, }
}
pub fn verify_checksum(&self, serialized_data: &[u8]) -> bool {
if serialized_data.len() < 24 {
return false;
}
let stored_checksum = u32::from_le_bytes([
serialized_data[20],
serialized_data[21],
serialized_data[22],
serialized_data[23],
]);
let mut hasher = crc32fast::Hasher::new();
hasher.update(&serialized_data[0..20]); hasher.update(&serialized_data[24..]); let computed = hasher.finalize();
stored_checksum == computed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lsn() {
let lsn = LSN::initial();
assert_eq!(lsn.0, 1);
let next = lsn.next();
assert_eq!(next.0, 2);
}
#[test]
fn test_wal_entry_new() {
let lsn = LSN(100);
let op = WalOperation::Checkpoint {
lsn: LSN(50),
timestamp: time::now(),
};
let entry = WalEntry::new(lsn, op);
assert_eq!(entry.lsn, lsn);
assert_eq!(entry.checksum, 0); }
#[test]
fn test_verify_checksum_short_data() {
let lsn = LSN(1);
let op = WalOperation::Checkpoint {
lsn: LSN(1),
timestamp: time::now(),
};
let entry = WalEntry::new(lsn, op);
assert!(!entry.verify_checksum(&[0u8; 10])); }
}
#[cfg(test)]
mod sentry_tests {
use super::*;
use crate::core::interning::GLOBAL_INTERNER;
use crate::core::property::PropertyMapBuilder;
use crate::storage::wal::segment_reader::parse_entry_at;
use crate::storage::wal::serialization::serialize_entry_into;
#[test]
fn test_wal_entry_round_trip_correctness() {
let lsn = LSN(12345);
let valid_from = crate::core::hlc::HybridTimestamp::new(1_000_000, 10).unwrap();
let embedding = vec![0.1f32, 0.2, 0.3, 0.4];
let properties = PropertyMapBuilder::new()
.insert("name", "Complex Node")
.insert("score", 99.5)
.insert("active", true)
.insert_vector("embedding", &embedding)
.build();
let op = WalOperation::CreateNode {
node_id: crate::core::NodeId::new(42).unwrap(),
label: GLOBAL_INTERNER.intern("TestLabel").unwrap(),
properties,
valid_from,
};
let mut original_entry = WalEntry::new(lsn, op);
original_entry.timestamp = crate::core::hlc::HybridTimestamp::new(2_000_000, 20).unwrap();
let mut buffer = Vec::new();
serialize_entry_into(&original_entry, &mut buffer).expect("Serialization failed");
let (parsed_entry, consumed) =
parse_entry_at(&buffer, 0, 1).expect("Deserialization failed");
assert_eq!(consumed, buffer.len(), "Should consume entire buffer");
original_entry.checksum = parsed_entry.checksum;
assert_eq!(
original_entry, parsed_entry,
"Round-trip failed: parsed entry does not match original"
);
}
#[test]
fn test_verify_checksum_success() {
let lsn = LSN(123);
let fixed_timestamp = crate::core::hlc::HybridTimestamp::new_unchecked(1_000_000, 0);
let op = WalOperation::Checkpoint {
lsn: LSN(456),
timestamp: fixed_timestamp,
};
let entry = WalEntry {
lsn,
timestamp: fixed_timestamp,
operation: op,
checksum: 0,
};
let mut buffer = Vec::new();
serialize_entry_into(&entry, &mut buffer).expect("Serialization failed");
assert!(
entry.verify_checksum(&buffer),
"Checksum verification failed for valid entry"
);
}
}