mod page_table;
use wasm_dbms_api::prelude::{Encode, MemoryResult, Page, PageOffset};
pub use self::page_table::PageRecord;
use self::page_table::PageTable;
use super::raw_record::RawRecord;
use crate::{MemoryAccess, align_up};
#[derive(Debug)]
pub struct PageLedger {
ledger_page: Page,
pages: PageTable,
}
impl PageLedger {
pub fn load(page: Page, mm: &mut impl MemoryAccess) -> MemoryResult<Self> {
Ok(Self {
pages: mm.read_at(page, 0)?,
ledger_page: page,
})
}
pub fn get_page_and_offset_for_record<R>(
&mut self,
record: &R,
mm: &mut impl MemoryAccess,
) -> MemoryResult<(Page, PageOffset)>
where
R: Encode,
{
let required_size = record.size() as u64;
let page_size = mm.page_size();
if required_size > page_size {
return Err(wasm_dbms_api::prelude::MemoryError::DataTooLarge {
page_size,
requested: required_size,
});
}
let next_page = self.pages.pages.iter().find(|page_record| {
let taken = page_size.saturating_sub(page_record.free);
taken + required_size <= page_size
});
if let Some(page_record) = next_page {
let offset = page_size.saturating_sub(page_record.free) as PageOffset;
return Ok((page_record.page, offset));
}
let new_page = mm.allocate_page()?;
self.pages.pages.push(PageRecord {
page: new_page,
free: page_size, });
Ok((new_page, 0))
}
pub fn commit<R>(
&mut self,
page: Page,
record: &R,
mm: &mut impl MemoryAccess,
) -> MemoryResult<()>
where
R: Encode,
{
if let Some(page_record) = self.pages.pages.iter_mut().find(|pr| pr.page == page) {
let record_size = record.size() as u64;
if page_record.free < record_size {
return Err(wasm_dbms_api::prelude::MemoryError::DataTooLarge {
page_size: page_record.free,
requested: record_size,
});
}
let padding = align_up::<RawRecord<R>>(record_size as usize);
let record_size = record_size + ((padding as u64).saturating_sub(record.size() as u64));
page_record.free = page_record.free.saturating_sub(record_size);
self.write(mm)?;
return Ok(());
}
Err(wasm_dbms_api::prelude::MemoryError::OutOfBounds)
}
pub fn pages(&self) -> &[PageRecord] {
&self.pages.pages
}
fn write(&self, mm: &mut impl MemoryAccess) -> MemoryResult<()> {
mm.write_at(self.ledger_page, 0, &self.pages)
}
}
#[cfg(test)]
mod tests {
use wasm_dbms_api::prelude::{DataSize, MSize, MemoryResult};
use super::super::raw_record::RAW_RECORD_HEADER_SIZE;
use super::page_table::PageRecord;
use super::*;
use crate::{HeapMemoryProvider, MemoryManager, MemoryProvider};
#[test]
fn test_should_store_pages_and_load_back() {
let mut mm = MemoryManager::init(HeapMemoryProvider::default());
let page = mm.allocate_page().unwrap();
let page_ledger = PageLedger {
pages: PageTable {
pages: vec![
PageRecord {
page: 10,
free: 100,
},
PageRecord {
page: 11,
free: 200,
},
PageRecord {
page: 12,
free: 300,
},
],
},
ledger_page: page,
};
page_ledger
.write(&mut mm)
.expect("failed to write page ledger");
let loaded_ledger = PageLedger::load(page, &mut mm).expect("failed to load page ledger");
assert_eq!(page_ledger.pages.pages, loaded_ledger.pages.pages);
}
#[test]
fn test_should_get_page_for_record() {
let mut mm = MemoryManager::init(HeapMemoryProvider::default());
let ledger_page = mm.allocate_page().expect("failed to allocate ledger page");
let mut page_ledger =
PageLedger::load(ledger_page, &mut mm).expect("failed to load page ledger");
assert!(page_ledger.pages.pages.is_empty());
let record = TestRecord { data: [1; 100] };
let (page, offset) = page_ledger
.get_page_and_offset_for_record(&record, &mut mm)
.expect("failed to get page for record");
assert_eq!(page_ledger.pages.pages.len(), 1);
assert_eq!((page_ledger.pages.pages[0].page, 0), (page, offset));
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE
);
page_ledger
.commit(page, &record, &mut mm)
.expect("failed to commit record allocation");
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE - 100 - RAW_RECORD_HEADER_SIZE as u64
);
let reloaded_ledger =
PageLedger::load(ledger_page, &mut mm).expect("failed to load page ledger");
assert_eq!(page_ledger.pages.pages, reloaded_ledger.pages.pages);
}
#[test]
fn test_should_get_page_with_offset() {
let mut mm = MemoryManager::init(HeapMemoryProvider::default());
let ledger_page = mm.allocate_page().expect("failed to allocate ledger page");
let mut page_ledger =
PageLedger::load(ledger_page, &mut mm).expect("failed to load page ledger");
assert!(page_ledger.pages.pages.is_empty());
let record = TestRecord { data: [1; 100] };
let (page, offset) = page_ledger
.get_page_and_offset_for_record(&record, &mut mm)
.expect("failed to get page for record");
assert_eq!(page_ledger.pages.pages.len(), 1);
assert_eq!((page_ledger.pages.pages[0].page, 0), (page, offset));
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE
);
page_ledger
.commit(page, &record, &mut mm)
.expect("failed to commit record allocation");
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE - 100 - RAW_RECORD_HEADER_SIZE as u64
);
let (page, offset) = page_ledger
.get_page_and_offset_for_record(&record, &mut mm)
.expect("failed to get page for record");
assert_eq!(page_ledger.pages.pages.len(), 1);
assert_eq!(
(
page_ledger.pages.pages[0].page,
100 + RAW_RECORD_HEADER_SIZE
),
(page, offset)
);
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE - 100 - RAW_RECORD_HEADER_SIZE as u64
);
}
#[test]
fn test_should_account_for_padding_on_commit() {
let mut mm = MemoryManager::init(HeapMemoryProvider::default());
let ledger_page = mm.allocate_page().expect("failed to allocate ledger page");
let mut page_ledger =
PageLedger::load(ledger_page, &mut mm).expect("failed to load page ledger");
assert!(page_ledger.pages.pages.is_empty());
let record = RecordWith32BytesPadding { data: [1; 100] };
let (page, offset) = page_ledger
.get_page_and_offset_for_record(&record, &mut mm)
.expect("failed to get page for record");
assert_eq!(page_ledger.pages.pages.len(), 1);
assert_eq!((page_ledger.pages.pages[0].page, 0), (page, offset));
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE
);
page_ledger
.commit(page, &record, &mut mm)
.expect("failed to commit record allocation");
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE - 128
);
page_ledger
.commit(page, &record, &mut mm)
.expect("failed to commit record allocation");
assert_eq!(
page_ledger.pages.pages[0].free,
HeapMemoryProvider::PAGE_SIZE - 128 - 128
);
}
#[derive(Debug, Clone)]
struct TestRecord {
data: [u8; 100],
}
impl Encode for TestRecord {
const SIZE: DataSize = DataSize::Fixed(100);
const ALIGNMENT: PageOffset = 100;
fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
std::borrow::Cow::Borrowed(&self.data)
}
fn decode(data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
where
Self: Sized,
{
let mut record = TestRecord { data: [0; 100] };
record.data.copy_from_slice(&data[0..100]);
Ok(record)
}
fn size(&self) -> MSize {
100
}
}
#[derive(Debug, Clone)]
struct RecordWith32BytesPadding {
data: [u8; 100],
}
impl Encode for RecordWith32BytesPadding {
const SIZE: DataSize = DataSize::Dynamic;
const ALIGNMENT: PageOffset = 32;
fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
std::borrow::Cow::Borrowed(&self.data)
}
fn decode(data: std::borrow::Cow<[u8]>) -> MemoryResult<Self>
where
Self: Sized,
{
let mut record = Self { data: [0; 100] };
record.data.copy_from_slice(&data[0..100]);
Ok(record)
}
fn size(&self) -> MSize {
100
}
}
}