1#![cfg_attr(
16 not(any(
17 feature = "state-store",
18 feature = "crypto-store",
19 feature = "event-cache-store",
20 feature = "media-store"
21 )),
22 allow(dead_code, unused_imports)
23)]
24
25mod connection;
26#[cfg(feature = "crypto-store")]
27mod crypto_store;
28mod error;
29#[cfg(feature = "event-cache-store")]
30mod event_cache_store;
31#[cfg(feature = "media-store")]
32mod media_store;
33#[cfg(feature = "state-store")]
34mod state_store;
35mod utils;
36use std::{
37 cmp::max,
38 fmt,
39 path::{Path, PathBuf},
40};
41
42use deadpool::managed::PoolConfig;
43use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
44
45#[cfg(feature = "crypto-store")]
46pub use self::crypto_store::SqliteCryptoStore;
47pub use self::error::OpenStoreError;
48#[cfg(feature = "event-cache-store")]
49pub use self::event_cache_store::SqliteEventCacheStore;
50#[cfg(feature = "media-store")]
51pub use self::media_store::SqliteMediaStore;
52#[cfg(feature = "state-store")]
53pub use self::state_store::{DATABASE_NAME as STATE_STORE_DATABASE_NAME, SqliteStateStore};
54
55#[cfg(test)]
56matrix_sdk_test_utils::init_tracing_for_tests!();
57
58#[derive(Clone, Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
60pub enum Secret {
61 Key(Box<[u8; 32]>),
63 PassPhrase(Zeroizing<String>),
65}
66
67#[derive(Clone)]
69pub struct SqliteStoreConfig {
70 path: PathBuf,
72 secret: Option<Secret>,
74 pool_config: PoolConfig,
76 runtime_config: RuntimeConfig,
78}
79
80impl fmt::Debug for SqliteStoreConfig {
81 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 formatter
83 .debug_struct("SqliteStoreConfig")
84 .field("path", &self.path)
85 .field("pool_config", &self.pool_config)
86 .field("runtime_config", &self.runtime_config)
87 .finish_non_exhaustive()
88 }
89}
90
91const POOL_MINIMUM_SIZE: usize = 2;
96
97impl SqliteStoreConfig {
98 pub fn new<P>(path: P) -> Self
101 where
102 P: AsRef<Path>,
103 {
104 Self {
105 path: path.as_ref().to_path_buf(),
106 pool_config: PoolConfig::new(max(POOL_MINIMUM_SIZE, num_cpus::get_physical() * 4)),
107 runtime_config: RuntimeConfig::default(),
108 secret: None,
109 }
110 }
111
112 pub fn with_low_memory_config<P>(path: P) -> Self
122 where
123 P: AsRef<Path>,
124 {
125 Self::new(path)
126 .pool_max_size(num_cpus::get_physical())
128 .cache_size(500_000)
130 .journal_size_limit(2_000_000)
132 }
133
134 pub fn path<P>(mut self, path: P) -> Self
136 where
137 P: AsRef<Path>,
138 {
139 self.path = path.as_ref().to_path_buf();
140 self
141 }
142
143 pub fn passphrase(mut self, passphrase: Option<&str>) -> Self {
145 self.secret =
146 passphrase.map(|passphrase| Secret::PassPhrase(Zeroizing::new(passphrase.to_owned())));
147 self
148 }
149
150 pub fn key(mut self, key: Option<&[u8; 32]>) -> Self {
152 self.secret = key.map(|key| Secret::Key(Box::new(*key)));
153 self
154 }
155
156 pub fn pool_max_size(mut self, max_size: usize) -> Self {
160 self.pool_config.max_size = max(POOL_MINIMUM_SIZE, max_size);
161 self
162 }
163
164 pub fn optimize(mut self, optimize: bool) -> Self {
176 self.runtime_config.optimize = optimize;
177 self
178 }
179
180 pub fn cache_size(mut self, cache_size: u32) -> Self {
188 self.runtime_config.cache_size = cache_size;
189 self
190 }
191
192 pub fn journal_size_limit(mut self, limit: u32) -> Self {
212 self.runtime_config.journal_size_limit = limit;
213 self
214 }
215
216 pub(crate) fn pool_config(&self) -> PoolConfig {
218 self.pool_config
219 }
220
221 pub(crate) fn runtime_config(&self) -> RuntimeConfig {
223 self.runtime_config
224 }
225
226 pub fn build_pool_of_connections(
228 &self,
229 database_name: &str,
230 ) -> Result<connection::Pool, connection::CreatePoolError> {
231 let path = self.path.join(database_name);
232 let manager = connection::Manager::new(path);
233
234 connection::Pool::builder(manager)
235 .config(self.pool_config)
236 .runtime(connection::RUNTIME)
237 .build()
238 .map_err(connection::CreatePoolError::Build)
239 }
240}
241
242#[derive(Clone, Copy, Debug)]
247struct RuntimeConfig {
248 optimize: bool,
250
251 cache_size: u32,
254
255 journal_size_limit: u32,
259}
260
261impl Default for RuntimeConfig {
262 fn default() -> Self {
263 Self {
264 optimize: true,
266 cache_size: 2_000_000,
268 journal_size_limit: 10_000_000,
270 }
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use std::{
277 ops::Not,
278 path::{Path, PathBuf},
279 };
280
281 use super::{POOL_MINIMUM_SIZE, Secret, SqliteStoreConfig};
282
283 #[test]
284 fn test_new() {
285 let store_config = SqliteStoreConfig::new(Path::new("foo"));
286
287 assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical() * 4);
288 assert!(store_config.runtime_config.optimize);
289 assert_eq!(store_config.runtime_config.cache_size, 2_000_000);
290 assert_eq!(store_config.runtime_config.journal_size_limit, 10_000_000);
291 }
292
293 #[test]
294 fn test_with_low_memory_config() {
295 let store_config = SqliteStoreConfig::with_low_memory_config(Path::new("foo"));
296
297 assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical());
298 assert!(store_config.runtime_config.optimize);
299 assert_eq!(store_config.runtime_config.cache_size, 500_000);
300 assert_eq!(store_config.runtime_config.journal_size_limit, 2_000_000);
301 }
302
303 #[test]
304 fn test_store_config_when_passphrase() {
305 let store_config = SqliteStoreConfig::new(Path::new("foo"))
306 .passphrase(Some("bar"))
307 .pool_max_size(42)
308 .optimize(false)
309 .cache_size(43)
310 .journal_size_limit(44);
311
312 assert_eq!(store_config.path, PathBuf::from("foo"));
313 assert_eq!(store_config.secret, Some(Secret::PassPhrase("bar".to_owned().into())));
314 assert_eq!(store_config.pool_config.max_size, 42);
315 assert!(store_config.runtime_config.optimize.not());
316 assert_eq!(store_config.runtime_config.cache_size, 43);
317 assert_eq!(store_config.runtime_config.journal_size_limit, 44);
318 }
319
320 #[test]
321 fn test_store_config_when_key() {
322 let store_config = SqliteStoreConfig::new(Path::new("foo"))
323 .key(Some(&[
324 143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
325 107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
326 ]))
327 .pool_max_size(42)
328 .optimize(false)
329 .cache_size(43)
330 .journal_size_limit(44);
331
332 assert_eq!(store_config.path, PathBuf::from("foo"));
333 assert_eq!(
334 store_config.secret,
335 Some(Secret::Key(Box::new([
336 143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
337 107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
338 ])))
339 );
340 assert_eq!(store_config.pool_config.max_size, 42);
341 assert!(store_config.runtime_config.optimize.not());
342 assert_eq!(store_config.runtime_config.cache_size, 43);
343 assert_eq!(store_config.runtime_config.journal_size_limit, 44);
344 }
345
346 #[test]
347 fn test_store_config_path() {
348 let store_config = SqliteStoreConfig::new(Path::new("foo")).path(Path::new("bar"));
349
350 assert_eq!(store_config.path, PathBuf::from("bar"));
351 }
352
353 #[test]
354 fn test_pool_size_has_a_minimum() {
355 let store_config = SqliteStoreConfig::new(Path::new("foo")).pool_max_size(1);
356
357 assert_eq!(store_config.pool_config.max_size, POOL_MINIMUM_SIZE);
358 }
359}