Files
Stalwart/crates/store/src/config.rs

349 lines
14 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::Arc;
use utils::config::{cron::SimpleCron, utils::ParseValue, Config};
use crate::{
backend::fs::FsStore,
write::purge::{PurgeSchedule, PurgeStore},
BlobStore, CompressionAlgo, FtsStore, LookupStore, QueryStore, Store, Stores,
};
#[cfg(feature = "s3")]
use crate::backend::s3::S3Store;
#[cfg(feature = "postgres")]
use crate::backend::postgres::PostgresStore;
#[cfg(feature = "mysql")]
use crate::backend::mysql::MysqlStore;
#[cfg(feature = "sqlite")]
use crate::backend::sqlite::SqliteStore;
#[cfg(feature = "foundation")]
use crate::backend::foundationdb::FdbStore;
#[cfg(feature = "rocks")]
use crate::backend::rocksdb::RocksDbStore;
#[cfg(feature = "elastic")]
use crate::backend::elastic::ElasticSearchStore;
#[cfg(feature = "redis")]
use crate::backend::redis::RedisStore;
impl Stores {
pub async fn parse_all(config: &mut Config) -> Self {
let mut stores = Self::parse(config).await;
stores.parse_lookups(config).await;
stores
}
pub async fn parse(config: &mut Config) -> Self {
let mut stores = Self::default();
stores.parse_stores(config).await;
stores
}
pub async fn parse_stores(&mut self, config: &mut Config) {
let is_reload = !self.stores.is_empty();
#[cfg(feature = "enterprise")]
let mut composite_stores = Vec::new();
let store_ids = config
.sub_keys("store", ".type")
.map(|id| id.to_string())
.collect::<Vec<_>>();
for id in store_ids {
let id = id.as_str();
// Parse store
#[cfg(feature = "test_mode")]
{
if config
.property_or_default::<bool>(("store", id, "disable"), "false")
.unwrap_or(false)
{
continue;
}
}
let protocol = if let Some(protocol) = config.value_require(("store", id, "type")) {
protocol.to_ascii_lowercase()
} else {
continue;
};
let prefix = ("store", id);
let store_id = id.to_string();
let compression_algo = config
.property_or_default::<CompressionAlgo>(("store", id, "compression"), "none")
.unwrap_or(CompressionAlgo::None);
match protocol.as_str() {
#[cfg(feature = "rocks")]
"rocksdb" => {
// Avoid opening the same store twice
if is_reload
&& self
.stores
.values()
.any(|store| matches!(store, Store::RocksDb(_)))
{
continue;
}
if let Some(db) = RocksDbStore::open(config, prefix).await.map(Store::from) {
self.stores.insert(store_id.clone(), db.clone());
self.fts_stores.insert(store_id.clone(), db.clone().into());
self.blob_stores.insert(
store_id.clone(),
BlobStore::from(db.clone()).with_compression(compression_algo),
);
self.lookup_stores.insert(store_id, db.into());
}
}
#[cfg(feature = "foundation")]
"foundationdb" => {
// Avoid opening the same store twice
if is_reload
&& self
.stores
.values()
.any(|store| matches!(store, Store::FoundationDb(_)))
{
continue;
}
if let Some(db) = FdbStore::open(config, prefix).await.map(Store::from) {
self.stores.insert(store_id.clone(), db.clone());
self.fts_stores.insert(store_id.clone(), db.clone().into());
self.blob_stores.insert(
store_id.clone(),
BlobStore::from(db.clone()).with_compression(compression_algo),
);
self.lookup_stores.insert(store_id, db.into());
}
}
#[cfg(feature = "postgres")]
"postgresql" => {
if let Some(db) = PostgresStore::open(config, prefix).await.map(Store::from) {
self.stores.insert(store_id.clone(), db.clone());
self.fts_stores.insert(store_id.clone(), db.clone().into());
self.blob_stores.insert(
store_id.clone(),
BlobStore::from(db.clone()).with_compression(compression_algo),
);
self.lookup_stores.insert(store_id.clone(), db.into());
}
}
#[cfg(feature = "mysql")]
"mysql" => {
if let Some(db) = MysqlStore::open(config, prefix).await.map(Store::from) {
self.stores.insert(store_id.clone(), db.clone());
self.fts_stores.insert(store_id.clone(), db.clone().into());
self.blob_stores.insert(
store_id.clone(),
BlobStore::from(db.clone()).with_compression(compression_algo),
);
self.lookup_stores.insert(store_id.clone(), db.into());
}
}
#[cfg(feature = "sqlite")]
"sqlite" => {
// Avoid opening the same store twice
if is_reload
&& self
.stores
.values()
.any(|store| matches!(store, Store::SQLite(_)))
{
continue;
}
if let Some(db) = SqliteStore::open(config, prefix).map(Store::from) {
self.stores.insert(store_id.clone(), db.clone());
self.fts_stores.insert(store_id.clone(), db.clone().into());
self.blob_stores.insert(
store_id.clone(),
BlobStore::from(db.clone()).with_compression(compression_algo),
);
self.lookup_stores.insert(store_id.clone(), db.into());
}
}
"fs" => {
if let Some(db) = FsStore::open(config, prefix).await.map(BlobStore::from) {
self.blob_stores
.insert(store_id, db.with_compression(compression_algo));
}
}
#[cfg(feature = "s3")]
"s3" => {
if let Some(db) = S3Store::open(config, prefix).await.map(BlobStore::from) {
self.blob_stores
.insert(store_id, db.with_compression(compression_algo));
}
}
#[cfg(feature = "elastic")]
"elasticsearch" => {
if let Some(db) = ElasticSearchStore::open(config, prefix)
.await
.map(FtsStore::from)
{
self.fts_stores.insert(store_id, db);
}
}
#[cfg(feature = "redis")]
"redis" => {
if let Some(db) = RedisStore::open(config, prefix)
.await
.map(LookupStore::from)
{
self.lookup_stores.insert(store_id, db);
}
}
#[cfg(feature = "enterprise")]
"composite-read" | "composite-blob" => {
composite_stores.push((store_id, protocol));
}
unknown => {
config.new_parse_warning(
("store", id, "type"),
format!("Unknown directory type: {unknown:?}"),
);
}
}
}
#[cfg(feature = "enterprise")]
for (id, protocol) in composite_stores {
let prefix = ("store", id.as_str());
match protocol.as_str() {
"composite-read" => {
if let Some(db) = crate::backend::composite::read_replica::SQLReadReplica::open(
config, prefix, self,
) {
self.stores.insert(id, Store::SQLReadReplica(db.into()));
}
}
"composite-blob" => {
if let Some(db) =
crate::backend::composite::distributed_blob::CompositeBlob::open(
config, prefix, self,
)
{
let store = BlobStore {
backend: crate::BlobBackend::Composite(db.into()),
compression: config
.property_or_default::<CompressionAlgo>(
("store", id.as_str(), "compression"),
"none",
)
.unwrap_or(CompressionAlgo::None),
};
self.blob_stores.insert(id, store);
}
}
_ => (),
}
}
}
pub async fn parse_lookups(&mut self, config: &mut Config) {
// Parse memory stores
self.parse_memory_stores(config);
// Add SQL queries as lookup stores
for (store_id, lookup_store) in self.stores.iter().filter_map(|(id, store)| {
if store.is_sql() {
Some((id.clone(), LookupStore::from(store.clone())))
} else {
None
}
}) {
// Add queries as lookup stores
for lookup_id in config.sub_keys(("store", store_id.as_str(), "query"), "") {
if let Some(query) = config.value(("store", store_id.as_str(), "query", lookup_id))
{
self.lookup_stores.insert(
format!("{store_id}/{lookup_id}"),
LookupStore::Query(Arc::new(QueryStore {
store: lookup_store.clone(),
query: query.to_string(),
})),
);
}
}
// Run init queries on database
for query in config
.values(("store", store_id.as_str(), "init.execute"))
.map(|(_, s)| s.to_string())
.collect::<Vec<_>>()
{
if let Err(err) = lookup_store.query::<usize>(&query, Vec::new()).await {
config.new_build_error(
("store", store_id.as_str()),
format!("Failed to initialize store: {err}"),
);
}
}
}
// Parse purge schedules
if let Some(store) = config
.value("storage.data")
.and_then(|store_id| self.stores.get(store_id))
{
let store_id = config.value("storage.data").unwrap().to_string();
self.purge_schedules.push(PurgeSchedule {
cron: config
.property_or_default::<SimpleCron>(
("store", store_id.as_str(), "purge.frequency"),
"0 3 *",
)
.unwrap_or_else(|| SimpleCron::parse_value("0 3 *").unwrap()),
store_id,
store: PurgeStore::Data(store.clone()),
});
if let Some(blob_store) = config
.value("storage.blob")
.and_then(|blob_store_id| self.blob_stores.get(blob_store_id))
{
let store_id = config.value("storage.blob").unwrap().to_string();
self.purge_schedules.push(PurgeSchedule {
cron: config
.property_or_default::<SimpleCron>(
("store", store_id.as_str(), "purge.frequency"),
"0 4 *",
)
.unwrap_or_else(|| SimpleCron::parse_value("0 4 *").unwrap()),
store_id,
store: PurgeStore::Blobs {
store: store.clone(),
blob_store: blob_store.clone(),
},
});
}
}
for (store_id, store) in &self.lookup_stores {
if matches!(store, LookupStore::Store(_)) {
self.purge_schedules.push(PurgeSchedule {
cron: config
.property_or_default::<SimpleCron>(
("store", store_id.as_str(), "purge.frequency"),
"0 5 *",
)
.unwrap_or_else(|| SimpleCron::parse_value("0 5 *").unwrap()),
store_id: store_id.clone(),
store: PurgeStore::Lookup(store.clone()),
});
}
}
}
}