1mod bounded;
11#[cfg(feature = "bridge")]
12mod bridge;
13mod decode;
14mod migrate;
15#[cfg(feature = "mysql")]
16mod mysql;
17mod options;
18#[cfg(feature = "sqlite")]
19mod sqlite;
20
21pub use bounded::{Bounded, BoundedTx, call_bound_from_env, parse_bound};
22#[cfg(feature = "bridge")]
23pub use bridge::{OnConn, OnPool};
24pub use migrate::{FromSqlxError, migrations_from_sqlx};
25#[cfg(feature = "mysql")]
26pub use mysql::{MysqlBackend, MysqlTx};
27pub use options::{ConnectOptions, TlsPolicy};
28pub use persistant_relational::{MigrateError, MigrateReport, Migrates, Migration};
29#[cfg(feature = "sqlite")]
30pub use sqlite::{SqliteBackend, SqliteTx};
31
32use persistant_relational::{
33 Dialect, Isolation, RelError, RelationalBackend, RelationalTx, Row, Value,
34};
35use sqlx::postgres::{PgArguments, PgPoolOptions, PgRow};
36use sqlx::query::Query;
37use sqlx::{Column, Postgres, Row as SqlxRow, TypeInfo, ValueRef};
38
39pub(crate) fn classify(e: sqlx::Error) -> RelError {
41 match &e {
42 sqlx::Error::Database(db) => {
43 let constraint = db.constraint().map(str::to_string);
44 match db.code().as_deref() {
45 Some("23505") => RelError::UniqueViolation { constraint },
46 Some("23503") => RelError::ForeignKeyViolation { constraint },
47 Some("23514") | Some("23502") => RelError::CheckViolation { constraint },
48 Some("40001") => RelError::Serialization,
49 Some("40P01") => RelError::Deadlock,
50 Some(c) if c.starts_with("08") || c.starts_with("57P") => {
51 RelError::Unavailable(e.to_string())
52 }
53 _ => RelError::Backend(e.to_string()),
54 }
55 }
56 sqlx::Error::Io(_) | sqlx::Error::PoolTimedOut | sqlx::Error::PoolClosed => {
57 RelError::Unavailable(e.to_string())
58 }
59 _ => RelError::Backend(e.to_string()),
60 }
61}
62
63struct UntypedNull;
68
69impl sqlx::Type<Postgres> for UntypedNull {
70 fn type_info() -> sqlx::postgres::PgTypeInfo {
71 sqlx::postgres::PgTypeInfo::with_oid(sqlx::postgres::types::Oid(0))
72 }
73
74 fn compatible(_: &sqlx::postgres::PgTypeInfo) -> bool {
75 true
76 }
77}
78
79impl<'q> sqlx::Encode<'q, Postgres> for UntypedNull {
80 fn encode_by_ref(
81 &self,
82 _: &mut sqlx::postgres::PgArgumentBuffer,
83 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
84 Ok(sqlx::encode::IsNull::Yes)
85 }
86}
87
88fn bind<'q>(
89 mut q: Query<'q, Postgres, PgArguments>,
90 params: &'q [Value],
91) -> Result<Query<'q, Postgres, PgArguments>, RelError> {
92 for p in params {
93 q = match p {
94 Value::Null => q.bind(UntypedNull),
96 Value::Bool(b) => q.bind(*b),
97 Value::Int(n) => q.bind(*n),
98 Value::Float(f) => q.bind(*f),
99 Value::Text(s) => q.bind(s.as_str()),
100 Value::Bytes(b) => q.bind(b.as_slice()),
101 other => {
102 return Err(RelError::Unsupported(format!(
103 "parameter {other:?} has no PostgreSQL binding in this driver"
104 )));
105 }
106 };
107 }
108 Ok(q)
109}
110
111fn decode_failed(e: sqlx::error::BoxDynError) -> RelError {
112 RelError::Backend(e.to_string())
113}
114
115fn malformed(column: &str, ty: &str, why: String) -> RelError {
116 RelError::Backend(format!("column {column:?} ({ty}): {why}"))
117}
118
119fn decode(row: &PgRow) -> Result<Row, RelError> {
120 let mut values = Vec::with_capacity(row.columns().len());
121 for (i, col) in row.columns().iter().enumerate() {
122 let raw = row.try_get_raw(i).map_err(classify)?;
123 if raw.is_null() {
124 values.push(Value::Null);
125 continue;
126 }
127 let name = col.type_info().name().to_string();
128 let v = match name.as_str() {
129 "BOOL" => Value::Bool(row.try_get::<bool, _>(i).map_err(classify)?),
130 "INT2" => Value::Int(i64::from(row.try_get::<i16, _>(i).map_err(classify)?)),
131 "INT4" => Value::Int(i64::from(row.try_get::<i32, _>(i).map_err(classify)?)),
132 "INT8" => Value::Int(row.try_get::<i64, _>(i).map_err(classify)?),
133 "FLOAT4" => Value::Float(decode::f32_shortest(
134 row.try_get::<f32, _>(i).map_err(classify)?,
135 )),
136 "FLOAT8" => Value::Float(row.try_get::<f64, _>(i).map_err(classify)?),
137 "TEXT" | "VARCHAR" | "NAME" | "BPCHAR" | "CHAR" => {
139 Value::Text(row.try_get::<String, _>(i).map_err(classify)?)
140 }
141 "BYTEA" => Value::Bytes(row.try_get::<Vec<u8>, _>(i).map_err(classify)?),
142 "UUID" => Value::Text(
144 decode::uuid_text(raw.as_bytes().map_err(decode_failed)?)
145 .map_err(|m| malformed(col.name(), &name, m))?,
146 ),
147 "NUMERIC" => Value::Text(
148 decode::numeric_text(raw.as_bytes().map_err(decode_failed)?)
149 .map_err(|m| malformed(col.name(), &name, m))?,
150 ),
151 "INET" | "CIDR" => Value::Text(
152 decode::inet_text(raw.as_bytes().map_err(decode_failed)?)
153 .map_err(|m| malformed(col.name(), &name, m))?,
154 ),
155 "JSON" => Value::Text(
156 String::from_utf8(raw.as_bytes().map_err(decode_failed)?.to_vec())
157 .map_err(|e| malformed(col.name(), &name, e.to_string()))?,
158 ),
159 "JSONB" => {
160 let b = raw.as_bytes().map_err(decode_failed)?;
162 match b.split_first() {
163 Some((1, rest)) => Value::Text(
164 String::from_utf8(rest.to_vec())
165 .map_err(|e| malformed(col.name(), &name, e.to_string()))?,
166 ),
167 _ => return Err(malformed(col.name(), &name, "unknown jsonb version".into())),
168 }
169 }
170 "DATE" => Value::Text(decode::date_text(
171 row.try_get::<sqlx::types::time::Date, _>(i)
172 .map_err(classify)?,
173 )),
174 "TIME" => Value::Text(decode::time_text(
175 row.try_get::<sqlx::types::time::Time, _>(i)
176 .map_err(classify)?,
177 )),
178 "TIMESTAMP" => Value::Text(decode::naive_text(
179 row.try_get::<sqlx::types::time::PrimitiveDateTime, _>(i)
180 .map_err(classify)?,
181 )),
182 "TIMESTAMPTZ" => Value::Text(decode::utc_text(
183 row.try_get::<sqlx::types::time::OffsetDateTime, _>(i)
184 .map_err(classify)?,
185 )),
186 other => {
187 return Err(RelError::Unsupported(format!(
188 "column {:?} has type {other}, which Value cannot carry; use PgBackend::pool",
189 col.name()
190 )));
191 }
192 };
193 values.push(v);
194 }
195 Ok(Row::new(values))
196}
197
198#[derive(Debug, Clone)]
200pub struct PgBackend {
201 pool: sqlx::PgPool,
202 version: String,
203}
204
205impl PgBackend {
206 pub async fn connect(url: &str) -> Result<Self, RelError> {
210 let pool = PgPoolOptions::new()
211 .max_connections(16)
212 .connect(url)
213 .await
214 .map_err(classify)?;
215 Self::from_pool(pool).await
216 }
217
218 pub async fn connect_secret(
221 secret: &persistant_relational::ConnectionSecret,
222 ) -> Result<Self, RelError> {
223 let url = secret.resolve()?;
224 Self::connect(url.expose()).await
225 }
226
227 pub async fn from_pool(pool: sqlx::PgPool) -> Result<Self, RelError> {
229 let full: String = sqlx::query_scalar("SHOW server_version")
230 .fetch_one(&pool)
231 .await
232 .map_err(classify)?;
233 let version = full.split_whitespace().next().unwrap_or("").to_string();
235 Ok(PgBackend { pool, version })
236 }
237
238 pub fn pool(&self) -> &sqlx::PgPool {
240 &self.pool
241 }
242}
243
244impl RelationalBackend for PgBackend {
245 type Tx<'a> = PgTx<'a>;
246
247 fn backend(&self) -> &str {
248 "postgres"
249 }
250
251 fn version(&self) -> &str {
252 &self.version
253 }
254
255 fn dialect(&self) -> Dialect {
256 Dialect::Postgres
257 }
258
259 async fn execute(&self, sql: &str, params: &[Value]) -> Result<u64, RelError> {
260 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
261 q.execute(&self.pool)
262 .await
263 .map(|r| r.rows_affected())
264 .map_err(classify)
265 }
266
267 async fn query(&self, sql: &str, params: &[Value]) -> Result<Vec<Row>, RelError> {
268 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
269 let rows = q.fetch_all(&self.pool).await.map_err(classify)?;
270 rows.iter().map(decode).collect()
271 }
272
273 async fn begin(&self, isolation: Isolation) -> Result<PgTx<'_>, RelError> {
274 let statement = match isolation {
275 Isolation::ReadCommitted => "BEGIN",
276 Isolation::RepeatableRead => "BEGIN ISOLATION LEVEL REPEATABLE READ",
277 Isolation::Serializable => "BEGIN ISOLATION LEVEL SERIALIZABLE",
278 other => return Err(RelError::Unsupported(format!("isolation level {other:?}"))),
279 };
280 Ok(PgTx {
281 tx: self.pool.begin_with(statement).await.map_err(classify)?,
282 })
283 }
284}
285
286#[derive(Debug)]
288pub struct PgTx<'a> {
289 tx: sqlx::Transaction<'a, Postgres>,
290}
291
292impl RelationalTx for PgTx<'_> {
293 async fn execute(&mut self, sql: &str, params: &[Value]) -> Result<u64, RelError> {
294 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
295 q.execute(&mut *self.tx)
296 .await
297 .map(|r| r.rows_affected())
298 .map_err(classify)
299 }
300
301 async fn query(&mut self, sql: &str, params: &[Value]) -> Result<Vec<Row>, RelError> {
302 let q = bind(sqlx::query(sqlx::AssertSqlSafe(sql.to_string())), params)?;
303 let rows = q.fetch_all(&mut *self.tx).await.map_err(classify)?;
304 rows.iter().map(decode).collect()
305 }
306
307 async fn commit(self) -> Result<(), RelError> {
308 self.tx.commit().await.map_err(classify)
309 }
310
311 async fn rollback(self) -> Result<(), RelError> {
312 self.tx.rollback().await.map_err(classify)
313 }
314}