use std::convert::TryInto;
pub use crate::storage::page::raw_page::PAGE_SIZE;
const HEADER_SIZE: usize = 8;
const SLOT_SIZE: usize = 4;
pub struct TablePage<T = [u8; PAGE_SIZE]> {
pub(crate) data: T,
}
impl TablePage<[u8; PAGE_SIZE]> {
pub fn new(page_id: u32) -> Self {
let mut page = Self {
data: [0u8; PAGE_SIZE],
};
page.set_page_id(page_id);
page.set_slot_count(0);
page.set_free_space_pointer(PAGE_SIZE as u16);
page
}
pub fn from_bytes(data: [u8; PAGE_SIZE]) -> Self {
Self { data }
}
}
impl<T> TablePage<T> {
pub fn new_view(data: T) -> Self {
Self { data }
}
}
impl<T: AsRef<[u8]>> TablePage<T> {
pub fn page_id(&self) -> u32 {
u32::from_le_bytes(self.data.as_ref()[0..4].try_into().unwrap())
}
pub fn slot_count(&self) -> u16 {
u16::from_le_bytes(self.data.as_ref()[4..6].try_into().unwrap())
}
fn free_space_pointer(&self) -> u16 {
u16::from_le_bytes(self.data.as_ref()[6..8].try_into().unwrap())
}
pub fn free_space(&self) -> usize {
let slot_array_end = HEADER_SIZE + self.slot_count() as usize * SLOT_SIZE;
self.free_space_pointer() as usize - slot_array_end
}
fn slot_entry_offset(slot_id: u16) -> usize {
HEADER_SIZE + slot_id as usize * SLOT_SIZE
}
fn read_slot(&self, slot_id: u16) -> Option<(u16, u16)> {
if slot_id >= self.slot_count() {
return None;
}
let off = Self::slot_entry_offset(slot_id);
let tuple_offset = u16::from_le_bytes(self.data.as_ref()[off..off + 2].try_into().unwrap());
let tuple_length =
u16::from_le_bytes(self.data.as_ref()[off + 2..off + 4].try_into().unwrap());
Some((tuple_offset, tuple_length))
}
pub fn get_tuple(&self, slot_id: u16) -> Option<&[u8]> {
let (offset, length) = self.read_slot(slot_id)?;
if length == 0 {
return None;
}
let offset = offset as usize;
let length = length as usize;
Some(&self.data.as_ref()[offset..offset + length])
}
pub fn as_bytes(&self) -> &[u8] {
self.data.as_ref()
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> TablePage<T> {
fn set_page_id(&mut self, page_id: u32) {
self.data.as_mut()[0..4].copy_from_slice(&page_id.to_le_bytes());
}
fn set_slot_count(&mut self, count: u16) {
self.data.as_mut()[4..6].copy_from_slice(&count.to_le_bytes());
}
fn set_free_space_pointer(&mut self, ptr: u16) {
self.data.as_mut()[6..8].copy_from_slice(&ptr.to_le_bytes());
}
fn write_slot(&mut self, slot_id: u16, tuple_offset: u16, tuple_length: u16) {
let off = Self::slot_entry_offset(slot_id);
self.data.as_mut()[off..off + 2].copy_from_slice(&tuple_offset.to_le_bytes());
self.data.as_mut()[off + 2..off + 4].copy_from_slice(&tuple_length.to_le_bytes());
}
pub fn insert_tuple(&mut self, tuple: &[u8]) -> Option<u16> {
let tuple_len = tuple.len();
if tuple_len > u16::MAX as usize {
return None;
}
let reusable_slot =
(0..self.slot_count()).find(|&id| matches!(self.read_slot(id), Some((_, 0))));
let needed = if reusable_slot.is_some() {
tuple_len
} else {
tuple_len + SLOT_SIZE
};
let slot_array_end = HEADER_SIZE + self.slot_count() as usize * SLOT_SIZE;
let fsp = self.free_space_pointer() as usize;
if fsp < slot_array_end || needed > fsp - slot_array_end {
return None;
}
let new_fsp = self.free_space_pointer() as usize - tuple_len;
self.data.as_mut()[new_fsp..new_fsp + tuple_len].copy_from_slice(tuple);
self.set_free_space_pointer(new_fsp as u16);
let slot_id = match reusable_slot {
Some(id) => id,
None => {
let id = self.slot_count();
self.set_slot_count(id + 1);
id
}
};
self.write_slot(slot_id, new_fsp as u16, tuple_len as u16);
Some(slot_id)
}
pub fn delete_tuple(&mut self, slot_id: u16) -> bool {
match self.read_slot(slot_id) {
Some((_, 0)) | None => false,
Some((offset, _)) => {
self.write_slot(slot_id, offset, 0);
true
}
}
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
self.data.as_mut()
}
}
impl<T: AsRef<[u8]>> AsRef<[u8]> for TablePage<T> {
fn as_ref(&self) -> &[u8] {
self.data.as_ref()
}
}
impl<T: AsMut<[u8]>> AsMut<[u8]> for TablePage<T> {
fn as_mut(&mut self) -> &mut [u8] {
self.data.as_mut()
}
}