use super::allocation::AllocationError;
use super::codec::DecodedPageShape;
use super::persistent_btree::{self, Child, Format, Page, PageRef};
use super::tree::{
encode_tree_internal_children, encode_tree_leaf_entries, tree_page_decode_shape,
};
use super::{
CanonicalDecodeError, DecodeLimits, LogicalName, TreeEntry, TreePage, decode_tree_page,
};
use crate::foundation::FileId;
use crate::performance::{OperationFailure, WorkBudget, WorkCounters, WorkError};
use crate::storage::{ObjectId, ObjectKind, ObjectStoreError};
use thiserror::Error;
pub(crate) struct TreeFormat;
impl Format for TreeFormat {
type Key = LogicalName;
type Value = TreeEntry;
fn kind() -> ObjectKind {
ObjectKind::TreePage
}
fn key(value: &Self::Value) -> &Self::Key {
&value.name
}
fn decode(bytes: &[u8], limits: DecodeLimits) -> Result<Page<Self>, CanonicalDecodeError> {
Ok(match decode_tree_page(bytes, limits)? {
TreePage::Leaf(entries) => Page::Leaf(entries),
TreePage::Internal(children) => Page::Internal(
children
.into_iter()
.map(|child| Child {
first: child.first_name,
page: child.page,
})
.collect(),
),
})
}
fn decode_shape(
bytes: &[u8],
limits: DecodeLimits,
) -> Result<DecodedPageShape, CanonicalDecodeError> {
tree_page_decode_shape(bytes, limits)
}
fn encode(
page: &PageRef<'_, Self>,
maximum_items: u32,
) -> Result<Vec<u8>, CanonicalDecodeError> {
match page {
PageRef::Leaf(entries) => encode_tree_leaf_entries(entries, maximum_items),
PageRef::Internal(children) => encode_tree_internal_children(
children.iter().map(|child| (&child.first, child.page)),
maximum_items,
),
}
}
fn page_encoded_length(
page: &PageRef<'_, Self>,
maximum_items: u32,
) -> Result<usize, CanonicalDecodeError> {
let count = match page {
PageRef::Leaf(entries) => entries.len(),
PageRef::Internal(children) => children.len(),
};
if u32::try_from(count).unwrap_or(u32::MAX) > maximum_items {
return Err(CanonicalDecodeError::LengthOverflow);
}
let mut bytes = 8_usize + 2 + 1 + 4;
match page {
PageRef::Leaf(entries) => {
for entry in *entries {
bytes = bytes
.checked_add(Self::leaf_item_encoded_length(entry)?)
.ok_or(CanonicalDecodeError::LengthOverflow)?;
}
}
PageRef::Internal(children) => {
for child in *children {
bytes = bytes
.checked_add(Self::internal_item_encoded_length(&child.first)?)
.ok_or(CanonicalDecodeError::LengthOverflow)?;
}
}
}
Ok(bytes)
}
fn leaf_item_encoded_length(value: &Self::Value) -> Result<usize, CanonicalDecodeError> {
value
.name
.as_bytes()
.len()
.checked_add(1 + 4 + 16 + 1)
.ok_or(CanonicalDecodeError::LengthOverflow)
}
fn internal_item_encoded_length(key: &Self::Key) -> Result<usize, CanonicalDecodeError> {
key.as_bytes()
.len()
.checked_add(1 + 4 + 32)
.ok_or(CanonicalDecodeError::LengthOverflow)
}
fn key_nested_bytes(key: &Self::Key) -> u64 {
u64::try_from(key.as_bytes().len()).unwrap_or(u64::MAX)
}
fn value_nested_bytes(value: &Self::Value) -> u64 {
Self::key_nested_bytes(&value.name)
}
fn try_clone_key(
key: &Self::Key,
maximum_bytes: u32,
) -> Result<Self::Key, CanonicalDecodeError> {
let mut bytes = Vec::new();
bytes
.try_reserve_exact(key.as_bytes().len())
.map_err(|_| CanonicalDecodeError::AllocationFailed)?;
bytes.extend_from_slice(key.as_bytes());
LogicalName::new(key.encoding(), bytes, maximum_bytes)
.map_err(|error| CanonicalDecodeError::Invariant(error.to_string()))
}
fn try_clone_value(
value: &Self::Value,
maximum_bytes: u32,
) -> Result<Self::Value, CanonicalDecodeError> {
Ok(TreeEntry {
name: Self::try_clone_key(&value.name, maximum_bytes)?,
file_id: value.file_id,
kind: value.kind,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TreeMutation {
Insert(TreeEntry),
Remove {
name: LogicalName,
expected_file_id: Option<FileId>,
},
Replace {
entry: TreeEntry,
expected_file_id: FileId,
},
}
impl persistent_btree::Mutation<TreeFormat> for TreeMutation {
type Error = TreeSemanticError;
fn key(&self) -> &LogicalName {
match self {
Self::Insert(entry) | Self::Replace { entry, .. } => &entry.name,
Self::Remove { name, .. } => name,
}
}
fn changes_cardinality(&self) -> bool {
matches!(self, Self::Insert(_) | Self::Remove { .. })
}
fn apply_current(&self, current: &mut Option<TreeEntry>) -> Result<(), Self::Error> {
match self {
Self::Insert(_) if current.is_some() => Err(TreeSemanticError::AlreadyExists),
Self::Insert(entry) => {
*current = Some(entry.clone());
Ok(())
}
Self::Remove { .. } | Self::Replace { .. } if current.is_none() => {
Err(TreeSemanticError::Missing)
}
Self::Remove {
expected_file_id, ..
} => {
let existing = current.as_ref().ok_or(TreeSemanticError::Missing)?;
if expected_file_id.is_some_and(|expected| existing.file_id != expected) {
return Err(TreeSemanticError::FileIdentityConflict);
}
*current = None;
Ok(())
}
Self::Replace {
entry,
expected_file_id,
} => {
let existing = current.as_ref().ok_or(TreeSemanticError::Missing)?;
if existing.file_id != *expected_file_id {
return Err(TreeSemanticError::FileIdentityConflict);
}
*current = Some(entry.clone());
Ok(())
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TreeMutationReceipt {
pub root: ObjectId,
pub work: WorkCounters,
}
pub type TreeMutationFailure = OperationFailure<TreeMutationError>;
pub fn apply_tree_mutations<S: crate::ImmediateObjectStore>(
store: &S,
root: ObjectId,
mutations: Vec<TreeMutation>,
maximum_mutations: u32,
limits: DecodeLimits,
budget: WorkBudget,
) -> Result<TreeMutationReceipt, TreeMutationFailure> {
let receipt = persistent_btree::apply::<S, TreeFormat, TreeMutation>(
store,
root,
mutations,
maximum_mutations,
limits,
budget,
)
.map_err(|failure| OperationFailure::new(map_error(failure.error), *failure.work))?;
Ok(TreeMutationReceipt {
root: receipt.root,
work: receipt.work,
})
}
pub async fn apply_tree_mutations_async<S: crate::AsyncObjectStore>(
store: &S,
root: ObjectId,
mutations: Vec<TreeMutation>,
maximum_mutations: u32,
limits: DecodeLimits,
budget: WorkBudget,
cancellation: &crate::CancellationToken,
) -> Result<TreeMutationReceipt, TreeMutationFailure> {
let receipt = persistent_btree::apply_async::<S, TreeFormat, TreeMutation>(
store,
root,
mutations,
maximum_mutations,
limits,
budget,
cancellation,
)
.await
.map_err(|failure| OperationFailure::new(map_error(failure.error), *failure.work))?;
Ok(TreeMutationReceipt {
root: receipt.root,
work: receipt.work,
})
}
fn map_error(error: persistent_btree::Error<TreeSemanticError>) -> TreeMutationError {
match error {
persistent_btree::Error::Empty => TreeMutationError::Empty,
persistent_btree::Error::TooManyMutations => TreeMutationError::TooManyMutations,
persistent_btree::Error::WrongRootKind => TreeMutationError::WrongRootKind,
persistent_btree::Error::InvalidLimits => TreeMutationError::InvalidLimits,
persistent_btree::Error::PageItemTooLarge => TreeMutationError::PageItemTooLarge,
persistent_btree::Error::HeightExceeded => TreeMutationError::HeightExceeded,
persistent_btree::Error::CycleOrAlias => TreeMutationError::CycleOrAlias,
persistent_btree::Error::ChildBoundsMismatch => TreeMutationError::ChildBoundsMismatch,
persistent_btree::Error::MutationContract => TreeMutationError::MutationContract,
persistent_btree::Error::Allocation(AllocationError::Work(error)) => {
TreeMutationError::Work(error)
}
persistent_btree::Error::AllocationFailed | persistent_btree::Error::Allocation(_) => {
TreeMutationError::AllocationFailed
}
persistent_btree::Error::Semantic(error) => error.into(),
persistent_btree::Error::Storage(error) => TreeMutationError::Storage(error),
persistent_btree::Error::Decode(error) => TreeMutationError::Decode(error),
persistent_btree::Error::Work(error) => TreeMutationError::Work(error),
}
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub(crate) enum TreeSemanticError {
#[error("tree mutation name already exists")]
AlreadyExists,
#[error("tree mutation name is absent")]
Missing,
#[error("tree mutation file identity precondition failed")]
FileIdentityConflict,
}
#[derive(Debug, Error)]
pub enum TreeMutationError {
#[error("tree mutation batch is empty")]
Empty,
#[error("tree mutation count exceeds its admitted bound")]
TooManyMutations,
#[error("tree mutation root is not a tree page")]
WrongRootKind,
#[error("tree mutation limits are invalid")]
InvalidLimits,
#[error("tree mutation item exceeds its page byte bound")]
PageItemTooLarge,
#[error("tree mutation exceeds its admitted height")]
HeightExceeded,
#[error("tree mutation encountered a cycle or page alias")]
CycleOrAlias,
#[error("tree mutation child bounds mismatch")]
ChildBoundsMismatch,
#[error("tree mutation violated its physical-plan contract")]
MutationContract,
#[error("tree mutation scratch allocation failed")]
AllocationFailed,
#[error("tree mutation name already exists")]
AlreadyExists,
#[error("tree mutation name is absent")]
Missing,
#[error("tree mutation file identity precondition failed")]
FileIdentityConflict,
#[error(transparent)]
Storage(#[from] ObjectStoreError),
#[error(transparent)]
Decode(#[from] CanonicalDecodeError),
#[error(transparent)]
Work(#[from] WorkError),
}
impl From<TreeSemanticError> for TreeMutationError {
fn from(value: TreeSemanticError) -> Self {
match value {
TreeSemanticError::AlreadyExists => Self::AlreadyExists,
TreeSemanticError::Missing => Self::Missing,
TreeSemanticError::FileIdentityConflict => Self::FileIdentityConflict,
}
}
}
#[cfg(all(test, feature = "memory"))]
#[path = "tests/tree_mutation.rs"]
mod tests;