tuwunel_database/mod.rs
1//! Persistent storage primitives for Tuwunel.
2//!
3//! The crate wraps RocksDB with typed maps, serialization helpers, and atomic
4//! transactions. Database handles expose the configured column families and the
5//! engine that owns them.
6
7#![deny(missing_docs)]
8
9extern crate rust_rocksdb as rocksdb;
10
11tuwunel_core::mod_ctor! {}
12tuwunel_core::mod_dtor! {}
13tuwunel_core::rustc_flags_capture! {}
14
15mod cork;
16mod de;
17mod deserialized;
18mod engine;
19mod handle;
20pub mod keyval;
21mod map;
22pub mod maps;
23mod pool;
24mod ser;
25mod stream;
26#[cfg(test)]
27mod tests;
28mod txn;
29pub(crate) mod util;
30
31use std::{ops::Index, sync::Arc};
32
33use log as _;
34use tuwunel_core::{Result, Server, err};
35
36pub use self::{
37 cork::Cork,
38 de::{Ignore, IgnoreAll, from_slice as deserialize_from_slice},
39 deserialized::Deserialized,
40 engine::Engine,
41 handle::Handle,
42 keyval::{KeyBuf, KeyVal, Slice, serialize_key, serialize_val},
43 map::{Get, Map, Qry, compact},
44 ser::{Cbor, Interfix, Json, SEP, Separator, serialize, serialize_to, serialize_to_vec},
45 txn::Txn,
46};
47pub(crate) use self::{engine::context::Context, util::or_else};
48use crate::maps::{Maps, MapsKey, MapsVal, open as open_maps};
49
50/// An open Tuwunel database and its configured maps.
51///
52/// Each instance owns maps created by one RocksDB engine. Typed accessors
53/// preserve that ownership relationship for individual reads and atomic
54/// transactions.
55pub struct Database {
56 maps: Maps,
57 /// The RocksDB engine backing every map in this database.
58 ///
59 /// Callers use the engine for database-wide operations such as backups and
60 /// memory reporting. Maps and transactions must remain associated with
61 /// this same engine.
62 pub engine: Arc<Engine>,
63 pub(crate) _ctx: Arc<Context>,
64}
65
66impl Database {
67 /// Loads an existing database or creates a new one.
68 ///
69 /// The configured map catalog is opened after the engine and indexed by
70 /// column family identity. The returned shared handle keeps the engine
71 /// context alive for its full lifetime.
72 pub async fn open(server: &Arc<Server>) -> Result<Arc<Self>> {
73 let ctx = Context::new(server)?;
74 let engine = Engine::open(ctx.clone(), maps::MAPS).await?;
75 let maps = open_maps(&engine)?;
76 let cf_index = maps
77 .values()
78 .map(|map| (map.cf_id(), Arc::downgrade(map)))
79 .collect();
80
81 engine.set_cf_index(cf_index);
82
83 Ok(Arc::new(Self { maps, engine, _ctx: ctx }))
84 }
85
86 #[inline]
87 /// Creates an empty transaction for this database.
88 ///
89 /// The transaction is bound to this database's engine and accepts writes
90 /// only for maps owned by that engine. Queued operations remain unapplied
91 /// until the transaction is executed.
92 pub fn txn(&self) -> Txn { Txn::new(&self.engine) }
93
94 #[inline]
95 /// Retrieves a configured map by name.
96 ///
97 /// The returned map belongs to this database's engine. An unknown name
98 /// produces a not-found database error.
99 pub fn get(&self, name: &str) -> Result<&Arc<Map>> {
100 self.maps
101 .get(name)
102 .ok_or_else(|| err!(Request(NotFound("column not found"))))
103 }
104
105 /// Opens an existing column family outside the configured map catalog.
106 ///
107 /// Migration readers use this for foreign database families that are not
108 /// described by `MAPS`. An absent family returns `None` without creating
109 /// it.
110 pub fn open_cf(&self, name: &'static str) -> Result<Option<Arc<Map>>> {
111 self.engine
112 .has_cf(name)
113 .then(|| Map::open(&self.engine, name))
114 .transpose()
115 }
116
117 #[inline]
118 /// Iterates over configured map names and handles.
119 ///
120 /// Entries follow the catalog's sorted map order. Every yielded handle
121 /// belongs to this database's engine.
122 pub fn iter(&self) -> impl Iterator<Item = (&MapsKey, &MapsVal)> + Send + '_ {
123 self.maps.iter()
124 }
125
126 #[inline]
127 /// Iterates over the configured map names.
128 ///
129 /// Names follow the catalog's sorted map order. The iterator borrows this
130 /// database for the duration of the traversal.
131 pub fn keys(&self) -> impl Iterator<Item = &MapsKey> + Send + '_ { self.maps.keys() }
132
133 #[inline]
134 #[must_use]
135 /// Reports whether the engine rejects writes.
136 ///
137 /// Writes are rejected when the database is opened read-only or as a
138 /// secondary instance. The value applies to every map owned by this
139 /// database.
140 pub fn is_read_only(&self) -> bool { self.engine.is_read_only() }
141
142 #[inline]
143 #[must_use]
144 /// Reports whether this database is a secondary RocksDB instance.
145 ///
146 /// A secondary instance follows another database and does not act as its
147 /// primary writer. The value applies to every map owned by this database.
148 pub fn is_secondary(&self) -> bool { self.engine.is_secondary() }
149}
150
151impl Index<&str> for Database {
152 type Output = Arc<Map>;
153
154 /// Retrieves a configured map by name.
155 ///
156 /// Indexing offers concise access when the map name is a static database
157 /// invariant. Use [`Database::get`] when absence should be handled as an
158 /// error.
159 ///
160 /// # Panics
161 ///
162 /// Panics if this database has no configured map with the requested name.
163 fn index(&self, name: &str) -> &Self::Output {
164 self.maps
165 .get(name)
166 .expect("column in database does not exist")
167 }
168}