use std::path::PathBuf;
pub const DEFAULT_PAGE_SIZE: usize = 8192;
pub type PageNum = u64;
#[derive(Debug, Clone)]
pub struct Frame {
pub page_num: PageNum,
pub pin_count: u32,
pub is_dirty: bool,
pub clock_ref: bool,
pub data: Vec<u8>,
pub numa_node: u32,
}
impl Frame {
pub fn new(page_num: PageNum, data: Vec<u8>) -> Self {
Self {
page_num,
pin_count: 0,
is_dirty: false,
clock_ref: true,
data,
numa_node: 0,
}
}
#[inline(always)]
pub fn pin(&mut self) {
self.pin_count += 1;
}
#[inline(always)]
pub fn unpin(&mut self) {
if self.pin_count > 0 {
self.pin_count -= 1;
}
}
#[inline(always)]
pub fn is_pinned(&self) -> bool {
self.pin_count > 0
}
#[inline(always)]
pub fn mark_dirty(&mut self) {
self.is_dirty = true;
}
}
#[derive(Debug, Clone)]
pub struct FileHandle {
pub path: PathBuf,
pub num_pages: u64,
pub page_size: usize,
pub free_space_manager: Option<std::sync::Arc<crate::free_space_manager::FreeSpaceManager>>,
}
impl FileHandle {
pub fn new(path: PathBuf, page_size: usize) -> Self {
let num_pages = if path.exists() {
let len = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
len / page_size as u64
} else {
0
};
Self {
path,
num_pages,
page_size,
free_space_manager: None,
}
}
pub fn with_free_space_manager(mut self, fsm: std::sync::Arc<crate::free_space_manager::FreeSpaceManager>) -> Self {
self.free_space_manager = Some(fsm);
self
}
#[inline(always)]
pub fn page_offset(&self, page_num: PageNum) -> u64 {
page_num * self.page_size as u64
}
#[cfg(not(target_arch = "wasm32"))]
pub fn read_page(&self, page_num: PageNum) -> std::io::Result<Vec<u8>> {
use std::io::{Read, Seek, SeekFrom};
let mut file = std::fs::File::open(&self.path)?;
let offset = self.page_offset(page_num);
file.seek(SeekFrom::Start(offset))?;
let mut buf = vec![0u8; self.page_size];
file.read_exact(&mut buf)?;
Ok(buf)
}
#[cfg(target_arch = "wasm32")]
pub fn read_page(&self, page_num: PageNum) -> std::io::Result<Vec<u8>> {
Ok(vec![0u8; self.page_size])
}
#[cfg(not(target_arch = "wasm32"))]
pub fn write_page(&self, page_num: PageNum, data: &[u8]) -> std::io::Result<()> {
use std::io::{Seek, SeekFrom, Write};
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(false)
.open(&self.path)?;
let offset = self.page_offset(page_num);
file.seek(SeekFrom::Start(offset))?;
file.write_all(data)?;
Ok(())
}
#[cfg(target_arch = "wasm32")]
pub fn write_page(&self, _page_num: PageNum, _data: &[u8]) -> std::io::Result<()> {
Ok(())
}
pub fn allocate_page(&mut self) -> PageNum {
if let Some(fsm) = &self.free_space_manager {
if let Some(range) = fsm.pop_free_pages(1) {
return range.start_page_idx;
}
}
let page_num = self.num_pages;
self.num_pages += 1;
page_num
}
pub fn free_page(&self, page_num: PageNum) {
if let Some(fsm) = &self.free_space_manager {
fsm.add_uncheckpointed_free_pages(crate::free_space_manager::PageRange::new(page_num, 1));
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn extend_file(&mut self, num_pages: u64) -> std::io::Result<PageNum> {
use std::io::Write;
let start_page = self.num_pages;
let extend_bytes = (num_pages as usize) * self.page_size;
let mut file = std::fs::OpenOptions::new().create(true).append(true).open(&self.path)?;
let zeros = vec![0u8; extend_bytes];
file.write_all(&zeros)?;
file.flush()?;
self.num_pages += num_pages;
Ok(start_page)
}
#[cfg(target_arch = "wasm32")]
pub fn extend_file(&mut self, num_pages: u64) -> std::io::Result<PageNum> {
let start_page = self.num_pages;
self.num_pages += num_pages;
Ok(start_page)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_pin_unpin() {
let mut frame = Frame::new(0, vec![0u8; DEFAULT_PAGE_SIZE]);
assert!(!frame.is_pinned());
assert_eq!(frame.pin_count, 0);
frame.pin();
assert!(frame.is_pinned());
assert_eq!(frame.pin_count, 1);
frame.unpin();
assert!(!frame.is_pinned());
}
#[test]
fn test_frame_dirty() {
let mut frame = Frame::new(1, vec![0u8; DEFAULT_PAGE_SIZE]);
assert!(!frame.is_dirty);
frame.mark_dirty();
assert!(frame.is_dirty);
}
#[test]
fn test_page_offset() {
let fh = FileHandle::new(PathBuf::from("test.db"), DEFAULT_PAGE_SIZE);
assert_eq!(fh.page_offset(0), 0);
assert_eq!(fh.page_offset(1), 8192);
assert_eq!(fh.page_offset(5), 40960);
}
#[test]
fn test_file_handle_fsm() {
let path = PathBuf::from("test_fsm.db");
let _ = std::fs::remove_file(&path);
let fsm = std::sync::Arc::new(crate::free_space_manager::FreeSpaceManager::new());
let mut fh = FileHandle::new(path.clone(), DEFAULT_PAGE_SIZE).with_free_space_manager(fsm.clone());
let p1 = fh.allocate_page(); let p2 = fh.allocate_page(); let p3 = fh.allocate_page(); assert_eq!(p1, 0);
assert_eq!(p2, 1);
assert_eq!(p3, 2);
assert_eq!(fh.num_pages, 3);
fh.free_page(p2);
fsm.finalize_checkpoint();
let p4 = fh.allocate_page();
assert_eq!(p4, 1);
assert_eq!(fh.num_pages, 3);
let p5 = fh.allocate_page();
assert_eq!(p5, 3);
assert_eq!(fh.num_pages, 4);
let _ = std::fs::remove_file(&path); }
}