radicle: Make SQLite pragmas configurable
Expose choosing particular SQLite pragmas via configuration.
This commit is contained in:
parent
6cc3da95e6
commit
f4aee203ab
|
|
@ -311,6 +311,10 @@ $ rad config schema
|
|||
"default": "block"
|
||||
}
|
||||
},
|
||||
"database": {
|
||||
"description": "Database configuration.",
|
||||
"$ref": "#/$defs/Config"
|
||||
},
|
||||
"secret": {
|
||||
"description": "Path to a file containing an Ed25519 secret key, in OpenSSH format, i.e./nwith the `-----BEGIN OPENSSH PRIVATE KEY-----` header. The corresponding/npublic key will be used as the Node ID./n/nA decryption password cannot be configured, but passed at runtime via/nthe environment variable `RAD_PASSPHRASE`.",
|
||||
"type": [
|
||||
|
|
@ -660,6 +664,62 @@ $ rad config schema
|
|||
"const": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Config": {
|
||||
"description": "Database configuration.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sqlite": {
|
||||
"description": "SQLite configuration.",
|
||||
"$ref": "#/$defs/SqliteConfig"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sqlite"
|
||||
]
|
||||
},
|
||||
"SqliteConfig": {
|
||||
"description": "SQLite database configuration.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pragma": {
|
||||
"$ref": "#/$defs/Pragma"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pragma": {
|
||||
"description": "Global SQLite pragma statements to make in order to configure SQLite itself,/nsee <https://sqlite.org/pragma.html>.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"journalMode": {
|
||||
"$ref": "#/$defs/JournalMode"
|
||||
},
|
||||
"synchronous": {
|
||||
"$ref": "#/$defs/Synchronous"
|
||||
}
|
||||
}
|
||||
},
|
||||
"JournalMode": {
|
||||
"description": "Value for a `journal_mode` pragma statement./nFor a description of all variants please refer to/n<https://sqlite.org/pragma.html#pragma_journal_mode>./nNote that when SQLite documentation talks about /"the application/",/nthe application linked against this crate, e.g. Radicle Node, Radicle CLI,/nand others, is meant.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"DELETE",
|
||||
"TRUNCATE",
|
||||
"PERSIST",
|
||||
"MEMORY",
|
||||
"WAL",
|
||||
"OFF"
|
||||
]
|
||||
},
|
||||
"Synchronous": {
|
||||
"description": "Value for a `synchronous` pragma statement./nFor a description of all variants please refer to/n<https://sqlite.org/pragma.html#pragma_synchronous>.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"EXTRA",
|
||||
"FULL",
|
||||
"NORMAL",
|
||||
"OFF"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -509,6 +509,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>,
|
||||
|
|
@ -545,6 +548,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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue