matdb 0.1.0

An experimental embedded SQL-like DBMS
Documentation
use anyhow::{bail, Result};

use crate::Db;

use super::encoding::{
    decode_page, Page, FREE_PAGES_IN_FOOTER_COUNT, FREE_PAGES_IN_FREELIST_COUNT,
};

pub enum FreeStyle {
    CanBeFreed,
    CantBeFreed,
    CanBeFreedNextTx,
}

impl Db {
    pub(super) fn can_be_freed(&mut self, page_number: u64) -> Result<FreeStyle> {
        Ok(match decode_page(&self.get_page(page_number)?)? {
            super::encoding::Page::Header | super::encoding::Page::Footer(..) => {
                FreeStyle::CantBeFreed
            }
            super::encoding::Page::BTreeLeaf(tx_id, ..)
            | super::encoding::Page::BTreeInterior(tx_id, ..)
            | super::encoding::Page::Overflow(tx_id, ..) => {
                if self.this_tx_id == tx_id {
                    FreeStyle::CanBeFreed
                } else {
                    FreeStyle::CantBeFreed
                }
            }
            super::encoding::Page::Freelist(..) => FreeStyle::CanBeFreedNextTx,
        })
    }

    fn maybe_append_freelist_page(&mut self) -> Result<()> {
        if self.free_list_total_len() >= FREE_PAGES_IN_FOOTER_COUNT + FREE_PAGES_IN_FREELIST_COUNT {
            let mut free_pages_to_write: Vec<u64> = Vec::new();

            for _ in 0..FREE_PAGES_IN_FREELIST_COUNT {
                free_pages_to_write.push(
                    self.next_free_list
                        .pop_first()
                        .or_else(|| self.free_list.pop_first())
                        .unwrap(),
                );
            }

            self.next_freelist_ptr =
                Some(self.append_freelist(self.next_freelist_ptr, free_pages_to_write)?);
        }

        Ok(())
    }

    /// - btree pages can be freed if they're in the current transaction
    /// - footers and headers can never be freed
    /// - overflow pages can be freed if they are created in the current transaction
    /// - freelist pages can be freed in the next transaction
    pub(super) fn free_page(&mut self, page_number: u64) -> Result<()> {
        match decode_page(&self.get_page(page_number)?)? {
            Page::Header | super::encoding::Page::Footer(..) => {}
            Page::BTreeLeaf(tx_id, ..)
            | Page::BTreeInterior(tx_id, ..)
            | Page::Overflow(tx_id, ..) => {
                if self.this_tx_id == tx_id {
                    self.free_list.insert(page_number);
                    self.maybe_append_freelist_page()?;
                }
            }
            Page::Freelist(..) => {
                self.next_free_list.insert(page_number);
                self.maybe_append_freelist_page()?;
            }
        }

        Ok(())
    }

    pub(super) fn free_overflow_pages(&mut self, first_page: u64) -> Result<()> {
        let mut page = Some(first_page);
        while let Some(page_no) = page {
            match decode_page(&self.get_page(page_no)?)? {
                Page::Overflow(tx_id, next, ..) => {
                    if tx_id != self.this_tx_id {
                        return Ok(());
                    } else {
                        self.free_list.insert(page_no);
                        self.maybe_append_freelist_page()?;
                        page = next;
                    }
                }
                _ => bail!("overflow page next-linked to a non-overflow page"),
            }
        }

        Ok(())
    }

    fn free_list_total_len(&self) -> usize {
        self.free_list.len() + self.next_free_list.len()
    }

    pub(super) fn transaction_finish_freelist_append(&mut self) -> Result<()> {
        if self.free_list_total_len() > FREE_PAGES_IN_FOOTER_COUNT {
            let list_count = self.free_list_total_len() - FREE_PAGES_IN_FOOTER_COUNT;

            let mut free_pages_to_write = Vec::new();
            for _ in 0..list_count {
                free_pages_to_write.push(
                    self.next_free_list
                        .pop_first()
                        .or_else(|| self.free_list.pop_first())
                        .unwrap(),
                );
            }

            self.next_freelist_ptr =
                Some(self.append_freelist(self.next_freelist_ptr, free_pages_to_write)?)
        }

        Ok(())
    }

    pub(super) fn load_next_freelist(&mut self) -> Result<()> {
        if let Some(next_freelist) = self.next_freelist_ptr {
            let Page::Freelist(next, freelist) = decode_page(&self.get_page(next_freelist)?)?
            else {
                unreachable!()
            };

            self.next_free_list.insert(next_freelist);
            self.maybe_append_freelist_page()?;

            self.next_freelist_ptr = next;
            for freepage in freelist {
                self.free_list.insert(freepage);
            }
        }

        Ok(())
    }
}