shardtree: Discard `REFERENCE` retention in leaf overwrites.
Also, disallow value-conflicted overwrites at the leaf level.
This commit is contained in:
parent
e55ff2d7f2
commit
ffc087424d
|
@ -7,6 +7,10 @@ and this project adheres to Rust's notion of
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `shardtree::tree::Tree::{is_leaf, map, try_map}`
|
||||||
|
- `shardtree::tree::LocatedTree::{map, try_map}`
|
||||||
|
|
||||||
## [0.3.1] - 2024-04-03
|
## [0.3.1] - 2024-04-03
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -302,10 +302,11 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
|
||||||
match (left, right) {
|
match (left, right) {
|
||||||
(Tree(Node::Nil), Tree(Node::Nil)) => Tree(Node::Nil),
|
(Tree(Node::Nil), Tree(Node::Nil)) => Tree(Node::Nil),
|
||||||
(Tree(Node::Leaf { value: lv }), Tree(Node::Leaf { value: rv }))
|
(Tree(Node::Leaf { value: lv }), Tree(Node::Leaf { value: rv }))
|
||||||
// we can prune right-hand leaves that are not marked; if a leaf
|
// we can prune right-hand leaves that are not marked or reference leaves; if a
|
||||||
// is a checkpoint then that information will be propagated to
|
// leaf is a checkpoint then that information will be propagated to the replacement
|
||||||
// the replacement leaf
|
// leaf
|
||||||
if lv.1 == RetentionFlags::EPHEMERAL && (rv.1 & RetentionFlags::MARKED) == RetentionFlags::EPHEMERAL =>
|
if lv.1 == RetentionFlags::EPHEMERAL &&
|
||||||
|
(rv.1 & (RetentionFlags::MARKED | RetentionFlags::REFERENCE)) == RetentionFlags::EPHEMERAL =>
|
||||||
{
|
{
|
||||||
Tree(
|
Tree(
|
||||||
Node::Leaf {
|
Node::Leaf {
|
||||||
|
@ -600,12 +601,45 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
|
||||||
|
|
||||||
match into {
|
match into {
|
||||||
Tree(Node::Nil) => Ok(replacement(None, subtree)),
|
Tree(Node::Nil) => Ok(replacement(None, subtree)),
|
||||||
Tree(Node::Leaf { value: (value, _) }) => {
|
Tree(Node::Leaf {
|
||||||
|
value: (value, retention),
|
||||||
|
}) => {
|
||||||
if root_addr == subtree.root_addr {
|
if root_addr == subtree.root_addr {
|
||||||
|
// The current leaf is at the location we wish to transplant the root
|
||||||
|
// of the subtree being inserted, so we either replace the leaf
|
||||||
|
// entirely with the subtree, or reannotate the root so as to avoid
|
||||||
|
// discarding the existing leaf value.
|
||||||
|
|
||||||
if is_complete {
|
if is_complete {
|
||||||
// It is safe to replace the existing root unannotated, because we
|
Ok((
|
||||||
// can always recompute the root from a complete subtree.
|
if subtree.root.is_leaf() {
|
||||||
Ok((subtree.root, vec![]))
|
// When replacing a leaf with a leaf, `REFERENCE` retention
|
||||||
|
// will be discarded unless both leaves have `REFERENCE`
|
||||||
|
// retention.
|
||||||
|
subtree
|
||||||
|
.root
|
||||||
|
.try_map::<(H, RetentionFlags), InsertionError, _>(
|
||||||
|
&|(v0, ret0)| {
|
||||||
|
if v0 == value {
|
||||||
|
let retention_result: RetentionFlags =
|
||||||
|
((*retention | *ret0)
|
||||||
|
- RetentionFlags::REFERENCE)
|
||||||
|
| (RetentionFlags::REFERENCE
|
||||||
|
& *retention
|
||||||
|
& *ret0);
|
||||||
|
Ok((value.clone(), retention_result))
|
||||||
|
} else {
|
||||||
|
Err(InsertionError::Conflict(root_addr))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
// It is safe to replace the existing root unannotated, because we
|
||||||
|
// can always recompute the root from a complete subtree.
|
||||||
|
subtree.root
|
||||||
|
},
|
||||||
|
vec![],
|
||||||
|
))
|
||||||
} else if subtree.root.node_value().iter().all(|v| v == &value) {
|
} else if subtree.root.node_value().iter().all(|v| v == &value) {
|
||||||
Ok((
|
Ok((
|
||||||
// at this point we statically know the root to be a parent
|
// at this point we statically know the root to be a parent
|
||||||
|
@ -932,7 +966,7 @@ mod tests {
|
||||||
|
|
||||||
use super::{LocatedPrunableTree, PrunableTree, RetentionFlags};
|
use super::{LocatedPrunableTree, PrunableTree, RetentionFlags};
|
||||||
use crate::{
|
use crate::{
|
||||||
error::QueryError,
|
error::{InsertionError, QueryError},
|
||||||
tree::{
|
tree::{
|
||||||
tests::{leaf, nil, parent},
|
tests::{leaf, nil, parent},
|
||||||
LocatedTree,
|
LocatedTree,
|
||||||
|
@ -1101,21 +1135,16 @@ mod tests {
|
||||||
root: parent(leaf(("a".to_string(), RetentionFlags::MARKED)), nil()),
|
root: parent(leaf(("a".to_string(), RetentionFlags::MARKED)), nil()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let conflict_addr = Address::from_parts(1.into(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
t.insert_subtree(
|
t.insert_subtree(
|
||||||
LocatedTree {
|
LocatedTree {
|
||||||
root_addr: Address::from_parts(1.into(), 2),
|
root_addr: conflict_addr,
|
||||||
root: leaf(("b".to_string(), RetentionFlags::EPHEMERAL)),
|
root: leaf(("b".to_string(), RetentionFlags::EPHEMERAL)),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
Ok((
|
Err(InsertionError::Conflict(conflict_addr)),
|
||||||
LocatedTree {
|
|
||||||
root_addr: Address::from_parts(2.into(), 1),
|
|
||||||
root: parent(leaf(("b".to_string(), RetentionFlags::EPHEMERAL)), nil()),
|
|
||||||
},
|
|
||||||
vec![],
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,11 @@ impl<A, V> Tree<A, V> {
|
||||||
Tree(self.0.reannotate(ann))
|
Tree(self.0.reannotate(ann))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` this is a [`Node::Leaf`], `false` otherwise.
|
||||||
|
pub fn is_leaf(&self) -> bool {
|
||||||
|
matches!(&self.0, Node::Leaf { .. })
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if no [`Node::Nil`] nodes are present in the tree, `false` otherwise.
|
/// Returns `true` if no [`Node::Nil`] nodes are present in the tree, `false` otherwise.
|
||||||
pub fn is_complete(&self) -> bool {
|
pub fn is_complete(&self) -> bool {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
|
@ -152,6 +157,41 @@ impl<A, V> Tree<A, V> {
|
||||||
Node::Nil => vec![root_addr],
|
Node::Nil => vec![root_addr],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies the provided function to each leaf of the tree and returns
|
||||||
|
/// a new tree having the same structure as the original.
|
||||||
|
pub fn map<B, F: Fn(&V) -> B>(&self, f: &F) -> Tree<A, B>
|
||||||
|
where
|
||||||
|
A: Clone,
|
||||||
|
{
|
||||||
|
match &self.0 {
|
||||||
|
Node::Parent { ann, left, right } => Tree(Node::Parent {
|
||||||
|
ann: ann.clone(),
|
||||||
|
left: Arc::new(left.map(f)),
|
||||||
|
right: Arc::new(right.map(f)),
|
||||||
|
}),
|
||||||
|
Node::Leaf { value } => Tree(Node::Leaf { value: f(value) }),
|
||||||
|
Node::Nil => Tree(Node::Nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the provided function to each leaf of the tree and returns
|
||||||
|
/// a new tree having the same structure as the original, or an error
|
||||||
|
/// if any transformation of the leaf fails.
|
||||||
|
pub fn try_map<B, E, F: Fn(&V) -> Result<B, E>>(&self, f: &F) -> Result<Tree<A, B>, E>
|
||||||
|
where
|
||||||
|
A: Clone,
|
||||||
|
{
|
||||||
|
Ok(Tree(match &self.0 {
|
||||||
|
Node::Parent { ann, left, right } => Node::Parent {
|
||||||
|
ann: ann.clone(),
|
||||||
|
left: Arc::new(left.try_map(f)?),
|
||||||
|
right: Arc::new(right.try_map(f)?),
|
||||||
|
},
|
||||||
|
Node::Leaf { value } => Node::Leaf { value: f(value)? },
|
||||||
|
Node::Nil => Node::Nil,
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A binary Merkle tree with its root at the given address.
|
/// A binary Merkle tree with its root at the given address.
|
||||||
|
@ -240,6 +280,31 @@ impl<A, V> LocatedTree<A, V> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies the provided function to each leaf of the tree and returns
|
||||||
|
/// a new tree having the same structure as the original.
|
||||||
|
pub fn map<B, F: Fn(&V) -> B>(&self, f: &F) -> LocatedTree<A, B>
|
||||||
|
where
|
||||||
|
A: Clone,
|
||||||
|
{
|
||||||
|
LocatedTree {
|
||||||
|
root_addr: self.root_addr,
|
||||||
|
root: self.root.map(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the provided function to each leaf of the tree and returns
|
||||||
|
/// a new tree having the same structure as the original, or an error
|
||||||
|
/// if any transformation of the leaf fails.
|
||||||
|
pub fn try_map<B, E, F: Fn(&V) -> Result<B, E>>(&self, f: &F) -> Result<LocatedTree<A, B>, E>
|
||||||
|
where
|
||||||
|
A: Clone,
|
||||||
|
{
|
||||||
|
Ok(LocatedTree {
|
||||||
|
root_addr: self.root_addr,
|
||||||
|
root: self.root.try_map(f)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Default + Clone, V: Clone> LocatedTree<A, V> {
|
impl<A: Default + Clone, V: Clone> LocatedTree<A, V> {
|
||||||
|
|
Loading…
Reference in New Issue