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