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(())
}
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(())
}
}