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