Skip to content

Commit

Permalink
feat: Database and WriteTransaction
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuhvi committed Dec 31, 2023
1 parent e13e66d commit a808fac
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 278 deletions.
11 changes: 6 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
blake3 = "1.5.0"
redb = "1.4.0"
thiserror = "1.0.53"

[dev-dependencies]
proptest = "1.4.0"
Expand Down
123 changes: 123 additions & 0 deletions mast/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//! Kytz Database
use crate::node::Node;
use crate::operations::read::{get_node, root_hash, root_node};
use crate::operations::{insert, remove};
use crate::{Hash, Result};

pub struct Database {
pub(crate) inner: redb::Database,
}

impl Database {
/// Create a new in-memory database.
pub fn in_memory() -> Self {
let backend = redb::backends::InMemoryBackend::new();
let inner = redb::Database::builder()
.create_with_backend(backend)
.unwrap();

Self { inner }
}

pub fn begin_write(&self) -> Result<WriteTransaction> {
let txn = self.inner.begin_write().unwrap();
WriteTransaction::new(txn)
}

pub fn iter(&self, treap: &str) -> TreapIterator<'_> {
// TODO: save tables instead of opening a new one on every next() call.
TreapIterator::new(self, treap.to_string())
}

// === Private Methods ===

pub(crate) fn get_node(&self, hash: &Option<Hash>) -> Option<Node> {
get_node(&self, hash)
}

pub(crate) fn root_hash(&self, treap: &str) -> Option<Hash> {
root_hash(&self, treap)
}

pub(crate) fn root(&self, treap: &str) -> Option<Node> {
root_node(self, treap)
}
}

pub struct TreapIterator<'db> {
db: &'db Database,
treap: String,
stack: Vec<Node>,
}

impl<'db> TreapIterator<'db> {
fn new(db: &'db Database, treap: String) -> Self {
let mut iter = TreapIterator {
db,
treap: treap.clone(),
stack: Vec::new(),
};

if let Some(root) = db.root(&treap) {
iter.push_left(root)
};

iter
}

fn push_left(&mut self, mut node: Node) {
while let Some(left) = self.db.get_node(node.left()) {
self.stack.push(node);
node = left;
}
self.stack.push(node);
}
}

impl<'a> Iterator for TreapIterator<'a> {
type Item = Node;

fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop() {
Some(node) => {
if let Some(right) = self.db.get_node(node.right()) {
self.push_left(right)
}

Some(node.clone())
}
_ => None,
}
}
}

pub struct WriteTransaction<'db> {
inner: redb::WriteTransaction<'db>,
}

impl<'db> WriteTransaction<'db> {
pub(crate) fn new(inner: redb::WriteTransaction<'db>) -> Result<Self> {
Ok(Self { inner })
}

pub fn insert(
&mut self,
treap: &str,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
) -> Option<Node> {
// TODO: validate key and value length.
// key and value mast be less than 2^32 bytes.

insert(&mut self.inner, treap, key.as_ref(), value.as_ref())
}

pub fn remove(&mut self, treap: &str, key: impl AsRef<[u8]>) -> Option<Node> {
remove(&mut self.inner, treap, key.as_ref())
}

pub fn commit(self) -> Result<()> {
self.inner.commit().map_err(|e| e.into())
}
}
24 changes: 24 additions & 0 deletions mast/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Main Crate Error
#[derive(thiserror::Error, Debug)]
/// Mainline crate error enum.
pub enum Error {
/// For starter, to remove as code matures.
#[error("Generic error: {0}")]
Generic(String),
/// For starter, to remove as code matures.
#[error("Static error: {0}")]
Static(&'static str),

#[error(transparent)]
/// Transparent [std::io::Error]
IO(#[from] std::io::Error),

#[error(transparent)]
/// Transparent [redb::CommitError]
CommitError(#[from] redb::CommitError),

#[error(transparent)]
/// Error from `redb::TransactionError`.
TransactionError(#[from] redb::TransactionError),
}
10 changes: 8 additions & 2 deletions mast/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#![allow(unused)]

pub mod db;
pub mod error;
mod node;
mod operations;
pub mod treap;

#[cfg(test)]
mod test;

pub(crate) use blake3::{Hash, Hasher};
pub(crate) const HASH_LEN: usize = 32;

pub const HASH_LEN: usize = 32;
pub use db::Database;
pub use error::Error;

// Alias Result to be the crate Result.
pub type Result<T, E = Error> = core::result::Result<T, E>;
53 changes: 31 additions & 22 deletions mast/src/operations/insert.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use blake3::Hash;
use redb::Table;

use super::search::binary_search_path;
use super::{read::root_node_inner, search::binary_search_path, NODES_TABLE, ROOTS_TABLE};
use crate::node::{Branch, Node};

// Watch this [video](https://youtu.be/NxRXhBur6Xs?si=GNwaUOfuGwr_tBKI&t=1763) for a good explanation of the unzipping algorithm.
Expand Down Expand Up @@ -101,20 +101,25 @@ use crate::node::{Branch, Node};
//

pub(crate) fn insert(
nodes_table: &'_ mut Table<&'static [u8], (u64, &'static [u8])>,
root: Option<Node>,
write_txn: &mut redb::WriteTransaction,
treap: &str,
key: &[u8],
value: &[u8],
) -> Node {
let mut path = binary_search_path(nodes_table, root, key);
) -> Option<Node> {
let mut roots_table = write_txn.open_table(ROOTS_TABLE).unwrap();
let mut nodes_table = write_txn.open_table(NODES_TABLE).unwrap();

let old_root = root_node_inner(&roots_table, &nodes_table, treap);

let mut path = binary_search_path(&nodes_table, old_root, key);

let mut left_subtree: Option<Hash> = None;
let mut right_subtree: Option<Hash> = None;

// Unzip the lower path to get left and right children of the inserted node.
for (node, branch) in path.lower.iter_mut().rev() {
// Decrement the old version.
node.decrement_ref_count().save(nodes_table);
node.decrement_ref_count().save(&mut nodes_table);

match branch {
Branch::Right => {
Expand All @@ -127,58 +132,62 @@ pub(crate) fn insert(
}
}

node.increment_ref_count().save(nodes_table);
node.increment_ref_count().save(&mut nodes_table);
}

let mut root;
let mut new_root;

if let Some(mut found) = path.found {
if found.value() == value {
// There is really nothing to update. Skip traversing upwards.

return path.upper.first().map(|(n, _)| n.clone()).unwrap_or(found);
return Some(found);
}

// Decrement the old version.
found.decrement_ref_count().save(nodes_table);
found.decrement_ref_count().save(&mut nodes_table);

// Else, update the value and rehashe the node so that we can update the hashes upwards.
found
.set_value(value)
.increment_ref_count()
.save(nodes_table);
.save(&mut nodes_table);

root = found
new_root = found
} else {
// Insert the new node.
let mut node = Node::new(key, value);

node.set_left_child(left_subtree)
.set_right_child(right_subtree)
.increment_ref_count()
.save(nodes_table);
.save(&mut nodes_table);

root = node
new_root = node
};

let mut upper_path = path.upper;

// Propagate the new hashes upwards if there are any nodes in the upper_path.
while let Some((mut node, branch)) = upper_path.pop() {
node.decrement_ref_count().save(nodes_table);
node.decrement_ref_count().save(&mut nodes_table);

match branch {
Branch::Left => node.set_left_child(Some(root.hash())),
Branch::Right => node.set_right_child(Some(root.hash())),
Branch::Left => node.set_left_child(Some(new_root.hash())),
Branch::Right => node.set_right_child(Some(new_root.hash())),
};

node.increment_ref_count().save(nodes_table);
node.increment_ref_count().save(&mut nodes_table);

root = node;
new_root = node;
}

// Finally return the new root to be set to the root.
root
// Finally set the new root .
roots_table
.insert(treap.as_bytes(), new_root.hash().as_bytes().as_slice())
.unwrap();

// No older value was found.
None
}

#[cfg(test)]
Expand Down
19 changes: 19 additions & 0 deletions mast/src/operations/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
pub mod insert;
pub mod read;
pub mod remove;
mod search;

pub(crate) use insert::insert;
pub(crate) use remove::remove;

use redb::{ReadableTable, TableDefinition};

// Table: Nodes v0
// stores all the hash treap nodes from all the treaps in the storage.
//
// Key: `[u8; 32]` # Node hash
// Value: `(u64, [u8])` # (RefCount, EncodedNode)
pub const NODES_TABLE: TableDefinition<&[u8], (u64, &[u8])> =
TableDefinition::new("kytz:hash_treap:nodes:v0");

// Table: Roots v0
// stores all the current roots for all treaps in the storage.
//
// Key: `[u8; 32]` # Treap name
// Value: `[u8; 32]` # Hash
pub const ROOTS_TABLE: TableDefinition<&[u8], &[u8]> =
TableDefinition::new("kytz:hash_treap:roots:v0");
Loading

0 comments on commit a808fac

Please sign in to comment.