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}