quill-sql 0.2.1

An educational Rust relational database (RDBMS) inspired by CMU 15445
Documentation
use crate::buffer::{PageId, INVALID_PAGE_ID, PAGE_SIZE};
use crate::error::{QuillSQLError, QuillSQLResult};
use crate::storage::codec::CommonCodec;
use std::sync::LazyLock;

pub static EMPTY_META_PAGE: MetaPage = MetaPage {
    major_version: 0,
    minor_version: 0,
    freelist_page_id: 0,
    information_schema_schemas_first_page_id: 0,
    information_schema_tables_first_page_id: 0,
    information_schema_columns_first_page_id: 0,
    information_schema_indexes_first_page_id: 0,
};

pub static META_PAGE_SIZE: LazyLock<usize> = LazyLock::new(|| PAGE_SIZE);

#[derive(Debug, Eq, PartialEq)]
pub struct MetaPage {
    pub major_version: u32,
    pub minor_version: u32,
    pub freelist_page_id: PageId,
    pub information_schema_schemas_first_page_id: PageId,
    pub information_schema_tables_first_page_id: PageId,
    pub information_schema_columns_first_page_id: PageId,
    pub information_schema_indexes_first_page_id: PageId,
}

impl MetaPage {
    pub fn try_new() -> QuillSQLResult<Self> {
        let version_str = env!("CARGO_PKG_VERSION");
        let version_arr = version_str.split('.').collect::<Vec<&str>>();
        if version_arr.len() < 2 {
            return Err(QuillSQLError::Storage(format!(
                "Package version is not xx.xx {}",
                version_str
            )));
        }
        let major_version = version_arr[0].parse::<u32>().map_err(|_| {
            QuillSQLError::Storage(format!("Failed to parse major version {}", version_arr[0]))
        })?;
        let minor_version = version_arr[1].parse::<u32>().map_err(|_| {
            QuillSQLError::Storage(format!("Failed to parse minor version {}", version_arr[1]))
        })?;

        Ok(Self {
            major_version,
            minor_version,
            freelist_page_id: INVALID_PAGE_ID,
            information_schema_schemas_first_page_id: INVALID_PAGE_ID,
            information_schema_tables_first_page_id: INVALID_PAGE_ID,
            information_schema_columns_first_page_id: INVALID_PAGE_ID,
            information_schema_indexes_first_page_id: INVALID_PAGE_ID,
        })
    }
}

pub fn encode_meta_page(page: &MetaPage) -> Vec<u8> {
    let mut bytes = Vec::with_capacity(PAGE_SIZE);
    bytes.extend(CommonCodec::encode_u32(page.major_version));
    bytes.extend(CommonCodec::encode_u32(page.minor_version));
    bytes.extend(CommonCodec::encode_u32(page.freelist_page_id));
    bytes.extend(CommonCodec::encode_u32(
        page.information_schema_schemas_first_page_id,
    ));
    bytes.extend(CommonCodec::encode_u32(
        page.information_schema_tables_first_page_id,
    ));
    bytes.extend(CommonCodec::encode_u32(
        page.information_schema_columns_first_page_id,
    ));
    bytes.extend(CommonCodec::encode_u32(
        page.information_schema_indexes_first_page_id,
    ));
    bytes.resize(PAGE_SIZE, 0);
    bytes
}

pub fn decode_meta_page(bytes: &[u8]) -> QuillSQLResult<(MetaPage, usize)> {
    if bytes.len() != PAGE_SIZE {
        return Err(QuillSQLError::Storage(format!(
            "Meta page size is not {} instead of {}",
            PAGE_SIZE,
            bytes.len()
        )));
    }

    let mut left_bytes = bytes;

    let (major_version, offset) = CommonCodec::decode_u32(left_bytes)?;
    left_bytes = &left_bytes[offset..];
    let (minor_version, offset) = CommonCodec::decode_u32(left_bytes)?;
    left_bytes = &left_bytes[offset..];
    let (freelist_page_id, offset) = CommonCodec::decode_u32(left_bytes)?;
    left_bytes = &left_bytes[offset..];
    let (information_schema_schemas_first_page_id, offset) = CommonCodec::decode_u32(left_bytes)?;
    left_bytes = &left_bytes[offset..];
    let (information_schema_tables_first_page_id, offset) = CommonCodec::decode_u32(left_bytes)?;
    left_bytes = &left_bytes[offset..];
    let (information_schema_columns_first_page_id, offset) = CommonCodec::decode_u32(left_bytes)?;
    left_bytes = &left_bytes[offset..];
    let (information_schema_indexes_first_page_id, offset) = CommonCodec::decode_u32(left_bytes)?;
    left_bytes = &left_bytes[offset..];

    Ok((
        MetaPage {
            major_version,
            minor_version,
            freelist_page_id,
            information_schema_schemas_first_page_id,
            information_schema_tables_first_page_id,
            information_schema_columns_first_page_id,
            information_schema_indexes_first_page_id,
        },
        PAGE_SIZE - left_bytes.len(),
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn meta_page_codec_roundtrip() {
        let page = MetaPage::try_new().unwrap();
        let (decoded, _) = decode_meta_page(&encode_meta_page(&page)).unwrap();
        assert_eq!(page, decoded);
    }
}