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 aaa6b961b881dde5a9d69f546a7ba35e1496a2b2
parent 00b073a1f582a3a19725e529a28006c85dbac0fe
6 files changed +196 -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::sqlite_ext::JournalMode::WAL)?
+
            .journal_mode(config.database.sqlite.pragma.journal_mode)?
+
            .synchronous(config.database.sqlite.pragma.synchronous)?
            .init(
                &id,
                announcement.features,
modified crates/radicle/CHANGELOG.md
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

+
- SQLite configuration is modeled as `radicle::node::db::config::Config`
+
  and can be configured via `radicle::profile::config::Config`.
+
  The two pragmas `journal_mode` and `synchronous` are exposed this way.
+

### Changed

### Removed
modified crates/radicle/src/node/config.rs
@@ -508,6 +508,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: node::db::config::Config,
    /// Extra fields that aren't supported.
    #[serde(flatten, skip_serializing)]
    pub extra: json::Map<String, json::Value>,
@@ -544,6 +547,7 @@ impl Config {
            workers: Workers::default(),
            log: LogLevel::default(),
            seeding_policy: DefaultSeedingPolicy::default(),
+
            database: node::db::config::Config::default(),
            extra: json::Map::default(),
            secret: None,
        }
modified crates/radicle/src/node/db.rs
@@ -20,6 +20,7 @@ use crate::node::{
};
use crate::sql::transaction;

+
pub mod config;
pub mod sqlite_ext;

/// How long to wait for the database lock to be released before failing a read.
added crates/radicle/src/node/db/config.rs
@@ -0,0 +1,183 @@
+
use super::sqlite_ext::*;
+

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

+
/// 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 {
+
    /// SQLite configuration.
+
    #[serde(skip_serializing_if = "crate::serde_ext::is_default")]
+
    pub sqlite: SqliteConfig,
+
}
+

+
/// 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 SqliteConfig {
+
    #[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 = [
+
            (None, None, JournalMode::WAL, Synchronous::FULL),
+
            (
+
                Some("WAL"),
+
                Some("NORMAL"),
+
                JournalMode::WAL,
+
                Synchronous::NORMAL,
+
            ),
+
            (
+
                Some("WAL"),
+
                Some("FULL"),
+
                JournalMode::WAL,
+
                Synchronous::FULL,
+
            ),
+
            (Some("WAL"), Some("OFF"), JournalMode::WAL, Synchronous::OFF),
+
            (
+
                Some("DELETE"),
+
                Some("FULL"),
+
                JournalMode::DELETE,
+
                Synchronous::FULL,
+
            ),
+
            (
+
                Some("DELETE"),
+
                Some("EXTRA"),
+
                JournalMode::DELETE,
+
                Synchronous::EXTRA,
+
            ),
+
            (
+
                Some("WAL"),
+
                Some("NORMAL"),
+
                JournalMode::WAL,
+
                Synchronous::NORMAL,
+
            ),
+
            (
+
                Some("DELETE"),
+
                Some("NORMAL"),
+
                JournalMode::DELETE,
+
                Synchronous::NORMAL,
+
            ),
+
        ];
+

+
        for (journal_mode, synchronous, expected_journal_mode, expected_synchronous) 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);
+
            }
+

+
            #[allow(clippy::unwrap_used)]
+
            let config: SqliteConfig = serde_json::from_value(config).unwrap();
+

+
            assert_eq!(
+
                config.pragma,
+
                Pragma {
+
                    journal_mode: expected_journal_mode,
+
                    synchronous: expected_synchronous
+
                }
+
            );
+
        }
+
    }
+

+
    #[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::sqlite_ext::JournalMode::WAL)?
+
            .journal_mode(config.node.database.sqlite.pragma.journal_mode)?
+
            .synchronous(config.node.database.sqlite.pragma.synchronous)?
            .init(
                &public_key,
                config.node.features(),