Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added a convenience method that will return an existing item du… #342

Merged
merged 6 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions grovedb-version/src/version/grovedb_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ pub struct GroveDBOperationsInsertVersions {
pub add_element_on_transaction: FeatureVersion,
pub add_element_without_transaction: FeatureVersion,
pub insert_if_not_exists: FeatureVersion,
pub insert_if_not_exists_return_existing_element: FeatureVersion,
pub insert_if_changed_value: FeatureVersion,
}

Expand Down
1 change: 1 addition & 0 deletions grovedb-version/src/version/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub const GROVE_V1: GroveVersion = GroveVersion {
add_element_on_transaction: 0,
add_element_without_transaction: 0,
insert_if_not_exists: 0,
insert_if_not_exists_return_existing_element: 0,
insert_if_changed_value: 0,
},
delete: GroveDBOperationsDeleteVersions {
Expand Down
189 changes: 189 additions & 0 deletions grovedb/src/operations/insert/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,16 @@
})
}

fn insert_on_transaction<'db, 'b, B: AsRef<[u8]>>(
&self,
path: SubtreePath<'b, B>,
key: &[u8],
element: Element,
options: InsertOptions,
transaction: &'db Transaction,
batch: &StorageBatch,
grove_version: &GroveVersion,
) -> CostResult<(), Error> {

Check warning on line 126 in grovedb/src/operations/insert/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

this function has too many arguments (8/7)

warning: this function has too many arguments (8/7) --> grovedb/src/operations/insert/mod.rs:117:5 | 117 | / fn insert_on_transaction<'db, 'b, B: AsRef<[u8]>>( 118 | | &self, 119 | | path: SubtreePath<'b, B>, 120 | | key: &[u8], ... | 125 | | grove_version: &GroveVersion, 126 | | ) -> CostResult<(), Error> { | |______________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
check_grovedb_v0_with_cost!(
"insert_on_transaction",
grove_version
Expand Down Expand Up @@ -214,16 +214,16 @@
/// first make sure other merk exist
/// if it exists, then create merk to be inserted, and get root hash
/// we only care about root hash of merk to be inserted
fn add_element_on_transaction<'db, B: AsRef<[u8]>>(
&'db self,
path: SubtreePath<B>,
key: &[u8],
element: Element,
options: InsertOptions,
transaction: &'db Transaction,
batch: &'db StorageBatch,
grove_version: &GroveVersion,
) -> CostResult<Merk<PrefixedRocksDbTransactionContext<'db>>, Error> {

Check warning on line 226 in grovedb/src/operations/insert/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

this function has too many arguments (8/7)

warning: this function has too many arguments (8/7) --> grovedb/src/operations/insert/mod.rs:217:5 | 217 | / fn add_element_on_transaction<'db, B: AsRef<[u8]>>( 218 | | &'db self, 219 | | path: SubtreePath<B>, 220 | | key: &[u8], ... | 225 | | grove_version: &GroveVersion, 226 | | ) -> CostResult<Merk<PrefixedRocksDbTransactionContext<'db>>, Error> { | |________________________________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
check_grovedb_v0_with_cost!(
"add_element_on_transaction",
grove_version
Expand Down Expand Up @@ -486,6 +486,23 @@
}

/// Insert if not exists
/// Insert if not exists
///
/// Inserts an element at the specified path and key if it does not already
/// exist.
///
/// # Arguments
///
/// * `path` - The path where the element should be inserted.
/// * `key` - The key under which the element should be inserted.
/// * `element` - The element to insert.
/// * `transaction` - The transaction argument, if any.
/// * `grove_version` - The GroveDB version.
///
/// # Returns
///
/// Returns a `CostResult<bool, Error>` indicating whether the element was
/// inserted (`true`) or already existed (`false`).
pub fn insert_if_not_exists<'b, B, P>(
&self,
path: P,
Expand Down Expand Up @@ -522,6 +539,62 @@
}
}

/// Insert if not exists
/// If the item does exist return it
///
/// Inserts an element at the given `path` and `key` if it does not exist.
/// If the element already exists, returns the existing element.
///
/// # Arguments
///
/// * `path` - The path where the element should be inserted.
/// * `key` - The key under which the element should be inserted.
/// * `element` - The element to insert.
/// * `transaction` - The transaction argument, if any.
/// * `grove_version` - The GroveDB version.
///
/// # Returns
///
/// Returns a `CostResult<Option<Element>, Error>`, where
/// `Ok(Some(element))` is the existing element if it was found, or
/// `Ok(None)` if the new element was inserted.
pub fn insert_if_not_exists_return_existing_element<'b, B, P>(
&self,
path: P,
key: &[u8],
element: Element,
transaction: TransactionArg,
grove_version: &GroveVersion,
) -> CostResult<Option<Element>, Error>
where
B: AsRef<[u8]> + 'b,
P: Into<SubtreePath<'b, B>>,
{
check_grovedb_v0_with_cost!(
"insert_if_not_exists_return_existing_element",
grove_version
.grovedb_versions
.operations
.insert
.insert_if_not_exists_return_existing_element
);

let mut cost = OperationCost::default();
let subtree_path: SubtreePath<_> = path.into();

let previous_element = cost_return_on_error!(
&mut cost,
self.get_raw_optional(subtree_path.clone(), key, transaction, grove_version)
);
if previous_element.is_some() {
Ok(previous_element).wrap_with_cost(cost)
} else {
self.insert(subtree_path, key, element, None, transaction, grove_version)
.map_ok(|_| None)
.add_cost(cost)
}
}

/// Insert if the value changed
/// We return if the value was inserted
/// If the value was changed then we return the previous element
Expand Down Expand Up @@ -788,6 +861,122 @@
assert!(matches!(result, Err(Error::InvalidParentLayerPath(_))));
}

#[test]
fn test_insert_if_not_exists_return_existing_element() {
let grove_version = GroveVersion::latest();
let db = make_test_grovedb(grove_version);

let element_key = b"key1";
let new_element = Element::new_item(b"new_value".to_vec());

// Insert a new element and check if it returns None
let result = db
.insert_if_not_exists_return_existing_element(
[TEST_LEAF].as_ref(),
element_key,
new_element.clone(),
None,
grove_version,
)
.unwrap()
.expect("Expected insertion of new element");

assert_eq!(result, None);

// Try inserting the same element again and expect it to return the existing
// element
let result = db
.insert_if_not_exists_return_existing_element(
[TEST_LEAF].as_ref(),
element_key,
Element::new_item(b"another_value".to_vec()),
None,
grove_version,
)
.unwrap()
.expect("Expected to return existing element");

assert_eq!(result, Some(new_element.clone()));

// Check if the existing element is still the original one and not replaced
let fetched_element = db
.get([TEST_LEAF].as_ref(), element_key, None, grove_version)
.unwrap()
.expect("Expected to retrieve the existing element");

assert_eq!(fetched_element, new_element);
}

#[test]
fn test_insert_if_not_exists_return_existing_element_with_transaction() {
let grove_version = GroveVersion::latest();
let db = make_test_grovedb(grove_version);

let element_key = b"key2";
let new_element = Element::new_item(b"transaction_value".to_vec());
let transaction = db.start_transaction();

// Insert a new element within a transaction and check if it returns None
let result = db
.insert_if_not_exists_return_existing_element(
[TEST_LEAF].as_ref(),
element_key,
new_element.clone(),
Some(&transaction),
grove_version,
)
.unwrap()
.expect("Expected insertion of new element in transaction");

assert_eq!(result, None);

// Try inserting the same element again within the transaction
// and expect it to return the existing element
let result = db
.insert_if_not_exists_return_existing_element(
[TEST_LEAF].as_ref(),
element_key,
Element::new_item(b"another_transaction_value".to_vec()),
Some(&transaction),
grove_version,
)
.unwrap()
.expect("Expected to return existing element in transaction");

assert_eq!(result, Some(new_element.clone()));

// Commit the transaction
db.commit_transaction(transaction).unwrap().unwrap();

// Check if the element is still the original one and not replaced
let fetched_element = db
.get([TEST_LEAF].as_ref(), element_key, None, grove_version)
.unwrap()
.expect("Expected to retrieve the existing element after transaction commit");

assert_eq!(fetched_element, new_element);
}

#[test]
fn test_insert_if_not_exists_return_existing_element_invalid_path() {
let grove_version = GroveVersion::latest();
let db = make_test_grovedb(grove_version);

// Try inserting to an invalid path and expect an error
let result = db.insert_if_not_exists_return_existing_element(
[b"invalid_path"].as_ref(),
b"key",
Element::new_item(b"value".to_vec()),
None,
grove_version,
);

assert!(matches!(
result.unwrap(),
Err(Error::InvalidParentLayerPath(_))
));
}

#[test]
fn test_one_insert_item_cost() {
let grove_version = GroveVersion::latest();
Expand Down
Loading