cuprate_database/
env.rs

1//! Abstracted database environment; `trait Env`.
2
3//---------------------------------------------------------------------------------------------------- Import
4use std::num::NonZeroUsize;
5
6use crate::{
7    config::Config,
8    database::{DatabaseIter, DatabaseRo, DatabaseRw},
9    error::{DbResult, InitError},
10    resize::ResizeAlgorithm,
11    table::Table,
12    transaction::{TxRo, TxRw},
13};
14
15//---------------------------------------------------------------------------------------------------- Env
16/// Database environment abstraction.
17///
18/// Essentially, the functions that can be called on [`ConcreteEnv`](crate::ConcreteEnv).
19///
20/// # `Drop`
21/// Objects that implement [`Env`] _should_ probably
22/// [`Env::sync`] in their drop implementations,
23/// although, no invariant relies on this (yet).
24///
25/// # Lifetimes
26/// The lifetimes associated with `Env` have a sequential flow:
27/// ```text
28/// Env -> Tx -> Database
29/// ```
30///
31/// As in:
32/// - open database tables only live as long as...
33/// - transactions which only live as long as the...
34/// - database environment
35pub trait Env: Sized {
36    //------------------------------------------------ Constants
37    /// Does the database backend need to be manually
38    /// resized when the memory-map is full?
39    ///
40    /// # Invariant
41    /// If this is `false`, that means this [`Env`]
42    /// must _never_ return a [`crate::RuntimeError::ResizeNeeded`].
43    ///
44    /// If this is `true`, [`Env::resize_map`] & [`Env::current_map_size`]
45    /// _must_ be re-implemented, as it just panics by default.
46    const MANUAL_RESIZE: bool;
47
48    /// Does the database backend forcefully sync/flush
49    /// to disk on every transaction commit?
50    ///
51    /// This is used as an optimization.
52    const SYNCS_PER_TX: bool;
53
54    //------------------------------------------------ Types
55    /// The struct representing the actual backend's database environment.
56    ///
57    /// This is used as the `self` in [`EnvInner`] functions, so whatever
58    /// this type is, is what will be accessible from those functions.
59    ///
60    // # HACK
61    // For `heed`, this is just `heed::Env`, for `redb` this is
62    // `(redb::Database, redb::Durability)` as each transaction
63    // needs the sync mode set during creation.
64    type EnvInner<'env>: EnvInner<'env>
65    where
66        Self: 'env;
67
68    /// The read-only transaction type of the backend.
69    type TxRo<'env>: TxRo<'env>
70    where
71        Self: 'env;
72
73    /// The read/write transaction type of the backend.
74    type TxRw<'env>: TxRw<'env>
75    where
76        Self: 'env;
77
78    //------------------------------------------------ Required
79    /// Open the database environment, using the passed [`Config`].
80    ///
81    /// # Invariants
82    /// This function does not create any tables.
83    ///
84    /// You must create all possible tables with [`EnvInner::create_db`]
85    /// before attempting to open any.
86    ///
87    /// # Errors
88    /// This will error if the database file could not be opened.
89    ///
90    /// This is the only [`Env`] function that will return
91    /// an [`InitError`] instead of a [`crate::RuntimeError`].
92    fn open(config: Config) -> Result<Self, InitError>;
93
94    /// Return the [`Config`] that this database was [`Env::open`]ed with.
95    fn config(&self) -> &Config;
96
97    /// Fully sync the database caches to disk.
98    ///
99    /// # Invariant
100    /// This must **fully** and **synchronously** flush the database data to disk.
101    ///
102    /// I.e., after this function returns, there must be no doubts
103    /// that the data isn't synced yet, it _must_ be synced.
104    ///
105    // FIXME: either this invariant or `sync()` itself will most
106    // likely be removed/changed after `SyncMode` is finalized.
107    ///
108    /// # Errors
109    /// If there is a synchronization error, this should return an error.
110    fn sync(&self) -> DbResult<()>;
111
112    /// Resize the database's memory map to a
113    /// new (bigger) size using a [`ResizeAlgorithm`].
114    ///
115    /// By default, this function will use the `ResizeAlgorithm` in [`Env::config`].
116    ///
117    /// If `resize_algorithm` is `Some`, that will be used instead.
118    ///
119    /// This function returns the _new_ memory map size in bytes.
120    ///
121    /// # Invariant
122    /// This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`.
123    ///
124    /// Otherwise, this function will panic with `unreachable!()`.
125    #[expect(unused_variables)]
126    fn resize_map(&self, resize_algorithm: Option<ResizeAlgorithm>) -> NonZeroUsize {
127        unreachable!()
128    }
129
130    /// What is the _current_ size of the database's memory map in bytes?
131    ///
132    /// # Invariant
133    /// 1. This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`.
134    /// 2. This function must be accurate, as [`Env::resize_map()`] may depend on it.
135    fn current_map_size(&self) -> usize {
136        unreachable!()
137    }
138
139    /// Return the [`Env::EnvInner`].
140    ///
141    /// # Locking behavior
142    /// When using the `heed` backend, [`Env::EnvInner`] is a
143    /// `RwLockReadGuard`, i.e., calling this function takes a
144    /// read lock on the `heed::Env`.
145    ///
146    /// Be aware of this, as other functions (currently only
147    /// [`Env::resize_map`]) will take a _write_ lock.
148    fn env_inner(&self) -> Self::EnvInner<'_>;
149
150    //------------------------------------------------ Provided
151    /// Return the amount of actual of bytes the database is taking up on disk.
152    ///
153    /// This is the current _disk_ value in bytes, not the memory map.
154    ///
155    /// # Errors
156    /// This will error if either:
157    ///
158    /// - [`std::fs::File::open`]
159    /// - [`std::fs::File::metadata`]
160    ///
161    /// failed on the database file on disk.
162    fn disk_size_bytes(&self) -> std::io::Result<u64> {
163        // We have the direct PATH to the file,
164        // no need to use backend-specific functions.
165        //
166        // INVARIANT: as we are only accessing the metadata of
167        // the file and not reading the bytes, it should be
168        // fine even with a memory mapped file being actively
169        // written to.
170        Ok(std::fs::File::open(&self.config().db_file)?
171            .metadata()?
172            .len())
173    }
174}
175
176//---------------------------------------------------------------------------------------------------- DatabaseRo
177/// Document the INVARIANT that the `heed` backend
178/// must use [`EnvInner::create_db`] when initially
179/// opening/creating tables.
180macro_rules! doc_heed_create_db_invariant {
181    () => {
182        r"The first time you open/create tables, you _must_ use [`EnvInner::create_db`]
183to set the proper flags / [`Key`](crate::Key) comparison for the `heed` backend.
184
185Subsequent table opens will follow the flags/ordering, but only if
186[`EnvInner::create_db`] was the _first_ function to open/create it."
187    };
188}
189
190/// The inner [`Env`] type.
191///
192/// This type is created with [`Env::env_inner`] and represents
193/// the type able to generate transactions and open tables.
194///
195/// # Locking behavior
196/// As noted in `Env::env_inner`, this is a `RwLockReadGuard`
197/// when using the `heed` backend, be aware of this and do
198/// not hold onto an `EnvInner` for a long time.
199///
200/// # Tables
201/// Note that when opening tables with [`EnvInner::open_db_ro`],
202/// they must be created first or else it will return error.
203///
204/// See [`EnvInner::create_db`] for creating tables.
205///
206/// # Invariant
207#[doc = doc_heed_create_db_invariant!()]
208pub trait EnvInner<'env> {
209    /// The read-only transaction type of the backend.
210    ///
211    /// `'tx` is the lifetime of the transaction itself.
212    type Ro<'tx>: TxRo<'tx>;
213    /// The read-write transaction type of the backend.
214    ///
215    /// `'tx` is the lifetime of the transaction itself.
216    type Rw<'tx>: TxRw<'tx>;
217
218    /// Create a read-only transaction.
219    ///
220    /// # Errors
221    /// This will only return [`crate::RuntimeError::Io`] if it errors.
222    fn tx_ro(&self) -> DbResult<Self::Ro<'_>>;
223
224    /// Create a read/write transaction.
225    ///
226    /// # Errors
227    /// This will only return [`crate::RuntimeError::Io`] if it errors.
228    fn tx_rw(&self) -> DbResult<Self::Rw<'_>>;
229
230    /// Open a database in read-only mode.
231    ///
232    /// The returned value can have [`DatabaseRo`]
233    /// & [`DatabaseIter`] functions called on it.
234    ///
235    /// This will open the database [`Table`]
236    /// passed as a generic to this function.
237    ///
238    /// ```rust
239    /// # use cuprate_database::{
240    /// #     ConcreteEnv,
241    /// #     config::ConfigBuilder,
242    /// #     Env, EnvInner,
243    /// #     DatabaseRo, DatabaseRw, TxRo, TxRw,
244    /// # };
245    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
246    /// # let tmp_dir = tempfile::tempdir()?;
247    /// # let db_dir = tmp_dir.path().to_owned();
248    /// # let config = ConfigBuilder::new(db_dir.into()).build();
249    /// # let env = ConcreteEnv::open(config)?;
250    /// #
251    /// # struct Table;
252    /// # impl cuprate_database::Table for Table {
253    /// #     const NAME: &'static str = "table";
254    /// #     type Key = u8;
255    /// #     type Value = u64;
256    /// # }
257    /// #
258    /// # let env_inner = env.env_inner();
259    /// # let tx_rw = env_inner.tx_rw()?;
260    /// # env_inner.create_db::<Table>(&tx_rw)?;
261    /// # TxRw::commit(tx_rw);
262    /// #
263    /// # let tx_ro = env_inner.tx_ro()?;
264    /// let db = env_inner.open_db_ro::<Table>(&tx_ro);
265    /// //  ^                           ^
266    /// // database table             table metadata
267    /// //                            (name, key/value type)
268    /// # Ok(()) }
269    /// ```
270    ///
271    /// # Errors
272    /// This will only return [`crate::RuntimeError::Io`] on normal errors.
273    ///
274    /// If the specified table is not created upon before this function is called,
275    /// this will return [`crate::RuntimeError::TableNotFound`].
276    ///
277    /// # Invariant
278    #[doc = doc_heed_create_db_invariant!()]
279    fn open_db_ro<T: Table>(
280        &self,
281        tx_ro: &Self::Ro<'_>,
282    ) -> DbResult<impl DatabaseRo<T> + DatabaseIter<T>>;
283
284    /// Open a database in read/write mode.
285    ///
286    /// All [`DatabaseRo`] functions are also callable
287    /// with the returned [`DatabaseRw`] structure.
288    ///
289    /// Note that [`DatabaseIter`] functions are _not_
290    /// available to [`DatabaseRw`] structures.
291    ///
292    /// This will open the database [`Table`]
293    /// passed as a generic to this function.
294    ///
295    /// # Errors
296    /// This will only return [`crate::RuntimeError::Io`] on errors.
297    ///
298    /// # Invariant
299    #[doc = doc_heed_create_db_invariant!()]
300    fn open_db_rw<T: Table>(&self, tx_rw: &Self::Rw<'_>) -> DbResult<impl DatabaseRw<T>>;
301
302    /// Create a database table.
303    ///
304    /// This will create the database [`Table`] passed as a generic to this function.
305    ///
306    /// # Errors
307    /// This will only return [`crate::RuntimeError::Io`] on errors.
308    ///
309    /// # Invariant
310    #[doc = doc_heed_create_db_invariant!()]
311    fn create_db<T: Table>(&self, tx_rw: &Self::Rw<'_>) -> DbResult<()>;
312
313    /// Clear all `(key, value)`'s from a database table.
314    ///
315    /// This will delete all key and values in the passed
316    /// `T: Table`, but the table itself will continue to exist.
317    ///
318    /// Note that this operation is tied to `tx_rw`, as such this
319    /// function's effects can be aborted using [`TxRw::abort`].
320    ///
321    /// # Errors
322    /// This will return [`crate::RuntimeError::Io`] on normal errors.
323    ///
324    /// If the specified table is not created upon before this function is called,
325    /// this will return [`crate::RuntimeError::TableNotFound`].
326    fn clear_db<T: Table>(&self, tx_rw: &mut Self::Rw<'_>) -> DbResult<()>;
327}