Skip to content

Commit

Permalink
modifications
Browse files Browse the repository at this point in the history
  • Loading branch information
QuantumExplorer committed Sep 29, 2023
1 parent 061f923 commit 29200cd
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 54 deletions.
4 changes: 4 additions & 0 deletions grovedb/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub enum Error {
/// Deleting non empty tree
DeletingNonEmptyTree(&'static str),

#[error("clearing tree with subtrees not allowed error: {0}")]
/// Clearing tree with subtrees not allowed
ClearingTreeWithSubtreesNotAllowed(&'static str),

// Client allowed errors
#[error("just in time element flags client error: {0}")]
/// Just in time element flags client error
Expand Down
203 changes: 149 additions & 54 deletions grovedb/src/operations/delete/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ use crate::{
};
use crate::{raw_decode, util::merk_optional_tx_path_not_empty};

#[cfg(feature = "full")]
#[derive(Clone)]
/// Clear options
pub struct ClearOptions {
/// Check for Subtrees
pub check_for_subtrees: bool,
/// Allow deleting non empty trees if we check for subtrees
pub allow_deleting_subtrees: bool,
/// If we check for subtrees, and we don't allow deleting and there are
/// some, should we error?
pub trying_to_clear_with_subtrees_returns_error: bool,
}

#[cfg(feature = "full")]
impl Default for ClearOptions {
fn default() -> Self {
ClearOptions {
check_for_subtrees: true,
allow_deleting_subtrees: false,
trying_to_clear_with_subtrees_returns_error: true,
}
}
}

#[cfg(feature = "full")]
#[derive(Clone)]
/// Delete options
Expand Down Expand Up @@ -140,11 +164,31 @@ impl GroveDb {
}

/// Delete all elements in a specified subtree
pub fn clear_subtree<'b, B, P>(
/// Returns if we successfully cleared the subtree
fn clear_subtree<'b, B, P>(

Check warning on line 168 in grovedb/src/operations/delete/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

methods `clear_subtree` and `clear_subtree_with_costs` are never used

warning: methods `clear_subtree` and `clear_subtree_with_costs` are never used --> grovedb/src/operations/delete/mod.rs:168:8 | 127 | impl GroveDb { | ------------ methods in this implementation ... 168 | fn clear_subtree<'b, B, P>( | ^^^^^^^^^^^^^ ... 186 | fn clear_subtree_with_costs<'b, B, P>( | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(dead_code)]` on by default
&self,
path: P,
options: Option<ClearOptions>,
transaction: TransactionArg,
) -> CostResult<(), Error>
) -> Result<bool, Error>
where
B: AsRef<[u8]> + 'b,
P: Into<SubtreePath<'b, B>>,
{
self.clear_subtree_with_costs(path, options, transaction)
.unwrap()
}

/// Delete all elements in a specified subtree and get back costs
/// Warning: The costs for this operation are not yet correct, hence we
/// should keep this private for now
/// Returns if we successfully cleared the subtree
fn clear_subtree_with_costs<'b, B, P>(
&self,
path: P,
options: Option<ClearOptions>,
transaction: TransactionArg,
) -> CostResult<bool, Error>
where
B: AsRef<[u8]> + 'b,
P: Into<SubtreePath<'b, B>>,
Expand All @@ -153,6 +197,8 @@ impl GroveDb {
let mut cost = OperationCost::default();
let batch = StorageBatch::new();

let options = options.unwrap_or_default();

if let Some(transaction) = transaction {
let mut merk_to_clear = cost_return_on_error!(
&mut cost,
Expand All @@ -163,36 +209,48 @@ impl GroveDb {
)
);

let mut all_query = Query::new();
all_query.insert_all();
if options.check_for_subtrees {
let mut all_query = Query::new();
all_query.insert_all();

let mut element_iterator =
KVIterator::new(merk_to_clear.storage.raw_iter(), &all_query).unwrap();
let mut element_iterator =
KVIterator::new(merk_to_clear.storage.raw_iter(), &all_query).unwrap();

// delete all nested subtrees
while let Some((key, element_value)) =
element_iterator.next_kv().unwrap_add_cost(&mut cost)
{
let element = raw_decode(&element_value).unwrap();
if element.is_tree() {
cost_return_on_error!(
&mut cost,
self.delete(
subtree_path.clone(),
key.as_slice(),
Some(DeleteOptions {
allow_deleting_non_empty_trees: true,
deleting_non_empty_trees_returns_error: false,
..Default::default()
}),
Some(transaction),
)
);
// delete all nested subtrees
while let Some((key, element_value)) =
element_iterator.next_kv().unwrap_add_cost(&mut cost)
{
let element = raw_decode(&element_value).unwrap();
if element.is_tree() {
if options.allow_deleting_subtrees {
cost_return_on_error!(
&mut cost,
self.delete(
subtree_path.clone(),
key.as_slice(),
Some(DeleteOptions {
allow_deleting_non_empty_trees: true,
deleting_non_empty_trees_returns_error: false,
..Default::default()
}),
Some(transaction),
)
);
} else if options.trying_to_clear_with_subtrees_returns_error {
return Err(Error::ClearingTreeWithSubtreesNotAllowed(
"options do not allow to clear this merk tree as it contains \
subtrees",
))
.wrap_with_cost(cost);
} else {
return Ok(false).wrap_with_cost(cost);
}
}
}
}

// delete non subtree values
merk_to_clear.clear();
cost_return_on_error!(&mut cost, merk_to_clear.clear().map_err(Error::MerkError));

// propagate changes
let mut merk_cache: HashMap<SubtreePath<B>, Merk<PrefixedRocksDbTransactionContext>> =
Expand All @@ -213,36 +271,47 @@ impl GroveDb {
self.open_non_transactional_merk_at_path(subtree_path.clone(), Some(&batch))
);

let mut all_query = Query::new();
all_query.insert_all();
if options.check_for_subtrees {
let mut all_query = Query::new();
all_query.insert_all();

let mut element_iterator =
KVIterator::new(merk_to_clear.storage.raw_iter(), &all_query).unwrap();
let mut element_iterator =
KVIterator::new(merk_to_clear.storage.raw_iter(), &all_query).unwrap();

// delete all nested subtrees
while let Some((key, element_value)) =
element_iterator.next_kv().unwrap_add_cost(&mut cost)
{
let element = raw_decode(&element_value).unwrap();
if element.is_tree() {
cost_return_on_error!(
&mut cost,
self.delete(
subtree_path.clone(),
key.as_slice(),
Some(DeleteOptions {
allow_deleting_non_empty_trees: true,
deleting_non_empty_trees_returns_error: false,
..Default::default()
}),
None
)
);
// delete all nested subtrees
while let Some((key, element_value)) =
element_iterator.next_kv().unwrap_add_cost(&mut cost)
{
let element = raw_decode(&element_value).unwrap();
if options.allow_deleting_subtrees {
if element.is_tree() {
cost_return_on_error!(
&mut cost,
self.delete(
subtree_path.clone(),
key.as_slice(),
Some(DeleteOptions {
allow_deleting_non_empty_trees: true,
deleting_non_empty_trees_returns_error: false,
..Default::default()
}),
None
)
);
}
} else if options.trying_to_clear_with_subtrees_returns_error {
return Err(Error::ClearingTreeWithSubtreesNotAllowed(
"options do not allow to clear this merk tree as it contains subtrees",
))
.wrap_with_cost(cost);
} else {
return Ok(false).wrap_with_cost(cost);
}
}
}

// delete non subtree values
merk_to_clear.clear();
cost_return_on_error!(&mut cost, merk_to_clear.clear().map_err(Error::MerkError));

// propagate changes
let mut merk_cache: HashMap<SubtreePath<B>, Merk<PrefixedRocksDbStorageContext>> =
Expand All @@ -265,7 +334,7 @@ impl GroveDb {
.map_err(Into::into)
);

Ok(()).wrap_with_cost(cost)
Ok(true).wrap_with_cost(cost)
}

/// Delete element with sectional storage function
Expand Down Expand Up @@ -868,7 +937,7 @@ mod tests {
use pretty_assertions::assert_eq;

use crate::{
operations::delete::{delete_up_tree::DeleteUpTreeOptions, DeleteOptions},
operations::delete::{delete_up_tree::DeleteUpTreeOptions, ClearOptions, DeleteOptions},
tests::{
common::EMPTY_PATH, make_empty_grovedb, make_test_grovedb, ANOTHER_TEST_LEAF, TEST_LEAF,
},
Expand Down Expand Up @@ -1634,10 +1703,36 @@ mod tests {
assert_ne!(key1_merk.root_hash().unwrap(), [0; 32]);

let root_hash_before_clear = db.root_hash(None).unwrap().unwrap();
db.clear_subtree([TEST_LEAF, b"key1"].as_ref(), None)
.unwrap()
db.clear_subtree([TEST_LEAF, b"key1"].as_ref(), None, None)
.expect_err("unable to delete subtree");

let success = db
.clear_subtree(
[TEST_LEAF, b"key1"].as_ref(),
Some(ClearOptions {
check_for_subtrees: true,
allow_deleting_subtrees: false,
trying_to_clear_with_subtrees_returns_error: false,
}),
None,
)
.expect("expected no error");
assert!(!success);

let success = db
.clear_subtree(
[TEST_LEAF, b"key1"].as_ref(),
Some(ClearOptions {
check_for_subtrees: true,
allow_deleting_subtrees: true,
trying_to_clear_with_subtrees_returns_error: false,
}),
None,
)
.expect("unable to delete subtree");

assert!(success);

assert!(matches!(
db.get([TEST_LEAF, b"key1"].as_ref(), b"key2", None)
.unwrap(),
Expand Down

0 comments on commit 29200cd

Please sign in to comment.