#![cfg_attr(
not(any(feature = "state-store", feature = "crypto-store", feature = "event-cache")),
allow(dead_code, unused_imports)
)]
mod connection;
#[cfg(feature = "crypto-store")]
mod crypto_store;
mod error;
#[cfg(feature = "event-cache")]
mod event_cache_store;
#[cfg(feature = "event-cache")]
mod media_store;
#[cfg(feature = "state-store")]
mod state_store;
mod utils;
use std::{
cmp::max,
fmt,
path::{Path, PathBuf},
};
use deadpool::managed::PoolConfig;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
#[cfg(feature = "crypto-store")]
pub use self::crypto_store::SqliteCryptoStore;
pub use self::error::OpenStoreError;
#[cfg(feature = "event-cache")]
pub use self::event_cache_store::SqliteEventCacheStore;
#[cfg(feature = "event-cache")]
pub use self::media_store::SqliteMediaStore;
#[cfg(feature = "state-store")]
pub use self::state_store::{SqliteStateStore, DATABASE_NAME as STATE_STORE_DATABASE_NAME};
#[cfg(test)]
matrix_sdk_test_utils::init_tracing_for_tests!();
#[derive(Clone, Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
pub enum Secret {
Key(Box<[u8; 32]>),
PassPhrase(Zeroizing<String>),
}
#[derive(Clone)]
pub struct SqliteStoreConfig {
path: PathBuf,
secret: Option<Secret>,
pool_config: PoolConfig,
runtime_config: RuntimeConfig,
}
impl fmt::Debug for SqliteStoreConfig {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SqliteStoreConfig")
.field("path", &self.path)
.field("pool_config", &self.pool_config)
.field("runtime_config", &self.runtime_config)
.finish_non_exhaustive()
}
}
const POOL_MINIMUM_SIZE: usize = 2;
impl SqliteStoreConfig {
pub fn new<P>(path: P) -> Self
where
P: AsRef<Path>,
{
Self {
path: path.as_ref().to_path_buf(),
pool_config: PoolConfig::new(max(POOL_MINIMUM_SIZE, num_cpus::get_physical() * 4)),
runtime_config: RuntimeConfig::default(),
secret: None,
}
}
pub fn with_low_memory_config<P>(path: P) -> Self
where
P: AsRef<Path>,
{
Self::new(path)
.pool_max_size(num_cpus::get_physical())
.cache_size(500_000)
.journal_size_limit(2_000_000)
}
pub fn path<P>(mut self, path: P) -> Self
where
P: AsRef<Path>,
{
self.path = path.as_ref().to_path_buf();
self
}
pub fn passphrase(mut self, passphrase: Option<&str>) -> Self {
self.secret =
passphrase.map(|passphrase| Secret::PassPhrase(Zeroizing::new(passphrase.to_owned())));
self
}
pub fn key(mut self, key: Option<&[u8; 32]>) -> Self {
self.secret = key.map(|key| Secret::Key(Box::new(*key)));
self
}
pub fn pool_max_size(mut self, max_size: usize) -> Self {
self.pool_config.max_size = max(POOL_MINIMUM_SIZE, max_size);
self
}
pub fn optimize(mut self, optimize: bool) -> Self {
self.runtime_config.optimize = optimize;
self
}
pub fn cache_size(mut self, cache_size: u32) -> Self {
self.runtime_config.cache_size = cache_size;
self
}
pub fn journal_size_limit(mut self, limit: u32) -> Self {
self.runtime_config.journal_size_limit = limit;
self
}
pub fn build_pool_of_connections(
&self,
database_name: &str,
) -> Result<connection::Pool, connection::CreatePoolError> {
let path = self.path.join(database_name);
let manager = connection::Manager::new(path);
connection::Pool::builder(manager)
.config(self.pool_config)
.runtime(connection::RUNTIME)
.build()
.map_err(connection::CreatePoolError::Build)
}
}
#[derive(Clone, Debug)]
struct RuntimeConfig {
optimize: bool,
cache_size: u32,
journal_size_limit: u32,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
optimize: true,
cache_size: 2_000_000,
journal_size_limit: 10_000_000,
}
}
}
#[cfg(test)]
mod tests {
use std::{
ops::Not,
path::{Path, PathBuf},
};
use super::{Secret, SqliteStoreConfig, POOL_MINIMUM_SIZE};
#[test]
fn test_new() {
let store_config = SqliteStoreConfig::new(Path::new("foo"));
assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical() * 4);
assert!(store_config.runtime_config.optimize);
assert_eq!(store_config.runtime_config.cache_size, 2_000_000);
assert_eq!(store_config.runtime_config.journal_size_limit, 10_000_000);
}
#[test]
fn test_with_low_memory_config() {
let store_config = SqliteStoreConfig::with_low_memory_config(Path::new("foo"));
assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical());
assert!(store_config.runtime_config.optimize);
assert_eq!(store_config.runtime_config.cache_size, 500_000);
assert_eq!(store_config.runtime_config.journal_size_limit, 2_000_000);
}
#[test]
fn test_store_config_when_passphrase() {
let store_config = SqliteStoreConfig::new(Path::new("foo"))
.passphrase(Some("bar"))
.pool_max_size(42)
.optimize(false)
.cache_size(43)
.journal_size_limit(44);
assert_eq!(store_config.path, PathBuf::from("foo"));
assert_eq!(store_config.secret, Some(Secret::PassPhrase("bar".to_owned().into())));
assert_eq!(store_config.pool_config.max_size, 42);
assert!(store_config.runtime_config.optimize.not());
assert_eq!(store_config.runtime_config.cache_size, 43);
assert_eq!(store_config.runtime_config.journal_size_limit, 44);
}
#[test]
fn test_store_config_when_key() {
let store_config = SqliteStoreConfig::new(Path::new("foo"))
.key(Some(&[
143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
]))
.pool_max_size(42)
.optimize(false)
.cache_size(43)
.journal_size_limit(44);
assert_eq!(store_config.path, PathBuf::from("foo"));
assert_eq!(
store_config.secret,
Some(Secret::Key(Box::new([
143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
])))
);
assert_eq!(store_config.pool_config.max_size, 42);
assert!(store_config.runtime_config.optimize.not());
assert_eq!(store_config.runtime_config.cache_size, 43);
assert_eq!(store_config.runtime_config.journal_size_limit, 44);
}
#[test]
fn test_store_config_path() {
let store_config = SqliteStoreConfig::new(Path::new("foo")).path(Path::new("bar"));
assert_eq!(store_config.path, PathBuf::from("bar"));
}
#[test]
fn test_pool_size_has_a_minimum() {
let store_config = SqliteStoreConfig::new(Path::new("foo")).pool_max_size(1);
assert_eq!(store_config.pool_config.max_size, POOL_MINIMUM_SIZE);
}
}