diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 94464040d..5d16da5f7 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -9,14 +9,10 @@ use std::{ }; pub use merk::proofs::{query::QueryItem, Query}; -use merk::{ - self, - proofs::query::Map, - Merk, -}; +use merk::{self, proofs::query::Map, Merk}; use rs_merkle::{algorithms::Sha256, Hasher, MerkleProof, MerkleTree}; use storage::{ - rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, + rocksdb_storage::{self, PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, Storage, }; pub use subtree::Element; @@ -152,6 +148,71 @@ impl GroveDb { res } + pub fn delete(&mut self, path: &[&[u8]], key: Vec) -> Result<(), Error> { + let element = self.get_raw(path, &key)?; + if path.is_empty() { + // Attempt to delete a root tree leaf + Err(Error::InvalidPath( + "root tree leafs currently cannot be deleted", + )) + } else { + let mut merk = self + .subtrees + .get_mut(&Self::compress_subtree_key(path, None)) + .ok_or(Error::InvalidPath("no subtree found under that path"))?; + Element::delete(&mut merk, key.clone())?; + if let Element::Tree(_) = element { + // TODO: dumb traversal should not be tolerated + let mut concat_path: Vec> = path.iter().map(|x| x.to_vec()).collect(); + concat_path.push(key); + let subtrees_paths = self.find_subtrees(concat_path)?; + for subtree_path in subtrees_paths { + // TODO: eventually we need to do something about this nested slices + let subtree_path_ref: Vec<&[u8]> = + subtree_path.iter().map(|x| x.as_slice()).collect(); + let prefix = Self::compress_subtree_key(&subtree_path_ref, None); + if let Some(subtree) = self.subtrees.remove(&prefix) { + subtree.clear().map_err(|e| { + Error::CorruptedData(format!( + "unable to cleanup tree from storage: {}", + e + )) + })?; + } + } + } + self.propagate_changes(path)?; + Ok(()) + } + } + + // TODO: dumb traversal should not be tolerated + /// Finds keys which are trees for a given subtree recursively. + /// One element means a key of a `merk`, n > 1 elements mean relative path + /// for a deeply nested subtree. + fn find_subtrees(&self, path: Vec>) -> Result>>, Error> { + let mut queue: Vec>> = vec![path.clone()]; + let mut result: Vec>> = vec![path.clone()]; + + while let Some(q) = queue.pop() { + // TODO: eventually we need to do something about this nested slices + let q_ref: Vec<&[u8]> = q.iter().map(|x| x.as_slice()).collect(); + let mut iter = self.elements_iterator(&q_ref)?; + while let Some((key, value)) = iter.next()? { + match value { + Element::Tree(_) => { + let mut sub_path = q.clone(); + sub_path.push(key); + queue.push(sub_path.clone()); + result.push(sub_path); + } + _ => {} + } + } + } + Ok(result) + } + // TODO: split the function into smaller ones pub fn insert( &mut self, diff --git a/grovedb/src/subtree.rs b/grovedb/src/subtree.rs index e6fe576b2..465e2bbd5 100644 --- a/grovedb/src/subtree.rs +++ b/grovedb/src/subtree.rs @@ -29,6 +29,14 @@ impl Element { Element::Tree(Default::default()) } + /// Delete an element from Merk under a key + pub fn delete(merk: &mut Merk, key: Vec) -> Result<(), Error> { + // TODO: delete references on this element + let batch = [(key, Op::Delete)]; + merk.apply(&batch, &[]) + .map_err(|e| Error::CorruptedData(e.to_string())) + } + /// Get an element from Merk under a key; path should be resolved and proper /// Merk should be loaded by this moment pub fn get(merk: &Merk, key: &[u8]) -> Result { diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index 4c9da3c8f..66ee140c4 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -759,3 +759,89 @@ fn test_compress_path_not_possible_collision() { GroveDb::compress_subtree_key(&path_a, None), ); } + +#[test] +fn test_element_deletion() { + let mut db = make_grovedb(); + let element = Element::Item(b"ayy".to_vec()); + db.insert(&[TEST_LEAF], b"key".to_vec(), element.clone()) + .expect("successful insert"); + let root_hash = db.root_tree.root().unwrap(); + assert!(db.delete(&[TEST_LEAF], b"key".to_vec()).is_ok(),); + assert!(matches!( + db.get(&[TEST_LEAF], b"key"), + Err(Error::InvalidPath(_)) + )); + assert_ne!(root_hash, db.root_tree.root().unwrap()); +} + +#[test] +fn test_find_subtrees() { + let element = Element::Item(b"ayy".to_vec()); + let mut db = make_grovedb(); + // Insert some nested subtrees + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + .expect("successful subtree 1 insert"); + db.insert( + &[TEST_LEAF, b"key1"], + b"key2".to_vec(), + Element::empty_tree(), + ) + .expect("successful subtree 2 insert"); + // Insert an element into subtree + db.insert( + &[TEST_LEAF, b"key1", b"key2"], + b"key3".to_vec(), + element.clone(), + ) + .expect("successful value insert"); + db.insert(&[TEST_LEAF], b"key4".to_vec(), Element::empty_tree()) + .expect("successful subtree 3 insert"); + let subtrees = db + .find_subtrees(vec![TEST_LEAF.to_vec()]) + .expect("cannot get subtrees"); + assert_eq!( + vec![ + vec![TEST_LEAF.to_vec()], + vec![TEST_LEAF.to_vec(), b"key1".to_vec()], + vec![TEST_LEAF.to_vec(), b"key4".to_vec()], + vec![TEST_LEAF.to_vec(), b"key1".to_vec(), b"key2".to_vec()], + ], + subtrees + ); +} + +#[test] +fn test_subtree_deletion() { + let element = Element::Item(b"ayy".to_vec()); + let mut db = make_grovedb(); + // Insert some nested subtrees + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + .expect("successful subtree 1 insert"); + db.insert( + &[TEST_LEAF, b"key1"], + b"key2".to_vec(), + Element::empty_tree(), + ) + .expect("successful subtree 2 insert"); + // Insert an element into subtree + db.insert( + &[TEST_LEAF, b"key1", b"key2"], + b"key3".to_vec(), + element.clone(), + ) + .expect("successful value insert"); + db.insert(&[TEST_LEAF], b"key4".to_vec(), Element::empty_tree()) + .expect("successful subtree 3 insert"); + + let root_hash = db.root_tree.root().unwrap(); + db.delete(&[TEST_LEAF], b"key1".to_vec()) + .expect("unable to delete subtree"); + assert!(matches!( + db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3"), + Err(Error::InvalidPath(_)) + )); + assert_eq!(db.subtrees.len(), 3); // TEST_LEAF, ANOTHER_TEST_LEAF and TEST_LEAF.key4 stay + assert!(db.get(&[TEST_LEAF], b"key4").is_ok()); + assert_ne!(root_hash, db.root_tree.root().unwrap()); +} diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index 00ff9d6c3..7d2a62401 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -4,7 +4,7 @@ pub mod chunks; use std::{cell::Cell, cmp::Ordering, collections::LinkedList}; use anyhow::{anyhow, bail, Result}; -use storage::{self, Batch, Storage, Store}; +use storage::{self, Batch, RawIterator, Storage, Store}; use crate::{ proofs::{encode_into, query::QueryItem, Query}, @@ -40,6 +40,21 @@ where Ok(merk) } + /// Deletes tree data + pub fn clear(self) -> Result<()> { + let mut iter = self.raw_iter(); + iter.seek_to_first(); + let mut to_delete = self.storage.new_batch()?; + while iter.valid() { + if let Some(key) = iter.key() { + to_delete.delete(key); + } + iter.next(); + } + self.storage.commit_batch(to_delete)?; + Ok(()) + } + /// Gets an auxiliary value. pub fn get_aux(&self, key: &[u8]) -> Result>> { Ok(self.storage.get_aux(key)?) diff --git a/storage/src/lib.rs b/storage/src/lib.rs index e1ec51f9f..c88461179 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -161,6 +161,8 @@ pub trait RawIterator { fn next(&mut self); + fn prev(&mut self); + fn value(&self) -> Option<&[u8]>; fn key(&self) -> Option<&[u8]>; diff --git a/storage/src/rocksdb_storage.rs b/storage/src/rocksdb_storage.rs index 75725341d..3d2d80456 100644 --- a/storage/src/rocksdb_storage.rs +++ b/storage/src/rocksdb_storage.rs @@ -217,6 +217,10 @@ impl RawIterator for RawPrefixedIterator<'_> { self.rocksdb_iterator.next(); } + fn prev(&mut self) { + self.rocksdb_iterator.prev(); + } + fn value(&self) -> Option<&[u8]> { if self.valid() { self.rocksdb_iterator.value()