use crate::config::TreeConfig;
use crate::diff::{
ConflictResolver, DiffResult, IgnoreConflictsResolver, MergeConflict, MergeResult,
};
use crate::digest::ValueDigest;
use crate::node::{Node, ProllyNode};
use crate::proof::Proof;
use crate::storage::NodeStorage;
use std::sync::Arc;
pub trait Tree<const N: usize, S: NodeStorage<N>> {
fn new(storage: S, config: TreeConfig<N>) -> Self;
fn insert(&mut self, key: Vec<u8>, value: Vec<u8>);
fn insert_batch(&mut self, keys: &[Vec<u8>], values: &[Vec<u8>]);
fn update(&mut self, key: Vec<u8>, value: Vec<u8>) -> bool;
fn delete(&mut self, key: &[u8]) -> bool;
fn delete_batch(&mut self, keys: &[Vec<u8>]);
fn find(&self, key: &[u8]) -> Option<ProllyNode<N>>;
fn traverse(&self) -> String;
fn formatted_traverse<F>(&self, formatter: F) -> String
where
F: Fn(&ProllyNode<N>) -> String;
fn get_root_hash(&self) -> Option<ValueDigest<N>>;
fn size(&self) -> usize;
fn depth(&self) -> usize;
fn summary(&self) -> String;
fn stats(&self) -> TreeStats;
fn load_config(storage: &S) -> Result<TreeConfig<N>, &'static str>;
fn save_config(&self) -> Result<(), &'static str>;
fn generate_proof(&self, key: &[u8]) -> Proof<N>;
fn verify(&self, proof: Proof<N>, key: &[u8], expected_value: Option<&[u8]>) -> bool;
fn diff(&self, other: &Self) -> Vec<DiffResult>;
fn print(&mut self);
fn print_proof(&self, key: &[u8]) -> bool;
fn merge(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
) -> Vec<MergeResult>;
fn apply_merge_results(
&self,
destination_root: &ValueDigest<N>,
merge_results: &[MergeResult],
) -> Result<Self, Vec<MergeConflict>>
where
Self: Sized;
fn merge_trees<R: ConflictResolver>(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
resolver: &R,
) -> Result<Self, Vec<MergeConflict>>
where
Self: Sized;
fn merge_trees_ignore_conflicts(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
) -> Result<Self, Vec<MergeConflict>>
where
Self: Sized,
{
self.merge_trees(
source_root,
destination_root,
base_root,
&IgnoreConflictsResolver,
)
}
}
pub struct TreeStats {
pub num_nodes: usize,
pub num_leaves: usize,
pub num_internal_nodes: usize,
pub avg_node_size: f64,
pub total_key_value_pairs: usize,
}
impl TreeStats {
pub fn new() -> Self {
TreeStats {
num_nodes: 0,
num_leaves: 0,
num_internal_nodes: 0,
avg_node_size: 0.0,
total_key_value_pairs: 0,
}
}
}
impl Default for TreeStats {
fn default() -> Self {
TreeStats::new()
}
}
#[derive(Debug)]
pub struct ProllyTree<const N: usize, S: NodeStorage<N>> {
pub root: ProllyNode<N>,
pub storage: S,
pub config: TreeConfig<N>,
}
impl<const N: usize, S: NodeStorage<N>> Tree<N, S> for ProllyTree<N, S> {
fn new(storage: S, config: TreeConfig<N>) -> Self {
let root = ProllyNode {
keys: Vec::new(),
key_schema: config.key_schema.clone(),
values: Vec::new(),
value_schema: config.value_schema.clone(),
is_leaf: true,
level: 0,
base: config.base,
modulus: config.modulus,
min_chunk_size: config.min_chunk_size,
max_chunk_size: config.max_chunk_size,
pattern: config.pattern,
split: false,
merged: false,
encode_types: Vec::new(),
encode_values: Vec::new(),
};
let root_hash = Some(root.get_hash());
let mut tree = ProllyTree {
root,
storage,
config,
};
tree.config.root_hash = root_hash;
tree
}
fn insert(&mut self, key: Vec<u8>, value: Vec<u8>) {
self.root.insert(key, value, &mut self.storage, Vec::new());
self.persist_root();
}
fn insert_batch(&mut self, keys: &[Vec<u8>], values: &[Vec<u8>]) {
self.root
.insert_batch(keys, values, &mut self.storage, Vec::new());
}
fn update(&mut self, key: Vec<u8>, value: Vec<u8>) -> bool {
if self.find(&key).is_some() {
self.insert(key, value);
true
} else {
false
}
}
fn delete(&mut self, key: &[u8]) -> bool {
let deleted = self.root.delete(key, &mut self.storage, Vec::new());
if deleted {
self.persist_root();
}
deleted
}
fn delete_batch(&mut self, keys: &[Vec<u8>]) {
self.root.delete_batch(keys, &mut self.storage, Vec::new());
}
fn find(&self, key: &[u8]) -> Option<ProllyNode<N>> {
self.root.find(key, &self.storage)
}
fn traverse(&self) -> String {
self.root.traverse(&self.storage)
}
fn formatted_traverse<F>(&self, formatter: F) -> String
where
F: Fn(&ProllyNode<N>) -> String,
{
self.root.formatted_traverse(&self.storage, formatter)
}
fn get_root_hash(&self) -> Option<ValueDigest<N>> {
Option::from(self.root.get_hash())
}
fn size(&self) -> usize {
fn count_pairs<const N: usize, S: NodeStorage<N>>(
node: &ProllyNode<N>,
storage: &S,
) -> usize {
if node.is_leaf {
node.keys.len()
} else {
let mut count = 0;
for value in &node.values {
if let Some(child_node) =
storage.get_node_by_hash(&ValueDigest::raw_hash(value))
{
count += count_pairs(&child_node, storage);
}
}
count
}
}
count_pairs(&self.root, &self.storage)
}
fn depth(&self) -> usize {
(self.root.level as usize) + 1
}
fn summary(&self) -> String {
let stats = self.stats();
format!(
"Tree Summary:\n- Number of Key-Value Pairs: {}\n- Number of Nodes: {}\n- Number of Leaves: {}\n- Number of Internal Nodes: {}\n- Average Leaf Node Size: {:.2}",
self.size(),
stats.num_nodes,
stats.num_leaves,
stats.num_internal_nodes,
stats.avg_node_size
)
}
fn stats(&self) -> TreeStats {
fn collect_stats<const N: usize, S: NodeStorage<N>>(
node: &ProllyNode<N>,
storage: &S,
stats: &mut TreeStats,
) {
stats.num_nodes += 1;
if node.is_leaf {
stats.num_leaves += 1;
stats.total_key_value_pairs += node.keys.len();
} else {
stats.num_internal_nodes += 1;
for value in &node.values {
if let Some(child_node) =
storage.get_node_by_hash(&ValueDigest::raw_hash(value))
{
collect_stats(&child_node, storage, stats);
}
}
}
}
let mut stats = TreeStats::new();
collect_stats(&self.root, &self.storage, &mut stats);
if stats.num_leaves > 0 {
stats.avg_node_size = stats.total_key_value_pairs as f64 / stats.num_leaves as f64;
}
stats
}
fn load_config(storage: &S) -> Result<TreeConfig<N>, &'static str> {
if let Some(config_data) = storage.get_config("tree_config") {
let config: TreeConfig<N> =
serde_json::from_slice(&config_data).map_err(|_| "Failed to deserialize config")?;
Ok(config)
} else {
Err("Config not found")
}
}
fn save_config(&self) -> Result<(), &'static str> {
let mut config = self.config.clone();
config.root_hash = Option::from(self.root.get_hash());
let config_data = serde_json::to_vec(&config).map_err(|_| "Failed to serialize config")?;
self.storage.save_config("tree_config", &config_data);
Ok(())
}
fn generate_proof(&self, key: &[u8]) -> Proof<N> {
fn generate_proof_recursive<const N: usize, S: NodeStorage<N>>(
node: &ProllyNode<N>,
key: &[u8],
storage: &S,
path: &mut Vec<ValueDigest<N>>,
) -> Option<ValueDigest<N>> {
path.push(node.get_hash());
if node.is_leaf {
if node.keys.iter().any(|k| k == key) {
Some(node.get_hash())
} else {
None
}
} else {
let i = node.keys.iter().rposition(|k| key >= &k[..]).unwrap_or(0);
let child_hash = node.values[i].clone();
if let Some(child_node) =
storage.get_node_by_hash(&ValueDigest::raw_hash(&child_hash))
{
generate_proof_recursive(&child_node, key, storage, path)
} else {
None
}
}
}
let mut path = Vec::new();
let target_hash = generate_proof_recursive(&self.root, key, &self.storage, &mut path);
Proof { path, target_hash }
}
fn verify(&self, proof: Proof<N>, key: &[u8], expected_value: Option<&[u8]>) -> bool {
let mut current_hash = self.root.get_hash();
for (i, node_hash) in proof.path.iter().enumerate() {
if let Some(node) = self.storage.get_node_by_hash(¤t_hash) {
if node.get_hash() != *node_hash {
return false;
}
if i == proof.path.len() - 1 {
return if node.is_leaf {
node.keys.iter().any(|k| k == key)
&& match expected_value {
None => true,
Some(ev) => node.values.iter().any(|v| ev == &v[..]),
}
} else {
false };
}
let child_index = node.keys.iter().rposition(|k| key >= &k[..]).unwrap_or(0);
current_hash = ValueDigest::raw_hash(&node.values[child_index]);
} else {
return false;
}
}
false }
fn diff(&self, other: &Self) -> Vec<DiffResult> {
let mut diffs = Vec::new();
self.diff_recursive(&self.root, &other.root, &mut diffs);
diffs
}
fn print(&mut self) {
self.root.print_tree(&self.storage);
}
fn print_proof(&self, key: &[u8]) -> bool {
let proof = self.generate_proof(key);
let is_valid = self.verify(proof.clone(), key, None);
#[cfg(feature = "tracing")]
tracing::debug!("root:");
#[cfg(not(feature = "tracing"))]
println!("root:");
self.root.print_tree_with_proof(&self.storage, &proof, key);
#[cfg(feature = "tracing")]
{
tracing::debug!("Proof for key {:?} is valid: {}", key, is_valid);
tracing::debug!("Proof: {:#?}", proof);
}
#[cfg(not(feature = "tracing"))]
{
println!("\nProof for key {key:?} is valid: {is_valid}");
println!("Proof: {proof:#?}");
}
is_valid
}
fn merge(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
) -> Vec<MergeResult> {
let source_tree = self.storage.get_node_by_hash(source_root);
let destination_tree = self.storage.get_node_by_hash(destination_root);
let base_tree = self.storage.get_node_by_hash(base_root);
let (source_tree, destination_tree, base_tree) =
match (source_tree, destination_tree, base_tree) {
(Some(s), Some(d), Some(b)) => (s, d, b),
_ => {
return vec![MergeResult::Conflict(MergeConflict {
key: b"<merge_error>".to_vec(),
base_value: None,
source_value: None,
destination_value: Some(b"Failed to load tree from storage".to_vec()),
})];
}
};
let mut base_to_source_diffs = Vec::new();
let mut base_to_destination_diffs = Vec::new();
self.diff_nodes_recursive(&base_tree, &source_tree, &mut base_to_source_diffs);
self.diff_nodes_recursive(
&base_tree,
&destination_tree,
&mut base_to_destination_diffs,
);
let mut source_changes: std::collections::HashMap<Vec<u8>, DiffResult> =
std::collections::HashMap::new();
let mut destination_changes: std::collections::HashMap<Vec<u8>, DiffResult> =
std::collections::HashMap::new();
for diff in base_to_source_diffs {
let key = match &diff {
DiffResult::Added(k, _) => k.clone(),
DiffResult::Removed(k, _) => k.clone(),
DiffResult::Modified(k, _, _) => k.clone(),
};
source_changes.insert(key, diff);
}
for diff in base_to_destination_diffs {
let key = match &diff {
DiffResult::Added(k, _) => k.clone(),
DiffResult::Removed(k, _) => k.clone(),
DiffResult::Modified(k, _, _) => k.clone(),
};
destination_changes.insert(key, diff);
}
let mut all_changed_keys = std::collections::HashSet::new();
for key in source_changes.keys() {
all_changed_keys.insert(key.clone());
}
for key in destination_changes.keys() {
all_changed_keys.insert(key.clone());
}
let mut merge_results = Vec::new();
for key in all_changed_keys {
let source_change = source_changes.get(&key);
let destination_change = destination_changes.get(&key);
match (source_change, destination_change) {
(Some(source_diff), None) => match source_diff {
DiffResult::Added(_, value) => {
merge_results.push(MergeResult::Added(key, value.clone()));
}
DiffResult::Removed(_, _) => {
merge_results.push(MergeResult::Removed(key));
}
DiffResult::Modified(_, _, new_value) => {
merge_results.push(MergeResult::Modified(key, new_value.clone()));
}
},
(None, Some(_)) => {
}
(Some(source_diff), Some(destination_diff)) => {
let conflict =
self.detect_conflict(&key, source_diff, destination_diff, &base_tree);
if let Some(conflict) = conflict {
merge_results.push(MergeResult::Conflict(conflict));
} else {
match source_diff {
DiffResult::Added(_, value) => {
merge_results.push(MergeResult::Added(key, value.clone()));
}
DiffResult::Removed(_, _) => {
merge_results.push(MergeResult::Removed(key));
}
DiffResult::Modified(_, _, new_value) => {
merge_results.push(MergeResult::Modified(key, new_value.clone()));
}
}
}
}
(None, None) => {}
}
}
merge_results
}
fn apply_merge_results(
&self,
destination_root: &ValueDigest<N>,
merge_results: &[MergeResult],
) -> Result<Self, Vec<MergeConflict>> {
let mut conflicts = Vec::new();
for result in merge_results {
if let MergeResult::Conflict(conflict) = result {
conflicts.push((*conflict).clone());
}
}
if !conflicts.is_empty() {
return Err(conflicts);
}
let destination_tree =
self.storage
.get_node_by_hash(destination_root)
.ok_or_else(|| {
vec![MergeConflict {
key: b"<apply_error>".to_vec(),
base_value: None,
source_value: None,
destination_value: Some(b"Failed to load destination tree".to_vec()),
}]
})?;
let mut new_tree = ProllyTree {
root: Arc::unwrap_or_clone(destination_tree),
storage: self.storage.clone(),
config: self.config.clone(),
};
for result in merge_results {
match result {
MergeResult::Added(key, value) => {
new_tree.insert(key.clone(), value.clone());
}
MergeResult::Modified(key, value) => {
new_tree.insert(key.clone(), value.clone()); }
MergeResult::Removed(key) => {
new_tree.delete(key);
}
MergeResult::Conflict(_) => {
unreachable!("Conflicts should have been filtered out");
}
}
}
Ok(new_tree)
}
fn merge_trees<R: ConflictResolver>(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
resolver: &R,
) -> Result<Self, Vec<MergeConflict>> {
let merge_results = self.merge(source_root, destination_root, base_root);
let mut resolved_results = Vec::new();
let mut unresolved_conflicts = Vec::new();
for result in merge_results {
match result {
MergeResult::Conflict(conflict) => {
if let Some(resolved_result) = resolver.resolve_conflict(&conflict) {
resolved_results.push(resolved_result);
} else {
unresolved_conflicts.push(conflict);
}
}
other => resolved_results.push(other),
}
}
if !unresolved_conflicts.is_empty() {
return Err(unresolved_conflicts);
}
self.apply_merge_results(destination_root, &resolved_results)
}
}
impl<const N: usize, S: NodeStorage<N>> ProllyTree<N, S> {
fn diff_nodes_recursive(
&self,
old_node: &ProllyNode<N>,
new_node: &ProllyNode<N>,
diffs: &mut Vec<DiffResult>,
) {
self.diff_recursive(old_node, new_node, diffs);
}
fn find_value_in_node(&self, node: &ProllyNode<N>, key: &[u8]) -> Option<Vec<u8>> {
if node.is_leaf {
for (i, k) in node.keys.iter().enumerate() {
if k.as_slice() == key {
return Some(node.values[i].clone());
}
}
} else {
return node.find(key, &self.storage).and_then(|found_node| {
found_node
.keys
.iter()
.zip(found_node.values.iter())
.find(|(k, _)| k.as_slice() == key)
.map(|(_, v)| v.clone())
});
}
None
}
fn detect_conflict(
&self,
key: &[u8],
source_diff: &DiffResult,
destination_diff: &DiffResult,
base_node: &ProllyNode<N>,
) -> Option<MergeConflict> {
let base_value = self.find_value_in_node(base_node, key);
match (source_diff, destination_diff) {
(DiffResult::Added(_, source_value), DiffResult::Added(_, destination_value)) => {
if source_value != destination_value {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
} else {
None }
}
(DiffResult::Removed(_, _), DiffResult::Removed(_, _)) => None,
(
DiffResult::Modified(_, _, source_value),
DiffResult::Modified(_, _, destination_value),
) => {
if source_value != destination_value {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
} else {
None }
}
(DiffResult::Added(_, source_value), DiffResult::Removed(_, _)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: None,
})
}
(DiffResult::Removed(_, _), DiffResult::Added(_, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: None,
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Added(_, source_value), DiffResult::Modified(_, _, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Modified(_, _, source_value), DiffResult::Added(_, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Removed(_, _), DiffResult::Modified(_, _, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: None,
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Modified(_, _, source_value), DiffResult::Removed(_, _)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: None,
})
}
}
}
fn diff_recursive(
&self,
old_node: &ProllyNode<N>,
new_node: &ProllyNode<N>,
diffs: &mut Vec<DiffResult>,
) {
let mut old_iter = old_node.keys.iter().zip(old_node.values.iter()).peekable();
let mut new_iter = new_node.keys.iter().zip(new_node.values.iter()).peekable();
while let (Some((old_key, old_value)), Some((new_key, new_value))) =
(old_iter.peek(), new_iter.peek())
{
match old_key.cmp(new_key) {
std::cmp::Ordering::Less => {
diffs.push(DiffResult::Removed(old_key.to_vec(), old_value.to_vec()));
old_iter.next();
}
std::cmp::Ordering::Greater => {
diffs.push(DiffResult::Added(new_key.to_vec(), new_value.to_vec()));
new_iter.next();
}
std::cmp::Ordering::Equal => {
if old_value != new_value {
diffs.push(DiffResult::Modified(
old_key.to_vec(),
old_value.to_vec(),
new_value.to_vec(),
));
}
old_iter.next();
new_iter.next();
}
}
}
for (old_key, old_value) in old_iter {
diffs.push(DiffResult::Removed(old_key.clone(), old_value.clone()));
}
for (new_key, new_value) in new_iter {
diffs.push(DiffResult::Added(new_key.clone(), new_value.clone()));
}
}
pub fn persist_root(&mut self) {
let root_hash = self.root.get_hash();
if self
.storage
.insert_node(root_hash.clone(), self.root.clone())
.is_ok()
{
self.config.root_hash = Some(root_hash);
let _ = self.save_config();
}
}
pub fn load_from_storage(storage: S, config: TreeConfig<N>) -> Option<Self> {
if let Some(ref root_hash) = config.root_hash {
if let Some(root_node) = storage.get_node_by_hash(root_hash) {
return Some(ProllyTree {
root: Arc::unwrap_or_clone(root_node),
storage,
config,
});
}
}
None
}
pub fn collect_keys(&self) -> Vec<Vec<u8>> {
let mut keys = Vec::new();
self.collect_keys_recursive(&self.root, &mut keys);
keys
}
fn collect_keys_recursive(&self, node: &ProllyNode<N>, keys: &mut Vec<Vec<u8>>) {
for key in &node.keys {
keys.push(key.clone());
}
for child_node in node.children(&self.storage) {
self.collect_keys_recursive(&child_node, keys);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::InMemoryNodeStorage;
#[test]
fn inmem_node_storage_test() {
let config = TreeConfig {
base: 131,
modulus: 1_000_000_009,
min_chunk_size: 4,
max_chunk_size: 8 * 1024,
pattern: 0b101,
root_hash: None,
key_schema: None,
value_schema: None,
encode_types: vec![],
};
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, config);
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
let traversal = tree.formatted_traverse(|node| {
let keys_as_strings: Vec<String> = node.keys.iter().map(|k| format!("{k:?}")).collect();
format!("[L{}: {}]", node.level, keys_as_strings.join(", "))
});
println!("Traversal: {traversal}");
tree.update(b"key1".to_vec(), b"new_value1".to_vec());
if let Some(node) = tree.find(b"key1") {
println!("Found key1 with value: {node:?}");
} else {
println!("key1 not found");
}
if tree.delete(b"key2") {
println!("key2 deleted");
} else {
println!("key2 not found");
}
println!("Size: {}", tree.size());
println!("Depth: {}", tree.depth());
println!("Summary: {}", tree.summary());
println!("{:?}", tree.root.print_tree(&tree.storage));
}
#[test]
fn file_node_storage_test() {
use crate::storage::FileNodeStorage;
use std::fs;
use std::path::PathBuf;
let config = TreeConfig {
base: 131,
modulus: 1_000_000_009,
min_chunk_size: 4,
max_chunk_size: 8 * 1024,
pattern: 0b101,
root_hash: None,
key_schema: None,
value_schema: None,
encode_types: vec![],
};
let storage_dir = PathBuf::from("/tmp/prolly_tree_storage");
let storage = FileNodeStorage::<32>::new(storage_dir.clone()).unwrap();
let mut tree = ProllyTree::new(storage, config);
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
let traversal = tree.formatted_traverse(|node| {
let keys_as_strings: Vec<String> = node.keys.iter().map(|k| format!("{k:?}")).collect();
format!("[L{}: {}]", node.level, keys_as_strings.join(", "))
});
println!("Traversal: {traversal}");
tree.update(b"key1".to_vec(), b"new_value1".to_vec());
if let Some(node) = tree.find(b"key1") {
println!("Found key1 with value: {node:?}");
} else {
println!("key1 not found");
}
if tree.delete(b"key2") {
println!("key2 deleted");
} else {
println!("key2 not found");
}
println!("Size: {}", tree.size());
println!("Depth: {}", tree.depth());
println!("Summary: {}", tree.summary());
println!("{:?}", tree.root.print_tree(&tree.storage));
fs::remove_dir_all(storage_dir).unwrap();
}
#[test]
fn test_insert_and_find() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
assert!(tree.find(b"key1").is_some());
assert!(tree.find(b"key2").is_some());
assert!(tree.find(b"key3").is_none());
}
#[test]
fn test_persist_and_load() {
let storage = InMemoryNodeStorage::<32>::default();
let config = TreeConfig::default();
let mut tree = ProllyTree::new(storage.clone(), config.clone());
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
tree.persist_root();
let loaded_tree = ProllyTree::load_from_storage(tree.storage, tree.config)
.expect("Should be able to load tree from storage");
assert!(loaded_tree.find(b"key1").is_some());
assert!(loaded_tree.find(b"key2").is_some());
assert!(loaded_tree.find(b"key3").is_none());
}
#[test]
fn test_insert_batch_and_find() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
let keys = vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()];
let values = vec![b"value1".to_vec(), b"value2".to_vec(), b"value3".to_vec()];
tree.insert_batch(&keys, &values);
assert!(tree.find(b"key1").is_some());
assert!(tree.find(b"key2").is_some());
assert!(tree.find(b"key3").is_some());
assert!(tree.find(b"key4").is_none());
}
#[test]
fn test_delete() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
assert!(tree.delete(b"key1"));
assert!(tree.find(b"key1").is_none());
assert!(tree.find(b"key2").is_some());
}
#[test]
fn test_delete_batch() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
let keys = vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()];
let values = vec![b"value1".to_vec(), b"value2".to_vec(), b"value3".to_vec()];
tree.insert_batch(&keys, &values);
assert!(tree.find(b"key1").is_some());
assert!(tree.find(b"key2").is_some());
assert!(tree.find(b"key3").is_some());
tree.delete_batch(&keys);
assert!(tree.find(b"key1").is_none());
assert!(tree.find(b"key2").is_none());
assert!(tree.find(b"key3").is_none());
}
#[test]
fn test_traverse() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
let key1 = b"key1".to_vec();
let key2 = b"key2".to_vec();
tree.insert(key1.clone(), b"value1".to_vec());
tree.insert(key2.clone(), b"value2".to_vec());
let traversal = tree.traverse();
let expected_key1 = format!("{key1:?}");
let expected_key2 = format!("{key2:?}");
assert!(traversal.contains(&expected_key1.to_string()));
assert!(traversal.contains(&expected_key2.to_string()));
}
#[test]
fn test_stats() {
let storage = InMemoryNodeStorage::<32>::default();
let config = TreeConfig {
base: 131,
modulus: 1_000_000_009,
min_chunk_size: 16,
max_chunk_size: 8 * 1024,
pattern: 0b111,
root_hash: None,
key_schema: None,
value_schema: None,
encode_types: vec![],
};
let mut tree = ProllyTree::new(storage, config);
let max_key = 3000u32;
for i in 0..max_key {
let key = i.to_be_bytes().to_vec();
let value = i.to_be_bytes().to_vec();
tree.insert(key.clone(), value.clone());
}
println!("{:?}", tree.root.print_tree(&tree.storage));
for i in 0..max_key {
let key = i.to_be_bytes().to_vec();
assert!(tree.find(&key).is_some());
}
let non_existing_key = (max_key + 10).to_be_bytes().to_vec();
assert!(tree.find(&non_existing_key).is_none());
assert_eq!(tree.size(), max_key as usize);
assert_eq!(tree.depth(), 3);
println!("Size: {}", tree.size());
println!("Depth: {}", tree.depth());
println!("Summary: {}", tree.summary());
}
#[test]
fn test_generate_proof() {
let config = TreeConfig::default();
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, config);
for i in 0..100 {
let key = vec![i];
let value = vec![i];
tree.insert(key.clone(), value.clone());
}
let key_to_prove = vec![5];
let proof = tree.generate_proof(&key_to_prove);
let verified = tree.verify(proof, &key_to_prove, Some(&key_to_prove));
assert!(verified);
let key_to_prove_wrong = vec![120];
let proof_wrong = tree.generate_proof(&key_to_prove_wrong);
let verified_wrong =
tree.verify(proof_wrong, &key_to_prove_wrong, Some(&key_to_prove_wrong));
assert!(!verified_wrong);
}
#[test]
fn test_diff() {
let config = TreeConfig::default();
let storage1 = InMemoryNodeStorage::<32>::default();
let mut tree1 = ProllyTree::new(storage1, config.clone());
let storage2 = InMemoryNodeStorage::<32>::default();
let mut tree2 = ProllyTree::new(storage2, config);
for i in 0..50 {
tree1.insert(vec![i], vec![i]);
}
for i in 0..50 {
tree2.insert(vec![i], vec![i]);
}
tree2.insert(vec![10], vec![200]);
println!("{:?}", tree1.root.print_tree(&tree1.storage));
println!("{:?}", tree2.root.print_tree(&tree2.storage));
let differences = tree1.diff(&tree2);
for diff in &differences {
match diff {
DiffResult::Added(key, value) => {
println!("Added: key = {key:?}, value = {value:?}");
}
DiffResult::Removed(key, value) => {
println!("Removed: key = {key:?}, value = {value:?}");
}
DiffResult::Modified(key, old_value, new_value) => {
println!(
"Modified: key = {key:?}, old_value = {old_value:?}, new_value = {new_value:?}"
);
}
}
}
}
#[test]
fn test_print_proof_demo() {
let config = TreeConfig::default();
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, config);
for i in 0..20 {
tree.insert(vec![i], vec![i * 10]);
}
println!("=== Prolly Tree with Proof Visualization Demo ===");
let existing_key = vec![10];
println!("\n--- Testing with existing key {:?} ---", existing_key);
let is_valid = tree.print_proof(&existing_key);
assert!(is_valid, "Proof should be valid for existing key");
let non_existing_key = vec![25];
println!(
"\n--- Testing with non-existing key {:?} ---",
non_existing_key
);
let is_valid = tree.print_proof(&non_existing_key);
assert!(!is_valid, "Proof should be invalid for non-existing key");
println!("\n=== Demo completed successfully ===");
}
#[test]
fn test_merge_simple() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 0);
}
#[test]
fn test_merge_with_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
base_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"source_value".to_vec());
source_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"dest_value".to_vec());
dest_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 1);
match &merge_results[0] {
MergeResult::Conflict(conflict) => {
assert_eq!(conflict.key, b"key1".to_vec());
assert_eq!(conflict.base_value, Some(b"value1".to_vec()));
assert_eq!(conflict.source_value, Some(b"source_value".to_vec()));
assert_eq!(conflict.destination_value, Some(b"dest_value".to_vec()));
}
_ => panic!("Expected conflict, got: {:?}", merge_results[0]),
}
}
#[test]
fn test_merge_to_empty_tree() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let base_tree = ProllyTree::new(storage.clone(), config.clone());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"value1".to_vec());
source_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let dest_tree = ProllyTree::new(storage.clone(), config.clone());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 2);
let mut keys_added = std::collections::HashSet::new();
for result in merge_results {
match result {
MergeResult::Added(key, _) => {
keys_added.insert(key);
}
_ => panic!("Expected only additions, got: {:?}", result),
}
}
assert!(keys_added.contains(&b"key1".to_vec()));
assert!(keys_added.contains(&b"key2".to_vec()));
}
#[test]
fn test_merge_add_remove_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"modified_value1".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 2);
let mut has_addition = false;
let mut has_conflict = false;
for result in merge_results {
match result {
MergeResult::Added(key, value) => {
assert_eq!(key, b"key2".to_vec());
assert_eq!(value, b"value2".to_vec());
has_addition = true;
}
MergeResult::Conflict(conflict) => {
assert_eq!(conflict.key, b"key1".to_vec());
assert_eq!(conflict.base_value, Some(b"value1".to_vec()));
assert_eq!(conflict.source_value, None); assert_eq!(
conflict.destination_value,
Some(b"modified_value1".to_vec())
);
has_conflict = true;
}
_ => panic!("Unexpected merge result: {:?}", result),
}
}
assert!(has_addition && has_conflict);
}
#[test]
fn test_merge_complex_scenario() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"modify_conflict".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"modify_conflict".to_vec(), b"source_value".to_vec()); source_tree.insert(b"new_in_source".to_vec(), b"source_addition".to_vec()); let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"modify_conflict".to_vec(), b"dest_value".to_vec()); let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
let mut added_keys = std::collections::HashSet::new();
let mut conflicts = std::collections::HashMap::new();
for result in merge_results {
match result {
MergeResult::Added(key, _) => {
added_keys.insert(key);
}
MergeResult::Conflict(conflict) => {
conflicts.insert(conflict.key.clone(), conflict);
}
_ => panic!("Unexpected merge result: {:?}", result),
}
}
assert!(added_keys.contains(&b"new_in_source".to_vec()));
assert!(conflicts.contains_key(&b"modify_conflict".to_vec()));
let conflict = &conflicts[&b"modify_conflict".to_vec()];
assert_eq!(conflict.source_value, Some(b"source_value".to_vec()));
assert_eq!(conflict.destination_value, Some(b"dest_value".to_vec()));
}
#[test]
fn test_merge_same_changes_no_conflict() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
base_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"same_new_value".to_vec()); source_tree.insert(b"key2".to_vec(), b"value2".to_vec()); source_tree.insert(b"key3".to_vec(), b"same_addition".to_vec()); let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"same_new_value".to_vec()); dest_tree.insert(b"key2".to_vec(), b"value2".to_vec()); dest_tree.insert(b"key3".to_vec(), b"same_addition".to_vec()); let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 2);
let mut has_modification = false;
let mut has_addition = false;
for result in merge_results {
match result {
MergeResult::Modified(key, value) => {
assert_eq!(key, b"key1".to_vec());
assert_eq!(value, b"same_new_value".to_vec());
has_modification = true;
}
MergeResult::Added(key, value) => {
assert_eq!(key, b"key3".to_vec());
assert_eq!(value, b"same_addition".to_vec());
has_addition = true;
}
_ => panic!("Unexpected merge result: {:?}", result),
}
}
assert!(has_modification && has_addition);
}
#[test]
fn test_apply_merge_results_success() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"existing".to_vec(), b"value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let merge_results = vec![
MergeResult::Added(b"new_key".to_vec(), b"new_value".to_vec()),
MergeResult::Modified(b"existing".to_vec(), b"modified_value".to_vec()),
];
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.apply_merge_results(&base_root, &merge_results);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"new_key").is_some());
assert!(merged_tree.find(b"existing").is_some());
if let Some(node) = merged_tree.find(b"new_key") {
let key_idx = node.keys.iter().position(|k| k == b"new_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"new_value".to_vec());
}
if let Some(node) = merged_tree.find(b"existing") {
let key_idx = node.keys.iter().position(|k| k == b"existing").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"modified_value".to_vec());
}
}
#[test]
fn test_apply_merge_results_with_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let base_tree = ProllyTree::new(storage.clone(), config.clone());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let merge_results = vec![
MergeResult::Added(b"new_key".to_vec(), b"new_value".to_vec()),
MergeResult::Conflict(MergeConflict {
key: b"conflict_key".to_vec(),
base_value: Some(b"base".to_vec()),
source_value: Some(b"source".to_vec()),
destination_value: Some(b"dest".to_vec()),
}),
];
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.apply_merge_results(&base_root, &merge_results);
assert!(result.is_err());
let conflicts = result.expect_err("Expected conflicts");
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].key, b"conflict_key".to_vec());
}
#[test]
fn test_merge_trees_success() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"shared".to_vec(), b"original".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"shared".to_vec(), b"original".to_vec());
source_tree.insert(b"from_source".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"shared".to_vec(), b"original".to_vec());
dest_tree.insert(b"from_dest".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.merge_trees_ignore_conflicts(&source_root, &dest_root, &base_root);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"shared").is_some());
assert!(merged_tree.find(b"from_source").is_some());
assert!(merged_tree.find(b"from_dest").is_some()); }
#[test]
fn test_merge_trees_with_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"original".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
struct NoResolutionResolver;
impl ConflictResolver for NoResolutionResolver {
fn resolve_conflict(&self, _conflict: &MergeConflict) -> Option<MergeResult> {
None }
}
let resolver = NoResolutionResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_err());
let conflicts = result.expect_err("Expected conflicts");
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].key, b"conflict_key".to_vec());
}
#[test]
fn test_merge_trees_with_ignore_conflicts_resolver() {
use crate::diff::IgnoreConflictsResolver;
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"shared".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"shared".to_vec(), b"source_value".to_vec());
source_tree.insert(b"source_only".to_vec(), b"source_only_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"shared".to_vec(), b"dest_value".to_vec());
dest_tree.insert(b"dest_only".to_vec(), b"dest_only_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let resolver = IgnoreConflictsResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"source_only").is_some());
assert!(merged_tree.find(b"dest_only").is_some());
if let Some(node) = merged_tree.find(b"shared") {
let key_idx = node.keys.iter().position(|k| k == b"shared").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"dest_value".to_vec());
}
}
#[test]
fn test_merge_trees_with_take_source_resolver() {
use crate::diff::TakeSourceResolver;
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let resolver = TakeSourceResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_ok());
let merged_tree = result.unwrap();
if let Some(node) = merged_tree.find(b"conflict_key") {
let key_idx = node.keys.iter().position(|k| k == b"conflict_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"source_value".to_vec());
}
}
#[test]
fn test_merge_trees_with_take_destination_resolver() {
use crate::diff::TakeDestinationResolver;
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let resolver = TakeDestinationResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_ok());
let merged_tree = result.unwrap();
if let Some(node) = merged_tree.find(b"conflict_key") {
let key_idx = node.keys.iter().position(|k| k == b"conflict_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"dest_value".to_vec());
}
}
#[test]
fn test_merge_trees_ignore_conflicts_convenience_method() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
source_tree.insert(b"new_key".to_vec(), b"new_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.merge_trees_ignore_conflicts(&source_root, &dest_root, &base_root);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"new_key").is_some());
if let Some(node) = merged_tree.find(b"conflict_key") {
let key_idx = node.keys.iter().position(|k| k == b"conflict_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"dest_value".to_vec());
}
}
}