cuprate_database/
env.rs

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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
//! Abstracted database environment; `trait Env`.

//---------------------------------------------------------------------------------------------------- Import
use std::num::NonZeroUsize;

use crate::{
    config::Config,
    database::{DatabaseIter, DatabaseRo, DatabaseRw},
    error::{InitError, RuntimeError},
    resize::ResizeAlgorithm,
    table::Table,
    transaction::{TxRo, TxRw},
};

//---------------------------------------------------------------------------------------------------- Env
/// Database environment abstraction.
///
/// Essentially, the functions that can be called on [`ConcreteEnv`](crate::ConcreteEnv).
///
/// # `Drop`
/// Objects that implement [`Env`] _should_ probably
/// [`Env::sync`] in their drop implementations,
/// although, no invariant relies on this (yet).
///
/// # Lifetimes
/// The lifetimes associated with `Env` have a sequential flow:
/// ```text
/// Env -> Tx -> Database
/// ```
///
/// As in:
/// - open database tables only live as long as...
/// - transactions which only live as long as the...
/// - database environment
pub trait Env: Sized {
    //------------------------------------------------ Constants
    /// Does the database backend need to be manually
    /// resized when the memory-map is full?
    ///
    /// # Invariant
    /// If this is `false`, that means this [`Env`]
    /// must _never_ return a [`RuntimeError::ResizeNeeded`].
    ///
    /// If this is `true`, [`Env::resize_map`] & [`Env::current_map_size`]
    /// _must_ be re-implemented, as it just panics by default.
    const MANUAL_RESIZE: bool;

    /// Does the database backend forcefully sync/flush
    /// to disk on every transaction commit?
    ///
    /// This is used as an optimization.
    const SYNCS_PER_TX: bool;

    //------------------------------------------------ Types
    /// The struct representing the actual backend's database environment.
    ///
    /// This is used as the `self` in [`EnvInner`] functions, so whatever
    /// this type is, is what will be accessible from those functions.
    ///
    // # HACK
    // For `heed`, this is just `heed::Env`, for `redb` this is
    // `(redb::Database, redb::Durability)` as each transaction
    // needs the sync mode set during creation.
    type EnvInner<'env>: EnvInner<'env>
    where
        Self: 'env;

    /// The read-only transaction type of the backend.
    type TxRo<'env>: TxRo<'env>
    where
        Self: 'env;

    /// The read/write transaction type of the backend.
    type TxRw<'env>: TxRw<'env>
    where
        Self: 'env;

    //------------------------------------------------ Required
    /// Open the database environment, using the passed [`Config`].
    ///
    /// # Invariants
    /// This function does not create any tables.
    ///
    /// You must create all possible tables with [`EnvInner::create_db`]
    /// before attempting to open any.
    ///
    /// # Errors
    /// This will error if the database file could not be opened.
    ///
    /// This is the only [`Env`] function that will return
    /// an [`InitError`] instead of a [`RuntimeError`].
    fn open(config: Config) -> Result<Self, InitError>;

    /// Return the [`Config`] that this database was [`Env::open`]ed with.
    fn config(&self) -> &Config;

    /// Fully sync the database caches to disk.
    ///
    /// # Invariant
    /// This must **fully** and **synchronously** flush the database data to disk.
    ///
    /// I.e., after this function returns, there must be no doubts
    /// that the data isn't synced yet, it _must_ be synced.
    ///
    // FIXME: either this invariant or `sync()` itself will most
    // likely be removed/changed after `SyncMode` is finalized.
    ///
    /// # Errors
    /// If there is a synchronization error, this should return an error.
    fn sync(&self) -> Result<(), RuntimeError>;

    /// Resize the database's memory map to a
    /// new (bigger) size using a [`ResizeAlgorithm`].
    ///
    /// By default, this function will use the `ResizeAlgorithm` in [`Env::config`].
    ///
    /// If `resize_algorithm` is `Some`, that will be used instead.
    ///
    /// This function returns the _new_ memory map size in bytes.
    ///
    /// # Invariant
    /// This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`.
    ///
    /// Otherwise, this function will panic with `unreachable!()`.
    #[expect(unused_variables)]
    fn resize_map(&self, resize_algorithm: Option<ResizeAlgorithm>) -> NonZeroUsize {
        unreachable!()
    }

    /// What is the _current_ size of the database's memory map in bytes?
    ///
    /// # Invariant
    /// 1. This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`.
    /// 2. This function must be accurate, as [`Env::resize_map()`] may depend on it.
    fn current_map_size(&self) -> usize {
        unreachable!()
    }

    /// Return the [`Env::EnvInner`].
    ///
    /// # Locking behavior
    /// When using the `heed` backend, [`Env::EnvInner`] is a
    /// `RwLockReadGuard`, i.e., calling this function takes a
    /// read lock on the `heed::Env`.
    ///
    /// Be aware of this, as other functions (currently only
    /// [`Env::resize_map`]) will take a _write_ lock.
    fn env_inner(&self) -> Self::EnvInner<'_>;

    //------------------------------------------------ Provided
    /// Return the amount of actual of bytes the database is taking up on disk.
    ///
    /// This is the current _disk_ value in bytes, not the memory map.
    ///
    /// # Errors
    /// This will error if either:
    ///
    /// - [`std::fs::File::open`]
    /// - [`std::fs::File::metadata`]
    ///
    /// failed on the database file on disk.
    fn disk_size_bytes(&self) -> std::io::Result<u64> {
        // We have the direct PATH to the file,
        // no need to use backend-specific functions.
        //
        // INVARIANT: as we are only accessing the metadata of
        // the file and not reading the bytes, it should be
        // fine even with a memory mapped file being actively
        // written to.
        Ok(std::fs::File::open(&self.config().db_file)?
            .metadata()?
            .len())
    }
}

//---------------------------------------------------------------------------------------------------- DatabaseRo
/// Document the INVARIANT that the `heed` backend
/// must use [`EnvInner::create_db`] when initially
/// opening/creating tables.
macro_rules! doc_heed_create_db_invariant {
    () => {
        r#"The first time you open/create tables, you _must_ use [`EnvInner::create_db`]
to set the proper flags / [`Key`](crate::Key) comparison for the `heed` backend.

Subsequent table opens will follow the flags/ordering, but only if
[`EnvInner::create_db`] was the _first_ function to open/create it."#
    };
}

/// The inner [`Env`] type.
///
/// This type is created with [`Env::env_inner`] and represents
/// the type able to generate transactions and open tables.
///
/// # Locking behavior
/// As noted in `Env::env_inner`, this is a `RwLockReadGuard`
/// when using the `heed` backend, be aware of this and do
/// not hold onto an `EnvInner` for a long time.
///
/// # Tables
/// Note that when opening tables with [`EnvInner::open_db_ro`],
/// they must be created first or else it will return error.
///
/// See [`EnvInner::create_db`] for creating tables.
///
/// # Invariant
#[doc = doc_heed_create_db_invariant!()]
pub trait EnvInner<'env> {
    /// The read-only transaction type of the backend.
    ///
    /// `'tx` is the lifetime of the transaction itself.
    type Ro<'tx>: TxRo<'tx>;
    /// The read-write transaction type of the backend.
    ///
    /// `'tx` is the lifetime of the transaction itself.
    type Rw<'tx>: TxRw<'tx>;

    /// Create a read-only transaction.
    ///
    /// # Errors
    /// This will only return [`RuntimeError::Io`] if it errors.
    fn tx_ro(&self) -> Result<Self::Ro<'_>, RuntimeError>;

    /// Create a read/write transaction.
    ///
    /// # Errors
    /// This will only return [`RuntimeError::Io`] if it errors.
    fn tx_rw(&self) -> Result<Self::Rw<'_>, RuntimeError>;

    /// Open a database in read-only mode.
    ///
    /// The returned value can have [`DatabaseRo`]
    /// & [`DatabaseIter`] functions called on it.
    ///
    /// This will open the database [`Table`]
    /// passed as a generic to this function.
    ///
    /// ```rust
    /// # use cuprate_database::{
    /// #     ConcreteEnv,
    /// #     config::ConfigBuilder,
    /// #     Env, EnvInner,
    /// #     DatabaseRo, DatabaseRw, TxRo, TxRw,
    /// # };
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// # let tmp_dir = tempfile::tempdir()?;
    /// # let db_dir = tmp_dir.path().to_owned();
    /// # let config = ConfigBuilder::new(db_dir.into()).build();
    /// # let env = ConcreteEnv::open(config)?;
    /// #
    /// # struct Table;
    /// # impl cuprate_database::Table for Table {
    /// #     const NAME: &'static str = "table";
    /// #     type Key = u8;
    /// #     type Value = u64;
    /// # }
    /// #
    /// # let env_inner = env.env_inner();
    /// # let tx_rw = env_inner.tx_rw()?;
    /// # env_inner.create_db::<Table>(&tx_rw)?;
    /// # TxRw::commit(tx_rw);
    /// #
    /// # let tx_ro = env_inner.tx_ro()?;
    /// let db = env_inner.open_db_ro::<Table>(&tx_ro);
    /// //  ^                           ^
    /// // database table             table metadata
    /// //                            (name, key/value type)
    /// # Ok(()) }
    /// ```
    ///
    /// # Errors
    /// This will only return [`RuntimeError::Io`] on normal errors.
    ///
    /// If the specified table is not created upon before this function is called,
    /// this will return [`RuntimeError::TableNotFound`].
    ///
    /// # Invariant
    #[doc = doc_heed_create_db_invariant!()]
    fn open_db_ro<T: Table>(
        &self,
        tx_ro: &Self::Ro<'_>,
    ) -> Result<impl DatabaseRo<T> + DatabaseIter<T>, RuntimeError>;

    /// Open a database in read/write mode.
    ///
    /// All [`DatabaseRo`] functions are also callable
    /// with the returned [`DatabaseRw`] structure.
    ///
    /// Note that [`DatabaseIter`] functions are _not_
    /// available to [`DatabaseRw`] structures.
    ///
    /// This will open the database [`Table`]
    /// passed as a generic to this function.
    ///
    /// # Errors
    /// This will only return [`RuntimeError::Io`] on errors.
    ///
    /// # Invariant
    #[doc = doc_heed_create_db_invariant!()]
    fn open_db_rw<T: Table>(
        &self,
        tx_rw: &Self::Rw<'_>,
    ) -> Result<impl DatabaseRw<T>, RuntimeError>;

    /// Create a database table.
    ///
    /// This will create the database [`Table`] passed as a generic to this function.
    ///
    /// # Errors
    /// This will only return [`RuntimeError::Io`] on errors.
    ///
    /// # Invariant
    #[doc = doc_heed_create_db_invariant!()]
    fn create_db<T: Table>(&self, tx_rw: &Self::Rw<'_>) -> Result<(), RuntimeError>;

    /// Clear all `(key, value)`'s from a database table.
    ///
    /// This will delete all key and values in the passed
    /// `T: Table`, but the table itself will continue to exist.
    ///
    /// Note that this operation is tied to `tx_rw`, as such this
    /// function's effects can be aborted using [`TxRw::abort`].
    ///
    /// # Errors
    /// This will return [`RuntimeError::Io`] on normal errors.
    ///
    /// If the specified table is not created upon before this function is called,
    /// this will return [`RuntimeError::TableNotFound`].
    fn clear_db<T: Table>(&self, tx_rw: &mut Self::Rw<'_>) -> Result<(), RuntimeError>;
}