use std::borrow::Cow;
use wasm_dbms_api::prelude::{
DEFAULT_ALIGNMENT, DataSize, Encode, MSize, MemoryError, MemoryResult, Page, PageOffset,
};
const HEADER_SIZE: u64 = 4;
const ENTRY_SIZE: u64 = 4;
pub const UNCLAIMED_PAGES_CAPACITY: u32 = {
let max_bytes = MSize::MAX as u64;
let entries = (max_bytes - HEADER_SIZE) / ENTRY_SIZE;
entries as u32
};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct UnclaimedPages {
pages: Vec<Page>,
}
impl UnclaimedPages {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.pages.len()
}
pub fn remaining_capacity(&self) -> u32 {
UNCLAIMED_PAGES_CAPACITY - (self.pages.len() as u32)
}
pub fn is_empty(&self) -> bool {
self.pages.is_empty()
}
pub fn pop(&mut self) -> Option<Page> {
self.pages.pop()
}
pub fn push(&mut self, page: Page) -> MemoryResult<()> {
if self.pages.len() as u32 >= UNCLAIMED_PAGES_CAPACITY {
return Err(MemoryError::UnclaimedPagesFull {
capacity: UNCLAIMED_PAGES_CAPACITY,
});
}
self.pages.push(page);
Ok(())
}
pub fn as_slice(&self) -> &[Page] {
&self.pages
}
}
impl Encode for UnclaimedPages {
const SIZE: DataSize = DataSize::Dynamic;
const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
fn encode(&'_ self) -> Cow<'_, [u8]> {
let mut buf = Vec::with_capacity(self.size() as usize);
buf.extend_from_slice(&(self.pages.len() as u32).to_le_bytes());
for &page in &self.pages {
buf.extend_from_slice(&page.to_le_bytes());
}
Cow::Owned(buf)
}
fn decode(data: Cow<[u8]>) -> MemoryResult<Self>
where
Self: Sized,
{
if data.len() < HEADER_SIZE as usize {
return Ok(Self::default());
}
let count = u32::from_le_bytes(data[0..4].try_into()?) as usize;
if count > UNCLAIMED_PAGES_CAPACITY as usize {
return Err(MemoryError::UnclaimedPagesFull {
capacity: UNCLAIMED_PAGES_CAPACITY,
});
}
let mut pages = Vec::with_capacity(count);
let mut cursor = HEADER_SIZE as usize;
for _ in 0..count {
let page = Page::from_le_bytes(data[cursor..cursor + 4].try_into()?);
pages.push(page);
cursor += 4;
}
Ok(Self { pages })
}
fn size(&self) -> MSize {
(HEADER_SIZE as MSize) + (self.pages.len() as MSize) * (ENTRY_SIZE as MSize)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_be_empty_by_default() {
let ledger = UnclaimedPages::new();
assert!(ledger.is_empty());
assert_eq!(ledger.len(), 0);
}
#[test]
fn test_should_push_and_pop() {
let mut ledger = UnclaimedPages::new();
ledger.push(10).expect("push");
ledger.push(20).expect("push");
ledger.push(30).expect("push");
assert_eq!(ledger.len(), 3);
assert_eq!(ledger.pop(), Some(30));
assert_eq!(ledger.pop(), Some(20));
assert_eq!(ledger.pop(), Some(10));
assert_eq!(ledger.pop(), None);
}
#[test]
fn test_should_round_trip_encode_decode() {
let mut ledger = UnclaimedPages::new();
for page in [3u32, 5, 7, 11] {
ledger.push(page).expect("push");
}
let encoded = ledger.encode();
let decoded = UnclaimedPages::decode(encoded).expect("decode");
assert_eq!(ledger, decoded);
}
#[test]
fn test_should_decode_empty_buffer_as_empty_ledger() {
let buf = vec![0u8; 65536];
let ledger = UnclaimedPages::decode(Cow::Owned(buf)).expect("decode");
assert!(ledger.is_empty());
}
#[test]
fn test_should_reject_push_when_full() {
let mut ledger = UnclaimedPages {
pages: vec![0; UNCLAIMED_PAGES_CAPACITY as usize],
};
let err = ledger.push(42).expect_err("push at capacity");
assert!(matches!(err, MemoryError::UnclaimedPagesFull { .. }));
}
#[test]
fn test_size_matches_encoded_length() {
let mut ledger = UnclaimedPages::new();
ledger.push(1).expect("push");
ledger.push(2).expect("push");
let encoded = ledger.encode();
assert_eq!(ledger.size() as usize, encoded.len());
}
}