diff --git a/grovedb-version/src/version/v2.rs b/grovedb-version/src/version/v2.rs index 304b034c..6bb0e31e 100644 --- a/grovedb-version/src/version/v2.rs +++ b/grovedb-version/src/version/v2.rs @@ -13,7 +13,7 @@ use crate::version::{ }; pub const GROVE_V2: GroveVersion = GroveVersion { - protocol_version: 0, + protocol_version: 1, grovedb_versions: GroveDBVersions { apply_batch: GroveDBApplyBatchVersions { apply_batch_structure: 0, @@ -186,7 +186,7 @@ pub const GROVE_V2: GroveVersion = GroveVersion { }, merk_versions: MerkVersions { average_case_costs: MerkAverageCaseCostsVersions { - add_average_case_merk_propagate: 1, + add_average_case_merk_propagate: 1, // changed }, }, }; diff --git a/grovedb/Cargo.toml b/grovedb/Cargo.toml index c294463d..02fca145 100644 --- a/grovedb/Cargo.toml +++ b/grovedb/Cargo.toml @@ -45,6 +45,7 @@ criterion = "0.5.1" hex = "0.4.3" pretty_assertions = "1.4.0" rand = "0.8.5" +assert_matches = "1.5.0" [[bench]] name = "insertion_benchmark" diff --git a/grovedb/src/batch/mod.rs b/grovedb/src/batch/mod.rs index 97b0ed72..c14426ef 100644 --- a/grovedb/src/batch/mod.rs +++ b/grovedb/src/batch/mod.rs @@ -1864,6 +1864,28 @@ impl GroveDb { aggregate_data, } .into(); + } else if let Element::BigSumTree(.., flags) = + element + { + *mutable_occupied_entry = + GroveOp::InsertTreeWithRootHash { + hash: root_hash, + root_key: calculated_root_key, + flags: flags.clone(), + aggregate_data, + } + .into(); + } else if let Element::CountTree(.., flags) = + element + { + *mutable_occupied_entry = + GroveOp::InsertTreeWithRootHash { + hash: root_hash, + root_key: calculated_root_key, + flags: flags.clone(), + aggregate_data, + } + .into(); } else { return Err(Error::InvalidBatchOperation( "insertion of element under a non tree", diff --git a/grovedb/src/element/constructor.rs b/grovedb/src/element/constructor.rs index 556df1da..0ba0fd8f 100644 --- a/grovedb/src/element/constructor.rs +++ b/grovedb/src/element/constructor.rs @@ -29,6 +29,18 @@ impl Element { Element::new_sum_tree(Default::default()) } + #[cfg(feature = "full")] + /// Set element to default empty big sum tree without flags + pub fn empty_big_sum_tree() -> Self { + Element::new_big_sum_tree(Default::default()) + } + + #[cfg(feature = "full")] + /// Set element to default empty count tree without flags + pub fn empty_count_tree() -> Self { + Element::new_count_tree(Default::default()) + } + #[cfg(feature = "full")] /// Set element to default empty sum tree with flags pub fn empty_sum_tree_with_flags(flags: Option) -> Self { diff --git a/grovedb/src/element/helpers.rs b/grovedb/src/element/helpers.rs index f22a2c7e..8b8a22a1 100644 --- a/grovedb/src/element/helpers.rs +++ b/grovedb/src/element/helpers.rs @@ -21,7 +21,7 @@ use grovedb_version::{check_grovedb_v0, error::GroveVersionError, version::Grove #[cfg(feature = "full")] use integer_encoding::VarInt; -use crate::element::BIG_SUM_TREE_COST_SIZE; +use crate::element::{BIG_SUM_TREE_COST_SIZE, COUNT_TREE_COST_SIZE}; #[cfg(feature = "full")] use crate::reference_path::path_from_reference_path_type; #[cfg(any(feature = "full", feature = "verify"))] @@ -45,6 +45,16 @@ impl Element { } } + #[cfg(any(feature = "full", feature = "verify"))] + /// Decoded the integer value in the CountTree element type, returns 1 for + /// everything else + pub fn count_value_or_default(&self) -> u64 { + match self { + Element::CountTree(_, count_value, _) => *count_value, + _ => 1, + } + } + #[cfg(any(feature = "full", feature = "verify"))] /// Decoded the integer value in the SumItem element type, returns 0 for /// everything else @@ -232,7 +242,7 @@ impl Element { TreeType::NormalTree => Ok(BasicMerkNode), TreeType::SumTree => Ok(SummedMerkNode(self.sum_value_or_default())), TreeType::BigSumTree => Ok(BigSummedMerkNode(self.big_sum_value_or_default())), - TreeType::CountTree => Ok(CountedMerkNode(1)), + TreeType::CountTree => Ok(CountedMerkNode(self.count_value_or_default())), } } @@ -388,6 +398,17 @@ impl Element { key_len, value_len, node_type, ) } + Element::CountTree(_, _count_value, flags) => { + let flags_len = flags.map_or(0, |flags| { + let flags_len = flags.len() as u32; + flags_len + flags_len.required_space() as u32 + }); + let value_len = COUNT_TREE_COST_SIZE + flags_len; + let key_len = key.len() as u32; + KV::layered_value_byte_cost_size_for_key_and_value_lengths( + key_len, value_len, node_type, + ) + } Element::SumItem(.., flags) => { let flags_len = flags.map_or(0, |flags| { let flags_len = flags.len() as u32; @@ -414,6 +435,7 @@ impl Element { Element::SumTree(..) => Ok(SUM_TREE_COST_SIZE), Element::BigSumTree(..) => Ok(BIG_SUM_TREE_COST_SIZE), Element::SumItem(..) => Ok(SUM_ITEM_COST_SIZE), + Element::CountTree(..) => Ok(COUNT_TREE_COST_SIZE), _ => Err(Error::CorruptedCodeExecution( "trying to get tree cost from non tree element", )), @@ -436,6 +458,7 @@ impl Element { Element::Tree(..) => Some(LayeredValueDefinedCost(cost)), Element::SumTree(..) => Some(LayeredValueDefinedCost(cost)), Element::BigSumTree(..) => Some(LayeredValueDefinedCost(cost)), + Element::CountTree(..) => Some(LayeredValueDefinedCost(cost)), Element::SumItem(..) => Some(SpecializedValueDefinedCost(cost)), _ => None, } diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 84cfeac5..36907b09 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -501,7 +501,8 @@ impl GroveDb { let tree_type = element.tree_type(); if let Element::Tree(root_key, _) | Element::SumTree(root_key, ..) - | Element::BigSumTree(root_key, ..) = element + | Element::BigSumTree(root_key, ..) + | Element::CountTree(root_key, ..) = element { let tree_type = tree_type.expect("expected tree type"); Merk::open_layered_with_root_key( @@ -715,7 +716,7 @@ impl GroveDb { grove_version ) ); - let (root_hash, root_key, sum) = cost_return_on_error!( + let (root_hash, root_key, aggregate_data) = cost_return_on_error!( &mut cost, child_tree .root_hash_key_and_aggregate_data() @@ -728,7 +729,7 @@ impl GroveDb { parent_key, root_key, root_hash, - sum, + aggregate_data, grove_version, ) ); @@ -832,6 +833,19 @@ impl GroveDb { None, grove_version, ) + } else if let Element::CountTree(.., flag) = element { + let tree = Element::new_count_tree_with_flags_and_count_value( + maybe_root_key, + aggregate_data.as_u64(), + flag, + ); + tree.insert_subtree( + parent_tree, + key.as_ref(), + root_tree_hash, + None, + grove_version, + ) } else { Err(Error::InvalidPath( "can only propagate on tree items".to_owned(), @@ -912,6 +926,25 @@ impl GroveDb { merk_feature_type, grove_version, ) + } else if let Element::CountTree(.., flag) = element { + let tree = Element::new_count_tree_with_flags_and_count_value( + maybe_root_key, + aggregate_data.as_u64(), + flag, + ); + let merk_feature_type = cost_return_on_error!( + &mut cost, + tree.get_feature_type(parent_tree.tree_type) + .wrap_with_cost(OperationCost::default()) + ); + tree.insert_subtree_into_batch_operations( + key, + root_tree_hash, + true, + batch_operations, + merk_feature_type, + grove_version, + ) } else { Err(Error::InvalidPath( "can only propagate on tree items".to_owned(), diff --git a/grovedb/src/operations/get/mod.rs b/grovedb/src/operations/get/mod.rs index b6289699..073e02ab 100644 --- a/grovedb/src/operations/get/mod.rs +++ b/grovedb/src/operations/get/mod.rs @@ -455,7 +455,10 @@ impl GroveDb { } .unwrap_add_cost(&mut cost); match element { - Ok(Element::Tree(..)) | Ok(Element::SumTree(..)) => Ok(()).wrap_with_cost(cost), + Ok(Element::Tree(..)) + | Ok(Element::SumTree(..)) + | Ok(Element::BigSumTree(..)) + | Ok(Element::CountTree(..)) => Ok(()).wrap_with_cost(cost), Ok(_) | Err(Error::PathKeyNotFound(_)) => Err(error_fn()).wrap_with_cost(cost), Err(e) => Err(e).wrap_with_cost(cost), } diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index 5926fedd..fc357cd8 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -317,7 +317,10 @@ impl GroveDb { ) ); } - Element::Tree(ref value, _) | Element::SumTree(ref value, ..) => { + Element::Tree(ref value, _) + | Element::SumTree(ref value, ..) + | Element::BigSumTree(ref value, ..) + | Element::CountTree(ref value, ..) => { if value.is_some() { return Err(Error::InvalidCodeExecution( "a tree should be empty at the moment of insertion when not using batches", @@ -450,7 +453,10 @@ impl GroveDb { ) ); } - Element::Tree(ref value, _) | Element::SumTree(ref value, ..) => { + Element::Tree(ref value, _) + | Element::SumTree(ref value, ..) + | Element::BigSumTree(ref value, ..) + | Element::CountTree(ref value, ..) => { if value.is_some() { return Err(Error::InvalidCodeExecution( "a tree should be empty at the moment of insertion when not using batches", @@ -1593,6 +1599,87 @@ mod tests { ); } + #[test] + fn test_one_insert_item_cost_under_count_tree() { + let grove_version = GroveVersion::latest(); + let db = make_empty_grovedb(); + let tx = db.start_transaction(); + + db.insert( + EMPTY_PATH, + b"tree", + Element::empty_count_tree(), + None, + Some(&tx), + grove_version, + ) + .unwrap() + .unwrap(); + + let cost = db + .insert( + [b"tree".as_slice()].as_ref(), + b"key1", + Element::new_item(b"test".to_vec()), + None, + Some(&tx), + grove_version, + ) + .cost_as_result() + .unwrap(); + + // Explanation for 152 storage_written_bytes + + // Key -> 37 bytes + // 32 bytes for the key prefix + // 4 bytes for the key + // 1 byte for key_size (required space for 36) + + // Value -> 81 + // 1 for the enum type item + // 1 for size of test bytes + // 4 for test bytes + // 1 for the flag option (but no flags) + // 32 for node hash + // 32 for value hash (trees have this for free) + // 9 for Count node + // 1 byte for the value_size (required space for 1) + + // Parent Hook -> 48 + // Key Bytes 4 + // Hash Size 32 + // Key Length 1 + // Count Merk 9 + // Child Heights 2 + + // Total 37 + 81 + 48 = 166 + + // Explanation for replaced bytes + + // Replaced parent Value -> 86 + // 1 for the flag option (but no flags) + // 1 for the enum type + // 1 for an empty option + // 1 for the count merk + // 9 for the count + // 32 for node hash + // 40 for the parent hook + // 2 byte for the value_size + assert_eq!( + cost, + OperationCost { + seek_count: 5, // todo: verify this + storage_cost: StorageCost { + added_bytes: 166, + replaced_bytes: 87, + removed_bytes: NoStorageRemoval + }, + storage_loaded_bytes: 162, // todo: verify this + hash_node_calls: 8, // todo: verify this + } + ); + } + #[test] fn test_one_insert_item_with_apple_flags_cost() { let grove_version = GroveVersion::latest(); diff --git a/grovedb/src/operations/proof/generate.rs b/grovedb/src/operations/proof/generate.rs index 6e814f67..7708d569 100644 --- a/grovedb/src/operations/proof/generate.rs +++ b/grovedb/src/operations/proof/generate.rs @@ -314,7 +314,10 @@ impl GroveDb { } has_a_result_at_level |= true; } - Ok(Element::Tree(Some(_), _)) | Ok(Element::SumTree(Some(_), ..)) + Ok(Element::Tree(Some(_), _)) + | Ok(Element::SumTree(Some(_), ..)) + | Ok(Element::BigSumTree(Some(_), ..)) + | Ok(Element::CountTree(Some(_), ..)) if !done_with_results && query.has_subquery_or_matching_in_path_on_key(key) => { diff --git a/grovedb/src/tests/count_tree_tests.rs b/grovedb/src/tests/count_tree_tests.rs new file mode 100644 index 00000000..6ac71ed0 --- /dev/null +++ b/grovedb/src/tests/count_tree_tests.rs @@ -0,0 +1,852 @@ +//! Count tree tests + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use grovedb_merk::{ + proofs::Query, + tree::{kv::ValueDefinedCostType, AggregateData}, + TreeFeatureType::{BasicMerkNode, CountedMerkNode}, + }; + use grovedb_storage::StorageBatch; + use grovedb_version::version::GroveVersion; + + use crate::{ + batch::QualifiedGroveDbOp, + reference_path::ReferencePathType, + tests::{make_test_grovedb, TEST_LEAF}, + Element, Error, GroveDb, PathQuery, + }; + + #[test] + fn test_count_tree_behaves_like_regular_tree() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_count_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + // Can fetch count tree + let count_tree = db + .get([TEST_LEAF].as_ref(), b"key", None, grove_version) + .unwrap() + .expect("should get count tree"); + assert!(matches!(count_tree, Element::CountTree(..))); + + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"innerkey", + Element::new_item(vec![1]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"innerkey2", + Element::new_item(vec![3]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"innerkey3", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + + // Test proper item retrieval + let item = db + .get( + [TEST_LEAF, b"key"].as_ref(), + b"innerkey", + None, + grove_version, + ) + .unwrap() + .expect("should get item"); + assert_eq!(item, Element::new_item(vec![1])); + + // Test proof generation + let mut query = Query::new(); + query.insert_key(b"innerkey2".to_vec()); + + let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"key".to_vec()], query); + let proof = db + .prove_query(&path_query, None, grove_version) + .unwrap() + .expect("should generate proof"); + let (root_hash, result_set) = GroveDb::verify_query_raw(&proof, &path_query, grove_version) + .expect("should verify proof"); + assert_eq!( + root_hash, + db.grove_db.root_hash(None, grove_version).unwrap().unwrap() + ); + assert_eq!(result_set.len(), 1); + assert_eq!( + Element::deserialize(&result_set[0].value, grove_version) + .expect("should deserialize element"), + Element::new_item(vec![3]) + ); + } + + #[test] + fn test_homogenous_node_type_in_count_trees_and_regular_trees() { + let grove_version = GroveVersion::latest(); + // All elements in a count tree must have a count feature type + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_count_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + // Add count items + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item1", + Element::new_item(vec![30]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item2", + Element::new_item(vec![10]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + // Add regular items + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item3", + Element::new_item(vec![10]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item4", + Element::new_item(vec![15]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + + let batch = StorageBatch::new(); + + // Open merk and check all elements in it + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + let feature_type_node_1 = merk + .get_feature_type( + b"item1", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("expected feature type"); + let feature_type_node_2 = merk + .get_feature_type( + b"item2", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("expected feature type"); + let feature_type_node_3 = merk + .get_feature_type( + b"item3", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("expected feature type"); + let feature_type_node_4 = merk + .get_feature_type( + b"item4", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("expected feature type"); + + assert_matches!(feature_type_node_1, CountedMerkNode(1)); + assert_matches!(feature_type_node_2, CountedMerkNode(1)); + assert_matches!(feature_type_node_3, CountedMerkNode(1)); + assert_matches!(feature_type_node_4, CountedMerkNode(1)); + + // Perform the same test on regular trees + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item1", + Element::new_item(vec![30]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item2", + Element::new_item(vec![10]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert!(matches!( + merk.get_feature_type( + b"item1", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(BasicMerkNode) + )); + assert!(matches!( + merk.get_feature_type( + b"item2", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(BasicMerkNode) + )); + assert_eq!( + merk.aggregate_data().expect("expected to get count"), + AggregateData::NoAggregateData + ); + } + + #[test] + fn test_count_tree_feature() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + let batch = StorageBatch::new(); + + // Sum should be non for non count tree + // TODO: change interface to retrieve element directly + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get count"), + AggregateData::NoAggregateData + ); + + // Add count tree + db.insert( + [TEST_LEAF].as_ref(), + b"key2", + Element::empty_count_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert count tree"); + let count_tree = db + .get([TEST_LEAF].as_ref(), b"key2", None, grove_version) + .unwrap() + .expect("should retrieve tree"); + assert_eq!(count_tree.count_value_or_default(), 0); + + // Add count items to the count tree + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item1", + Element::new_item(vec![30]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + // TODO: change interface to retrieve element directly + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get count"), + AggregateData::Count(1) + ); + + // Add more count items + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item2", + Element::new_item(vec![3]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item3", + Element::new_item(vec![3]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get count"), + AggregateData::Count(3) + ); + + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item4", + Element::new_item(vec![29]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get count"), + AggregateData::Count(4) + ); + + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item2", + Element::new_item(vec![10]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item3", + Element::new_item(vec![3]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get count"), + AggregateData::Count(4) + ); + + db.delete( + [TEST_LEAF, b"key2"].as_ref(), + b"item4", + None, + None, + grove_version, + ) + .unwrap() + .expect("expected to delete"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get count"), + AggregateData::Count(3) + ); + } + + #[test] + fn test_count_tree_propagation() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + // Tree + // count_key: CountTree + // / \ + // countitem3 tree2: CountTree + // + // tree2 : CountTree + // / + // item1 item2 item3 ref1 + db.insert( + [TEST_LEAF].as_ref(), + b"count_key", + Element::empty_count_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"count_key"].as_ref(), + b"tree2", + Element::empty_count_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"count_key"].as_ref(), + b"countitem3", + Element::new_item(vec![3]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"count_key", b"tree2"].as_ref(), + b"item1", + Element::new_item(vec![2]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"count_key", b"tree2"].as_ref(), + b"item2", + Element::new_item(vec![5]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"count_key", b"tree2"].as_ref(), + b"item3", + Element::new_item(vec![10]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"count_key", b"tree2"].as_ref(), + b"ref1", + Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ + TEST_LEAF.to_vec(), + b"count_key".to_vec(), + b"tree2".to_vec(), + b"item1".to_vec(), + ])), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + + let count_tree = db + .get([TEST_LEAF].as_ref(), b"count_key", None, grove_version) + .unwrap() + .expect("should fetch tree"); + assert_eq!(count_tree.count_value_or_default(), 5); + + let batch = StorageBatch::new(); + + // Assert node feature types + let test_leaf_merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + let root_tree_feature_type = test_leaf_merk + .get_feature_type( + b"count_key", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("tree feature type"); + + assert_matches!(root_tree_feature_type, BasicMerkNode); + + let parent_count_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"count_key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + let count_tree_feature_type = parent_count_tree + .get_feature_type( + b"tree2", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("tree feature type"); + assert_matches!(count_tree_feature_type, CountedMerkNode(4)); + + let child_count_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"count_key", b"tree2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + let count_tree_feature_type = child_count_tree + .get_feature_type( + b"item1", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("tree feature type"); + + assert_matches!(count_tree_feature_type, CountedMerkNode(1)); + + let count_tree_feature_type = child_count_tree + .get_feature_type( + b"item2", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("tree feature type"); + + assert_matches!(count_tree_feature_type, CountedMerkNode(1)); + + let count_tree_feature_type = child_count_tree + .get_feature_type( + b"item3", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("tree feature type"); + + assert_matches!(count_tree_feature_type, CountedMerkNode(1)); + + let count_tree_feature_type = child_count_tree + .get_feature_type( + b"ref1", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("tree feature type"); + + assert_matches!(count_tree_feature_type, CountedMerkNode(1)); + } + + #[test] + fn test_count_tree_with_batches() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + let ops = vec![ + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec()], + b"key1".to_vec(), + Element::empty_count_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"a".to_vec(), + Element::new_item(vec![214]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"b".to_vec(), + Element::new_item(vec![10]), + ), + ]; + db.apply_batch(ops, None, None, grove_version) + .unwrap() + .expect("should apply batch"); + + let batch = StorageBatch::new(); + let count_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key1"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + + let tree_feature_type_a = count_tree + .get_feature_type( + b"a", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("expected tree feature type"); + + let tree_feature_type_b = count_tree + .get_feature_type( + b"a", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("expected tree feature type"); + + assert_matches!(tree_feature_type_a, CountedMerkNode(1)); + assert_matches!(tree_feature_type_b, CountedMerkNode(1)); + + // Create new batch to use existing tree + let ops = vec![QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"c".to_vec(), + Element::new_item(vec![10]), + )]; + db.apply_batch(ops, None, None, grove_version) + .unwrap() + .expect("should apply batch"); + + let batch = StorageBatch::new(); + let count_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key1"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + let tree_feature_type_c = count_tree + .get_feature_type( + b"c", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("node should exist") + .expect("expected tree feature type"); + assert_matches!(tree_feature_type_c, CountedMerkNode(1)); + assert_eq!( + count_tree.aggregate_data().expect("expected to get count"), + AggregateData::Count(3) + ); + + // Test propagation + // Add a new count tree with its own count items, should affect count of + // original tree + let ops = vec![ + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"d".to_vec(), + Element::empty_count_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"d".to_vec()], + b"first".to_vec(), + Element::new_item(vec![2]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"d".to_vec()], + b"second".to_vec(), + Element::new_item(vec![4]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"e".to_vec(), + Element::empty_count_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], + b"first".to_vec(), + Element::new_item(vec![3]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], + b"second".to_vec(), + Element::new_item(vec![4]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], + b"third".to_vec(), + Element::empty_count_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![ + TEST_LEAF.to_vec(), + b"key1".to_vec(), + b"e".to_vec(), + b"third".to_vec(), + ], + b"a".to_vec(), + Element::new_item(vec![5]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![ + TEST_LEAF.to_vec(), + b"key1".to_vec(), + b"e".to_vec(), + b"third".to_vec(), + ], + b"b".to_vec(), + Element::new_item(vec![5]), + ), + ]; + db.apply_batch(ops, None, None, grove_version) + .unwrap() + .expect("should apply batch"); + + let batch = StorageBatch::new(); + let count_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key1"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + count_tree.aggregate_data().expect("expected to get count"), + AggregateData::Count(9) + ); + } +} diff --git a/grovedb/src/tests/mod.rs b/grovedb/src/tests/mod.rs index 896346c5..bd6561ff 100644 --- a/grovedb/src/tests/mod.rs +++ b/grovedb/src/tests/mod.rs @@ -6,6 +6,7 @@ mod query_tests; mod sum_tree_tests; +mod count_tree_tests; mod tree_hashes_tests; use std::{ diff --git a/grovedb/src/tests/sum_tree_tests.rs b/grovedb/src/tests/sum_tree_tests.rs index 1371e27e..215249ca 100644 --- a/grovedb/src/tests/sum_tree_tests.rs +++ b/grovedb/src/tests/sum_tree_tests.rs @@ -1,754 +1,285 @@ //! Sum tree tests -use grovedb_merk::{ - proofs::Query, - tree::{kv::ValueDefinedCostType, AggregateData}, - TreeFeatureType::{BasicMerkNode, SummedMerkNode}, -}; -use grovedb_storage::StorageBatch; -use grovedb_version::version::GroveVersion; - -use crate::{ - batch::QualifiedGroveDbOp, - reference_path::ReferencePathType, - tests::{make_test_grovedb, TEST_LEAF}, - Element, Error, GroveDb, PathQuery, -}; - -#[test] -fn test_sum_tree_behaves_like_regular_tree() { - let grove_version = GroveVersion::latest(); - let db = make_test_grovedb(grove_version); - db.insert( - [TEST_LEAF].as_ref(), - b"key", - Element::empty_sum_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - - // Can fetch sum tree - let sum_tree = db - .get([TEST_LEAF].as_ref(), b"key", None, grove_version) - .unwrap() - .expect("should get tree"); - assert!(matches!(sum_tree, Element::SumTree(..))); - - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"innerkey", - Element::new_item(vec![1]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"innerkey2", - Element::new_item(vec![3]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"innerkey3", - Element::empty_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - - // Test proper item retrieval - let item = db - .get( +#[cfg(test)] +mod tests { + use grovedb_merk::{ + proofs::Query, + tree::{kv::ValueDefinedCostType, AggregateData}, + TreeFeatureType::{BasicMerkNode, SummedMerkNode}, + }; + use grovedb_storage::StorageBatch; + use grovedb_version::version::GroveVersion; + + use crate::{ + batch::QualifiedGroveDbOp, + element::SumValue, + reference_path::ReferencePathType, + tests::{make_test_grovedb, TEST_LEAF}, + Element, Error, GroveDb, PathQuery, + }; + + #[test] + fn test_sum_tree_behaves_like_regular_tree() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_sum_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + // Can fetch sum tree + let sum_tree = db + .get([TEST_LEAF].as_ref(), b"key", None, grove_version) + .unwrap() + .expect("should get tree"); + assert!(matches!(sum_tree, Element::SumTree(..))); + + db.insert( [TEST_LEAF, b"key"].as_ref(), b"innerkey", + Element::new_item(vec![1]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"innerkey2", + Element::new_item(vec![3]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"innerkey3", + Element::empty_tree(), + None, None, grove_version, ) .unwrap() - .expect("should get item"); - assert_eq!(item, Element::new_item(vec![1])); + .expect("should insert item"); - // Test proof generation - let mut query = Query::new(); - query.insert_key(b"innerkey2".to_vec()); + // Test proper item retrieval + let item = db + .get( + [TEST_LEAF, b"key"].as_ref(), + b"innerkey", + None, + grove_version, + ) + .unwrap() + .expect("should get item"); + assert_eq!(item, Element::new_item(vec![1])); - let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"key".to_vec()], query); - let proof = db - .prove_query(&path_query, None, grove_version) - .unwrap() - .expect("should generate proof"); - let (root_hash, result_set) = - GroveDb::verify_query_raw(&proof, &path_query, grove_version).expect("should verify proof"); - assert_eq!( - root_hash, - db.grove_db.root_hash(None, grove_version).unwrap().unwrap() - ); - assert_eq!(result_set.len(), 1); - assert_eq!( - Element::deserialize(&result_set[0].value, grove_version) - .expect("should deserialize element"), - Element::new_item(vec![3]) - ); -} + // Test proof generation + let mut query = Query::new(); + query.insert_key(b"innerkey2".to_vec()); -#[test] -fn test_sum_item_behaves_like_regular_item() { - let grove_version = GroveVersion::latest(); - let db = make_test_grovedb(grove_version); - db.insert( - [TEST_LEAF].as_ref(), - b"sumkey", - Element::empty_sum_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - db.insert( - [TEST_LEAF, b"sumkey"].as_ref(), - b"k1", - Element::new_item(vec![1]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - db.insert( - [TEST_LEAF, b"sumkey"].as_ref(), - b"k2", - Element::new_sum_item(5), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - db.insert( - [TEST_LEAF, b"sumkey"].as_ref(), - b"k3", - Element::empty_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - - // Test proper item retrieval - let item = db - .get([TEST_LEAF, b"sumkey"].as_ref(), b"k2", None, grove_version) - .unwrap() - .expect("should get item"); - assert_eq!(item, Element::new_sum_item(5)); - - // Test proof generation - let mut query = Query::new(); - query.insert_key(b"k2".to_vec()); - - let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"sumkey".to_vec()], query); - let proof = db - .prove_query(&path_query, None, grove_version) - .unwrap() - .expect("should generate proof"); - let (root_hash, result_set) = - GroveDb::verify_query_raw(&proof, &path_query, grove_version).expect("should verify proof"); - assert_eq!( - root_hash, - db.grove_db.root_hash(None, grove_version).unwrap().unwrap() - ); - assert_eq!(result_set.len(), 1); - let element_from_proof = Element::deserialize(&result_set[0].value, grove_version) - .expect("should deserialize element"); - assert_eq!(element_from_proof, Element::new_sum_item(5)); - assert_eq!(element_from_proof.sum_value_or_default(), 5); -} + let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"key".to_vec()], query); + let proof = db + .prove_query(&path_query, None, grove_version) + .unwrap() + .expect("should generate proof"); + let (root_hash, result_set) = GroveDb::verify_query_raw(&proof, &path_query, grove_version) + .expect("should verify proof"); + assert_eq!( + root_hash, + db.grove_db.root_hash(None, grove_version).unwrap().unwrap() + ); + assert_eq!(result_set.len(), 1); + assert_eq!( + Element::deserialize(&result_set[0].value, grove_version) + .expect("should deserialize element"), + Element::new_item(vec![3]) + ); + } -#[test] -fn test_cannot_insert_sum_item_in_regular_tree() { - let grove_version = GroveVersion::latest(); - let db = make_test_grovedb(grove_version); - db.insert( - [TEST_LEAF].as_ref(), - b"sumkey", - Element::empty_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - assert!(matches!( + #[test] + fn test_sum_item_behaves_like_regular_item() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); db.insert( - [TEST_LEAF, b"sumkey"].as_ref(), - b"k1", - Element::new_sum_item(5), + [TEST_LEAF].as_ref(), + b"sumkey", + Element::empty_sum_tree(), None, None, - grove_version - ) - .unwrap(), - Err(Error::InvalidInput("cannot add sum item to non sum tree")) - )); -} - -#[test] -fn test_homogenous_node_type_in_sum_trees_and_regular_trees() { - let grove_version = GroveVersion::latest(); - // All elements in a sum tree must have a summed feature type - let db = make_test_grovedb(grove_version); - db.insert( - [TEST_LEAF].as_ref(), - b"key", - Element::empty_sum_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - // Add sum items - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"item1", - Element::new_sum_item(30), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"item2", - Element::new_sum_item(10), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - // Add regular items - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"item3", - Element::new_item(vec![10]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"item4", - Element::new_item(vec![15]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - - let batch = StorageBatch::new(); - - // Open merk and check all elements in it - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert!(matches!( - merk.get_feature_type( - b"item1", - true, - None::<&fn(&[u8], &GroveVersion) -> Option>, - grove_version + grove_version, ) .unwrap() - .expect("node should exist"), - Some(SummedMerkNode(30)) - )); - assert!(matches!( - merk.get_feature_type( - b"item2", - true, - None::<&fn(&[u8], &GroveVersion) -> Option>, - grove_version + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"sumkey"].as_ref(), + b"k1", + Element::new_item(vec![1]), + None, + None, + grove_version, ) .unwrap() - .expect("node should exist"), - Some(SummedMerkNode(10)) - )); - assert!(matches!( - merk.get_feature_type( - b"item3", - true, - None::<&fn(&[u8], &GroveVersion) -> Option>, - grove_version + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"sumkey"].as_ref(), + b"k2", + Element::new_sum_item(5), + None, + None, + grove_version, ) .unwrap() - .expect("node should exist"), - Some(SummedMerkNode(0)) - )); - assert!(matches!( - merk.get_feature_type( - b"item4", - true, - None::<&fn(&[u8], &GroveVersion) -> Option>, - grove_version - ) - .unwrap() - .expect("node should exist"), - Some(SummedMerkNode(0)) - )); - assert_eq!( - merk.aggregate_data().expect("expected to get sum").as_i64(), - 40 - ); - - // Perform the same test on regular trees - let db = make_test_grovedb(grove_version); - db.insert( - [TEST_LEAF].as_ref(), - b"key", - Element::empty_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"item1", - Element::new_item(vec![30]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"item2", - Element::new_item(vec![10]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert!(matches!( - merk.get_feature_type( - b"item1", - true, - Some(&Element::value_defined_cost_for_serialized_value), - grove_version + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"sumkey"].as_ref(), + b"k3", + Element::empty_tree(), + None, + None, + grove_version, ) .unwrap() - .expect("node should exist"), - Some(BasicMerkNode) - )); - assert!(matches!( - merk.get_feature_type( - b"item2", - true, - Some(&Element::value_defined_cost_for_serialized_value), - grove_version - ) - .unwrap() - .expect("node should exist"), - Some(BasicMerkNode) - )); - assert_eq!( - merk.aggregate_data().expect("expected to get sum"), - AggregateData::NoAggregateData - ); -} + .expect("should insert tree"); -#[test] -fn test_sum_tree_feature() { - let grove_version = GroveVersion::latest(); - let db = make_test_grovedb(grove_version); - db.insert( - [TEST_LEAF].as_ref(), - b"key", - Element::empty_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - - let batch = StorageBatch::new(); - - // Sum should be non for non sum tree - // TODO: change interface to retrieve element directly - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert_eq!( - merk.aggregate_data().expect("expected to get sum"), - AggregateData::NoAggregateData - ); - - // Add sum tree - db.insert( - [TEST_LEAF].as_ref(), - b"key2", - Element::empty_sum_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert sum tree"); - let sum_tree = db - .get([TEST_LEAF].as_ref(), b"key2", None, grove_version) - .unwrap() - .expect("should retrieve tree"); - assert_eq!(sum_tree.sum_value_or_default(), 0); - - // Add sum items to the sum tree - db.insert( - [TEST_LEAF, b"key2"].as_ref(), - b"item1", - Element::new_sum_item(30), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - // TODO: change interface to retrieve element directly - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key2"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert_eq!( - merk.aggregate_data().expect("expected to get sum"), - AggregateData::Sum(30) - ); - - // Add more sum items - db.insert( - [TEST_LEAF, b"key2"].as_ref(), - b"item2", - Element::new_sum_item(-10), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key2"].as_ref(), - b"item3", - Element::new_sum_item(50), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key2"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert_eq!( - merk.aggregate_data().expect("expected to get sum"), - AggregateData::Sum(70) - ); // 30 - 10 + 50 = 70 - - // Add non sum items, result should remain the same - db.insert( - [TEST_LEAF, b"key2"].as_ref(), - b"item4", - Element::new_item(vec![29]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key2"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert_eq!( - merk.aggregate_data().expect("expected to get sum"), - AggregateData::Sum(70) - ); - - // Update existing sum items - db.insert( - [TEST_LEAF, b"key2"].as_ref(), - b"item2", - Element::new_sum_item(10), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key2"].as_ref(), - b"item3", - Element::new_sum_item(-100), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key2"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert_eq!( - merk.aggregate_data().expect("expected to get sum"), - AggregateData::Sum(-60) - ); // 30 + 10 - 100 = -60 - - // We can not replace a normal item with a sum item, so let's delete it first - db.delete( - [TEST_LEAF, b"key2"].as_ref(), - b"item4", - None, - None, - grove_version, - ) - .unwrap() - .expect("expected to delete"); - // Use a large value - db.insert( - [TEST_LEAF, b"key2"].as_ref(), - b"item4", - Element::new_sum_item(10000000), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - let merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key2"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert_eq!( - merk.aggregate_data().expect("expected to get sum"), - AggregateData::Sum(9999940) - ); // 30 + - // 10 - - // 100 + - // 10000000 - - // TODO: Test out overflows -} + // Test proper item retrieval + let item = db + .get([TEST_LEAF, b"sumkey"].as_ref(), b"k2", None, grove_version) + .unwrap() + .expect("should get item"); + assert_eq!(item, Element::new_sum_item(5)); -#[test] -fn test_sum_tree_propagation() { - let grove_version = GroveVersion::latest(); - let db = make_test_grovedb(grove_version); - // Tree - // SumTree - // SumTree - // Item1 - // SumItem1 - // SumItem2 - // SumItem3 - db.insert( - [TEST_LEAF].as_ref(), - b"key", - Element::empty_sum_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"tree2", - Element::empty_sum_tree(), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - db.insert( - [TEST_LEAF, b"key"].as_ref(), - b"sumitem3", - Element::new_sum_item(20), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert tree"); - db.insert( - [TEST_LEAF, b"key", b"tree2"].as_ref(), - b"item1", - Element::new_item(vec![2]), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key", b"tree2"].as_ref(), - b"sumitem1", - Element::new_sum_item(5), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key", b"tree2"].as_ref(), - b"sumitem2", - Element::new_sum_item(10), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - db.insert( - [TEST_LEAF, b"key", b"tree2"].as_ref(), - b"item2", - Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ - TEST_LEAF.to_vec(), - b"key".to_vec(), - b"tree2".to_vec(), - b"sumitem1".to_vec(), - ])), - None, - None, - grove_version, - ) - .unwrap() - .expect("should insert item"); - - let sum_tree = db - .get([TEST_LEAF].as_ref(), b"key", None, grove_version) - .unwrap() - .expect("should fetch tree"); - assert_eq!(sum_tree.sum_value_or_default(), 35); - - let batch = StorageBatch::new(); - - // Assert node feature types - let test_leaf_merk = db - .open_non_transactional_merk_at_path( - [TEST_LEAF].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert!(matches!( - test_leaf_merk - .get_feature_type( - b"key", - true, - Some(&Element::value_defined_cost_for_serialized_value), - grove_version - ) + // Test proof generation + let mut query = Query::new(); + query.insert_key(b"k2".to_vec()); + + let path_query = + PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"sumkey".to_vec()], query); + let proof = db + .prove_query(&path_query, None, grove_version) .unwrap() - .expect("node should exist"), - Some(BasicMerkNode) - )); + .expect("should generate proof"); + let (root_hash, result_set) = GroveDb::verify_query_raw(&proof, &path_query, grove_version) + .expect("should verify proof"); + assert_eq!( + root_hash, + db.grove_db.root_hash(None, grove_version).unwrap().unwrap() + ); + assert_eq!(result_set.len(), 1); + let element_from_proof = Element::deserialize(&result_set[0].value, grove_version) + .expect("should deserialize element"); + assert_eq!(element_from_proof, Element::new_sum_item(5)); + assert_eq!(element_from_proof.sum_value_or_default(), 5); + } - let parent_sum_tree = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key"].as_ref().into(), - Some(&batch), + #[test] + fn test_cannot_insert_sum_item_in_regular_tree() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"sumkey", + Element::empty_tree(), + None, + None, grove_version, ) .unwrap() - .expect("should open tree"); - assert!(matches!( - parent_sum_tree - .get_feature_type( - b"tree2", - true, - Some(&Element::value_defined_cost_for_serialized_value), + .expect("should insert tree"); + assert!(matches!( + db.insert( + [TEST_LEAF, b"sumkey"].as_ref(), + b"k1", + Element::new_sum_item(5), + None, + None, grove_version ) - .unwrap() - .expect("node should exist"), - Some(SummedMerkNode(15)) /* 15 because the child sum tree has one sum item of - * value 5 and - * another of value 10 */ - )); + .unwrap(), + Err(Error::InvalidInput("cannot add sum item to non sum tree")) + )); + } - let child_sum_tree = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key", b"tree2"].as_ref().into(), - Some(&batch), + #[test] + fn test_homogenous_node_type_in_sum_trees_and_regular_trees() { + let grove_version = GroveVersion::latest(); + // All elements in a sum tree must have a summed feature type + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_sum_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + // Add sum items + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item1", + Element::new_sum_item(30), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item2", + Element::new_sum_item(10), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + // Add regular items + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item3", + Element::new_item(vec![10]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item4", + Element::new_item(vec![15]), + None, + None, grove_version, ) .unwrap() - .expect("should open tree"); - assert!(matches!( - child_sum_tree - .get_feature_type( + .expect("should insert item"); + + let batch = StorageBatch::new(); + + // Open merk and check all elements in it + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert!(matches!( + merk.get_feature_type( b"item1", true, None::<&fn(&[u8], &GroveVersion) -> Option>, @@ -756,219 +287,933 @@ fn test_sum_tree_propagation() { ) .unwrap() .expect("node should exist"), - Some(SummedMerkNode(0)) - )); - assert!(matches!( - child_sum_tree - .get_feature_type( - b"sumitem1", + Some(SummedMerkNode(30)) + )); + assert!(matches!( + merk.get_feature_type( + b"item2", true, None::<&fn(&[u8], &GroveVersion) -> Option>, grove_version ) .unwrap() .expect("node should exist"), - Some(SummedMerkNode(5)) - )); - assert!(matches!( - child_sum_tree - .get_feature_type( - b"sumitem2", + Some(SummedMerkNode(10)) + )); + assert!(matches!( + merk.get_feature_type( + b"item3", true, None::<&fn(&[u8], &GroveVersion) -> Option>, grove_version ) .unwrap() .expect("node should exist"), - Some(SummedMerkNode(10)) - )); - - // TODO: should references take the sum of the referenced element?? - assert!(matches!( - child_sum_tree - .get_feature_type( - b"item2", + Some(SummedMerkNode(0)) + )); + assert!(matches!( + merk.get_feature_type( + b"item4", true, None::<&fn(&[u8], &GroveVersion) -> Option>, grove_version ) .unwrap() .expect("node should exist"), - Some(SummedMerkNode(0)) - )); -} + Some(SummedMerkNode(0)) + )); + assert_eq!( + merk.aggregate_data().expect("expected to get sum").as_i64(), + 40 + ); -#[test] -fn test_sum_tree_with_batches() { - let grove_version = GroveVersion::latest(); - let db = make_test_grovedb(grove_version); - let ops = vec![ - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec()], - b"key1".to_vec(), - Element::empty_sum_tree(), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec()], - b"a".to_vec(), - Element::new_item(vec![214]), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec()], - b"b".to_vec(), - Element::new_sum_item(10), - ), - ]; - db.apply_batch(ops, None, None, grove_version) + // Perform the same test on regular trees + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_tree(), + None, + None, + grove_version, + ) .unwrap() - .expect("should apply batch"); - - let batch = StorageBatch::new(); - let sum_tree = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key1"].as_ref().into(), - Some(&batch), + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item1", + Element::new_item(vec![30]), + None, + None, grove_version, ) .unwrap() - .expect("should open tree"); + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"item2", + Element::new_item(vec![10]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); - assert!(matches!( - sum_tree - .get_feature_type( - b"a", + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert!(matches!( + merk.get_feature_type( + b"item1", true, Some(&Element::value_defined_cost_for_serialized_value), grove_version ) .unwrap() .expect("node should exist"), - Some(SummedMerkNode(0)) - )); - assert!(matches!( - sum_tree - .get_feature_type( - b"b", + Some(BasicMerkNode) + )); + assert!(matches!( + merk.get_feature_type( + b"item2", true, Some(&Element::value_defined_cost_for_serialized_value), grove_version ) .unwrap() .expect("node should exist"), - Some(SummedMerkNode(10)) - )); + Some(BasicMerkNode) + )); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::NoAggregateData + ); + } - // Create new batch to use existing tree - let ops = vec![QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec()], - b"c".to_vec(), - Element::new_sum_item(10), - )]; - db.apply_batch(ops, None, None, grove_version) + #[test] + fn test_sum_tree_feature() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_tree(), + None, + None, + grove_version, + ) .unwrap() - .expect("should apply batch"); + .expect("should insert tree"); - let batch = StorageBatch::new(); - let sum_tree = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key1"].as_ref().into(), - Some(&batch), + let batch = StorageBatch::new(); + + // Sum should be non for non sum tree + // TODO: change interface to retrieve element directly + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::NoAggregateData + ); + + // Add sum tree + db.insert( + [TEST_LEAF].as_ref(), + b"key2", + Element::empty_sum_tree(), + None, + None, grove_version, ) .unwrap() - .expect("should open tree"); - assert!(matches!( - sum_tree - .get_feature_type( - b"c", - true, - None::<&fn(&[u8], &GroveVersion) -> Option>, - grove_version + .expect("should insert sum tree"); + let sum_tree = db + .get([TEST_LEAF].as_ref(), b"key2", None, grove_version) + .unwrap() + .expect("should retrieve tree"); + assert_eq!(sum_tree.sum_value_or_default(), 0); + + // Add sum items to the sum tree + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item1", + Element::new_sum_item(30), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + // TODO: change interface to retrieve element directly + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, ) .unwrap() - .expect("node should exist"), - Some(SummedMerkNode(10)) - )); - assert_eq!( - sum_tree.aggregate_data().expect("expected to get sum"), - AggregateData::Sum(20) - ); - - // Test propagation - // Add a new sum tree with its own sum items, should affect sum of original - // tree - let ops = vec![ - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec()], - b"d".to_vec(), + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(30) + ); + + // Add more sum items + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item2", + Element::new_sum_item(-10), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item3", + Element::new_sum_item(50), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(70) + ); // 30 - 10 + 50 = 70 + + // Add non sum items, result should remain the same + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item4", + Element::new_item(vec![29]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(70) + ); + + // Update existing sum items + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item2", + Element::new_sum_item(10), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item3", + Element::new_sum_item(-100), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(-60) + ); // 30 + 10 - 100 = -60 + + // We can not replace a normal item with a sum item, so let's delete it first + db.delete( + [TEST_LEAF, b"key2"].as_ref(), + b"item4", + None, + None, + grove_version, + ) + .unwrap() + .expect("expected to delete"); + // Use a large value + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item4", + Element::new_sum_item(10000000), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(9999940) + ); // 30 + + // 10 - + // 100 + + // 10000000 + } + + #[test] + fn test_sum_tree_overflow() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + db.insert( + [TEST_LEAF].as_ref(), + b"key", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + let batch = StorageBatch::new(); + + // Sum should be non for non sum tree + // TODO: change interface to retrieve element directly + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::NoAggregateData + ); + + // Add sum tree + db.insert( + [TEST_LEAF].as_ref(), + b"key2", Element::empty_sum_tree(), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"d".to_vec()], - b"first".to_vec(), - Element::new_sum_item(4), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"d".to_vec()], - b"second".to_vec(), - Element::new_item(vec![4]), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec()], - b"e".to_vec(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert sum tree"); + let sum_tree = db + .get([TEST_LEAF].as_ref(), b"key2", None, grove_version) + .unwrap() + .expect("should retrieve tree"); + assert_eq!(sum_tree.sum_value_or_default(), 0); + + // Add sum items to the sum tree + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item1", + Element::new_sum_item(SumValue::MAX), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + // TODO: change interface to retrieve element directly + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(SumValue::MAX) + ); + + // Subtract 10 from Max should work + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item2", + Element::new_sum_item(-10), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(SumValue::MAX - 10) + ); + + // Add 20 from Max should overflow + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item3", + Element::new_sum_item(20), + None, + None, + grove_version, + ) + .unwrap() + .expect_err("should not be able to insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(SumValue::MAX - 10) + ); + + // Add non sum items, result should remain the same + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item4", + Element::new_item(vec![29]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(SumValue::MAX - 10) + ); + + // Update existing sum item will overflow + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item2", + Element::new_sum_item(10), // we are replacing -10 with 10 + None, + None, + grove_version, + ) + .unwrap() + .expect_err("should not be able to insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(SumValue::MAX - 10) + ); + + // Update existing sum item will overflow + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item2", + Element::new_sum_item(SumValue::MIN), // we are replacing -10 with SumValue::MIN + None, + None, + grove_version, + ) + .unwrap() + .expect("should be able to insert item"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(-1) + ); + + db.insert( + [TEST_LEAF, b"key2"].as_ref(), + b"item3", + Element::new_sum_item(-40), + None, + None, + grove_version, + ) + .unwrap() + .expect("should be able to insert item"); + + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(-41) + ); + + // Deleting item1 should make us overflow + db.delete( + [TEST_LEAF, b"key2"].as_ref(), + b"item1", + None, + None, + grove_version, + ) + .unwrap() + .expect_err("expected not be able to delete"); + let merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + merk.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(-41) + ); + } + + #[test] + fn test_sum_tree_propagation() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + // Tree + // SumTree + // SumTree + // Item1 + // SumItem1 + // SumItem2 + // SumItem3 + db.insert( + [TEST_LEAF].as_ref(), + b"key", Element::empty_sum_tree(), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], - b"first".to_vec(), - Element::new_sum_item(12), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], - b"second".to_vec(), - Element::new_item(vec![4]), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], - b"third".to_vec(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"tree2", Element::empty_sum_tree(), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![ - TEST_LEAF.to_vec(), - b"key1".to_vec(), - b"e".to_vec(), - b"third".to_vec(), - ], - b"a".to_vec(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"key"].as_ref(), + b"sumitem3", + Element::new_sum_item(20), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + db.insert( + [TEST_LEAF, b"key", b"tree2"].as_ref(), + b"item1", + Element::new_item(vec![2]), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key", b"tree2"].as_ref(), + b"sumitem1", Element::new_sum_item(5), - ), - QualifiedGroveDbOp::insert_or_replace_op( - vec![ + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key", b"tree2"].as_ref(), + b"sumitem2", + Element::new_sum_item(10), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + db.insert( + [TEST_LEAF, b"key", b"tree2"].as_ref(), + b"item2", + Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ TEST_LEAF.to_vec(), + b"key".to_vec(), + b"tree2".to_vec(), + b"sumitem1".to_vec(), + ])), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + + let sum_tree = db + .get([TEST_LEAF].as_ref(), b"key", None, grove_version) + .unwrap() + .expect("should fetch tree"); + assert_eq!(sum_tree.sum_value_or_default(), 35); + + let batch = StorageBatch::new(); + + // Assert node feature types + let test_leaf_merk = db + .open_non_transactional_merk_at_path( + [TEST_LEAF].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert!(matches!( + test_leaf_merk + .get_feature_type( + b"key", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(BasicMerkNode) + )); + + let parent_sum_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert!(matches!( + parent_sum_tree + .get_feature_type( + b"tree2", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(15)) /* 15 because the child sum tree has one sum item of + * value 5 and + * another of value 10 */ + )); + + let child_sum_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key", b"tree2"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert!(matches!( + child_sum_tree + .get_feature_type( + b"item1", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(0)) + )); + assert!(matches!( + child_sum_tree + .get_feature_type( + b"sumitem1", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(5)) + )); + assert!(matches!( + child_sum_tree + .get_feature_type( + b"sumitem2", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(10)) + )); + + // TODO: should references take the sum of the referenced element?? + assert!(matches!( + child_sum_tree + .get_feature_type( + b"item2", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(0)) + )); + } + + #[test] + fn test_sum_tree_with_batches() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + let ops = vec![ + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec()], b"key1".to_vec(), + Element::empty_sum_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"a".to_vec(), + Element::new_item(vec![214]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"b".to_vec(), + Element::new_sum_item(10), + ), + ]; + db.apply_batch(ops, None, None, grove_version) + .unwrap() + .expect("should apply batch"); + + let batch = StorageBatch::new(); + let sum_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key1"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + + assert!(matches!( + sum_tree + .get_feature_type( + b"a", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(0)) + )); + assert!(matches!( + sum_tree + .get_feature_type( + b"b", + true, + Some(&Element::value_defined_cost_for_serialized_value), + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(10)) + )); + + // Create new batch to use existing tree + let ops = vec![QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"c".to_vec(), + Element::new_sum_item(10), + )]; + db.apply_batch(ops, None, None, grove_version) + .unwrap() + .expect("should apply batch"); + + let batch = StorageBatch::new(); + let sum_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key1"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert!(matches!( + sum_tree + .get_feature_type( + b"c", + true, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version + ) + .unwrap() + .expect("node should exist"), + Some(SummedMerkNode(10)) + )); + assert_eq!( + sum_tree.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(20) + ); + + // Test propagation + // Add a new sum tree with its own sum items, should affect sum of original + // tree + let ops = vec![ + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + b"d".to_vec(), + Element::empty_sum_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"d".to_vec()], + b"first".to_vec(), + Element::new_sum_item(4), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"d".to_vec()], + b"second".to_vec(), + Element::new_item(vec![4]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], b"e".to_vec(), + Element::empty_sum_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], + b"first".to_vec(), + Element::new_sum_item(12), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], + b"second".to_vec(), + Element::new_item(vec![4]), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"e".to_vec()], b"third".to_vec(), - ], - b"b".to_vec(), - Element::new_item(vec![5]), - ), - ]; - db.apply_batch(ops, None, None, grove_version) - .unwrap() - .expect("should apply batch"); + Element::empty_sum_tree(), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![ + TEST_LEAF.to_vec(), + b"key1".to_vec(), + b"e".to_vec(), + b"third".to_vec(), + ], + b"a".to_vec(), + Element::new_sum_item(5), + ), + QualifiedGroveDbOp::insert_or_replace_op( + vec![ + TEST_LEAF.to_vec(), + b"key1".to_vec(), + b"e".to_vec(), + b"third".to_vec(), + ], + b"b".to_vec(), + Element::new_item(vec![5]), + ), + ]; + db.apply_batch(ops, None, None, grove_version) + .unwrap() + .expect("should apply batch"); - let batch = StorageBatch::new(); - let sum_tree = db - .open_non_transactional_merk_at_path( - [TEST_LEAF, b"key1"].as_ref().into(), - Some(&batch), - grove_version, - ) - .unwrap() - .expect("should open tree"); - assert_eq!( - sum_tree.aggregate_data().expect("expected to get sum"), - AggregateData::Sum(41) - ); + let batch = StorageBatch::new(); + let sum_tree = db + .open_non_transactional_merk_at_path( + [TEST_LEAF, b"key1"].as_ref().into(), + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + assert_eq!( + sum_tree.aggregate_data().expect("expected to get sum"), + AggregateData::Sum(41) + ); + } } diff --git a/merk/src/estimated_costs/average_case_costs.rs b/merk/src/estimated_costs/average_case_costs.rs index f5a8fdd3..9ff58aef 100644 --- a/merk/src/estimated_costs/average_case_costs.rs +++ b/merk/src/estimated_costs/average_case_costs.rs @@ -2,10 +2,7 @@ #[cfg(feature = "full")] use grovedb_costs::{CostResult, CostsExt, OperationCost}; -use grovedb_version::{ - error::GroveVersionError, - version::{FeatureVersion, GroveVersion}, -}; +use grovedb_version::{error::GroveVersionError, version::GroveVersion}; #[cfg(feature = "full")] use integer_encoding::VarInt; diff --git a/merk/src/tree/mod.rs b/merk/src/tree/mod.rs index 2d24825e..7600246b 100644 --- a/merk/src/tree/mod.rs +++ b/merk/src/tree/mod.rs @@ -458,7 +458,7 @@ impl TreeNode { match link.aggregateData() { AggregateData::NoAggregateData => 0, AggregateData::Sum(s) => s.encode_var_vec().len() as u32, - AggregateData::BigSum(s) => 16 as u32, + AggregateData::BigSum(_) => 16 as u32, AggregateData::Count(c) => c.encode_var_vec().len() as u32, }, ) @@ -506,7 +506,7 @@ impl TreeNode { Some(link) => match link.aggregateData() { AggregateData::NoAggregateData => Ok(0), AggregateData::Sum(s) => Ok(s), - AggregateData::BigSum(s) => Err(Error::BigSumTreeUnderNormalSumTree( + AggregateData::BigSum(_) => Err(Error::BigSumTreeUnderNormalSumTree( "for aggregate data as i64".to_string(), )), AggregateData::Count(c) => { @@ -535,7 +535,7 @@ impl TreeNode { Ok(s as u64) } } - AggregateData::BigSum(s) => Err(Error::BigSumTreeUnderNormalSumTree( + AggregateData::BigSum(_) => Err(Error::BigSumTreeUnderNormalSumTree( "for aggregate data as u64".to_string(), )), AggregateData::Count(c) => Ok(c),