use std::{
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::PathBuf,
};
use crate::storage::{
error::StorageError,
file::wal::Wal,
page::{TablePage, table_page::PAGE_SIZE},
};
pub struct HeapFile {
file: File,
path: PathBuf,
wal: Wal,
pub num_pages: u32,
}
impl HeapFile {
pub fn open(path: impl Into<PathBuf>) -> Result<Self, StorageError> {
let path = path.into();
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true) .open(&path)
.map_err(|e| StorageError::io(&path, e))?;
let file_len = file
.metadata()
.map_err(|e| StorageError::io(&path, e))?
.len();
let num_pages = (file_len / PAGE_SIZE as u64) as u32;
let wal = Wal::open(&path)?;
let mut me = Self {
file,
path,
wal,
num_pages,
};
me.recover()?;
Ok(me)
}
pub fn allocate_page(&mut self) -> Result<u32, StorageError> {
let page_id = self.num_pages;
let page = TablePage::new(page_id);
self.wal.log_page(page_id, &page)?;
self.seek_to(page_id)?;
self.file
.write_all(page.as_bytes())
.map_err(|e| StorageError::io(&self.path, e))?;
self.num_pages += 1;
Ok(page_id)
}
pub fn read_page(&mut self, page_id: u32) -> Result<TablePage, StorageError> {
if page_id >= self.num_pages {
return Err(StorageError::PageOutOfBounds {
page_id,
num_pages: self.num_pages,
});
}
self.seek_to(page_id)?;
let mut buf = [0u8; PAGE_SIZE];
self.file
.read_exact(&mut buf)
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(TablePage::from_bytes(buf))
}
pub fn write_page(&mut self, page_id: u32, page: &TablePage) -> Result<(), StorageError> {
if page_id >= self.num_pages {
return Err(StorageError::PageOutOfBounds {
page_id,
num_pages: self.num_pages,
});
}
self.wal.log_page(page_id, page)?;
self.seek_to(page_id)?;
self.file
.write_all(page.as_bytes())
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(())
}
fn seek_to(&mut self, page_id: u32) -> Result<(), StorageError> {
let offset = page_id as u64 * PAGE_SIZE as u64;
self.file
.seek(SeekFrom::Start(offset))
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(())
}
fn recover(&mut self) -> Result<(), StorageError> {
let records = self.wal.read_all_valid_records()?;
if records.is_empty() {
return Ok(());
}
for (page_id, data) in records {
self.write_page_cover(page_id, &TablePage::from_bytes(data))?;
}
self.file
.sync_all()
.map_err(|e| StorageError::io(&self.path, e))?;
self.wal.checkpoint()
}
fn write_page_cover(&mut self, page_id: u32, page: &TablePage) -> Result<(), StorageError> {
while self.num_pages <= page_id {
self.seek_to(self.num_pages)?;
let filter = TablePage::new(self.num_pages);
self.file
.write_all(filter.as_bytes())
.map_err(|e| StorageError::io(&self.path, e))?;
self.num_pages += 1;
}
self.seek_to(page_id)?;
self.file
.write_all(page.as_bytes())
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(())
}
pub fn checkpoint(&mut self) -> Result<(), StorageError> {
self.file
.sync_all()
.map_err(|e| StorageError::io(&self.path, e))?;
self.wal.checkpoint()
}
}