1use qbrs_core::delete::Delete;
7use qbrs_core::dialect::Postgres;
8use qbrs_core::expr::Value;
9use qbrs_core::insert::Insert;
10use qbrs_core::row::{Row, RowCons, RowNil};
11use qbrs_core::select::{DynSelect, Prepared, PreparedParams, Select, Selection, SetOp, Total};
12use qbrs_core::statement::{Returning, Statement, WrittenTable};
13use qbrs_core::update::Update;
14use sqlx::Row as _;
15use sqlx::postgres::PgRow;
16
17#[derive(Debug, thiserror::Error)]
21pub enum Error {
22 #[error(transparent)]
25 Sqlx(#[from] sqlx::Error),
26
27 #[error(transparent)]
31 UnresolvedPlaceholder(#[from] qbrs_core::select::UnresolvedPlaceholder),
32
33 #[error(transparent)]
38 NothingToSet(#[from] qbrs_core::update::NothingToSet),
39
40 #[error(transparent)]
41 NothingToInsert(#[from] qbrs_core::insert::NothingToInsert),
42
43 #[error("`{0}` values need the matching feature on `qbrs-sqlx` too")]
47 FeatureNotEnabled(&'static str),
48}
49
50pub type Result<T> = std::result::Result<T, Error>;
53
54pub mod prelude {
65 pub use crate::Error;
66 pub use crate::{
67 CountExt, CountQuery, DecodeRow, ExecuteExt, LoadExt, PreparedCountExt, PreparedExt,
68 PreparedQuery, PreparedTotal, RowQuery, WriteStatement,
69 };
70}
71
72fn bind_value<'q>(
79 query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
80 v: Value,
81) -> Result<sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>> {
82 Ok(match v {
83 Value::I32(x) => query.bind(x),
84 Value::I64(x) => query.bind(x),
85 Value::F64(x) => query.bind(x),
86 Value::Text(x) => query.bind(x),
87 Value::Bool(x) => query.bind(x),
88 Value::Bytes(x) => query.bind(x),
89 Value::NullI32 => query.bind(None::<i32>),
90 Value::NullI64 => query.bind(None::<i64>),
91 Value::NullF64 => query.bind(None::<f64>),
92 Value::NullText => query.bind(None::<String>),
93 Value::NullBool => query.bind(None::<bool>),
94 Value::NullBytes => query.bind(None::<Vec<u8>>),
95 #[cfg(feature = "chrono")]
96 Value::Timestamptz(x) => query.bind(x),
97 #[cfg(feature = "chrono")]
98 Value::NullTimestamptz => query.bind(None::<chrono::DateTime<chrono::Utc>>),
99 #[cfg(feature = "chrono")]
100 Value::Date(x) => query.bind(x),
101 #[cfg(feature = "chrono")]
102 Value::NullDate => query.bind(None::<chrono::NaiveDate>),
103 #[cfg(feature = "uuid")]
104 Value::Uuid(x) => query.bind(x),
105 #[cfg(feature = "uuid")]
106 Value::NullUuid => query.bind(None::<uuid::Uuid>),
107 #[cfg(feature = "decimal")]
108 Value::Numeric(x) => query.bind(x),
109 #[cfg(feature = "decimal")]
110 Value::NullNumeric => query.bind(None::<rust_decimal::Decimal>),
111 Value::Placeholder(name) => {
112 return Err(qbrs_core::select::UnresolvedPlaceholder(name).into());
113 }
114 #[allow(unreachable_patterns)]
117 other => return Err(Error::FeatureNotEnabled(other.type_name())),
118 })
119}
120
121fn bind_all<'q>(
122 mut query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
123 params: Vec<Value>,
124) -> Result<sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>> {
125 for p in params {
126 query = bind_value(query, p)?;
127 }
128 Ok(query)
129}
130
131async fn fetch_all<'e, T: DecodeRow, E: sqlx::PgExecutor<'e>>(
135 executor: E,
136 sql: &str,
137 params: Vec<Value>,
138) -> Result<Vec<T>> {
139 let rows = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
140 .fetch_all(executor)
141 .await?;
142 rows.iter()
143 .map(|row| T::decode_at(row, &mut 0).map_err(Error::from))
144 .collect()
145}
146
147async fn fetch_optional<'e, T: DecodeRow, E: sqlx::PgExecutor<'e>>(
148 executor: E,
149 sql: &str,
150 params: Vec<Value>,
151) -> Result<Option<T>> {
152 let row = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
153 .fetch_optional(executor)
154 .await?;
155 row.as_ref()
156 .map(|r| T::decode_at(r, &mut 0).map_err(Error::from))
157 .transpose()
158}
159
160async fn execute_only<'e, E: sqlx::PgExecutor<'e>>(
161 executor: E,
162 sql: &str,
163 params: Vec<Value>,
164) -> Result<u64> {
165 let result = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
166 .execute(executor)
167 .await?;
168 Ok(result.rows_affected())
169}
170
171#[diagnostic::on_unimplemented(
182 message = "`{Self}` isn't a query this crate can run",
183 label = "a `Select`, a `RETURNING`, a `DynSelect` or a set operation, in the `Postgres` dialect, whose values are all types `DecodeRow` covers"
184)]
185pub trait RowQuery<Idx> {
186 type Output: DecodeRow;
187
188 #[doc(hidden)]
189 fn rendered(&self) -> (String, Vec<Value>);
190}
191
192pub trait LoadExt {
205 fn load<'e, Idx, E: sqlx::PgExecutor<'e>>(
206 &self,
207 executor: E,
208 ) -> impl std::future::Future<Output = Result<Vec<<Self as RowQuery<Idx>>::Output>>>
209 where
210 Self: RowQuery<Idx>,
211 {
212 let (sql, params) = self.rendered();
213 async move { fetch_all::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
214 }
215
216 fn load_one<'e, Idx, E: sqlx::PgExecutor<'e>>(
217 &self,
218 executor: E,
219 ) -> impl std::future::Future<Output = Result<Option<<Self as RowQuery<Idx>>::Output>>>
220 where
221 Self: RowQuery<Idx>,
222 {
223 let (sql, params) = self.rendered();
224 async move { fetch_optional::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
225 }
226}
227
228impl<D, Scope, Sel, Outer> LoadExt for Select<D, Scope, Sel, Outer> {}
229impl<S, Sel> LoadExt for Returning<S, Sel> {}
230impl<D, Output> LoadExt for DynSelect<D, Output> {}
231impl<D, Output> LoadExt for SetOp<D, Output> {}
232impl<D, R: qbrs_core::insert::InsertRow> LoadExt for Insert<D, R> {}
233impl<D, T: qbrs_core::scope::Table> LoadExt for Update<D, T> {}
234impl<D, T: qbrs_core::scope::Table> LoadExt for Delete<D, T> {}
235
236impl<Scope, Sel, Idx> RowQuery<Idx> for Select<Postgres, Scope, Sel>
237where
238 Sel: Selection<Scope, Idx>,
239 Sel::Output: DecodeRow,
240{
241 type Output = Sel::Output;
242
243 fn rendered(&self) -> (String, Vec<Value>) {
244 self.to_sql::<Idx>(Postgres)
245 }
246}
247
248#[diagnostic::on_unimplemented(
253 message = "`{Self}` isn't a query this crate can count",
254 label = "a `Select`, a `DynSelect` or a set operation in the `Postgres` dialect is; a writing statement reports rows affected through `.execute(..)` instead"
255)]
256pub trait CountQuery<Idx> {
257 #[doc(hidden)]
258 fn count_rendered(&self) -> (String, Vec<Value>);
259}
260
261pub trait CountExt {
264 fn count<'e, Idx, E: sqlx::PgExecutor<'e>>(
265 &self,
266 executor: E,
267 ) -> impl std::future::Future<Output = Result<i64>>
268 where
269 Self: CountQuery<Idx>,
270 {
271 count_rows(executor, self.count_rendered())
272 }
273}
274
275impl<D, Scope, Sel, Outer> CountExt for Select<D, Scope, Sel, Outer> {}
276impl<S, Sel> CountExt for Returning<S, Sel> {}
277impl<D, Output> CountExt for DynSelect<D, Output> {}
278impl<D, Output> CountExt for SetOp<D, Output> {}
279impl<D, R: qbrs_core::insert::InsertRow> CountExt for Insert<D, R> {}
280impl<D, T: qbrs_core::scope::Table> CountExt for Update<D, T> {}
281impl<D, T: qbrs_core::scope::Table> CountExt for Delete<D, T> {}
282
283impl<Scope, Sel: Selection<Scope, Idx>, Idx> CountQuery<Idx> for Select<Postgres, Scope, Sel> {
284 fn count_rendered(&self) -> (String, Vec<Value>) {
285 self.count_sql::<Idx>(Postgres)
286 }
287}
288
289impl<Output> CountQuery<()> for DynSelect<Postgres, Output> {
293 fn count_rendered(&self) -> (String, Vec<Value>) {
294 self.count_sql(Postgres)
295 }
296}
297
298impl<Output> CountQuery<()> for SetOp<Postgres, Output> {
299 fn count_rendered(&self) -> (String, Vec<Value>) {
300 self.count_sql(Postgres)
301 }
302}
303
304async fn count_rows<'e, E: sqlx::PgExecutor<'e>>(
305 executor: E,
306 (sql, params): (String, Vec<Value>),
307) -> Result<i64> {
308 let row = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql.as_str())), params)?
309 .fetch_one(executor)
310 .await?;
311 Ok(row.try_get::<i64, _>(0)?)
312}
313
314#[diagnostic::on_unimplemented(
317 message = "`{Self}` isn't a statement this crate can execute",
318 label = "an `INSERT`, `UPDATE` or `DELETE` in the `Postgres` dialect is; a `SELECT` or a `RETURNING` yields rows, so it goes through `.load(..)` — and a prepared query through `.load(.., params)`"
319)]
320pub trait WriteStatement {
321 #[doc(hidden)]
322 fn write_rendered(&self) -> (String, Vec<Value>);
323}
324
325#[diagnostic::do_not_recommend]
326impl<S: Statement<Dialect = Postgres>> WriteStatement for S {
327 fn write_rendered(&self) -> (String, Vec<Value>) {
328 self.to_sql(Postgres)
329 }
330}
331
332pub trait ExecuteExt {
335 fn execute<'e, E: sqlx::PgExecutor<'e>>(
336 &self,
337 executor: E,
338 ) -> impl std::future::Future<Output = Result<u64>>
339 where
340 Self: WriteStatement,
341 {
342 let (sql, params) = self.write_rendered();
343 async move { execute_only(executor, &sql, params).await }
344 }
345}
346
347impl<D, Scope, Sel, Outer> ExecuteExt for Select<D, Scope, Sel, Outer> {}
348impl<S, Sel> ExecuteExt for Returning<S, Sel> {}
349impl<D, Output> ExecuteExt for DynSelect<D, Output> {}
350impl<D, Output> ExecuteExt for SetOp<D, Output> {}
351impl<D, R: qbrs_core::insert::InsertRow> ExecuteExt for Insert<D, R> {}
352impl<D, T: qbrs_core::scope::Table> ExecuteExt for Update<D, T> {}
353impl<D, T: qbrs_core::scope::Table> ExecuteExt for Delete<D, T> {}
354
355impl<S: Statement<Dialect = Postgres>, Sel, Idx> RowQuery<Idx> for Returning<S, Sel>
358where
359 Sel: Selection<WrittenTable<S::Table>, Idx>,
360 Sel::Output: DecodeRow,
361{
362 type Output = Sel::Output;
363 fn rendered(&self) -> (String, Vec<Value>) {
364 self.to_sql(Postgres)
365 }
366}
367
368#[diagnostic::on_unimplemented(
374 message = "`{Self}` isn't a value this crate can decode",
375 label = "every selected column has to decode to one of the six built-in natives, or to a type whose feature is on here as well as on `qbrs`",
376 note = "`chrono`/`uuid`/`decimal` have to be enabled on `qbrs-sqlx` too — they are separate `cfg`s over one `Value`"
377)]
378pub trait DecodeRow: Sized {
379 #[doc(hidden)]
380 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self>;
381}
382
383macro_rules! decode_row_leaf {
384 ($ty:ty) => {
385 impl DecodeRow for $ty {
386 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
387 let v = row.try_get::<$ty, _>(*idx)?;
388 *idx += 1;
389 Ok(v)
390 }
391 }
392 impl DecodeRow for Option<$ty> {
393 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
394 let v = row.try_get::<Option<$ty>, _>(*idx)?;
395 *idx += 1;
396 Ok(v)
397 }
398 }
399 };
400}
401decode_row_leaf!(i32);
402decode_row_leaf!(i64);
403decode_row_leaf!(f64);
404decode_row_leaf!(String);
405decode_row_leaf!(bool);
406decode_row_leaf!(Vec<u8>);
407#[cfg(feature = "chrono")]
408decode_row_leaf!(chrono::DateTime<chrono::Utc>);
409#[cfg(feature = "chrono")]
410decode_row_leaf!(chrono::NaiveDate);
411#[cfg(feature = "uuid")]
412decode_row_leaf!(uuid::Uuid);
413#[cfg(feature = "decimal")]
414decode_row_leaf!(rust_decimal::Decimal);
415
416impl DecodeRow for RowNil {
417 fn decode_at(_row: &PgRow, _idx: &mut usize) -> sqlx::Result<Self> {
418 Ok(RowNil)
419 }
420}
421
422impl<K, V: DecodeRow, Tail: DecodeRow> DecodeRow for RowCons<K, V, Tail> {
423 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
424 let value = V::decode_at(row, idx)?;
425 Ok(RowCons::new(value, Tail::decode_at(row, idx)?))
426 }
427}
428
429impl<L: DecodeRow> DecodeRow for Row<L> {
430 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
431 Ok(Row::new(L::decode_at(row, idx)?))
432 }
433}
434
435impl<Output: DecodeRow> RowQuery<()> for DynSelect<Postgres, Output> {
439 type Output = Output;
440 fn rendered(&self) -> (String, Vec<Value>) {
441 self.to_sql(Postgres)
442 }
443}
444
445impl<Output: DecodeRow> RowQuery<()> for SetOp<Postgres, Output> {
446 type Output = Output;
447 fn rendered(&self) -> (String, Vec<Value>) {
448 self.to_sql(Postgres)
449 }
450}
451
452#[diagnostic::on_unimplemented(
458 message = "`{Self}` isn't a prepared query this crate can run",
459 label = "a `.prepare()`-built query is — `Prepared<D, Params, Output>`, params before output — and its `Params` have to be the ones it declared"
460)]
461pub trait PreparedQuery<Params> {
462 type Output: DecodeRow;
463 #[doc(hidden)]
464 fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)>;
465}
466
467pub trait PreparedExt {
470 fn load<'e, Params, E: sqlx::PgExecutor<'e>>(
471 &self,
472 executor: E,
473 params: Params,
474 ) -> impl std::future::Future<Output = Result<Vec<<Self as PreparedQuery<Params>>::Output>>>
475 where
476 Self: PreparedQuery<Params>,
477 {
478 let resolved = self.resolved(params);
479 async move {
480 let (sql, values) = resolved?;
481 fetch_all::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values).await
482 }
483 }
484
485 fn load_one<'e, Params, E: sqlx::PgExecutor<'e>>(
486 &self,
487 executor: E,
488 params: Params,
489 ) -> impl std::future::Future<Output = Result<Option<<Self as PreparedQuery<Params>>::Output>>>
490 where
491 Self: PreparedQuery<Params>,
492 {
493 let resolved = self.resolved(params);
494 async move {
495 let (sql, values) = resolved?;
496 fetch_optional::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values)
497 .await
498 }
499 }
500}
501
502impl<D, Params, Output> PreparedExt for Prepared<D, Params, Output> {}
503
504impl<D, Params, Output> ExecuteExt for Prepared<D, Params, Output> {}
508
509#[diagnostic::do_not_recommend]
510impl<Params: PreparedParams, Output: DecodeRow> PreparedQuery<Params>
511 for Prepared<Postgres, Params, Output>
512{
513 type Output = Output;
514
515 fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)> {
516 Ok(self.resolve(params)?)
517 }
518}
519
520#[diagnostic::on_unimplemented(
523 message = "`{Self}` isn't a prepared total this crate can run",
524 label = "`.prepare_count()` builds one; `.prepare()` builds a query whose rows go through `.load(..)`"
525)]
526pub trait PreparedTotal<Params> {
527 #[doc(hidden)]
528 fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)>;
529}
530
531impl<Params: PreparedParams> PreparedTotal<Params> for Prepared<Postgres, Params, Total> {
532 fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)> {
533 Ok(self.resolve(params)?)
534 }
535}
536
537pub trait PreparedCountExt {
540 fn count<'e, Params, E: sqlx::PgExecutor<'e>>(
541 &self,
542 executor: E,
543 params: Params,
544 ) -> impl std::future::Future<Output = Result<i64>>
545 where
546 Self: PreparedTotal<Params>,
547 {
548 let resolved = self.resolved_count(params);
549 async move { count_rows(executor, resolved?).await }
550 }
551}
552
553impl<D, Params, Output> PreparedCountExt for Prepared<D, Params, Output> {}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558
559 #[test]
563 fn unresolved_placeholder_is_a_typed_error_not_a_sqlx_configuration_string() {
564 let query = sqlx::query(sqlx::AssertSqlSafe("SELECT $1"));
565 let err = match bind_all(query, vec![Value::Placeholder("email")]) {
566 Err(e) => e,
567 Ok(_) => panic!("unresolved placeholder must fail to bind"),
568 };
569
570 assert!(matches!(
571 err,
572 Error::UnresolvedPlaceholder(qbrs_core::select::UnresolvedPlaceholder("email"))
573 ));
574 let _: &dyn std::error::Error = &err;
577 assert_eq!(err.to_string(), "no value provided for placeholder `email`");
578 }
579
580 #[test]
581 fn sqlx_errors_convert_via_from() {
582 let err: Error = sqlx::Error::RowNotFound.into();
583 assert!(matches!(err, Error::Sqlx(sqlx::Error::RowNotFound)));
584 }
585}