#![warn(missing_docs)]
#![deny(unsafe_code)]
mod reader;
mod sink;
mod writer;
pub use reader::{replay_all, replay_range, WalReader, WalRecord};
pub use sink::WalSink;
pub use writer::WalWriter;
pub const WAL_MAGIC: &[u8; 4] = b"CXWL";
pub const WAL_VERSION: u16 = 1;
pub const WAL_HEADER_SIZE: usize = 6;
pub const WAL_RECORD_HEADER_SIZE: usize = 18;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum WalRecordType {
Data = 0,
Batch = 1,
Schema = 2,
Tombstone = 3,
}
impl WalRecordType {
#[must_use]
pub fn from_u8(val: u8) -> Option<Self> {
match val {
0 => Some(Self::Data),
1 => Some(Self::Batch),
2 => Some(Self::Schema),
3 => Some(Self::Tombstone),
_ => None,
}
}
}
pub const WAL_PAYLOAD_VERSION: u8 = 1;
pub(crate) const WAL_COMPRESS_MAGIC: &[u8; 4] = b"CXLZ";
pub(crate) fn compress_wal_payload(payload: &[u8]) -> Vec<u8> {
let compressed = lz4_flex::compress_prepend_size(payload);
let mut result = Vec::with_capacity(4 + compressed.len());
result.extend_from_slice(WAL_COMPRESS_MAGIC);
result.extend_from_slice(&compressed);
result
}
pub(crate) fn decompress_wal_payload(payload: Vec<u8>) -> Result<Vec<u8>, String> {
if payload.len() >= 4 && &payload[..4] == WAL_COMPRESS_MAGIC {
lz4_flex::decompress_size_prepended(&payload[4..])
.map_err(|e| format!("LZ4 decompression failed: {e}"))
} else {
Ok(payload)
}
}
pub const BATCH_MAGIC: &[u8; 4] = b"CXBT";
#[must_use]
pub fn encode_batch_payload(payloads: &[&[u8]]) -> Vec<u8> {
let total: usize = 4 + 4 + payloads.iter().map(|p| 4 + p.len()).sum::<usize>();
let mut buf = Vec::with_capacity(total);
buf.extend_from_slice(BATCH_MAGIC);
buf.extend_from_slice(&(payloads.len() as u32).to_le_bytes());
for p in payloads {
buf.extend_from_slice(&(p.len() as u32).to_le_bytes());
buf.extend_from_slice(p);
}
buf
}
#[must_use]
pub fn decode_batch_payload(data: &[u8]) -> Option<Vec<Vec<u8>>> {
if data.len() < 8 || &data[..4] != BATCH_MAGIC {
return None;
}
let count = u32::from_le_bytes([data[4], data[5], data[6], data[7]]) as usize;
let remaining = data.len() - 8;
if count > remaining / 4 {
return None; }
let mut offset = 8;
let mut payloads = Vec::with_capacity(count);
for _ in 0..count {
if offset + 4 > data.len() {
return None; }
let len = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]) as usize;
offset += 4;
if offset + len > data.len() {
return None; }
payloads.push(data[offset..offset + len].to_vec());
offset += len;
}
Some(payloads)
}