1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
//! A lightweight, embedded key-value database for mobile clients (i.e., iOS, Android),
//! written in Rust.
//!
//! `ThetaDB` is suitable for use on mobile clients with **"High-Read, Low-Write"** demands,
//! it uses B+ Tree as the foundational layer for index management.
//!
//! Inspired by Go's [BoltDB](https://github.com/boltdb/bolt), ThetaDB uses `mmap`, relying
//! on the operating system to keep memory and database files in sync. ThetaDB also implements
//! `shadow paging` to guarantee atomicity and durability of transactions, preventing data loss
//! or damage to the internal structures of database.
//!
//! # Open Database
//!
//! Use following way to open the database at the specified path. If the database file does not
//! exist, ThetaDB will automatically create and initialize it.
//!
//! ```
//! use thetadb::{Options, ThetaDB, Result};
//! # fn try_main() -> Result<()> {
//!
//! let path = "target/db.theta";
//!
//! // The simplest way to open with default `Options`:
//! let db = ThetaDB::open(path)?;
//!
//! // Open with `Options`:
//! let db = Options::new()
//! .force_sync(true)
//! .mempool_capacity(8)
//! .open(path)?;
//! # Ok(())
//! # }
//!
//! # fn main() { try_main().unwrap(); }
//! ```
//! ThetaDB will automatically close when the database instance is destroyed.
//!
//! # Get, Insert, Update, Delete
//!
//! ```
//! # use thetadb::{ThetaDB, Result};
//! # fn try_main() -> Result<()> {
//! # let db = ThetaDB::open("target/db.theta")?;
//! // Insert a new key-value pair into database.
//! db.put(b"foo", b"foo")?;
//!
//! // Check if the database contains a given key.
//! assert!(db.contains(b"foo")?);
//! assert!(!db.contains(b"unknown")?);
//!
//! // Get the value associated with a given key.
//! assert_eq!(
//! db.get(b"foo")?,
//! Some(b"foo".to_vec())
//! );
//! assert_eq!(
//! db.get(b"unknown")?,
//! None
//! );
//!
//! // Update an existing value associated with a given key.
//! db.put(b"foo", b"bar")?;
//! assert_eq!(
//! db.get(b"foo")?,
//! Some(b"bar".to_vec())
//! );
//!
//! // Delete an existing key-value pair from database.
//! db.delete(b"foo")?;
//! assert!(!db.contains(b"foo")?);
//! assert_eq!(
//! db.get(b"foo")?,
//! None
//! );
//! # Ok(())
//! # }
//! # fn main() { try_main().unwrap(); }
//! ```
//!
//! # Transaction
//!
//! ThetaDB has two kinds of transactions: `Read-Only Transaction` and `Read-Write Transaction`.
//! The read-only transaction allows for read-only access and the read-write transaction allows
//! modification.
//!
//! ThetaDB allows a number of read-only transactions at a time but allows at most one read-write
//! transaction at a time. When a read-write transaction is committing, it has exclusive access
//! to the database until the commit is completed, at which point other transactions trying to
//! access the database will be blocked. You can think of this situation as `shared access` and
//! `exclusive access` to reader-writer lock.
//!
//! ## Read-Only Transaction
//!
//! ```
//! # use thetadb::{ThetaDB, Result};
//! # fn try_main() -> Result<()> {
//! # let db = ThetaDB::open("target/db.theta")?;
//! // Start a read-only transaction.
//! let tx = db.begin_tx()?;
//!
//! // Then perform read-only access.
//! _ = tx.contains(b"foo")?;
//! _ = tx.get(b"foo")?;
//!
//! // Or you can perform a read-only transaction using closure,
//! // with `view` method:
//! db.view(|tx| {
//! _ = tx.contains(b"foo")?;
//! _ = tx.get(b"foo")?;
//! Ok(())
//! })?;
//! # Ok(())
//! # }
//! # fn main() { try_main().unwrap(); }
//! ```
//!
//! ## Read-Write Transaction
//!
//! ThetaBD's read-write transactions are designed to automatically rollback, and therefore any
//! changes made to the transaction will be discarded unless you explicity call the `commit`
//! method.
//!
//! Or you can perform a read-write transaction using closure, if no errors occur, then the
//! transaction will be commit automatically after the closure call.
//!
//! ```
//! # use thetadb::{ThetaDB, Result};
//! # fn try_main() -> Result<()> {
//! # let db = ThetaDB::open("target/db.theta")?;
//! // Start a read-write transaction.
//! let mut tx = db.begin_tx_mut()?;
//!
//! // Then perform read-write access.
//! tx.put(b"hello", b"world")?;
//! _ = tx.get(b"hello")?;
//!
//! // Finally, commit the transaction.
//! tx.commit()?;
//!
//! // Or you can perform a read-write transaction using closure,
//! // with `update` method:
//! db.update(|tx| {
//! tx.put(b"hello", b"world")?;
//! _ = tx.get(b"hello")?;
//! Ok(())
//! })?;
//! # Ok(())
//! # }
//! # fn main() { try_main().unwrap(); }
//! ```
//!
//! ## Attention
//!
//! ❗️ Transaction instances are nonsendable, which means it's not safe to send them to another
//! thread. Rust leverages `Ownership` system and the `Send` and `Sync` traits to enforce
//! requirements automatically, whereas Swift requires us to manually ensure these guarantees.
//!
//! ❗️ Read-only transactions and read-write transaction must not overlap, otherwise a deadlock
//! will be occurred.
//!
//! 😺 So ThetaDB recommends that if you want to use transactions, use the APIs with closure
//! parameter (i.e., `view`, `update`).
//!
//! # Cursor
//!
//! We can freely traverse the data in the ThetaDB using `Cursor`.
//!
//! For instance, we can iterate over all the key-value pairs in the ThetaDB like this:
//!
//! ```
//! # use thetadb::{ThetaDB, Result};
//! # fn try_main() -> Result<()> {
//! # let db = ThetaDB::open("target/db.theta")?;
//! // Forward traversal.
//! let mut cursor = db.first_cursor()?;
//! while let Some((key, value)) = cursor.key_value()? {
//! println!("{:?} => {:?}", key, value);
//! cursor.next()?;
//! }
//!
//! // Backward traversal.
//! let mut cursor = db.last_cursor()?;
//! while let Some((key, value)) = cursor.key_value()? {
//! println!("{:?} => {:?}", key, value);
//! cursor.prev()?;
//! }
//! # Ok(())
//! # }
//! # fn main() { try_main().unwrap(); }
//! ```
//!
//! Or we can perform range queries on ThetaDB in this way:
//!
//! ```
//! # use thetadb::{ThetaDB, Result};
//! # fn try_main() -> Result<()> {
//! # let db = ThetaDB::open("target/db.theta")?;
//! let mut cursor = db.cursor_from_key(b"C")?;
//! while let Some((key, value)) = cursor.key_value()? {
//! if key == b"G" {
//! break;
//! }
//! println!("{:?} => {:?}", key, value);
//! cursor.next()?;
//! }
//! # Ok(())
//! # }
//! # fn main() { try_main().unwrap(); }
//! ```
//!
//! ## Attention
//!
//! ❗️ Cursor is also a transaction (can be understood as a read-only transaction), so it alse
//! follows the transaction considerations mentioned above.
//!
#![allow(clippy::unit_arg)]
mod bptree;
mod chunk;
mod db;
mod error;
mod freelist;
mod medium;
mod meta;
mod storage;
mod tx;
pub use crate::{
db::{Options, ThetaDB},
error::{Error, ErrorCode, Result},
tx::{CursorTx, Debugger, Tx, TxMut},
};
/// The maximum length of a key that can be put into the database.
pub const MAX_KEY_LEN: usize = 255;
/// The maximum length of a value that can be put into the database.
pub const MAX_VALUE_LEN: usize = 10 * 1024 * 1024;