feophantlib/engine/io/page_formats/
page_header.rs

1//! See https://www.postgresql.org/docs/current/storage-page-layout.html for reference documentation
2//! I'm only implementing enough for my needs until proven otherwise
3use crate::engine::io::{format_traits::Parseable, ConstEncodedSize};
4
5use super::{ItemIdData, UInt12, UInt12Error};
6use bytes::{Buf, BufMut};
7use std::convert::TryFrom;
8use thiserror::Error;
9
10#[derive(Debug, PartialEq)]
11pub struct PageHeader {
12    pd_lower: UInt12,
13    pd_upper: UInt12,
14}
15
16impl PageHeader {
17    pub fn new() -> PageHeader {
18        PageHeader {
19            pd_lower: UInt12::new((PageHeader::encoded_size()) as u16).unwrap(),
20            pd_upper: UInt12::max(),
21        }
22    }
23
24    pub fn get_item_count(&self) -> usize {
25        let lower: usize = self.pd_lower.to_u16().into();
26        (lower - PageHeader::encoded_size()) / ItemIdData::encoded_size()
27    }
28
29    pub fn get_free_space(&self) -> usize {
30        //Handle no free space
31        if self.pd_upper < self.pd_lower {
32            return 0;
33        }
34        (self.pd_upper - self.pd_lower).to_u16() as usize + 1
35    }
36
37    pub fn can_fit(&self, row_size: usize) -> bool {
38        let needed = row_size + ItemIdData::encoded_size();
39        let have = self.get_free_space();
40        have >= needed
41    }
42
43    pub fn add_item(&mut self, row_size: usize) -> Result<ItemIdData, PageHeaderError> {
44        if !self.can_fit(row_size) {
45            return Err(PageHeaderError::InsufficentFreeSpace());
46        }
47
48        let row_u12 = UInt12::try_from(row_size)?;
49
50        self.pd_lower += UInt12::try_from(ItemIdData::encoded_size())?;
51        self.pd_upper -= row_u12;
52
53        //Need to increment the offset by 1 since the pointer is now pointing a free space
54        let item_offset = self.pd_upper + UInt12::new(1).unwrap();
55
56        Ok(ItemIdData::new(item_offset, row_u12))
57    }
58
59    pub fn serialize(&self, buffer: &mut impl BufMut) {
60        UInt12::serialize_packed(buffer, &[self.pd_lower, self.pd_upper]);
61    }
62}
63
64impl Default for PageHeader {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl ConstEncodedSize for PageHeader {
71    fn encoded_size() -> usize {
72        3
73    }
74}
75
76impl Parseable<PageHeaderError> for PageHeader {
77    type Output = Self;
78    fn parse(buffer: &mut impl Buf) -> Result<Self, PageHeaderError> {
79        let items = UInt12::parse_packed(buffer, 2)?;
80        Ok(PageHeader {
81            pd_lower: items[0],
82            pd_upper: items[1],
83        })
84    }
85}
86
87#[derive(Debug, Error)]
88pub enum PageHeaderError {
89    #[error("Not enough space to add")]
90    InsufficentFreeSpace(),
91    #[error("Value for u12 too large")]
92    TooLarge(#[from] UInt12Error),
93    #[error("Not enough data has {0} bytes")]
94    InsufficentData(usize),
95    #[error("Lower offset is too large")]
96    LowerOffsetTooLarge(),
97    #[error("Upper offset is too large")]
98    UpperOffsetTooLarge(),
99}
100
101#[cfg(test)]
102mod tests {
103    use bytes::BytesMut;
104
105    use crate::constants::PAGE_SIZE;
106
107    use super::*;
108
109    #[test]
110    fn sizes_match() -> Result<(), Box<dyn std::error::Error>> {
111        let mut test = PageHeader::new();
112        let calc_len = PageHeader::encoded_size();
113
114        let mut buffer = BytesMut::new();
115        test.serialize(&mut buffer);
116
117        assert_eq!(calc_len, buffer.freeze().len());
118        Ok(())
119    }
120
121    #[test]
122    fn test_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
123        let test = PageHeader::new();
124        let mut buffer = BytesMut::new();
125        test.serialize(&mut buffer);
126        let test_rt = PageHeader::parse(&mut buffer)?;
127
128        let test_new = PageHeader::new();
129        assert_eq!(test_rt, test_new);
130
131        Ok(())
132    }
133
134    #[test]
135    fn test_initial_freespace() {
136        let test = PageHeader::new();
137
138        let default_free_space: usize = PAGE_SIZE as usize - PageHeader::encoded_size();
139        let found_free_space = test.get_free_space();
140        assert_eq!(found_free_space, default_free_space);
141    }
142
143    #[test]
144    fn test_item_count() {
145        let mut test = PageHeader::new();
146
147        test.add_item(5).unwrap();
148        test.add_item(5).unwrap();
149
150        assert_eq!(test.get_item_count(), 2);
151
152        let remain_free = PAGE_SIZE as usize //Initial
153            - PageHeader::encoded_size() //Header
154            - (ItemIdData::encoded_size() * 2) //Two items
155            - 10; //Their data
156        assert_eq!(test.get_free_space(), remain_free)
157    }
158
159    #[test]
160    fn test_too_big() -> Result<(), Box<dyn std::error::Error>> {
161        let mut test = PageHeader::new();
162
163        let needed = PAGE_SIZE as usize - PageHeader::encoded_size() - ItemIdData::encoded_size();
164        test.add_item(needed)?; //Should be maxed out
165
166        assert_eq!(test.get_item_count(), 1); //Should have an item
167        assert_eq!(test.get_free_space(), 0); //Should be full
168        assert!(!test.can_fit(1)); //Should not be able to store a tiny item
169        assert!(test.add_item(0).is_err()); //Adding more should fail
170
171        Ok(())
172    }
173}