use crate::engine::io::{format_traits::Parseable, ConstEncodedSize};
use super::{ItemIdData, UInt12, UInt12Error};
use bytes::{Buf, BufMut};
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Debug, PartialEq)]
pub struct PageHeader {
pd_lower: UInt12,
pd_upper: UInt12,
}
impl PageHeader {
pub fn new() -> PageHeader {
PageHeader {
pd_lower: UInt12::new((PageHeader::encoded_size()) as u16).unwrap(),
pd_upper: UInt12::max(),
}
}
pub fn get_item_count(&self) -> usize {
let lower: usize = self.pd_lower.to_u16().into();
(lower - PageHeader::encoded_size()) / ItemIdData::encoded_size()
}
pub fn get_free_space(&self) -> usize {
if self.pd_upper < self.pd_lower {
return 0;
}
(self.pd_upper - self.pd_lower).to_u16() as usize + 1
}
pub fn can_fit(&self, row_size: usize) -> bool {
let needed = row_size + ItemIdData::encoded_size();
let have = self.get_free_space();
have >= needed
}
pub fn add_item(&mut self, row_size: usize) -> Result<ItemIdData, PageHeaderError> {
if !self.can_fit(row_size) {
return Err(PageHeaderError::InsufficentFreeSpace());
}
let row_u12 = UInt12::try_from(row_size)?;
self.pd_lower += UInt12::try_from(ItemIdData::encoded_size())?;
self.pd_upper -= row_u12;
let item_offset = self.pd_upper + UInt12::new(1).unwrap();
Ok(ItemIdData::new(item_offset, row_u12))
}
pub fn serialize(&self, buffer: &mut impl BufMut) {
UInt12::serialize_packed(buffer, &[self.pd_lower, self.pd_upper]);
}
}
impl Default for PageHeader {
fn default() -> Self {
Self::new()
}
}
impl ConstEncodedSize for PageHeader {
fn encoded_size() -> usize {
3
}
}
impl Parseable<PageHeaderError> for PageHeader {
type Output = Self;
fn parse(buffer: &mut impl Buf) -> Result<Self, PageHeaderError> {
let items = UInt12::parse_packed(buffer, 2)?;
Ok(PageHeader {
pd_lower: items[0],
pd_upper: items[1],
})
}
}
#[derive(Debug, Error)]
pub enum PageHeaderError {
#[error("Not enough space to add")]
InsufficentFreeSpace(),
#[error("Value for u12 too large")]
TooLarge(#[from] UInt12Error),
#[error("Not enough data has {0} bytes")]
InsufficentData(usize),
#[error("Lower offset is too large")]
LowerOffsetTooLarge(),
#[error("Upper offset is too large")]
UpperOffsetTooLarge(),
}
#[cfg(test)]
mod tests {
use bytes::BytesMut;
use crate::constants::PAGE_SIZE;
use super::*;
#[test]
fn sizes_match() -> Result<(), Box<dyn std::error::Error>> {
let mut test = PageHeader::new();
let calc_len = PageHeader::encoded_size();
let mut buffer = BytesMut::new();
test.serialize(&mut buffer);
assert_eq!(calc_len, buffer.freeze().len());
Ok(())
}
#[test]
fn test_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
let test = PageHeader::new();
let mut buffer = BytesMut::new();
test.serialize(&mut buffer);
let test_rt = PageHeader::parse(&mut buffer)?;
let test_new = PageHeader::new();
assert_eq!(test_rt, test_new);
Ok(())
}
#[test]
fn test_initial_freespace() {
let test = PageHeader::new();
let default_free_space: usize = PAGE_SIZE as usize - PageHeader::encoded_size();
let found_free_space = test.get_free_space();
assert_eq!(found_free_space, default_free_space);
}
#[test]
fn test_item_count() {
let mut test = PageHeader::new();
test.add_item(5).unwrap();
test.add_item(5).unwrap();
assert_eq!(test.get_item_count(), 2);
let remain_free = PAGE_SIZE as usize - PageHeader::encoded_size() - (ItemIdData::encoded_size() * 2) - 10; assert_eq!(test.get_free_space(), remain_free)
}
#[test]
fn test_too_big() -> Result<(), Box<dyn std::error::Error>> {
let mut test = PageHeader::new();
let needed = PAGE_SIZE as usize - PageHeader::encoded_size() - ItemIdData::encoded_size();
test.add_item(needed)?;
assert_eq!(test.get_item_count(), 1); assert_eq!(test.get_free_space(), 0); assert!(!test.can_fit(1)); assert!(test.add_item(0).is_err());
Ok(())
}
}