use super::*;
pub(super) trait RecordDecoder {
fn with_unit<T>(&self, operation: impl FnOnce() -> Result<T>) -> Result<T>;
fn decode_body(&self, code: u64, body: &[u8]) -> Result<WalOp>;
fn batch(&self, count: u32) -> Vec<WalOp>;
fn push(&self, batch: &mut Vec<WalOp>, op: WalOp) -> Result<()>;
}
pub(super) fn decode_record<D: RecordDecoder>(
bytes: &[u8],
decoder: &D,
) -> Result<BinaryWalRecordDecode> {
let (lsn, record_kind, commit_epoch, op_count) = decoder.with_unit(|| {
if bytes.len() < 21 {
return Err(HawDBError::Storage(
"binary WAL record envelope is truncated".to_string(),
));
}
let lsn = u64::from_le_bytes(bytes[0..8].try_into().expect("8-byte lsn"));
let record_kind = bytes[8];
let commit_epoch =
u64::from_le_bytes(bytes[9..17].try_into().expect("8-byte commit epoch"));
let op_count = u32::from_le_bytes(bytes[17..21].try_into().expect("4-byte op count"));
Ok((lsn, record_kind, commit_epoch, op_count))
})?;
let mut pos = 21usize;
let op = match record_kind {
RECORD_KIND_SINGLE => {
if op_count != 1 {
return Err(HawDBError::Storage(format!(
"single-op WAL record declares op_count {op_count}"
)));
}
let op = decode_frame(bytes, &mut pos, decoder)?;
if let WalOp::Batch(_) = op {
return Err(HawDBError::Storage(
"single-op WAL record carries a batch envelope".to_string(),
));
}
op
}
RECORD_KIND_BATCH => {
let mut ops = decoder.batch(op_count);
for _ in 0..op_count {
decoder.push(&mut ops, decode_frame(bytes, &mut pos, decoder)?)?;
}
WalOp::Batch(ops)
}
kind => {
return Err(HawDBError::Storage(format!(
"unknown WAL record kind {kind}"
)));
}
};
if pos != bytes.len() {
return Err(HawDBError::Storage(format!(
"binary WAL record has {} trailing bytes",
bytes.len() - pos
)));
}
Ok(BinaryWalRecordDecode::Entry {
entry: WalEntry { lsn, op },
commit_epoch,
})
}
pub(super) fn decode_frame<D: RecordDecoder>(
bytes: &[u8],
pos: &mut usize,
decoder: &D,
) -> Result<WalOp> {
let (code, body) = decoder.with_unit(|| {
let code = decode_varint_u64(bytes, pos)?;
let body = decode_len_body(bytes, pos)?;
Ok((code, body))
})?;
decoder.decode_body(code, body)
}