From fa4834bc1286ca0687c5a5940ba7d9f7ed12dd7a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 28 Sep 2024 00:22:27 +0700 Subject: [PATCH 1/6] feat: added a convenience method that will return an existing item during a check for insertion --- grovedb/src/operations/insert/mod.rs | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index af7629ae..0177b1bc 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -486,6 +486,7 @@ impl GroveDb { } /// Insert if not exists + /// The bool return is whether we were able to insert pub fn insert_if_not_exists<'b, B, P>( &self, path: P, @@ -522,6 +523,45 @@ impl GroveDb { } } + /// Insert if not exists + /// If the item does exist return it + 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, Error> + where + B: AsRef<[u8]> + 'b, + P: Into>, + { + check_grovedb_v0_with_cost!( + "insert_if_not_exists", + grove_version + .grovedb_versions + .operations + .insert + .insert_if_not_exists + ); + + 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 From 102087535df7500907ec347556b64ab5f2698f97 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 28 Sep 2024 00:26:53 +0700 Subject: [PATCH 2/6] feat: added a convenience method that will return an existing item during a check for insertion --- grovedb-version/src/version/grovedb_versions.rs | 1 + grovedb-version/src/version/v1.rs | 1 + grovedb/src/operations/insert/mod.rs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/grovedb-version/src/version/grovedb_versions.rs b/grovedb-version/src/version/grovedb_versions.rs index 51bbdcc6..598fa178 100644 --- a/grovedb-version/src/version/grovedb_versions.rs +++ b/grovedb-version/src/version/grovedb_versions.rs @@ -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, } diff --git a/grovedb-version/src/version/v1.rs b/grovedb-version/src/version/v1.rs index 19bf135e..97cfb38b 100644 --- a/grovedb-version/src/version/v1.rs +++ b/grovedb-version/src/version/v1.rs @@ -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 { diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index 0177b1bc..ca64dcf4 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -543,7 +543,7 @@ impl GroveDb { .grovedb_versions .operations .insert - .insert_if_not_exists + .insert_if_not_exists_return_existing_element ); let mut cost = OperationCost::default(); From e987ff6c0a476ba2319c9651fcc8c15282ac8ab4 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Sat, 28 Sep 2024 01:43:24 +0700 Subject: [PATCH 3/6] Update grovedb/src/operations/insert/mod.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- grovedb/src/operations/insert/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index ca64dcf4..caaaa1f1 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -525,6 +525,21 @@ impl GroveDb { /// 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, 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, From fa3a9ebbf2ca5340a5287cf109e243c5c791f641 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Sat, 28 Sep 2024 01:43:37 +0700 Subject: [PATCH 4/6] Update grovedb/src/operations/insert/mod.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- grovedb/src/operations/insert/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index caaaa1f1..8bc32a2d 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -553,13 +553,14 @@ impl GroveDb { P: Into>, { check_grovedb_v0_with_cost!( - "insert_if_not_exists", + "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(); From ed6a0a0e6140cab5c3e8bb3c70b2da0b442c5778 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Sat, 28 Sep 2024 01:44:05 +0700 Subject: [PATCH 5/6] Update grovedb/src/operations/insert/mod.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- grovedb/src/operations/insert/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index 8bc32a2d..65a9946f 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -486,7 +486,21 @@ impl GroveDb { } /// Insert if not exists - /// The bool return is whether we were able to insert + /// 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` indicating whether the element was inserted (`true`) or already existed (`false`). pub fn insert_if_not_exists<'b, B, P>( &self, path: P, From 24521901d8067271d96f8578a9c875d8e072685e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 28 Sep 2024 01:51:07 +0700 Subject: [PATCH 6/6] added unit tests --- grovedb/src/operations/insert/mod.rs | 127 ++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index 65a9946f..5926fedd 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -488,7 +488,8 @@ impl GroveDb { /// Insert if not exists /// Insert if not exists /// - /// Inserts an element at the specified path and key if it does not already exist. + /// Inserts an element at the specified path and key if it does not already + /// exist. /// /// # Arguments /// @@ -500,7 +501,8 @@ impl GroveDb { /// /// # Returns /// - /// Returns a `CostResult` indicating whether the element was inserted (`true`) or already existed (`false`). + /// Returns a `CostResult` indicating whether the element was + /// inserted (`true`) or already existed (`false`). pub fn insert_if_not_exists<'b, B, P>( &self, path: P, @@ -553,7 +555,9 @@ impl GroveDb { /// /// # Returns /// - /// Returns a `CostResult, Error>`, where `Ok(Some(element))` is the existing element if it was found, or `Ok(None)` if the new element was inserted. + /// Returns a `CostResult, 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, @@ -574,7 +578,6 @@ impl GroveDb { .insert .insert_if_not_exists_return_existing_element ); - ); let mut cost = OperationCost::default(); let subtree_path: SubtreePath<_> = path.into(); @@ -858,6 +861,122 @@ mod tests { 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();