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"
|
"default": "block"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"database": {
|
||||||
|
"description": "Database configuration.",
|
||||||
|
"$ref": "#/$defs/Config"
|
||||||
|
},
|
||||||
"secret": {
|
"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`.",
|
"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": [
|
"type": [
|
||||||
|
|
@ -660,6 +664,62 @@ $ rad config schema
|
||||||
"const": "all"
|
"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..");
|
log::info!(target: "node", "Opening node database..");
|
||||||
let db = home
|
let db = home
|
||||||
.database_mut()?
|
.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(
|
.init(
|
||||||
&id,
|
&id,
|
||||||
announcement.features,
|
announcement.features,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### 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
|
### Changed
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,9 @@ pub struct Config {
|
||||||
/// Default seeding policy.
|
/// Default seeding policy.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub seeding_policy: DefaultSeedingPolicy,
|
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.
|
/// Extra fields that aren't supported.
|
||||||
#[serde(flatten, skip_serializing)]
|
#[serde(flatten, skip_serializing)]
|
||||||
pub extra: json::Map<String, json::Value>,
|
pub extra: json::Map<String, json::Value>,
|
||||||
|
|
@ -545,6 +548,7 @@ impl Config {
|
||||||
workers: Workers::default(),
|
workers: Workers::default(),
|
||||||
log: LogLevel::default(),
|
log: LogLevel::default(),
|
||||||
seeding_policy: DefaultSeedingPolicy::default(),
|
seeding_policy: DefaultSeedingPolicy::default(),
|
||||||
|
database: node::db::config::Config::default(),
|
||||||
extra: json::Map::default(),
|
extra: json::Map::default(),
|
||||||
secret: None,
|
secret: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use crate::node::{
|
||||||
};
|
};
|
||||||
use crate::sql::transaction;
|
use crate::sql::transaction;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
pub mod sqlite_ext;
|
pub mod sqlite_ext;
|
||||||
|
|
||||||
/// How long to wait for the database lock to be released before failing a read.
|
/// 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.policies_mut()?;
|
||||||
home.notifications_mut()?;
|
home.notifications_mut()?;
|
||||||
home.database_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(
|
.init(
|
||||||
&public_key,
|
&public_key,
|
||||||
config.node.features(),
|
config.node.features(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue