Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(katana-db): database transaction abstractions #2171

Merged
merged 4 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions crates/katana/storage/db/src/abstraction/cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use crate::error::DatabaseError;
use crate::tables::{self, DupSort, Table};
use crate::utils::KeyValue;

/// Cursor trait for navigating the items within a database.
pub trait DbCursor<T: Table>: Sized {
/// Retrieves the first key/value pair, positioning the cursor at the first key/value pair in
/// the table.
fn first(&mut self) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Retrieves key/value pair at current cursor position.
fn current(&mut self) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Retrieves the next key/value pair, positioning the cursor at the next key/value pair in
/// the table.
fn next(&mut self) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Retrieves the previous key/value pair, positioning the cursor at the previous key/value pair
/// in the table.
fn prev(&mut self) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Retrieves the last key/value pair, positioning the cursor at the last key/value pair in
/// the table.
fn last(&mut self) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Set the cursor to the specified key, returning and positioning the cursor at the item if
/// found.
fn set(&mut self, key: T::Key) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Search for a `key` in a table, returning and positioning the cursor at the first item whose
/// key is greater than or equal to `key`.
fn seek(&mut self, key: T::Key) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Creates a walker to iterate over the table items.
///
/// If `start_key` is `None`, the walker will start at the first item of the table. Otherwise,
/// it will start at the first item whose key is greater than or equal to `start_key`.
fn walk(&mut self, start_key: Option<T::Key>) -> Result<Walker<'_, T, Self>, DatabaseError>;
}

/// Cursor trait for read-write operations.
pub trait DbCursorMut<T: Table>: DbCursor<T> {
/// Database operation that will update an existing row if a specified value already
/// exists in a table, and insert a new row if the specified value doesn't already exist
fn upsert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError>;

/// Puts a key/value pair into the database. The cursor will be positioned at the new data item,
/// or on failure, usually near it.
fn insert(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError>;

/// Appends the data to the end of the table. Consequently, the append operation
/// will fail if the inserted key is less than the last table key
fn append(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError>;

/// Deletes the current key/value pair.
fn delete_current(&mut self) -> Result<(), DatabaseError>;
}

/// Cursor trait for DUPSORT tables.
pub trait DbDupSortCursor<T: DupSort>: DbCursor<T> {
/// Positions the cursor at next data item of current key, returning the next `key-value`
/// pair of a DUPSORT table.
fn next_dup(&mut self) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Similar to `next_dup()`, but instead of returning a `key-value` pair, it returns
/// only the `value`.
fn next_dup_val(&mut self) -> Result<Option<T::Value>, DatabaseError>;

/// Returns the next key/value pair skipping the duplicates.
fn next_no_dup(&mut self) -> Result<Option<KeyValue<T>>, DatabaseError>;

/// Search for a `key` and `subkey` pair in a DUPSORT table. Positioning the cursor at the first
/// item whose `subkey` is greater than or equal to the specified `subkey`.
fn seek_by_key_subkey(
&mut self,
key: <T as Table>::Key,
subkey: <T as DupSort>::SubKey,
) -> Result<Option<<T as Table>::Value>, DatabaseError>;

/// Depending on its arguments, returns an iterator starting at:
/// - Some(key), Some(subkey): a `key` item whose data is >= than `subkey`
/// - Some(key), None: first item of a specified `key`
/// - None, Some(subkey): like first case, but in the first key
/// - None, None: first item in the table
/// of a DUPSORT table.
fn walk_dup(
&mut self,
key: Option<T::Key>,
subkey: Option<<T as DupSort>::SubKey>,
) -> Result<Option<DupWalker<'_, T, Self>>, DatabaseError>;
}

/// Cursor trait for read-write operations on DUPSORT tables.
pub trait DbDupSortCursorMut<T: tables::DupSort>: DbDupSortCursor<T> + DbCursorMut<T> {
/// Deletes all values for the current key.
fn delete_current_duplicates(&mut self) -> Result<(), DatabaseError>;

/// Appends the data as a duplicate for the current key.
fn append_dup(&mut self, key: T::Key, value: T::Value) -> Result<(), DatabaseError>;
}

/// A key-value pair coming from an iterator.
///
/// The `Result` represents that the operation might fail, while the `Option` represents whether or
/// not there is an entry.
pub type IterPairResult<T> = Option<Result<KeyValue<T>, DatabaseError>>;

/// Provides an iterator to a `Cursor` when handling `Table`.
#[derive(Debug)]
pub struct Walker<'c, T: Table, C: DbCursor<T>> {
/// Cursor to be used to walk through the table.
cursor: &'c mut C,
/// Initial position of the dup walker. The value (key/value pair) where to start the walk.
start: IterPairResult<T>,
}

impl<'c, T, C> Walker<'c, T, C>
where
T: Table,
C: DbCursor<T>,
{
/// Create a new [`Walker`] from a [`Cursor`] and a [`IterPairResult`].
pub fn new(cursor: &'c mut C, start: IterPairResult<T>) -> Self {
Self { cursor, start }
}
}

impl<T, C> Walker<'_, T, C>
where
T: Table,
C: DbCursorMut<T>,
{
/// Delete the `key/value` pair item at the current position of the walker.
pub fn delete_current(&mut self) -> Result<(), DatabaseError> {
self.cursor.delete_current()
}

Check warning on line 136 in crates/katana/storage/db/src/abstraction/cursor.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/storage/db/src/abstraction/cursor.rs#L134-L136

Added lines #L134 - L136 were not covered by tests
}

impl<T, C> std::iter::Iterator for Walker<'_, T, C>
where
T: Table,
C: DbCursor<T>,
{
type Item = Result<KeyValue<T>, DatabaseError>;
fn next(&mut self) -> Option<Self::Item> {
if let value @ Some(_) = self.start.take() { value } else { self.cursor.next().transpose() }
}
}

/// A cursor iterator for `DUPSORT` table.
///
/// Similar to [`Walker`], but for `DUPSORT` table.
#[derive(Debug)]
pub struct DupWalker<'c, T: Table, C: DbCursor<T>> {
/// Cursor to be used to walk through the table.
cursor: &'c mut C,
/// Initial position of the dup walker. The value (key/value pair) where to start the walk.
start: IterPairResult<T>,
}

impl<'c, T, C> DupWalker<'c, T, C>
where
T: DupSort,
C: DbCursor<T>,
{
/// Creates a new [`DupWalker`] from a [`Cursor`] and a [`IterPairResult`].
pub fn new(cursor: &'c mut C, start: IterPairResult<T>) -> Self {
Self { cursor, start }
}
}

impl<T, C> DupWalker<'_, T, C>
where
T: DupSort,
C: DbCursorMut<T>,
{
/// Delete the item at the current position of the walker.
pub fn delete_current(&mut self) -> Result<(), DatabaseError> {
self.cursor.delete_current()
}

Check warning on line 180 in crates/katana/storage/db/src/abstraction/cursor.rs

View check run for this annotation

Codecov / codecov/patch

crates/katana/storage/db/src/abstraction/cursor.rs#L178-L180

Added lines #L178 - L180 were not covered by tests
}

impl<T, C> std::iter::Iterator for DupWalker<'_, T, C>
where
T: DupSort,
C: DbDupSortCursor<T>,
{
type Item = Result<KeyValue<T>, DatabaseError>;
fn next(&mut self) -> Option<Self::Item> {
if let value @ Some(_) = self.start.take() {
value
} else {
self.cursor.next_dup().transpose()
}
}
}
5 changes: 5 additions & 0 deletions crates/katana/storage/db/src/abstraction/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod cursor;
mod transaction;

pub use cursor::*;
pub use transaction::*;
68 changes: 68 additions & 0 deletions crates/katana/storage/db/src/abstraction/transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use super::cursor::{DbCursor, DbCursorMut};
use super::{DbDupSortCursor, DbDupSortCursorMut};
use crate::error::DatabaseError;
use crate::tables::{DupSort, Table};

/// Trait for read-only transaction type.
pub trait DbTx {
/// The cursor type.
type Cursor<T: Table>: DbCursor<T>;

/// The cursor type for dupsort table.
// TODO: ideally we should only define the cursor type once,
// find a way to not have to define both cursor types in both traits
type DupCursor<T: DupSort>: DbDupSortCursor<T>;

/// Creates a cursor to iterate over a table items.
fn cursor<T: Table>(&self) -> Result<Self::Cursor<T>, DatabaseError>;

/// Creates a cursor to iterate over a dupsort table items.
fn cursor_dup<T: DupSort>(&self) -> Result<Self::DupCursor<T>, DatabaseError>;

/// Gets a value from a table using the given key.
fn get<T: Table>(&self, key: T::Key) -> Result<Option<T::Value>, DatabaseError>;

/// Returns number of entries in the table.
fn entries<T: Table>(&self) -> Result<usize, DatabaseError>;

/// Commits the transaction.
fn commit(self) -> Result<bool, DatabaseError>;

/// Aborts the transaction.
fn abort(self);
}

/// Trait for read-write transaction type.
pub trait DbTxMut: DbTx {
/// The mutable cursor type.
type Cursor<T: Table>: DbCursorMut<T>;

/// The mutable cursor type for dupsort table.
// TODO: find a way to not have to define both cursor types in both traits
type DupCursor<T: DupSort>: DbDupSortCursorMut<T>;

/// Creates a cursor to mutably iterate over a table items.
fn cursor_mut<T: Table>(&self) -> Result<<Self as DbTxMut>::Cursor<T>, DatabaseError>;

/// Creates a cursor to iterate over a dupsort table items.
fn cursor_dup_mut<T: DupSort>(&self) -> Result<<Self as DbTxMut>::DupCursor<T>, DatabaseError>;

/// Inserts an item into a database.
///
/// This function stores key/data pairs in the database. The default behavior is to enter the
/// new key/data pair, replacing any previously existing key if duplicates are disallowed, or
/// adding a duplicate data item if duplicates are allowed (DatabaseFlags::DUP_SORT).
fn put<T: Table>(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError>;

/// Delete items from a database, removing the key/data pair if it exists.
///
/// If the data parameter is [Some] only the matching data item will be deleted. Otherwise, if
/// data parameter is [None], any/all value(s) for specified key will be deleted.
///
/// Returns `true` if the key/value pair was present.
fn delete<T: Table>(&self, key: T::Key, value: Option<T::Value>)
-> Result<bool, DatabaseError>;

/// Clears all entries in the given database. This will empty the database.
fn clear<T: Table>(&self) -> Result<(), DatabaseError>;
}
1 change: 1 addition & 0 deletions crates/katana/storage/db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::Path;

use anyhow::{anyhow, Context};

pub mod abstraction;
pub mod codecs;
pub mod error;
pub mod mdbx;
Expand Down
Loading
Loading