1use crate::connection_init::ConnectionInit;
12use crate::shared::{
13 ConnSlot, TxSlot, affected_result, map_error, returning_result, with_conn, with_tx,
14};
15use crate::sql::{CompiledQuery, Postgres, compile, compile_write};
16use async_trait::async_trait;
17use siderite_orm::{
18 Backend, BackendCapabilities, BackendError, ExecResult, Executor, IsolationLevel, OrmError,
19 QueryError, QueryPlan, QueryResult, Row, Transaction, Value, WritePlan,
20};
21use sqlx::encode::IsNull;
22use sqlx::error::BoxDynError;
23use sqlx::postgres::types::Oid;
24use sqlx::postgres::{
25 PgArgumentBuffer, PgArguments, PgPool, PgPoolOptions, PgRow, PgTypeInfo, PgValueRef,
26};
27use sqlx::types::chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
28use sqlx::types::uuid::Uuid;
29use sqlx::types::{Decimal, JsonValue};
30use sqlx::{Column as _, Executor as _, Row as _, TypeInfo as _, ValueRef as _};
31
32#[derive(Debug, Clone)]
34pub struct PgBackend {
35 pool: PgPool,
36}
37
38impl PgBackend {
39 pub async fn connect(url: &str) -> Result<Self, BackendError> {
53 let options = PgPoolOptions::new()
54 .max_connections(10)
55 .test_before_acquire(false);
56 Self::connect_with(url, options).await
57 }
58
59 pub async fn connect_with(url: &str, options: PgPoolOptions) -> Result<Self, BackendError> {
75 let init: ConnectionInit<sqlx::Postgres> =
76 std::sync::Arc::new(|_, _| Box::pin(async { Ok(()) }));
77 Self::connect_with_init(url, options, init).await
78 }
79
80 pub async fn connect_with_init(
93 url: &str,
94 options: PgPoolOptions,
95 init: ConnectionInit<sqlx::Postgres>,
96 ) -> Result<Self, BackendError> {
97 let pool = options
98 .after_connect(move |conn, meta| {
99 let init = init.clone();
100 Box::pin(async move {
101 init(conn, meta).await?;
102 conn.execute("SET TIME ZONE 'UTC'").await?;
103 Ok(())
104 })
105 })
106 .connect(url)
107 .await
108 .map_err(|e| BackendError::Connection(e.to_string()))?;
109 Ok(Self { pool })
110 }
111
112 pub fn from_pool(pool: PgPool) -> Self {
115 Self { pool }
116 }
117}
118
119#[async_trait]
120impl Executor for PgBackend {
121 fn capabilities(&self) -> BackendCapabilities {
122 BackendCapabilities::postgres()
123 }
124
125 async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
126 let compiled = compile(plan, &Postgres)?;
127 fetch_rows(&self.pool, &compiled.sql, compiled.params).await
128 }
129
130 async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
131 let compiled = compile_write(plan, &Postgres)?;
132 run_write(&self.pool, compiled, !plan.returning().is_empty()).await
133 }
134
135 async fn fetch_raw(&self, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError> {
136 fetch_rows(&self.pool, sql, params).await
137 }
138
139 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, OrmError> {
140 execute_rows(&self.pool, sql, params).await
141 }
142
143 async fn execute_script(&self, sql: &str) -> Result<(), OrmError> {
144 script_result(sqlx::Executor::execute(&self.pool, sql).await)
145 }
146}
147
148#[async_trait]
149impl Backend for PgBackend {
150 fn read_parameter_count(&self, plan: &QueryPlan) -> Result<Option<usize>, OrmError> {
151 Ok(Some(compile(plan, &Postgres)?.params.len()))
152 }
153
154 async fn begin(
155 &self,
156 isolation: Option<IsolationLevel>,
157 ) -> Result<Box<dyn Transaction>, OrmError> {
158 if let Some(level) = isolation {
159 self.capabilities()
160 .require(siderite_orm::Feature::Isolation(level))?;
161 }
162 let mut tx = self.pool.begin().await.map_err(map_error)?;
163 if let Some(level) = isolation {
164 let statement = format!("SET TRANSACTION ISOLATION LEVEL {}", isolation_sql(level));
166 sqlx::query(&statement)
167 .execute(&mut *tx)
168 .await
169 .map_err(map_error)?;
170 }
171 Ok(Box::new(PgTransaction(TxSlot::new(tx))))
172 }
173
174 async fn begin_schema(&self, transactional: bool) -> Result<Box<dyn Transaction>, OrmError> {
175 if transactional {
176 return self.begin(None).await;
177 }
178 let conn = self.pool.acquire().await.map_err(map_error)?;
179 Ok(Box::new(PgHeld(ConnSlot::new(conn))))
180 }
181}
182
183fn isolation_sql(level: IsolationLevel) -> &'static str {
184 match level {
185 IsolationLevel::ReadCommitted => "READ COMMITTED",
186 IsolationLevel::RepeatableRead => "REPEATABLE READ",
187 IsolationLevel::Serializable => "SERIALIZABLE",
188 }
189}
190
191struct PgTransaction(TxSlot<sqlx::Postgres>);
193
194#[async_trait]
195impl Executor for PgTransaction {
196 fn capabilities(&self) -> BackendCapabilities {
197 BackendCapabilities::postgres()
198 }
199
200 async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
201 let compiled = compile(plan, &Postgres)?;
202 with_tx!(self.0, conn => fetch_rows(conn, &compiled.sql, compiled.params).await)
203 }
204
205 async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
206 let compiled = compile_write(plan, &Postgres)?;
207 let returning = !plan.returning().is_empty();
208 with_tx!(self.0, conn => run_write(conn, compiled, returning).await)
209 }
210
211 async fn fetch_raw(&self, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError> {
212 with_tx!(self.0, conn => fetch_rows(conn, sql, params).await)
213 }
214
215 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, OrmError> {
216 with_tx!(self.0, conn => execute_rows(conn, sql, params).await)
217 }
218
219 async fn execute_script(&self, sql: &str) -> Result<(), OrmError> {
220 with_tx!(self.0, conn => script_result(sqlx::Executor::execute(conn, sql).await))
221 }
222}
223
224#[async_trait]
225impl Transaction for PgTransaction {
226 async fn commit(&self) -> Result<(), OrmError> {
227 self.0.commit().await
228 }
229
230 async fn rollback(&self) -> Result<(), OrmError> {
231 self.0.rollback().await
232 }
233}
234
235struct PgHeld(ConnSlot<sqlx::Postgres>);
242
243#[async_trait]
244impl Executor for PgHeld {
245 fn capabilities(&self) -> BackendCapabilities {
246 BackendCapabilities::postgres()
247 }
248
249 async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
250 let compiled = compile(plan, &Postgres)?;
251 with_conn!(self.0, conn => fetch_rows(conn, &compiled.sql, compiled.params).await)
252 }
253
254 async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
255 let compiled = compile_write(plan, &Postgres)?;
256 let returning = !plan.returning().is_empty();
257 with_conn!(self.0, conn => run_write(conn, compiled, returning).await)
258 }
259
260 async fn fetch_raw(&self, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError> {
261 with_conn!(self.0, conn => fetch_rows(conn, sql, params).await)
262 }
263
264 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, OrmError> {
265 with_conn!(self.0, conn => execute_rows(conn, sql, params).await)
266 }
267
268 async fn execute_script(&self, sql: &str) -> Result<(), OrmError> {
269 with_conn!(self.0, conn => script_result(sqlx::Executor::execute(conn, sql).await))
270 }
271}
272
273#[async_trait]
274impl Transaction for PgHeld {
275 async fn commit(&self) -> Result<(), OrmError> {
276 let mut conn = self.0.take().await?;
277 conn.close_on_drop();
278 drop(conn);
279 Ok(())
280 }
281
282 async fn rollback(&self) -> Result<(), OrmError> {
283 let mut conn = self.0.take().await?;
284 conn.close_on_drop();
285 drop(conn);
286 Ok(())
287 }
288}
289
290struct UntypedNull;
294
295impl sqlx::Type<sqlx::Postgres> for UntypedNull {
296 fn type_info() -> PgTypeInfo {
297 PgTypeInfo::with_oid(Oid(0))
298 }
299}
300
301impl sqlx::Encode<'_, sqlx::Postgres> for UntypedNull {
302 fn encode_by_ref(&self, _buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
303 Ok(IsNull::Yes)
304 }
305}
306
307fn bind_all(sql: &str, params: Vec<Value>) -> sqlx::query::Query<'_, sqlx::Postgres, PgArguments> {
308 params
309 .into_iter()
310 .fold(sqlx::query(sql), |query, param| match param {
311 Value::Null => query.bind(UntypedNull),
312 Value::Bool(v) => query.bind(v),
313 Value::Int(v) => query.bind(v),
314 Value::Float(v) => query.bind(v),
315 Value::Decimal(v) => query.bind(v),
316 Value::Text(v) => query.bind(v),
317 Value::Bytes(v) => query.bind(v),
318 Value::Uuid(v) => query.bind(v),
319 Value::Date(v) => query.bind(v),
320 Value::Time(v) => query.bind(v),
321 Value::Timestamp(v) => query.bind(v),
322 Value::Json(v) => query.bind(v),
323 })
324}
325
326async fn fetch_rows<'c, E>(ex: E, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError>
327where
328 E: sqlx::Executor<'c, Database = sqlx::Postgres>,
329{
330 let rows = bind_all(sql, params)
331 .fetch_all(ex)
332 .await
333 .map_err(map_error)?;
334 let rows = rows.iter().map(decode_row).collect::<Result<_, _>>()?;
335 Ok(QueryResult { rows })
336}
337
338async fn execute_rows<'c, E>(ex: E, sql: &str, params: Vec<Value>) -> Result<u64, OrmError>
339where
340 E: sqlx::Executor<'c, Database = sqlx::Postgres>,
341{
342 let done = bind_all(sql, params).execute(ex).await.map_err(map_error)?;
343 Ok(done.rows_affected())
344}
345
346async fn run_write<'c, E>(
347 ex: E,
348 compiled: CompiledQuery,
349 returning: bool,
350) -> Result<ExecResult, OrmError>
351where
352 E: sqlx::Executor<'c, Database = sqlx::Postgres>,
353{
354 if returning {
355 let rows = fetch_rows(ex, &compiled.sql, compiled.params).await?.rows;
356 Ok(returning_result(rows))
357 } else {
358 Ok(affected_result(
359 execute_rows(ex, &compiled.sql, compiled.params).await?,
360 ))
361 }
362}
363
364fn script_result(done: Result<sqlx::postgres::PgQueryResult, sqlx::Error>) -> Result<(), OrmError> {
365 done.map(|_| ()).map_err(|e| map_error(e).into())
366}
367
368fn decode_row(row: &PgRow) -> Result<Row, QueryError> {
369 row.columns()
370 .iter()
371 .enumerate()
372 .map(|(i, col)| {
373 let name = col.name().to_owned();
374 let raw = row.try_get_raw(i).map_err(|e| QueryError::Decode {
375 column: name.clone(),
376 reason: e.to_string(),
377 })?;
378 let value = if raw.is_null() {
379 Value::Null
380 } else {
381 decode_value(row, i, raw).map_err(|reason| QueryError::Decode {
382 column: name.clone(),
383 reason,
384 })?
385 };
386 Ok((name, value))
387 })
388 .collect::<Result<Vec<_>, _>>()
389 .map(Row::new)
390}
391
392fn decode_value(row: &PgRow, i: usize, raw: PgValueRef<'_>) -> Result<Value, String> {
394 macro_rules! get {
396 ($t:ty => $into:expr) => {
397 row.try_get::<$t, _>(i)
398 .map($into)
399 .map_err(|e| e.to_string())
400 };
401 }
402 macro_rules! array {
404 ($t:ty) => {
405 get!(Vec<$t> => |items| Value::Json(JsonValue::Array(items.into_iter().map(JsonValue::from).collect())))
406 };
407 }
408 let type_info = raw.type_info();
409 match type_info.name() {
410 "BOOL" => get!(bool => Value::Bool),
411 "INT2" => get!(i16 => |v| Value::Int(i64::from(v))),
412 "INT4" => get!(i32 => |v| Value::Int(i64::from(v))),
413 "INT8" => get!(i64 => Value::Int),
414 "FLOAT4" => get!(f32 => |v| Value::Float(f64::from(v))),
415 "FLOAT8" => get!(f64 => Value::Float),
416 "NUMERIC" => get!(Decimal => Value::Decimal),
417 "TEXT" | "VARCHAR" | "BPCHAR" | "NAME" => get!(String => Value::Text),
418 "BYTEA" => get!(Vec<u8> => Value::Bytes),
419 "UUID" => get!(Uuid => Value::Uuid),
420 "DATE" => get!(NaiveDate => Value::Date),
421 "TIME" => get!(NaiveTime => Value::Time),
422 "TIMESTAMPTZ" => get!(DateTime<Utc> => Value::Timestamp),
423 "TIMESTAMP" => get!(NaiveDateTime => |v| Value::Timestamp(v.and_utc())),
424 "JSON" | "JSONB" => get!(JsonValue => Value::Json),
425 "INT2[]" => array!(i16),
426 "INT4[]" => array!(i32),
427 "INT8[]" => array!(i64),
428 "FLOAT8[]" => array!(f64),
429 "BOOL[]" => array!(bool),
430 "TEXT[]" | "VARCHAR[]" => array!(String),
431 other => Err(format!("unsupported PostgreSQL type {other}")),
432 }
433}