Skip to main content

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}