Files
ChuSQL/chusql-storage/src/page.rs
T

121 lines
3.8 KiB
Rust

use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::Path;
// 页管理:数据库文件按固定大小的页组织,页大小由配置给(默认 4096,见 config.rs)。
// 只负责"按页号读 / 写",不理解页里的内容 —— 内容怎么解释由上层(堆表 / B+ 树 / WAL)决定。
// 页号从 0 开始,第 n 页在文件中的偏移是 n * page_size。
/// 页号:从 0 开始。u64 足够大,不用担心中途用完。
pub type PageId = u64;
/// 一页:固定 page_size 字节,外加自己的页号。
#[derive(Debug, Clone)]
pub struct Page {
pub id: PageId,
pub data: Vec<u8>,
}
impl Page {
/// 新建一页,内容全 0。
pub fn new(id: PageId, size: usize) -> Self {
Page {
id,
data: vec![0u8; size],
}
}
/// 清零。复用时先清空,避免残留旧数据。
pub fn zero(&mut self) {
self.data.fill(0);
}
}
/// 页文件:负责把 Page 落到磁盘上(不做缓存,每次读写都直接 syscall;缓冲池是后面的阶段)。
#[derive(Debug)]
pub struct PageFile {
file: File,
page_size: usize,
}
impl PageFile {
/// 打开(或创建)一个页文件;页大小由配置传进来。
pub fn open<P: AsRef<Path>>(path: P, page_size: usize) -> io::Result<Self> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path)?;
Ok(PageFile { file, page_size })
}
/// 这个文件的页大小。
pub fn page_size(&self) -> usize {
self.page_size
}
/// 读取指定页号的页;页号超出文件末尾时返回 UnexpectedEof。
pub fn read_page(&mut self, id: PageId) -> io::Result<Page> {
let mut data = vec![0u8; self.page_size];
self.file.seek(SeekFrom::Start(id * self.page_size as u64))?;
self.file.read_exact(&mut data)?;
Ok(Page { id, data })
}
/// 写入一页。若页号超出当前文件末尾,文件会自动扩展。
pub fn write_page(&mut self, page: &Page) -> io::Result<()> {
if page.data.len() != self.page_size {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"page has {} bytes, this file uses {}",
page.data.len(),
self.page_size
),
));
}
self.file
.seek(SeekFrom::Start(page.id * self.page_size as u64))?;
self.file.write_all(&page.data)?;
self.file.flush()?;
Ok(())
}
/// 在文件末尾追加一页,返回新页的页号。
pub fn append_page(&mut self) -> io::Result<Page> {
let id = self.num_pages()?;
let page = Page::new(id, self.page_size);
self.write_page(&page)?;
Ok(page)
}
/// 当前文件里有多少页;长度不是页大小整数倍,说明页大小和文件对不上。
pub fn num_pages(&mut self) -> io::Result<PageId> {
let len = self.file.metadata()?.len();
let size = self.page_size as u64;
if len % size != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"file length {} is not a multiple of page size {} (did page.size change?)",
len, size
),
));
}
Ok(len / size)
}
/// 强制把文件缓冲刷到磁盘。写入关键路径时用。
pub fn sync(&mut self) -> io::Result<()> {
self.file.sync_all()
}
/// 清空文件,游标归零。
pub fn truncate(&mut self) -> io::Result<()> {
self.file.set_len(0)?;
self.file.seek(SeekFrom::Start(0))?;
Ok(())
}
}