use crate::storage::overflow::{OverflowChain, OverflowError};
use crate::storage::page_store::PageStore;
use reddb_file::vector_value_codec as value_codec;
use reddb_file::BTreeValueCell;
pub const OVERFLOW_THRESHOLD: usize = reddb_file::BTREE_VALUE_OVERFLOW_THRESHOLD;
pub const MAX_VALUE_SIZE: usize = reddb_file::BTREE_VALUE_MAX_SIZE;
pub const POINTER_CELL_LEN: usize = reddb_file::BTREE_VALUE_POINTER_CELL_LEN;
#[derive(Debug)]
pub enum ValueLayoutError {
ValueTooLarge(usize),
UnknownFlag(u8),
TruncatedPointer { got: usize },
Codec(value_codec::ValueCodecError),
Overflow(OverflowError),
}
impl std::fmt::Display for ValueLayoutError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ValueTooLarge(n) => {
write!(f, "value too large: {} bytes (max {})", n, MAX_VALUE_SIZE)
}
Self::UnknownFlag(b) => write!(f, "unknown leaf-cell flag byte: 0b{:08b}", b),
Self::TruncatedPointer { got } => {
write!(
f,
"pointer cell truncated: need {} bytes after flag, got {}",
POINTER_CELL_LEN - 1,
got
)
}
Self::Codec(e) => write!(f, "value codec: {}", e),
Self::Overflow(e) => write!(f, "overflow chain: {}", e),
}
}
}
impl std::error::Error for ValueLayoutError {}
impl From<value_codec::ValueCodecError> for ValueLayoutError {
fn from(e: value_codec::ValueCodecError) -> Self {
Self::Codec(e)
}
}
impl From<reddb_file::BTreeValueCellError> for ValueLayoutError {
fn from(e: reddb_file::BTreeValueCellError) -> Self {
match e {
reddb_file::BTreeValueCellError::UnknownFlag(flag) => Self::UnknownFlag(flag),
reddb_file::BTreeValueCellError::TruncatedPointer { got } => {
Self::TruncatedPointer { got }
}
}
}
}
impl From<OverflowError> for ValueLayoutError {
fn from(e: OverflowError) -> Self {
Self::Overflow(e)
}
}
pub fn encode<P: PageStore>(pager: &P, value: &[u8]) -> Result<Vec<u8>, ValueLayoutError> {
if value.len() > MAX_VALUE_SIZE {
return Err(ValueLayoutError::ValueTooLarge(value.len()));
}
if value.len() <= OVERFLOW_THRESHOLD {
return Ok(reddb_file::encode_btree_inline_raw(value));
}
let (codec_flag, codec_bytes) = value_codec::encode(value);
if codec_flag == value_codec::ValueFlag::Lz4 && codec_bytes.len() <= OVERFLOW_THRESHOLD {
return Ok(reddb_file::encode_btree_inline_compressed(&codec_bytes));
}
let is_compressed = codec_flag == value_codec::ValueFlag::Lz4;
let chain = OverflowChain::new(pager);
let (head, total_len) = chain.store(&codec_bytes)?;
Ok(reddb_file::encode_btree_pointer(
head,
total_len,
is_compressed,
))
}
pub fn decode<P: PageStore>(pager: &P, stored: &[u8]) -> Result<Vec<u8>, ValueLayoutError> {
match reddb_file::decode_btree_value_cell(stored)? {
BTreeValueCell::Pointer {
is_compressed,
head_page_id,
total_len,
} => {
let chain = OverflowChain::new(pager);
let chain_bytes = chain.read(head_page_id, total_len)?;
if is_compressed {
Ok(value_codec::decode(
value_codec::ValueFlag::Lz4,
&chain_bytes,
)?)
} else {
Ok(chain_bytes)
}
}
BTreeValueCell::Inline {
is_compressed,
payload,
} => Ok(reddb_file::decode_btree_inline_payload(
is_compressed,
payload,
)?),
}
}
pub fn pointer_head(stored: &[u8]) -> Option<u32> {
reddb_file::btree_value_pointer_head(stored)
}
#[inline]
#[allow(dead_code)]
pub fn projected_cell_len(input: &[u8]) -> usize {
let codec_len = value_codec::would_encode_to(input);
reddb_file::btree_projected_cell_len(input.len(), codec_len)
}