use std::borrow::Cow;
use wasm_dbms_api::prelude::{DecodeError, Encode, MemoryError, MemoryResult, Page};
use crate::MemoryAccess;
const INITIAL_READ_SIZE: usize = 1_024;
pub(super) fn read_sized<D, MA>(
page: Page,
mm: &mut MA,
encoded_len: fn(&[u8]) -> Option<usize>,
) -> MemoryResult<D>
where
D: Encode,
MA: MemoryAccess,
{
let page_size = mm.page_size() as usize;
let mut buf = vec![0u8; INITIAL_READ_SIZE.min(page_size)];
loop {
mm.read_at_raw(page, 0, &mut buf)?;
match encoded_len(&buf) {
Some(len) if len <= buf.len() => {
buf.truncate(len);
return D::decode(Cow::Owned(buf));
}
Some(len) if len <= page_size => buf.resize(len, 0),
None if buf.len() < page_size => buf.resize((buf.len() * 4).min(page_size), 0),
Some(_) | None => return Err(MemoryError::DecodeError(DecodeError::TooShort)),
}
}
}
#[cfg(test)]
mod tests {
use wasm_dbms_api::prelude::{DEFAULT_ALIGNMENT, DataSize, MSize, PageOffset};
use super::*;
use crate::{HeapMemoryProvider, MemoryManager};
#[derive(Debug, Clone, PartialEq, Eq)]
struct Blob(Vec<u8>);
impl Blob {
fn encoded_len(prefix: &[u8]) -> Option<usize> {
let header = prefix.get(..2)?;
Some(2 + u16::from_le_bytes([header[0], header[1]]) as usize)
}
}
impl Encode for Blob {
const SIZE: DataSize = DataSize::Dynamic;
const ALIGNMENT: PageOffset = DEFAULT_ALIGNMENT;
fn encode(&'_ self) -> Cow<'_, [u8]> {
let mut bytes = (self.0.len() as u16).to_le_bytes().to_vec();
bytes.extend_from_slice(&self.0);
Cow::Owned(bytes)
}
fn decode(data: Cow<[u8]>) -> MemoryResult<Self> {
let len = u16::from_le_bytes([data[0], data[1]]) as usize;
assert_eq!(data.len(), 2 + len, "decode got extra bytes");
Ok(Self(data[2..].to_vec()))
}
fn size(&self) -> MSize {
2 + self.0.len() as MSize
}
}
fn page_with(blob: &Blob) -> (MemoryManager<HeapMemoryProvider>, Page) {
let mut mm = MemoryManager::init(HeapMemoryProvider::default());
let page = mm.claim_page().expect("claim page");
mm.write_at_raw(page, 0, &blob.encode())
.expect("write blob");
(mm, page)
}
#[test]
fn test_reads_value_shorter_than_first_read() {
let blob = Blob(vec![7; 10]);
let (mut mm, page) = page_with(&blob);
let read: Blob = read_sized(page, &mut mm, Blob::encoded_len).expect("read");
assert_eq!(read, blob);
}
#[test]
fn test_reads_value_longer_than_first_read() {
let blob = Blob(vec![9; 40_000]);
let (mut mm, page) = page_with(&blob);
let read: Blob = read_sized(page, &mut mm, Blob::encoded_len).expect("read");
assert_eq!(read, blob);
}
#[test]
fn test_grows_prefix_until_length_is_known() {
fn late_len(prefix: &[u8]) -> Option<usize> {
prefix.get(4_999).map(|_| 5_002)
}
let blob = Blob(vec![1; 5_000]);
let (mut mm, page) = page_with(&blob);
let read: Blob = read_sized(page, &mut mm, late_len).expect("read");
assert_eq!(read, blob);
}
#[test]
fn test_rejects_length_past_page_end() {
let (mut mm, page) = page_with(&Blob(vec![]));
let result: MemoryResult<Blob> = read_sized(page, &mut mm, |_| Some(70_000));
assert!(matches!(
result,
Err(MemoryError::DecodeError(DecodeError::TooShort))
));
}
#[test]
fn test_rejects_length_never_known() {
let (mut mm, page) = page_with(&Blob(vec![]));
let result: MemoryResult<Blob> = read_sized(page, &mut mm, |_| None);
assert!(matches!(
result,
Err(MemoryError::DecodeError(DecodeError::TooShort))
));
}
}