Skip to main content

systemprompt_database/services/
database.rs

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