diff --git a/src/lib.rs b/src/lib.rs index 0c45f581..47dc43a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,9 +62,13 @@ pub use error::{ TransactionError, }; pub use multimap_table::{ - MultimapRange, MultimapTable, MultimapValue, ReadOnlyMultimapTable, ReadableMultimapTable, + MultimapRange, MultimapTable, MultimapValue, ReadOnlyMultimapTable, + ReadOnlyUntypedMultimapTable, ReadableMultimapTable, +}; +pub use table::{ + Drain, DrainFilter, Range, ReadOnlyTable, ReadOnlyUntypedTable, ReadableTable, Table, + TableStats, }; -pub use table::{Drain, DrainFilter, Range, ReadOnlyTable, ReadableTable, Table, TableStats}; pub use transactions::{DatabaseStats, Durability, ReadTransaction, WriteTransaction}; pub use tree_store::{AccessGuard, AccessGuardMut, Savepoint}; pub use types::{RedbKey, RedbValue, TypeName}; diff --git a/src/multimap_table.rs b/src/multimap_table.rs index e3a7d9eb..98a4462c 100644 --- a/src/multimap_table.rs +++ b/src/multimap_table.rs @@ -1174,6 +1174,54 @@ pub trait ReadableMultimapTable: Sea } } +/// A read-only untyped multimap table +pub struct ReadOnlyUntypedMultimapTable<'txn> { + tree: RawBtree<'txn>, + fixed_key_size: Option, + fixed_value_size: Option, + mem: &'txn TransactionalMemory, +} + +impl<'txn> ReadOnlyUntypedMultimapTable<'txn> { + pub(crate) fn new( + root_page: Option<(PageNumber, Checksum)>, + fixed_key_size: Option, + fixed_value_size: Option, + mem: &'txn TransactionalMemory, + ) -> Self { + Self { + tree: RawBtree::new( + root_page, + fixed_key_size, + DynamicCollection::<()>::fixed_width_with(fixed_value_size), + mem, + ), + fixed_key_size, + fixed_value_size, + mem, + } + } + + /// Retrieves information about storage usage for the table + pub fn stats(&self) -> Result { + let tree_stats = multimap_btree_stats( + self.tree.get_root().map(|(p, _)| p), + self.mem, + self.fixed_key_size, + self.fixed_value_size, + )?; + + Ok(TableStats { + tree_height: tree_stats.tree_height, + leaf_pages: tree_stats.leaf_pages, + branch_pages: tree_stats.branch_pages, + stored_leaf_bytes: tree_stats.stored_leaf_bytes, + metadata_bytes: tree_stats.metadata_bytes, + fragmented_bytes: tree_stats.fragmented_bytes, + }) + } +} + /// A read-only multimap table pub struct ReadOnlyMultimapTable<'txn, K: RedbKey + 'static, V: RedbKey + 'static> { tree: Btree<'txn, K, &'static DynamicCollection>, diff --git a/src/table.rs b/src/table.rs index 441291b1..9d004ef5 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,7 +1,7 @@ use crate::sealed::Sealed; use crate::tree_store::{ AccessGuardMut, Btree, BtreeDrain, BtreeDrainFilter, BtreeMut, BtreeRangeIter, Checksum, - PageHint, PageNumber, TransactionalMemory, MAX_VALUE_LENGTH, + PageHint, PageNumber, RawBtree, TransactionalMemory, MAX_VALUE_LENGTH, }; use crate::types::{RedbKey, RedbValue, RedbValueMutInPlace}; use crate::{AccessGuard, StorageError, WriteTransaction}; @@ -373,6 +373,38 @@ pub trait ReadableTable: Sealed { } } +/// A read-only untyped table +pub struct ReadOnlyUntypedTable<'txn> { + tree: RawBtree<'txn>, +} + +impl<'txn> ReadOnlyUntypedTable<'txn> { + pub(crate) fn new( + root_page: Option<(PageNumber, Checksum)>, + fixed_key_size: Option, + fixed_value_size: Option, + mem: &'txn TransactionalMemory, + ) -> Self { + Self { + tree: RawBtree::new(root_page, fixed_key_size, fixed_value_size, mem), + } + } + + /// Retrieves information about storage usage for the table + pub fn stats(&self) -> Result { + let tree_stats = self.tree.stats()?; + + Ok(TableStats { + tree_height: tree_stats.tree_height, + leaf_pages: tree_stats.leaf_pages, + branch_pages: tree_stats.branch_pages, + stored_leaf_bytes: tree_stats.stored_leaf_bytes, + metadata_bytes: tree_stats.metadata_bytes, + fragmented_bytes: tree_stats.fragmented_bytes, + }) + } +} + /// A read-only table pub struct ReadOnlyTable<'txn, K: RedbKey + 'static, V: RedbValue + 'static> { name: String, diff --git a/src/transactions.rs b/src/transactions.rs index c4bb9565..e73f3e72 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -1,5 +1,7 @@ use crate::error::CommitError; +use crate::multimap_table::ReadOnlyUntypedMultimapTable; use crate::sealed::Sealed; +use crate::table::ReadOnlyUntypedTable; use crate::transaction_tracker::{SavepointId, TransactionId, TransactionTracker}; use crate::tree_store::{ Btree, BtreeMut, Checksum, FreedPageList, FreedTableKey, InternalTableDefinition, PageHint, @@ -1185,6 +1187,24 @@ impl<'db> ReadTransaction<'db> { )?) } + /// Open the given table without a type + pub fn open_untyped_table( + &self, + handle: impl TableHandle, + ) -> Result { + let header = self + .tree + .get_table_untyped(handle.name(), TableType::Normal)? + .ok_or_else(|| TableError::TableDoesNotExist(handle.name().to_string()))?; + + Ok(ReadOnlyUntypedTable::new( + header.get_root(), + header.get_fixed_key_size(), + header.get_fixed_value_size(), + self.mem, + )) + } + /// Open the given table pub fn open_multimap_table( &self, @@ -1202,6 +1222,24 @@ impl<'db> ReadTransaction<'db> { )?) } + /// Open the given table without a type + pub fn open_untyped_multimap_table( + &self, + handle: impl MultimapTableHandle, + ) -> Result { + let header = self + .tree + .get_table_untyped(handle.name(), TableType::Multimap)? + .ok_or_else(|| TableError::TableDoesNotExist(handle.name().to_string()))?; + + Ok(ReadOnlyUntypedMultimapTable::new( + header.get_root(), + header.get_fixed_key_size(), + header.get_fixed_value_size(), + self.mem, + )) + } + /// List all the tables pub fn list_tables(&self) -> Result> { self.tree diff --git a/src/tree_store/btree.rs b/src/tree_store/btree.rs index f8a0b849..4366dcbc 100644 --- a/src/tree_store/btree.rs +++ b/src/tree_store/btree.rs @@ -478,6 +478,19 @@ impl<'a> RawBtree<'a> { } } + pub(crate) fn get_root(&self) -> Option<(PageNumber, Checksum)> { + self.root + } + + pub(crate) fn stats(&self) -> Result { + btree_stats( + self.root.map(|(p, _)| p), + self.mem, + self.fixed_key_size, + self.fixed_value_size, + ) + } + pub(crate) fn verify_checksum(&self) -> Result { if let Some((root, checksum)) = self.root { self.verify_checksum_helper(root, checksum) diff --git a/tests/basic_tests.rs b/tests/basic_tests.rs index ecc3d593..4138c832 100644 --- a/tests/basic_tests.rs +++ b/tests/basic_tests.rs @@ -37,6 +37,26 @@ fn len() { assert_eq!(table.len().unwrap(), 3); } +#[test] +fn table_stats() { + let tmpfile = create_tempfile(); + let db = Database::create(tmpfile.path()).unwrap(); + let write_txn = db.begin_write().unwrap(); + { + let mut table = write_txn.open_table(STR_TABLE).unwrap(); + table.insert("hello", "world").unwrap(); + table.insert("hello2", "world2").unwrap(); + table.insert("hi", "world").unwrap(); + } + write_txn.commit().unwrap(); + + let read_txn = db.begin_read().unwrap(); + let table = read_txn.open_table(STR_TABLE).unwrap(); + let untyped_table = read_txn.open_untyped_table(STR_TABLE).unwrap(); + assert_eq!(table.stats().unwrap().tree_height(), 1); + assert_eq!(untyped_table.stats().unwrap().tree_height(), 1); +} + #[test] fn in_memory() { let db = Database::builder()