use anyhow::Context;
use postgres::{Client, NoTls};
pub fn connect_readonly(database_url: &str) -> anyhow::Result<Client> {
let mut client = connect(database_url)?;
client
.execute("SET default_transaction_read_only = on", &[])
.context("failed to set PostgreSQL connection read-only")?;
Ok(client)
}
pub fn connect_readwrite(database_url: &str) -> anyhow::Result<Client> {
connect(database_url)
}
pub fn read_config_value(conn: &mut Client, key: &str) -> anyhow::Result<Option<String>> {
let row = conn
.query_opt("SELECT value FROM config_store WHERE key = $1", &[&key])
.with_context(|| format!("failed to read config_store key {key:?}"))?;
row.map(|r| {
r.try_get("value")
.with_context(|| format!("config_store key {key:?} value was not text"))
})
.transpose()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaCheck {
pub object_name: String,
pub check_kind: String,
pub passed: bool,
pub detail: Option<String>,
}
pub fn validate_schema(
conn: &mut Client,
validator: impl FnOnce(&mut Client) -> Vec<SchemaCheck>,
) -> Vec<SchemaCheck> {
run_schema_validator(conn, validator)
}
fn connect(database_url: &str) -> anyhow::Result<Client> {
Client::connect(database_url, NoTls).context("failed to connect to the Gobby PostgreSQL hub")
}
fn run_schema_validator<C>(
conn: &mut C,
validator: impl FnOnce(&mut C) -> Vec<SchemaCheck>,
) -> Vec<SchemaCheck> {
validator(conn)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attached_validation_is_non_destructive() {
let mut conn = vec!["existing-state"];
let checks = run_schema_validator(&mut conn, |conn| {
assert_eq!(conn.as_slice(), ["existing-state"]);
conn.push("validator-ran");
vec![SchemaCheck {
object_name: "consumer_table".to_string(),
check_kind: "table exists".to_string(),
passed: true,
detail: None,
}]
});
assert_eq!(conn, vec!["existing-state", "validator-ran"]);
assert_eq!(checks.len(), 1);
assert_eq!(checks[0].object_name, "consumer_table");
assert!(checks[0].passed);
}
#[test]
fn schema_validator_is_domain_supplied() {
let mut domain_objects = ["domain_symbols", "domain_bm25_idx"].into_iter();
let checks = run_schema_validator(&mut domain_objects, |objects| {
objects
.map(|object_name| SchemaCheck {
object_name: object_name.to_string(),
check_kind: "consumer supplied".to_string(),
passed: true,
detail: None,
})
.collect::<Vec<_>>()
});
assert_eq!(
checks
.iter()
.map(|check| check.object_name.as_str())
.collect::<Vec<_>>(),
vec!["domain_symbols", "domain_bm25_idx"]
);
}
#[test]
fn validate_schema_accepts_postgres_client_validators() {
let _validate: fn(&mut Client, fn(&mut Client) -> Vec<SchemaCheck>) -> Vec<SchemaCheck> =
validate_schema;
}
}