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