systemprompt_database/lifecycle/
validation.rs1use crate::error::{DatabaseResult, RepositoryError};
7use crate::services::{Database, DatabaseProvider};
8
9pub async fn validate_database_connection(db: &dyn DatabaseProvider) -> DatabaseResult<()> {
10 db.test_connection().await.map_err(|e| {
11 RepositoryError::Internal(format!("Failed to establish database connection: {e}"))
12 })
13}
14
15pub async fn validate_write_pool_is_primary(db: &Database) -> DatabaseResult<()> {
16 if !db.write().is_postgres() {
17 return Ok(());
18 }
19
20 let result = db
21 .write()
22 .query_raw(&"SELECT pg_is_in_recovery() as in_recovery")
23 .await?;
24
25 let in_recovery = result
26 .first()
27 .and_then(|row| row.get("in_recovery"))
28 .and_then(serde_json::Value::as_bool)
29 .ok_or_else(|| {
30 RepositoryError::Internal(
31 "Failed to determine whether the write pool is a primary".to_owned(),
32 )
33 })?;
34
35 if !in_recovery {
36 return Ok(());
37 }
38
39 Err(RepositoryError::invalid_state(if db.has_write_pool() {
40 "`database_write_url` points at a read-only standby. Writes, migrations and \
41 LISTEN/NOTIFY all require the primary — point it at the primary and restart"
42 } else {
43 "`database_url` points at a read-only standby and no `database_write_url` is set, so \
44 the write pool falls back to it. Set `database_write_url` (or `DATABASE_WRITE_URL` \
45 with the env secrets source) to the primary and restart"
46 }))
47}
48
49pub async fn validate_table_exists(
50 db: &dyn DatabaseProvider,
51 table_name: &str,
52) -> DatabaseResult<bool> {
53 let result = db
54 .query_raw_with(
55 &"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = \
56 'public' AND table_name = $1) as exists",
57 &[&table_name],
58 )
59 .await?;
60
61 result
62 .first()
63 .and_then(|row| row.get("exists"))
64 .and_then(serde_json::Value::as_bool)
65 .ok_or_else(|| {
66 RepositoryError::Internal(format!(
67 "Failed to check table existence for '{table_name}'"
68 ))
69 })
70}
71
72pub async fn validate_column_exists(
73 db: &dyn DatabaseProvider,
74 table_name: &str,
75 column_name: &str,
76) -> DatabaseResult<bool> {
77 let result = db
78 .query_raw_with(
79 &"SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = \
80 'public' AND table_name = $1 AND column_name = $2) as exists",
81 &[&table_name, &column_name],
82 )
83 .await?;
84
85 result
86 .first()
87 .and_then(|row| row.get("exists"))
88 .and_then(serde_json::Value::as_bool)
89 .ok_or_else(|| {
90 RepositoryError::Internal(format!(
91 "Failed to check column existence for '{table_name}.{column_name}'"
92 ))
93 })
94}