#[cfg(test)]
use crate::error::Result;
use crate::storage::PageId;
#[derive(Debug, Clone, Default)]
pub struct FreeList {
pages: Vec<PageId>,
}
impl FreeList {
#[cfg(test)]
pub const METADATA_VERSION: u32 = 1;
pub fn new() -> Self {
Self { pages: Vec::new() }
}
pub fn pop_free_page(&mut self) -> Option<PageId> {
self.pages.pop()
}
pub fn push_free_page(&mut self, page_id: PageId) {
if !self.pages.contains(&page_id) {
self.pages.push(page_id);
}
}
pub fn as_slice(&self) -> &[PageId] {
&self.pages
}
pub fn replace(&mut self, free_pages: Vec<PageId>) {
self.pages = free_pages;
}
pub fn compact_trailing_pages(&mut self, next_page_id: &mut u32, minimum_next_page_id: u32) {
while *next_page_id > minimum_next_page_id {
let candidate = *next_page_id - 1;
if let Some(position) = self.pages.iter().position(|page_id| *page_id == candidate) {
self.pages.swap_remove(position);
*next_page_id -= 1;
} else {
break;
}
}
}
#[cfg(test)]
pub fn deserialize_metadata_lines(
version: u32,
expected_count: usize,
records: &[String],
) -> Result<Self> {
if version != Self::METADATA_VERSION {
return Err(crate::error::HematiteError::StorageError(format!(
"Unsupported freelist metadata version: expected {}, got {}",
Self::METADATA_VERSION,
version
)));
}
let mut pages = Vec::with_capacity(records.len());
for record in records {
let payload = record.strip_prefix("freelist|").ok_or_else(|| {
crate::error::HematiteError::StorageError(
"Invalid freelist metadata record prefix".to_string(),
)
})?;
let page_id = payload.parse::<u32>().map_err(|_| {
crate::error::HematiteError::StorageError(
"Invalid freelist page id metadata".to_string(),
)
})?;
if pages.contains(&page_id) {
return Err(crate::error::HematiteError::StorageError(format!(
"Duplicate freelist page id {} in metadata",
page_id
)));
}
pages.push(page_id);
}
if pages.len() != expected_count {
return Err(crate::error::HematiteError::StorageError(format!(
"Freelist metadata count mismatch: expected {}, got {}",
expected_count,
pages.len()
)));
}
Ok(Self { pages })
}
}