1use super::{Db, DbError, FetchedThen, RawRows, Tx};
4use crate::error::DatabaseError;
5use crate::row_json::{row_to_json, row_to_values};
6use postrust_sql::SqlParam;
7use sqlx::postgres::{PgArguments, PgPool, PgRow};
8use sqlx::query::Query;
9use sqlx::{Postgres, Row, ValueRef};
10
11pub struct SqlxTx {
18 tx: sqlx::Transaction<'static, Postgres>,
19 queued: Vec<(String, Vec<SqlParam>)>,
20}
21
22impl Db for PgPool {
23 type Tx<'a> = SqlxTx;
24
25 async fn begin(&self) -> Result<SqlxTx, DbError> {
26 Ok(SqlxTx {
27 tx: PgPool::begin(self).await?,
28 queued: Vec::new(),
29 })
30 }
31}
32
33impl SqlxTx {
34 async fn flush(&mut self) -> Result<(), DbError> {
36 for (sql, params) in std::mem::take(&mut self.queued) {
37 bind(sqlx::query(&sql), ¶ms)
38 .execute(&mut *self.tx)
39 .await
40 .map_err(|e| DbError::from(e).queued())?;
41 }
42 Ok(())
43 }
44}
45
46impl Tx for SqlxTx {
47 async fn execute(&mut self, sql: &str, params: &[SqlParam]) -> Result<u64, DbError> {
48 self.flush().await?;
49 Ok(bind(sqlx::query(sql), params)
50 .execute(&mut *self.tx)
51 .await?
52 .rows_affected())
53 }
54
55 async fn fetch_json(
56 &mut self,
57 sql: &str,
58 params: &[SqlParam],
59 ) -> Result<Vec<serde_json::Value>, DbError> {
60 self.flush().await?;
64 Ok(fetch_all(&mut self.tx, sql, params)
65 .await?
66 .into_iter()
67 .map(|row| row_to_json(&row))
68 .collect())
69 }
70
71 async fn fetch_values(
72 &mut self,
73 sql: &str,
74 params: &[SqlParam],
75 ) -> Result<Vec<Vec<serde_json::Value>>, DbError> {
76 self.flush().await?;
77 Ok(fetch_all(&mut self.tx, sql, params)
78 .await?
79 .into_iter()
80 .map(|row| row_to_values(&row))
81 .collect())
82 }
83
84 async fn fetch_raw(
85 &mut self,
86 sql: &str,
87 params: &[SqlParam],
88 count_column: &str,
89 ) -> Result<RawRows, DbError> {
90 self.flush().await?;
91 let rows = fetch_all(&mut self.tx, sql, params).await?;
92 let first = rows.first().and_then(|row| {
93 let value = row.try_get_raw(0).ok()?;
94 if value.is_null() {
95 return None;
96 }
97 Some(value.as_bytes().ok()?.to_vec())
98 });
99 let count = rows
100 .first()
101 .and_then(|row| row.try_get::<i64, _>(count_column).ok());
102 Ok(RawRows::new(rows.len(), first, count))
103 }
104
105 async fn commit(mut self) -> Result<(), DbError> {
106 self.flush().await?;
107 Ok(self.tx.commit().await?)
108 }
109
110 fn queue(&mut self, sql: &str, params: &[SqlParam]) {
111 self.queued.push((sql.to_string(), params.to_vec()));
112 }
113
114 async fn fetch_json_then(&mut self, sql: &str, params: &[SqlParam], then: &str) -> FetchedThen {
115 let rows = self.fetch_json(sql, params).await?;
116 let then = self.fetch_values(then, &[]).await?;
117 Ok((rows, then))
118 }
119
120 async fn fetch_json_and_commit(
121 mut self,
122 sql: &str,
123 params: &[SqlParam],
124 ) -> Result<Vec<serde_json::Value>, DbError> {
125 let rows = self.fetch_json(sql, params).await?;
126 self.commit().await?;
127 Ok(rows)
128 }
129}
130
131async fn fetch_all(
132 tx: &mut sqlx::Transaction<'static, Postgres>,
133 sql: &str,
134 params: &[SqlParam],
135) -> Result<Vec<PgRow>, sqlx::Error> {
136 bind(sqlx::query(sql), params).fetch_all(&mut **tx).await
137}
138
139fn bind<'q>(
142 mut query: Query<'q, Postgres, PgArguments>,
143 params: &'q [SqlParam],
144) -> Query<'q, Postgres, PgArguments> {
145 for param in params {
146 query = match param {
147 SqlParam::Null => query.bind(None::<String>),
148 SqlParam::Bool(b) => query.bind(b),
149 SqlParam::Int(n) => query.bind(n),
150 SqlParam::Float(f) => query.bind(f),
151 SqlParam::Text(s) => query.bind(s),
152 SqlParam::Bytes(b) => query.bind(b),
153 SqlParam::Json(j) => query.bind(j),
154 SqlParam::Uuid(u) => query.bind(u),
155 SqlParam::Timestamp(t) => query.bind(t),
156 SqlParam::Array(arr) => {
157 let strings: Vec<String> = arr
159 .iter()
160 .map(|p| match p {
161 SqlParam::Text(s) => s.clone(),
162 SqlParam::Int(n) => n.to_string(),
163 SqlParam::Bool(b) => b.to_string(),
164 other => format!("{:?}", other),
165 })
166 .collect();
167 query.bind(strings)
168 }
169 };
170 }
171
172 query
173}
174
175impl From<sqlx::Error> for DbError {
176 fn from(e: sqlx::Error) -> Self {
177 let message = e.to_string();
178 match e {
179 sqlx::Error::Database(db_err) => {
180 let (details, hint) = db_err
182 .try_downcast_ref::<sqlx::postgres::PgDatabaseError>()
183 .map(|pg_err| {
184 (
185 pg_err.detail().map(String::from),
186 pg_err.hint().map(String::from),
187 )
188 })
189 .unwrap_or((None, None));
190
191 DbError::database(
192 message,
193 DatabaseError {
194 code: db_err.code().map(|c| c.to_string()).unwrap_or_default(),
195 message: db_err.message().to_string(),
196 details,
197 hint,
198 constraint: db_err.constraint().map(|s| s.to_string()),
199 table: db_err.table().map(|s| s.to_string()),
200 column: None,
201 },
202 )
203 }
204 sqlx::Error::Io(_)
207 | sqlx::Error::Tls(_)
208 | sqlx::Error::PoolTimedOut
209 | sqlx::Error::PoolClosed
210 | sqlx::Error::WorkerCrashed => DbError::connection(message),
211 _ => DbError::other(message),
212 }
213 }
214}