Skip to content

Commit

Permalink
Merge pull request #342 from dashpay/feat/InsertIfNotExistsReturnExis…
Browse files Browse the repository at this point in the history
…tingElement

feat: added a convenience method that will return an existing item du…
  • Loading branch information
QuantumExplorer authored Sep 27, 2024
2 parents fd2f134 + 2452190 commit e813cad
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 0 deletions.
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 @@ -486,6 +486,23 @@ 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.
///
/// # 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 @@ 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<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 @@ 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();
Expand Down

0 comments on commit e813cad

Please sign in to comment.