use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::error::{MnemoError, Result};
use crate::format::PAGE_SIZE;
const FRAME_MAGIC: &[u8; 4] = b"MWAL";
const FRAME_HEADER: usize = 4 + 1 + 8 + 8 + 4 + 4; const KIND_DATA: u8 = 1;
const KIND_COMMIT: u8 = 2;
pub type Frame = (u64, Vec<u8>);
pub fn txn_byte_len(n: usize, page_size: usize) -> u64 {
((n * (FRAME_HEADER + page_size)) + FRAME_HEADER) as u64
}
fn crc32(seed: u32, data: &[u8]) -> u32 {
let mut c = !seed;
for &b in data {
c ^= b as u32;
for _ in 0..8 {
let mask = (c & 1).wrapping_neg();
c = (c >> 1) ^ (0xEDB8_8320 & mask);
}
}
!c
}
pub fn checksum(data: &[u8]) -> u32 {
crc32(0, data)
}
fn put_u32(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn put_u64(buf: &mut Vec<u8>, v: u64) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn rd_u32(b: &[u8], o: usize) -> u32 {
u32::from_le_bytes(b[o..o + 4].try_into().unwrap())
}
fn rd_u64(b: &[u8], o: usize) -> u64 {
u64::from_le_bytes(b[o..o + 8].try_into().unwrap())
}
pub fn commit(
file: &mut File,
wal_start: u64,
wal_pages: u64,
txn_id: u64,
frames: &[Frame],
) -> Result<()> {
let capacity = wal_pages * PAGE_SIZE as u64;
let need = txn_byte_len(frames.len(), PAGE_SIZE);
if need > capacity {
return Err(MnemoError::Invalid(format!(
"transaction needs {need} WAL bytes but the region holds {capacity}"
)));
}
let mut buf: Vec<u8> = Vec::with_capacity(need as usize);
let mut running = 0u32; for (page_no, payload) in frames {
buf.extend_from_slice(FRAME_MAGIC);
buf.push(KIND_DATA);
put_u64(&mut buf, txn_id);
put_u64(&mut buf, *page_no);
put_u32(&mut buf, payload.len() as u32);
let fc = checksum(payload);
put_u32(&mut buf, fc);
buf.extend_from_slice(payload);
running = crc32(running, payload);
}
buf.extend_from_slice(FRAME_MAGIC);
buf.push(KIND_COMMIT);
put_u64(&mut buf, txn_id);
put_u64(&mut buf, 0);
put_u32(&mut buf, 0);
put_u32(&mut buf, running);
file.seek(SeekFrom::Start(wal_start * PAGE_SIZE as u64))?;
file.write_all(&buf)?;
file.sync_all()?;
Ok(())
}
pub fn recover(
file: &mut File,
wal_start: u64,
wal_pages: u64,
wal_seq: u64,
) -> Result<Option<Vec<Frame>>> {
if wal_pages == 0 {
return Ok(None);
}
let capacity = (wal_pages * PAGE_SIZE as u64) as usize;
let mut region = vec![0u8; capacity];
file.seek(SeekFrom::Start(wal_start * PAGE_SIZE as u64))?;
let mut filled = 0usize;
while filled < capacity {
match file.read(&mut region[filled..])? {
0 => break,
n => filled += n,
}
}
let mut off = 0usize;
let mut frames: Vec<Frame> = Vec::new();
let mut running = 0u32;
let mut txn: Option<u64> = None;
while off + FRAME_HEADER <= filled {
if ®ion[off..off + 4] != FRAME_MAGIC {
break; }
let kind = region[off + 4];
let id = rd_u64(®ion, off + 5);
let page_no = rd_u64(®ion, off + 13);
let len = rd_u32(®ion, off + 21) as usize;
let crc = rd_u32(®ion, off + 25);
let body = off + FRAME_HEADER;
match txn {
None => txn = Some(id),
Some(t) if t == id => {}
Some(_) => break, }
match kind {
KIND_DATA => {
if body + len > filled {
break; }
let payload = ®ion[body..body + len];
if checksum(payload) != crc {
break; }
running = crc32(running, payload);
frames.push((page_no, payload.to_vec()));
off = body + len;
}
KIND_COMMIT => {
if crc != running {
break;
}
let id = txn.unwrap_or(0);
if id > wal_seq && !frames.is_empty() {
return Ok(Some(frames));
}
return Ok(None); }
_ => break,
}
}
Ok(None)
}