use super::*;
impl Page {
pub fn new(page_type: PageType, page_id: u32) -> Self {
let mut page = Self {
data: [0u8; PAGE_SIZE],
};
let header = PageHeader::new(page_type, page_id);
page.set_header(&header);
page
}
pub fn from_bytes(data: [u8; PAGE_SIZE]) -> Self {
Self { data }
}
pub fn from_slice(slice: &[u8]) -> Result<Self, PageError> {
if slice.len() != PAGE_SIZE {
return Err(PageError::InvalidSize(slice.len()));
}
let mut data = [0u8; PAGE_SIZE];
data.copy_from_slice(slice);
Ok(Self { data })
}
#[inline]
pub fn as_bytes(&self) -> &[u8; PAGE_SIZE] {
&self.data
}
#[inline]
pub fn as_bytes_mut(&mut self) -> &mut [u8; PAGE_SIZE] {
&mut self.data
}
pub fn header(&self) -> Result<PageHeader, PageError> {
let header_bytes: [u8; HEADER_SIZE] = self.data[..HEADER_SIZE]
.try_into()
.expect("header size mismatch");
PageHeader::from_bytes(&header_bytes)
}
pub fn set_header(&mut self, header: &PageHeader) {
let bytes = header.to_bytes();
self.data[..HEADER_SIZE].copy_from_slice(&bytes);
}
pub fn page_type(&self) -> Result<PageType, PageError> {
let page_type = reddb_file::paged_page_type(&self.data);
PageType::from_u8(page_type).ok_or(PageError::InvalidPageType(page_type))
}
pub fn page_id(&self) -> u32 {
reddb_file::paged_page_id(&self.data)
}
pub fn lsn(&self) -> u64 {
reddb_file::paged_page_lsn(&self.data)
}
pub fn set_lsn(&mut self, lsn: u64) {
reddb_file::set_paged_page_lsn(&mut self.data, lsn);
}
pub fn cell_count(&self) -> u16 {
reddb_file::paged_page_cell_count(&self.data)
}
pub fn set_cell_count(&mut self, count: u16) {
reddb_file::set_paged_page_cell_count(&mut self.data, count);
}
pub fn parent_id(&self) -> u32 {
reddb_file::paged_page_parent_id(&self.data)
}
pub fn set_parent_id(&mut self, parent_id: u32) {
reddb_file::set_paged_page_parent_id(&mut self.data, parent_id);
}
pub fn right_child(&self) -> u32 {
reddb_file::paged_page_right_child(&self.data)
}
pub fn set_right_child(&mut self, child_id: u32) {
reddb_file::set_paged_page_right_child(&mut self.data, child_id);
}
pub fn free_start(&self) -> u16 {
reddb_file::paged_page_free_start(&self.data)
}
pub fn set_free_start(&mut self, offset: u16) {
reddb_file::set_paged_page_free_start(&mut self.data, offset);
}
pub fn free_end(&self) -> u16 {
reddb_file::paged_page_free_end(&self.data)
}
pub fn set_free_end(&mut self, offset: u16) {
reddb_file::set_paged_page_free_end(&mut self.data, offset);
}
#[inline]
pub fn content(&self) -> &[u8] {
&self.data[HEADER_SIZE..]
}
#[inline]
pub fn content_mut(&mut self) -> &mut [u8] {
&mut self.data[HEADER_SIZE..]
}
pub fn update_checksum(&mut self) {
reddb_file::clear_paged_page_checksum(&mut self.data);
let checksum = crc32(&self.data);
reddb_file::set_paged_page_checksum(&mut self.data, checksum);
}
pub fn verify_checksum(&self) -> Result<(), PageError> {
let stored = reddb_file::paged_page_checksum(&self.data);
let mut temp = self.data;
reddb_file::clear_paged_page_checksum(&mut temp);
let calculated = crc32(&temp);
if stored != calculated {
Err(PageError::ChecksumMismatch {
expected: stored,
actual: calculated,
})
} else {
Ok(())
}
}
pub fn get_cell_pointer(&self, index: usize) -> Result<u16, PageError> {
let count = self.cell_count() as usize;
if index >= count {
return Err(PageError::CellOutOfBounds(index));
}
reddb_file::paged_cell_pointer(&self.data, index).ok_or(PageError::CellOutOfBounds(index))
}
pub fn set_cell_pointer(&mut self, index: usize, pointer: u16) -> Result<(), PageError> {
if !reddb_file::paged_cell_pointer_is_valid(pointer) {
return Err(PageError::InvalidCellPointer(pointer));
}
if !reddb_file::set_paged_cell_pointer(&mut self.data, index, pointer) {
return Err(PageError::CellOutOfBounds(index));
}
Ok(())
}
pub fn get_cell(&self, index: usize) -> Result<&[u8], PageError> {
let pointer = self.get_cell_pointer(index)? as usize;
reddb_file::paged_cell_bytes(&self.data, pointer as u16)
.ok_or(PageError::InvalidCellPointer(pointer as u16))
}
pub fn insert_cell(&mut self, key: &[u8], value: &[u8]) -> Result<usize, PageError> {
let key_len = key.len();
let value_len = value.len();
if key_len > u16::MAX as usize {
return Err(PageError::OverflowRequired);
}
let cell_size =
reddb_file::paged_cell_len(key_len, value_len).ok_or(PageError::OverflowRequired)?;
if cell_size > CONTENT_SIZE - 2 {
return Err(PageError::OverflowRequired);
}
let mut header = self.header()?;
let space_needed = 2 + cell_size;
if header.free_space() < space_needed {
return Err(PageError::PageFull);
}
let cell_offset = header.free_end as usize - cell_size;
if !reddb_file::write_paged_cell(&mut self.data, cell_offset as u16, key, value) {
return Err(PageError::InvalidCellPointer(cell_offset as u16));
}
let cell_index = header.cell_count as usize;
if !reddb_file::set_paged_cell_pointer(&mut self.data, cell_index, cell_offset as u16) {
return Err(PageError::CellOutOfBounds(cell_index));
}
header.cell_count += 1;
header.free_start += 2;
header.free_end = cell_offset as u16;
header.set_flag(PageFlag::Dirty);
self.set_header(&header);
Ok(cell_index)
}
pub fn read_cell(&self, index: usize) -> Result<(Vec<u8>, Vec<u8>), PageError> {
let cell = self.get_cell(index)?;
let (key, value) =
reddb_file::paged_cell_key_value(cell).ok_or(PageError::InvalidCellPointer(0))?;
Ok((key.to_vec(), value.to_vec()))
}
pub fn search_key(&self, key: &[u8]) -> Result<usize, usize> {
let count = self.cell_count() as usize;
if count == 0 {
return Err(0);
}
let mut low = 0;
let mut high = count;
while low < high {
let mid = (low + high) / 2;
let (cell_key, _) = self.read_cell(mid).map_err(|_| mid)?;
match cell_key.as_slice().cmp(key) {
std::cmp::Ordering::Less => low = mid + 1,
std::cmp::Ordering::Greater => high = mid,
std::cmp::Ordering::Equal => return Ok(mid),
}
}
Err(low)
}
pub fn new_header_page(page_count: u32) -> Self {
let mut page = Self::new(PageType::Header, 0);
reddb_file::init_database_header_page(&mut page.data, page_count)
.expect("fixed-size page can hold database header");
page.update_checksum();
page
}
pub fn read_page_count(&self) -> u32 {
reddb_file::database_header_page_count(&self.data).expect("fixed-size page has page count")
}
pub fn write_page_count(&mut self, count: u32) {
reddb_file::set_database_header_page_count(&mut self.data, count)
.expect("fixed-size page has page count");
}
pub fn read_freelist_head(&self) -> u32 {
reddb_file::database_header_freelist_head(&self.data)
.expect("fixed-size page has freelist head")
}
pub fn write_freelist_head(&mut self, page_id: u32) {
reddb_file::set_database_header_freelist_head(&mut self.data, page_id)
.expect("fixed-size page has freelist head");
}
pub fn verify_header_page(&self) -> Result<(), PageError> {
if !reddb_file::database_header_magic_matches(&self.data) {
return Err(PageError::InvalidPageType(self.data[0]));
}
let stored_page_size = reddb_file::database_header_page_size(&self.data)
.map_err(|_| PageError::InvalidSize(self.data.len()))?
as usize;
if stored_page_size != PAGE_SIZE {
return Err(PageError::InvalidSize(stored_page_size));
}
Ok(())
}
}