Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle: Make SQLite pragmas configurable
Yorgos Saslis committed 2 months ago
commit 915dc2ffb378999ebe447ddcff6002c26ea1ccbe
parent c0f0f15b73f08cdc33fd00166ac0acd9c686a158
4 files changed +190 -2
modified crates/radicle-node/src/runtime.rs
@@ -177,7 +177,8 @@ impl Runtime {
        log::info!(target: "node", "Opening node database..");
        let db = home
            .database_mut()?
-
            .journal_mode(node::db::JournalMode::WAL)?
+
            .journal_mode(config.database.sqlite.pragma.journal_mode)?
+
            .synchronous(config.database.sqlite.pragma.synchronous)?
            .init(
                &id,
                announcement.features,
modified crates/radicle/src/node/config.rs
@@ -1,3 +1,5 @@
+
mod sqlite;
+

use std::collections::HashSet;
use std::ops::Deref;
use std::str::FromStr;
@@ -459,6 +461,16 @@ impl From<DefaultSeedingPolicy> for SeedingPolicy {
    }
}

+
/// Database configuration.
+
#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)]
+
#[serde(rename_all = "camelCase")]
+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+
pub struct DatabaseConfig {
+
    /// SQLite configuration.
+
    #[serde(skip_serializing_if = "crate::serde_ext::is_default")]
+
    pub sqlite: sqlite::Config,
+
}
+

/// Service configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -508,6 +520,9 @@ pub struct Config {
    /// Default seeding policy.
    #[serde(default)]
    pub seeding_policy: DefaultSeedingPolicy,
+
    /// Database configuration.
+
    #[serde(default, skip_serializing_if = "crate::serde_ext::is_default")]
+
    pub database: DatabaseConfig,
    /// Extra fields that aren't supported.
    #[serde(flatten, skip_serializing)]
    pub extra: json::Map<String, json::Value>,
@@ -544,6 +559,7 @@ impl Config {
            workers: Workers::default(),
            log: LogLevel::default(),
            seeding_policy: DefaultSeedingPolicy::default(),
+
            database: DatabaseConfig::default(),
            extra: json::Map::default(),
            secret: None,
        }
added crates/radicle/src/node/config/sqlite.rs
@@ -0,0 +1,170 @@
+
use crate::node::db::*;
+

+
const DEFAULT_JOURNAL_MODE: JournalMode = JournalMode::WAL;
+
const DEFAULT_SYNCHRONOUS: Synchronous = Synchronous::FULL;
+

+
/// SQLite database configuration.
+
#[derive(Debug, Default, Copy, Clone, PartialEq, ::serde::Serialize, ::serde::Deserialize)]
+
#[serde(rename_all = "camelCase")]
+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+
pub struct Config {
+
    #[serde(default, skip_serializing_if = "crate::serde_ext::is_default")]
+
    pub pragma: Pragma,
+
}
+

+
/// Global SQLite pragma statements to make in order to configure SQLite itself,
+
/// see <https://sqlite.org/pragma.html>.
+
#[derive(Debug, Copy, Clone, PartialEq, ::serde::Serialize, ::serde::Deserialize)]
+
#[serde(rename_all = "camelCase")]
+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+
pub struct Pragma {
+
    #[serde(
+
        default = "serde::journal_mode::default",
+
        skip_serializing_if = "serde::journal_mode::is_default"
+
    )]
+
    pub journal_mode: JournalMode,
+

+
    #[serde(
+
        default = "serde::synchronous::default",
+
        skip_serializing_if = "serde::synchronous::is_default"
+
    )]
+
    pub synchronous: Synchronous,
+
}
+

+
pub mod serde {
+
    use super::*;
+

+
    pub mod journal_mode {
+
        use super::*;
+

+
        pub fn default() -> JournalMode {
+
            DEFAULT_JOURNAL_MODE
+
        }
+

+
        pub fn is_default(journal_mode: &JournalMode) -> bool {
+
            matches!(journal_mode, &DEFAULT_JOURNAL_MODE)
+
        }
+
    }
+

+
    pub mod synchronous {
+
        use super::*;
+

+
        pub fn default() -> Synchronous {
+
            DEFAULT_SYNCHRONOUS
+
        }
+

+
        pub fn is_default(synchronous: &Synchronous) -> bool {
+
            matches!(synchronous, &DEFAULT_SYNCHRONOUS)
+
        }
+
    }
+
}
+

+
impl Default for Pragma {
+
    fn default() -> Self {
+
        Self {
+
            journal_mode: DEFAULT_JOURNAL_MODE,
+
            synchronous: DEFAULT_SYNCHRONOUS,
+
        }
+
    }
+
}
+

+
#[cfg(test)]
+
mod test {
+
    use crate::assert_matches;
+

+
    use super::*;
+
    use serde_json::json;
+

+
    #[test]
+
    fn database_config_valid_combinations() {
+
        let cases = [
+
            // (journal_mode, synchronous, expected_journal, expected_sync, description)
+
            (None, None, JournalMode::WAL, Synchronous::FULL, "defaults"),
+
            (
+
                Some("WAL"),
+
                Some("NORMAL"),
+
                JournalMode::WAL,
+
                Synchronous::NORMAL,
+
                "WAL+NORMAL (recommended)",
+
            ),
+
            (
+
                Some("WAL"),
+
                Some("FULL"),
+
                JournalMode::WAL,
+
                Synchronous::FULL,
+
                "WAL+FULL (max durability)",
+
            ),
+
            (
+
                Some("WAL"),
+
                Some("OFF"),
+
                JournalMode::WAL,
+
                Synchronous::OFF,
+
                "WAL+OFF (max performance)",
+
            ),
+
            (
+
                Some("DELETE"),
+
                Some("FULL"),
+
                JournalMode::DELETE,
+
                Synchronous::FULL,
+
                "DELETE+FULL",
+
            ),
+
            (
+
                Some("DELETE"),
+
                Some("EXTRA"),
+
                JournalMode::DELETE,
+
                Synchronous::EXTRA,
+
                "DELETE+EXTRA (max durability)",
+
            ),
+
        ];
+

+
        for (journal_mode, synchronous, expected_journal, expected_sync, description) in cases {
+
            let mut config = json!({});
+

+
            if let Some(journal_mode) = journal_mode {
+
                config["pragma"]["journalMode"] = json!(journal_mode);
+
            }
+

+
            if let Some(synchronous) = synchronous {
+
                config["pragma"]["synchronous"] = json!(synchronous);
+
            }
+

+
            let config: Config = serde_json::from_value(config).unwrap();
+

+
            assert_eq!(
+
                config.pragma.journal_mode, expected_journal,
+
                "{}",
+
                description
+
            );
+
            assert_eq!(config.pragma.synchronous, expected_sync, "{}", description);
+
        }
+
    }
+

+
    #[test]
+
    fn invalid() {
+
        let invalid_cases = [
+
            (Some("INVALID"), Some("NORMAL"), "invalid journal_mode"),
+
            (Some("WAL"), Some("INVALID"), "invalid synchronous"),
+
            (Some("WAL"), Some("normal"), "lowercase synchronous"),
+
            (Some("Wal"), Some("NORMAL"), "mixed case journal_mode"),
+
        ];
+

+
        for (journal_mode, synchronous, description) in invalid_cases {
+
            let mut pragma = json!({});
+

+
            if let Some(journal_mode) = journal_mode {
+
                pragma["journalMode"] = json!(journal_mode);
+
            }
+

+
            if let Some(synchronous) = synchronous {
+
                pragma["synchronous"] = json!(synchronous);
+
            }
+

+
            assert_matches!(
+
                serde_json::from_value::<Pragma>(pragma),
+
                Err(_),
+
                "{}",
+
                description,
+
            );
+
        }
+
    }
+
}
modified crates/radicle/src/profile.rs
@@ -253,7 +253,8 @@ impl Profile {
        home.policies_mut()?;
        home.notifications_mut()?;
        home.database_mut()?
-
            .journal_mode(node::db::JournalMode::WAL)?
+
            .journal_mode(config.node.database.sqlite.pragma.journal_mode)?
+
            .synchronous(config.node.database.sqlite.pragma.synchronous)?
            .init(
                &public_key,
                config.node.features(),