use std::{
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::{Path, PathBuf},
};
use crate::storage::{
error::StorageError,
page::{TablePage, table_page::PAGE_SIZE},
};
const WAL_MAGIC: u32 = 0x57414C31;
const RECORD_SIZE: usize = 4 + 4 + PAGE_SIZE + 4;
pub struct Wal {
file: File,
path: PathBuf,
}
impl Wal {
fn wal_path_for(data_path: &Path) -> PathBuf {
let mut os = data_path.as_os_str().to_owned();
os.push(".wal");
PathBuf::from(os)
}
pub fn open(data_path: &Path) -> Result<Self, StorageError> {
let path = Self::wal_path_for(data_path);
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.map_err(|e| StorageError::io(&path, e))?;
Ok(Self { file, path })
}
pub fn log_page(&mut self, page_id: u32, page: &TablePage) -> Result<(), StorageError> {
self.file
.seek(SeekFrom::End(0))
.map_err(|e| StorageError::io(&self.path, e))?;
let mut buf = Vec::with_capacity(RECORD_SIZE);
buf.extend_from_slice(&WAL_MAGIC.to_le_bytes());
buf.extend_from_slice(&page_id.to_le_bytes());
buf.extend_from_slice(page.as_bytes());
let checksum = fnv1a(&buf[4..]);
buf.extend_from_slice(&checksum.to_le_bytes());
self.file
.write_all(&buf)
.map_err(|e| StorageError::io(&self.path, e))?;
self.file
.sync_data()
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(())
}
pub fn read_all_valid_records(&mut self) -> Result<Vec<(u32, [u8; PAGE_SIZE])>, StorageError> {
self.file
.seek(SeekFrom::Start(0))
.map_err(|e| StorageError::io(&self.path, e))?;
let mut records = Vec::new();
let mut buf = vec![0u8; RECORD_SIZE];
loop {
if self.file.read_exact(&mut buf).is_err() {
break; }
let magic = u32::from_le_bytes(buf[0..4].try_into().unwrap());
if magic != WAL_MAGIC {
break;
}
let page_id = u32::from_le_bytes(buf[4..8].try_into().unwrap());
let data = &buf[8..8 + PAGE_SIZE];
let stored_checksum =
u32::from_le_bytes(buf[8 + PAGE_SIZE..RECORD_SIZE].try_into().unwrap());
let computed = fnv1a(&buf[4..8 + PAGE_SIZE]);
if computed != stored_checksum {
break; }
let mut page_data = [0u8; PAGE_SIZE];
page_data.copy_from_slice(data);
records.push((page_id, page_data));
}
Ok(records)
}
pub fn checkpoint(&mut self) -> Result<(), StorageError> {
self.file
.set_len(0)
.map_err(|e| StorageError::io(&self.path, e))?;
self.file
.seek(SeekFrom::Start(0))
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(())
}
}
fn fnv1a(data: &[u8]) -> u32 {
let mut hash: u32 = 0x811c9dc5;
for &b in data {
hash ^= b as u32;
hash = hash.wrapping_mul(0x01000193);
}
hash
}