Skip to content

Commit

Permalink
Improve MerkleDB docs [ECR-4006] (exonum#1624)
Browse files Browse the repository at this point in the history
  • Loading branch information
slowli authored and Oleksandr Anyshchenko committed Dec 18, 2019
1 parent 12c8fd2 commit 59f7237
Show file tree
Hide file tree
Showing 17 changed files with 368 additions and 189 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ jobs:
- touch target/doc/std/option/enum.Option.html
- touch target/doc/std/primitive.usize.html
- touch target/doc/std/primitive.char.html
# https://github.com/deadlinks/cargo-deadlinks/issues/37
- touch target/doc/enum.HashTag.html
- cargo deadlinks --dir target/doc

# Run kcov.
Expand Down
10 changes: 1 addition & 9 deletions components/merkledb/benches/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use failure::{self, format_err};
use rand::{rngs::StdRng, RngCore, SeedableRng};

use exonum_crypto::{self, hash, Hash};
use exonum_merkledb::{
impl_object_hash_for_binary_value, proof_map_index::BranchNode, BinaryKey, BinaryValue,
ObjectHash,
};
use exonum_merkledb::{impl_object_hash_for_binary_value, BinaryKey, BinaryValue, ObjectHash};

const CHUNK_SIZE: usize = 64;
const SEED: [u8; 32] = [100; 32];
Expand Down Expand Up @@ -132,10 +129,6 @@ fn gen_cursor_data() -> CursorData {
})
}

fn gen_branch_node_data() -> BranchNode {
BranchNode::empty()
}

fn bench_binary_value<F, V>(c: &mut Criterion, name: &str, f: F)
where
F: Fn() -> V + 'static + Clone + Copy,
Expand Down Expand Up @@ -203,6 +196,5 @@ pub fn bench_encoding(c: &mut Criterion) {
bench_binary_value(c, "bytes", gen_bytes_data);
bench_binary_value(c, "simple", gen_sample_data);
bench_binary_value(c, "cursor", gen_cursor_data);
bench_binary_value(c, "branch_node", gen_branch_node_data);
c.bench_function("encoding/storage_key/concat", bench_binary_key_concat);
}
22 changes: 21 additions & 1 deletion components/merkledb/src/access/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ use crate::{
/// Extension trait allowing for easy access to indices from any type implementing
/// `Access`.
///
/// # Implementation details
///
/// This trait is essentially a thin wrapper around [`FromAccess`]. Where `FromAccess` returns
/// an access error, the methods of this trait will `unwrap()` the error and panic.
///
/// [`FromAccess`]: trait.FromAccess.html
///
/// # Examples
///
/// ```
Expand All @@ -23,14 +30,23 @@ use crate::{
/// let mut list: ListIndex<_, String> = fork.get_list("list");
/// list.push("foo".to_owned());
/// }
///
/// // ...and on `Snapshot`s:
/// let snapshot = db.snapshot();
/// assert!(snapshot
/// .get_map::<_, u64, String>("map")
/// .get(&0)
/// .is_none());
///
/// // ...and on `ReadonlyFork`s:
/// let list = fork.readonly().get_list::<_, String>("list");
/// {
/// let list = fork.readonly().get_list::<_, String>("list");
/// assert_eq!(list.len(), 1);
/// }
///
/// // ...and on `Patch`es:
/// let patch = fork.into_patch();
/// let list = patch.get_list::<_, String>("list");
/// assert_eq!(list.len(), 1);
/// ```
pub trait AccessExt: Access {
Expand Down Expand Up @@ -226,6 +242,10 @@ pub trait AccessExt: Access {
}

/// Touches an index at the specified address, asserting that it has a specific type.
///
/// # Errors
///
/// Returns an error if the index has in invalid type.
fn touch_index<I>(self, addr: I, index_type: IndexType) -> Result<(), AccessError>
where
I: Into<IndexAddress>,
Expand Down
37 changes: 28 additions & 9 deletions components/merkledb/src/access/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ mod extensions;

/// High-level access to database data.
///
/// This trait is not intended to be implemented by the types outside the crate; indeed,
/// it instantiates `ViewWithMetadata`, which is crate-private. Correspondingly, `Access` methods
/// rarely need to be used directly; use [its extension trait][`AccessExt`] instead.
///
/// [`AccessExt`]: trait.AccessExt.html
///
/// # Examples
///
/// `Access` can be used as a bound on structured database objects and their
Expand All @@ -37,7 +43,7 @@ pub trait Access: Clone {
/// Raw access serving as the basis for created indices.
type Base: RawAccess;

/// Gets or creates a generic `View` with the specified address.
/// Gets or creates a generic view with the specified address.
fn get_or_create_view(
self,
addr: IndexAddress,
Expand All @@ -57,7 +63,12 @@ impl<T: RawAccess> Access for T {
}
}

/// Access that prepends the specified prefix to each created view.
/// Access that prepends the specified prefix to each created view. The prefix is separated
/// from user-provided names with a dot char `'.'`.
///
/// Since the prefix itself cannot contain a dot, `Prefixed` accesses provide namespace
/// separation. A set of indexes to which `Prefixed` provides access does not intersect
/// with a set of indexes accessed by a `Prefixed` instance with another prefix.
///
/// # Examples
///
Expand All @@ -78,11 +89,13 @@ pub struct Prefixed<'a, T> {
}

impl<'a, T: Access> Prefixed<'a, T> {
/// Creates new prefixed access.
/// Creates a new prefixed access.
///
/// # Panics
///
/// Will panic if the prefix does not conform to valid names for indexes.
/// - Will panic if the prefix is not a [valid prefix name].
///
/// [valid prefix name]: ../validation/fn.is_valid_index_name_component.html
pub fn new(prefix: impl Into<Cow<'a, str>>, access: T) -> Self {
let prefix = prefix.into();
assert_valid_name_component(prefix.as_ref());
Expand All @@ -103,7 +116,7 @@ impl<T: Access> Access for Prefixed<'_, T> {
}
}

/// Error together with location information.
/// Access error together with the location information.
#[derive(Debug, Fail)]
pub struct AccessError {
/// Address of the index where the error has occurred.
Expand All @@ -121,6 +134,9 @@ impl fmt::Display for AccessError {
}

/// Error that can be emitted during accessing an object from the database.
///
/// This type is not intended to be exhaustively matched. It can be extended in the future
/// without breaking the semver compatibility.
#[derive(Debug, Fail)]
pub enum AccessErrorKind {
/// Index has wrong type.
Expand Down Expand Up @@ -159,16 +175,19 @@ pub enum AccessErrorKind {
/// Custom error.
#[fail(display = "{}", _0)]
Custom(#[fail(cause)] Error),

#[doc(hidden)]
#[fail(display = "")] // Never actually generated.
__NonExhaustive,
}

/// Constructs an object atop the database. The constructed object provides access to data
/// in the DB, akin to an object-relational mapping.
///
/// The access to DB can be readonly or read-write, depending on the `T: Access` type param.
/// Most object should implement `FromAccess<T>` for all `T: Access`, unless there are compelling
/// reasons not to.
/// Most object should implement `FromAccess<T>` for all `T: Access`.
///
/// Simplest `FromAccess` implementors are indexes; it is implemented for [`Lazy`] and [`Group`].
/// Simplest `FromAccess` implementors are indexes; it is also implemented for [`Lazy`] and [`Group`].
/// `FromAccess` can be implemented for more complex *components*. Thus, `FromAccess` can
/// be used to compose storage objects from simpler ones.
///
Expand Down Expand Up @@ -243,7 +262,7 @@ pub trait FromAccess<T: Access>: Sized {

/// Constructs the object from the root of the `access`.
///
/// The default implementation uses `Self::from_access()`.
/// The default implementation uses `Self::from_access()` with an empty address.
fn from_root(access: T) -> Result<Self, AccessError> {
Self::from_access(access, IndexAddress::default())
}
Expand Down
6 changes: 3 additions & 3 deletions components/merkledb/src/backends/rocksdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ impl RocksDB {
Ok(db)
}

/// Creates checkpoint of this database in the given directory. (see [RocksDb docs][1] for
/// details).
/// Creates checkpoint of this database in the given directory. See [RocksDB docs] for
/// details.
///
/// Successfully created checkpoint can be opened using `RocksDB::open`.
///
/// [1]: https://github.com/facebook/rocksdb/wiki/Checkpoints
/// [RocksDB docs]: https://github.com/facebook/rocksdb/wiki/Checkpoints
pub fn create_checkpoint<T: AsRef<Path>>(&self, path: T) -> crate::Result<()> {
let checkpoint = Checkpoint::new(&*self.db)?;
checkpoint.create_checkpoint(path)?;
Expand Down
78 changes: 51 additions & 27 deletions components/merkledb/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,28 +292,40 @@ pub enum Change {
Delete,
}

/// A combination of a database snapshot and a sequence of changes on top of it.
/// A combination of a database snapshot and changes on top of it.
///
/// A `Fork` provides both immutable and mutable operations over the database. Like [`Snapshot`],
/// `Fork` provides read isolation. When mutable operations ([`put`], [`remove`] and
/// [`remove_by_prefix`]) are applied to a fork, the subsequent reads act as if the changes
/// A `Fork` provides both immutable and mutable operations over the database by implementing
/// the [`RawAccessMut`] trait. Like [`Snapshot`], `Fork` provides read isolation.
/// When mutable operations are applied to a fork, the subsequent reads act as if the changes
/// are applied to the database; in reality, these changes are accumulated in memory.
///
/// To apply changes to the database, you need to convert a `Fork` into a [`Patch`] using
/// To apply the changes to the database, you need to convert a `Fork` into a [`Patch`] using
/// [`into_patch`] and then atomically [`merge`] it into the database. If two
/// conflicting forks are merged into a database, this can lead to an inconsistent state. If you
/// need to consistently apply several sets of changes to the same data, the next fork should be
/// created after the previous fork has been merged.
///
/// `Fork` also supports checkpoints ([`flush`] and
/// [`rollback`] methods), which allows rolling back some of the latest changes (e.g., after
/// a runtime error). Checkpoint is created automatically after calling the `flush` method.
/// `Fork` also supports checkpoints ([`flush`] and [`rollback`] methods), which allows
/// rolling back the latest changes. A checkpoint is created automatically after calling
/// the `flush` method.
///
/// `Fork` provides methods for both reading and writing data. Thus, `&Fork` is used
/// as a storage view for creating read-write indices representation.
/// ```
/// # use exonum_merkledb::{access::AccessExt, Database, TemporaryDB};
/// let db = TemporaryDB::new();
/// let mut fork = db.fork();
/// fork.get_list("list").extend(vec![1_u32, 2]);
/// fork.flush();
/// fork.get_list("list").push(3_u32);
/// fork.rollback();
/// // The changes after the latest `flush()` are now forgotten.
/// let list = fork.get_list::<_, u32>("list");
/// assert_eq!(list.len(), 2);
/// # assert_eq!(list.iter().collect::<Vec<_>>(), vec![1, 2]);
/// ```
///
/// **Note.** Unless stated otherwise, "key" in the method descriptions below refers
/// to a full key (a string column family name + key as an array of bytes within the family).
/// In order to convert a fork into `&dyn Snapshot` presentation, convert it into a `Patch`
/// and use a reference to it (`Patch` implements `Snapshot`). Using `<Fork as RawAccess>::snapshot`
/// for this purpose is logically incorrect and may lead to hard-to-debug errors.
///
/// # Borrow checking
///
Expand Down Expand Up @@ -353,10 +365,8 @@ pub enum Change {
/// a shared reference to an index if there is an exclusive reference to the same index,
/// and vice versa.
///
/// [`RawAccessMut`]: access/trait.RawAccessMut.html
/// [`Snapshot`]: trait.Snapshot.html
/// [`put`]: #method.put
/// [`remove`]: #method.remove
/// [`remove_by_prefix`]: #method.remove_by_prefix
/// [`Patch`]: struct.Patch.html
/// [`into_patch`]: #method.into_patch
/// [`merge`]: trait.Database.html#tymethod.merge
Expand All @@ -371,11 +381,28 @@ pub struct Fork {
working_patch: WorkingPatch,
}

/// A set of serial changes that should be applied to a storage atomically.
/// A set of changes that can be atomically applied to a `Database`.
///
/// This set can contain changes from multiple indexes. Changes can be read from the `Patch`
/// using its `RawAccess` implementation.
///
/// # Examples
///
/// This set can contain changes from multiple tables. When a block is added to
/// the blockchain, changes are first collected into a patch and then applied to
/// the storage.
/// ```
/// # use exonum_merkledb::{
/// # access::AccessExt, Database, ObjectHash, Patch, SystemSchema, TemporaryDB,
/// # };
/// let db = TemporaryDB::new();
/// let fork = db.fork();
/// fork.get_proof_list("list").extend(vec![1_i32, 2, 3]);
/// let patch: Patch = fork.into_patch();
/// // The patch contains changes recorded in the fork.
/// let list = patch.get_proof_list::<_, i32>("list");
/// assert_eq!(list.len(), 3);
/// // Unlike `Fork`, `Patch`es have consistent aggregated state.
/// let aggregator = SystemSchema::new(&patch).state_aggregator();
/// assert_eq!(aggregator.get("list").unwrap(), list.object_hash());
/// ```
#[derive(Debug)]
pub struct Patch {
snapshot: Box<dyn Snapshot>,
Expand Down Expand Up @@ -406,7 +433,7 @@ enum NextIterValue {
/// A `Database` instance is shared across different threads, so it must be `Sync` and `Send`.
///
/// There is no way to directly interact with data in the database; use [`snapshot`], [`fork`]
/// and [`merge`] methods for indirect interaction. See [the module documentation](index.html)
/// and [`merge`] methods for indirect interaction. See [the crate-level documentation](index.html)
/// for more details.
///
/// Note that `Database` effectively has [interior mutability][interior-mut];
Expand All @@ -429,7 +456,7 @@ enum NextIterValue {
/// [`snapshot`]: #tymethod.snapshot
/// [`fork`]: #method.fork
/// [`merge`]: #tymethod.merge
/// [interior-mut]: https://doc.rust-lang.org/book/second-edition/ch15-05-interior-mutability.html
/// [interior-mut]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
pub trait Database: Send + Sync + 'static {
/// Creates a new snapshot of the database from its current state.
fn snapshot(&self) -> Box<dyn Snapshot>;
Expand Down Expand Up @@ -534,17 +561,14 @@ impl<T: Database> DatabaseExt for T {}
/// A `Snapshot` instance is an immutable representation of a certain storage state.
/// It provides read isolation, so consistency is guaranteed even if the data in
/// the database changes between reads.
///
/// **Note.** Unless stated otherwise, "key" in the method descriptions below refers
/// to a full key (a string column family name + key as an array of bytes within the family).
pub trait Snapshot: Send + Sync + 'static {
/// Returns a value corresponding to the specified key as a raw vector of bytes,
/// Returns a value corresponding to the specified address and key as a raw vector of bytes,
/// or `None` if it does not exist.
fn get(&self, name: &ResolvedAddress, key: &[u8]) -> Option<Vec<u8>>;

/// Returns `true` if the snapshot contains a value for the specified key.
/// Returns `true` if the snapshot contains a value for the specified address and key.
///
/// Default implementation checks existence of the value using [`get`](#tymethod.get).
/// The default implementation checks existence of the value using [`get`](#tymethod.get).
fn contains(&self, name: &ResolvedAddress, key: &[u8]) -> bool {
self.get(name, key).is_some()
}
Expand Down
9 changes: 4 additions & 5 deletions components/merkledb/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@

use std::error::Error as StdError;

/// The error type for I/O operations with storage.
/// The error type for I/O operations with the `Database`.
///
/// These errors result in a panic. Storage errors are fatal as in the case of
/// database issues, the system stops working. Assuming that there are other
/// nodes and secret keys and other crucial data are not stored in the data base,
/// the operation of the system can be resumed from a backup or by rebooting the node.
/// Application code in most cases should consider these errors as fatal. At the same time,
/// it may be possible to recover from an error after manual intervention (e.g., by restarting
/// the process or freeing up more disc space).
#[derive(Fail, Debug, Clone)]
#[fail(display = "{}", message)]
pub struct Error {
Expand Down
Loading

0 comments on commit 59f7237

Please sign in to comment.