From a5cb164a1eddfc02e51ad82742b5c0022032d265 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Thu, 23 Dec 2021 21:38:43 +0300 Subject: [PATCH 01/16] replaced rocksdb::DB with rocksdb::OptimisticTransactionDB --- grovedb/src/lib.rs | 17 ++++++------ merk/Cargo.toml | 2 +- merk/src/test_utils/crash_merk.rs | 2 +- merk/src/test_utils/temp_merk.rs | 2 +- storage/Cargo.toml | 2 +- storage/src/rocksdb_storage.rs | 46 +++++++++++++++++++++++++------ 6 files changed, 50 insertions(+), 21 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 2aa0c0f0c..eb011883f 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -15,6 +15,7 @@ use storage::{ rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, Storage, }; +use storage::rocksdb_storage::OptimisticTransactionDB; pub use subtree::Element; /// Limit of possible indirections @@ -46,13 +47,13 @@ pub struct GroveDb { root_leaf_keys: HashMap, usize>, subtrees: HashMap, Merk>, meta_storage: PrefixedRocksDbStorage, - db: Rc, + db: Rc, } impl GroveDb { pub fn open>(path: P) -> Result { let db = Rc::new( - storage::rocksdb_storage::DB::open_cf_descriptors( + storage::rocksdb_storage::OptimisticTransactionDB::open_cf_descriptors( &storage::rocksdb_storage::default_db_opts(), path, storage::rocksdb_storage::column_families(), @@ -96,12 +97,12 @@ impl GroveDb { }) } - pub fn checkpoint>(&self, path: P) -> Result { - storage::rocksdb_storage::Checkpoint::new(&self.db) - .and_then(|x| x.create_checkpoint(&path)) - .map_err(PrefixedRocksDbStorageError::RocksDbError)?; - GroveDb::open(path) - } + // pub fn checkpoint>(&self, path: P) -> Result { + // storage::rocksdb_storage::Checkpoint::new(self.db.as_ref()) + // .and_then(|x| x.create_checkpoint(&path)) + // .map_err(PrefixedRocksDbStorageError::RocksDbError)?; + // GroveDb::open(path) + // } fn store_subtrees_keys_data(&self) -> Result<(), Error> { let prefixes: Vec> = self.subtrees.keys().map(|x| x.clone()).collect(); diff --git a/merk/Cargo.toml b/merk/Cargo.toml index 493fad326..b82930f21 100644 --- a/merk/Cargo.toml +++ b/merk/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" tempdir = "0.3.7" storage = { path = "../storage" } thiserror = "1.0.30" -rocksdb = "0.17.0" +rocksdb = { git = "https://github.com/yiyuanliu/rust-rocksdb", branch = "transaction" } anyhow = "1.0.51" failure = "0.1.8" diff --git a/merk/src/test_utils/crash_merk.rs b/merk/src/test_utils/crash_merk.rs index df007831b..6f301ea60 100644 --- a/merk/src/test_utils/crash_merk.rs +++ b/merk/src/test_utils/crash_merk.rs @@ -14,7 +14,7 @@ use crate::Merk; pub struct CrashMerk { merk: Merk, path: Option, - _db: Rc, + _db: Rc, } impl CrashMerk { diff --git a/merk/src/test_utils/temp_merk.rs b/merk/src/test_utils/temp_merk.rs index fc49a27e5..d784c4945 100644 --- a/merk/src/test_utils/temp_merk.rs +++ b/merk/src/test_utils/temp_merk.rs @@ -12,7 +12,7 @@ use crate::Merk; pub struct TempMerk { pub inner: Merk, pub path: TempDir, - _db: Rc, + _db: Rc, } impl TempMerk { diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 1eddc5da1..b10b5d9a0 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] num_cpus = "1.13.0" -rocksdb = "0.17.0" +rocksdb = { git = "https://github.com/yiyuanliu/rust-rocksdb", branch = "transaction" } tempdir = "0.3.7" thiserror = "1.0.30" diff --git a/storage/src/rocksdb_storage.rs b/storage/src/rocksdb_storage.rs index 541e4cb8a..e60e5d6ec 100644 --- a/storage/src/rocksdb_storage.rs +++ b/storage/src/rocksdb_storage.rs @@ -1,8 +1,8 @@ //! Storage implementation using RocksDB use std::{path::Path, rc::Rc}; -pub use rocksdb::{checkpoint::Checkpoint, Error, DB}; -use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, DBRawIterator, WriteBatch}; +pub use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; +use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, DBRawIterator, WriteBatch, WriteBatchWithTransaction}; use crate::{Batch, RawIterator, Storage}; @@ -32,9 +32,9 @@ pub fn column_families() -> Vec { } /// Create RocksDB with default settings -pub fn default_rocksdb(path: &Path) -> Rc { +pub fn default_rocksdb(path: &Path) -> Rc { Rc::new( - rocksdb::DB::open_cf_descriptors(&default_db_opts(), &path, column_families()) + rocksdb::OptimisticTransactionDB::open_cf_descriptors(&default_db_opts(), &path, column_families()) .expect("cannot create rocksdb"), ) } @@ -47,7 +47,7 @@ fn make_prefixed_key(prefix: Vec, key: &[u8]) -> Vec { /// RocksDB wrapper to store items with prefixes pub struct PrefixedRocksDbStorage { - db: Rc, + db: Rc, prefix: Vec, } @@ -61,7 +61,7 @@ pub enum PrefixedRocksDbStorageError { impl PrefixedRocksDbStorage { /// Wraps RocksDB to prepend prefixes to each operation - pub fn new(db: Rc, prefix: Vec) -> Result { + pub fn new(db: Rc, prefix: Vec) -> Result { Ok(PrefixedRocksDbStorage { prefix, db }) } @@ -93,10 +93,38 @@ impl PrefixedRocksDbStorage { } } +pub type DBRawTransactionIterator<'a> = rocksdb::DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; + +impl RawIterator for DBRawTransactionIterator<'_> { + fn seek_to_first(&mut self) { + DBRawTransactionIterator::seek_to_first(self) + } + + fn seek(&mut self, key: &[u8]) { + DBRawTransactionIterator::seek(self, key) + } + + fn next(&mut self) { + DBRawTransactionIterator::next(self) + } + + fn value(&self) -> Option<&[u8]> { + DBRawTransactionIterator::value(self) + } + + fn key(&self) -> Option<&[u8]> { + DBRawTransactionIterator::key(self) + } + + fn valid(&self) -> bool { + DBRawTransactionIterator::valid(self) + } +} + impl Storage for PrefixedRocksDbStorage { type Batch<'a> = PrefixedRocksDbBatch<'a>; type Error = PrefixedRocksDbStorageError; - type RawIterator<'a> = rocksdb::DBRawIterator<'a>; + type RawIterator<'a> = DBRawTransactionIterator<'a>; fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { self.db @@ -174,7 +202,7 @@ impl Storage for PrefixedRocksDbStorage { fn new_batch<'a>(&'a self) -> Result, Self::Error> { Ok(PrefixedRocksDbBatch { prefix: self.prefix.clone(), - batch: WriteBatch::default(), + batch: WriteBatchWithTransaction::::default(), cf_aux: self.cf_aux()?, cf_roots: self.cf_roots()?, }) @@ -224,7 +252,7 @@ impl RawIterator for rocksdb::DBRawIterator<'_> { /// Wrapper to RocksDB batch pub struct PrefixedRocksDbBatch<'a> { prefix: Vec, - batch: rocksdb::WriteBatch, + batch: rocksdb::WriteBatchWithTransaction, cf_aux: &'a ColumnFamily, cf_roots: &'a ColumnFamily, } From f5daa099ab933cb683e016b0527eb6fdc6ae1dab Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 24 Dec 2021 12:25:34 +0300 Subject: [PATCH 02/16] refactor storage to move every struct in its own file --- grovedb/src/lib.rs | 5 +- storage/src/lib.rs | 57 +++++ storage/src/rocksdb_storage/batch.rs | 49 ++++ .../mod.rs} | 219 ++---------------- storage/src/rocksdb_storage/storage.rs | 159 +++++++++++++ storage/src/rocksdb_storage/transaction.rs | 100 ++++++++ 6 files changed, 388 insertions(+), 201 deletions(-) create mode 100644 storage/src/rocksdb_storage/batch.rs rename storage/src/{rocksdb_storage.rs => rocksdb_storage/mod.rs} (60%) create mode 100644 storage/src/rocksdb_storage/storage.rs create mode 100644 storage/src/rocksdb_storage/transaction.rs diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index eb011883f..1b24b1749 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -97,8 +97,11 @@ impl GroveDb { }) } + // TODO: Checkpoints are currently not implemented for the transactional DB // pub fn checkpoint>(&self, path: P) -> Result { - // storage::rocksdb_storage::Checkpoint::new(self.db.as_ref()) + // // let snapshot = self.db.transaction().snapshot(); + // + // storage::rocksdb_storage::Checkpoint::new(&self.db) // .and_then(|x| x.create_checkpoint(&path)) // .map_err(PrefixedRocksDbStorageError::RocksDbError)?; // GroveDb::open(path) diff --git a/storage/src/lib.rs b/storage/src/lib.rs index e1ec51f9f..a05e0ccb0 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,6 +1,56 @@ #![feature(generic_associated_types)] + +use rocksdb::OptimisticTransactionDB; + pub mod rocksdb_storage; +pub trait Transaction { + /// Storage error type + type Error: std::error::Error + Send + Sync + 'static; + + /// Commit data from the transaction + fn commit(&self) -> Result<(), Self::Error>; + + /// Rollback the transaction + fn rollback(&self) -> Result<(), Self::Error>; + + /// Put `value` into data storage with `key` + fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Put `value` into auxiliary data storage with `key` + fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Put `value` into trees roots storage with `key` + fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Put `value` into GroveDB metadata storage with `key` + fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from data storage + fn delete(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from auxiliary data storage + fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from trees roots storage + fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from GroveDB metadata storage + fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Get entry by `key` from data storage + fn get(&self, key: &[u8]) -> Result>, Self::Error>; + + /// Get entry by `key` from auxiliary data storage + fn get_aux(&self, key: &[u8]) -> Result>, Self::Error>; + + /// Get entry by `key` from trees roots storage + fn get_root(&self, key: &[u8]) -> Result>, Self::Error>; + + /// Get entry by `key` from GroveDB metadata storage + fn get_meta(&self, key: &[u8]) -> Result>, Self::Error>; +} + /// `Storage` is able to store and retrieve arbitrary bytes by key pub trait Storage { /// Storage error type @@ -15,6 +65,8 @@ pub trait Storage { where Self: 'a; + type StorageTransaction: Transaction; + /// Put `value` into data storage with `key` fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; @@ -62,6 +114,9 @@ pub trait Storage { /// Get raw iterator over storage fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a>; + + /// Starts DB transaction + fn transaction(&self) -> Self::StorageTransaction; } impl<'b, S: Storage> Storage for &'b S { @@ -75,6 +130,8 @@ impl<'b, S: Storage> Storage for &'b S { 'b: 'a, = S::RawIterator<'a>; + type StorageTransaction = (); + fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { (*self).put(key, value) } diff --git a/storage/src/rocksdb_storage/batch.rs b/storage/src/rocksdb_storage/batch.rs new file mode 100644 index 000000000..723cf23a6 --- /dev/null +++ b/storage/src/rocksdb_storage/batch.rs @@ -0,0 +1,49 @@ +use rocksdb::ColumnFamily; +use crate::Batch; +use super::make_prefixed_key; + +/// Wrapper to RocksDB batch +pub struct PrefixedRocksDbBatch<'a> { + pub prefix: Vec, + pub batch: rocksdb::WriteBatchWithTransaction, + pub cf_aux: &'a ColumnFamily, + pub cf_roots: &'a ColumnFamily, +} + +impl<'a> Batch for PrefixedRocksDbBatch<'a> { + fn put(&mut self, key: &[u8], value: &[u8]) { + self.batch + .put(make_prefixed_key(self.prefix.clone(), key), value) + } + + fn put_aux(&mut self, key: &[u8], value: &[u8]) { + self.batch.put_cf( + self.cf_aux, + make_prefixed_key(self.prefix.clone(), key), + value, + ) + } + + fn put_root(&mut self, key: &[u8], value: &[u8]) { + self.batch.put_cf( + self.cf_roots, + make_prefixed_key(self.prefix.clone(), key), + value, + ) + } + + fn delete(&mut self, key: &[u8]) { + self.batch + .delete(make_prefixed_key(self.prefix.clone(), key)) + } + + fn delete_aux(&mut self, key: &[u8]) { + self.batch + .delete_cf(self.cf_aux, make_prefixed_key(self.prefix.clone(), key)) + } + + fn delete_root(&mut self, key: &[u8]) { + self.batch + .delete_cf(self.cf_roots, make_prefixed_key(self.prefix.clone(), key)) + } +} \ No newline at end of file diff --git a/storage/src/rocksdb_storage.rs b/storage/src/rocksdb_storage/mod.rs similarity index 60% rename from storage/src/rocksdb_storage.rs rename to storage/src/rocksdb_storage/mod.rs index e60e5d6ec..f2b373b40 100644 --- a/storage/src/rocksdb_storage.rs +++ b/storage/src/rocksdb_storage/mod.rs @@ -1,10 +1,16 @@ //! Storage implementation using RocksDB use std::{path::Path, rc::Rc}; - pub use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, DBRawIterator, WriteBatch, WriteBatchWithTransaction}; +use crate::{Batch, RawIterator, Storage, Transaction}; + +mod transaction; +mod batch; +mod storage; -use crate::{Batch, RawIterator, Storage}; +pub use batch::PrefixedRocksDbBatch; +pub use transaction::PrefixedRocksDbTransaction; +pub use storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}; const AUX_CF_NAME: &str = "aux"; const ROOTS_CF_NAME: &str = "roots"; @@ -45,54 +51,6 @@ fn make_prefixed_key(prefix: Vec, key: &[u8]) -> Vec { prefixed_key } -/// RocksDB wrapper to store items with prefixes -pub struct PrefixedRocksDbStorage { - db: Rc, - prefix: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum PrefixedRocksDbStorageError { - #[error("column family not found: {0}")] - ColumnFamilyNotFound(&'static str), - #[error(transparent)] - RocksDbError(#[from] rocksdb::Error), -} - -impl PrefixedRocksDbStorage { - /// Wraps RocksDB to prepend prefixes to each operation - pub fn new(db: Rc, prefix: Vec) -> Result { - Ok(PrefixedRocksDbStorage { prefix, db }) - } - - /// Get auxiliary data column family - fn cf_aux(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { - self.db - .cf_handle(AUX_CF_NAME) - .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( - AUX_CF_NAME, - )) - } - - /// Get trees roots data column family - fn cf_roots(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { - self.db - .cf_handle(ROOTS_CF_NAME) - .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( - ROOTS_CF_NAME, - )) - } - - /// Get metadata column family - fn cf_meta(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { - self.db - .cf_handle(META_CF_NAME) - .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( - META_CF_NAME, - )) - } -} - pub type DBRawTransactionIterator<'a> = rocksdb::DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; impl RawIterator for DBRawTransactionIterator<'_> { @@ -121,108 +79,6 @@ impl RawIterator for DBRawTransactionIterator<'_> { } } -impl Storage for PrefixedRocksDbStorage { - type Batch<'a> = PrefixedRocksDbBatch<'a>; - type Error = PrefixedRocksDbStorageError; - type RawIterator<'a> = DBRawTransactionIterator<'a>; - - fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - self.db - .put(make_prefixed_key(self.prefix.clone(), key), value)?; - Ok(()) - } - - fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - self.db.put_cf( - self.cf_aux()?, - make_prefixed_key(self.prefix.clone(), key), - value, - )?; - Ok(()) - } - - fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - self.db.put_cf( - self.cf_roots()?, - make_prefixed_key(self.prefix.clone(), key), - value, - )?; - Ok(()) - } - - fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { - self.db - .delete(make_prefixed_key(self.prefix.clone(), key))?; - Ok(()) - } - - fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error> { - self.db - .delete_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?; - Ok(()) - } - - fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error> { - self.db.delete_cf( - self.cf_roots()?, - make_prefixed_key(self.prefix.clone(), key), - )?; - Ok(()) - } - - fn get(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.db.get(make_prefixed_key(self.prefix.clone(), key))?) - } - - fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self - .db - .get_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?) - } - - fn get_root(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.db.get_cf( - self.cf_roots()?, - make_prefixed_key(self.prefix.clone(), key), - )?) - } - - fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - Ok(self.db.put_cf(self.cf_meta()?, key, value)?) - } - - fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { - Ok(self.db.delete_cf(self.cf_meta()?, key)?) - } - - fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.db.get_cf(self.cf_meta()?, key)?) - } - - fn new_batch<'a>(&'a self) -> Result, Self::Error> { - Ok(PrefixedRocksDbBatch { - prefix: self.prefix.clone(), - batch: WriteBatchWithTransaction::::default(), - cf_aux: self.cf_aux()?, - cf_roots: self.cf_roots()?, - }) - } - - fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { - self.db.write(batch.batch)?; - Ok(()) - } - - fn flush(&self) -> Result<(), Self::Error> { - self.db.flush()?; - Ok(()) - } - - fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { - self.db.raw_iterator() - } -} - impl RawIterator for rocksdb::DBRawIterator<'_> { fn seek_to_first(&mut self) { DBRawIterator::seek_to_first(self) @@ -249,52 +105,6 @@ impl RawIterator for rocksdb::DBRawIterator<'_> { } } -/// Wrapper to RocksDB batch -pub struct PrefixedRocksDbBatch<'a> { - prefix: Vec, - batch: rocksdb::WriteBatchWithTransaction, - cf_aux: &'a ColumnFamily, - cf_roots: &'a ColumnFamily, -} - -impl<'a> Batch for PrefixedRocksDbBatch<'a> { - fn put(&mut self, key: &[u8], value: &[u8]) { - self.batch - .put(make_prefixed_key(self.prefix.clone(), key), value) - } - - fn put_aux(&mut self, key: &[u8], value: &[u8]) { - self.batch.put_cf( - self.cf_aux, - make_prefixed_key(self.prefix.clone(), key), - value, - ) - } - - fn put_root(&mut self, key: &[u8], value: &[u8]) { - self.batch.put_cf( - self.cf_roots, - make_prefixed_key(self.prefix.clone(), key), - value, - ) - } - - fn delete(&mut self, key: &[u8]) { - self.batch - .delete(make_prefixed_key(self.prefix.clone(), key)) - } - - fn delete_aux(&mut self, key: &[u8]) { - self.batch - .delete_cf(self.cf_aux, make_prefixed_key(self.prefix.clone(), key)) - } - - fn delete_root(&mut self, key: &[u8]) { - self.batch - .delete_cf(self.cf_roots, make_prefixed_key(self.prefix.clone(), key)) - } -} - #[cfg(test)] mod tests { use std::ops::Deref; @@ -324,7 +134,7 @@ mod tests { default_rocksdb(tmp_dir.path()), b"test".to_vec(), ) - .expect("cannot create prefixed rocksdb storage"), + .expect("cannot create prefixed rocksdb storage"), _tmp_dir: tmp_dir, } } @@ -516,4 +326,13 @@ mod tests { b"yeet" ); } -} + + #[test] + fn transaction_commit_should_work() { + let storage = TempPrefixedStorage::new(); + let mut transaction = storage.transaction(); + transaction.put(b"key1", b"value1"); + transaction.put(b"key2", b"value2"); + transaction.put_root(b"root", b"yeet"); + } +} \ No newline at end of file diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs new file mode 100644 index 000000000..8ec8addee --- /dev/null +++ b/storage/src/rocksdb_storage/storage.rs @@ -0,0 +1,159 @@ +use std::{path::Path, rc::Rc}; +use rocksdb::WriteBatchWithTransaction; +use crate::Storage; +use super::{ + AUX_CF_NAME, ROOTS_CF_NAME, META_CF_NAME, + DBRawTransactionIterator, PrefixedRocksDbBatch, PrefixedRocksDbTransaction, + make_prefixed_key +}; + +/// RocksDB wrapper to store items with prefixes +pub struct PrefixedRocksDbStorage { + pub(crate) db: Rc, + prefix: Vec, +} + +#[derive(thiserror::Error, Debug)] +pub enum PrefixedRocksDbStorageError { + #[error("column family not found: {0}")] + ColumnFamilyNotFound(&'static str), + #[error(transparent)] + RocksDbError(#[from] rocksdb::Error), +} + +impl PrefixedRocksDbStorage { + /// Wraps RocksDB to prepend prefixes to each operation + pub fn new(db: Rc, prefix: Vec) -> Result { + Ok(PrefixedRocksDbStorage { prefix, db }) + } + + /// Get auxiliary data column family + fn cf_aux(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { + self.db + .cf_handle(AUX_CF_NAME) + .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( + AUX_CF_NAME, + )) + } + + /// Get trees roots data column family + fn cf_roots(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { + self.db + .cf_handle(ROOTS_CF_NAME) + .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( + ROOTS_CF_NAME, + )) + } + + /// Get metadata column family + fn cf_meta(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { + self.db + .cf_handle(META_CF_NAME) + .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( + META_CF_NAME, + )) + } +} + +impl Storage for PrefixedRocksDbStorage { + type Batch<'a> = PrefixedRocksDbBatch<'a>; + type Error = PrefixedRocksDbStorageError; + type RawIterator<'a> = DBRawTransactionIterator<'a>; + type StorageTransaction<'a> = PrefixedRocksDbTransaction<'a>; + + fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + self.db + .put(make_prefixed_key(self.prefix.clone(), key), value)?; + Ok(()) + } + + fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + self.db.put_cf( + self.cf_aux()?, + make_prefixed_key(self.prefix.clone(), key), + value, + )?; + Ok(()) + } + + fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + self.db.put_cf( + self.cf_roots()?, + make_prefixed_key(self.prefix.clone(), key), + value, + )?; + Ok(()) + } + + fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { + self.db + .delete(make_prefixed_key(self.prefix.clone(), key))?; + Ok(()) + } + + fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error> { + self.db + .delete_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?; + Ok(()) + } + + fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error> { + self.db.delete_cf( + self.cf_roots()?, + make_prefixed_key(self.prefix.clone(), key), + )?; + Ok(()) + } + + fn get(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self.db.get(make_prefixed_key(self.prefix.clone(), key))?) + } + + fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self + .db + .get_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?) + } + + fn get_root(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self.db.get_cf( + self.cf_roots()?, + make_prefixed_key(self.prefix.clone(), key), + )?) + } + + fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + Ok(self.db.put_cf(self.cf_meta()?, key, value)?) + } + + fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { + Ok(self.db.delete_cf(self.cf_meta()?, key)?) + } + + fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self.db.get_cf(self.cf_meta()?, key)?) + } + + fn new_batch<'a>(&'a self) -> Result, Self::Error> { + Ok(PrefixedRocksDbBatch { + prefix: self.prefix.clone(), + batch: WriteBatchWithTransaction::::default(), + cf_aux: self.cf_aux()?, + cf_roots: self.cf_roots()?, + }) + } + + fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { + self.db.write(batch.batch)?; + Ok(()) + } + + fn flush(&self) -> Result<(), Self::Error> { + self.db.flush()?; + Ok(()) + } + + fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { + self.db.raw_iterator() + } +} \ No newline at end of file diff --git a/storage/src/rocksdb_storage/transaction.rs b/storage/src/rocksdb_storage/transaction.rs new file mode 100644 index 000000000..8c72cc70a --- /dev/null +++ b/storage/src/rocksdb_storage/transaction.rs @@ -0,0 +1,100 @@ +use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; + +use crate::Transaction; +use super::{PrefixedRocksDbStorageError, make_prefixed_key}; + +pub struct PrefixedRocksDbTransaction<'a> { + transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, + prefix: Vec, +} +// TODO: Implement snapshots for transactions +impl PrefixedRocksDbTransaction<'_> { + fn new(db: &OptimisticTransactionDB, prefix: Vec) -> Self { + Self { transaction: db.transaction(), prefix } + } +} + +impl Transaction for PrefixedRocksDbTransaction<'_> { + type Error = PrefixedRocksDbStorageError; + + fn commit(&self) { + self.transaction.commit(); + } + + fn rollback(&self) { + self.transaction.rollback(); + } + + fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + self.transaction + .put(make_prefixed_key(self.prefix.clone(), key), value)?; + Ok(()) + } + + fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + self.transaction.put_cf( + self.cf_aux()?, + make_prefixed_key(self.prefix.clone(), key), + value, + )?; + Ok(()) + } + + fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + self.transaction.put_cf( + self.cf_roots()?, + make_prefixed_key(self.prefix.clone(), key), + value, + )?; + Ok(()) + } + + fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + Ok(self.transaction.put_cf(self.cf_meta()?, key, value)?) + } + + fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { + self.transaction + .delete(make_prefixed_key(self.prefix.clone(), key))?; + Ok(()) + } + + fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error> { + self.transaction + .delete_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?; + Ok(()) + } + + fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error> { + self.transaction.delete_cf( + self.cf_roots()?, + make_prefixed_key(self.prefix.clone(), key), + )?; + Ok(()) + } + + fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { + Ok(self.transaction.delete_cf(self.cf_meta()?, key)?) + } + + fn get(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self.transaction.get(make_prefixed_key(self.prefix.clone(), key))?) + } + + fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self + .transaction + .get_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?) + } + + fn get_root(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self.transaction.get_cf( + self.cf_roots()?, + make_prefixed_key(self.prefix.clone(), key), + )?) + } + + fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { + Ok(self.transaction.get_cf(self.cf_meta()?, key)?) + } +} From e0e0be009614f84e709bf62bc753817d3e3c45e2 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 24 Dec 2021 15:34:01 +0300 Subject: [PATCH 03/16] fixed lifetimes for transactions --- grovedb/src/lib.rs | 1 - storage/src/lib.rs | 22 +++++---- storage/src/rocksdb_storage/mod.rs | 4 +- storage/src/rocksdb_storage/storage.rs | 10 ++++- storage/src/rocksdb_storage/transaction.rs | 52 ++++++++++++++++++---- 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 1b24b1749..563d7197e 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -15,7 +15,6 @@ use storage::{ rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, Storage, }; -use storage::rocksdb_storage::OptimisticTransactionDB; pub use subtree::Element; /// Limit of possible indirections diff --git a/storage/src/lib.rs b/storage/src/lib.rs index a05e0ccb0..c6a2e57a3 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,15 +1,12 @@ #![feature(generic_associated_types)] - -use rocksdb::OptimisticTransactionDB; - pub mod rocksdb_storage; pub trait Transaction { /// Storage error type type Error: std::error::Error + Send + Sync + 'static; - /// Commit data from the transaction - fn commit(&self) -> Result<(), Self::Error>; + /// Commit data from the transaction. Consumes transaction + fn commit(self) -> Result<(), Self::Error>; /// Rollback the transaction fn rollback(&self) -> Result<(), Self::Error>; @@ -65,7 +62,9 @@ pub trait Storage { where Self: 'a; - type StorageTransaction: Transaction; + type StorageTransaction<'a>: Transaction + where + Self: 'a; /// Put `value` into data storage with `key` fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; @@ -116,7 +115,7 @@ pub trait Storage { fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a>; /// Starts DB transaction - fn transaction(&self) -> Self::StorageTransaction; + fn transaction<'a>(&'a self) -> Self::StorageTransaction<'a>; } impl<'b, S: Storage> Storage for &'b S { @@ -130,7 +129,10 @@ impl<'b, S: Storage> Storage for &'b S { 'b: 'a, = S::RawIterator<'a>; - type StorageTransaction = (); + type StorageTransaction<'a> + where + 'b: 'a, + = S::StorageTransaction<'a>; fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { (*self).put(key, value) @@ -195,6 +197,10 @@ impl<'b, S: Storage> Storage for &'b S { fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { (*self).raw_iter() } + + fn transaction<'a>(&'a self) -> Self::StorageTransaction<'a> { + (*self).transaction() + } } pub trait Batch { diff --git a/storage/src/rocksdb_storage/mod.rs b/storage/src/rocksdb_storage/mod.rs index f2b373b40..ad4817d15 100644 --- a/storage/src/rocksdb_storage/mod.rs +++ b/storage/src/rocksdb_storage/mod.rs @@ -1,8 +1,8 @@ //! Storage implementation using RocksDB use std::{path::Path, rc::Rc}; pub use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; -use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, DBRawIterator, WriteBatch, WriteBatchWithTransaction}; -use crate::{Batch, RawIterator, Storage, Transaction}; +use rocksdb::{ColumnFamilyDescriptor, DBRawIterator}; +use crate::{RawIterator}; mod transaction; mod batch; diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index 8ec8addee..68b8ff531 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -1,4 +1,4 @@ -use std::{path::Path, rc::Rc}; +use std::rc::Rc; use rocksdb::WriteBatchWithTransaction; use crate::Storage; use super::{ @@ -156,4 +156,12 @@ impl Storage for PrefixedRocksDbStorage { fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { self.db.raw_iterator() } + + fn transaction<'a>(&'a self) -> Self::StorageTransaction<'a> { + PrefixedRocksDbTransaction::new( + self.db.transaction(), + self.prefix.clone(), + &self.db + ) + } } \ No newline at end of file diff --git a/storage/src/rocksdb_storage/transaction.rs b/storage/src/rocksdb_storage/transaction.rs index 8c72cc70a..cb6f0412c 100644 --- a/storage/src/rocksdb_storage/transaction.rs +++ b/storage/src/rocksdb_storage/transaction.rs @@ -1,28 +1,62 @@ -use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; +use rocksdb::{OptimisticTransactionDB}; use crate::Transaction; -use super::{PrefixedRocksDbStorageError, make_prefixed_key}; +use super::{ + PrefixedRocksDbStorageError, + make_prefixed_key, + AUX_CF_NAME, ROOTS_CF_NAME, META_CF_NAME +}; pub struct PrefixedRocksDbTransaction<'a> { transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, prefix: Vec, + db: &'a OptimisticTransactionDB, } // TODO: Implement snapshots for transactions -impl PrefixedRocksDbTransaction<'_> { - fn new(db: &OptimisticTransactionDB, prefix: Vec) -> Self { - Self { transaction: db.transaction(), prefix } +impl<'a> PrefixedRocksDbTransaction<'a> { + pub(crate) fn new(transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, prefix: Vec, db: &'a OptimisticTransactionDB) -> Self { + Self { transaction, prefix, db } + } + + /// Get auxiliary data column family + fn cf_aux(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { + self.db + .cf_handle(AUX_CF_NAME) + .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( + AUX_CF_NAME, + )) + } + + /// Get trees roots data column family + fn cf_roots(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { + self.db + .cf_handle(ROOTS_CF_NAME) + .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( + ROOTS_CF_NAME, + )) + } + + /// Get metadata column family + fn cf_meta(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { + self.db + .cf_handle(META_CF_NAME) + .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( + META_CF_NAME, + )) } } impl Transaction for PrefixedRocksDbTransaction<'_> { type Error = PrefixedRocksDbStorageError; - fn commit(&self) { - self.transaction.commit(); + fn commit(self) -> Result<(), Self::Error> { + self.transaction.commit()?; + Ok(()) } - fn rollback(&self) { - self.transaction.rollback(); + fn rollback(&self) -> Result<(), Self::Error> { + self.transaction.rollback()?; + Ok(()) } fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { From 7980deef2f28eb5227efde96fd350992b2d2f1f3 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 24 Dec 2021 16:36:39 +0300 Subject: [PATCH 04/16] formatted the code --- merk/src/tree/ops.rs | 4 +++- storage/src/lib.rs | 3 +-- storage/src/rocksdb_storage/batch.rs | 5 +++-- storage/src/rocksdb_storage/mod.rs | 24 ++++++++++++++-------- storage/src/rocksdb_storage/storage.rs | 22 ++++++++++---------- storage/src/rocksdb_storage/transaction.rs | 24 ++++++++++++++-------- 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/merk/src/tree/ops.rs b/merk/src/tree/ops.rs index 7df0301ae..cd53d7e66 100644 --- a/merk/src/tree/ops.rs +++ b/merk/src/tree/ops.rs @@ -97,9 +97,11 @@ where // TODO: take from batch so we don't have to clone let mid_tree = Tree::new(mid_key.to_vec(), mid_value.to_vec()); let mid_walker = Walker::new(mid_tree, PanicSource {}); + + // use walker, ignore deleted_keys since it should be empty Ok(mid_walker .recurse(batch, mid_index, true)? - .0 // use walker, ignore deleted_keys since it should be empty + .0 .map(|w| w.into_inner())) } diff --git a/storage/src/lib.rs b/storage/src/lib.rs index c6a2e57a3..42a6871f7 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -5,7 +5,7 @@ pub trait Transaction { /// Storage error type type Error: std::error::Error + Send + Sync + 'static; - /// Commit data from the transaction. Consumes transaction + /// Commit data from the transaction. Consumes the transaction fn commit(self) -> Result<(), Self::Error>; /// Rollback the transaction @@ -128,7 +128,6 @@ impl<'b, S: Storage> Storage for &'b S { where 'b: 'a, = S::RawIterator<'a>; - type StorageTransaction<'a> where 'b: 'a, diff --git a/storage/src/rocksdb_storage/batch.rs b/storage/src/rocksdb_storage/batch.rs index 723cf23a6..729ec7abf 100644 --- a/storage/src/rocksdb_storage/batch.rs +++ b/storage/src/rocksdb_storage/batch.rs @@ -1,6 +1,7 @@ use rocksdb::ColumnFamily; -use crate::Batch; + use super::make_prefixed_key; +use crate::Batch; /// Wrapper to RocksDB batch pub struct PrefixedRocksDbBatch<'a> { @@ -46,4 +47,4 @@ impl<'a> Batch for PrefixedRocksDbBatch<'a> { self.batch .delete_cf(self.cf_roots, make_prefixed_key(self.prefix.clone(), key)) } -} \ No newline at end of file +} diff --git a/storage/src/rocksdb_storage/mod.rs b/storage/src/rocksdb_storage/mod.rs index ad4817d15..bc53efb72 100644 --- a/storage/src/rocksdb_storage/mod.rs +++ b/storage/src/rocksdb_storage/mod.rs @@ -1,16 +1,18 @@ //! Storage implementation using RocksDB use std::{path::Path, rc::Rc}; + pub use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; use rocksdb::{ColumnFamilyDescriptor, DBRawIterator}; -use crate::{RawIterator}; -mod transaction; +use crate::RawIterator; + mod batch; mod storage; +mod transaction; pub use batch::PrefixedRocksDbBatch; -pub use transaction::PrefixedRocksDbTransaction; pub use storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}; +pub use transaction::PrefixedRocksDbTransaction; const AUX_CF_NAME: &str = "aux"; const ROOTS_CF_NAME: &str = "roots"; @@ -40,8 +42,12 @@ pub fn column_families() -> Vec { /// Create RocksDB with default settings pub fn default_rocksdb(path: &Path) -> Rc { Rc::new( - rocksdb::OptimisticTransactionDB::open_cf_descriptors(&default_db_opts(), &path, column_families()) - .expect("cannot create rocksdb"), + rocksdb::OptimisticTransactionDB::open_cf_descriptors( + &default_db_opts(), + &path, + column_families(), + ) + .expect("cannot create rocksdb"), ) } @@ -51,7 +57,8 @@ fn make_prefixed_key(prefix: Vec, key: &[u8]) -> Vec { prefixed_key } -pub type DBRawTransactionIterator<'a> = rocksdb::DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; +pub type DBRawTransactionIterator<'a> = + rocksdb::DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; impl RawIterator for DBRawTransactionIterator<'_> { fn seek_to_first(&mut self) { @@ -112,6 +119,7 @@ mod tests { use tempdir::TempDir; use super::*; + use crate::{Batch, Storage, Transaction}; struct TempPrefixedStorage { storage: PrefixedRocksDbStorage, @@ -134,7 +142,7 @@ mod tests { default_rocksdb(tmp_dir.path()), b"test".to_vec(), ) - .expect("cannot create prefixed rocksdb storage"), + .expect("cannot create prefixed rocksdb storage"), _tmp_dir: tmp_dir, } } @@ -335,4 +343,4 @@ mod tests { transaction.put(b"key2", b"value2"); transaction.put_root(b"root", b"yeet"); } -} \ No newline at end of file +} diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index 68b8ff531..9d6f88388 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -1,11 +1,12 @@ use std::rc::Rc; + use rocksdb::WriteBatchWithTransaction; -use crate::Storage; + use super::{ - AUX_CF_NAME, ROOTS_CF_NAME, META_CF_NAME, - DBRawTransactionIterator, PrefixedRocksDbBatch, PrefixedRocksDbTransaction, - make_prefixed_key + make_prefixed_key, DBRawTransactionIterator, PrefixedRocksDbBatch, PrefixedRocksDbTransaction, + AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; +use crate::Storage; /// RocksDB wrapper to store items with prefixes pub struct PrefixedRocksDbStorage { @@ -23,7 +24,10 @@ pub enum PrefixedRocksDbStorageError { impl PrefixedRocksDbStorage { /// Wraps RocksDB to prepend prefixes to each operation - pub fn new(db: Rc, prefix: Vec) -> Result { + pub fn new( + db: Rc, + prefix: Vec, + ) -> Result { Ok(PrefixedRocksDbStorage { prefix, db }) } @@ -158,10 +162,6 @@ impl Storage for PrefixedRocksDbStorage { } fn transaction<'a>(&'a self) -> Self::StorageTransaction<'a> { - PrefixedRocksDbTransaction::new( - self.db.transaction(), - self.prefix.clone(), - &self.db - ) + PrefixedRocksDbTransaction::new(self.db.transaction(), self.prefix.clone(), &self.db) } -} \ No newline at end of file +} diff --git a/storage/src/rocksdb_storage/transaction.rs b/storage/src/rocksdb_storage/transaction.rs index cb6f0412c..55a1372f3 100644 --- a/storage/src/rocksdb_storage/transaction.rs +++ b/storage/src/rocksdb_storage/transaction.rs @@ -1,11 +1,9 @@ -use rocksdb::{OptimisticTransactionDB}; +use rocksdb::OptimisticTransactionDB; -use crate::Transaction; use super::{ - PrefixedRocksDbStorageError, - make_prefixed_key, - AUX_CF_NAME, ROOTS_CF_NAME, META_CF_NAME + make_prefixed_key, PrefixedRocksDbStorageError, AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; +use crate::Transaction; pub struct PrefixedRocksDbTransaction<'a> { transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, @@ -14,8 +12,16 @@ pub struct PrefixedRocksDbTransaction<'a> { } // TODO: Implement snapshots for transactions impl<'a> PrefixedRocksDbTransaction<'a> { - pub(crate) fn new(transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, prefix: Vec, db: &'a OptimisticTransactionDB) -> Self { - Self { transaction, prefix, db } + pub(crate) fn new( + transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, + prefix: Vec, + db: &'a OptimisticTransactionDB, + ) -> Self { + Self { + transaction, + prefix, + db, + } } /// Get auxiliary data column family @@ -112,7 +118,9 @@ impl Transaction for PrefixedRocksDbTransaction<'_> { } fn get(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.transaction.get(make_prefixed_key(self.prefix.clone(), key))?) + Ok(self + .transaction + .get(make_prefixed_key(self.prefix.clone(), key))?) } fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { From aa8cde7f632b698b1bf7bd7e32e4c16203ce0530 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 24 Dec 2021 19:35:18 +0300 Subject: [PATCH 05/16] fix merk compilation and added a transaction to the GroveDB --- grovedb/src/lib.rs | 1 + grovedb/src/tests.rs | 164 +++++++++++++++-------------- grovedb/src/transaction.rs | 28 +++++ merk/src/merk/mod.rs | 4 +- storage/src/lib.rs | 94 ++++++++--------- storage/src/rocksdb_storage/mod.rs | 14 ++- 6 files changed, 174 insertions(+), 131 deletions(-) create mode 100644 grovedb/src/transaction.rs diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 563d7197e..ebc5f8092 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -1,6 +1,7 @@ mod subtree; #[cfg(test)] mod tests; +mod transaction; use std::{ collections::{HashMap, HashSet}, diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index ee03c7ae2..790edb740 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -265,13 +265,19 @@ fn test_proof_construction() { // Manually build the ads structures let mut inner_tree_merk = TempMerk::new(); let value_element = Element::Item(b"value1".to_vec()); - value_element.insert(&mut inner_tree_merk, b"key1".to_vec()); + value_element + .insert(&mut inner_tree_merk, b"key1".to_vec()) + .expect("Expected to insert value"); let value_element = Element::Item(b"value2".to_vec()); - value_element.insert(&mut inner_tree_merk, b"key2".to_vec()); + value_element + .insert(&mut inner_tree_merk, b"key2".to_vec()) + .expect("Expected to insert value"); let mut test_leaf_merk = TempMerk::new(); let inner_tree_root_element = Element::Tree(inner_tree_merk.root_hash()); - inner_tree_root_element.insert(&mut test_leaf_merk, b"innertree".to_vec()); + inner_tree_root_element + .insert(&mut test_leaf_merk, b"innertree".to_vec()) + .expect("Expected to insert value"); let another_test_leaf_merk = TempMerk::new(); @@ -302,86 +308,86 @@ fn test_proof_construction() { let root_leaf_keys: HashMap, usize> = bincode::deserialize(&proof[3][..]).unwrap(); assert_eq!(root_leaf_keys.len(), temp_db.root_leaf_keys.len()); - for (key, index) in &root_leaf_keys { + for (key, _index) in &root_leaf_keys { assert_eq!(root_leaf_keys[key], temp_db.root_leaf_keys[key]); } } -#[test] -fn test_checkpoint() { - let mut db = make_grovedb(); - let element1 = Element::Item(b"ayy".to_vec()); - - db.insert(&[], b"key1".to_vec(), Element::empty_tree()) - .expect("cannot insert a subtree 1 into GroveDB"); - db.insert(&[b"key1"], b"key2".to_vec(), Element::empty_tree()) - .expect("cannot insert a subtree 2 into GroveDB"); - db.insert(&[b"key1", b"key2"], b"key3".to_vec(), element1.clone()) - .expect("cannot insert an item into GroveDB"); - - assert_eq!( - db.get(&[b"key1", b"key2"], b"key3") - .expect("cannot get from grovedb"), - element1 - ); - - let checkpoint_tempdir = TempDir::new("checkpoint").expect("cannot open tempdir"); - let mut checkpoint = db - .checkpoint(checkpoint_tempdir.path().join("checkpoint")) - .expect("cannot create a checkpoint"); - - assert_eq!( - db.get(&[b"key1", b"key2"], b"key3") - .expect("cannot get from grovedb"), - element1 - ); - assert_eq!( - checkpoint - .get(&[b"key1", b"key2"], b"key3") - .expect("cannot get from checkpoint"), - element1 - ); - - let element2 = Element::Item(b"ayy2".to_vec()); - let element3 = Element::Item(b"ayy3".to_vec()); - - checkpoint - .insert(&[b"key1"], b"key4".to_vec(), element2.clone()) - .expect("cannot insert into checkpoint"); - - db.insert(&[b"key1"], b"key4".to_vec(), element3.clone()) - .expect("cannot insert into GroveDB"); - - assert_eq!( - checkpoint - .get(&[b"key1"], b"key4") - .expect("cannot get from checkpoint"), - element2, - ); - - assert_eq!( - db.get(&[b"key1"], b"key4") - .expect("cannot get from GroveDB"), - element3 - ); - - checkpoint - .insert(&[b"key1"], b"key5".to_vec(), element3.clone()) - .expect("cannot insert into checkpoint"); - - db.insert(&[b"key1"], b"key6".to_vec(), element3.clone()) - .expect("cannot insert into GroveDB"); - - assert!(matches!( - checkpoint.get(&[b"key1"], b"key6"), - Err(Error::InvalidPath(_)) - )); - - assert!(matches!( - db.get(&[b"key1"], b"key5"), - Err(Error::InvalidPath(_)) - )); -} +// #[test] +// fn test_checkpoint() { +// let mut db = make_grovedb(); +// let element1 = Element::Item(b"ayy".to_vec()); +// +// db.insert(&[], b"key1".to_vec(), Element::empty_tree()) +// .expect("cannot insert a subtree 1 into GroveDB"); +// db.insert(&[b"key1"], b"key2".to_vec(), Element::empty_tree()) +// .expect("cannot insert a subtree 2 into GroveDB"); +// db.insert(&[b"key1", b"key2"], b"key3".to_vec(), element1.clone()) +// .expect("cannot insert an item into GroveDB"); +// +// assert_eq!( +// db.get(&[b"key1", b"key2"], b"key3") +// .expect("cannot get from grovedb"), +// element1 +// ); +// +// let checkpoint_tempdir = TempDir::new("checkpoint").expect("cannot open +// tempdir"); let mut checkpoint = db +// .checkpoint(checkpoint_tempdir.path().join("checkpoint")) +// .expect("cannot create a checkpoint"); +// +// assert_eq!( +// db.get(&[b"key1", b"key2"], b"key3") +// .expect("cannot get from grovedb"), +// element1 +// ); +// assert_eq!( +// checkpoint +// .get(&[b"key1", b"key2"], b"key3") +// .expect("cannot get from checkpoint"), +// element1 +// ); +// +// let element2 = Element::Item(b"ayy2".to_vec()); +// let element3 = Element::Item(b"ayy3".to_vec()); +// +// checkpoint +// .insert(&[b"key1"], b"key4".to_vec(), element2.clone()) +// .expect("cannot insert into checkpoint"); +// +// db.insert(&[b"key1"], b"key4".to_vec(), element3.clone()) +// .expect("cannot insert into GroveDB"); +// +// assert_eq!( +// checkpoint +// .get(&[b"key1"], b"key4") +// .expect("cannot get from checkpoint"), +// element2, +// ); +// +// assert_eq!( +// db.get(&[b"key1"], b"key4") +// .expect("cannot get from GroveDB"), +// element3 +// ); +// +// checkpoint +// .insert(&[b"key1"], b"key5".to_vec(), element3.clone()) +// .expect("cannot insert into checkpoint"); +// +// db.insert(&[b"key1"], b"key6".to_vec(), element3.clone()) +// .expect("cannot insert into GroveDB"); +// +// assert!(matches!( +// checkpoint.get(&[b"key1"], b"key6"), +// Err(Error::InvalidPath(_)) +// )); +// +// assert!(matches!( +// db.get(&[b"key1"], b"key5"), +// Err(Error::InvalidPath(_)) +// )); +// } #[test] fn test_insert_if_not_exists() { diff --git a/grovedb/src/transaction.rs b/grovedb/src/transaction.rs new file mode 100644 index 000000000..8ea508114 --- /dev/null +++ b/grovedb/src/transaction.rs @@ -0,0 +1,28 @@ +// use super::{subtree, Error}; +// +// pub struct GroveDbTransaction { +// +// } +// +// impl GroveDbTransaction { +// pub fn insert( +// &mut self, +// path: &[&[u8]], +// key: Vec, +// mut element: subtree::Element, +// ) -> Result<(), Error> { } +// +// pub fn insert_if_not_exists( +// &mut self, +// path: &[&[u8]], +// key: Vec, +// element: subtree::Element, +// ) -> Result { +// +// } +// +// pub fn get(&self, path: &[&[u8]], key: &[u8]) -> Result { +// +// } +// } diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index e77c422ee..bec39b2e9 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -372,12 +372,14 @@ impl Commit for MerkCommitter { #[cfg(test)] mod test { + use rocksdb::{DBRawIteratorWithThreadMode, OptimisticTransactionDB}; use storage::rocksdb_storage::{default_rocksdb, PrefixedRocksDbStorage}; use tempdir::TempDir; use super::{Merk, MerkSource, RefWalker}; use crate::{test_utils::*, Op}; + type OptimisticTransactionDBRawIterator<'a> = DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; // TODO: Close and then reopen test fn assert_invariants(merk: &TempMerk) { @@ -539,7 +541,7 @@ mod test { #[test] fn reopen_iter() { - fn collect(iter: &mut rocksdb::DBRawIterator, nodes: &mut Vec<(Vec, Vec)>) { + fn collect(iter: &mut OptimisticTransactionDBRawIterator, nodes: &mut Vec<(Vec, Vec)>) { while iter.valid() { nodes.push((iter.key().unwrap().to_vec(), iter.value().unwrap().to_vec())); iter.next(); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 42a6871f7..7037009fb 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,53 +1,6 @@ #![feature(generic_associated_types)] pub mod rocksdb_storage; -pub trait Transaction { - /// Storage error type - type Error: std::error::Error + Send + Sync + 'static; - - /// Commit data from the transaction. Consumes the transaction - fn commit(self) -> Result<(), Self::Error>; - - /// Rollback the transaction - fn rollback(&self) -> Result<(), Self::Error>; - - /// Put `value` into data storage with `key` - fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; - - /// Put `value` into auxiliary data storage with `key` - fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; - - /// Put `value` into trees roots storage with `key` - fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; - - /// Put `value` into GroveDB metadata storage with `key` - fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; - - /// Delete entry with `key` from data storage - fn delete(&self, key: &[u8]) -> Result<(), Self::Error>; - - /// Delete entry with `key` from auxiliary data storage - fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error>; - - /// Delete entry with `key` from trees roots storage - fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error>; - - /// Delete entry with `key` from GroveDB metadata storage - fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error>; - - /// Get entry by `key` from data storage - fn get(&self, key: &[u8]) -> Result>, Self::Error>; - - /// Get entry by `key` from auxiliary data storage - fn get_aux(&self, key: &[u8]) -> Result>, Self::Error>; - - /// Get entry by `key` from trees roots storage - fn get_root(&self, key: &[u8]) -> Result>, Self::Error>; - - /// Get entry by `key` from GroveDB metadata storage - fn get_meta(&self, key: &[u8]) -> Result>, Self::Error>; -} - /// `Storage` is able to store and retrieve arbitrary bytes by key pub trait Storage { /// Storage error type @@ -230,6 +183,53 @@ pub trait RawIterator { fn valid(&self) -> bool; } +pub trait Transaction { + /// Storage error type + type Error: std::error::Error + Send + Sync + 'static; + + /// Commit data from the transaction. Consumes the transaction + fn commit(self) -> Result<(), Self::Error>; + + /// Rollback the transaction + fn rollback(&self) -> Result<(), Self::Error>; + + /// Put `value` into data storage with `key` + fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Put `value` into auxiliary data storage with `key` + fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Put `value` into trees roots storage with `key` + fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Put `value` into GroveDB metadata storage with `key` + fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from data storage + fn delete(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from auxiliary data storage + fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from trees roots storage + fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Delete entry with `key` from GroveDB metadata storage + fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error>; + + /// Get entry by `key` from data storage + fn get(&self, key: &[u8]) -> Result>, Self::Error>; + + /// Get entry by `key` from auxiliary data storage + fn get_aux(&self, key: &[u8]) -> Result>, Self::Error>; + + /// Get entry by `key` from trees roots storage + fn get_root(&self, key: &[u8]) -> Result>, Self::Error>; + + /// Get entry by `key` from GroveDB metadata storage + fn get_meta(&self, key: &[u8]) -> Result>, Self::Error>; +} + /// The `Store` trait allows to store its implementor by key using a storage `S` /// or to delete it. pub trait Store diff --git a/storage/src/rocksdb_storage/mod.rs b/storage/src/rocksdb_storage/mod.rs index bc53efb72..d6445fa67 100644 --- a/storage/src/rocksdb_storage/mod.rs +++ b/storage/src/rocksdb_storage/mod.rs @@ -338,9 +338,15 @@ mod tests { #[test] fn transaction_commit_should_work() { let storage = TempPrefixedStorage::new(); - let mut transaction = storage.transaction(); - transaction.put(b"key1", b"value1"); - transaction.put(b"key2", b"value2"); - transaction.put_root(b"root", b"yeet"); + let transaction = storage.transaction(); + transaction + .put(b"key1", b"value1") + .expect("Expected to put value"); + transaction + .put(b"key2", b"value2") + .expect("Expected to put value"); + transaction + .put_root(b"root", b"yeet") + .expect("Expected to put root"); } } From 23b0f663cf2d99ba27e216cae695048d2dc22c8a Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Mon, 27 Dec 2021 17:34:34 +0300 Subject: [PATCH 06/16] refactor transactional rocksdb storage --- grovedb/src/lib.rs | 11 +- merk/src/merk/mod.rs | 8 +- storage/src/lib.rs | 47 ++++--- storage/src/rocksdb_storage/mod.rs | 7 +- storage/src/rocksdb_storage/storage.rs | 150 ++++++++++++++++++++- storage/src/rocksdb_storage/transaction.rs | 20 +-- 6 files changed, 202 insertions(+), 41 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index ebc5f8092..105dd2a67 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -13,8 +13,11 @@ pub use merk::proofs::{query::QueryItem, Query}; use merk::{self, Merk}; use rs_merkle::{algorithms::Sha256, MerkleTree}; use storage::{ - rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, - Storage, + rocksdb_storage::{ + OptimisticTransactionDB, OptimisticTransactionDBTransaction, PrefixedRocksDbStorage, + PrefixedRocksDbStorageError, PrefixedRocksDbTransaction, + }, + Storage, Transaction, }; pub use subtree::Element; @@ -365,4 +368,8 @@ impl GroveDb { } res } + + pub fn transaction(&self) -> OptimisticTransactionDBTransaction { + self.db.transaction() + } } diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index bec39b2e9..c7bcebf1e 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -379,7 +379,8 @@ mod test { use super::{Merk, MerkSource, RefWalker}; use crate::{test_utils::*, Op}; - type OptimisticTransactionDBRawIterator<'a> = DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; + type OptimisticTransactionDBRawIterator<'a> = + DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; // TODO: Close and then reopen test fn assert_invariants(merk: &TempMerk) { @@ -541,7 +542,10 @@ mod test { #[test] fn reopen_iter() { - fn collect(iter: &mut OptimisticTransactionDBRawIterator, nodes: &mut Vec<(Vec, Vec)>) { + fn collect( + iter: &mut OptimisticTransactionDBRawIterator, + nodes: &mut Vec<(Vec, Vec)>, + ) { while iter.valid() { nodes.push((iter.key().unwrap().to_vec(), iter.value().unwrap().to_vec())); iter.next(); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 7037009fb..feb481682 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,6 +1,9 @@ #![feature(generic_associated_types)] pub mod rocksdb_storage; +// Marker trait for underlying DB transactions +pub trait DBTransaction<'a> {} + /// `Storage` is able to store and retrieve arbitrary bytes by key pub trait Storage { /// Storage error type @@ -16,6 +19,9 @@ pub trait Storage { Self: 'a; type StorageTransaction<'a>: Transaction + where + Self: 'a; + type DBTransaction<'a>: DBTransaction<'a> where Self: 'a; @@ -68,15 +74,15 @@ pub trait Storage { fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a>; /// Starts DB transaction - fn transaction<'a>(&'a self) -> Self::StorageTransaction<'a>; + fn transaction<'a>(&'a self, tx: &'a Self::DBTransaction<'a>) -> Self::StorageTransaction<'a>; } impl<'b, S: Storage> Storage for &'b S { + type Error = S::Error; type Batch<'a> where 'b: 'a, = S::Batch<'a>; - type Error = S::Error; type RawIterator<'a> where 'b: 'a, @@ -85,6 +91,10 @@ impl<'b, S: Storage> Storage for &'b S { where 'b: 'a, = S::StorageTransaction<'a>; + type DBTransaction<'a> + where + 'b: 'a, + = S::DBTransaction<'a>; fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { (*self).put(key, value) @@ -98,6 +108,10 @@ impl<'b, S: Storage> Storage for &'b S { (*self).put_root(key, value) } + fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + (*self).put_meta(key, value) + } + fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { (*self).delete(key) } @@ -110,6 +124,10 @@ impl<'b, S: Storage> Storage for &'b S { (*self).delete_root(key) } + fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { + (*self).delete_meta(key) + } + fn get(&self, key: &[u8]) -> Result>, Self::Error> { (*self).get(key) } @@ -122,14 +140,6 @@ impl<'b, S: Storage> Storage for &'b S { (*self).get_root(key) } - fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - (*self).put_meta(key, value) - } - - fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { - (*self).delete_meta(key) - } - fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { (*self).get_meta(key) } @@ -150,8 +160,11 @@ impl<'b, S: Storage> Storage for &'b S { (*self).raw_iter() } - fn transaction<'a>(&'a self) -> Self::StorageTransaction<'a> { - (*self).transaction() + fn transaction<'a>( + &'a self, + transaction: &'a Self::DBTransaction<'a>, + ) -> Self::StorageTransaction<'a> { + (*self).transaction(transaction) } } @@ -183,16 +196,14 @@ pub trait RawIterator { fn valid(&self) -> bool; } +/// Please note that the `Transaction` trait is used to access the underlying transaction +/// through the storage, but many storages can share the same DB transaction. Thus, the +/// storage itself can not commit the transaction, and transaction should be committed +/// by its original opener - GroveDB instance in our case. pub trait Transaction { /// Storage error type type Error: std::error::Error + Send + Sync + 'static; - /// Commit data from the transaction. Consumes the transaction - fn commit(self) -> Result<(), Self::Error>; - - /// Rollback the transaction - fn rollback(&self) -> Result<(), Self::Error>; - /// Put `value` into data storage with `key` fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; diff --git a/storage/src/rocksdb_storage/mod.rs b/storage/src/rocksdb_storage/mod.rs index d6445fa67..c3ae4e1c5 100644 --- a/storage/src/rocksdb_storage/mod.rs +++ b/storage/src/rocksdb_storage/mod.rs @@ -4,7 +4,7 @@ use std::{path::Path, rc::Rc}; pub use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; use rocksdb::{ColumnFamilyDescriptor, DBRawIterator}; -use crate::RawIterator; +use crate::{DBTransaction, RawIterator}; mod batch; mod storage; @@ -60,6 +60,11 @@ fn make_prefixed_key(prefix: Vec, key: &[u8]) -> Vec { pub type DBRawTransactionIterator<'a> = rocksdb::DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>; +pub type OptimisticTransactionDBTransaction<'a> = rocksdb::Transaction<'a, OptimisticTransactionDB>; + +// Implement marker trait for internal DB transaction +impl DBTransaction<'_> for OptimisticTransactionDBTransaction<'_> {} + impl RawIterator for DBRawTransactionIterator<'_> { fn seek_to_first(&mut self) { DBRawTransactionIterator::seek_to_first(self) diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index 9d6f88388..dcf4b2f8a 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -6,7 +6,7 @@ use super::{ make_prefixed_key, DBRawTransactionIterator, PrefixedRocksDbBatch, PrefixedRocksDbTransaction, AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; -use crate::Storage; +use crate::{rocksdb_storage::OptimisticTransactionDBTransaction, Storage, Transaction}; /// RocksDB wrapper to store items with prefixes pub struct PrefixedRocksDbStorage { @@ -61,6 +61,7 @@ impl PrefixedRocksDbStorage { impl Storage for PrefixedRocksDbStorage { type Batch<'a> = PrefixedRocksDbBatch<'a>; + type DBTransaction<'a> = OptimisticTransactionDBTransaction<'a>; type Error = PrefixedRocksDbStorageError; type RawIterator<'a> = DBRawTransactionIterator<'a>; type StorageTransaction<'a> = PrefixedRocksDbTransaction<'a>; @@ -161,7 +162,150 @@ impl Storage for PrefixedRocksDbStorage { self.db.raw_iterator() } - fn transaction<'a>(&'a self) -> Self::StorageTransaction<'a> { - PrefixedRocksDbTransaction::new(self.db.transaction(), self.prefix.clone(), &self.db) + fn transaction<'a>( + &'a self, + db_transaction: &'a OptimisticTransactionDBTransaction, + ) -> Self::StorageTransaction<'a> { + PrefixedRocksDbTransaction::new(db_transaction, self.prefix.clone(), &self.db) + } +} + +pub struct TransactionalStorage<'a> { + storage: &'a PrefixedRocksDbStorage, + transaction: Option>, +} + +impl<'a> TransactionalStorage<'a> { + pub fn new( + storage: &'a PrefixedRocksDbStorage, + db_transaction: Option<&'a OptimisticTransactionDBTransaction>, + ) -> Self { + Self { + storage, transaction: db_transaction.map(|tx| storage.transaction(tx)) + } + } +} + +impl<'b> Storage for TransactionalStorage<'b> { + type Error = PrefixedRocksDbStorageError; + type Batch<'a> + where + 'b: 'a, + = PrefixedRocksDbBatch<'a>; + type RawIterator<'a> + where + 'b: 'a, + = DBRawTransactionIterator<'a>; + type StorageTransaction<'a> + where + 'b: 'a, + = PrefixedRocksDbTransaction<'a>; + type DBTransaction<'a> + where + 'b: 'a, + = OptimisticTransactionDBTransaction<'a>; + + fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.put(key, value), + Some(tx) => tx.put(key, value), + } + } + + fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.put_aux(key, value), + Some(tx) => tx.put_aux(key, value), + } + } + + fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.put_root(key, value), + Some(tx) => tx.put_root(key, value), + } + } + + fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.put_meta(key, value), + Some(tx) => tx.put_meta(key, value), + } + } + + fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.delete(key), + Some(tx) => tx.delete(key), + } + } + + fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.delete_aux(key), + Some(tx) => tx.delete_aux(key), + } + } + + fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.delete_root(key), + Some(tx) => tx.delete_root(key), + } + } + + fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { + match &self.transaction { + None => self.storage.delete_meta(key), + Some(tx) => tx.delete_meta(key), + } + } + + fn get(&self, key: &[u8]) -> Result>, Self::Error> { + match &self.transaction { + None => self.storage.get(key), + Some(tx) => tx.get(key), + } + } + + fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { + match &self.transaction { + None => self.storage.get_aux(key), + Some(tx) => tx.get_aux(key), + } + } + + fn get_root(&self, key: &[u8]) -> Result>, Self::Error> { + match &self.transaction { + None => self.storage.get_root(key), + Some(tx) => tx.get_root(key), + } + } + + fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { + match &self.transaction { + None => self.storage.get_meta(key), + Some(tx) => tx.get_meta(key), + } + } + + fn new_batch<'a>(&'a self) -> Result, Self::Error> { + self.storage.new_batch() + } + + fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { + self.storage.commit_batch(batch) + } + + fn flush(&self) -> Result<(), Self::Error> { + self.storage.flush() + } + + fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { + self.storage.raw_iter() + } + + fn transaction<'a>(&'a self, tx: &'a Self::DBTransaction<'a>) -> Self::StorageTransaction<'a> { + self.storage.transaction(tx) } } diff --git a/storage/src/rocksdb_storage/transaction.rs b/storage/src/rocksdb_storage/transaction.rs index 55a1372f3..1560bbd54 100644 --- a/storage/src/rocksdb_storage/transaction.rs +++ b/storage/src/rocksdb_storage/transaction.rs @@ -3,17 +3,17 @@ use rocksdb::OptimisticTransactionDB; use super::{ make_prefixed_key, PrefixedRocksDbStorageError, AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; -use crate::Transaction; +use crate::{Transaction}; pub struct PrefixedRocksDbTransaction<'a> { - transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, + transaction: &'a rocksdb::Transaction<'a, OptimisticTransactionDB>, prefix: Vec, - db: &'a OptimisticTransactionDB, + pub(crate) db: &'a OptimisticTransactionDB, } // TODO: Implement snapshots for transactions impl<'a> PrefixedRocksDbTransaction<'a> { - pub(crate) fn new( - transaction: rocksdb::Transaction<'a, OptimisticTransactionDB>, + pub fn new( + transaction: &'a rocksdb::Transaction<'a, OptimisticTransactionDB>, prefix: Vec, db: &'a OptimisticTransactionDB, ) -> Self { @@ -55,16 +55,6 @@ impl<'a> PrefixedRocksDbTransaction<'a> { impl Transaction for PrefixedRocksDbTransaction<'_> { type Error = PrefixedRocksDbStorageError; - fn commit(self) -> Result<(), Self::Error> { - self.transaction.commit()?; - Ok(()) - } - - fn rollback(&self) -> Result<(), Self::Error> { - self.transaction.rollback()?; - Ok(()) - } - fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { self.transaction .put(make_prefixed_key(self.prefix.clone(), key), value)?; From 6224f7e3877945004eefaeb93806a3e855238d7f Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Tue, 28 Dec 2021 22:40:25 +0300 Subject: [PATCH 07/16] add transaction support for the insert method --- grovedb/src/lib.rs | 24 +- grovedb/src/subtree.rs | 13 +- merk/src/merk/mod.rs | 44 +-- node-grove/src/lib.rs | 3 +- storage/src/lib.rs | 6 +- storage/src/rocksdb_storage/batch.rs | 98 ++++++- storage/src/rocksdb_storage/storage.rs | 311 +++++++++++---------- storage/src/rocksdb_storage/transaction.rs | 2 +- 8 files changed, 311 insertions(+), 190 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 105dd2a67..01dd68bf7 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -142,11 +142,12 @@ impl GroveDb { } // TODO: split the function into smaller ones - pub fn insert( - &mut self, + pub fn insert<'a: 'b, 'b>( + &'a mut self, path: &[&[u8]], key: Vec, mut element: subtree::Element, + transaction: Option<&'b ::DBTransaction<'b>> ) -> Result<(), Error> { let compressed_path = Self::compress_path(path, None); match &mut element { @@ -178,7 +179,7 @@ impl GroveDb { self.root_leaf_keys .insert(compressed_path_subtree, self.root_tree.leaves_len()); } - self.propagate_changes(&[&key])?; + self.propagate_changes(&[&key], transaction)?; } else { // Add subtree to another subtree. // First, check if a subtree exists to create a new subtree under it @@ -195,8 +196,8 @@ impl GroveDb { .get_mut(&compressed_path) .expect("merk object must exist in `subtrees`"); // need to mark key as taken in the upper tree - element.insert(&mut merk, key)?; - self.propagate_changes(path)?; + element.insert(&mut merk, key, transaction)?; + self.propagate_changes(path, transaction)?; } self.store_subtrees_keys_data()?; } @@ -213,23 +214,24 @@ impl GroveDb { .subtrees .get_mut(&compressed_path) .ok_or(Error::InvalidPath("no subtree found under that path"))?; - element.insert(&mut merk, key)?; - self.propagate_changes(path)?; + element.insert(&mut merk, key, transaction)?; + self.propagate_changes(path, transaction)?; } } Ok(()) } - pub fn insert_if_not_exists( + pub fn insert_if_not_exists<'a: 'b, 'b>( &mut self, path: &[&[u8]], key: Vec, element: subtree::Element, + transaction: Option<&'b ::DBTransaction<'b>> ) -> Result { if self.get(path, &key).is_ok() { return Ok(false); } - match self.insert(path, key, element) { + match self.insert(path, key, element, transaction) { Ok(_) => Ok(true), Err(e) => Err(e), } @@ -329,7 +331,7 @@ impl GroveDb { } /// Method to propagate updated subtree root hashes up to GroveDB root - fn propagate_changes(&mut self, path: &[&[u8]]) -> Result<(), Error> { + fn propagate_changes<'a: 'b, 'b>(&'a mut self, path: &[&[u8]], transaction: Option<&'b ::DBTransaction<'b>>) -> Result<(), Error> { let mut split_path = path.split_last(); // Go up until only one element in path, which means a key of a root tree while let Some((key, path_slice)) = split_path { @@ -349,7 +351,7 @@ impl GroveDb { .subtrees .get_mut(&compressed_path_upper_tree) .ok_or(Error::InvalidPath("no subtree found under that path"))?; - element.insert(upper_tree, key.to_vec())?; + element.insert(upper_tree, key.to_vec(), transaction)?; split_path = path_slice.split_last(); } } diff --git a/grovedb/src/subtree.rs b/grovedb/src/subtree.rs index 5e479be32..bc49f6608 100644 --- a/grovedb/src/subtree.rs +++ b/grovedb/src/subtree.rs @@ -4,6 +4,7 @@ use merk::Op; use serde::{Deserialize, Serialize}; use storage::rocksdb_storage::PrefixedRocksDbStorage; +use storage::Storage; use crate::{Error, Merk}; @@ -41,19 +42,23 @@ impl Element { /// Insert an element in Merk under a key; path should be resolved and /// proper Merk should be loaded by this moment - pub fn insert( - &self, + /// If transaction is not passed, the batch will be written immediately. + /// If transaction is passed, the operation will be committed on the transaction + /// commit. + pub fn insert<'a: 'b, 'b>( + &'a self, merk: &mut Merk, key: Vec, + transaction: Option<&'b ::DBTransaction<'b>> ) -> Result<(), Error> { - let batch = + let batch_operations = [( key, Op::Put(bincode::serialize(self).map_err(|_| { Error::CorruptedData(String::from("unable to serialize element")) })?), )]; - merk.apply(&batch, &[]) + merk.apply(&batch_operations, &[], transaction) .map_err(|e| Error::CorruptedData(e.to_string())) } } diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index c7bcebf1e..d3c9405a3 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -112,7 +112,7 @@ where /// # Example /// ``` /// # let mut store = merk::test_utils::TempMerk::new(); - /// # store.apply(&[(vec![4,5,6], Op::Put(vec![0]))], &[]).unwrap(); + /// # store.apply(&[(vec![4,5,6], Op::Put(vec![0]))], &[], None).unwrap(); /// /// use merk::Op; /// @@ -120,9 +120,9 @@ where /// (vec![1, 2, 3], Op::Put(vec![4, 5, 6])), // puts value [4,5,6] to key[1,2,3] /// (vec![4, 5, 6], Op::Delete), // deletes key [4,5,6] /// ]; - /// store.apply(batch, &[]).unwrap(); + /// store.apply(batch, &[], None).unwrap(); /// ``` - pub fn apply(&mut self, batch: &MerkBatch, aux: &MerkBatch) -> Result<()> { + pub fn apply<'a: 'b, 'b>(&'a mut self, batch: &MerkBatch, aux: &MerkBatch, transaction: Option<&'b S::DBTransaction<'b>>) -> Result<()> { // ensure keys in batch are sorted and unique let mut maybe_prev_key: Option> = None; for (key, _) in batch.iter() { @@ -136,7 +136,7 @@ where maybe_prev_key = Some(key.to_vec()); } - unsafe { self.apply_unchecked(batch, aux) } + unsafe { self.apply_unchecked(batch, aux, transaction) } } /// Applies a batch of operations (puts and deletes) to the tree. @@ -150,7 +150,7 @@ where /// # Example /// ``` /// # let mut store = merk::test_utils::TempMerk::new(); - /// # store.apply(&[(vec![4,5,6], Op::Put(vec![0]))], &[]).unwrap(); + /// # store.apply(&[(vec![4,5,6], Op::Put(vec![0]))], &[], None).unwrap(); /// /// use merk::Op; /// @@ -158,9 +158,9 @@ where /// (vec![1, 2, 3], Op::Put(vec![4, 5, 6])), // puts value [4,5,6] to key [1,2,3] /// (vec![4, 5, 6], Op::Delete), // deletes key [4,5,6] /// ]; - /// unsafe { store.apply_unchecked(batch, &[]).unwrap() }; + /// unsafe { store.apply_unchecked(batch, &[], None).unwrap() }; /// ``` - pub unsafe fn apply_unchecked(&mut self, batch: &MerkBatch, aux: &MerkBatch) -> Result<()> { + pub unsafe fn apply_unchecked<'a: 'b, 'b>(&'a mut self, batch: &MerkBatch, aux: &MerkBatch, transaction: Option<&'b S::DBTransaction<'b>>) -> Result<()> { let maybe_walker = self .tree .take() @@ -171,7 +171,7 @@ where self.tree.set(maybe_tree); // commit changes to db - self.commit(deleted_keys, aux) + self.commit(deleted_keys, aux, transaction) } /// Creates a Merkle proof for the list of queried keys. For each key in the @@ -221,8 +221,8 @@ where }) } - pub fn commit(&mut self, deleted_keys: LinkedList>, aux: &MerkBatch) -> Result<()> { - let mut batch = self.storage.new_batch()?; + pub fn commit<'a: 'b, 'b>(&'a mut self, deleted_keys: LinkedList>, aux: &MerkBatch, transaction: Option<&'b S::DBTransaction<'b>>) -> Result<()> { + let mut batch = self.storage.new_batch(transaction)?; let mut to_batch = self.use_tree_mut(|maybe_tree| -> UseTreeMutResult { // TODO: concurrent commit if let Some(tree) = maybe_tree { @@ -395,7 +395,7 @@ mod test { let batch_size = 20; let mut merk = TempMerk::new(); let batch = make_batch_seq(0..batch_size); - merk.apply(&batch, &[]).expect("apply failed"); + merk.apply(&batch, &[], None).expect("apply failed"); assert_invariants(&merk); assert_eq!( @@ -413,11 +413,11 @@ mod test { let mut merk = TempMerk::new(); let batch = make_batch_seq(0..batch_size); - merk.apply(&batch, &[]).expect("apply failed"); + merk.apply(&batch, &[], None).expect("apply failed"); assert_invariants(&merk); let batch = make_batch_seq(batch_size..(batch_size * 2)); - merk.apply(&batch, &[]).expect("apply failed"); + merk.apply(&batch, &[], None).expect("apply failed"); assert_invariants(&merk); } @@ -430,7 +430,7 @@ mod test { for i in 0..(tree_size / batch_size) { println!("i:{}", i); let batch = make_batch_rand(batch_size, i); - merk.apply(&batch, &[]).expect("apply failed"); + merk.apply(&batch, &[], None).expect("apply failed"); } } @@ -439,10 +439,10 @@ mod test { let mut merk = TempMerk::new(); let batch = make_batch_rand(10, 1); - merk.apply(&batch, &[]).expect("apply failed"); + merk.apply(&batch, &[], None).expect("apply failed"); let key = batch.first().unwrap().0.clone(); - merk.apply(&[(key.clone(), Op::Delete)], &[]).unwrap(); + merk.apply(&[(key.clone(), Op::Delete)], &[], None).unwrap(); let value = merk.inner.get(key.as_slice()).unwrap(); assert!(value.is_none()); @@ -451,7 +451,7 @@ mod test { #[test] fn aux_data() { let mut merk = TempMerk::new(); - merk.apply(&[], &[(vec![1, 2, 3], Op::Put(vec![4, 5, 6]))]) + merk.apply(&[], &[(vec![1, 2, 3], Op::Put(vec![4, 5, 6]))], None) .expect("apply failed"); let val = merk.get_aux(&[1, 2, 3]).unwrap(); assert_eq!(val, Some(vec![4, 5, 6])); @@ -464,12 +464,13 @@ mod test { merk.apply( &[(vec![0], Op::Put(vec![1]))], &[(vec![2], Op::Put(vec![3]))], + None ) .expect("apply failed"); // make enough changes so that main column family gets auto-flushed for i in 0..250 { - merk.apply(&make_batch_seq(i * 2_000..(i + 1) * 2_000), &[]) + merk.apply(&make_batch_seq(i * 2_000..(i + 1) * 2_000), &[], None) .expect("apply failed"); } merk.crash(); @@ -485,7 +486,7 @@ mod test { assert!(merk.get(&[1, 2, 3]).unwrap().is_none()); // cached - merk.apply(&[(vec![5, 5, 5], Op::Put(vec![]))], &[]) + merk.apply(&[(vec![5, 5, 5], Op::Put(vec![]))], &[], None) .unwrap(); assert!(merk.get(&[1, 2, 3]).unwrap().is_none()); @@ -497,6 +498,7 @@ mod test { (vec![2, 2, 2], Op::Put(vec![])), ], &[], + None, ) .unwrap(); assert!(merk.get(&[3, 3, 3]).unwrap().is_none()); @@ -520,7 +522,7 @@ mod test { let mut merk = Merk::open(PrefixedRocksDbStorage::new(db, Vec::new()).unwrap()).unwrap(); let batch = make_batch_seq(1..10_000); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let mut tree = merk.tree.take().unwrap(); let walker = RefWalker::new(&mut tree, merk.source()); @@ -558,7 +560,7 @@ mod test { let mut merk = Merk::open(PrefixedRocksDbStorage::new(db, Vec::new()).unwrap()).unwrap(); let batch = make_batch_seq(1..10_000); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let mut nodes = vec![]; collect(&mut merk.raw_iter(), &mut nodes); diff --git a/node-grove/src/lib.rs b/node-grove/src/lib.rs index 48bf89285..b139afc5c 100644 --- a/node-grove/src/lib.rs +++ b/node-grove/src/lib.rs @@ -169,7 +169,8 @@ impl GroveDbWrapper { db.send_to_db_thread(move |grove_db: &mut GroveDb, channel| { let path_slice: Vec<&[u8]> = path.iter().map(|fragment| fragment.as_slice()).collect(); - let result = grove_db.insert(&path_slice, key, element); + // TODO: IMPLEMENT BINDINGS FOR THE TRANSACTION + let result = grove_db.insert(&path_slice, key, element, None); channel.send(move |mut task_context| { let callback = js_callback.into_inner(&mut task_context); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index feb481682..7a210b5f3 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -62,7 +62,7 @@ pub trait Storage { fn get_meta(&self, key: &[u8]) -> Result>, Self::Error>; /// Initialize a new batch - fn new_batch<'a>(&'a self) -> Result, Self::Error>; + fn new_batch<'a: 'b, 'b>(&'a self, transaction: Option<&'b Self::DBTransaction<'b>>) -> Result, Self::Error>; /// Commits changes from batch into storage fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error>; @@ -144,8 +144,8 @@ impl<'b, S: Storage> Storage for &'b S { (*self).get_meta(key) } - fn new_batch<'a>(&'a self) -> Result, Self::Error> { - (*self).new_batch() + fn new_batch<'a: 'c, 'c>(&'a self, transaction: Option<&'c Self::DBTransaction<'c>>) -> Result, Self::Error> { + (*self).new_batch(transaction) } fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { diff --git a/storage/src/rocksdb_storage/batch.rs b/storage/src/rocksdb_storage/batch.rs index 729ec7abf..8efaf05e8 100644 --- a/storage/src/rocksdb_storage/batch.rs +++ b/storage/src/rocksdb_storage/batch.rs @@ -1,4 +1,4 @@ -use rocksdb::ColumnFamily; +use rocksdb::{ColumnFamily, OptimisticTransactionDB}; use super::make_prefixed_key; use crate::Batch; @@ -48,3 +48,99 @@ impl<'a> Batch for PrefixedRocksDbBatch<'a> { .delete_cf(self.cf_roots, make_prefixed_key(self.prefix.clone(), key)) } } + +/// Wrapper to RocksDB batch +pub struct PrefixedTransactionalRocksDbBatch<'a> { + pub prefix: Vec, + pub cf_aux: &'a ColumnFamily, + pub cf_roots: &'a ColumnFamily, + pub transaction: &'a rocksdb::Transaction<'a, OptimisticTransactionDB>, +} + +// TODO: don't ignore errors +impl<'a> Batch for PrefixedTransactionalRocksDbBatch<'a> { + fn put(&mut self, key: &[u8], value: &[u8]) { + self.transaction + .put(make_prefixed_key(self.prefix.clone(), key), value); + } + + fn put_aux(&mut self, key: &[u8], value: &[u8]) { + self.transaction.put_cf( + self.cf_aux, + make_prefixed_key(self.prefix.clone(), key), + value, + ); + } + + fn put_root(&mut self, key: &[u8], value: &[u8]) { + self.transaction.put_cf( + self.cf_roots, + make_prefixed_key(self.prefix.clone(), key), + value, + ); + } + + fn delete(&mut self, key: &[u8]) { + self.transaction + .delete(make_prefixed_key(self.prefix.clone(), key)); + } + + fn delete_aux(&mut self, key: &[u8]) { + self.transaction + .delete_cf(self.cf_aux, make_prefixed_key(self.prefix.clone(), key)); + } + + fn delete_root(&mut self, key: &[u8]) { + self.transaction + .delete_cf(self.cf_roots, make_prefixed_key(self.prefix.clone(), key)); + } +} + +pub enum OrBatch<'a> { + Batch(PrefixedRocksDbBatch<'a>), + TransactionalBatch(PrefixedTransactionalRocksDbBatch<'a>) +} + +impl <'a> Batch for OrBatch<'a> { + fn put(&mut self, key: &[u8], value: &[u8]) { + match self { + Self::TransactionalBatch(batch) => batch.put(key, value), + Self::Batch(batch) => batch.put(key, value) + } + } + + fn put_aux(&mut self, key: &[u8], value: &[u8]) { + match self { + Self::TransactionalBatch(batch) => batch.put_aux(key, value), + Self::Batch(batch) => batch.put_aux(key, value) + } + } + + fn put_root(&mut self, key: &[u8], value: &[u8]) { + match self { + Self::TransactionalBatch(batch) => batch.put_root(key, value), + Self::Batch(batch) => batch.put_root(key, value) + } + } + + fn delete(&mut self, key: &[u8]) { + match self { + Self::TransactionalBatch(batch) => batch.delete(key), + Self::Batch(batch) => batch.delete(key) + } + } + + fn delete_aux(&mut self, key: &[u8]) { + match self { + Self::TransactionalBatch(batch) => batch.delete_aux(key), + Self::Batch(batch) => batch.delete_aux(key) + } + } + + fn delete_root(&mut self, key: &[u8]) { + match self { + Self::TransactionalBatch(batch) => batch.delete_root(key), + Self::Batch(batch) => batch.delete_root(key) + } + } +} \ No newline at end of file diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index dcf4b2f8a..8d7b6f5fc 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -7,6 +7,7 @@ use super::{ AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; use crate::{rocksdb_storage::OptimisticTransactionDBTransaction, Storage, Transaction}; +use crate::rocksdb_storage::batch::{OrBatch, PrefixedTransactionalRocksDbBatch}; /// RocksDB wrapper to store items with prefixes pub struct PrefixedRocksDbStorage { @@ -60,7 +61,7 @@ impl PrefixedRocksDbStorage { } impl Storage for PrefixedRocksDbStorage { - type Batch<'a> = PrefixedRocksDbBatch<'a>; + type Batch<'a> = OrBatch<'a>; type DBTransaction<'a> = OptimisticTransactionDBTransaction<'a>; type Error = PrefixedRocksDbStorageError; type RawIterator<'a> = DBRawTransactionIterator<'a>; @@ -139,17 +140,30 @@ impl Storage for PrefixedRocksDbStorage { Ok(self.db.get_cf(self.cf_meta()?, key)?) } - fn new_batch<'a>(&'a self) -> Result, Self::Error> { - Ok(PrefixedRocksDbBatch { - prefix: self.prefix.clone(), - batch: WriteBatchWithTransaction::::default(), - cf_aux: self.cf_aux()?, - cf_roots: self.cf_roots()?, - }) + fn new_batch<'a: 'b, 'b>(&'a self, transaction: Option<&'b OptimisticTransactionDBTransaction>) -> Result, Self::Error> { + match transaction { + Some(tx) => Ok(OrBatch::TransactionalBatch(PrefixedTransactionalRocksDbBatch { + prefix: self.prefix.clone(), + transaction: tx, + cf_aux: self.cf_aux()?, + cf_roots: self.cf_roots()?, + })), + None => Ok(OrBatch::Batch(PrefixedRocksDbBatch { + prefix: self.prefix.clone(), + batch: WriteBatchWithTransaction::::default(), + cf_aux: self.cf_aux()?, + cf_roots: self.cf_roots()?, + })) + } } fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { - self.db.write(batch.batch)?; + // Do nothing if transaction exists, as the transaction must be explicitly committed by + // its creator + match batch { + OrBatch::TransactionalBatch(_) => {}, + OrBatch::Batch(batch) => self.db.write(batch.batch)? + } Ok(()) } @@ -170,142 +184,143 @@ impl Storage for PrefixedRocksDbStorage { } } -pub struct TransactionalStorage<'a> { - storage: &'a PrefixedRocksDbStorage, - transaction: Option>, -} - -impl<'a> TransactionalStorage<'a> { - pub fn new( - storage: &'a PrefixedRocksDbStorage, - db_transaction: Option<&'a OptimisticTransactionDBTransaction>, - ) -> Self { - Self { - storage, transaction: db_transaction.map(|tx| storage.transaction(tx)) - } - } -} - -impl<'b> Storage for TransactionalStorage<'b> { - type Error = PrefixedRocksDbStorageError; - type Batch<'a> - where - 'b: 'a, - = PrefixedRocksDbBatch<'a>; - type RawIterator<'a> - where - 'b: 'a, - = DBRawTransactionIterator<'a>; - type StorageTransaction<'a> - where - 'b: 'a, - = PrefixedRocksDbTransaction<'a>; - type DBTransaction<'a> - where - 'b: 'a, - = OptimisticTransactionDBTransaction<'a>; - - fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.put(key, value), - Some(tx) => tx.put(key, value), - } - } - - fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.put_aux(key, value), - Some(tx) => tx.put_aux(key, value), - } - } - - fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.put_root(key, value), - Some(tx) => tx.put_root(key, value), - } - } - - fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.put_meta(key, value), - Some(tx) => tx.put_meta(key, value), - } - } - - fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.delete(key), - Some(tx) => tx.delete(key), - } - } - - fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.delete_aux(key), - Some(tx) => tx.delete_aux(key), - } - } - - fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.delete_root(key), - Some(tx) => tx.delete_root(key), - } - } - - fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { - match &self.transaction { - None => self.storage.delete_meta(key), - Some(tx) => tx.delete_meta(key), - } - } - - fn get(&self, key: &[u8]) -> Result>, Self::Error> { - match &self.transaction { - None => self.storage.get(key), - Some(tx) => tx.get(key), - } - } - - fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { - match &self.transaction { - None => self.storage.get_aux(key), - Some(tx) => tx.get_aux(key), - } - } - - fn get_root(&self, key: &[u8]) -> Result>, Self::Error> { - match &self.transaction { - None => self.storage.get_root(key), - Some(tx) => tx.get_root(key), - } - } - - fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { - match &self.transaction { - None => self.storage.get_meta(key), - Some(tx) => tx.get_meta(key), - } - } - - fn new_batch<'a>(&'a self) -> Result, Self::Error> { - self.storage.new_batch() - } - - fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { - self.storage.commit_batch(batch) - } - - fn flush(&self) -> Result<(), Self::Error> { - self.storage.flush() - } - - fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { - self.storage.raw_iter() - } - - fn transaction<'a>(&'a self, tx: &'a Self::DBTransaction<'a>) -> Self::StorageTransaction<'a> { - self.storage.transaction(tx) - } -} +// pub struct TransactionalPrefixedRocksDbStorage<'a> { +// storage: &'a PrefixedRocksDbStorage, +// transaction: Option>, +// } +// +// impl<'a> TransactionalPrefixedRocksDbStorage<'a> { +// pub fn new( +// storage: &'a PrefixedRocksDbStorage, +// db_transaction: Option<&'a OptimisticTransactionDBTransaction>, +// ) -> Self { +// Self { +// storage, +// transaction: db_transaction.map(|tx| storage.transaction(tx)) +// } +// } +// } +// +// impl<'b> Storage for TransactionalPrefixedRocksDbStorage<'b> { +// type Error = PrefixedRocksDbStorageError; +// type Batch<'a> +// where +// 'b: 'a, +// = PrefixedRocksDbBatch<'a>; +// type RawIterator<'a> +// where +// 'b: 'a, +// = DBRawTransactionIterator<'a>; +// type StorageTransaction<'a> +// where +// 'b: 'a, +// = PrefixedRocksDbTransaction<'a>; +// type DBTransaction<'a> +// where +// 'b: 'a, +// = OptimisticTransactionDBTransaction<'a>; +// +// fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.put(key, value), +// Some(tx) => tx.put(key, value), +// } +// } +// +// fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.put_aux(key, value), +// Some(tx) => tx.put_aux(key, value), +// } +// } +// +// fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.put_root(key, value), +// Some(tx) => tx.put_root(key, value), +// } +// } +// +// fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.put_meta(key, value), +// Some(tx) => tx.put_meta(key, value), +// } +// } +// +// fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.delete(key), +// Some(tx) => tx.delete(key), +// } +// } +// +// fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.delete_aux(key), +// Some(tx) => tx.delete_aux(key), +// } +// } +// +// fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.delete_root(key), +// Some(tx) => tx.delete_root(key), +// } +// } +// +// fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { +// match &self.transaction { +// None => self.storage.delete_meta(key), +// Some(tx) => tx.delete_meta(key), +// } +// } +// +// fn get(&self, key: &[u8]) -> Result>, Self::Error> { +// match &self.transaction { +// None => self.storage.get(key), +// Some(tx) => tx.get(key), +// } +// } +// +// fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { +// match &self.transaction { +// None => self.storage.get_aux(key), +// Some(tx) => tx.get_aux(key), +// } +// } +// +// fn get_root(&self, key: &[u8]) -> Result>, Self::Error> { +// match &self.transaction { +// None => self.storage.get_root(key), +// Some(tx) => tx.get_root(key), +// } +// } +// +// fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { +// match &self.transaction { +// None => self.storage.get_meta(key), +// Some(tx) => tx.get_meta(key), +// } +// } +// +// fn new_batch<'a>(&'a self) -> Result, Self::Error> { +// self.storage.new_batch() +// } +// +// fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { +// self.storage.commit_batch(batch) +// } +// +// fn flush(&self) -> Result<(), Self::Error> { +// self.storage.flush() +// } +// +// fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { +// self.storage.raw_iter() +// } +// +// fn transaction<'a>(&'a self, tx: &'a Self::DBTransaction<'a>) -> Self::StorageTransaction<'a> { +// self.storage.transaction(tx) +// } +// } diff --git a/storage/src/rocksdb_storage/transaction.rs b/storage/src/rocksdb_storage/transaction.rs index 1560bbd54..9a4788e06 100644 --- a/storage/src/rocksdb_storage/transaction.rs +++ b/storage/src/rocksdb_storage/transaction.rs @@ -3,7 +3,7 @@ use rocksdb::OptimisticTransactionDB; use super::{ make_prefixed_key, PrefixedRocksDbStorageError, AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; -use crate::{Transaction}; +use crate::{Batch, Transaction}; pub struct PrefixedRocksDbTransaction<'a> { transaction: &'a rocksdb::Transaction<'a, OptimisticTransactionDB>, From 213c42c434aa5d5aedc54a12ef95e9f07ccffb07 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Wed, 29 Dec 2021 14:10:39 +0300 Subject: [PATCH 08/16] add tests for transactions --- grovedb/src/lib.rs | 24 ++--- grovedb/src/subtree.rs | 4 +- grovedb/src/tests.rs | 102 +++++++++++++++++---- grovedb/src/transaction.rs | 47 ++++++++-- merk/src/merk/chunks.rs | 16 ++-- merk/src/proofs/chunk.rs | 2 +- merk/src/test_utils/crash_merk.rs | 2 +- storage/src/rocksdb_storage/mod.rs | 33 +++---- storage/src/rocksdb_storage/storage.rs | 46 ++++++---- storage/src/rocksdb_storage/transaction.rs | 2 +- 10 files changed, 195 insertions(+), 83 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 01dd68bf7..1c0c34b8b 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -13,13 +13,11 @@ pub use merk::proofs::{query::QueryItem, Query}; use merk::{self, Merk}; use rs_merkle::{algorithms::Sha256, MerkleTree}; use storage::{ - rocksdb_storage::{ - OptimisticTransactionDB, OptimisticTransactionDBTransaction, PrefixedRocksDbStorage, - PrefixedRocksDbStorageError, PrefixedRocksDbTransaction, - }, - Storage, Transaction, + rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, + Storage, }; pub use subtree::Element; +// pub use transaction::GroveDbTransaction; /// Limit of possible indirections const MAX_REFERENCE_HOPS: usize = 10; @@ -50,7 +48,7 @@ pub struct GroveDb { root_leaf_keys: HashMap, usize>, subtrees: HashMap, Merk>, meta_storage: PrefixedRocksDbStorage, - db: Rc, + pub(crate) db: Rc, } impl GroveDb { @@ -147,7 +145,7 @@ impl GroveDb { path: &[&[u8]], key: Vec, mut element: subtree::Element, - transaction: Option<&'b ::DBTransaction<'b>> + transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result<(), Error> { let compressed_path = Self::compress_path(path, None); match &mut element { @@ -226,7 +224,7 @@ impl GroveDb { path: &[&[u8]], key: Vec, element: subtree::Element, - transaction: Option<&'b ::DBTransaction<'b>> + transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result { if self.get(path, &key).is_ok() { return Ok(false); @@ -331,7 +329,11 @@ impl GroveDb { } /// Method to propagate updated subtree root hashes up to GroveDB root - fn propagate_changes<'a: 'b, 'b>(&'a mut self, path: &[&[u8]], transaction: Option<&'b ::DBTransaction<'b>>) -> Result<(), Error> { + fn propagate_changes<'a: 'b, 'b>( + &'a mut self, + path: &[&[u8]], + transaction: Option<&'b ::DBTransaction<'b>>, + ) -> Result<(), Error> { let mut split_path = path.split_last(); // Go up until only one element in path, which means a key of a root tree while let Some((key, path_slice)) = split_path { @@ -371,7 +373,7 @@ impl GroveDb { res } - pub fn transaction(&self) -> OptimisticTransactionDBTransaction { - self.db.transaction() + pub fn storage(&self) -> Rc { + self.db.clone() } } diff --git a/grovedb/src/subtree.rs b/grovedb/src/subtree.rs index bc49f6608..c4fc26abe 100644 --- a/grovedb/src/subtree.rs +++ b/grovedb/src/subtree.rs @@ -73,10 +73,10 @@ mod tests { fn test_success_insert() { let mut merk = TempMerk::new(); Element::empty_tree() - .insert(&mut merk, b"mykey".to_vec()) + .insert(&mut merk, b"mykey".to_vec(), None) .expect("expected successful insertion"); Element::Item(b"value".to_vec()) - .insert(&mut merk, b"another-key".to_vec()) + .insert(&mut merk, b"another-key".to_vec(), None) .expect("expected successful insertion 2"); assert_eq!( diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index 790edb740..b64c88e56 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -40,9 +40,9 @@ fn make_grovedb() -> TempGroveDb { } fn add_test_leafs(db: &mut GroveDb) { - db.insert(&[], TEST_LEAF.to_vec(), Element::empty_tree()) + db.insert(&[], TEST_LEAF.to_vec(), Element::empty_tree(), None) .expect("successful root tree leaf insert"); - db.insert(&[], ANOTHER_TEST_LEAF.to_vec(), Element::empty_tree()) + db.insert(&[], ANOTHER_TEST_LEAF.to_vec(), Element::empty_tree(), None) .expect("successful root tree leaf 2 insert"); } @@ -56,7 +56,7 @@ fn test_init() { fn test_insert_value_to_merk() { let mut db = make_grovedb(); let element = Element::Item(b"ayy".to_vec()); - db.insert(&[TEST_LEAF], b"key".to_vec(), element.clone()) + db.insert(&[TEST_LEAF], b"key".to_vec(), element.clone(), None) .expect("successful insert"); assert_eq!( db.get(&[TEST_LEAF], b"key").expect("succesful get"), @@ -70,11 +70,16 @@ fn test_insert_value_to_subtree() { let element = Element::Item(b"ayy".to_vec()); // Insert a subtree first - db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("successful subtree insert"); // Insert an element into subtree - db.insert(&[TEST_LEAF, b"key1"], b"key2".to_vec(), element.clone()) - .expect("successful value insert"); + db.insert( + &[TEST_LEAF, b"key1"], + b"key2".to_vec(), + element.clone(), + None, + ) + .expect("successful value insert"); assert_eq!( db.get(&[TEST_LEAF, b"key1"], b"key2") .expect("succesful get"), @@ -89,12 +94,13 @@ fn test_changes_propagated() { let element = Element::Item(b"ayy".to_vec()); // Insert some nested subtrees - db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("successful subtree 1 insert"); db.insert( &[TEST_LEAF, b"key1"], b"key2".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree 2 insert"); // Insert an element into subtree @@ -102,6 +108,7 @@ fn test_changes_propagated() { &[TEST_LEAF, b"key1", b"key2"], b"key3".to_vec(), element.clone(), + None, ) .expect("successful value insert"); assert_eq!( @@ -122,14 +129,20 @@ fn test_follow_references() { &[TEST_LEAF], b"reference_key".to_vec(), Element::Reference(vec![TEST_LEAF.to_vec(), b"key2".to_vec(), b"key3".to_vec()]), + None, ) .expect("successful reference insert"); // Insert an item to refer to - db.insert(&[TEST_LEAF], b"key2".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key2".to_vec(), Element::empty_tree(), None) .expect("successful subtree 1 insert"); - db.insert(&[TEST_LEAF, b"key2"], b"key3".to_vec(), element.clone()) - .expect("successful value insert"); + db.insert( + &[TEST_LEAF, b"key2"], + b"key3".to_vec(), + element.clone(), + None, + ) + .expect("successful value insert"); assert_eq!( db.get(&[TEST_LEAF], b"reference_key") .expect("succesful get"), @@ -145,6 +158,7 @@ fn test_cyclic_references() { &[TEST_LEAF], b"reference_key_1".to_vec(), Element::Reference(vec![TEST_LEAF.to_vec(), b"reference_key_2".to_vec()]), + None, ) .expect("successful reference 1 insert"); @@ -152,6 +166,7 @@ fn test_cyclic_references() { &[TEST_LEAF], b"reference_key_2".to_vec(), Element::Reference(vec![TEST_LEAF.to_vec(), b"reference_key_1".to_vec()]), + None, ) .expect("successful reference 2 insert"); @@ -171,6 +186,7 @@ fn test_too_many_indirections() { &[TEST_LEAF], b"key0".to_vec(), Element::Item(b"oops".to_vec()), + None, ) .expect("successful item insert"); @@ -179,6 +195,7 @@ fn test_too_many_indirections() { &[TEST_LEAF], keygen(i), Element::Reference(vec![TEST_LEAF.to_vec(), keygen(i - 1)]), + None, ) .expect("successful reference insert"); } @@ -200,12 +217,13 @@ fn test_tree_structure_is_presistent() { add_test_leafs(&mut db); // Insert some nested subtrees - db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("successful subtree 1 insert"); db.insert( &[TEST_LEAF, b"key1"], b"key2".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree 2 insert"); // Insert an element into subtree @@ -213,6 +231,7 @@ fn test_tree_structure_is_presistent() { &[TEST_LEAF, b"key1", b"key2"], b"key3".to_vec(), element.clone(), + None, ) .expect("successful value insert"); assert_eq!( @@ -245,13 +264,19 @@ fn test_root_tree_leafs_are_noted() { fn test_proof_construction() { let mut temp_db = make_grovedb(); temp_db - .insert(&[TEST_LEAF], b"innertree".to_vec(), Element::empty_tree()) + .insert( + &[TEST_LEAF], + b"innertree".to_vec(), + Element::empty_tree(), + None, + ) .expect("successful subtree insert"); temp_db .insert( &[TEST_LEAF, b"innertree"], b"key1".to_vec(), Element::Item(b"value1".to_vec()), + None, ) .expect("successful item insert"); temp_db @@ -259,6 +284,7 @@ fn test_proof_construction() { &[TEST_LEAF, b"innertree"], b"key2".to_vec(), Element::Item(b"value2".to_vec()), + None, ) .expect("successful item insert"); @@ -266,17 +292,17 @@ fn test_proof_construction() { let mut inner_tree_merk = TempMerk::new(); let value_element = Element::Item(b"value1".to_vec()); value_element - .insert(&mut inner_tree_merk, b"key1".to_vec()) + .insert(&mut inner_tree_merk, b"key1".to_vec(), None) .expect("Expected to insert value"); let value_element = Element::Item(b"value2".to_vec()); value_element - .insert(&mut inner_tree_merk, b"key2".to_vec()) + .insert(&mut inner_tree_merk, b"key2".to_vec(), None) .expect("Expected to insert value"); let mut test_leaf_merk = TempMerk::new(); let inner_tree_root_element = Element::Tree(inner_tree_merk.root_hash()); inner_tree_root_element - .insert(&mut test_leaf_merk, b"innertree".to_vec()) + .insert(&mut test_leaf_merk, b"innertree".to_vec(), None) .expect("Expected to insert value"); let another_test_leaf_merk = TempMerk::new(); @@ -395,12 +421,12 @@ fn test_insert_if_not_exists() { // Insert twice at the same path assert_eq!( - db.insert_if_not_exists(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert_if_not_exists(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("Provided valid path"), true ); assert_eq!( - db.insert_if_not_exists(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert_if_not_exists(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("Provided valid path"), false ); @@ -410,6 +436,48 @@ fn test_insert_if_not_exists() { &[TEST_LEAF, b"unknown"], b"key1".to_vec(), Element::empty_tree(), + None, + ); + assert!(matches!(result, Err(Error::InvalidPath(_)))); +} + +#[test] +fn test_insert_with_transaction_should_use_transaction() { + let mut db = make_grovedb(); + let storage = db.storage(); + let transaction = storage.transaction(); + + // Insert twice at the same path + assert_eq!( + db.insert_if_not_exists( + &[TEST_LEAF], + b"key1".to_vec(), + Element::empty_tree(), + Some(&transaction) + ) + .expect("Provided valid path"), + true + ); + assert_eq!( + db.insert_if_not_exists( + &[TEST_LEAF], + b"key1".to_vec(), + Element::empty_tree(), + Some(&transaction) + ) + .expect("Provided valid path"), + false + ); + + // Should propagate errors from insertion + let result = db.insert_if_not_exists( + &[TEST_LEAF, b"unknown"], + b"key1".to_vec(), + Element::empty_tree(), + Some(&transaction), ); assert!(matches!(result, Err(Error::InvalidPath(_)))); + + // let key = b"key1".to_vec(); + // let kekco = db.get(&[TEST_LEAF], b"key1".as_ref()); } diff --git a/grovedb/src/transaction.rs b/grovedb/src/transaction.rs index 8ea508114..4bac838ce 100644 --- a/grovedb/src/transaction.rs +++ b/grovedb/src/transaction.rs @@ -1,16 +1,36 @@ +// use std::rc::Rc; +// use storage::rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}; +// use storage::Storage; +// use crate::GroveDb; // use super::{subtree, Error}; // -// pub struct GroveDbTransaction { -// +// pub struct GroveDbTransaction<'a, 'db> { +// grove_db: &'a mut GroveDb, +// db: Rc, +// transaction: Option<::DBTransaction<'db>> // } // -// impl GroveDbTransaction { +// impl<'a, 'db> GroveDbTransaction<'a, 'db> { +// pub fn new(grove_db: &'a mut GroveDb, db: Rc) -> Self { +// let kek = Self { +// grove_db, db, transaction: None +// }; +// kek.start() +// } +// +// fn start(mut self) -> Self { +// self.transaction = Some(self.db.transaction()); +// self +// } +// // pub fn insert( // &mut self, // path: &[&[u8]], // key: Vec, // mut element: subtree::Element, -// ) -> Result<(), Error> { } +// ) -> Result<(), Error> { +// self.grove_db.insert(path, key, element, self.transaction.as_ref()) +// } // // pub fn insert_if_not_exists( // &mut self, @@ -18,11 +38,22 @@ // key: Vec, // element: subtree::Element, // ) -> Result { -// +// self.grove_db.insert_if_not_exists(path, key, element, self.transaction.as_ref()) // } // -// pub fn get(&self, path: &[&[u8]], key: &[u8]) -> Result { +// // pub fn get(&self, path: &[&[u8]], key: &[u8]) -> Result { +// // self.grove_db.get(path, key, Some(&self.transaction)) +// // } // -// } +// // /// Commits and consumes the transaction +// // pub fn commit(self) -> Result<(), Error> { +// // self.transaction.commit().map_err(Into::::into)?; +// // Ok(()) +// // } +// // +// // /// Rolls back the transaction +// // pub fn rollback(&self) -> Result<(), Error> { +// // self.transaction.rollback().map_err(Into::::into)?; +// // Ok(()) +// // } // } diff --git a/merk/src/merk/chunks.rs b/merk/src/merk/chunks.rs index 55a9aaa21..6e5396ec8 100644 --- a/merk/src/merk/chunks.rs +++ b/merk/src/merk/chunks.rs @@ -193,7 +193,7 @@ mod tests { fn len_small() { let mut merk = TempMerk::new(); let batch = make_batch_seq(1..256); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let chunks = merk.chunks().unwrap(); assert_eq!(chunks.len(), 1); @@ -204,7 +204,7 @@ mod tests { fn len_big() { let mut merk = TempMerk::new(); let batch = make_batch_seq(1..10_000); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let chunks = merk.chunks().unwrap(); assert_eq!(chunks.len(), 129); @@ -215,7 +215,7 @@ mod tests { fn generate_and_verify_chunks() { let mut merk = TempMerk::new(); let batch = make_batch_seq(1..10_000); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let mut chunks = merk.chunks().unwrap().into_iter().map(Result::unwrap); @@ -241,7 +241,7 @@ mod tests { let mut merk = Merk::open(PrefixedRocksDbStorage::new(db, Vec::new()).unwrap()).unwrap(); let batch = make_batch_seq(1..10); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); merk.chunks() .unwrap() @@ -288,7 +288,7 @@ mod tests { fn random_access_chunks() { let mut merk = TempMerk::new(); let batch = make_batch_seq(1..111); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let chunks = merk .chunks() @@ -322,7 +322,7 @@ mod tests { fn test_chunk_index_oob() { let mut merk = TempMerk::new(); let batch = make_batch_seq(1..42); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let mut producer = merk.chunks().unwrap(); let _chunk = producer.chunk(50000).unwrap(); @@ -332,7 +332,7 @@ mod tests { fn test_chunk_index_gt_1_access() { let mut merk = TempMerk::new(); let batch = make_batch_seq(1..513); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let mut producer = merk.chunks().unwrap(); println!("length: {}", producer.len()); @@ -413,7 +413,7 @@ mod tests { fn test_next_chunk_index_oob() { let mut merk = TempMerk::new(); let batch = make_batch_seq(1..42); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let mut producer = merk.chunks().unwrap(); let _chunk1 = producer.next_chunk(); diff --git a/merk/src/proofs/chunk.rs b/merk/src/proofs/chunk.rs index f0495878c..cbaaf23f4 100644 --- a/merk/src/proofs/chunk.rs +++ b/merk/src/proofs/chunk.rs @@ -407,7 +407,7 @@ mod tests { fn leaf_chunk_roundtrip() { let mut merk = TempMerk::new(); let batch = make_batch_seq(0..31); - merk.apply(batch.as_slice(), &[]).unwrap(); + merk.apply(batch.as_slice(), &[], None).unwrap(); let root_node = merk.tree.take(); let root_key = root_node.as_ref().unwrap().key().to_vec(); diff --git a/merk/src/test_utils/crash_merk.rs b/merk/src/test_utils/crash_merk.rs index 6f301ea60..09048f4da 100644 --- a/merk/src/test_utils/crash_merk.rs +++ b/merk/src/test_utils/crash_merk.rs @@ -61,7 +61,7 @@ mod tests { #[ignore] // currently this still works because we enabled the WAL fn crash() { let mut merk = CrashMerk::open().expect("failed to open merk"); - merk.apply(&[(vec![1, 2, 3], Op::Put(vec![4, 5, 6]))], &[]) + merk.apply(&[(vec![1, 2, 3], Op::Put(vec![4, 5, 6]))], &[], None) .expect("apply failed"); merk.crash(); diff --git a/storage/src/rocksdb_storage/mod.rs b/storage/src/rocksdb_storage/mod.rs index c3ae4e1c5..2e78b6db6 100644 --- a/storage/src/rocksdb_storage/mod.rs +++ b/storage/src/rocksdb_storage/mod.rs @@ -11,9 +11,10 @@ mod storage; mod transaction; pub use batch::PrefixedRocksDbBatch; -pub use storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}; pub use transaction::PrefixedRocksDbTransaction; +pub use self::storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}; + const AUX_CF_NAME: &str = "aux"; const ROOTS_CF_NAME: &str = "roots"; const META_CF_NAME: &str = "meta"; @@ -312,7 +313,7 @@ mod tests { #[test] fn test_batch() { let storage = TempPrefixedStorage::new(); - let mut batch = storage.new_batch().expect("cannot create batch"); + let mut batch = storage.new_batch(None).expect("cannot create batch"); batch.put(b"key1", b"value1"); batch.put(b"key2", b"value2"); batch.put_root(b"root", b"yeet"); @@ -340,18 +341,18 @@ mod tests { ); } - #[test] - fn transaction_commit_should_work() { - let storage = TempPrefixedStorage::new(); - let transaction = storage.transaction(); - transaction - .put(b"key1", b"value1") - .expect("Expected to put value"); - transaction - .put(b"key2", b"value2") - .expect("Expected to put value"); - transaction - .put_root(b"root", b"yeet") - .expect("Expected to put root"); - } + // #[test] + // fn transaction_commit_should_work() { + // let storage = TempPrefixedStorage::new(); + // let transaction = storage.transaction(); + // transaction + // .put(b"key1", b"value1") + // .expect("Expected to put value"); + // transaction + // .put(b"key2", b"value2") + // .expect("Expected to put value"); + // transaction + // .put_root(b"root", b"yeet") + // .expect("Expected to put root"); + // } } diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index 8d7b6f5fc..6b09307d2 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -6,8 +6,13 @@ use super::{ make_prefixed_key, DBRawTransactionIterator, PrefixedRocksDbBatch, PrefixedRocksDbTransaction, AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; -use crate::{rocksdb_storage::OptimisticTransactionDBTransaction, Storage, Transaction}; -use crate::rocksdb_storage::batch::{OrBatch, PrefixedTransactionalRocksDbBatch}; +use crate::{ + rocksdb_storage::{ + batch::{OrBatch, PrefixedTransactionalRocksDbBatch}, + OptimisticTransactionDBTransaction, + }, + Storage, +}; /// RocksDB wrapper to store items with prefixes pub struct PrefixedRocksDbStorage { @@ -140,29 +145,34 @@ impl Storage for PrefixedRocksDbStorage { Ok(self.db.get_cf(self.cf_meta()?, key)?) } - fn new_batch<'a: 'b, 'b>(&'a self, transaction: Option<&'b OptimisticTransactionDBTransaction>) -> Result, Self::Error> { + fn new_batch<'a: 'b, 'b>( + &'a self, + transaction: Option<&'b OptimisticTransactionDBTransaction>, + ) -> Result, Self::Error> { match transaction { - Some(tx) => Ok(OrBatch::TransactionalBatch(PrefixedTransactionalRocksDbBatch { - prefix: self.prefix.clone(), - transaction: tx, - cf_aux: self.cf_aux()?, - cf_roots: self.cf_roots()?, - })), + Some(tx) => Ok(OrBatch::TransactionalBatch( + PrefixedTransactionalRocksDbBatch { + prefix: self.prefix.clone(), + transaction: tx, + cf_aux: self.cf_aux()?, + cf_roots: self.cf_roots()?, + }, + )), None => Ok(OrBatch::Batch(PrefixedRocksDbBatch { prefix: self.prefix.clone(), batch: WriteBatchWithTransaction::::default(), cf_aux: self.cf_aux()?, cf_roots: self.cf_roots()?, - })) + })), } } fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { - // Do nothing if transaction exists, as the transaction must be explicitly committed by - // its creator + // Do nothing if transaction exists, as the transaction must be explicitly + // committed by its creator match batch { - OrBatch::TransactionalBatch(_) => {}, - OrBatch::Batch(batch) => self.db.write(batch.batch)? + OrBatch::TransactionalBatch(_) => {} + OrBatch::Batch(batch) => self.db.write(batch.batch)?, } Ok(()) } @@ -308,8 +318,8 @@ impl Storage for PrefixedRocksDbStorage { // self.storage.new_batch() // } // -// fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { -// self.storage.commit_batch(batch) +// fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), +// Self::Error> { self.storage.commit_batch(batch) // } // // fn flush(&self) -> Result<(), Self::Error> { @@ -320,7 +330,7 @@ impl Storage for PrefixedRocksDbStorage { // self.storage.raw_iter() // } // -// fn transaction<'a>(&'a self, tx: &'a Self::DBTransaction<'a>) -> Self::StorageTransaction<'a> { -// self.storage.transaction(tx) +// fn transaction<'a>(&'a self, tx: &'a Self::DBTransaction<'a>) -> +// Self::StorageTransaction<'a> { self.storage.transaction(tx) // } // } diff --git a/storage/src/rocksdb_storage/transaction.rs b/storage/src/rocksdb_storage/transaction.rs index 9a4788e06..b744e2a9e 100644 --- a/storage/src/rocksdb_storage/transaction.rs +++ b/storage/src/rocksdb_storage/transaction.rs @@ -3,7 +3,7 @@ use rocksdb::OptimisticTransactionDB; use super::{ make_prefixed_key, PrefixedRocksDbStorageError, AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; -use crate::{Batch, Transaction}; +use crate::Transaction; pub struct PrefixedRocksDbTransaction<'a> { transaction: &'a rocksdb::Transaction<'a, OptimisticTransactionDB>, From 809f0bab0a73a2524699a2af3de412e79c494bce Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Thu, 30 Dec 2021 20:22:03 +0300 Subject: [PATCH 09/16] implement some tree isolation --- grovedb/src/lib.rs | 177 ++++++++++++++++++++----- grovedb/src/subtree.rs | 9 +- grovedb/src/tests.rs | 45 +++---- grovedb/src/transaction.rs | 51 ++++--- merk/src/merk/mod.rs | 42 +++++- merk/src/tree/kv.rs | 1 + merk/src/tree/link.rs | 1 + merk/src/tree/mod.rs | 4 +- storage/src/rocksdb_storage/storage.rs | 1 + 9 files changed, 237 insertions(+), 94 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 1c0c34b8b..0fb5dcab1 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -13,10 +13,14 @@ pub use merk::proofs::{query::QueryItem, Query}; use merk::{self, Merk}; use rs_merkle::{algorithms::Sha256, MerkleTree}; use storage::{ - rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, - Storage, + rocksdb_storage::{ + OptimisticTransactionDBTransaction, PrefixedRocksDbStorage, PrefixedRocksDbStorageError, + }, + Storage, Transaction, }; pub use subtree::Element; + +use crate::transaction::GroveDbTransaction; // pub use transaction::GroveDbTransaction; /// Limit of possible indirections @@ -49,9 +53,35 @@ pub struct GroveDb { subtrees: HashMap, Merk>, meta_storage: PrefixedRocksDbStorage, pub(crate) db: Rc, + // Locks the database for writes during the transaction + is_readonly: bool, + // Temp trees used for writes during transaction + temp_root_tree: MerkleTree, + temp_root_leaf_keys: HashMap, usize>, + temp_subtrees: HashMap, Merk>, } impl GroveDb { + pub fn new( + root_tree: MerkleTree, + root_leaf_keys: HashMap, usize>, + subtrees: HashMap, Merk>, + meta_storage: PrefixedRocksDbStorage, + db: Rc, + ) -> Self { + Self { + root_tree, + root_leaf_keys, + subtrees, + meta_storage, + db, + temp_root_tree: MerkleTree::new(), + temp_root_leaf_keys: HashMap::new(), + temp_subtrees: HashMap::new(), + is_readonly: false, + } + } + pub fn open>(path: P) -> Result { let db = Rc::new( storage::rocksdb_storage::OptimisticTransactionDB::open_cf_descriptors( @@ -89,13 +119,13 @@ impl GroveDb { HashMap::new() }; - Ok(GroveDb { - root_tree: Self::build_root_tree(&subtrees, &root_leaf_keys), - db, - subtrees, + Ok(GroveDb::new( + Self::build_root_tree(&subtrees, &root_leaf_keys), root_leaf_keys, + subtrees, meta_storage, - }) + db, + )) } // TODO: Checkpoints are currently not implemented for the transactional DB @@ -108,19 +138,51 @@ impl GroveDb { // GroveDb::open(path) // } - fn store_subtrees_keys_data(&self) -> Result<(), Error> { - let prefixes: Vec> = self.subtrees.keys().map(|x| x.clone()).collect(); - self.meta_storage.put_meta( - SUBTRESS_SERIALIZED_KEY, - &bincode::serialize(&prefixes) - .map_err(|_| Error::CorruptedData(String::from("unable to serialize prefixes")))?, - )?; - self.meta_storage.put_meta( - ROOT_LEAFS_SERIALIZED_KEY, - &bincode::serialize(&self.root_leaf_keys).map_err(|_| { - Error::CorruptedData(String::from("unable to serialize root leafs")) - })?, - )?; + fn store_subtrees_keys_data( + &self, + db_transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result<(), Error> { + let subtrees = match db_transaction { + None => &self.subtrees, + Some(_) => &self.temp_subtrees, + }; + + let prefixes: Vec> = subtrees.keys().map(|x| x.clone()).collect(); + + // TODO: make StorageOrTransaction which will has the access to either storage + // or transaction + match db_transaction { + None => { + self.meta_storage.put_meta( + SUBTRESS_SERIALIZED_KEY, + &bincode::serialize(&prefixes).map_err(|_| { + Error::CorruptedData(String::from("unable to serialize prefixes")) + })?, + )?; + self.meta_storage.put_meta( + ROOT_LEAFS_SERIALIZED_KEY, + &bincode::serialize(&self.temp_root_leaf_keys).map_err(|_| { + Error::CorruptedData(String::from("unable to serialize root leafs")) + })?, + )?; + } + Some(tx) => { + let transaction = self.meta_storage.transaction(tx); + transaction.put_meta( + SUBTRESS_SERIALIZED_KEY, + &bincode::serialize(&prefixes).map_err(|_| { + Error::CorruptedData(String::from("unable to serialize prefixes")) + })?, + )?; + transaction.put_meta( + ROOT_LEAFS_SERIALIZED_KEY, + &bincode::serialize(&self.root_leaf_keys).map_err(|_| { + Error::CorruptedData(String::from("unable to serialize root leafs")) + })?, + )?; + } + } + Ok(()) } @@ -147,6 +209,21 @@ impl GroveDb { mut element: subtree::Element, transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result<(), Error> { + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + + let root_leaf_keys = match transaction { + None => &mut self.root_leaf_keys, + Some(_) => &mut self.temp_root_leaf_keys, + }; + + let root_tree = match transaction { + None => &mut self.root_tree, + Some(_) => &mut self.temp_root_tree, + }; + let compressed_path = Self::compress_path(path, None); match &mut element { Element::Tree(subtree_root_hash) => { @@ -169,25 +246,23 @@ impl GroveDb { // Open Merk and put handle into `subtrees` dictionary accessible by its // compressed path let (compressed_path_subtree, subtree_merk) = create_subtree_merk()?; - self.subtrees - .insert(compressed_path_subtree.clone(), subtree_merk); + subtrees.insert(compressed_path_subtree.clone(), subtree_merk); // Update root leafs index to persist rs-merkle structure later - if self.root_leaf_keys.get(&compressed_path_subtree).is_none() { - self.root_leaf_keys - .insert(compressed_path_subtree, self.root_tree.leaves_len()); + if root_leaf_keys.get(&compressed_path_subtree).is_none() { + root_leaf_keys.insert(compressed_path_subtree, root_tree.leaves_len()); } self.propagate_changes(&[&key], transaction)?; } else { // Add subtree to another subtree. // First, check if a subtree exists to create a new subtree under it - self.subtrees + subtrees .get(&compressed_path) .ok_or(Error::InvalidPath("no subtree found under that path"))?; let (compressed_path_subtree, subtree_merk) = create_subtree_merk()?; // Set tree value as a a subtree root hash *subtree_root_hash = subtree_merk.root_hash(); - self.subtrees.insert(compressed_path_subtree, subtree_merk); + subtrees.insert(compressed_path_subtree, subtree_merk); // Had to take merk from `subtrees` once again to solve multiple &mut s let mut merk = self .subtrees @@ -197,7 +272,7 @@ impl GroveDb { element.insert(&mut merk, key, transaction)?; self.propagate_changes(path, transaction)?; } - self.store_subtrees_keys_data()?; + self.store_subtrees_keys_data(transaction)?; } _ => { // If path is empty that means there is an attempt to insert something into a @@ -334,23 +409,36 @@ impl GroveDb { path: &[&[u8]], transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result<(), Error> { + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + + let root_leaf_keys = match transaction { + None => &mut self.root_leaf_keys, + Some(_) => &mut self.temp_root_leaf_keys, + }; + let mut split_path = path.split_last(); // Go up until only one element in path, which means a key of a root tree while let Some((key, path_slice)) = split_path { if path_slice.is_empty() { // Hit the root tree - self.root_tree = Self::build_root_tree(&self.subtrees, &self.root_leaf_keys); + match transaction { + None => self.root_tree = Self::build_root_tree(&subtrees, &root_leaf_keys), + Some(_) => { + self.temp_root_tree = Self::build_root_tree(&subtrees, &root_leaf_keys) + } + }; break; } else { let compressed_path_upper_tree = Self::compress_path(path_slice, None); let compressed_path_subtree = Self::compress_path(path_slice, Some(key)); - let subtree = self - .subtrees + let subtree = subtrees .get(&compressed_path_subtree) .ok_or(Error::InvalidPath("no subtree found under that path"))?; let element = Element::Tree(subtree.root_hash()); - let upper_tree = self - .subtrees + let upper_tree = subtrees .get_mut(&compressed_path_upper_tree) .ok_or(Error::InvalidPath("no subtree found under that path"))?; element.insert(upper_tree, key.to_vec(), transaction)?; @@ -376,4 +464,27 @@ impl GroveDb { pub fn storage(&self) -> Rc { self.db.clone() } + + pub fn start_transaction(&mut self) { + // Locking all writes outside of the transaction + self.is_readonly = true; + + // Cloning all the trees to maintain original state before the transaction + self.temp_root_tree = self.root_tree.clone(); + self.temp_root_leaf_keys = self.root_leaf_keys.clone(); + self.temp_subtrees = self.subtrees.clone(); + } + + pub fn commit_transaction(&mut self) { + // Enabling writes again + self.is_readonly = false; + + // Copying all changes that were made during the transaction into the db + self.root_tree = self.temp_root_tree.clone(); + self.root_leaf_keys = self.temp_root_leaf_keys.drain().collect(); + self.subtrees = self.temp_subtrees.drain().collect(); + + // TODO: root tree actually does support transactions, no need to do that + self.temp_root_tree = MerkleTree::new(); + } } diff --git a/grovedb/src/subtree.rs b/grovedb/src/subtree.rs index c4fc26abe..17b9457d2 100644 --- a/grovedb/src/subtree.rs +++ b/grovedb/src/subtree.rs @@ -3,8 +3,7 @@ //! Merk API to GroveDB needs. use merk::Op; use serde::{Deserialize, Serialize}; -use storage::rocksdb_storage::PrefixedRocksDbStorage; -use storage::Storage; +use storage::{rocksdb_storage::PrefixedRocksDbStorage, Storage}; use crate::{Error, Merk}; @@ -43,13 +42,13 @@ impl Element { /// Insert an element in Merk under a key; path should be resolved and /// proper Merk should be loaded by this moment /// If transaction is not passed, the batch will be written immediately. - /// If transaction is passed, the operation will be committed on the transaction - /// commit. + /// If transaction is passed, the operation will be committed on the + /// transaction commit. pub fn insert<'a: 'b, 'b>( &'a self, merk: &mut Merk, key: Vec, - transaction: Option<&'b ::DBTransaction<'b>> + transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result<(), Error> { let batch_operations = [( diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index b64c88e56..6b5280dec 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -444,40 +444,27 @@ fn test_insert_if_not_exists() { #[test] fn test_insert_with_transaction_should_use_transaction() { let mut db = make_grovedb(); + db.start_transaction(); let storage = db.storage(); let transaction = storage.transaction(); - // Insert twice at the same path - assert_eq!( - db.insert_if_not_exists( - &[TEST_LEAF], - b"key1".to_vec(), - Element::empty_tree(), - Some(&transaction) - ) - .expect("Provided valid path"), - true - ); - assert_eq!( - db.insert_if_not_exists( - &[TEST_LEAF], - b"key1".to_vec(), - Element::empty_tree(), - Some(&transaction) - ) - .expect("Provided valid path"), - false - ); + // Check that there's no such key in the DB + let key = b"key1".to_vec(); + let result = db.get(&[TEST_LEAF], &key); + assert!(matches!(result, Err(Error::InvalidPath(_)))); - // Should propagate errors from insertion - let result = db.insert_if_not_exists( - &[TEST_LEAF, b"unknown"], - b"key1".to_vec(), + db.insert( + &[TEST_LEAF], + key.clone(), Element::empty_tree(), Some(&transaction), - ); - assert!(matches!(result, Err(Error::InvalidPath(_)))); + ) + .expect("Expected db.insert to work"); + + // transaction.rollback(); - // let key = b"key1".to_vec(); - // let kekco = db.get(&[TEST_LEAF], b"key1".as_ref()); + // Get without the transaction should return previous data set + let key = b"key1".to_vec(); + let result = db.get(&[TEST_LEAF], &key); + assert!(matches!(result, Err(Error::InvalidPath(_)))); } diff --git a/grovedb/src/transaction.rs b/grovedb/src/transaction.rs index 4bac838ce..a94c0b54b 100644 --- a/grovedb/src/transaction.rs +++ b/grovedb/src/transaction.rs @@ -1,19 +1,22 @@ -// use std::rc::Rc; -// use storage::rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}; -// use storage::Storage; -// use crate::GroveDb; -// use super::{subtree, Error}; -// +use std::rc::Rc; + +use storage::{ + rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, + Storage, +}; + +use super::{subtree, Error}; +use crate::GroveDb; // pub struct GroveDbTransaction<'a, 'db> { // grove_db: &'a mut GroveDb, // db: Rc, -// transaction: Option<::DBTransaction<'db>> -// } +// transaction: Option<::DBTransaction<'db>> } // // impl<'a, 'db> GroveDbTransaction<'a, 'db> { -// pub fn new(grove_db: &'a mut GroveDb, db: Rc) -> Self { -// let kek = Self { -// grove_db, db, transaction: None +// pub fn new(grove_db: &'a mut GroveDb, db: +// Rc) -> Self { let +// kek = Self { grove_db, db, transaction: None // }; // kek.start() // } @@ -38,22 +41,30 @@ // key: Vec, // element: subtree::Element, // ) -> Result { -// self.grove_db.insert_if_not_exists(path, key, element, self.transaction.as_ref()) -// } +// self.grove_db.insert_if_not_exists(path, key, element, +// self.transaction.as_ref()) } // -// // pub fn get(&self, path: &[&[u8]], key: &[u8]) -> Result { -// // self.grove_db.get(path, key, Some(&self.transaction)) -// // } +// // pub fn get(&self, path: &[&[u8]], key: &[u8]) -> +// Result { // self.grove_db.get(path, key, +// Some(&self.transaction)) // } // // // /// Commits and consumes the transaction // // pub fn commit(self) -> Result<(), Error> { -// // self.transaction.commit().map_err(Into::::into)?; -// // Ok(()) +// // +// self.transaction.commit().map_err(Into::::into)? +// ; // Ok(()) // // } // // // // /// Rolls back the transaction // // pub fn rollback(&self) -> Result<(), Error> { -// // self.transaction.rollback().map_err(Into::::into)?; -// // Ok(()) +// // +// self.transaction.rollback().map_err(Into:::: +// into)?; // Ok(()) // // } // } + +pub struct GroveDbTransaction<'a> { + db_transaction: Option<::DBTransaction<'a>>, +} + +// impl GroveDbTransaction<'_> {} diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index d3c9405a3..e0b0daedb 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -4,7 +4,7 @@ pub mod chunks; use std::{cell::Cell, cmp::Ordering, collections::LinkedList}; use anyhow::{anyhow, bail, Result}; -use storage::{self, Batch, Storage, Store}; +use storage::{self, rocksdb_storage::PrefixedRocksDbStorage, Batch, Storage, Store}; use crate::{ proofs::{encode_into, query::QueryItem, Query}, @@ -122,7 +122,12 @@ where /// ]; /// store.apply(batch, &[], None).unwrap(); /// ``` - pub fn apply<'a: 'b, 'b>(&'a mut self, batch: &MerkBatch, aux: &MerkBatch, transaction: Option<&'b S::DBTransaction<'b>>) -> Result<()> { + pub fn apply<'a: 'b, 'b>( + &'a mut self, + batch: &MerkBatch, + aux: &MerkBatch, + transaction: Option<&'b S::DBTransaction<'b>>, + ) -> Result<()> { // ensure keys in batch are sorted and unique let mut maybe_prev_key: Option> = None; for (key, _) in batch.iter() { @@ -160,7 +165,12 @@ where /// ]; /// unsafe { store.apply_unchecked(batch, &[], None).unwrap() }; /// ``` - pub unsafe fn apply_unchecked<'a: 'b, 'b>(&'a mut self, batch: &MerkBatch, aux: &MerkBatch, transaction: Option<&'b S::DBTransaction<'b>>) -> Result<()> { + pub unsafe fn apply_unchecked<'a: 'b, 'b>( + &'a mut self, + batch: &MerkBatch, + aux: &MerkBatch, + transaction: Option<&'b S::DBTransaction<'b>>, + ) -> Result<()> { let maybe_walker = self .tree .take() @@ -221,7 +231,12 @@ where }) } - pub fn commit<'a: 'b, 'b>(&'a mut self, deleted_keys: LinkedList>, aux: &MerkBatch, transaction: Option<&'b S::DBTransaction<'b>>) -> Result<()> { + pub fn commit<'a: 'b, 'b>( + &'a mut self, + deleted_keys: LinkedList>, + aux: &MerkBatch, + transaction: Option<&'b S::DBTransaction<'b>>, + ) -> Result<()> { let mut batch = self.storage.new_batch(transaction)?; let mut to_batch = self.use_tree_mut(|maybe_tree| -> UseTreeMutResult { // TODO: concurrent commit @@ -314,6 +329,23 @@ where } } +impl Clone for Merk { + fn clone(&self) -> Self { + let tree_clone = match self.tree.take() { + None => None, + Some(tree) => { + let clone = tree.clone(); + self.tree.set(Some(tree)); + Some(clone) + } + }; + Self { + tree: Cell::new(tree_clone), + storage: self.storage.clone(), + } + } +} + // TODO: get rid of Fetch/source and use GroveDB storage abstraction #[derive(Debug)] pub struct MerkSource<'a, S: Storage> { @@ -464,7 +496,7 @@ mod test { merk.apply( &[(vec![0], Op::Put(vec![1]))], &[(vec![2], Op::Put(vec![3]))], - None + None, ) .expect("apply failed"); diff --git a/merk/src/tree/kv.rs b/merk/src/tree/kv.rs index 8307d5b03..95801551f 100644 --- a/merk/src/tree/kv.rs +++ b/merk/src/tree/kv.rs @@ -10,6 +10,7 @@ use super::hash::{kv_hash, Hash, HASH_LENGTH, NULL_HASH}; // field and value field. /// Contains a key/value pair, and the hash of the key/value pair. +#[derive(Clone)] pub struct KV { pub(super) key: Vec, pub(super) value: Vec, diff --git a/merk/src/tree/link.rs b/merk/src/tree/link.rs index d02820ea8..b3ad2058c 100644 --- a/merk/src/tree/link.rs +++ b/merk/src/tree/link.rs @@ -11,6 +11,7 @@ use super::{hash::Hash, Tree}; /// Represents a reference to a child tree node. Links may or may not contain /// the child's `Tree` instance (storing its key if not). +#[derive(Clone)] pub enum Link { /// Represents a child tree node which has been pruned from memory, only /// retaining a reference to it (its key). The child node can always be diff --git a/merk/src/tree/mod.rs b/merk/src/tree/mod.rs index a6dbad230..0ece56160 100644 --- a/merk/src/tree/mod.rs +++ b/merk/src/tree/mod.rs @@ -25,7 +25,7 @@ pub use walk::{Fetch, RefWalker, Walker}; // relevant methods /// The fields of the `Tree` type, stored on the heap. -#[derive(Encode, Decode)] +#[derive(Clone, Encode, Decode)] struct TreeInner { left: Option, right: Option, @@ -37,7 +37,7 @@ struct TreeInner { /// Trees' inner fields are stored on the heap so that nodes can recursively /// link to each other, and so we can detach nodes from their parents, then /// reattach without allocating or freeing heap memory. -#[derive(Encode, Decode)] +#[derive(Clone, Encode, Decode)] pub struct Tree { inner: Box, } diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index 6b09307d2..5978f5775 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -15,6 +15,7 @@ use crate::{ }; /// RocksDB wrapper to store items with prefixes +#[derive(Clone)] pub struct PrefixedRocksDbStorage { pub(crate) db: Rc, prefix: Vec, From afe285263c2a4df129c1650e89cabf48d40b2512 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Thu, 30 Dec 2021 23:14:38 +0300 Subject: [PATCH 10/16] make transaction fully work --- grovedb/src/lib.rs | 71 +++++++++++++++---------- grovedb/src/tests.rs | 72 +++++++++++++++++++------- merk/src/merk/mod.rs | 25 ++++----- node-grove/src/lib.rs | 4 +- storage/src/rocksdb_storage/storage.rs | 28 ++++++---- 5 files changed, 130 insertions(+), 70 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 0fb5dcab1..b9ac00bf1 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -48,7 +48,7 @@ pub enum Error { } pub struct GroveDb { - root_tree: MerkleTree, + pub root_tree: MerkleTree, root_leaf_keys: HashMap, usize>, subtrees: HashMap, Merk>, meta_storage: PrefixedRocksDbStorage, @@ -56,7 +56,7 @@ pub struct GroveDb { // Locks the database for writes during the transaction is_readonly: bool, // Temp trees used for writes during transaction - temp_root_tree: MerkleTree, + pub temp_root_tree: MerkleTree, temp_root_leaf_keys: HashMap, usize>, temp_subtrees: HashMap, Merk>, } @@ -264,8 +264,7 @@ impl GroveDb { *subtree_root_hash = subtree_merk.root_hash(); subtrees.insert(compressed_path_subtree, subtree_merk); // Had to take merk from `subtrees` once again to solve multiple &mut s - let mut merk = self - .subtrees + let mut merk = subtrees .get_mut(&compressed_path) .expect("merk object must exist in `subtrees`"); // need to mark key as taken in the upper tree @@ -283,8 +282,7 @@ impl GroveDb { )); } // Get a Merk by a path - let mut merk = self - .subtrees + let mut merk = subtrees .get_mut(&compressed_path) .ok_or(Error::InvalidPath("no subtree found under that path"))?; element.insert(&mut merk, key, transaction)?; @@ -301,7 +299,7 @@ impl GroveDb { element: subtree::Element, transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result { - if self.get(path, &key).is_ok() { + if self.get(path, &key, transaction).is_ok() { return Ok(false); } match self.insert(path, key, element, transaction) { @@ -310,23 +308,43 @@ impl GroveDb { } } - pub fn get(&self, path: &[&[u8]], key: &[u8]) -> Result { - match self.get_raw(path, key)? { - Element::Reference(reference_path) => self.follow_reference(reference_path), + pub fn get<'a>( + &self, + path: &[&[u8]], + key: &[u8], + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result { + match self.get_raw(path, key, transaction)? { + Element::Reference(reference_path) => { + self.follow_reference(reference_path, transaction) + } other => Ok(other), } } /// Get tree item without following references - fn get_raw(&self, path: &[&[u8]], key: &[u8]) -> Result { - let merk = self - .subtrees + fn get_raw( + &self, + path: &[&[u8]], + key: &[u8], + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result { + let subtrees = match transaction { + None => &self.subtrees, + Some(_) => &self.temp_subtrees, + }; + let merk = subtrees .get(&Self::compress_path(path, None)) .ok_or(Error::InvalidPath("no subtree found under that path"))?; + Element::get(&merk, key) } - fn follow_reference(&self, mut path: Vec>) -> Result { + fn follow_reference( + &self, + mut path: Vec>, + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result { let mut hops_left = MAX_REFERENCE_HOPS; let mut current_element; let mut visited = HashSet::new(); @@ -343,6 +361,7 @@ impl GroveDb { .collect::>() .as_slice(), key, + transaction, )?; } else { return Err(Error::InvalidPath("empty path")); @@ -475,16 +494,16 @@ impl GroveDb { self.temp_subtrees = self.subtrees.clone(); } - pub fn commit_transaction(&mut self) { - // Enabling writes again - self.is_readonly = false; - - // Copying all changes that were made during the transaction into the db - self.root_tree = self.temp_root_tree.clone(); - self.root_leaf_keys = self.temp_root_leaf_keys.drain().collect(); - self.subtrees = self.temp_subtrees.drain().collect(); - - // TODO: root tree actually does support transactions, no need to do that - self.temp_root_tree = MerkleTree::new(); - } + // pub fn commit_transaction(&mut self) { + // // Enabling writes again + // self.is_readonly = false; + // + // // Copying all changes that were made during the transaction into the db + // self.root_tree = self.temp_root_tree.clone(); + // self.root_leaf_keys = self.temp_root_leaf_keys.drain().collect(); + // self.subtrees = self.temp_subtrees.drain().collect(); + // + // // TODO: root tree actually does support transactions, no need to do that + // self.temp_root_tree = MerkleTree::new(); + // } } diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index 6b5280dec..8a2c8f8c1 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -1,4 +1,7 @@ -use std::ops::{Deref, DerefMut}; +use std::{ + ops::{Deref, DerefMut}, + option::Option::None, +}; use merk::test_utils::TempMerk; use tempdir::TempDir; @@ -59,7 +62,7 @@ fn test_insert_value_to_merk() { db.insert(&[TEST_LEAF], b"key".to_vec(), element.clone(), None) .expect("successful insert"); assert_eq!( - db.get(&[TEST_LEAF], b"key").expect("succesful get"), + db.get(&[TEST_LEAF], b"key", None).expect("succesful get"), element ); } @@ -81,7 +84,7 @@ fn test_insert_value_to_subtree() { ) .expect("successful value insert"); assert_eq!( - db.get(&[TEST_LEAF, b"key1"], b"key2") + db.get(&[TEST_LEAF, b"key1"], b"key2", None) .expect("succesful get"), element ); @@ -112,7 +115,7 @@ fn test_changes_propagated() { ) .expect("successful value insert"); assert_eq!( - db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3") + db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3", None) .expect("succesful get"), element ); @@ -144,7 +147,7 @@ fn test_follow_references() { ) .expect("successful value insert"); assert_eq!( - db.get(&[TEST_LEAF], b"reference_key") + db.get(&[TEST_LEAF], b"reference_key", None) .expect("succesful get"), element ); @@ -171,7 +174,7 @@ fn test_cyclic_references() { .expect("successful reference 2 insert"); assert!(matches!( - db.get(&[TEST_LEAF], b"reference_key_1").unwrap_err(), + db.get(&[TEST_LEAF], b"reference_key_1", None).unwrap_err(), Error::CyclicReference )); } @@ -201,7 +204,7 @@ fn test_too_many_indirections() { } assert!(matches!( - db.get(&[TEST_LEAF], &keygen(MAX_REFERENCE_HOPS + 1)) + db.get(&[TEST_LEAF], &keygen(MAX_REFERENCE_HOPS + 1), None) .unwrap_err(), Error::ReferenceLimit )); @@ -235,7 +238,7 @@ fn test_tree_structure_is_presistent() { ) .expect("successful value insert"); assert_eq!( - db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3") + db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3", None) .expect("succesful get 1"), element ); @@ -243,11 +246,13 @@ fn test_tree_structure_is_presistent() { // Open a persisted GroveDB let db = GroveDb::open(tmp_dir).unwrap(); assert_eq!( - db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3") + db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3", None) .expect("succesful get 2"), element ); - assert!(db.get(&[TEST_LEAF, b"key1", b"key2"], b"key4").is_err()); + assert!(db + .get(&[TEST_LEAF, b"key1", b"key2"], b"key4", None) + .is_err()); } #[test] @@ -443,28 +448,57 @@ fn test_insert_if_not_exists() { #[test] fn test_insert_with_transaction_should_use_transaction() { + let item_key = b"key3".to_vec(); + let subtree_key = b"subtree_key".to_vec(); + let mut db = make_grovedb(); db.start_transaction(); let storage = db.storage(); let transaction = storage.transaction(); + println!("root, {}", db.root_tree.root_hex().unwrap()); + println!("temp_root, {}", db.temp_root_tree.root_hex().unwrap()); + + println!("subtrees before: {}", db.subtrees.keys().len()); + println!("temp subtrees before: {}", db.temp_subtrees.keys().len()); + // Check that there's no such key in the DB - let key = b"key1".to_vec(); - let result = db.get(&[TEST_LEAF], &key); + let result = db.get(&[TEST_LEAF], &item_key, None); assert!(matches!(result, Err(Error::InvalidPath(_)))); + let element1 = Element::Item(b"ayy".to_vec()); + db.insert( &[TEST_LEAF], - key.clone(), - Element::empty_tree(), + item_key.clone(), + element1.clone(), Some(&transaction), ) - .expect("Expected db.insert to work"); + .expect("cannot insert an item into GroveDB"); - // transaction.rollback(); + // The key was inserted inside the transaction, so it shouldn't be possible + // to get it back without committing or using transaction + let result = db.get(&[TEST_LEAF], &item_key, None); + assert!(matches!(result, Err(Error::InvalidPath(_)))); + // Check that the element can be retrieved when transaction is passed + let result_with_transaction = db + .get(&[TEST_LEAF], &item_key, Some(&transaction)) + .expect("Expected to work"); + assert_eq!(result_with_transaction, Element::Item(b"ayy".to_vec())); - // Get without the transaction should return previous data set - let key = b"key1".to_vec(); - let result = db.get(&[TEST_LEAF], &key); + db.insert( + &[TEST_LEAF], + subtree_key.clone(), + Element::empty_tree(), + Some(&transaction), + ) + .expect("cannot insert an item into GroveDB"); + + let result = db.get(&[TEST_LEAF], &subtree_key, None); assert!(matches!(result, Err(Error::InvalidPath(_)))); + + let result_with_transaction = db + .get(&[TEST_LEAF], &subtree_key, Some(&transaction)) + .expect("Expected to work"); + assert_eq!(result_with_transaction, Element::empty_tree()); } diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index e0b0daedb..3001ca31b 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -331,18 +331,19 @@ where impl Clone for Merk { fn clone(&self) -> Self { - let tree_clone = match self.tree.take() { - None => None, - Some(tree) => { - let clone = tree.clone(); - self.tree.set(Some(tree)); - Some(clone) - } - }; - Self { - tree: Cell::new(tree_clone), - storage: self.storage.clone(), - } + // let tree_clone = match self.tree.take() { + // None => None, + // Some(tree) => { + // let clone = tree.clone(); + // self.tree.set(Some(tree)); + // Some(clone) + // } + // }; + // Self { + // tree: Cell::new(tree_clone), + // storage: self.storage.clone(), + // } + Self::open(self.storage.clone()).unwrap() } } diff --git a/node-grove/src/lib.rs b/node-grove/src/lib.rs index b139afc5c..007256b15 100644 --- a/node-grove/src/lib.rs +++ b/node-grove/src/lib.rs @@ -1,6 +1,6 @@ mod converter; -use std::{path::Path, sync::mpsc, thread}; +use std::{option::Option::None, path::Path, sync::mpsc, thread}; use grovedb::GroveDb; use neon::prelude::*; @@ -123,7 +123,7 @@ impl GroveDbWrapper { db.send_to_db_thread(move |grove_db: &mut GroveDb, channel| { let path_slice: Vec<&[u8]> = path.iter().map(|fragment| fragment.as_slice()).collect(); - let result = grove_db.get(&path_slice, &key); + let result = grove_db.get(&path_slice, &key, None); channel.send(move |mut task_context| { let callback = js_callback.into_inner(&mut task_context); diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index 5978f5775..7cd4ba0e7 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -151,20 +151,26 @@ impl Storage for PrefixedRocksDbStorage { transaction: Option<&'b OptimisticTransactionDBTransaction>, ) -> Result, Self::Error> { match transaction { - Some(tx) => Ok(OrBatch::TransactionalBatch( - PrefixedTransactionalRocksDbBatch { + Some(tx) => { + println!("Batch is transaction"); + Ok(OrBatch::TransactionalBatch( + PrefixedTransactionalRocksDbBatch { + prefix: self.prefix.clone(), + transaction: tx, + cf_aux: self.cf_aux()?, + cf_roots: self.cf_roots()?, + }, + )) + } + None => { + println!("Youre kinda gay bro"); + Ok(OrBatch::Batch(PrefixedRocksDbBatch { prefix: self.prefix.clone(), - transaction: tx, + batch: WriteBatchWithTransaction::::default(), cf_aux: self.cf_aux()?, cf_roots: self.cf_roots()?, - }, - )), - None => Ok(OrBatch::Batch(PrefixedRocksDbBatch { - prefix: self.prefix.clone(), - batch: WriteBatchWithTransaction::::default(), - cf_aux: self.cf_aux()?, - cf_roots: self.cf_roots()?, - })), + })) + } } } From 2e5c4672056c9737b9ad29a6e6ec6dc1c9f97342 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 31 Dec 2021 12:39:20 +0300 Subject: [PATCH 11/16] create separate tests for different element types, transaction commit, etc --- grovedb/src/lib.rs | 24 ++++++++++++------------ grovedb/src/tests.rs | 38 ++++++++++++++++++++++++++++++-------- grovedb/src/transaction.rs | 12 ++++++++++-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index b9ac00bf1..87157fe95 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -494,16 +494,16 @@ impl GroveDb { self.temp_subtrees = self.subtrees.clone(); } - // pub fn commit_transaction(&mut self) { - // // Enabling writes again - // self.is_readonly = false; - // - // // Copying all changes that were made during the transaction into the db - // self.root_tree = self.temp_root_tree.clone(); - // self.root_leaf_keys = self.temp_root_leaf_keys.drain().collect(); - // self.subtrees = self.temp_subtrees.drain().collect(); - // - // // TODO: root tree actually does support transactions, no need to do that - // self.temp_root_tree = MerkleTree::new(); - // } + pub fn commit_transaction(&mut self) { + // Enabling writes again + self.is_readonly = false; + + // Copying all changes that were made during the transaction into the db + self.root_tree = self.temp_root_tree.clone(); + self.root_leaf_keys = self.temp_root_leaf_keys.drain().collect(); + self.subtrees = self.temp_subtrees.drain().collect(); + + // TODO: root tree actually does support transactions, no need to do that + self.temp_root_tree = MerkleTree::new(); + } } diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index 8a2c8f8c1..0e2e2da1d 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -447,21 +447,14 @@ fn test_insert_if_not_exists() { } #[test] -fn test_insert_with_transaction_should_use_transaction() { +fn insert_item_with_transaction_should_use_transaction() { let item_key = b"key3".to_vec(); - let subtree_key = b"subtree_key".to_vec(); let mut db = make_grovedb(); db.start_transaction(); let storage = db.storage(); let transaction = storage.transaction(); - println!("root, {}", db.root_tree.root_hex().unwrap()); - println!("temp_root, {}", db.temp_root_tree.root_hex().unwrap()); - - println!("subtrees before: {}", db.subtrees.keys().len()); - println!("temp subtrees before: {}", db.temp_subtrees.keys().len()); - // Check that there's no such key in the DB let result = db.get(&[TEST_LEAF], &item_key, None); assert!(matches!(result, Err(Error::InvalidPath(_)))); @@ -486,6 +479,30 @@ fn test_insert_with_transaction_should_use_transaction() { .expect("Expected to work"); assert_eq!(result_with_transaction, Element::Item(b"ayy".to_vec())); + // Test that commit works + transaction.commit(); + db.commit_transaction(); + + // Check that the change was committed + let result = db + .get(&[TEST_LEAF], &item_key, None) + .expect("Expected transaction to work"); + assert_eq!(result, Element::Item(b"ayy".to_vec())); +} + +#[test] +fn insert_tree_with_transaction_should_use_transaction() { + let subtree_key = b"subtree_key".to_vec(); + + let mut db = make_grovedb(); + db.start_transaction(); + let storage = db.storage(); + let transaction = storage.transaction(); + + // Check that there's no such key in the DB + let result = db.get(&[TEST_LEAF], &subtree_key, None); + assert!(matches!(result, Err(Error::InvalidPath(_)))); + db.insert( &[TEST_LEAF], subtree_key.clone(), @@ -501,4 +518,9 @@ fn test_insert_with_transaction_should_use_transaction() { .get(&[TEST_LEAF], &subtree_key, Some(&transaction)) .expect("Expected to work"); assert_eq!(result_with_transaction, Element::empty_tree()); + + let result = db + .get(&[TEST_LEAF], &subtree_key, None) + .expect("Expected transaction to work"); + assert_eq!(result, Element::empty_tree()); } diff --git a/grovedb/src/transaction.rs b/grovedb/src/transaction.rs index a94c0b54b..a0746f163 100644 --- a/grovedb/src/transaction.rs +++ b/grovedb/src/transaction.rs @@ -64,7 +64,15 @@ use crate::GroveDb; // } pub struct GroveDbTransaction<'a> { - db_transaction: Option<::DBTransaction<'a>>, + db: Rc, + db_transaction: ::DBTransaction<'a>, } -// impl GroveDbTransaction<'_> {} +// impl<'a> GroveDbTransaction<'a> { +// pub fn new(db: Rc) -> +// Self { Self { +// db_transaction: db.transaction(), +// db, +// } +// } +// } From c58ace213802483aafccca6d3c95e7a110d56087 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 31 Dec 2021 15:28:43 +0300 Subject: [PATCH 12/16] add a test for db lock --- grovedb/src/lib.rs | 30 ++++++++++++++++++-- grovedb/src/tests.rs | 35 ++++++++++++++++++++---- grovedb/src/transaction.rs | 56 ++++++++++++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 21 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 87157fe95..9b936c1db 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -45,6 +45,11 @@ pub enum Error { StorageError(#[from] PrefixedRocksDbStorageError), #[error("data corruption error: {0}")] CorruptedData(String), + #[error( + "db is in readonly mode due to the active transaction. Please provide transaction or \ + commit it" + )] + DbIsInReadonlyMode, } pub struct GroveDb { @@ -209,6 +214,12 @@ impl GroveDb { mut element: subtree::Element, transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result<(), Error> { + if let None = transaction { + if self.is_readonly { + return Err(Error::DbIsInReadonlyMode); + } + } + let subtrees = match transaction { None => &mut self.subtrees, Some(_) => &mut self.temp_subtrees, @@ -484,7 +495,10 @@ impl GroveDb { self.db.clone() } - pub fn start_transaction(&mut self) { + pub fn start_transaction(&mut self) -> Result<(), Error> { + if self.is_readonly { + return Err(Error::DbIsInReadonlyMode); + } // Locking all writes outside of the transaction self.is_readonly = true; @@ -492,9 +506,14 @@ impl GroveDb { self.temp_root_tree = self.root_tree.clone(); self.temp_root_leaf_keys = self.root_leaf_keys.clone(); self.temp_subtrees = self.subtrees.clone(); + + Ok(()) } - pub fn commit_transaction(&mut self) { + pub fn commit_transaction( + &mut self, + db_transaction: OptimisticTransactionDBTransaction, + ) -> Result<(), Error> { // Enabling writes again self.is_readonly = false; @@ -503,7 +522,12 @@ impl GroveDb { self.root_leaf_keys = self.temp_root_leaf_keys.drain().collect(); self.subtrees = self.temp_subtrees.drain().collect(); - // TODO: root tree actually does support transactions, no need to do that + // TODO: root tree actually does support transactions, so this + // code can be reworked to account for that self.temp_root_tree = MerkleTree::new(); + + Ok(db_transaction + .commit() + .map_err(PrefixedRocksDbStorageError::RocksDbError)?) } } diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index 0e2e2da1d..aca6f2a91 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -480,8 +480,8 @@ fn insert_item_with_transaction_should_use_transaction() { assert_eq!(result_with_transaction, Element::Item(b"ayy".to_vec())); // Test that commit works - transaction.commit(); - db.commit_transaction(); + // transaction.commit(); + db.commit_transaction(transaction); // Check that the change was committed let result = db @@ -495,9 +495,9 @@ fn insert_tree_with_transaction_should_use_transaction() { let subtree_key = b"subtree_key".to_vec(); let mut db = make_grovedb(); - db.start_transaction(); let storage = db.storage(); - let transaction = storage.transaction(); + let db_transaction = storage.transaction(); + db.start_transaction(); // Check that there's no such key in the DB let result = db.get(&[TEST_LEAF], &subtree_key, None); @@ -507,7 +507,7 @@ fn insert_tree_with_transaction_should_use_transaction() { &[TEST_LEAF], subtree_key.clone(), Element::empty_tree(), - Some(&transaction), + Some(&db_transaction), ) .expect("cannot insert an item into GroveDB"); @@ -515,12 +515,35 @@ fn insert_tree_with_transaction_should_use_transaction() { assert!(matches!(result, Err(Error::InvalidPath(_)))); let result_with_transaction = db - .get(&[TEST_LEAF], &subtree_key, Some(&transaction)) + .get(&[TEST_LEAF], &subtree_key, Some(&db_transaction)) .expect("Expected to work"); assert_eq!(result_with_transaction, Element::empty_tree()); + db.commit_transaction(db_transaction); + let result = db .get(&[TEST_LEAF], &subtree_key, None) .expect("Expected transaction to work"); assert_eq!(result, Element::empty_tree()); } + +#[test] +fn insert_should_return_error_when_trying_to_insert_while_transaction_is_in_process() { + let item_key = b"key3".to_vec(); + + let mut db = make_grovedb(); + db.start_transaction(); + let storage = db.storage(); + let transaction = storage.transaction(); + + let element1 = Element::Item(b"ayy".to_vec()); + + let result = db.insert(&[TEST_LEAF], item_key.clone(), element1.clone(), None); + assert!(matches!(result, Err(Error::DbIsInReadonlyMode))); + + db.commit_transaction(transaction); + + // Check that writes are unlocked after the transaction is committed + let result = db.insert(&[TEST_LEAF], item_key.clone(), element1.clone(), None); + assert!(matches!(result, Ok(()))); +} diff --git a/grovedb/src/transaction.rs b/grovedb/src/transaction.rs index a0746f163..46243f2fa 100644 --- a/grovedb/src/transaction.rs +++ b/grovedb/src/transaction.rs @@ -1,7 +1,14 @@ -use std::rc::Rc; +use std::{ + collections::{hash_map::Drain, HashMap}, + rc::Rc, +}; +use merk::Merk; +use rs_merkle::{algorithms::Sha256, MerkleTree}; use storage::{ - rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, + rocksdb_storage::{ + OptimisticTransactionDBTransaction, PrefixedRocksDbStorage, PrefixedRocksDbStorageError, + }, Storage, }; @@ -64,15 +71,40 @@ use crate::GroveDb; // } pub struct GroveDbTransaction<'a> { - db: Rc, - db_transaction: ::DBTransaction<'a>, + db_transaction: OptimisticTransactionDBTransaction<'a>, + root_tree: MerkleTree, + root_leaf_keys: HashMap, usize>, + subtrees: HashMap, Merk>, } -// impl<'a> GroveDbTransaction<'a> { -// pub fn new(db: Rc) -> -// Self { Self { -// db_transaction: db.transaction(), -// db, -// } -// } -// } +impl<'a> GroveDbTransaction<'a> { + pub fn new( + db_transaction: OptimisticTransactionDBTransaction<'a>, + root_tree: MerkleTree, + root_leaf_keys: HashMap, usize>, + subtrees: HashMap, Merk>, + ) -> Self { + Self { + db_transaction, + root_tree, + root_leaf_keys, + subtrees, + } + } + + pub fn root_leaf_keys_mut(&mut self) -> &mut HashMap, usize> { + &mut self.root_leaf_keys + } + + pub fn root_leaf_keys(&self) -> &HashMap, usize> { + &self.root_leaf_keys + } + + pub fn db_transaction(&self) -> &OptimisticTransactionDBTransaction { + &self.db_transaction + } + + pub fn commit_db(self) -> Result<(), storage::rocksdb_storage::Error> { + self.db_transaction.commit() + } +} From 307c6350212e71d07740040c4d1c37752bbec27c Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 31 Dec 2021 15:56:07 +0300 Subject: [PATCH 13/16] add documentation for transaction methods --- grovedb/src/lib.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 9b936c1db..fe19c5e1f 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -491,10 +491,58 @@ impl GroveDb { res } + /// Returns a clone of reference counter to the underlying db storage. + /// Useful when working with transactions. For more details, please + /// refer to the [`GroveDb::start_transaction`] examples section. pub fn storage(&self) -> Rc { self.db.clone() } + /// Starts database transaction. Please note that you have to start + /// underlying storage transaction manually. + /// + /// ## Examples: + /// ``` + /// # use grovedb::{Element, Error, GroveDb}; + /// # use rs_merkle::{MerkleTree, MerkleProof, algorithms::Sha256, Hasher, utils}; + /// # use std::convert::TryFrom; + /// # use tempdir::TempDir; + /// # + /// # fn main() -> Result<(), Box> { + /// const TEST_LEAF: &[u8] = b"test_leaf"; + /// + /// let tmp_dir = TempDir::new("db").unwrap(); + /// let mut db = GroveDb::open(tmp_dir.path())?; + /// db.insert(&[], TEST_LEAF.to_vec(), Element::empty_tree(), None)?; + /// + /// let storage = db.storage(); + /// let db_transaction = storage.transaction(); + /// db.start_transaction(); + /// + /// let subtree_key = b"subtree_key".to_vec(); + /// db.insert( + /// &[TEST_LEAF], + /// subtree_key.clone(), + /// Element::empty_tree(), + /// Some(&db_transaction), + /// )?; + /// + /// // This action exists only inside the transaction for now + /// let result = db.get(&[TEST_LEAF], &subtree_key, None); + /// assert!(matches!(result, Err(Error::InvalidPath(_)))); + /// + /// // To access values inside the transaction, transaction needs to be passed to the `db::get` + /// let result_with_transaction = db.get(&[TEST_LEAF], &subtree_key, Some(&db_transaction))?; + /// assert_eq!(result_with_transaction, Element::empty_tree()); + /// + /// // After transaction is committed, the value from it can be accessed normally. + /// db.commit_transaction(db_transaction); + /// let result = db.get(&[TEST_LEAF], &subtree_key, None)?; + /// assert_eq!(result, Element::empty_tree()); + /// + /// # Ok(()) + /// # } + /// ``` pub fn start_transaction(&mut self) -> Result<(), Error> { if self.is_readonly { return Err(Error::DbIsInReadonlyMode); @@ -510,6 +558,8 @@ impl GroveDb { Ok(()) } + /// Commits previously started db transaction. For more details on the + /// transaction usage, please check [`GroveDb::start_transaction`] pub fn commit_transaction( &mut self, db_transaction: OptimisticTransactionDBTransaction, From 6810bac8807af90e24331849766bb8a7b00a4c64 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 31 Dec 2021 16:02:17 +0300 Subject: [PATCH 14/16] brought back proper merk cloning --- grovedb/src/tests.rs | 6 +++--- merk/src/merk/mod.rs | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index aca6f2a91..71ead1de9 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -447,7 +447,7 @@ fn test_insert_if_not_exists() { } #[test] -fn insert_item_with_transaction_should_use_transaction() { +fn transaction_insert_item_with_transaction_should_use_transaction() { let item_key = b"key3".to_vec(); let mut db = make_grovedb(); @@ -491,7 +491,7 @@ fn insert_item_with_transaction_should_use_transaction() { } #[test] -fn insert_tree_with_transaction_should_use_transaction() { +fn transaction_insert_tree_with_transaction_should_use_transaction() { let subtree_key = b"subtree_key".to_vec(); let mut db = make_grovedb(); @@ -528,7 +528,7 @@ fn insert_tree_with_transaction_should_use_transaction() { } #[test] -fn insert_should_return_error_when_trying_to_insert_while_transaction_is_in_process() { +fn transaction_insert_should_return_error_when_trying_to_insert_while_transaction_is_in_process() { let item_key = b"key3".to_vec(); let mut db = make_grovedb(); diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index 3001ca31b..e0b0daedb 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -331,19 +331,18 @@ where impl Clone for Merk { fn clone(&self) -> Self { - // let tree_clone = match self.tree.take() { - // None => None, - // Some(tree) => { - // let clone = tree.clone(); - // self.tree.set(Some(tree)); - // Some(clone) - // } - // }; - // Self { - // tree: Cell::new(tree_clone), - // storage: self.storage.clone(), - // } - Self::open(self.storage.clone()).unwrap() + let tree_clone = match self.tree.take() { + None => None, + Some(tree) => { + let clone = tree.clone(); + self.tree.set(Some(tree)); + Some(clone) + } + }; + Self { + tree: Cell::new(tree_clone), + storage: self.storage.clone(), + } } } From ffe5ef6fd868e9e094423c2d57add1fed23b740b Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Fri, 31 Dec 2021 19:03:27 +0300 Subject: [PATCH 15/16] fixed all merge conflicts --- grovedb/src/lib.rs | 16 +- grovedb/src/operations/delete.rs | 78 +++++---- grovedb/src/operations/get.rs | 51 ++++-- grovedb/src/operations/insert.rs | 107 +++++++++--- grovedb/src/subtree.rs | 25 ++- grovedb/src/tests.rs | 113 +++++++++---- grovedb/src/transaction.rs | 16 +- merk/src/merk/mod.rs | 34 ++-- storage/src/rocksdb_storage/mod.rs | 220 +++++-------------------- storage/src/rocksdb_storage/storage.rs | 39 ++--- 10 files changed, 354 insertions(+), 345 deletions(-) diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 64eca43db..27a883f2f 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -18,7 +18,7 @@ use storage::{ }; pub use subtree::Element; -use crate::transaction::GroveDbTransaction; +// use crate::transaction::GroveDbTransaction; // pub use transaction::GroveDbTransaction; /// A key to store serialized data about subtree prefixes to restore HADS @@ -217,9 +217,17 @@ impl GroveDb { res } - pub fn elements_iterator(&self, path: &[&[u8]]) -> Result { - let merk = self - .subtrees + pub fn elements_iterator( + &self, + path: &[&[u8]], + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result { + let subtrees = match transaction { + None => &self.subtrees, + Some(_) => &self.temp_subtrees, + }; + + let merk = subtrees .get(&Self::compress_subtree_key(path, None)) .ok_or(Error::InvalidPath("no subtree found under that path"))?; Ok(Element::iterator(merk.raw_iter())) diff --git a/grovedb/src/operations/delete.rs b/grovedb/src/operations/delete.rs index 080b33f24..cfa8537c9 100644 --- a/grovedb/src/operations/delete.rs +++ b/grovedb/src/operations/delete.rs @@ -1,40 +1,58 @@ +use storage::rocksdb_storage::OptimisticTransactionDBTransaction; + use crate::{Element, Error, GroveDb}; impl GroveDb { - pub fn delete(&mut self, path: &[&[u8]], key: Vec) -> Result<(), Error> { - let element = self.get_raw(path, &key)?; + pub fn delete( + &mut self, + path: &[&[u8]], + key: Vec, + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result<(), Error> { + if let None = transaction { + if self.is_readonly { + return Err(Error::DbIsInReadonlyMode); + } + } + + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + if path.is_empty() { // Attempt to delete a root tree leaf Err(Error::InvalidPath( "root tree leafs currently cannot be deleted", )) } else { - let mut merk = self - .subtrees + let mut merk = subtrees .get_mut(&Self::compress_subtree_key(path, None)) .ok_or(Error::InvalidPath("no subtree found under that path"))?; - Element::delete(&mut merk, key.clone())?; - if let Element::Tree(_) = element { - // TODO: dumb traversal should not be tolerated - let mut concat_path: Vec> = path.iter().map(|x| x.to_vec()).collect(); - concat_path.push(key); - let subtrees_paths = self.find_subtrees(concat_path)?; - for subtree_path in subtrees_paths { - // TODO: eventually we need to do something about this nested slices - let subtree_path_ref: Vec<&[u8]> = - subtree_path.iter().map(|x| x.as_slice()).collect(); - let prefix = Self::compress_subtree_key(&subtree_path_ref, None); - if let Some(subtree) = self.subtrees.remove(&prefix) { - subtree.clear().map_err(|e| { - Error::CorruptedData(format!( - "unable to cleanup tree from storage: {}", - e - )) - })?; - } - } - } - self.propagate_changes(path)?; + Element::delete(&mut merk, key.clone(), transaction)?; + + // TODO: BRING CLEAR BACK! + // if let Element::Tree(_) = element { + // // TODO: dumb traversal should not be tolerated + // let mut concat_path: Vec> = path.iter().map(|x| + // x.to_vec()).collect(); concat_path.push(key); + // let subtrees_paths = self.find_subtrees(concat_path, transaction)?; + // for subtree_path in subtrees_paths { + // // TODO: eventually we need to do something about this nested slices + // let subtree_path_ref: Vec<&[u8]> = + // subtree_path.iter().map(|x| x.as_slice()).collect(); + // let prefix = Self::compress_subtree_key(&subtree_path_ref, None); + // if let Some(subtree) = subtrees.remove(&prefix) { + // subtree.clear().map_err(|e| { + // Error::CorruptedData(format!( + // "unable to cleanup tree from storage: {}", + // e + // )) + // })?; + // } + // } + // } + self.propagate_changes(path, transaction)?; Ok(()) } } @@ -43,14 +61,18 @@ impl GroveDb { /// Finds keys which are trees for a given subtree recursively. /// One element means a key of a `merk`, n > 1 elements mean relative path /// for a deeply nested subtree. - pub(crate) fn find_subtrees(&self, path: Vec>) -> Result>>, Error> { + pub(crate) fn find_subtrees( + &self, + path: Vec>, + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result>>, Error> { let mut queue: Vec>> = vec![path.clone()]; let mut result: Vec>> = vec![path.clone()]; while let Some(q) = queue.pop() { // TODO: eventually we need to do something about this nested slices let q_ref: Vec<&[u8]> = q.iter().map(|x| x.as_slice()).collect(); - let mut iter = self.elements_iterator(&q_ref)?; + let mut iter = self.elements_iterator(&q_ref, transaction)?; while let Some((key, value)) = iter.next()? { match value { Element::Tree(_) => { diff --git a/grovedb/src/operations/get.rs b/grovedb/src/operations/get.rs index 4097aace3..9dd9f2275 100644 --- a/grovedb/src/operations/get.rs +++ b/grovedb/src/operations/get.rs @@ -1,19 +1,32 @@ use std::collections::HashSet; +use storage::rocksdb_storage::OptimisticTransactionDBTransaction; + use crate::{Element, Error, GroveDb, PathQuery}; /// Limit of possible indirections pub(crate) const MAX_REFERENCE_HOPS: usize = 10; impl GroveDb { - pub fn get(&self, path: &[&[u8]], key: &[u8]) -> Result { - match self.get_raw(path, key)? { - Element::Reference(reference_path) => self.follow_reference(reference_path), + pub fn get( + &self, + path: &[&[u8]], + key: &[u8], + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result { + match self.get_raw(path, key, transaction)? { + Element::Reference(reference_path) => { + self.follow_reference(reference_path, transaction) + } other => Ok(other), } } - fn follow_reference(&self, mut path: Vec>) -> Result { + fn follow_reference( + &self, + mut path: Vec>, + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result { let mut hops_left = MAX_REFERENCE_HOPS; let mut current_element; let mut visited = HashSet::new(); @@ -30,6 +43,7 @@ impl GroveDb { .collect::>() .as_slice(), key, + transaction, )?; } else { return Err(Error::InvalidPath("empty path")); @@ -45,19 +59,36 @@ impl GroveDb { } /// Get tree item without following references - pub(super) fn get_raw(&self, path: &[&[u8]], key: &[u8]) -> Result { - let merk = self - .subtrees + pub(super) fn get_raw( + &self, + path: &[&[u8]], + key: &[u8], + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result { + let subtrees = match transaction { + None => &self.subtrees, + Some(_) => &self.temp_subtrees, + }; + + let merk = subtrees .get(&Self::compress_subtree_key(path, None)) .ok_or(Error::InvalidPath("no subtree found under that path"))?; Element::get(&merk, key) } - pub fn get_query(&mut self, path_queries: &[PathQuery]) -> Result, Error> { + pub fn get_query( + &mut self, + path_queries: &[PathQuery], + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result, Error> { + let subtrees = match transaction { + None => &self.subtrees, + Some(_) => &self.temp_subtrees, + }; + let mut result = Vec::new(); for query in path_queries { - let merk = self - .subtrees + let merk = subtrees .get(&Self::compress_subtree_key(query.path, None)) .ok_or(Error::InvalidPath("no subtree found under that path"))?; let subtree_results = Element::get_query(merk, &query.query)?; diff --git a/grovedb/src/operations/insert.rs b/grovedb/src/operations/insert.rs index 2042e1fb7..ae314b6b8 100644 --- a/grovedb/src/operations/insert.rs +++ b/grovedb/src/operations/insert.rs @@ -1,13 +1,13 @@ use std::rc::Rc; -use storage::rocksdb_storage; +use storage::{rocksdb_storage, Storage}; use crate::{Element, Error, GroveDb, Merk, PrefixedRocksDbStorage}; /// A helper function that builds a prefix for a key under a path and opens a /// Merk instance. fn create_merk_with_prefix( - db: Rc, + db: Rc, path: &[&[u8]], key: &[u8], ) -> Result<(Vec, Merk), Error> { @@ -20,15 +20,32 @@ fn create_merk_with_prefix( } impl GroveDb { - pub fn insert(&mut self, path: &[&[u8]], key: Vec, element: Element) -> Result<(), Error> { + pub fn insert<'a: 'b, 'b>( + &'a mut self, + path: &[&[u8]], + key: Vec, + element: Element, + transaction: Option<&'b ::DBTransaction<'b>>, + ) -> Result<(), Error> { + if let None = transaction { + if self.is_readonly { + return Err(Error::DbIsInReadonlyMode); + } + } + + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + match element { Element::Tree(_) => { if path.is_empty() { - self.add_root_leaf(&key)?; + self.add_root_leaf(&key, transaction)?; } else { - self.add_non_root_subtree(path, key)?; + self.add_non_root_subtree(path, key, transaction)?; } - self.store_subtrees_keys_data()?; + self.store_subtrees_keys_data(transaction)?; } _ => { // If path is empty that means there is an attempt to insert something into a @@ -39,65 +56,103 @@ impl GroveDb { )); } // Get a Merk by a path - let mut merk = self - .subtrees + let mut merk = subtrees .get_mut(&Self::compress_subtree_key(path, None)) .ok_or(Error::InvalidPath("no subtree found under that path"))?; - element.insert(&mut merk, key)?; - self.propagate_changes(path)?; + element.insert(&mut merk, key, transaction)?; + self.propagate_changes(path, transaction)?; } } Ok(()) } /// Add subtree to the root tree - fn add_root_leaf(&mut self, key: &[u8]) -> Result<(), Error> { + fn add_root_leaf<'a: 'b, 'b>( + &'a mut self, + key: &[u8], + transaction: Option<&'b ::DBTransaction<'b>>, + ) -> Result<(), Error> { + if let None = transaction { + if self.is_readonly { + return Err(Error::DbIsInReadonlyMode); + } + } + + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + + let root_leaf_keys = match transaction { + None => &mut self.root_leaf_keys, + Some(_) => &mut self.temp_root_leaf_keys, + }; + + let root_tree = match transaction { + None => &mut self.root_tree, + Some(_) => &mut self.temp_root_tree, + }; // Open Merk and put handle into `subtrees` dictionary accessible by its // compressed path let (subtree_prefix, subtree_merk) = create_merk_with_prefix(self.db.clone(), &[], &key)?; - self.subtrees.insert(subtree_prefix.clone(), subtree_merk); + subtrees.insert(subtree_prefix.clone(), subtree_merk); // Update root leafs index to persist rs-merkle structure later - if self.root_leaf_keys.get(&subtree_prefix).is_none() { - self.root_leaf_keys - .insert(subtree_prefix, self.root_tree.leaves_len()); + if root_leaf_keys.get(&subtree_prefix).is_none() { + root_leaf_keys.insert(subtree_prefix, root_tree.leaves_len()); } - self.propagate_changes(&[&key])?; + self.propagate_changes(&[&key], transaction)?; Ok(()) } // Add subtree to another subtree. - fn add_non_root_subtree(&mut self, path: &[&[u8]], key: Vec) -> Result<(), Error> { + fn add_non_root_subtree<'a: 'b, 'b>( + &'a mut self, + path: &[&[u8]], + key: Vec, + transaction: Option<&'b ::DBTransaction<'b>>, + ) -> Result<(), Error> { + if let None = transaction { + if self.is_readonly { + return Err(Error::DbIsInReadonlyMode); + } + } + + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + let compressed_path = Self::compress_subtree_key(path, None); // First, check if a subtree exists to create a new subtree under it - self.subtrees + subtrees .get(&compressed_path) .ok_or(Error::InvalidPath("no subtree found under that path"))?; let (subtree_prefix, subtree_merk) = create_merk_with_prefix(self.db.clone(), path, &key)?; // Set tree value as a a subtree root hash let element = Element::Tree(subtree_merk.root_hash()); - self.subtrees.insert(subtree_prefix, subtree_merk); + subtrees.insert(subtree_prefix, subtree_merk); // Had to take merk from `subtrees` once again to solve multiple &mut s - let mut merk = self - .subtrees + let mut merk = subtrees .get_mut(&compressed_path) .expect("merk object must exist in `subtrees`"); // need to mark key as taken in the upper tree - element.insert(&mut merk, key)?; - self.propagate_changes(path)?; + element.insert(&mut merk, key, transaction)?; + self.propagate_changes(path, transaction)?; Ok(()) } - pub fn insert_if_not_exists( + pub fn insert_if_not_exists<'a: 'b, 'b>( &mut self, path: &[&[u8]], key: Vec, element: Element, + transaction: Option<&'b ::DBTransaction<'b>>, ) -> Result { - if self.get(path, &key).is_ok() { + if self.get(path, &key, transaction).is_ok() { return Ok(false); } - match self.insert(path, key, element) { + match self.insert(path, key, element, transaction) { Ok(_) => Ok(true), Err(e) => Err(e), } diff --git a/grovedb/src/subtree.rs b/grovedb/src/subtree.rs index d3399dfc3..9b1d4a83c 100644 --- a/grovedb/src/subtree.rs +++ b/grovedb/src/subtree.rs @@ -10,7 +10,10 @@ use merk::{ }; use serde::{Deserialize, Serialize}; use storage::{ - rocksdb_storage::{PrefixedRocksDbStorage, RawPrefixedIterator}, + rocksdb_storage::{ + OptimisticTransactionDBTransaction, PrefixedRocksDbStorage, + RawPrefixedTransactionalIterator, + }, RawIterator, Storage, Store, }; @@ -36,10 +39,14 @@ impl Element { } /// Delete an element from Merk under a key - pub fn delete(merk: &mut Merk, key: Vec) -> Result<(), Error> { + pub fn delete( + merk: &mut Merk, + key: Vec, + transaction: Option<&OptimisticTransactionDBTransaction>, + ) -> Result<(), Error> { // TODO: delete references on this element let batch = [(key, Op::Delete)]; - merk.apply(&batch, &[]) + merk.apply(&batch, &[], transaction) .map_err(|e| Error::CorruptedData(e.to_string())) } @@ -119,14 +126,14 @@ impl Element { .map_err(|e| Error::CorruptedData(e.to_string())) } - pub fn iterator(mut raw_iter: RawPrefixedIterator) -> ElementsIterator { + pub fn iterator(mut raw_iter: RawPrefixedTransactionalIterator) -> ElementsIterator { raw_iter.seek_to_first(); ElementsIterator { raw_iter } } } pub struct ElementsIterator<'a> { - raw_iter: RawPrefixedIterator<'a>, + raw_iter: RawPrefixedTransactionalIterator<'a>, } fn raw_decode(bytes: &[u8]) -> Result { @@ -179,16 +186,16 @@ mod tests { fn test_get_query() { let mut merk = TempMerk::new(); Element::Item(b"ayyd".to_vec()) - .insert(&mut merk, b"d".to_vec()) + .insert(&mut merk, b"d".to_vec(), None) .expect("expected successful insertion"); Element::Item(b"ayyc".to_vec()) - .insert(&mut merk, b"c".to_vec()) + .insert(&mut merk, b"c".to_vec(), None) .expect("expected successful insertion"); Element::Item(b"ayya".to_vec()) - .insert(&mut merk, b"a".to_vec()) + .insert(&mut merk, b"a".to_vec(), None) .expect("expected successful insertion"); Element::Item(b"ayyb".to_vec()) - .insert(&mut merk, b"b".to_vec()) + .insert(&mut merk, b"b".to_vec(), None) .expect("expected successful insertion"); // Test queries by key diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index f13300b59..acbf837ef 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -5,8 +5,8 @@ use std::{ use merk::test_utils::TempMerk; use tempdir::TempDir; -use test::RunIgnored::No; +// use test::RunIgnored::No; use super::*; const TEST_LEAF: &[u8] = b"test_leaf"; @@ -297,6 +297,7 @@ fn test_proof_construction() { &[ANOTHER_TEST_LEAF], b"innertree2".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree insert"); temp_db @@ -304,6 +305,7 @@ fn test_proof_construction() { &[ANOTHER_TEST_LEAF], b"innertree3".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree insert"); // Insert level 2 nodes @@ -328,6 +330,7 @@ fn test_proof_construction() { &[ANOTHER_TEST_LEAF, b"innertree2"], b"key3".to_vec(), Element::Item(b"value3".to_vec()), + None, ) .expect("successful subtree insert"); temp_db @@ -335,6 +338,7 @@ fn test_proof_construction() { &[ANOTHER_TEST_LEAF, b"innertree3"], b"key4".to_vec(), Element::Item(b"value4".to_vec()), + None, ) .expect("successful subtree insert"); @@ -352,16 +356,16 @@ fn test_proof_construction() { let mut inner_tree_3 = TempMerk::new(); let value_four = Element::Item(b"value4".to_vec()); - value_four.insert(&mut inner_tree_3, b"key4".to_vec()); + value_four.insert(&mut inner_tree_3, b"key4".to_vec(), None); // Insert level 1 nodes let mut test_leaf = TempMerk::new(); let inner_tree_root = Element::Tree(inner_tree.root_hash()); - inner_tree_root.insert(&mut test_leaf, b"innertree".to_vec()); + inner_tree_root.insert(&mut test_leaf, b"innertree".to_vec(), None); let mut another_test_leaf = TempMerk::new(); let inner_tree_2_root = Element::Tree(inner_tree_2.root_hash()); - inner_tree_2_root.insert(&mut another_test_leaf, b"innertree2".to_vec()); + inner_tree_2_root.insert(&mut another_test_leaf, b"innertree2".to_vec(), None); let inner_tree_3_root = Element::Tree(inner_tree_3.root_hash()); - inner_tree_3_root.insert(&mut another_test_leaf, b"innertree3".to_vec()); + inner_tree_3_root.insert(&mut another_test_leaf, b"innertree3".to_vec(), None); // Insert root nodes let leaves = [test_leaf.root_hash(), another_test_leaf.root_hash()]; let root_tree = MerkleTree::::from_leaves(&leaves); @@ -509,13 +513,19 @@ fn test_successful_proof_verification() { let mut temp_db = make_grovedb(); // Insert level 1 nodes temp_db - .insert(&[TEST_LEAF], b"innertree".to_vec(), Element::empty_tree()) + .insert( + &[TEST_LEAF], + b"innertree".to_vec(), + Element::empty_tree(), + None, + ) .expect("successful subtree insert"); temp_db .insert( &[ANOTHER_TEST_LEAF], b"innertree2".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree insert"); temp_db @@ -523,6 +533,7 @@ fn test_successful_proof_verification() { &[ANOTHER_TEST_LEAF], b"innertree3".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree insert"); // Insert level 2 nodes @@ -531,6 +542,7 @@ fn test_successful_proof_verification() { &[TEST_LEAF, b"innertree"], b"key1".to_vec(), Element::Item(b"value1".to_vec()), + None, ) .expect("successful subtree insert"); temp_db @@ -538,6 +550,7 @@ fn test_successful_proof_verification() { &[TEST_LEAF, b"innertree"], b"key2".to_vec(), Element::Item(b"value2".to_vec()), + None, ) .expect("successful subtree insert"); temp_db @@ -545,6 +558,7 @@ fn test_successful_proof_verification() { &[ANOTHER_TEST_LEAF, b"innertree2"], b"key3".to_vec(), Element::Item(b"value3".to_vec()), + None, ) .expect("successful subtree insert"); temp_db @@ -552,6 +566,7 @@ fn test_successful_proof_verification() { &[ANOTHER_TEST_LEAF, b"innertree3"], b"key4".to_vec(), Element::Item(b"value4".to_vec()), + None, ) .expect("successful subtree insert"); @@ -828,12 +843,18 @@ fn test_subtree_pairs_iterator() { let element2 = Element::Item(b"lmao".to_vec()); // Insert some nested subtrees - db.insert(&[TEST_LEAF], b"subtree1".to_vec(), Element::empty_tree()) - .expect("successful subtree 1 insert"); + db.insert( + &[TEST_LEAF], + b"subtree1".to_vec(), + Element::empty_tree(), + None, + ) + .expect("successful subtree 1 insert"); db.insert( &[TEST_LEAF, b"subtree1"], b"subtree11".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree 2 insert"); // Insert an element into subtree @@ -841,10 +862,11 @@ fn test_subtree_pairs_iterator() { &[TEST_LEAF, b"subtree1", b"subtree11"], b"key1".to_vec(), element.clone(), + None, ) .expect("successful value insert"); assert_eq!( - db.get(&[TEST_LEAF, b"subtree1", b"subtree11"], b"key1") + db.get(&[TEST_LEAF, b"subtree1", b"subtree11"], b"key1", None) .expect("succesful get 1"), element ); @@ -852,26 +874,34 @@ fn test_subtree_pairs_iterator() { &[TEST_LEAF, b"subtree1", b"subtree11"], b"key0".to_vec(), element.clone(), + None, ) .expect("successful value insert"); db.insert( &[TEST_LEAF, b"subtree1"], b"subtree12".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree 3 insert"); - db.insert(&[TEST_LEAF, b"subtree1"], b"key1".to_vec(), element.clone()) - .expect("succesful value insert"); + db.insert( + &[TEST_LEAF, b"subtree1"], + b"key1".to_vec(), + element.clone(), + None, + ) + .expect("succesful value insert"); db.insert( &[TEST_LEAF, b"subtree1"], b"key2".to_vec(), element2.clone(), + None, ) .expect("succesful value insert"); // Iterate over subtree1 to see if keys of other subtrees messed up let mut iter = db - .elements_iterator(&[TEST_LEAF, b"subtree1"]) + .elements_iterator(&[TEST_LEAF, b"subtree1"], None) .expect("cannot create iterator"); assert_eq!(iter.next().unwrap(), Some((b"key1".to_vec(), element))); assert_eq!(iter.next().unwrap(), Some((b"key2".to_vec(), element2))); @@ -902,12 +932,12 @@ fn test_compress_path_not_possible_collision() { fn test_element_deletion() { let mut db = make_grovedb(); let element = Element::Item(b"ayy".to_vec()); - db.insert(&[TEST_LEAF], b"key".to_vec(), element.clone()) + db.insert(&[TEST_LEAF], b"key".to_vec(), element.clone(), None) .expect("successful insert"); let root_hash = db.root_tree.root().unwrap(); - assert!(db.delete(&[TEST_LEAF], b"key".to_vec()).is_ok(),); + assert!(db.delete(&[TEST_LEAF], b"key".to_vec(), None).is_ok()); assert!(matches!( - db.get(&[TEST_LEAF], b"key"), + db.get(&[TEST_LEAF], b"key", None), Err(Error::InvalidPath(_)) )); assert_ne!(root_hash, db.root_tree.root().unwrap()); @@ -918,12 +948,13 @@ fn test_find_subtrees() { let element = Element::Item(b"ayy".to_vec()); let mut db = make_grovedb(); // Insert some nested subtrees - db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("successful subtree 1 insert"); db.insert( &[TEST_LEAF, b"key1"], b"key2".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree 2 insert"); // Insert an element into subtree @@ -931,12 +962,13 @@ fn test_find_subtrees() { &[TEST_LEAF, b"key1", b"key2"], b"key3".to_vec(), element.clone(), + None, ) .expect("successful value insert"); - db.insert(&[TEST_LEAF], b"key4".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key4".to_vec(), Element::empty_tree(), None) .expect("successful subtree 3 insert"); let subtrees = db - .find_subtrees(vec![TEST_LEAF.to_vec()]) + .find_subtrees(vec![TEST_LEAF.to_vec()], None) .expect("cannot get subtrees"); assert_eq!( vec![ @@ -954,12 +986,13 @@ fn test_subtree_deletion() { let element = Element::Item(b"ayy".to_vec()); let mut db = make_grovedb(); // Insert some nested subtrees - db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("successful subtree 1 insert"); db.insert( &[TEST_LEAF, b"key1"], b"key2".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree 2 insert"); // Insert an element into subtree @@ -967,21 +1000,23 @@ fn test_subtree_deletion() { &[TEST_LEAF, b"key1", b"key2"], b"key3".to_vec(), element.clone(), + None, ) .expect("successful value insert"); - db.insert(&[TEST_LEAF], b"key4".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key4".to_vec(), Element::empty_tree(), None) .expect("successful subtree 3 insert"); - let root_hash = db.root_tree.root().unwrap(); - db.delete(&[TEST_LEAF], b"key1".to_vec()) - .expect("unable to delete subtree"); - assert!(matches!( - db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3"), - Err(Error::InvalidPath(_)) - )); - assert_eq!(db.subtrees.len(), 3); // TEST_LEAF, ANOTHER_TEST_LEAF and TEST_LEAF.key4 stay - assert!(db.get(&[TEST_LEAF], b"key4").is_ok()); - assert_ne!(root_hash, db.root_tree.root().unwrap()); + // TODO: Because I've removed clear, this test doesn't work anymore + // let root_hash = db.root_tree.root().unwrap(); + // db.delete(&[TEST_LEAF], b"key1".to_vec(), None) + // .expect("unable to delete subtree"); + // assert!(matches!( + // db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3", None), + // Err(Error::InvalidPath(_)) + // )); + // assert_eq!(db.subtrees.len(), 3); // TEST_LEAF, ANOTHER_TEST_LEAF and + // TEST_LEAF.key4 stay assert!(db.get(&[TEST_LEAF], b"key4", + // None).is_ok()); assert_ne!(root_hash, db.root_tree.root().unwrap()); } #[test] @@ -989,33 +1024,37 @@ fn test_get_query() { let mut db = make_grovedb(); // Insert a couple of subtrees first - db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("successful subtree insert"); - db.insert(&[TEST_LEAF], b"key2".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key2".to_vec(), Element::empty_tree(), None) .expect("successful subtree insert"); // Insert some elements into subtree db.insert( &[TEST_LEAF, b"key1"], b"key3".to_vec(), Element::Item(b"ayya".to_vec()), + None, ) .expect("successful value insert"); db.insert( &[TEST_LEAF, b"key1"], b"key4".to_vec(), Element::Item(b"ayyb".to_vec()), + None, ) .expect("successful value insert"); db.insert( &[TEST_LEAF, b"key1"], b"key5".to_vec(), Element::Item(b"ayyc".to_vec()), + None, ) .expect("successful value insert"); db.insert( &[TEST_LEAF, b"key2"], b"key6".to_vec(), Element::Item(b"ayyd".to_vec()), + None, ) .expect("successful value insert"); @@ -1035,7 +1074,7 @@ fn test_get_query() { query: query2, }; assert_eq!( - db.get_query(&[path_query1, path_query2]) + db.get_query(&[path_query1, path_query2], None) .expect("expected successful get_query"), vec![ subtree::Element::Item(b"ayya".to_vec()), @@ -1050,12 +1089,13 @@ fn test_aux_uses_separate_cf() { let element = Element::Item(b"ayy".to_vec()); let mut db = make_grovedb(); // Insert some nested subtrees - db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree()) + db.insert(&[TEST_LEAF], b"key1".to_vec(), Element::empty_tree(), None) .expect("successful subtree 1 insert"); db.insert( &[TEST_LEAF, b"key1"], b"key2".to_vec(), Element::empty_tree(), + None, ) .expect("successful subtree 2 insert"); // Insert an element into subtree @@ -1063,6 +1103,7 @@ fn test_aux_uses_separate_cf() { &[TEST_LEAF, b"key1", b"key2"], b"key3".to_vec(), element.clone(), + None, ) .expect("successful value insert"); @@ -1072,7 +1113,7 @@ fn test_aux_uses_separate_cf() { db.delete_aux(b"key3").expect("cannot delete from aux"); assert_eq!( - db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3") + db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3", None) .expect("cannot get element"), element ); @@ -1084,6 +1125,6 @@ fn test_aux_uses_separate_cf() { db.get_aux(b"key2").expect("cannot get from aux"), Some(b"b".to_vec()) ); - assert_eq!(db.get_aux(b"key3").expect("cannot get from aux"), None,); + assert_eq!(db.get_aux(b"key3").expect("cannot get from aux"), None); assert_eq!(db.get_aux(b"key4").expect("cannot get from aux"), None); } diff --git a/grovedb/src/transaction.rs b/grovedb/src/transaction.rs index 46243f2fa..8b311c106 100644 --- a/grovedb/src/transaction.rs +++ b/grovedb/src/transaction.rs @@ -1,19 +1,11 @@ -use std::{ - collections::{hash_map::Drain, HashMap}, - rc::Rc, -}; +use std::collections::HashMap; use merk::Merk; use rs_merkle::{algorithms::Sha256, MerkleTree}; -use storage::{ - rocksdb_storage::{ - OptimisticTransactionDBTransaction, PrefixedRocksDbStorage, PrefixedRocksDbStorageError, - }, - Storage, -}; +use storage::rocksdb_storage::{OptimisticTransactionDBTransaction, PrefixedRocksDbStorage}; + +// use super::subtree; -use super::{subtree, Error}; -use crate::GroveDb; // pub struct GroveDbTransaction<'a, 'db> { // grove_db: &'a mut GroveDb, // db: Rc, diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index 722a326a7..483e4416c 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -40,20 +40,20 @@ where Ok(merk) } - /// Deletes tree data - pub fn clear(self) -> Result<()> { - let mut iter = self.raw_iter(); - iter.seek_to_first(); - let mut to_delete = self.storage.new_batch()?; - while iter.valid() { - if let Some(key) = iter.key() { - to_delete.delete(key); - } - iter.next(); - } - self.storage.commit_batch(to_delete)?; - Ok(()) - } + // /// Deletes tree data + // pub fn clear<'a>(self, transaction: Option<&'a S::DBTransaction<'a>>) -> + // Result<()> { let mut iter = self.raw_iter(); + // iter.seek_to_first(); + // let mut to_delete = self.storage.new_batch(transaction)?; + // while iter.valid() { + // if let Some(key) = iter.key() { + // to_delete.delete(key); + // } + // iter.next(); + // } + // self.storage.commit_batch(to_delete)?; + // Ok(()) + // } /// Gets an auxiliary value. pub fn get_aux(&self, key: &[u8]) -> Result>> { @@ -421,7 +421,9 @@ impl Commit for MerkCommitter { mod test { use rocksdb::{DBRawIteratorWithThreadMode, OptimisticTransactionDB}; use storage::{ - rocksdb_storage::{default_rocksdb, PrefixedRocksDbStorage}, + rocksdb_storage::{ + default_rocksdb, PrefixedRocksDbStorage, RawPrefixedTransactionalIterator, + }, RawIterator, }; use tempdir::TempDir; @@ -595,7 +597,7 @@ mod test { #[test] fn reopen_iter() { fn collect( - iter: &mut OptimisticTransactionDBRawIterator, + iter: &mut RawPrefixedTransactionalIterator, nodes: &mut Vec<(Vec, Vec)>, ) { while iter.valid() { diff --git a/storage/src/rocksdb_storage/mod.rs b/storage/src/rocksdb_storage/mod.rs index aa38d6edc..158560364 100644 --- a/storage/src/rocksdb_storage/mod.rs +++ b/storage/src/rocksdb_storage/mod.rs @@ -2,7 +2,7 @@ use std::{path::Path, rc::Rc}; pub use rocksdb::{checkpoint::Checkpoint, Error, OptimisticTransactionDB}; -use rocksdb::{ColumnFamilyDescriptor, DBRawIterator}; +use rocksdb::{ColumnFamilyDescriptor, DBRawIterator, DBRawIteratorWithThreadMode}; use crate::{DBTransaction, RawIterator}; @@ -31,6 +31,10 @@ pub fn default_db_opts() -> rocksdb::Options { opts } +pub type OptimisticTransactionDBTransaction<'a> = rocksdb::Transaction<'a, OptimisticTransactionDB>; + +impl<'a> DBTransaction<'a> for OptimisticTransactionDBTransaction<'a> {} + /// RocksDB column families pub fn column_families() -> Vec { vec![ @@ -58,156 +62,52 @@ fn make_prefixed_key(prefix: Vec, key: &[u8]) -> Vec { prefixed_key } -/// RocksDB wrapper to store items with prefixes -pub struct PrefixedRocksDbStorage { - db: Rc, - prefix: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum PrefixedRocksDbStorageError { - #[error("column family not found: {0}")] - ColumnFamilyNotFound(&'static str), - #[error(transparent)] - RocksDbError(#[from] rocksdb::Error), -} - -impl PrefixedRocksDbStorage { - /// Wraps RocksDB to prepend prefixes to each operation - pub fn new(db: Rc, prefix: Vec) -> Result { - Ok(PrefixedRocksDbStorage { prefix, db }) - } - - /// Get auxiliary data column family - fn cf_aux(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { - self.db - .cf_handle(AUX_CF_NAME) - .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( - AUX_CF_NAME, - )) - } - - /// Get trees roots data column family - fn cf_roots(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { - self.db - .cf_handle(ROOTS_CF_NAME) - .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( - ROOTS_CF_NAME, - )) - } - - /// Get metadata column family - fn cf_meta(&self) -> Result<&rocksdb::ColumnFamily, PrefixedRocksDbStorageError> { - self.db - .cf_handle(META_CF_NAME) - .ok_or(PrefixedRocksDbStorageError::ColumnFamilyNotFound( - META_CF_NAME, - )) - } +pub struct RawPrefixedTransactionalIterator<'a> { + rocksdb_iterator: DBRawIteratorWithThreadMode<'a, OptimisticTransactionDB>, + prefix: &'a [u8], } -impl Storage for PrefixedRocksDbStorage { - type Batch<'a> = PrefixedRocksDbBatch<'a>; - type Error = PrefixedRocksDbStorageError; - type RawIterator<'a> = RawPrefixedIterator<'a>; - - fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - self.db - .put(make_prefixed_key(self.prefix.clone(), key), value)?; - Ok(()) - } - - fn put_aux(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - self.db.put_cf( - self.cf_aux()?, - make_prefixed_key(self.prefix.clone(), key), - value, - )?; - Ok(()) - } - - fn put_root(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - self.db.put_cf( - self.cf_roots()?, - make_prefixed_key(self.prefix.clone(), key), - value, - )?; - Ok(()) - } - - fn delete(&self, key: &[u8]) -> Result<(), Self::Error> { - self.db - .delete(make_prefixed_key(self.prefix.clone(), key))?; - Ok(()) - } - - fn delete_aux(&self, key: &[u8]) -> Result<(), Self::Error> { - self.db - .delete_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?; - Ok(()) - } - - fn delete_root(&self, key: &[u8]) -> Result<(), Self::Error> { - self.db.delete_cf( - self.cf_roots()?, - make_prefixed_key(self.prefix.clone(), key), - )?; - Ok(()) - } - - fn get(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.db.get(make_prefixed_key(self.prefix.clone(), key))?) - } - - fn get_aux(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self - .db - .get_cf(self.cf_aux()?, make_prefixed_key(self.prefix.clone(), key))?) - } - - fn get_root(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.db.get_cf( - self.cf_roots()?, - make_prefixed_key(self.prefix.clone(), key), - )?) - } - - fn put_meta(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { - Ok(self.db.put_cf(self.cf_meta()?, key, value)?) +impl RawIterator for RawPrefixedTransactionalIterator<'_> { + fn seek_to_first(&mut self) { + self.rocksdb_iterator.seek(self.prefix); } - fn delete_meta(&self, key: &[u8]) -> Result<(), Self::Error> { - Ok(self.db.delete_cf(self.cf_meta()?, key)?) + fn seek(&mut self, key: &[u8]) { + self.rocksdb_iterator + .seek(make_prefixed_key(self.prefix.to_vec(), key)); } - fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.db.get_cf(self.cf_meta()?, key)?) + fn next(&mut self) { + self.rocksdb_iterator.next(); } - fn new_batch<'a>(&'a self) -> Result, Self::Error> { - Ok(PrefixedRocksDbBatch { - prefix: self.prefix.clone(), - batch: WriteBatch::default(), - cf_aux: self.cf_aux()?, - cf_roots: self.cf_roots()?, - }) + fn prev(&mut self) { + self.rocksdb_iterator.prev(); } - fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error> { - self.db.write(batch.batch)?; - Ok(()) + fn value(&self) -> Option<&[u8]> { + if self.valid() { + self.rocksdb_iterator.value() + } else { + None + } } - fn flush(&self) -> Result<(), Self::Error> { - self.db.flush()?; - Ok(()) + fn key(&self) -> Option<&[u8]> { + if self.valid() { + self.rocksdb_iterator + .key() + .map(|k| k.split_at(self.prefix.len()).1) + } else { + None + } } - fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { - RawPrefixedIterator { - rocksdb_iterator: self.db.raw_iterator(), - prefix: &self.prefix, - } + fn valid(&self) -> bool { + self.rocksdb_iterator + .key() + .map(|k| k.starts_with(self.prefix)) + .unwrap_or(false) } } @@ -260,52 +160,6 @@ impl RawIterator for RawPrefixedIterator<'_> { } } -/// Wrapper to RocksDB batch -pub struct PrefixedRocksDbBatch<'a> { - prefix: Vec, - batch: rocksdb::WriteBatch, - cf_aux: &'a ColumnFamily, - cf_roots: &'a ColumnFamily, -} - -impl<'a> Batch for PrefixedRocksDbBatch<'a> { - fn put(&mut self, key: &[u8], value: &[u8]) { - self.batch - .put(make_prefixed_key(self.prefix.clone(), key), value) - } - - fn put_aux(&mut self, key: &[u8], value: &[u8]) { - self.batch.put_cf( - self.cf_aux, - make_prefixed_key(self.prefix.clone(), key), - value, - ) - } - - fn put_root(&mut self, key: &[u8], value: &[u8]) { - self.batch.put_cf( - self.cf_roots, - make_prefixed_key(self.prefix.clone(), key), - value, - ) - } - - fn delete(&mut self, key: &[u8]) { - self.batch - .delete(make_prefixed_key(self.prefix.clone(), key)) - } - - fn delete_aux(&mut self, key: &[u8]) { - self.batch - .delete_cf(self.cf_aux, make_prefixed_key(self.prefix.clone(), key)) - } - - fn delete_root(&mut self, key: &[u8]) { - self.batch - .delete_cf(self.cf_roots, make_prefixed_key(self.prefix.clone(), key)) - } -} - #[cfg(test)] mod tests { use std::ops::Deref; diff --git a/storage/src/rocksdb_storage/storage.rs b/storage/src/rocksdb_storage/storage.rs index 7cd4ba0e7..75c2491fc 100644 --- a/storage/src/rocksdb_storage/storage.rs +++ b/storage/src/rocksdb_storage/storage.rs @@ -3,8 +3,8 @@ use std::rc::Rc; use rocksdb::WriteBatchWithTransaction; use super::{ - make_prefixed_key, DBRawTransactionIterator, PrefixedRocksDbBatch, PrefixedRocksDbTransaction, - AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, + make_prefixed_key, PrefixedRocksDbBatch, PrefixedRocksDbTransaction, + RawPrefixedTransactionalIterator, AUX_CF_NAME, META_CF_NAME, ROOTS_CF_NAME, }; use crate::{ rocksdb_storage::{ @@ -70,7 +70,7 @@ impl Storage for PrefixedRocksDbStorage { type Batch<'a> = OrBatch<'a>; type DBTransaction<'a> = OptimisticTransactionDBTransaction<'a>; type Error = PrefixedRocksDbStorageError; - type RawIterator<'a> = DBRawTransactionIterator<'a>; + type RawIterator<'a> = RawPrefixedTransactionalIterator<'a>; type StorageTransaction<'a> = PrefixedRocksDbTransaction<'a>; fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { @@ -151,26 +151,20 @@ impl Storage for PrefixedRocksDbStorage { transaction: Option<&'b OptimisticTransactionDBTransaction>, ) -> Result, Self::Error> { match transaction { - Some(tx) => { - println!("Batch is transaction"); - Ok(OrBatch::TransactionalBatch( - PrefixedTransactionalRocksDbBatch { - prefix: self.prefix.clone(), - transaction: tx, - cf_aux: self.cf_aux()?, - cf_roots: self.cf_roots()?, - }, - )) - } - None => { - println!("Youre kinda gay bro"); - Ok(OrBatch::Batch(PrefixedRocksDbBatch { + Some(tx) => Ok(OrBatch::TransactionalBatch( + PrefixedTransactionalRocksDbBatch { prefix: self.prefix.clone(), - batch: WriteBatchWithTransaction::::default(), + transaction: tx, cf_aux: self.cf_aux()?, cf_roots: self.cf_roots()?, - })) - } + }, + )), + None => Ok(OrBatch::Batch(PrefixedRocksDbBatch { + prefix: self.prefix.clone(), + batch: WriteBatchWithTransaction::::default(), + cf_aux: self.cf_aux()?, + cf_roots: self.cf_roots()?, + })), } } @@ -190,7 +184,10 @@ impl Storage for PrefixedRocksDbStorage { } fn raw_iter<'a>(&'a self) -> Self::RawIterator<'a> { - self.db.raw_iterator() + RawPrefixedTransactionalIterator { + rocksdb_iterator: self.db.raw_iterator(), + prefix: &self.prefix, + } } fn transaction<'a>( From a85f4c55d051df6bc767e118c659a8661ca64b75 Mon Sep 17 00:00:00 2001 From: Evgeny Fomin Date: Tue, 11 Jan 2022 16:28:32 +0300 Subject: [PATCH 16/16] bring deletion back --- grovedb/src/operations/delete.rs | 68 +++++++++++++++------------- grovedb/src/tests.rs | 21 ++++----- merk/src/merk/mod.rs | 29 ++++++------ storage/src/lib.rs | 29 +++++++----- storage/src/rocksdb_storage/batch.rs | 20 ++++---- 5 files changed, 90 insertions(+), 77 deletions(-) diff --git a/grovedb/src/operations/delete.rs b/grovedb/src/operations/delete.rs index cfa8537c9..4cccafcc0 100644 --- a/grovedb/src/operations/delete.rs +++ b/grovedb/src/operations/delete.rs @@ -14,44 +14,50 @@ impl GroveDb { return Err(Error::DbIsInReadonlyMode); } } - - let subtrees = match transaction { - None => &mut self.subtrees, - Some(_) => &mut self.temp_subtrees, - }; - if path.is_empty() { // Attempt to delete a root tree leaf Err(Error::InvalidPath( "root tree leafs currently cannot be deleted", )) } else { - let mut merk = subtrees - .get_mut(&Self::compress_subtree_key(path, None)) - .ok_or(Error::InvalidPath("no subtree found under that path"))?; - Element::delete(&mut merk, key.clone(), transaction)?; + let element = self.get_raw(path, &key, transaction)?; + { + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + + let mut merk = subtrees + .get_mut(&Self::compress_subtree_key(path, None)) + .ok_or(Error::InvalidPath("no subtree found under that path"))?; + Element::delete(&mut merk, key.clone(), transaction)?; + } - // TODO: BRING CLEAR BACK! - // if let Element::Tree(_) = element { - // // TODO: dumb traversal should not be tolerated - // let mut concat_path: Vec> = path.iter().map(|x| - // x.to_vec()).collect(); concat_path.push(key); - // let subtrees_paths = self.find_subtrees(concat_path, transaction)?; - // for subtree_path in subtrees_paths { - // // TODO: eventually we need to do something about this nested slices - // let subtree_path_ref: Vec<&[u8]> = - // subtree_path.iter().map(|x| x.as_slice()).collect(); - // let prefix = Self::compress_subtree_key(&subtree_path_ref, None); - // if let Some(subtree) = subtrees.remove(&prefix) { - // subtree.clear().map_err(|e| { - // Error::CorruptedData(format!( - // "unable to cleanup tree from storage: {}", - // e - // )) - // })?; - // } - // } - // } + if let Element::Tree(_) = element { + // TODO: dumb traversal should not be tolerated + let mut concat_path: Vec> = path.iter().map(|x| x.to_vec()).collect(); + concat_path.push(key); + let subtrees_paths = self.find_subtrees(concat_path, transaction)?; + let subtrees = match transaction { + None => &mut self.subtrees, + Some(_) => &mut self.temp_subtrees, + }; + + for subtree_path in subtrees_paths { + // TODO: eventually we need to do something about this nested slices + let subtree_path_ref: Vec<&[u8]> = + subtree_path.iter().map(|x| x.as_slice()).collect(); + let prefix = Self::compress_subtree_key(&subtree_path_ref, None); + if let Some(mut subtree) = subtrees.remove(&prefix) { + subtree.clear(transaction).map_err(|e| { + Error::CorruptedData(format!( + "unable to cleanup tree from storage: {}", + e + )) + })?; + } + } + } self.propagate_changes(path, transaction)?; Ok(()) } diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index c85656a41..937471e7f 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -991,17 +991,16 @@ fn test_subtree_deletion() { db.insert(&[TEST_LEAF], b"key4".to_vec(), Element::empty_tree(), None) .expect("successful subtree 3 insert"); - // TODO: Because I've removed clear, this test doesn't work anymore - // let root_hash = db.root_tree.root().unwrap(); - // db.delete(&[TEST_LEAF], b"key1".to_vec(), None) - // .expect("unable to delete subtree"); - // assert!(matches!( - // db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3", None), - // Err(Error::InvalidPath(_)) - // )); - // assert_eq!(db.subtrees.len(), 3); // TEST_LEAF, ANOTHER_TEST_LEAF and - // TEST_LEAF.key4 stay assert!(db.get(&[TEST_LEAF], b"key4", - // None).is_ok()); assert_ne!(root_hash, db.root_tree.root().unwrap()); + let root_hash = db.root_tree.root().unwrap(); + db.delete(&[TEST_LEAF], b"key1".to_vec(), None) + .expect("unable to delete subtree"); + assert!(matches!( + db.get(&[TEST_LEAF, b"key1", b"key2"], b"key3", None), + Err(Error::InvalidPath(_)) + )); + assert_eq!(db.subtrees.len(), 3); // TEST_LEAF, ANOTHER_TEST_LEAF TEST_LEAF.key4 stay + assert!(db.get(&[TEST_LEAF], b"key4", None).is_ok()); + assert_ne!(root_hash, db.root_tree.root().unwrap()); } #[test] diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index 483e4416c..99b64ad00 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -40,20 +40,21 @@ where Ok(merk) } - // /// Deletes tree data - // pub fn clear<'a>(self, transaction: Option<&'a S::DBTransaction<'a>>) -> - // Result<()> { let mut iter = self.raw_iter(); - // iter.seek_to_first(); - // let mut to_delete = self.storage.new_batch(transaction)?; - // while iter.valid() { - // if let Some(key) = iter.key() { - // to_delete.delete(key); - // } - // iter.next(); - // } - // self.storage.commit_batch(to_delete)?; - // Ok(()) - // } + /// Deletes tree data + pub fn clear<'a>(&'a mut self, transaction: Option<&'a S::DBTransaction<'a>>) -> Result<()> { + let mut iter = self.raw_iter(); + iter.seek_to_first(); + let mut to_delete = self.storage.new_batch(transaction)?; + while iter.valid() { + if let Some(key) = iter.key() { + to_delete.delete(key); + } + iter.next(); + } + self.storage.commit_batch(to_delete)?; + self.tree.set(None); + Ok(()) + } /// Gets an auxiliary value. pub fn get_aux(&self, key: &[u8]) -> Result>> { diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 8333c5cbe..bb7040c92 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -62,7 +62,10 @@ pub trait Storage { fn get_meta(&self, key: &[u8]) -> Result>, Self::Error>; /// Initialize a new batch - fn new_batch<'a: 'b, 'b>(&'a self, transaction: Option<&'b Self::DBTransaction<'b>>) -> Result, Self::Error>; + fn new_batch<'a: 'b, 'b>( + &'a self, + transaction: Option<&'b Self::DBTransaction<'b>>, + ) -> Result, Self::Error>; /// Commits changes from batch into storage fn commit_batch<'a>(&'a self, batch: Self::Batch<'a>) -> Result<(), Self::Error>; @@ -78,11 +81,15 @@ pub trait Storage { } impl<'b, S: Storage> Storage for &'b S { - type Error = S::Error; type Batch<'a> where 'b: 'a, = S::Batch<'a>; + type DBTransaction<'a> + where + 'b: 'a, + = S::DBTransaction<'a>; + type Error = S::Error; type RawIterator<'a> where 'b: 'a, @@ -91,10 +98,6 @@ impl<'b, S: Storage> Storage for &'b S { where 'b: 'a, = S::StorageTransaction<'a>; - type DBTransaction<'a> - where - 'b: 'a, - = S::DBTransaction<'a>; fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { (*self).put(key, value) @@ -144,7 +147,10 @@ impl<'b, S: Storage> Storage for &'b S { (*self).get_meta(key) } - fn new_batch<'a: 'c, 'c>(&'a self, transaction: Option<&'c Self::DBTransaction<'c>>) -> Result, Self::Error> { + fn new_batch<'a: 'c, 'c>( + &'a self, + transaction: Option<&'c Self::DBTransaction<'c>>, + ) -> Result, Self::Error> { (*self).new_batch(transaction) } @@ -198,10 +204,11 @@ pub trait RawIterator { fn valid(&self) -> bool; } -/// Please note that the `Transaction` trait is used to access the underlying transaction -/// through the storage, but many storages can share the same DB transaction. Thus, the -/// storage itself can not commit the transaction, and transaction should be committed -/// by its original opener - GroveDB instance in our case. +/// Please note that the `Transaction` trait is used to access the underlying +/// transaction through the storage, but many storages can share the same DB +/// transaction. Thus, the storage itself can not commit the transaction, and +/// transaction should be committed by its original opener - GroveDB instance in +/// our case. pub trait Transaction { /// Storage error type type Error: std::error::Error + Send + Sync + 'static; diff --git a/storage/src/rocksdb_storage/batch.rs b/storage/src/rocksdb_storage/batch.rs index 8efaf05e8..30b429216 100644 --- a/storage/src/rocksdb_storage/batch.rs +++ b/storage/src/rocksdb_storage/batch.rs @@ -54,7 +54,7 @@ pub struct PrefixedTransactionalRocksDbBatch<'a> { pub prefix: Vec, pub cf_aux: &'a ColumnFamily, pub cf_roots: &'a ColumnFamily, - pub transaction: &'a rocksdb::Transaction<'a, OptimisticTransactionDB>, + pub transaction: &'a rocksdb::Transaction<'a, OptimisticTransactionDB>, } // TODO: don't ignore errors @@ -98,49 +98,49 @@ impl<'a> Batch for PrefixedTransactionalRocksDbBatch<'a> { pub enum OrBatch<'a> { Batch(PrefixedRocksDbBatch<'a>), - TransactionalBatch(PrefixedTransactionalRocksDbBatch<'a>) + TransactionalBatch(PrefixedTransactionalRocksDbBatch<'a>), } -impl <'a> Batch for OrBatch<'a> { +impl<'a> Batch for OrBatch<'a> { fn put(&mut self, key: &[u8], value: &[u8]) { match self { Self::TransactionalBatch(batch) => batch.put(key, value), - Self::Batch(batch) => batch.put(key, value) + Self::Batch(batch) => batch.put(key, value), } } fn put_aux(&mut self, key: &[u8], value: &[u8]) { match self { Self::TransactionalBatch(batch) => batch.put_aux(key, value), - Self::Batch(batch) => batch.put_aux(key, value) + Self::Batch(batch) => batch.put_aux(key, value), } } fn put_root(&mut self, key: &[u8], value: &[u8]) { match self { Self::TransactionalBatch(batch) => batch.put_root(key, value), - Self::Batch(batch) => batch.put_root(key, value) + Self::Batch(batch) => batch.put_root(key, value), } } fn delete(&mut self, key: &[u8]) { match self { Self::TransactionalBatch(batch) => batch.delete(key), - Self::Batch(batch) => batch.delete(key) + Self::Batch(batch) => batch.delete(key), } } fn delete_aux(&mut self, key: &[u8]) { match self { Self::TransactionalBatch(batch) => batch.delete_aux(key), - Self::Batch(batch) => batch.delete_aux(key) + Self::Batch(batch) => batch.delete_aux(key), } } fn delete_root(&mut self, key: &[u8]) { match self { Self::TransactionalBatch(batch) => batch.delete_root(key), - Self::Batch(batch) => batch.delete_root(key) + Self::Batch(batch) => batch.delete_root(key), } } -} \ No newline at end of file +}