matrix_sdk_sqlite/
lib.rs

1// Copyright 2022 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14#![cfg_attr(
15    not(any(feature = "state-store", feature = "crypto-store", feature = "event-cache")),
16    allow(dead_code, unused_imports)
17)]
18
19#[cfg(feature = "crypto-store")]
20mod crypto_store;
21mod error;
22#[cfg(feature = "event-cache")]
23mod event_cache_store;
24#[cfg(feature = "state-store")]
25mod state_store;
26mod utils;
27use std::path::{Path, PathBuf};
28
29use deadpool_sqlite::PoolConfig;
30
31#[cfg(feature = "crypto-store")]
32pub use self::crypto_store::SqliteCryptoStore;
33pub use self::error::OpenStoreError;
34#[cfg(feature = "event-cache")]
35pub use self::event_cache_store::SqliteEventCacheStore;
36#[cfg(feature = "state-store")]
37pub use self::state_store::SqliteStateStore;
38
39#[cfg(test)]
40matrix_sdk_test::init_tracing_for_tests!();
41
42/// A configuration structure used for opening a store.
43pub struct SqliteStoreConfig {
44    /// Path to the database, without the file name.
45    path: PathBuf,
46    /// Passphrase to open the store, if any.
47    passphrase: Option<String>,
48    /// The pool configuration for [`deadpool_sqlite`].
49    pool_config: PoolConfig,
50    /// The runtime configuration to apply when opening an SQLite connection.
51    runtime_config: RuntimeConfig,
52}
53
54impl SqliteStoreConfig {
55    /// Create a new [`SqliteStoreConfig`] with a path representing the
56    /// directory containing the store database.
57    pub fn new<P>(path: P) -> Self
58    where
59        P: AsRef<Path>,
60    {
61        Self {
62            path: path.as_ref().to_path_buf(),
63            passphrase: None,
64            pool_config: PoolConfig::new(num_cpus::get_physical() * 4),
65            runtime_config: RuntimeConfig::default(),
66        }
67    }
68
69    /// Define the passphrase if the store is encoded.
70    pub fn passphrase(mut self, passphrase: Option<&str>) -> Self {
71        self.passphrase = passphrase.map(|passphrase| passphrase.to_owned());
72        self
73    }
74
75    /// Define the maximum pool size for [`deadpool_sqlite`].
76    ///
77    /// See [`deadpool_sqlite::PoolConfig::max_size`] to learn more.
78    pub fn pool_max_size(mut self, max_size: usize) -> Self {
79        self.pool_config.max_size = max_size;
80        self
81    }
82
83    /// Optimize the database.
84    ///
85    /// The SQLite documentation recommends to run this regularly and after any
86    /// schema change. The easiest is to do it consistently when the store is
87    /// constructed, after eventual migrations.
88    ///
89    /// See [`PRAGMA optimize`] to learn more.
90    ///
91    /// The default value is `true`.
92    ///
93    /// [`PRAGMA cache_size`]: https://www.sqlite.org/pragma.html#pragma_optimize
94    pub fn optimize(mut self, optimize: bool) -> Self {
95        self.runtime_config.optimize = optimize;
96        self
97    }
98
99    /// Define the maximum size in **bytes** the SQLite cache can use.
100    ///
101    /// See [`PRAGMA cache_size`] to learn more.
102    ///
103    /// The default value is 2Mib.
104    ///
105    /// [`PRAGMA cache_size`]: https://www.sqlite.org/pragma.html#pragma_cache_size
106    pub fn cache_size(mut self, cache_size: u32) -> Self {
107        self.runtime_config.cache_size = cache_size;
108        self
109    }
110
111    /// Limit the size of the WAL file, in **bytes**.
112    ///
113    /// By default, while the DB connections of the databases are open, [the
114    /// size of the WAL file can keep increasing][size_wal_file] depending on
115    /// the size needed for the transactions. A critical case is `VACUUM`
116    /// which basically writes the content of the DB file to the WAL file
117    /// before writing it back to the DB file, so we end up taking twice the
118    /// size of the database.
119    ///
120    /// By setting this limit, the WAL file is truncated after its content is
121    /// written to the database, if it is bigger than the limit.
122    ///
123    /// See [`PRAGMA journal_size_limit`] to learn more. The value `limit`
124    /// corresponds to `N` in `PRAGMA journal_size_limit = N`.
125    ///
126    /// The default value is 10Mib.
127    ///
128    /// [size_wal_file]: https://www.sqlite.org/wal.html#avoiding_excessively_large_wal_files
129    /// [`PRAGMA journal_size_limit`]: https://www.sqlite.org/pragma.html#pragma_journal_size_limit
130    pub fn journal_size_limit(mut self, limit: u32) -> Self {
131        self.runtime_config.journal_size_limit = limit;
132        self
133    }
134}
135
136/// This type represents values to set at runtime when a database is opened.
137///
138/// This configuration is applied by
139/// [`utils::SqliteAsyncConnExt::apply_runtime_config`].
140struct RuntimeConfig {
141    /// If `true`, [`utils::SqliteAsyncConnExt::optimize`] will be called.
142    optimize: bool,
143
144    /// Regardless of the value, [`utils::SqliteAsyncConnExt::cache_size`] will
145    /// always be called with this value.
146    cache_size: u32,
147
148    /// Regardless of the value,
149    /// [`utils::SqliteAsyncConnExt::journal_size_limit`] will always be called
150    /// with this value.
151    journal_size_limit: u32,
152}
153
154impl Default for RuntimeConfig {
155    fn default() -> Self {
156        Self {
157            // Optimize is always applied.
158            optimize: true,
159            // A cache of 2Mib.
160            cache_size: 2_000_000,
161            // A limit of 10Mib.
162            journal_size_limit: 10_000_000,
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use std::{
170        ops::Not,
171        path::{Path, PathBuf},
172    };
173
174    use super::SqliteStoreConfig;
175
176    #[test]
177    fn test_store_open_config() {
178        let store_open_config = SqliteStoreConfig::new(Path::new("foo"))
179            .passphrase(Some("bar"))
180            .pool_max_size(42)
181            .optimize(false)
182            .cache_size(43)
183            .journal_size_limit(44);
184
185        assert_eq!(store_open_config.path, PathBuf::from("foo"));
186        assert_eq!(store_open_config.passphrase, Some("bar".to_owned()));
187        assert_eq!(store_open_config.pool_config.max_size, 42);
188        assert!(store_open_config.runtime_config.optimize.not());
189        assert_eq!(store_open_config.runtime_config.cache_size, 43);
190        assert_eq!(store_open_config.runtime_config.journal_size_limit, 44);
191    }
192}