systemprompt_database/services/postgres/
mod.rs1pub mod connection;
10pub mod conversion;
11mod ext;
12mod introspection;
13pub mod transaction;
14
15use async_trait::async_trait;
16use sqlx::Executor;
17use sqlx::postgres::{PgConnectOptions, PgPool, PgSslMode};
18use std::str::FromStr;
19use std::sync::Arc;
20
21use super::provider::DatabaseProvider;
22use crate::error::{DatabaseResult, RepositoryError};
23use crate::models::{
24 DatabaseInfo, DatabaseTransaction, DbValue, JsonRow, QueryResult, QuerySelector, ToDbValue,
25};
26use conversion::{bind_params, row_to_json, rows_to_result};
27use transaction::PostgresTransaction;
28
29#[derive(Debug)]
30pub struct PostgresProvider {
31 pool: Arc<PgPool>,
32}
33
34impl PostgresProvider {
35 pub async fn new(database_url: &str) -> DatabaseResult<Self> {
36 Self::new_with_pool(database_url, &connection::PoolConfig::default()).await
37 }
38
39 pub async fn new_with_pool(
40 database_url: &str,
41 pool_config: &connection::PoolConfig,
42 ) -> DatabaseResult<Self> {
43 let mut connect_options = PgConnectOptions::from_str(database_url)?;
44
45 let ssl_mode = if database_url.contains("sslmode=require") {
46 PgSslMode::Require
47 } else if database_url.contains("sslmode=disable") {
48 PgSslMode::Disable
49 } else {
50 PgSslMode::Prefer
51 };
52
53 connect_options = connect_options
54 .application_name("systemprompt")
55 .statement_cache_capacity(0)
56 .ssl_mode(ssl_mode)
57 .options([("client_min_messages", "warning")]);
58
59 if let Some(ca_cert_path) = Self::get_cert_path() {
60 connect_options = connect_options.ssl_root_cert(&ca_cert_path);
61 }
62
63 let pool = connection::connect_with_retry(
64 connection::build_pool_options(pool_config),
65 connect_options,
66 )
67 .await?;
68
69 Ok(Self {
70 pool: Arc::new(pool),
71 })
72 }
73
74 #[must_use]
75 pub const fn from_pool(pool: Arc<PgPool>) -> Self {
76 Self { pool }
77 }
78
79 fn get_cert_path() -> Option<std::path::PathBuf> {
80 std::env::var("PGCA_CERT_PATH")
81 .ok()
82 .map(std::path::PathBuf::from)
83 }
84
85 #[must_use]
86 pub fn pool(&self) -> &PgPool {
87 &self.pool
88 }
89}
90
91#[async_trait]
92impl DatabaseProvider for PostgresProvider {
93 fn get_postgres_pool(&self) -> Option<Arc<PgPool>> {
94 Some(Arc::clone(&self.pool))
95 }
96
97 async fn execute(
98 &self,
99 query: &dyn QuerySelector,
100 params: &[&dyn ToDbValue],
101 ) -> DatabaseResult<u64> {
102 let sql = query.select_query();
103 let query_obj = sqlx::query(sqlx::AssertSqlSafe(sql));
104 let query_obj = bind_params(query_obj, params);
105
106 let result = query_obj.execute(&*self.pool).await?;
107
108 Ok(result.rows_affected())
109 }
110
111 async fn execute_raw(&self, sql: &str) -> DatabaseResult<()> {
112 let mut conn = self.pool.acquire().await?;
113
114 conn.execute(sqlx::AssertSqlSafe(sql.to_owned())).await?;
115
116 Ok(())
117 }
118
119 async fn fetch_all(
120 &self,
121 query: &dyn QuerySelector,
122 params: &[&dyn ToDbValue],
123 ) -> DatabaseResult<Vec<JsonRow>> {
124 let sql = query.select_query();
125 let query_obj = sqlx::query(sqlx::AssertSqlSafe(sql));
126 let query_obj = bind_params(query_obj, params);
127
128 let rows = query_obj.fetch_all(&*self.pool).await?;
129
130 Ok(rows.iter().map(row_to_json).collect())
131 }
132
133 async fn fetch_one(
134 &self,
135 query: &dyn QuerySelector,
136 params: &[&dyn ToDbValue],
137 ) -> DatabaseResult<JsonRow> {
138 let sql = query.select_query();
139 let query_obj = sqlx::query(sqlx::AssertSqlSafe(sql));
140 let query_obj = bind_params(query_obj, params);
141
142 let row = query_obj.fetch_one(&*self.pool).await?;
143
144 Ok(row_to_json(&row))
145 }
146
147 async fn fetch_optional(
148 &self,
149 query: &dyn QuerySelector,
150 params: &[&dyn ToDbValue],
151 ) -> DatabaseResult<Option<JsonRow>> {
152 let sql = query.select_query();
153 let query_obj = sqlx::query(sqlx::AssertSqlSafe(sql));
154 let query_obj = bind_params(query_obj, params);
155
156 let row = query_obj.fetch_optional(&*self.pool).await?;
157
158 Ok(row.map(|r| row_to_json(&r)))
159 }
160
161 async fn fetch_scalar_value(
162 &self,
163 query: &dyn QuerySelector,
164 params: &[&dyn ToDbValue],
165 ) -> DatabaseResult<DbValue> {
166 let row = self.fetch_one(query, params).await?;
167
168 let first_value = row
169 .values()
170 .next()
171 .ok_or_else(|| RepositoryError::invalid_state("No columns in result"))?;
172
173 let db_value = match first_value {
174 serde_json::Value::String(s) => DbValue::String(s.clone()),
175 serde_json::Value::Number(n) => n
176 .as_i64()
177 .map(DbValue::Int)
178 .or_else(|| n.as_f64().map(DbValue::Float))
179 .unwrap_or(DbValue::NullFloat),
180 serde_json::Value::Bool(b) => DbValue::Bool(*b),
181 serde_json::Value::Null => DbValue::NullString,
182 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
183 return Err(RepositoryError::invalid_state("Unsupported value type"));
184 },
185 };
186
187 Ok(db_value)
188 }
189
190 async fn begin_transaction(&self) -> DatabaseResult<Box<dyn DatabaseTransaction>> {
191 let tx = self.pool.begin().await?;
192
193 Ok(Box::new(PostgresTransaction::new(tx)))
194 }
195
196 async fn get_database_info(&self) -> DatabaseResult<DatabaseInfo> {
197 introspection::get_database_info(&self.pool).await
198 }
199
200 async fn test_connection(&self) -> DatabaseResult<()> {
201 sqlx::query("SELECT 1").fetch_one(&*self.pool).await?;
202 Ok(())
203 }
204
205 async fn execute_batch(&self, sql: &str) -> DatabaseResult<()> {
206 let statements = crate::services::SqlExecutor::parse_sql_statements(sql)?;
207 for statement in statements {
208 sqlx::query(sqlx::AssertSqlSafe(statement))
209 .execute(&*self.pool)
210 .await?;
211 }
212 Ok(())
213 }
214
215 async fn query_raw(&self, query: &dyn QuerySelector) -> DatabaseResult<QueryResult> {
216 let sql = query.select_query();
217 let start = std::time::Instant::now();
218
219 let rows = sqlx::query(sqlx::AssertSqlSafe(sql))
220 .fetch_all(&*self.pool)
221 .await?;
222
223 Ok(rows_to_result(rows, start))
224 }
225
226 async fn query_raw_with(
227 &self,
228 query: &dyn QuerySelector,
229 params: &[&dyn ToDbValue],
230 ) -> DatabaseResult<QueryResult> {
231 let sql = query.select_query();
232 let start = std::time::Instant::now();
233
234 let query_obj = bind_params(sqlx::query(sqlx::AssertSqlSafe(sql)), params);
235 let rows = query_obj.fetch_all(&*self.pool).await?;
236
237 Ok(rows_to_result(rows, start))
238 }
239}