Skip to main content

sbol_db_postgres/repo/
config.rs

1//! Durable instance configuration over Postgres.
2//!
3//! Entries live in `sbh_app_config`, a flat key to `jsonb` value table.
4//! [`set`](ConfigStore::set) upserts on the primary key, so a later write to a
5//! key overwrites its value and refreshes `updated_at`.
6
7use async_trait::async_trait;
8use sbol_db_core::{ConfigEntry, DomainError};
9use sbol_db_storage::ConfigStore;
10use serde_json::Value;
11use sqlx::Row;
12
13use crate::repo::db_err;
14use crate::PgPool;
15
16#[derive(Clone)]
17pub struct PgConfigStore {
18    pool: PgPool,
19}
20
21impl PgConfigStore {
22    pub fn new(pool: PgPool) -> Self {
23        Self { pool }
24    }
25}
26
27#[async_trait]
28impl ConfigStore for PgConfigStore {
29    async fn get(&self, key: &str) -> Result<Option<Value>, DomainError> {
30        let row = sqlx::query("SELECT value FROM sbh_app_config WHERE key = $1")
31            .bind(key)
32            .fetch_optional(&self.pool)
33            .await
34            .map_err(db_err)?;
35        match row {
36            Some(r) => Ok(Some(r.try_get::<Value, _>("value").map_err(db_err)?)),
37            None => Ok(None),
38        }
39    }
40
41    async fn set(&self, key: &str, value: &Value) -> Result<(), DomainError> {
42        sqlx::query(
43            "INSERT INTO sbh_app_config (key, value, updated_at) VALUES ($1, $2, now()) \
44             ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()",
45        )
46        .bind(key)
47        .bind(value)
48        .execute(&self.pool)
49        .await
50        .map_err(db_err)?;
51        Ok(())
52    }
53
54    async fn get_all(&self) -> Result<Vec<ConfigEntry>, DomainError> {
55        let rows = sqlx::query("SELECT key, value, updated_at FROM sbh_app_config")
56            .fetch_all(&self.pool)
57            .await
58            .map_err(db_err)?;
59        let mut out = Vec::with_capacity(rows.len());
60        for r in rows {
61            out.push(ConfigEntry {
62                key: r.try_get("key").map_err(db_err)?,
63                value: r.try_get("value").map_err(db_err)?,
64                updated_at: r.try_get("updated_at").map_err(db_err)?,
65            });
66        }
67        Ok(out)
68    }
69
70    async fn delete(&self, key: &str) -> Result<(), DomainError> {
71        sqlx::query("DELETE FROM sbh_app_config WHERE key = $1")
72            .bind(key)
73            .execute(&self.pool)
74            .await
75            .map_err(db_err)?;
76        Ok(())
77    }
78}