diff --git a/grovedb/src/element/insert.rs b/grovedb/src/element/insert.rs index 17a44d4d..c8e5e189 100644 --- a/grovedb/src/element/insert.rs +++ b/grovedb/src/element/insert.rs @@ -12,8 +12,7 @@ use grovedb_version::{ }; use integer_encoding::VarInt; -use super::CascadeOnUpdate; -use crate::{reference_path::ReferencePathType, Element, Error, Hash}; +use crate::{Element, Element::SumItem, Error, Hash}; impl Element { #[cfg(feature = "full")] @@ -23,7 +22,7 @@ impl Element { /// If transaction is passed, the operation will be committed on the /// transaction commit. pub fn insert<'db, K: AsRef<[u8]>, S: StorageContext<'db>>( - self, + &self, merk: &mut Merk, key: K, options: Option, @@ -31,40 +30,21 @@ impl Element { ) -> CostResult<(), Error> { check_grovedb_v0_with_cost!("insert", grove_version.grovedb_versions.element.insert); - let mut original_cost = Default::default(); - - let to_insert = if let Some(mut prev) = cost_return_on_error!( - &mut original_cost, - Self::get_optional_from_storage(&merk.storage, key.as_ref(), grove_version) - ) { - cost_return_on_error_no_add!( - original_cost, - self.promote_to_referenced_variant(&mut prev) - ) - } else { - self - }; - - let serialized = cost_return_on_error_default!(to_insert.serialize(grove_version)); + let serialized = cost_return_on_error_default!(self.serialize(grove_version)); - if !merk.is_sum_tree && to_insert.is_sum_item() { + if !merk.is_sum_tree && self.is_sum_item() { return Err(Error::InvalidInput("cannot add sum item to non sum tree")) - .wrap_with_cost(original_cost); + .wrap_with_cost(Default::default()); } let merk_feature_type = - cost_return_on_error_default!(to_insert.get_feature_type(merk.is_sum_tree)); - let batch_operations = if matches!( - to_insert, - Element::SumItem(..) | Element::SumItemWithBackwardsReferences(..) - ) { - let value_cost = cost_return_on_error_no_add!( - original_cost, - to_insert.get_specialized_cost(grove_version) - ); + cost_return_on_error_default!(self.get_feature_type(merk.is_sum_tree)); + let batch_operations = if matches!(self, SumItem(..)) { + let value_cost = + cost_return_on_error_default!(self.get_specialized_cost(grove_version)); let cost = value_cost - + to_insert.get_flags().as_ref().map_or(0, |flags| { + + self.get_flags().as_ref().map_or(0, |flags| { let flags_len = flags.len() as u32; flags_len + flags_len.required_space() as u32 }); @@ -89,7 +69,6 @@ impl Element { grove_version, ) .map_err(|e| Error::CorruptedData(e.to_string())) - .add_cost(original_cost) } #[cfg(feature = "full")] @@ -115,7 +94,7 @@ impl Element { Err(e) => return Err(e).wrap_with_cost(Default::default()), }; - let entry = if matches!(self, Element::SumItem(..)) { + let entry = if matches!(self, SumItem(..)) { let value_cost = cost_return_on_error_default!(self.get_specialized_cost(grove_version)); @@ -142,7 +121,7 @@ impl Element { /// If transaction is passed, the operation will be committed on the /// transaction commit. pub fn insert_if_not_exists<'db, S: StorageContext<'db>>( - self, + &self, merk: &mut Merk, key: &[u8], options: Option, @@ -210,52 +189,21 @@ impl Element { } } - #[cfg(feature = "full")] - /// Promote `Element` to referenced variant in case the old one was already - /// referenced. - fn promote_to_referenced_variant(self, old_element: &mut Element) -> Result { - if let Some(refs) = old_element.take_backward_references() { - // Since variants with backward references are publicly available, we still have - // to address them, meaning filling in the actual information about references - // from the database by discarding user input. - - match self { - Element::Item(value, flags) - | Element::ItemWithBackwardsReferences(value, _, flags) => { - Ok(Element::ItemWithBackwardsReferences(value, refs, flags)) - } - Element::Reference(ref_path, max_hops, flags) - | Element::BidirectionalReference(ref_path, _, max_hops, flags) => Ok( - Element::BidirectionalReference(ref_path, refs, max_hops, flags), - ), - Element::SumItem(sum, flags) - | Element::SumItemWithBackwardsReferences(sum, _, flags) => { - Ok(Element::SumItemWithBackwardsReferences(sum, refs, flags)) - } - - Element::Tree(..) | Element::SumTree(..) => Err(Error::NotSupported( - "cannot insert subtree in place of a referenced item".to_owned(), - )), - } - } else { - Ok(self) - } - } - #[cfg(feature = "full")] /// Insert an element in Merk under a key if the value is different from /// what already exists; path should be resolved and proper Merk should /// be loaded by this moment If transaction is not passed, the batch /// will be written immediately. If transaction is passed, the operation /// will be committed on the transaction commit. - /// If the value changed, it returns the old element under `Some`. + /// The bool represents if we indeed inserted. + /// If the value changed we return the old element. pub fn insert_if_changed_value<'db, S: StorageContext<'db>>( - self, + &self, merk: &mut Merk, key: &[u8], options: Option, grove_version: &GroveVersion, - ) -> CostResult, Error> { + ) -> CostResult<(bool, Option), Error> { check_grovedb_v0_with_cost!( "insert_if_changed_value", grove_version @@ -265,29 +213,19 @@ impl Element { ); let mut cost = OperationCost::default(); - let mut previous_element = cost_return_on_error!( + let previous_element = cost_return_on_error!( &mut cost, Self::get_optional_from_storage(&merk.storage, key, grove_version) ); - let to_insert = if let Some(prev) = previous_element.as_mut() { - cost_return_on_error_no_add!(cost, self.promote_to_referenced_variant(prev)) - } else { - self + let needs_insert = match &previous_element { + None => true, + Some(previous_element) => previous_element != self, }; - - let changed = previous_element - .as_ref() - .map(|p| !p.eq_no_backreferences(&to_insert)) - .unwrap_or(true); - - if changed { - cost_return_on_error!( - &mut cost, - to_insert.insert(merk, key, options, grove_version) - ); - Ok(previous_element).wrap_with_cost(cost) + if !needs_insert { + Ok((false, None)).wrap_with_cost(cost) } else { - Ok(None).wrap_with_cost(cost) + cost_return_on_error!(&mut cost, self.insert(merk, key, options, grove_version)); + Ok((true, previous_element)).wrap_with_cost(cost) } } @@ -347,43 +285,28 @@ impl Element { /// If transaction is not passed, the batch will be written immediately. /// If transaction is passed, the operation will be committed on the /// transaction commit. - /// Returns `bool` that indicates if a reference propagation is required. pub fn insert_reference<'db, K: AsRef<[u8]>, S: StorageContext<'db>>( - self, + &self, merk: &mut Merk, key: K, referenced_value: Hash, options: Option, grove_version: &GroveVersion, - ) -> CostResult { + ) -> CostResult<(), Error> { check_grovedb_v0_with_cost!( "insert_reference", grove_version.grovedb_versions.element.insert_reference ); - let mut cost = Default::default(); - - let (ref_updated, to_insert) = if let Some(mut prev) = cost_return_on_error!( - &mut cost, - Self::get_optional_from_storage(&merk.storage, key.as_ref(), grove_version) - ) { - ( - !prev.eq_no_backreferences(&self) && prev.has_backward_references(), - cost_return_on_error_no_add!(cost, self.promote_to_referenced_variant(&mut prev)), - ) - } else { - (false, self) - }; - - let serialized = match to_insert.serialize(grove_version) { + let serialized = match self.serialize(grove_version) { Ok(s) => s, - Err(e) => return Err(e).wrap_with_cost(cost), + Err(e) => return Err(e).wrap_with_cost(Default::default()), }; + let mut cost = OperationCost::default(); let merk_feature_type = cost_return_on_error!( &mut cost, - to_insert - .get_feature_type(merk.is_sum_tree) + self.get_feature_type(merk.is_sum_tree) .wrap_with_cost(OperationCost::default()) ); @@ -404,7 +327,6 @@ impl Element { grove_version, ) .map_err(|e| Error::CorruptedData(e.to_string())) - .map_ok(|_| ref_updated) } #[cfg(feature = "full")] @@ -541,52 +463,6 @@ impl Element { batch_operations.push(entry); Ok(()).wrap_with_cost(Default::default()) } - - /// Adds info on reference that points to this element. - pub(crate) fn referenced_from( - mut self, - reference: ReferencePathType, - cascade_on_update: CascadeOnUpdate, - ) -> Result { - match self { - Element::Item(data, flags) => Ok(Element::ItemWithBackwardsReferences( - data, - vec![(reference, cascade_on_update)], - flags, - )), - Element::ItemWithBackwardsReferences(_, ref mut backward_references, _) => { - backward_references.push((reference, cascade_on_update)); - Ok(self) - } - Element::Reference(reference_path, max_hops, flags) => { - Ok(Element::BidirectionalReference( - reference_path, - vec![(reference, cascade_on_update)], - max_hops, - flags, - )) - } - Element::BidirectionalReference(_, ref mut backward_references, ..) => { - backward_references.push((reference, cascade_on_update)); - Ok(self) - } - Element::SumItem(value, flags) => Ok(Element::SumItemWithBackwardsReferences( - value, - vec![(reference, cascade_on_update)], - flags, - )), - Element::SumItemWithBackwardsReferences(_, ref mut backward_references, _) => { - backward_references.push((reference, cascade_on_update)); - Ok(self) - } - Element::Tree(..) => Err(Error::NotSupported( - "Cannot add references pointing to subtrees".to_owned(), - )), - Element::SumTree(..) => Err(Error::NotSupported( - "Cannot add references pointing to sumtrees".to_owned(), - )), - } - } } #[cfg(feature = "full")] @@ -634,13 +510,14 @@ mod tests { merk.commit(grove_version); - let previous = Element::new_item(b"value".to_vec()) + let (inserted, previous) = Element::new_item(b"value".to_vec()) .insert_if_changed_value(&mut merk, b"another-key", None, grove_version) .unwrap() .expect("expected successful insertion 2"); merk.commit(grove_version); + assert!(!inserted); assert_eq!(previous, None); assert_eq!( Element::get(&merk, b"another-key", true, grove_version) @@ -675,11 +552,12 @@ mod tests { let batch = StorageBatch::new(); let mut merk = empty_path_merk(&*storage, &batch, &tx, grove_version); - let previous = Element::new_item(b"value2".to_vec()) + let (inserted, previous) = Element::new_item(b"value2".to_vec()) .insert_if_changed_value(&mut merk, b"another-key", None, grove_version) .unwrap() .expect("expected successful insertion 2"); + assert!(inserted); assert_eq!(previous, Some(Element::new_item(b"value".to_vec())),); storage @@ -704,11 +582,12 @@ mod tests { .insert(&mut merk, b"mykey", None, grove_version) .unwrap() .expect("expected successful insertion"); - let previous = Element::new_item(b"value2".to_vec()) + let (inserted, previous) = Element::new_item(b"value2".to_vec()) .insert_if_changed_value(&mut merk, b"another-key", None, grove_version) .unwrap() .expect("expected successful insertion 2"); + assert!(inserted); assert_eq!(previous, None); assert_eq!( diff --git a/grovedb/src/element/mod.rs b/grovedb/src/element/mod.rs index d607d35e..bbbc6b47 100644 --- a/grovedb/src/element/mod.rs +++ b/grovedb/src/element/mod.rs @@ -22,7 +22,6 @@ pub use query::QueryOptions; mod serialize; #[cfg(any(feature = "full", feature = "verify"))] use std::fmt; -use std::mem; use bincode::{Decode, Encode}; #[cfg(any(feature = "full", feature = "verify"))] @@ -93,7 +92,7 @@ pub enum Element { /// A reference to an object by its path BidirectionalReference( ReferencePathType, - Vec<(ReferencePathType, CascadeOnUpdate)>, + Option<(ReferencePathType, CascadeOnUpdate)>, MaxReferenceHop, Option, ), @@ -308,30 +307,6 @@ impl Element { crate::value_hash(&bytes).map(Result::Ok) } - /// Returns backward references if the `Element` in question participates in - /// bidirectional referencing machinery. - pub(crate) fn take_backward_references( - &mut self, - ) -> Option> { - match self { - Element::BidirectionalReference(_, refs, ..) - | Element::ItemWithBackwardsReferences(_, refs, ..) - | Element::SumItemWithBackwardsReferences(_, refs, ..) - if !refs.is_empty() => - { - Some(mem::take(refs)) - } - _ => None, - } - } - - /// Returns true if there are references to this `Element`. - pub(crate) fn has_backward_references(&self) -> bool { - matches!(self, Element::BidirectionalReference(_, refs, ..) - | Element::ItemWithBackwardsReferences(_, refs, ..) - | Element::SumItemWithBackwardsReferences(_, refs, ..) if !refs.is_empty()) - } - /// Checks elements for equality ignoring backreferences part. pub(crate) fn eq_no_backreferences(&self, other: &Self) -> bool { use Element::*; diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 5b0a3e3c..52bea01e 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -393,59 +393,6 @@ impl GroveDb { subtree.root_hash().map(Ok).add_cost(cost) } - /// Method to propagate updated subtree key changes one level up inside a - /// transaction - fn propagate_changes_with_batch_transaction<'b, B: AsRef<[u8]>>( - &self, - storage_batch: &StorageBatch, - mut merk_cache: HashMap, Merk>, - path: &SubtreePath<'b, B>, - transaction: &Transaction, - grove_version: &GroveVersion, - ) -> CostResult<(), Error> { - let mut cost = OperationCost::default(); - - let mut child_tree = cost_return_on_error_no_add!( - cost, - merk_cache.remove(path).ok_or(Error::CorruptedCodeExecution( - "Merk Cache should always contain the last path", - )) - ); - - let mut current_path = path.clone(); - - while let Some((parent_path, parent_key)) = current_path.derive_parent() { - let mut parent_tree = cost_return_on_error!( - &mut cost, - self.open_batch_transactional_merk_at_path( - storage_batch, - parent_path.clone(), - transaction, - false, - grove_version, - ) - ); - let (root_hash, root_key, sum) = cost_return_on_error!( - &mut cost, - child_tree.root_hash_key_and_sum().map_err(Error::MerkError) - ); - cost_return_on_error!( - &mut cost, - Self::update_tree_item_preserve_flag( - &mut parent_tree, - parent_key, - root_key, - root_hash, - sum, - grove_version, - ) - ); - child_tree = parent_tree; - current_path = parent_path; - } - Ok(()).wrap_with_cost(cost) - } - /// Method to propagate updated subtree key changes one level up inside a /// transaction fn propagate_changes_with_transaction<'b, B: AsRef<[u8]>>( diff --git a/grovedb/src/merk_cache.rs b/grovedb/src/merk_cache.rs index a01a46c3..ab0afc21 100644 --- a/grovedb/src/merk_cache.rs +++ b/grovedb/src/merk_cache.rs @@ -1,852 +1,264 @@ -//! Module dedicated to keep necessary Merks in memory and solve propagation -//! after usage automatically. +//! Module dedicated to keep necessary Merks in memory. use std::{ - cell::RefCell, - collections::{btree_map::Entry, BTreeMap, HashSet}, - mem::{self, MaybeUninit}, - ops::Deref, + cell::{Cell, UnsafeCell}, + collections::{btree_map::Entry, BTreeMap}, + ops::{Deref, DerefMut}, }; use grovedb_costs::{cost_return_on_error, cost_return_on_error_no_add, CostResult, CostsExt}; -use grovedb_merk::{tree::NULL_HASH, CryptoHash, Merk, MerkOptions}; +use grovedb_merk::Merk; use grovedb_path::SubtreePath; -use grovedb_storage::{rocksdb_storage::PrefixedRocksDbTransactionContext, StorageBatch}; +use grovedb_storage::{ + rocksdb_storage::{PrefixedRocksDbTransactionContext, RocksDbStorage}, + StorageBatch, +}; use grovedb_version::version::GroveVersion; -use crate::{ - element::CascadeOnUpdate, reference_path::ReferencePathType, Element, Error, GroveDb, - Transaction, -}; +use crate::{Element, Error, GroveDb, Transaction}; type TxMerk<'db> = Merk>; -type Key = Vec; - -struct CachedMerk<'db> { - to_propagate: bool, - merk: TxMerk<'db>, -} - -// type UpdatedReferences<'b, B> = RefCell, -// Vec)>>; - -/// Helper struct to split `MerkCache` into independent parts to allow splitting -/// borrows, meaning a dependency on the storage part that shall have a certain -/// lifetime won't clash with another dependency unrelated to the storage part. -struct MerkCacheStorage<'db, 'b, B> { - /// Subtrees opened during usage of this cache structure, the wrapper also - /// marks those that were changed. - merks: BTreeMap, CachedMerk<'db>>, - /// GroveDb provides a storage to open Merks against. +/// Structure to keep subtrees open in memory for repeated access. +pub(crate) struct MerkCache<'db, 'b, B: AsRef<[u8]>> { db: &'db GroveDb, - /// Nowadays GroveDb operates solely on transactional storage contexts. + version: &'db GroveVersion, + batch: Box, tx: &'db Transaction<'db>, - /// The `MerkCache` finalization result is a `StorageBatch` of operations. - /// It's then up to the user what actions to take on that result until - /// no further changes can be made to the storage. - batch: &'static StorageBatch, + merks: UnsafeCell, Box<(Cell, TxMerk<'db>)>>>, } -impl<'db, 'b, B: AsRef<[u8]>> MerkCacheStorage<'db, 'b, B> { - fn new(db: &'db GroveDb, tx: &'db Transaction<'db>) -> Self { - MerkCacheStorage { - merks: Default::default(), +impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> { + /// Initialize a new `MerkCache` instance + pub(crate) fn new( + db: &'db GroveDb, + tx: &'db Transaction<'db>, + version: &'db GroveVersion, + ) -> Self { + MerkCache { db, tx, - batch: Box::leak(Box::new(StorageBatch::default())), + version, + merks: Default::default(), + batch: Default::default(), } } - /// Get a mutable Merk reference from the cache. - /// If it doesn't present then it will be opened. - /// Returns `None` if there is no Merk under this path. - fn get_merk_mut_internal<'c>( - &'c mut self, + /// Returns cached Merk reference or opens one if needed. + /// + /// # Panics + /// Borrowing one Merk several times will cause a panic, previous borrow + /// shall reach end of the scope or to be `drop`ped manually. + pub(crate) fn get_merk<'c>( + &'c self, path: SubtreePath<'b, B>, - version: &GroveVersion, - ) -> CostResult<&'c mut CachedMerk<'db>, Error> { + ) -> CostResult, Error> { let mut cost = Default::default(); - match self.merks.entry(path) { - Entry::Occupied(e) => Ok(e.into_mut()).wrap_with_cost(cost), + // SAFETY: there are no other references to `merks` memory at the same time. + let boxed_flag_merk = match unsafe { + self.merks + .get() + .as_mut() + .expect("`UnsafeCell` is never null") + } + .entry(path.clone()) + { Entry::Vacant(e) => { let merk = cost_return_on_error!( &mut cost, self.db.open_transactional_merk_at_path( e.key().clone(), self.tx, - Some(self.batch), - version + // SAFETY: batch is allocated on the heap and we use only shared + // references, so as long as the `Box` allocation + // outlives those references we're safe, + // and it will outlive because Merks are dropped first. + Some(unsafe { + (&*self.batch as *const StorageBatch) + .as_ref() + .expect("`Box` is never null") + }), + self.version ) ); - Ok(e.insert(CachedMerk { - merk, - to_propagate: false, - })) - .wrap_with_cost(cost) + e.insert(Box::new((true.into(), merk))) } - } - } -} - -/// Merk caching structure. -/// -/// Since we usually postpone all writes to the very end with a single RocksDB -/// batch all intermediate changes to subtrees might not be tracked if we reopen -/// those Merks, so it's better to have them cached and proceed through the same -/// structure. Eventually we'll have enough info at the same place to perform -/// necessary propagations as well. -// SAFETY: please consult with other safety docs here before doing any changes -pub(crate) struct MerkCache<'db, 'b, B> { - storage: MerkCacheStorage<'db, 'b, B>, - version: &'db GroveVersion, -} + Entry::Occupied(e) => { + if e.get().0.get() { + e.into_mut() + } else { + panic!("Double borrow of a cached Merk") + } + } + }; -impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> { - pub(crate) fn new<'tx>( - db: &'db GroveDb, - tx: &'db Transaction<'db>, - version: &'db GroveVersion, - ) -> Self { - MerkCache { - storage: MerkCacheStorage::new(db, tx), - version, - // updated_references: Default::default(), - } + let taken_handle_ref: *const Cell = &boxed_flag_merk.0 as *const _; + let merk_ptr: *mut TxMerk<'db> = &mut boxed_flag_merk.1 as *mut _; + + // SAFETY: `MerkHandle` contains two references to the heap allocated memory, + // and we want to be sure that the referenced data will outlive those + // references plus borrowing rules aren't violated (one `&mut` or many + // `&` with no `&mut` at a time). + // + // To make sure changes to the internal structure won't affect existing borrows + // we have an indirection in a form of `Box`, that allows us to move and update + // `MerkCache` with new subtrees and possible reallocations without breaking + // `MerkHandle`'s references. We use `UnsafeCell` to connect lifetimes and check + // in compile time that `MerkHandle`s won't outlive the cache, even though we + // don't hold any references to it, but `&mut` reference would make this borrow + // exclusive for the whole time of `MerkHandle`, so it shall go intially through + // a shared reference. + // + // Borrowing rules are covered slightly more complicated way: + // 1. Of a pair behind heap allocation only Merk is uniquely borrowed by + // `MerkHandle` + // 2. Borrow flag is referenced by `MerkHandle` to be updated on `Drop` and is + // referenced while taking a new `MerkHandle` to check if it was already + // borrowed, that gives us two shared references to the same memory and + // that's allowed, note we're not referring to the Merk part of the pair + // 3. Borrow flag's reference points to a heap allocated memory and will remain + // valid just as the Merk reference to the memory right after the flag + Ok(unsafe { + MerkHandle { + merk: merk_ptr.as_mut().expect("`Box` contents are never null"), + taken_handle: taken_handle_ref + .as_ref() + .expect("`Box` contents are never null"), + } + }) + .wrap_with_cost(cost) } - /// Returns an array of mutable references to different Merks, where each - /// element in the array corresponds to a unique Merk based on its - /// position in the input paths array. - /// - /// # Panics - /// All input paths *must* be unique, otherwise it could provide multiple - /// mutable references to the same memory which is strictly prohibited. - pub(crate) fn get_multi_mut<'c, const N: usize>( - &'c mut self, - paths: [SubtreePath<'b, B>; N], - ) -> CostResult<[MerkHandle<'db, 'c>; N], Error> { - let mut result_uninit = [const { MaybeUninit::>::uninit() }; N]; + /// Consumes `MerkCache` into accumulated batch of uncommited operations + /// with subtrees' root hash propagation done. + pub(crate) fn into_batch(mut self) -> CostResult, Error> { let mut cost = Default::default(); + cost_return_on_error!(&mut cost, self.propagate_subtrees()); - let unique_args: HashSet<_> = paths.iter().collect(); - if unique_args.len() != N { - panic!("`get_multi_mut` keys must be unique"); - } - - for (i, path) in paths.into_iter().enumerate() { - // SAFETY is ensured by tying the lifetime of mutable references to the - // collection itself, preventing them from outliving the collection and - // ensuring exclusive access to the collection's layout through other - // mutable references. The mandatory keys' uniqueness check above makes - // sure no overlapping memory will be referenced. - let merk_ref = unsafe { - MerkHandle::new( - (cost_return_on_error!( - &mut cost, - self.storage - .get_merk_mut_internal(path.clone(), self.version) - ) as *mut CachedMerk<'db>) - .as_mut::<'c>() - .expect("not a null pointer"), - // UpdatedReferenceHandle { - // path, - // updated_references: &self.updated_references, - // }, - &self.version, - ) - }; - result_uninit[i].write(merk_ref); - } - - // SAFETY: An array of `MaybeUninit` references takes the same size as an array - // of references as long as they both have the same number of elements, - // N in our case. `mem::transmute` would represent it better, however, - // due to poor support of const generics in stable Rust we bypass - // compile-time size checks with pointer casts. - let result = - unsafe { (&result_uninit as *const _ as *const [MerkHandle<'db, 'c>; N]).read() }; - mem::forget(result_uninit); - - Ok(result).wrap_with_cost(cost) + // SAFETY: By this time all subtrees are taken and dropped during + // propagation, so there are no more references to the batch and in can be + // safely released into the world. + Ok(self.batch).wrap_with_cost(cost) } - /// Summarizes all performed operations on this `MerkCache` with necessary - /// propagations into a `Storagebatch`. - pub(crate) fn finalize(mut self) -> CostResult, Error> { - let batch_ptr = self.storage.batch as *const _; - - // Propagate updated subtrees' hashes up to the root and dropping all possible - // batch users: - let propagation_result = { - self.propagate_updated_references() - .flat_map_ok(|_| self.propagate_updated_merks()) - }; - - // SAFETY: The batch reference was created by `Box::leak` and restored into a - // `Box` form. No shared usage of that memory exists because the - // `MerkCache` structure was dropped above. - let result_batch = unsafe { Box::from_raw(batch_ptr as *mut _) }; - - // The batch's unsafety cleanup happens regardless of propagation results, so no - // early termination allowed. We do the mapping afterwards: - propagation_result.map_ok(|_| result_batch) - } - - // /// Inserts a reference into a cached Merk. - // /// The reason why this has to be a whole `MerkCache` method is that - // /// references involve opening and modifying several Merks at once, this - // /// makes it out of scope for a single `MerkHandle`. - // pub(crate) fn insert_reference<'c>( - // &'c mut self, - // path: SubtreePath<'b, B>, - // key: &[u8], - // ref_element: ReferenceElement, - // cascade_on_update: bool, - // options: Option, - // ) -> CostResult<(), Error> { - // let mut cost = Default::default(); - - // let follow_reference_result = cost_return_on_error!( - // &mut cost, - // self.follow_reference(path.clone(), key, ref_element.get_ref_path()) - // ); - - // let value_hash = cost_return_on_error!( - // &mut cost, - // follow_reference_result - // .last_element - // .value_hash(&self.version) - // ); - - // // The insertion of a reference requires that its hash value be equal to - // the // hash value of the item in the end of the reference's chain: - // let [mut merk] = cost_return_on_error!(&mut cost, - // self.get_multi_mut([path])); cost_return_on_error!( - // &mut cost, - // merk.insert_internal(key, ref_element.0.clone(), options, - // Some(value_hash)) ); - - // let version = self.version.clone(); // TODO - - // // The closest referenced item's backward references list of the chain - // shall be // updated with a new entry: - // let [mut closest_merk] = cost_return_on_error!( - // &mut cost, - // self.get_multi_mut([follow_reference_result.first_path]) - // ); - // let mut closest_element = cost_return_on_error!( - // &mut cost, - // Element::get( - // closest_merk.deref(), - // &follow_reference_result.first_key, - // true, - // &version - // ) - // ); - // // Updated backward references information: - // closest_element = cost_return_on_error_no_add!( - // cost, - // closest_element.referenced_from(ref_element.to_ref_path(), - // cascade_on_update) ); - // // And write it back: - // cost_return_on_error!( - // &mut cost, - // closest_merk.insert_internal( - // &follow_reference_result.first_key, - // closest_element, - // None, - // Some(value_hash), - // ) - // ); - - // todo!() - // } - - // /// Follows a reference returning the first link in the references chain and - // /// also the referenced item at the end of it. - // fn follow_reference( - // &mut self, - // self_path: SubtreePath<'b, B>, - // self_key: &[u8], - // reference_path: &ReferencePathType, - // ) -> CostResult, Error> { - // todo!() - // } - - /// Perform propagation of references' chains marked as changed. - fn propagate_updated_references(&mut self) -> CostResult<(), Error> { - todo!() - } - - /// Finalizes each Merk starting from the deepest subtrees, updating hashes - /// up to the root. - fn propagate_updated_merks(mut self) -> CostResult<(), Error> { + fn propagate_subtrees(&mut self) -> CostResult<(), Error> { let mut cost = Default::default(); - let version: &'db GroveVersion = self.version; + // This relies on [SubtreePath]'s ordering implementation to put the deepest + // path's first. + while let Some((path, flag_and_merk)) = self.merks.get_mut().pop_first() { + let merk = flag_and_merk.1; + if let Some((parent_path, parent_key)) = path.derive_parent() { + let mut parent_merk = cost_return_on_error!(&mut cost, self.get_merk(parent_path)); - // Picking Merks one by one as long as they have a parent - while let Some((parent_path, parent_key, subtree)) = - self.storage.merks.pop_first().and_then(|(path, subtree)| { - path.derive_parent() - .map(|(parent_path, parent_key)| (parent_path, parent_key, subtree)) - }) - { - // If a cached Merk wasn't changed, we don't need to propagate it: - if !subtree.to_propagate { - continue; + let (root_hash, root_key, sum) = cost_return_on_error!( + &mut cost, + merk.root_hash_key_and_sum().map_err(Error::MerkError) + ); + cost_return_on_error!( + &mut cost, + GroveDb::update_tree_item_preserve_flag( + &mut parent_merk, + parent_key, + root_key, + root_hash, + sum, + self.version, + ) + ); } - - let parent_subtree = cost_return_on_error!( - &mut cost, - self.storage.get_merk_mut_internal(parent_path, version) - ); - parent_subtree.to_propagate = true; - let (root_hash, root_key, root_sum) = cost_return_on_error!( - &mut cost, - subtree.merk.root_hash_key_and_sum().map_err(|e| e.into()) - ); - cost_return_on_error!( - &mut cost, - GroveDb::update_tree_item_preserve_flag( - &mut parent_subtree.merk, - parent_key, - root_key, - root_hash, - root_sum, - version - ) - ); } Ok(()).wrap_with_cost(cost) } } -struct FollowReferenceResult<'b, B> { - first_path: SubtreePath<'b, B>, - first_key: Vec, - last_element: Element, -} - -// /// A wrapper type to ensure `Element` is a reference wherever it is -// required. pub(crate) struct ReferenceElement(Element); - -// impl ReferenceElement { -// fn get_ref_path(&self) -> &ReferencePathType { -// match &self.0 { -// Element::Reference(ref_path, ..) | -// Element::BidirectionalReference(ref_path, ..) => { ref_path -// } -// _ => unreachable!(), -// } -// } - -// fn to_ref_path(self) -> ReferencePathType { -// match self.0 { -// Element::Reference(ref_path, ..) | -// Element::BidirectionalReference(ref_path, ..) => { ref_path -// } -// _ => unreachable!(), -// } -// } -// } - -// impl TryFrom for ReferenceElement { -// type Error = (); - -// fn try_from(value: Element) -> Result { -// match value { -// element @ Element::Reference(..) | element @ -// Element::BidirectionalReference(..) => { Ok(Self(element)) -// } -// _ => Err(()), -// } -// } -// } - -// /// A wrapper type to ensure `Element` is not a reference. -// pub(crate) struct NonReferenceElement(Element); - -// impl TryFrom for NonReferenceElement { -// type Error = (); - -// fn try_from(value: Element) -> Result { -// match value { -// Element::Reference(..) | Element::BidirectionalReference(..) => -// Err(()), element => Ok(Self(element)), -// } -// } -// } - -/// Handle to a cached Merk. +/// Wrapper over `Merk` tree to manage unqiue borrow dynamically. pub(crate) struct MerkHandle<'db, 'c> { - /// Reference to an opened Merk tree with transactional batch storage - /// context merk: &'c mut TxMerk<'db>, - version: &'db GroveVersion, - /// Mark this subtree as a subject to propagate - to_propagate: &'c mut bool, + taken_handle: &'c Cell, } -// /// Helper struct to signal `MerkCache` about updated references. -// struct UpdatedReferenceHandle<'c, 'b, B> { -// path: SubtreePath<'b, B>, -// updated_references: &'c UpdatedReferences<'b, B>, -// } - -// impl<'c, 'b, B: AsRef<[u8]>> UpdatedReferenceHandle<'c, 'b, B> { -// fn mark_updated_reference(&self, key: Key) { -// self.updated_references -// .borrow_mut() -// .insert((self.path.clone(), key)); -// } -// } - -/// It is allowed to dereference `MerkHandle` to regular Merks but in a -/// non-mutable way since we want to track what have been done to those Merks. impl<'db, 'c> Deref for MerkHandle<'db, 'c> { type Target = TxMerk<'db>; fn deref(&self) -> &Self::Target { - &self.merk + self.merk } } -/// This type represents changes made to a particular element, a difference -/// between its initial state and eventual state -enum Delta { - AddItem, - RemoveItem { - backward_references: Vec<(ReferencePathType, CascadeOnUpdate)>, - }, - UpdateItemToReference { - backward_references: Vec<(ReferencePathType, CascadeOnUpdate)>, - new_forward_reference: ReferencePathType, - }, - UpdateItemToItem { - backward_references: Vec<(ReferencePathType, CascadeOnUpdate)>, - }, - AddReference { - new_forward_reference: ReferencePathType, - }, - RemoveReference { - backward_references: Vec<(ReferencePathType, CascadeOnUpdate)>, - ex_forward_reference: ReferencePathType, - }, - UpdateReferenceToItem { - backward_references: Vec<(ReferencePathType, CascadeOnUpdate)>, - ex_forward_reference: ReferencePathType, - }, - UpdateReferenceToReference { - backward_references: Vec<(ReferencePathType, CascadeOnUpdate)>, - new_forward_reference: ReferencePathType, - ex_forward_reference: ReferencePathType, - }, -} - -impl Delta { - fn new(old_element: Option, new_element: Element) -> Option { - match (old_element, new_element) { - ( - None | Some(Element::Tree(..) | Element::SumTree(..)), - Element::Item(..) - | Element::ItemWithBackwardsReferences(..) - | Element::SumItem(..) - | Element::SumItemWithBackwardsReferences(..), - ) => Some(Self::AddItem), - ( - None | Some(Element::Tree(..) | Element::SumTree(..)), - Element::Reference(new_forward_reference, ..) - | Element::BidirectionalReference(new_forward_reference, ..), - ) => Some(Self::AddReference { - new_forward_reference, - }), - (_, Element::Tree(..) | Element::SumTree(..)) => None, - - ( - Some( - Element::ItemWithBackwardsReferences(_, backward_references, _) - | Element::SumItemWithBackwardsReferences(_, backward_references, _), - ), - Element::Item(..) - | Element::ItemWithBackwardsReferences(..) - | Element::SumItem(..) - | Element::SumItemWithBackwardsReferences(..), - ) => Some(Self::UpdateItemToItem { - backward_references, - }), - ( - Some(Element::Item(..) | Element::SumItem(..)), - Element::Item(..) - | Element::ItemWithBackwardsReferences(..) - | Element::SumItem(..) - | Element::SumItemWithBackwardsReferences(..), - ) => Some(Self::UpdateItemToItem { - backward_references: Vec::new(), - }), - ( - Some( - Element::ItemWithBackwardsReferences(_, backward_references, _) - | Element::SumItemWithBackwardsReferences(_, backward_references, _), - ), - Element::Reference(new_forward_reference, ..) - | Element::BidirectionalReference(new_forward_reference, ..), - ) => Some(Self::UpdateItemToReference { - backward_references, - new_forward_reference, - }), - ( - Some(Element::Item(..) | Element::SumItem(..)), - Element::Reference(new_forward_reference, ..) - | Element::BidirectionalReference(new_forward_reference, ..), - ) => Some(Self::UpdateItemToReference { - backward_references: Vec::new(), - new_forward_reference, - }), - ( - Some(Element::Reference(ex_forward_reference, ..)), - Element::Item(..) - | Element::ItemWithBackwardsReferences(..) - | Element::SumItem(..) - | Element::SumItemWithBackwardsReferences(..), - ) => Some(Self::UpdateReferenceToItem { - backward_references: Vec::new(), - ex_forward_reference, - }), - ( - Some(Element::BidirectionalReference( - ex_forward_reference, - backward_references, - .., - )), - Element::Item(..) - | Element::ItemWithBackwardsReferences(..) - | Element::SumItem(..) - | Element::SumItemWithBackwardsReferences(..), - ) => Some(Self::UpdateReferenceToItem { - backward_references, - ex_forward_reference, - }), - ( - Some(Element::Reference(ex_forward_reference, ..)), - Element::Reference(new_forward_reference, ..) - | Element::BidirectionalReference(new_forward_reference, ..), - ) => Some(Self::UpdateReferenceToReference { - backward_references: Vec::new(), - new_forward_reference, - ex_forward_reference, - }), - ( - Some(Element::BidirectionalReference( - ex_forward_reference, - backward_references, - .., - )), - Element::Reference(new_forward_reference, ..) - | Element::BidirectionalReference(new_forward_reference, ..), - ) => Some(Self::UpdateReferenceToReference { - backward_references, - new_forward_reference, - ex_forward_reference, - }), - } - } - - fn new_from_delete(element: Element) -> Option { - match element { - Element::Item(..) | Element::SumItem(..) => Some(Self::RemoveItem { - backward_references: Vec::new(), - }), - Element::ItemWithBackwardsReferences(_, backward_references, ..) - | Element::SumItemWithBackwardsReferences(_, backward_references, ..) => { - Some(Self::RemoveItem { - backward_references, - }) - } - Element::Reference(ex_forward_reference, ..) => Some(Self::RemoveReference { - backward_references: Vec::new(), - ex_forward_reference, - }), - Element::BidirectionalReference(ex_forward_reference, backward_references, ..) => { - Some(Self::RemoveReference { - backward_references, - ex_forward_reference, - }) - } - Element::Tree(..) | Element::SumTree(..) => None, - } +impl<'db, 'c> DerefMut for MerkHandle<'db, 'c> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.merk } } -impl<'db, 'c> MerkHandle<'db, 'c> { - pub(crate) fn insert( - &mut self, - key: &[u8], - element: Element, - options: Option, - ) -> CostResult<(), Error> { - let mut costs = Default::default(); - - let old_value = cost_return_on_error!( - &mut costs, - match element { - Element::Tree(ref value, ..) | Element::SumTree(ref value, ..) => - if value.is_some() { - Err(Error::InvalidCodeExecution( - "a tree should be empty at the moment of insertion when not using \ - batches", - )) - .wrap_with_cost(Default::default()) - } else { - element - .insert_subtree(self.merk, key, NULL_HASH, options, self.version) - .map_ok(|_| None) - }, - _ => element.insert_if_changed_value(self.merk, key, options, self.version), - } - ); - - *self.to_propagate = true; - - Ok(()).wrap_with_cost(costs) - } - - // fn insert_internal( - // &mut self, - // key: &[u8], - // element: Element, - // options: Option, - // referenced_value_hash: Option, - // ) -> CostResult<(), Error> { - // let mut costs = Default::default(); - - // // In case the item that was changed has been referenced, we indicate - // that // references should be propagated after - // if cost_return_on_error!( - // &mut costs, - // match element { - // Element::Item(..) - // | Element::SumItem(..) - // | Element::ItemWithBackwardsReferences(..) - // | Element::SumItemWithBackwardsReferences(..) => element - // .insert_if_changed_value(self.merk, key, options, - // self.version) .map_ok(|r| r.0), - // Element::Reference(..) | Element::BidirectionalReference(..) => - // element .insert_reference( - // self.merk, - // key, - // referenced_value_hash.expect("todo"), - // options, - // self.version - // ), - // Element::Tree(ref value, ..) | Element::SumTree(ref value, ..) => - // if value.is_some() { - // Err(Error::InvalidCodeExecution( - // "a tree should be empty at the moment of insertion - // when not using \ batches", - // )) - // .wrap_with_cost(Default::default()) - // } else { - // element - // .insert_subtree(self.merk, key, NULL_HASH, options, - // self.version) .map_ok(|_| false) - // }, - // } - // ) { - // self.updated_reference_handle - // .mark_updated_reference(key.to_vec()); - // } - - // *self.to_propagate = true; - - // Ok(()).wrap_with_cost(costs) - // } - - fn new(cached_merk: &'c mut CachedMerk<'db>, version: &'db GroveVersion) -> Self { - Self { - merk: &mut cached_merk.merk, - version, - to_propagate: &mut cached_merk.to_propagate, - } +impl<'db, 'c> Drop for MerkHandle<'db, 'c> { + fn drop(&mut self) { + self.taken_handle.set(false); } } #[cfg(test)] mod tests { - use grovedb_costs::OperationCost; use grovedb_path::SubtreePath; - use grovedb_storage::Storage; + use grovedb_storage::StorageBatch; use grovedb_version::version::GroveVersion; use super::MerkCache; use crate::{ - reference_path::ReferencePathType, - tests::{make_deep_tree, ANOTHER_TEST_LEAF, TEST_LEAF}, + tests::{make_deep_tree, make_test_grovedb, TEST_LEAF}, Element, }; - #[test] - fn cached_subtrees_are_free() { - let version = GroveVersion::latest(); - let db = make_deep_tree(&version); - let tx = db.start_transaction(); - let mut cache = MerkCache::new(&db, &tx, version); - - let mut cost: OperationCost = Default::default(); - let [test1, test2] = cache - .get_multi_mut([ - SubtreePath::from(&[TEST_LEAF]), - SubtreePath::from(&[ANOTHER_TEST_LEAF]), - ]) - .unwrap_add_cost(&mut cost) - .expect("unable to get subtrees"); - - // Assert trees aren't empty - assert!(test1.root_hash().unwrap() != [0; 32]); - assert!(test2.root_hash().unwrap() != [0; 32]); - - // Assert some cost been paid - assert!(!cost.is_nothing()); - - let mut next_cost: OperationCost = Default::default(); - let [_test1, _test2] = cache - .get_multi_mut([ - SubtreePath::from(&[TEST_LEAF]), - SubtreePath::from(&[ANOTHER_TEST_LEAF]), - ]) - .unwrap_add_cost(&mut next_cost) - .expect("unable to get subtrees"); - - // Assert it was for free now - assert!(next_cost.is_nothing()); - } - #[test] #[should_panic] - fn overlapping_references_should_panic() { + fn cant_borrow_twice() { let version = GroveVersion::latest(); - let db = make_deep_tree(&version); + let db = make_test_grovedb(&version); let tx = db.start_transaction(); - let mut cache = MerkCache::new(&db, &tx, version); - let _ = cache.get_multi_mut([ - SubtreePath::from(&[TEST_LEAF]), - SubtreePath::from(&[TEST_LEAF]), - ]); + let cache = MerkCache::new(&db, &tx, version); + + cache.get_merk(SubtreePath::empty()).unwrap().unwrap(); + cache.get_merk(SubtreePath::empty()).unwrap().unwrap(); } #[test] - fn changes_to_merk_cache_provide_non_empty_batch() { + fn subtrees_are_propagated() { let version = GroveVersion::latest(); let db = make_deep_tree(&version); let tx = db.start_transaction(); - let mut cache = MerkCache::new(&db, &tx, version); - let [_subtree] = cache - .get_multi_mut([SubtreePath::from(&[TEST_LEAF])]) - .unwrap() - .unwrap(); + let path = SubtreePath::from(&[TEST_LEAF, b"innertree"]); + let item = Element::new_item(b"hello".to_vec()); - // Do nothing and finalize a batch: - assert!(cache.finalize().unwrap().unwrap().len() == 0); + let no_propagation_ops_count = { + let batch = StorageBatch::new(); - let mut cache = MerkCache::new(&db, &tx, version); - let [mut subtree] = cache - .get_multi_mut([SubtreePath::from(&[TEST_LEAF])]) - .unwrap() - .unwrap(); - subtree - .insert( - b"ayy", - Element::new_item(b"lmao".to_vec()).try_into().unwrap(), - None, - ) - .unwrap() - .unwrap(); + let mut merk = db + .open_transactional_merk_at_path(path.clone(), &tx, Some(&batch), &version) + .unwrap() + .unwrap(); - // Do something and finalize another batch: - assert!(cache.finalize().unwrap().unwrap().len() > 0); - } + item.insert(&mut merk, b"k1", None, &version) + .unwrap() + .unwrap(); - #[test] - fn changes_to_merk_are_propagated() { - let version = GroveVersion::latest(); - let db = make_deep_tree(&version); - let tx = db.start_transaction(); - - let pre_hash = db.root_hash(Some(&tx), &version).unwrap().unwrap(); + batch.len() + }; - let mut cache = MerkCache::new(&db, &tx, version); - let [mut subtree] = cache - .get_multi_mut([SubtreePath::from(&[TEST_LEAF, b"innertree"])]) - .unwrap() - .unwrap(); - subtree - .insert( - b"ayy", - Element::new_item(b"lmao".to_vec()).try_into().unwrap(), - None, - ) - .unwrap() - .unwrap(); + let cache = MerkCache::new(&db, &tx, version); - let batch = cache.finalize().unwrap().unwrap(); + let mut merk = cache.get_merk(path).unwrap().unwrap(); - db.db - .commit_multi_context_batch(*batch, Some(&tx)) + item.insert(&mut merk, b"k1", None, &version) .unwrap() .unwrap(); - assert_ne!( - pre_hash, - db.root_hash(Some(&tx), &version).unwrap().unwrap() - ) - } + drop(merk); - // #[test] - // fn changes_to_referenced_values_are_marked_uncommitted() { - // let version = GroveVersion::latest(); - // let db = make_deep_tree(&version); - // let tx = db.start_transaction(); - - // let mut cache = MerkCache::new(&db, &tx, version); - // cache - // .insert_reference( - // SubtreePath::from(&[TEST_LEAF, b"innertree"]), - // b"ayy", - // - // Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ - // ANOTHER_TEST_LEAF.to_vec(), - // b"innertree2".to_vec(), - // b"k3".to_vec(), - // ])) - // .try_into() - // .unwrap(), - // false, - // None, - // ) - // .unwrap() - // .unwrap(); - - // assert!(cache.updated_references.borrow().is_empty()); - - // let [mut subtree] = cache - // .get_multi_mut([SubtreePath::from(&[ANOTHER_TEST_LEAF, - // b"innertree2"])]) .unwrap() - // .unwrap(); - - // subtree - // .insert( - // b"k3", - // Element::new_item(b"huh".to_vec()).try_into().unwrap(), - // None, - // ) - // .unwrap() - // .unwrap(); - - // assert!(!cache.updated_references.borrow().is_empty()); - // } + assert!(cache.into_batch().unwrap().unwrap().len() > no_propagation_ops_count); + } } diff --git a/grovedb/src/operations/delete/mod.rs b/grovedb/src/operations/delete/mod.rs index fb81532b..4accb506 100644 --- a/grovedb/src/operations/delete/mod.rs +++ b/grovedb/src/operations/delete/mod.rs @@ -707,11 +707,11 @@ impl GroveDb { merk_cache.insert(path.clone(), merk_to_delete_tree_from); cost_return_on_error!( &mut cost, - self.propagate_changes_with_batch_transaction( - batch, + self.propagate_changes_with_transaction( merk_cache, - &path, + path, transaction, + batch, grove_version, ) ); diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index 6a07b41b..b6e00be0 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -221,12 +221,7 @@ impl GroveDb { } match element { - Element::Reference(ref reference_path, ..) - | Element::BidirectionalReference(ref reference_path, ..) => { - // TODO: 1. turn referenced value into a backreference kind - // 2. if referencing another reference first, that one shall become - // bidirectional, the rest shall be bidirectional already - + Element::Reference(ref reference_path, ..) => { let path = path.to_vec(); // TODO: need for support for references in path library let reference_path = cost_return_on_error!( &mut cost, @@ -288,16 +283,6 @@ impl GroveDb { } } _ => { - // TODO: Check if overwriting an backreference-flavored item: - // 1. Item/SumItem -- update reference chains - // 2. Replace bidirectional reference with item: Previous in chain -- - // update hashes Next in chain reference -- make one directional - // reference if the last one Next is an actual item -- downgrade from - // backreferenced-flavor if the last one - // 3. Replace bidirectional reference with another reference: Old target - // reference becomes one directional if the last one Old target item is - // downgraded if the last one Update previous in chain with a new - // target cost_return_on_error!( &mut cost, element.insert( @@ -850,8 +835,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 4, /* 1 to get tree, 1 to check prev value for ref propagation, 1 to - * insert, 1 to insert into root tree */ + seek_count: 3, // 1 to get tree, 1 to insert, 1 to insert into root tree storage_cost: StorageCost { added_bytes: 149, replaced_bytes: 0, @@ -918,7 +902,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 6, + seek_count: 5, storage_cost: StorageCost { added_bytes: 170, replaced_bytes: 84, // todo: verify @@ -1001,7 +985,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 8, + seek_count: 7, storage_cost: StorageCost { added_bytes: 170, replaced_bytes: 217, @@ -1080,7 +1064,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 8, + seek_count: 7, storage_cost: StorageCost { added_bytes: 170, replaced_bytes: 217, // todo: verify @@ -1141,7 +1125,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 4, // 1 to get tree, 1 to insert, 1 to insert into root tree + seek_count: 3, // 1 to get tree, 1 to insert, 1 to insert into root tree storage_cost: StorageCost { added_bytes: 153, replaced_bytes: 0, @@ -1406,7 +1390,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 6, // todo: verify this + seek_count: 5, // todo: verify this storage_cost: StorageCost { added_bytes: 150, replaced_bytes: 78, @@ -1471,7 +1455,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 4, // todo: verify this + seek_count: 3, // todo: verify this storage_cost: StorageCost { added_bytes: 156, replaced_bytes: 0, @@ -1565,7 +1549,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 6, // todo: verify this + seek_count: 5, // todo: verify this storage_cost: StorageCost { added_bytes: 156, replaced_bytes: 78, @@ -1661,7 +1645,7 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 6, // todo: verify this + seek_count: 5, // todo: verify this storage_cost: StorageCost { added_bytes: 156, replaced_bytes: 82, @@ -1730,13 +1714,13 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 4, // todo: verify this + seek_count: 3, // todo: verify this storage_cost: StorageCost { added_bytes: 0, replaced_bytes: 112, removed_bytes: NoStorageRemoval }, - storage_loaded_bytes: 124, + storage_loaded_bytes: 77, hash_node_calls: 2, } ); @@ -1784,13 +1768,13 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 7, // todo: verify this + seek_count: 6, // todo: verify this storage_cost: StorageCost { added_bytes: 0, replaced_bytes: 190, removed_bytes: NoStorageRemoval }, - storage_loaded_bytes: 277, // todo verify this + storage_loaded_bytes: 230, // todo verify this hash_node_calls: 8, } ); @@ -1838,13 +1822,13 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 7, // todo: verify this + seek_count: 6, // todo: verify this storage_cost: StorageCost { added_bytes: 0, replaced_bytes: 248, removed_bytes: NoStorageRemoval }, - storage_loaded_bytes: 411, // todo verify this + storage_loaded_bytes: 266, // todo verify this hash_node_calls: 9, } ); @@ -1904,13 +1888,13 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 10, // todo: verify this + seek_count: 9, // todo: verify this storage_cost: StorageCost { added_bytes: 0, replaced_bytes: 409, // todo: verify this removed_bytes: NoStorageRemoval }, - storage_loaded_bytes: 632, // todo verify this + storage_loaded_bytes: 487, // todo verify this hash_node_calls: 11, } ); @@ -1958,13 +1942,13 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 7, // todo: verify this + seek_count: 6, // todo: verify this storage_cost: StorageCost { added_bytes: 0, replaced_bytes: 248, removed_bytes: NoStorageRemoval }, - storage_loaded_bytes: 421, // todo verify this + storage_loaded_bytes: 276, // todo verify this hash_node_calls: 9, } ); @@ -2012,13 +1996,13 @@ mod tests { assert_eq!( cost, OperationCost { - seek_count: 7, // todo: verify this + seek_count: 6, // todo: verify this storage_cost: StorageCost { added_bytes: 1, replaced_bytes: 191, // todo: verify this removed_bytes: NoStorageRemoval }, - storage_loaded_bytes: 279, + storage_loaded_bytes: 231, hash_node_calls: 8, } );