1mod migrate;
11mod options;
12#[cfg(feature = "sqlite")]
13mod sqlite;
14
15pub use migrate::{MigrateError, MigrateReport, Migration};
16pub use options::{ConnectOptions, TlsPolicy};
17#[cfg(feature = "sqlite")]
18pub use sqlite::{SqliteBackend, SqliteTx};
19
20use persistant_relational::{Dialect, RelError, RelationalBackend, RelationalTx, Row, Value};
21use sqlx::postgres::{PgArguments, PgPoolOptions, PgRow};
22use sqlx::query::Query;
23use sqlx::{Column, Postgres, Row as SqlxRow, TypeInfo, ValueRef};
24
25pub(crate) fn classify(e: sqlx::Error) -> RelError {
27 match &e {
28 sqlx::Error::Database(db) => {
29 let constraint = db.constraint().map(str::to_string);
30 match db.code().as_deref() {
31 Some("23505") => RelError::UniqueViolation { constraint },
32 Some("23503") => RelError::ForeignKeyViolation { constraint },
33 Some("23514") | Some("23502") => RelError::CheckViolation { constraint },
34 Some("40001") => RelError::Serialization,
35 Some("40P01") => RelError::Deadlock,
36 Some(c) if c.starts_with("08") || c.starts_with("57P") => {
37 RelError::Unavailable(e.to_string())
38 }
39 _ => RelError::Backend(e.to_string()),
40 }
41 }
42 sqlx::Error::Io(_) | sqlx::Error::PoolTimedOut | sqlx::Error::PoolClosed => {
43 RelError::Unavailable(e.to_string())
44 }
45 _ => RelError::Backend(e.to_string()),
46 }
47}
48
49fn bind<'q>(
50 mut q: Query<'q, Postgres, PgArguments>,
51 params: &'q [Value],
52) -> Result<Query<'q, Postgres, PgArguments>, RelError> {
53 for p in params {
54 q = match p {
55 Value::Null => q.bind(Option::<String>::None),
58 Value::Bool(b) => q.bind(*b),
59 Value::Int(n) => q.bind(*n),
60 Value::Float(f) => q.bind(*f),
61 Value::Text(s) => q.bind(s.as_str()),
62 Value::Bytes(b) => q.bind(b.as_slice()),
63 other => {
64 return Err(RelError::Unsupported(format!(
65 "parameter {other:?} has no PostgreSQL binding in this driver"
66 )));
67 }
68 };
69 }
70 Ok(q)
71}
72
73fn decode(row: &PgRow) -> Result<Row, RelError> {
74 let mut values = Vec::with_capacity(row.columns().len());
75 for (i, col) in row.columns().iter().enumerate() {
76 let raw = row.try_get_raw(i).map_err(classify)?;
77 if raw.is_null() {
78 values.push(Value::Null);
79 continue;
80 }
81 let name = col.type_info().name().to_string();
82 let v = match name.as_str() {
83 "BOOL" => Value::Bool(row.try_get::<bool, _>(i).map_err(classify)?),
84 "INT2" => Value::Int(i64::from(row.try_get::<i16, _>(i).map_err(classify)?)),
85 "INT4" => Value::Int(i64::from(row.try_get::<i32, _>(i).map_err(classify)?)),
86 "INT8" => Value::Int(row.try_get::<i64, _>(i).map_err(classify)?),
87 "FLOAT4" => Value::Float(f64::from(row.try_get::<f32, _>(i).map_err(classify)?)),
88 "FLOAT8" => Value::Float(row.try_get::<f64, _>(i).map_err(classify)?),
89 "TEXT" | "VARCHAR" | "NAME" | "BPCHAR" => {
90 Value::Text(row.try_get::<String, _>(i).map_err(classify)?)
91 }
92 "BYTEA" => Value::Bytes(row.try_get::<Vec<u8>, _>(i).map_err(classify)?),
93 other => {
94 return Err(RelError::Unsupported(format!(
95 "column {:?} has type {other}, which Value cannot carry; use PgBackend::pool",
96 col.name()
97 )));
98 }
99 };
100 values.push(v);
101 }
102 Ok(Row::new(values))
103}
104
105#[derive(Debug, Clone)]
107pub struct PgBackend {
108 pool: sqlx::PgPool,
109 version: String,
110}
111
112impl PgBackend {
113 pub async fn connect(url: &str) -> Result<Self, RelError> {
117 let pool = PgPoolOptions::new()
118 .max_connections(16)
119 .connect(url)
120 .await
121 .map_err(classify)?;
122 Self::from_pool(pool).await
123 }
124
125 pub async fn connect_secret(
128 secret: &persistant_relational::ConnectionSecret,
129 ) -> Result<Self, RelError> {
130 let url = secret.resolve()?;
131 Self::connect(url.expose()).await
132 }
133
134 pub async fn from_pool(pool: sqlx::PgPool) -> Result<Self, RelError> {
136 let full: String = sqlx::query_scalar("SHOW server_version")
137 .fetch_one(&pool)
138 .await
139 .map_err(classify)?;
140 let version = full.split_whitespace().next().unwrap_or("").to_string();
142 Ok(PgBackend { pool, version })
143 }
144
145 pub fn pool(&self) -> &sqlx::PgPool {
147 &self.pool
148 }
149}
150
151impl RelationalBackend for PgBackend {
152 type Tx<'a> = PgTx<'a>;
153
154 fn backend(&self) -> &str {
155 "postgres"
156 }
157
158 fn version(&self) -> &str {
159 &self.version
160 }
161
162 fn dialect(&self) -> Dialect {
163 Dialect::Postgres
164 }
165
166 async fn execute(&self, sql: &str, params: &[Value]) -> Result<u64, RelError> {
167 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
168 q.execute(&self.pool)
169 .await
170 .map(|r| r.rows_affected())
171 .map_err(classify)
172 }
173
174 async fn query(&self, sql: &str, params: &[Value]) -> Result<Vec<Row>, RelError> {
175 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
176 let rows = q.fetch_all(&self.pool).await.map_err(classify)?;
177 rows.iter().map(decode).collect()
178 }
179
180 async fn begin(&self) -> Result<PgTx<'_>, RelError> {
181 Ok(PgTx {
182 tx: self.pool.begin().await.map_err(classify)?,
183 })
184 }
185}
186
187#[derive(Debug)]
189pub struct PgTx<'a> {
190 tx: sqlx::Transaction<'a, Postgres>,
191}
192
193impl RelationalTx for PgTx<'_> {
194 async fn execute(&mut self, sql: &str, params: &[Value]) -> Result<u64, RelError> {
195 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
196 q.execute(&mut *self.tx)
197 .await
198 .map(|r| r.rows_affected())
199 .map_err(classify)
200 }
201
202 async fn query(&mut self, sql: &str, params: &[Value]) -> Result<Vec<Row>, RelError> {
203 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
204 let rows = q.fetch_all(&mut *self.tx).await.map_err(classify)?;
205 rows.iter().map(decode).collect()
206 }
207
208 async fn commit(self) -> Result<(), RelError> {
209 self.tx.commit().await.map_err(classify)
210 }
211
212 async fn rollback(self) -> Result<(), RelError> {
213 self.tx.rollback().await.map_err(classify)
214 }
215}