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::{SqliteStateStore, DATABASE_NAME as STATE_STORE_DATABASE_NAME};
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 fn build_pool_of_connections(
213 &self,
214 database_name: &str,
215 ) -> Result<connection::Pool, connection::CreatePoolError> {
216 let path = self.path.join(database_name);
217 let manager = connection::Manager::new(path);
218
219 connection::Pool::builder(manager)
220 .config(self.pool_config)
221 .runtime(connection::RUNTIME)
222 .build()
223 .map_err(connection::CreatePoolError::Build)
224 }
225}
226
227#[derive(Clone, Debug)]
232struct RuntimeConfig {
233 optimize: bool,
235
236 cache_size: u32,
239
240 journal_size_limit: u32,
244}
245
246impl Default for RuntimeConfig {
247 fn default() -> Self {
248 Self {
249 optimize: true,
251 cache_size: 2_000_000,
253 journal_size_limit: 10_000_000,
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use std::{
262 ops::Not,
263 path::{Path, PathBuf},
264 };
265
266 use super::{Secret, SqliteStoreConfig, POOL_MINIMUM_SIZE};
267
268 #[test]
269 fn test_new() {
270 let store_config = SqliteStoreConfig::new(Path::new("foo"));
271
272 assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical() * 4);
273 assert!(store_config.runtime_config.optimize);
274 assert_eq!(store_config.runtime_config.cache_size, 2_000_000);
275 assert_eq!(store_config.runtime_config.journal_size_limit, 10_000_000);
276 }
277
278 #[test]
279 fn test_with_low_memory_config() {
280 let store_config = SqliteStoreConfig::with_low_memory_config(Path::new("foo"));
281
282 assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical());
283 assert!(store_config.runtime_config.optimize);
284 assert_eq!(store_config.runtime_config.cache_size, 500_000);
285 assert_eq!(store_config.runtime_config.journal_size_limit, 2_000_000);
286 }
287
288 #[test]
289 fn test_store_config_when_passphrase() {
290 let store_config = SqliteStoreConfig::new(Path::new("foo"))
291 .passphrase(Some("bar"))
292 .pool_max_size(42)
293 .optimize(false)
294 .cache_size(43)
295 .journal_size_limit(44);
296
297 assert_eq!(store_config.path, PathBuf::from("foo"));
298 assert_eq!(store_config.secret, Some(Secret::PassPhrase("bar".to_owned().into())));
299 assert_eq!(store_config.pool_config.max_size, 42);
300 assert!(store_config.runtime_config.optimize.not());
301 assert_eq!(store_config.runtime_config.cache_size, 43);
302 assert_eq!(store_config.runtime_config.journal_size_limit, 44);
303 }
304
305 #[test]
306 fn test_store_config_when_key() {
307 let store_config = SqliteStoreConfig::new(Path::new("foo"))
308 .key(Some(&[
309 143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
310 107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
311 ]))
312 .pool_max_size(42)
313 .optimize(false)
314 .cache_size(43)
315 .journal_size_limit(44);
316
317 assert_eq!(store_config.path, PathBuf::from("foo"));
318 assert_eq!(
319 store_config.secret,
320 Some(Secret::Key(Box::new([
321 143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
322 107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
323 ])))
324 );
325 assert_eq!(store_config.pool_config.max_size, 42);
326 assert!(store_config.runtime_config.optimize.not());
327 assert_eq!(store_config.runtime_config.cache_size, 43);
328 assert_eq!(store_config.runtime_config.journal_size_limit, 44);
329 }
330
331 #[test]
332 fn test_store_config_path() {
333 let store_config = SqliteStoreConfig::new(Path::new("foo")).path(Path::new("bar"));
334
335 assert_eq!(store_config.path, PathBuf::from("bar"));
336 }
337
338 #[test]
339 fn test_pool_size_has_a_minimum() {
340 let store_config = SqliteStoreConfig::new(Path::new("foo")).pool_max_size(1);
341
342 assert_eq!(store_config.pool_config.max_size, POOL_MINIMUM_SIZE);
343 }
344}