Skip to main content

systemprompt_database/services/
database.rs

1//! Top-level [`Database`] handle that owns one or two
2//! [`DatabaseProvider`] instances (read + optional write) and exposes the
3//! query and transaction surface.
4
5use super::postgres::PostgresProvider;
6use super::postgres::connection::PoolConfig;
7use super::provider::DatabaseProvider;
8use crate::error::{DatabaseResult, RepositoryError};
9use crate::models::{DatabaseInfo, QueryResult};
10use std::sync::Arc;
11
12pub struct Database {
13    provider: Arc<dyn DatabaseProvider>,
14    write_provider: Option<Arc<dyn DatabaseProvider>>,
15}
16
17impl std::fmt::Debug for Database {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        f.debug_struct("Database")
20            .field("backend", &"PostgreSQL")
21            .finish()
22    }
23}
24
25impl Database {
26    pub async fn new_postgres(url: &str) -> DatabaseResult<Self> {
27        let provider = PostgresProvider::new(url).await?;
28        Ok(Self {
29            provider: Arc::new(provider),
30            write_provider: None,
31        })
32    }
33
34    pub async fn from_config(db_type: &str, url: &str) -> DatabaseResult<Self> {
35        match db_type.to_lowercase().as_str() {
36            "postgres" | "postgresql" | "" => Self::new_postgres(url).await,
37            other => Err(RepositoryError::invalid_argument(format!(
38                "Unsupported database type: {other}. Only PostgreSQL is supported."
39            ))),
40        }
41    }
42
43    pub async fn from_config_with_write(
44        db_type: &str,
45        read_url: &str,
46        write_url: Option<&str>,
47        pool: &PoolConfig,
48    ) -> DatabaseResult<Self> {
49        let provider: Arc<dyn DatabaseProvider> = match db_type.to_lowercase().as_str() {
50            "postgres" | "postgresql" | "" => {
51                Arc::new(PostgresProvider::new_with_pool(read_url, pool).await?)
52            },
53            other => {
54                return Err(RepositoryError::invalid_argument(format!(
55                    "Unsupported database type: {other}. Only PostgreSQL is supported."
56                )));
57            },
58        };
59
60        let write_provider: Option<Arc<dyn DatabaseProvider>> = match write_url {
61            Some(url) => Some(Arc::new(PostgresProvider::new_with_pool(url, pool).await?)),
62            None => None,
63        };
64
65        Ok(Self {
66            provider,
67            write_provider,
68        })
69    }
70
71    #[must_use]
72    pub fn from_pools(read: Arc<sqlx::PgPool>, write: Option<Arc<sqlx::PgPool>>) -> Self {
73        let write_provider = write.map(|pool| -> Arc<dyn DatabaseProvider> {
74            Arc::new(PostgresProvider::from_pool(pool))
75        });
76        Self {
77            provider: Arc::new(PostgresProvider::from_pool(read)),
78            write_provider,
79        }
80    }
81
82    fn require_postgres(pool: Option<Arc<sqlx::PgPool>>) -> DatabaseResult<Arc<sqlx::PgPool>> {
83        pool.ok_or_else(|| RepositoryError::invalid_state("Database is not PostgreSQL"))
84    }
85
86    #[must_use]
87    pub fn read(&self) -> &dyn DatabaseProvider {
88        self.provider.as_ref()
89    }
90
91    #[must_use]
92    pub fn write(&self) -> &dyn DatabaseProvider {
93        self.write_provider
94            .as_deref()
95            .unwrap_or_else(|| self.provider.as_ref())
96    }
97
98    #[must_use]
99    pub fn pool(&self) -> Option<Arc<sqlx::PgPool>> {
100        self.read().get_postgres_pool()
101    }
102
103    pub fn pool_arc(&self) -> DatabaseResult<Arc<sqlx::PgPool>> {
104        Self::require_postgres(self.read().get_postgres_pool())
105    }
106
107    #[must_use]
108    pub fn write_pool(&self) -> Option<Arc<sqlx::PgPool>> {
109        self.write().get_postgres_pool()
110    }
111
112    pub fn write_pool_arc(&self) -> DatabaseResult<Arc<sqlx::PgPool>> {
113        Self::require_postgres(self.write().get_postgres_pool())
114    }
115
116    #[must_use]
117    pub fn has_write_pool(&self) -> bool {
118        self.write_provider.is_some()
119    }
120
121    pub async fn execute_batch(&self, sql: &str) -> DatabaseResult<()> {
122        self.write().execute_batch(sql).await
123    }
124
125    pub async fn get_info(&self) -> DatabaseResult<DatabaseInfo> {
126        self.read().get_database_info().await
127    }
128
129    pub async fn test_connection(&self) -> DatabaseResult<()> {
130        self.provider.test_connection().await?;
131        if let Some(wp) = &self.write_provider {
132            wp.test_connection().await?;
133        }
134        Ok(())
135    }
136
137    pub async fn begin(&self) -> DatabaseResult<sqlx::Transaction<'_, sqlx::Postgres>> {
138        let pool = self.write_pool_arc()?;
139        pool.begin().await.map_err(Into::into)
140    }
141}
142
143pub type DbPool = Arc<Database>;
144
145pub trait DatabaseExt {
146    fn database(&self) -> Arc<Database>;
147}
148
149impl DatabaseExt for Arc<Database> {
150    fn database(&self) -> Arc<Database> {
151        Self::clone(self)
152    }
153}
154
155#[async_trait::async_trait]
156impl DatabaseProvider for Database {
157    fn get_postgres_pool(&self) -> Option<Arc<sqlx::PgPool>> {
158        self.read().get_postgres_pool()
159    }
160
161    async fn execute(
162        &self,
163        query: &dyn crate::models::QuerySelector,
164        params: &[&dyn crate::models::ToDbValue],
165    ) -> DatabaseResult<u64> {
166        self.write().execute(query, params).await
167    }
168
169    async fn execute_raw(&self, sql: &str) -> DatabaseResult<()> {
170        self.write().execute_raw(sql).await
171    }
172
173    async fn fetch_all(
174        &self,
175        query: &dyn crate::models::QuerySelector,
176        params: &[&dyn crate::models::ToDbValue],
177    ) -> DatabaseResult<Vec<crate::models::JsonRow>> {
178        self.read().fetch_all(query, params).await
179    }
180
181    async fn fetch_one(
182        &self,
183        query: &dyn crate::models::QuerySelector,
184        params: &[&dyn crate::models::ToDbValue],
185    ) -> DatabaseResult<crate::models::JsonRow> {
186        self.read().fetch_one(query, params).await
187    }
188
189    async fn fetch_optional(
190        &self,
191        query: &dyn crate::models::QuerySelector,
192        params: &[&dyn crate::models::ToDbValue],
193    ) -> DatabaseResult<Option<crate::models::JsonRow>> {
194        self.read().fetch_optional(query, params).await
195    }
196
197    async fn fetch_scalar_value(
198        &self,
199        query: &dyn crate::models::QuerySelector,
200        params: &[&dyn crate::models::ToDbValue],
201    ) -> DatabaseResult<crate::models::DbValue> {
202        self.read().fetch_scalar_value(query, params).await
203    }
204
205    async fn begin_transaction(
206        &self,
207    ) -> DatabaseResult<Box<dyn crate::models::DatabaseTransaction>> {
208        self.write().begin_transaction().await
209    }
210
211    async fn get_database_info(&self) -> DatabaseResult<DatabaseInfo> {
212        self.read().get_database_info().await
213    }
214
215    async fn test_connection(&self) -> DatabaseResult<()> {
216        self.read().test_connection().await
217    }
218
219    async fn execute_batch(&self, sql: &str) -> DatabaseResult<()> {
220        self.write().execute_batch(sql).await
221    }
222
223    async fn query_raw(
224        &self,
225        query: &dyn crate::models::QuerySelector,
226    ) -> DatabaseResult<QueryResult> {
227        self.read().query_raw(query).await
228    }
229
230    async fn query_raw_with(
231        &self,
232        query: &dyn crate::models::QuerySelector,
233        params: &[&dyn crate::models::ToDbValue],
234    ) -> DatabaseResult<QueryResult> {
235        self.read().query_raw_with(query, params).await
236    }
237}