Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fominok committed Nov 8, 2024
1 parent be17531 commit 0d92ceb
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 15 deletions.
15 changes: 10 additions & 5 deletions grovedb/src/element/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,28 +349,32 @@ impl Element {
/// If transaction is not passed, the batch will be written immediately.
/// If transaction is passed, the operation will be committed on the
/// transaction commit.
/// Returns `bool` that indicates if a reference propagation is required.
pub fn insert_reference<'db, K: AsRef<[u8]>, S: StorageContext<'db>>(
self,
merk: &mut Merk<S>,
key: K,
referenced_value: Hash,
options: Option<MerkOptions>,
grove_version: &GroveVersion,
) -> CostResult<(), Error> {
) -> CostResult<bool, Error> {
check_grovedb_v0_with_cost!(
"insert_reference",
grove_version.grovedb_versions.element.insert_reference
);

let mut cost = Default::default();

let to_insert = if let Some(mut prev) = cost_return_on_error!(
let (ref_updated, to_insert) = if let Some(mut prev) = cost_return_on_error!(
&mut cost,
Self::get_optional_from_storage(&merk.storage, key.as_ref(), grove_version)
) {
cost_return_on_error_no_add!(cost, self.promote_to_referenced_variant(&mut prev))
(
!prev.eq_no_backreferences(&self) && prev.has_backward_references(),
cost_return_on_error_no_add!(cost, self.promote_to_referenced_variant(&mut prev)),
)
} else {
self
(false, self)
};

let serialized = match to_insert.serialize(grove_version) {
Expand Down Expand Up @@ -402,6 +406,7 @@ impl Element {
grove_version,
)
.map_err(|e| Error::CorruptedData(e.to_string()))
.map_ok(|_| ref_updated)
}

#[cfg(feature = "full")]
Expand Down Expand Up @@ -540,7 +545,7 @@ impl Element {
}

/// Adds info on reference that points to this element.
fn referenced_from(
pub(crate) fn referenced_from(

Check warning on line 548 in grovedb/src/element/insert.rs

View workflow job for this annotation

GitHub Actions / clippy

method `referenced_from` is never used

warning: method `referenced_from` is never used --> grovedb/src/element/insert.rs:548:19 | 18 | impl Element { | ------------ method in this implementation ... 548 | pub(crate) fn referenced_from( | ^^^^^^^^^^^^^^^
mut self,
reference: ReferencePathType,
cascade_on_update: CascadeOnUpdate,
Expand Down
13 changes: 13 additions & 0 deletions grovedb/src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,19 @@ impl Element {
_ => false,
}
}

/// Downgrades `Element` variants with backward references to regular
/// variants.
pub(crate) fn cut_backreferences(self) -> Self {

Check warning on line 371 in grovedb/src/element/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

method `cut_backreferences` is never used

warning: method `cut_backreferences` is never used --> grovedb/src/element/mod.rs:371:19 | 286 | impl Element { | ------------ method in this implementation ... 371 | pub(crate) fn cut_backreferences(self) -> Self { | ^^^^^^^^^^^^^^^^^^
match self {
Element::ItemWithBackwardsReferences(value, _, flags) => Element::Item(value, flags),
Element::SumItemWithBackwardsReferences(sum, _, flags) => Element::SumItem(sum, flags),
Element::BidirectionalReference(ref_path, _, max_hops, flags) => {
Element::Reference(ref_path, max_hops, flags)
}
x => x,
}
}
}

#[cfg(any(feature = "full", feature = "visualize"))]
Expand Down
244 changes: 234 additions & 10 deletions grovedb/src/merk_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@
//! after usage automatically.

use std::{
borrow::Cow,
cell::RefCell,
collections::{btree_map::Entry, BTreeMap, HashSet},
mem::{self, MaybeUninit},
ops::Deref,
};

use grovedb_costs::{cost_return_on_error, CostResult, CostsExt};
use grovedb_merk::{Merk, MerkOptions};
use grovedb_costs::{cost_return_on_error, cost_return_on_error_no_add, CostResult, CostsExt};
use grovedb_merk::{tree::NULL_HASH, CryptoHash, Merk, MerkOptions};
use grovedb_path::SubtreePath;
use grovedb_storage::{rocksdb_storage::PrefixedRocksDbTransactionContext, StorageBatch};
use grovedb_version::version::GroveVersion;

use crate::{Element, Error, GroveDb, Transaction};
use crate::{reference_path::ReferencePathType, Element, Error, GroveDb, Transaction};

type TxMerk<'db> = Merk<PrefixedRocksDbTransactionContext<'db>>;

Expand Down Expand Up @@ -192,6 +191,87 @@ impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
propagation_result.map_ok(|_| result_batch)
}

/// Inserts a reference into a cached Merk.
/// The reason why this has to be a whole `MerkCache` method is that
/// references involve opening and modifying several Merks at once, this
/// makes it out of scope for a single `MerkHandle`.
pub(crate) fn insert_reference<'c>(
&'c mut self,
path: SubtreePath<'b, B>,
key: &[u8],
ref_element: ReferenceElement,
cascade_on_update: bool,
options: Option<MerkOptions>,
) -> CostResult<(), Error> {
let mut cost = Default::default();

let follow_reference_result = cost_return_on_error!(
&mut cost,
self.follow_reference(path.clone(), key, ref_element.get_ref_path())
);

let value_hash = cost_return_on_error!(
&mut cost,
follow_reference_result
.last_element
.value_hash(&self.version)

Check warning on line 217 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> grovedb/src/merk_cache.rs:217:29 | 217 | .value_hash(&self.version) | ^^^^^^^^^^^^^ help: change this to: `self.version` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
);

// The insertion of a reference requires that its hash value be equal to the
// hash value of the item in the end of the reference's chain:
let [mut merk] = cost_return_on_error!(&mut cost, self.get_multi_mut([path]));
cost_return_on_error!(
&mut cost,
merk.insert_internal(key, ref_element.0.clone(), options, Some(value_hash))
);

let version = self.version.clone(); // TODO

// The closest referenced item's backward references list of the chain shall be
// updated with a new entry:
let [mut closest_merk] = cost_return_on_error!(
&mut cost,
self.get_multi_mut([follow_reference_result.first_path])
);
let mut closest_element = cost_return_on_error!(
&mut cost,
Element::get(
closest_merk.deref(),
&follow_reference_result.first_key,
true,
&version
)
);
// Updated backward references information:
closest_element = cost_return_on_error_no_add!(
cost,
closest_element.referenced_from(ref_element.to_ref_path(), cascade_on_update)
);
// And write it back:
cost_return_on_error!(
&mut cost,
closest_merk.insert_internal(
&follow_reference_result.first_key,
closest_element,
None,
Some(value_hash),
)
);

todo!()
}

/// Follows a reference returning the first link in the references chain and
/// also the referenced item at the end of it.
fn follow_reference(
&mut self,
self_path: SubtreePath<'b, B>,

Check warning on line 268 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

unused variable: `self_path`

warning: unused variable: `self_path` --> grovedb/src/merk_cache.rs:268:9 | 268 | self_path: SubtreePath<'b, B>, | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_self_path`
self_key: &[u8],

Check warning on line 269 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

unused variable: `self_key`

warning: unused variable: `self_key` --> grovedb/src/merk_cache.rs:269:9 | 269 | self_key: &[u8], | ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_self_key`
reference_path: &ReferencePathType,

Check warning on line 270 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

unused variable: `reference_path`

warning: unused variable: `reference_path` --> grovedb/src/merk_cache.rs:270:9 | 270 | reference_path: &ReferencePathType, | ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_reference_path`
) -> CostResult<FollowReferenceResult<'b, B>, Error> {
todo!()
}

/// Perform propagation of references' chains marked as changed.
fn propagate_updated_references(&mut self) -> CostResult<(), Error> {
todo!()
Expand Down Expand Up @@ -242,6 +322,62 @@ impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
}
}

struct FollowReferenceResult<'b, B> {

Check warning on line 325 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `FollowReferenceResult` is never constructed

warning: struct `FollowReferenceResult` is never constructed --> grovedb/src/merk_cache.rs:325:8 | 325 | struct FollowReferenceResult<'b, B> { | ^^^^^^^^^^^^^^^^^^^^^
first_path: SubtreePath<'b, B>,
first_key: Vec<u8>,
last_element: Element,
}

/// A wrapper type to ensure `Element` is a reference wherever it is required.
pub(crate) struct ReferenceElement(Element);

Check warning on line 332 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

field `0` is never read

warning: field `0` is never read --> grovedb/src/merk_cache.rs:332:36 | 332 | pub(crate) struct ReferenceElement(Element); | ---------------- ^^^^^^^ | | | field in this struct | = help: consider removing this field

impl ReferenceElement {
fn get_ref_path(&self) -> &ReferencePathType {

Check warning on line 335 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

methods `get_ref_path` and `to_ref_path` are never used

warning: methods `get_ref_path` and `to_ref_path` are never used --> grovedb/src/merk_cache.rs:335:8 | 334 | impl ReferenceElement { | --------------------- methods in this implementation 335 | fn get_ref_path(&self) -> &ReferencePathType { | ^^^^^^^^^^^^ ... 344 | fn to_ref_path(self) -> ReferencePathType { | ^^^^^^^^^^^
match &self.0 {
Element::Reference(ref_path, ..) | Element::BidirectionalReference(ref_path, ..) => {
ref_path
}
_ => unreachable!(),
}
}

fn to_ref_path(self) -> ReferencePathType {

Check warning on line 344 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

methods with the following characteristics: (`to_*` and `self` type is not `Copy`) usually take `self` by reference

warning: methods with the following characteristics: (`to_*` and `self` type is not `Copy`) usually take `self` by reference --> grovedb/src/merk_cache.rs:344:20 | 344 | fn to_ref_path(self) -> ReferencePathType { | ^^^^ | = help: consider choosing a less ambiguous name = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention = note: `#[warn(clippy::wrong_self_convention)]` on by default
match self.0 {
Element::Reference(ref_path, ..) | Element::BidirectionalReference(ref_path, ..) => {
ref_path
}
_ => unreachable!(),
}
}
}

impl TryFrom<Element> for ReferenceElement {
type Error = ();

fn try_from(value: Element) -> Result<Self, Self::Error> {
match value {
element @ Element::Reference(..) | element @ Element::BidirectionalReference(..) => {
Ok(Self(element))
}
_ => Err(()),
}
}
}

/// A wrapper type to ensure `Element` is not a reference.
pub(crate) struct NonReferenceElement(Element);

Check warning on line 368 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

field `0` is never read

warning: field `0` is never read --> grovedb/src/merk_cache.rs:368:39 | 368 | pub(crate) struct NonReferenceElement(Element); | ------------------- ^^^^^^^ | | | field in this struct | = help: consider removing this field

impl TryFrom<Element> for NonReferenceElement {
type Error = ();

fn try_from(value: Element) -> Result<Self, Self::Error> {
match value {
Element::Reference(..) | Element::BidirectionalReference(..) => Err(()),
element => Ok(Self(element)),
}
}
}

/// Handle to a cached Merk.
pub(crate) struct MerkHandle<'db, 'c, 'b, B> {

Check warning on line 382 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `MerkHandle` is never constructed

warning: struct `MerkHandle` is never constructed --> grovedb/src/merk_cache.rs:382:19 | 382 | pub(crate) struct MerkHandle<'db, 'c, 'b, B> { | ^^^^^^^^^^
merk: &'c mut TxMerk<'db>,
Expand Down Expand Up @@ -276,24 +412,60 @@ impl<'db, 'c, 'b, B> Deref for MerkHandle<'db, 'c, 'b, B> {

impl<'db, 'c, 'b, B: AsRef<[u8]>> MerkHandle<'db, 'c, 'b, B> {
pub(crate) fn insert(

Check warning on line 414 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

associated items `insert`, `insert_internal`, and `new` are never used

warning: associated items `insert`, `insert_internal`, and `new` are never used --> grovedb/src/merk_cache.rs:414:19 | 413 | impl<'db, 'c, 'b, B: AsRef<[u8]>> MerkHandle<'db, 'c, 'b, B> { | ------------------------------------------------------------ associated items in this implementation 414 | pub(crate) fn insert( | ^^^^^^ ... 423 | fn insert_internal( | ^^^^^^^^^^^^^^^ ... 474 | fn new( | ^^^
&mut self,
key: &[u8],
NonReferenceElement(element): NonReferenceElement,
options: Option<MerkOptions>,
) -> CostResult<(), Error> {
self.insert_internal(key, element, options, None)
}

fn insert_internal(
&mut self,
key: &[u8],
element: Element,
options: Option<MerkOptions>,
referenced_value_hash: Option<CryptoHash>,
) -> CostResult<(), Error> {
let mut costs = Default::default();

// In case the item that was changed has been referenced, we indicate that
// references should be propagated after
if cost_return_on_error!(
&mut costs,
element.insert_if_changed_value(self.merk, key, options, self.version)
)
.0
{
match element {
Element::Item(..)
| Element::SumItem(..)
| Element::ItemWithBackwardsReferences(..)
| Element::SumItemWithBackwardsReferences(..) => element
.insert_if_changed_value(self.merk, key, options, self.version)
.map_ok(|r| r.0),
Element::Reference(..) | Element::BidirectionalReference(..) => element
.insert_reference(
self.merk,
key,
referenced_value_hash.expect("todo"),
options,
self.version
),
Element::Tree(ref value, ..) | Element::SumTree(ref value, ..) =>
if value.is_some() {
Err(Error::InvalidCodeExecution(
"a tree should be empty at the moment of insertion when not using \
batches",
))
.wrap_with_cost(Default::default())
} else {
element
.insert_subtree(self.merk, key, NULL_HASH, options, self.version)
.map_ok(|_| false)
},
}
) {
self.updated_reference_handle
.mark_updated_reference(key.to_vec());
}

*self.to_propagate = true;

Ok(()).wrap_with_cost(costs)
Expand Down Expand Up @@ -322,6 +494,7 @@ mod tests {

use super::MerkCache;
use crate::{
reference_path::ReferencePathType,
tests::{make_deep_tree, ANOTHER_TEST_LEAF, TEST_LEAF},
Element,
};
Expand Down Expand Up @@ -397,7 +570,11 @@ mod tests {
.unwrap()
.unwrap();
subtree
.insert(b"ayy", Element::new_item(b"lmao".to_vec()), None)
.insert(
b"ayy",
Element::new_item(b"lmao".to_vec()).try_into().unwrap(),
None,
)
.unwrap()
.unwrap();

Expand All @@ -419,7 +596,11 @@ mod tests {
.unwrap()
.unwrap();
subtree
.insert(b"ayy", Element::new_item(b"lmao".to_vec()), None)
.insert(
b"ayy",
Element::new_item(b"lmao".to_vec()).try_into().unwrap(),
None,
)
.unwrap()
.unwrap();

Expand All @@ -435,4 +616,47 @@ mod tests {
db.root_hash(Some(&tx), &version).unwrap().unwrap()
)
}

#[test]
fn changes_to_referenced_values_are_marked_uncommitted() {
let version = GroveVersion::latest();
let db = make_deep_tree(&version);
let tx = db.start_transaction();

let mut cache = MerkCache::new(&db, &tx, version);
cache
.insert_reference(
SubtreePath::from(&[TEST_LEAF, b"innertree"]),
b"ayy",
Element::new_reference(ReferencePathType::AbsolutePathReference(vec![
ANOTHER_TEST_LEAF.to_vec(),
b"innertree2".to_vec(),
b"k3".to_vec(),
]))
.try_into()
.unwrap(),
false,
None,
)
.unwrap()
.unwrap();

assert!(cache.updated_references.borrow().is_empty());

let [mut subtree] = cache
.get_multi_mut([SubtreePath::from(&[ANOTHER_TEST_LEAF, b"innertree2"])])
.unwrap()
.unwrap();

subtree
.insert(
b"k3",
Element::new_item(b"huh".to_vec()).try_into().unwrap(),
None,
)
.unwrap()
.unwrap();

assert!(!cache.updated_references.borrow().is_empty());
}
}

0 comments on commit 0d92ceb

Please sign in to comment.