1pub use futures_util::{Stream, StreamExt};
10use qbrs_core::delete::Delete;
11use qbrs_core::dialect::Postgres;
12use qbrs_core::expr::Value;
13use qbrs_core::insert::Insert;
14use qbrs_core::row::{Row, RowCons, RowNil};
15use qbrs_core::select::{DynSelect, Prepared, PreparedParams, Select, Selection, SetOp, Total};
16use qbrs_core::statement::{Returning, Statement, WrittenTable};
17use qbrs_core::update::Update;
18use sqlx::Row as _;
19use sqlx::postgres::PgRow;
20
21#[derive(Debug, thiserror::Error)]
25pub enum Error {
26 #[error(transparent)]
29 Sqlx(#[from] sqlx::Error),
30
31 #[error(transparent)]
35 UnresolvedPlaceholder(#[from] qbrs_core::select::UnresolvedPlaceholder),
36
37 #[error(transparent)]
42 NothingToSet(#[from] qbrs_core::update::NothingToSet),
43
44 #[error(transparent)]
45 NothingToInsert(#[from] qbrs_core::insert::NothingToInsert),
46
47 #[error("`{0}` values need the matching feature on `qbrs-sqlx` too")]
51 FeatureNotEnabled(&'static str),
52}
53
54pub type Result<T> = std::result::Result<T, Error>;
57
58pub mod prelude {
69 pub use crate::Error;
70 pub use crate::{
71 CountExt, CountQuery, DecodeRow, ExecuteExt, LoadExt, PreparedCountExt, PreparedExt,
72 PreparedQuery, PreparedTotal, RowQuery, Stream, StreamExt, WriteStatement,
73 };
74}
75
76fn bind_value<'q>(
83 query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
84 v: Value,
85) -> Result<sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>> {
86 Ok(match v {
87 Value::I32(x) => query.bind(x),
88 Value::I64(x) => query.bind(x),
89 Value::F64(x) => query.bind(x),
90 Value::Text(x) => query.bind(x),
91 Value::Bool(x) => query.bind(x),
92 Value::Bytes(x) => query.bind(x),
93 Value::NullI32 => query.bind(None::<i32>),
94 Value::NullI64 => query.bind(None::<i64>),
95 Value::NullF64 => query.bind(None::<f64>),
96 Value::NullText => query.bind(None::<String>),
97 Value::NullBool => query.bind(None::<bool>),
98 Value::NullBytes => query.bind(None::<Vec<u8>>),
99 Value::TextArray(x) => query.bind(x),
100 Value::NullTextArray => query.bind(None::<Vec<String>>),
101 Value::IntegerArray(x) => query.bind(x),
102 Value::NullIntegerArray => query.bind(None::<Vec<i32>>),
103 Value::BigIntArray(x) => query.bind(x),
104 Value::NullBigIntArray => query.bind(None::<Vec<i64>>),
105 #[cfg(feature = "uuid")]
106 Value::UuidArray(x) => query.bind(x),
107 #[cfg(feature = "uuid")]
108 Value::NullUuidArray => query.bind(None::<Vec<uuid::Uuid>>),
109 #[cfg(feature = "json")]
110 Value::Json(x) => query.bind(x),
111 #[cfg(feature = "json")]
112 Value::NullJson => query.bind(None::<serde_json::Value>),
113 #[cfg(feature = "chrono")]
114 Value::Timestamptz(x) => query.bind(x),
115 #[cfg(feature = "chrono")]
116 Value::NullTimestamptz => query.bind(None::<chrono::DateTime<chrono::Utc>>),
117 #[cfg(feature = "chrono")]
118 Value::Date(x) => query.bind(x),
119 #[cfg(feature = "chrono")]
120 Value::NullDate => query.bind(None::<chrono::NaiveDate>),
121 #[cfg(feature = "uuid")]
122 Value::Uuid(x) => query.bind(x),
123 #[cfg(feature = "uuid")]
124 Value::NullUuid => query.bind(None::<uuid::Uuid>),
125 #[cfg(feature = "decimal")]
126 Value::Numeric(x) => query.bind(x),
127 #[cfg(feature = "decimal")]
128 Value::NullNumeric => query.bind(None::<rust_decimal::Decimal>),
129 Value::Placeholder(name) => {
130 return Err(qbrs_core::select::UnresolvedPlaceholder(name).into());
131 }
132 #[allow(unreachable_patterns)]
135 other => return Err(Error::FeatureNotEnabled(other.type_name())),
136 })
137}
138
139fn bind_all<'q>(
140 mut query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
141 params: Vec<Value>,
142) -> Result<sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>> {
143 for p in params {
144 query = bind_value(query, p)?;
145 }
146 Ok(query)
147}
148
149async fn fetch_all<'e, T: DecodeRow, E: sqlx::PgExecutor<'e>>(
153 executor: E,
154 sql: &str,
155 params: Vec<Value>,
156) -> Result<Vec<T>> {
157 let rows = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
158 .fetch_all(executor)
159 .await?;
160 rows.iter()
161 .map(|row| T::decode_at(row, &mut 0).map_err(Error::from))
162 .collect()
163}
164
165async fn fetch_optional<'e, T: DecodeRow, E: sqlx::PgExecutor<'e>>(
166 executor: E,
167 sql: &str,
168 params: Vec<Value>,
169) -> Result<Option<T>> {
170 let row = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
171 .fetch_optional(executor)
172 .await?;
173 row.as_ref()
174 .map(|r| T::decode_at(r, &mut 0).map_err(Error::from))
175 .transpose()
176}
177
178fn fetch_stream<'e, T: DecodeRow + Send + 'e, E: sqlx::PgExecutor<'e>>(
182 executor: E,
183 sql: String,
184 params: Vec<Value>,
185) -> Result<impl Stream<Item = Result<T>> + Send + Unpin + 'e> {
186 let query = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?;
187 Ok(futures_util::StreamExt::map(query.fetch(executor), |row| {
188 T::decode_at(&row?, &mut 0).map_err(Error::from)
189 }))
190}
191
192async fn execute_only<'e, E: sqlx::PgExecutor<'e>>(
193 executor: E,
194 sql: &str,
195 params: Vec<Value>,
196) -> Result<u64> {
197 let result = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
198 .execute(executor)
199 .await?;
200 Ok(result.rows_affected())
201}
202
203#[diagnostic::on_unimplemented(
213 message = "`{Self}` isn't a query this crate can run",
214 label = "a `Select`, a `RETURNING`, a `DynSelect`, a set operation, or a `SELECT` with no `FROM`, in the `Postgres` dialect, whose values are all types `DecodeRow` covers"
215)]
216pub trait RowQuery<Idx> {
217 type Output: DecodeRow;
218
219 #[doc(hidden)]
220 fn rendered(&self) -> (String, Vec<Value>);
221}
222
223pub trait LoadExt {
237 fn load<'e, Idx, E: sqlx::PgExecutor<'e>>(
238 &self,
239 executor: E,
240 ) -> impl std::future::Future<Output = Result<Vec<<Self as RowQuery<Idx>>::Output>>>
241 where
242 Self: RowQuery<Idx>,
243 {
244 let (sql, params) = self.rendered();
245 async move { fetch_all::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
246 }
247
248 fn load_one<'e, Idx, E: sqlx::PgExecutor<'e>>(
249 &self,
250 executor: E,
251 ) -> impl std::future::Future<Output = Result<Option<<Self as RowQuery<Idx>>::Output>>>
252 where
253 Self: RowQuery<Idx>,
254 {
255 let (sql, params) = self.rendered();
256 async move { fetch_optional::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
257 }
258
259 fn stream<'e, Idx, E: sqlx::PgExecutor<'e>>(
278 &self,
279 executor: E,
280 ) -> Result<impl Stream<Item = Result<<Self as RowQuery<Idx>>::Output>> + Send + Unpin + 'e>
281 where
282 Self: RowQuery<Idx>,
283 <Self as RowQuery<Idx>>::Output: Send + 'e,
284 {
285 let (sql, params) = self.rendered();
286 fetch_stream::<<Self as RowQuery<Idx>>::Output, E>(executor, sql, params)
287 }
288}
289
290impl<D, Scope, Sel, Outer> LoadExt for Select<D, Scope, Sel, Outer> {}
291impl<Sel> LoadExt for qbrs_core::select::SelectSeed<Sel> {}
292impl<S, Sel> LoadExt for Returning<S, Sel> {}
293impl<D, Output> LoadExt for DynSelect<D, Output> {}
294impl<D, Output> LoadExt for SetOp<D, Output> {}
295impl<D, R: qbrs_core::insert::InsertRow> LoadExt for Insert<D, R> {}
296impl<D, T: qbrs_core::scope::Table> LoadExt for qbrs_core::insert::InsertSelect<D, T> {}
297impl<D, T: qbrs_core::scope::Table> LoadExt for Update<D, T> {}
298impl<D, T: qbrs_core::scope::Table> LoadExt for Delete<D, T> {}
299
300impl<Scope, Sel, Idx> RowQuery<Idx> for Select<Postgres, Scope, Sel>
301where
302 Sel: Selection<Scope, Idx>,
303 Sel::Output: DecodeRow,
304{
305 type Output = Sel::Output;
306
307 fn rendered(&self) -> (String, Vec<Value>) {
308 self.to_sql::<Idx>(Postgres)
309 }
310}
311
312impl<Sel, Idx> RowQuery<Idx> for qbrs_core::select::SelectSeed<Sel>
315where
316 Sel: Selection<qbrs_core::scope::Nil, Idx>,
317 Sel::Output: DecodeRow,
318{
319 type Output = Sel::Output;
320
321 fn rendered(&self) -> (String, Vec<Value>) {
322 self.to_sql::<Postgres, Idx>(Postgres)
323 }
324}
325
326#[diagnostic::on_unimplemented(
331 message = "`{Self}` isn't a query this crate can count",
332 label = "a `Select`, a `DynSelect` or a set operation in the `Postgres` dialect is; a writing statement reports rows affected through `.execute(..)` instead. A `SELECT` with no `FROM` returns one row, so this error usually means a `.from(..)` was left off"
333)]
334pub trait CountQuery<Idx> {
335 #[doc(hidden)]
336 fn count_rendered(&self) -> (String, Vec<Value>);
337}
338
339pub trait CountExt {
342 fn count<'e, Idx, E: sqlx::PgExecutor<'e>>(
343 &self,
344 executor: E,
345 ) -> impl std::future::Future<Output = Result<i64>>
346 where
347 Self: CountQuery<Idx>,
348 {
349 count_rows(executor, self.count_rendered())
350 }
351}
352
353impl<D, Scope, Sel, Outer> CountExt for Select<D, Scope, Sel, Outer> {}
354impl<Sel> CountExt for qbrs_core::select::SelectSeed<Sel> {}
355impl<S, Sel> CountExt for Returning<S, Sel> {}
356impl<D, Output> CountExt for DynSelect<D, Output> {}
357impl<D, Output> CountExt for SetOp<D, Output> {}
358impl<D, R: qbrs_core::insert::InsertRow> CountExt for Insert<D, R> {}
359impl<D, T: qbrs_core::scope::Table> CountExt for qbrs_core::insert::InsertSelect<D, T> {}
360impl<D, T: qbrs_core::scope::Table> CountExt for Update<D, T> {}
361impl<D, T: qbrs_core::scope::Table> CountExt for Delete<D, T> {}
362
363impl<Scope, Sel: Selection<Scope, Idx>, Idx> CountQuery<Idx> for Select<Postgres, Scope, Sel> {
364 fn count_rendered(&self) -> (String, Vec<Value>) {
365 self.count_sql::<Idx>(Postgres)
366 }
367}
368
369impl<Output> CountQuery<()> for DynSelect<Postgres, Output> {
373 fn count_rendered(&self) -> (String, Vec<Value>) {
374 self.count_sql(Postgres)
375 }
376}
377
378impl<Output> CountQuery<()> for SetOp<Postgres, Output> {
379 fn count_rendered(&self) -> (String, Vec<Value>) {
380 self.count_sql(Postgres)
381 }
382}
383
384async fn count_rows<'e, E: sqlx::PgExecutor<'e>>(
385 executor: E,
386 (sql, params): (String, Vec<Value>),
387) -> Result<i64> {
388 let row = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql.as_str())), params)?
389 .fetch_one(executor)
390 .await?;
391 Ok(row.try_get::<i64, _>(0)?)
392}
393
394#[diagnostic::on_unimplemented(
397 message = "`{Self}` isn't a statement this crate can execute",
398 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)`"
399)]
400pub trait WriteStatement {
401 #[doc(hidden)]
402 fn write_rendered(&self) -> (String, Vec<Value>);
403}
404
405#[diagnostic::do_not_recommend]
406impl<S: Statement<Dialect = Postgres>> WriteStatement for S {
407 fn write_rendered(&self) -> (String, Vec<Value>) {
408 self.to_sql(Postgres)
409 }
410}
411
412pub trait ExecuteExt {
415 fn execute<'e, E: sqlx::PgExecutor<'e>>(
416 &self,
417 executor: E,
418 ) -> impl std::future::Future<Output = Result<u64>>
419 where
420 Self: WriteStatement,
421 {
422 let (sql, params) = self.write_rendered();
423 async move { execute_only(executor, &sql, params).await }
424 }
425}
426
427impl<D, Scope, Sel, Outer> ExecuteExt for Select<D, Scope, Sel, Outer> {}
428impl<Sel> ExecuteExt for qbrs_core::select::SelectSeed<Sel> {}
429impl<S, Sel> ExecuteExt for Returning<S, Sel> {}
430impl<D, Output> ExecuteExt for DynSelect<D, Output> {}
431impl<D, Output> ExecuteExt for SetOp<D, Output> {}
432impl<D, R: qbrs_core::insert::InsertRow> ExecuteExt for Insert<D, R> {}
433impl<D, T: qbrs_core::scope::Table> ExecuteExt for qbrs_core::insert::InsertSelect<D, T> {}
434impl<D, T: qbrs_core::scope::Table> ExecuteExt for Update<D, T> {}
435impl<D, T: qbrs_core::scope::Table> ExecuteExt for Delete<D, T> {}
436
437impl<S: Statement<Dialect = Postgres>, Sel, Idx> RowQuery<Idx> for Returning<S, Sel>
440where
441 Sel: Selection<WrittenTable<S::Table>, Idx>,
442 Sel::Output: DecodeRow,
443{
444 type Output = Sel::Output;
445 fn rendered(&self) -> (String, Vec<Value>) {
446 self.to_sql(Postgres)
447 }
448}
449
450#[diagnostic::on_unimplemented(
456 message = "`{Self}` isn't a value this crate can decode",
457 label = "every selected column has to decode to one of the featureless natives, or to a type whose feature is on here as well as on `qbrs`",
458 note = "`chrono`/`uuid`/`decimal`/`json` have to be enabled on `qbrs-sqlx` too: they are separate `cfg`s over one `Value`"
459)]
460pub trait DecodeRow: Sized {
461 #[doc(hidden)]
462 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self>;
463}
464
465macro_rules! decode_row_leaf {
466 ($ty:ty) => {
467 impl DecodeRow for $ty {
468 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
469 let v = row.try_get::<$ty, _>(*idx)?;
470 *idx += 1;
471 Ok(v)
472 }
473 }
474 impl DecodeRow for Option<$ty> {
475 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
476 let v = row.try_get::<Option<$ty>, _>(*idx)?;
477 *idx += 1;
478 Ok(v)
479 }
480 }
481 };
482}
483decode_row_leaf!(i32);
484decode_row_leaf!(i64);
485decode_row_leaf!(f64);
486decode_row_leaf!(String);
487decode_row_leaf!(bool);
488decode_row_leaf!(Vec<u8>);
489decode_row_leaf!(Vec<String>);
490decode_row_leaf!(Vec<i32>);
491decode_row_leaf!(Vec<i64>);
492#[cfg(feature = "uuid")]
493decode_row_leaf!(Vec<uuid::Uuid>);
494#[cfg(feature = "json")]
495decode_row_leaf!(serde_json::Value);
496#[cfg(feature = "chrono")]
497decode_row_leaf!(chrono::DateTime<chrono::Utc>);
498#[cfg(feature = "chrono")]
499decode_row_leaf!(chrono::NaiveDate);
500#[cfg(feature = "uuid")]
501decode_row_leaf!(uuid::Uuid);
502#[cfg(feature = "decimal")]
503decode_row_leaf!(rust_decimal::Decimal);
504
505impl DecodeRow for RowNil {
506 fn decode_at(_row: &PgRow, _idx: &mut usize) -> sqlx::Result<Self> {
507 Ok(RowNil)
508 }
509}
510
511impl<K, V: DecodeRow, Tail: DecodeRow> DecodeRow for RowCons<K, V, Tail> {
512 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
513 let value = V::decode_at(row, idx)?;
514 Ok(RowCons::new(value, Tail::decode_at(row, idx)?))
515 }
516}
517
518impl<L: DecodeRow> DecodeRow for Row<L> {
519 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
520 Ok(Row::new(L::decode_at(row, idx)?))
521 }
522}
523
524impl<Output: DecodeRow> RowQuery<()> for DynSelect<Postgres, Output> {
528 type Output = Output;
529 fn rendered(&self) -> (String, Vec<Value>) {
530 self.to_sql(Postgres)
531 }
532}
533
534impl<Output: DecodeRow> RowQuery<()> for SetOp<Postgres, Output> {
535 type Output = Output;
536 fn rendered(&self) -> (String, Vec<Value>) {
537 self.to_sql(Postgres)
538 }
539}
540
541#[diagnostic::on_unimplemented(
547 message = "`{Self}` isn't a prepared query this crate can run",
548 label = "a `.prepare()`-built query is `Prepared<D, Params, Output>` (params before output), and its `Params` have to be the ones it declared"
549)]
550pub trait PreparedQuery<Params> {
551 type Output: DecodeRow;
552 #[doc(hidden)]
553 fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)>;
554}
555
556pub trait PreparedExt {
559 fn load<'e, Params, E: sqlx::PgExecutor<'e>>(
560 &self,
561 executor: E,
562 params: Params,
563 ) -> impl std::future::Future<Output = Result<Vec<<Self as PreparedQuery<Params>>::Output>>>
564 where
565 Self: PreparedQuery<Params>,
566 {
567 let resolved = self.resolved(params);
568 async move {
569 let (sql, values) = resolved?;
570 fetch_all::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values).await
571 }
572 }
573
574 fn load_one<'e, Params, E: sqlx::PgExecutor<'e>>(
575 &self,
576 executor: E,
577 params: Params,
578 ) -> impl std::future::Future<Output = Result<Option<<Self as PreparedQuery<Params>>::Output>>>
579 where
580 Self: PreparedQuery<Params>,
581 {
582 let resolved = self.resolved(params);
583 async move {
584 let (sql, values) = resolved?;
585 fetch_optional::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values)
586 .await
587 }
588 }
589
590 fn stream<'e, Params, E: sqlx::PgExecutor<'e>>(
594 &self,
595 executor: E,
596 params: Params,
597 ) -> Result<
598 impl Stream<Item = Result<<Self as PreparedQuery<Params>>::Output>> + Send + Unpin + 'e,
599 >
600 where
601 Self: PreparedQuery<Params>,
602 <Self as PreparedQuery<Params>>::Output: Send + 'e,
603 {
604 let (sql, values) = self.resolved(params)?;
605 fetch_stream::<<Self as PreparedQuery<Params>>::Output, E>(executor, sql, values)
606 }
607}
608
609impl<D, Params, Output> PreparedExt for Prepared<D, Params, Output> {}
610
611impl<D, Params, Output> ExecuteExt for Prepared<D, Params, Output> {}
615
616#[diagnostic::do_not_recommend]
617impl<Params: PreparedParams, Output: DecodeRow> PreparedQuery<Params>
618 for Prepared<Postgres, Params, Output>
619{
620 type Output = Output;
621
622 fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)> {
623 Ok(self.resolve(params)?)
624 }
625}
626
627#[diagnostic::on_unimplemented(
630 message = "`{Self}` isn't a prepared total this crate can run",
631 label = "`.prepare_count()` builds one; `.prepare()` builds a query whose rows go through `.load(..)`"
632)]
633pub trait PreparedTotal<Params> {
634 #[doc(hidden)]
635 fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)>;
636}
637
638impl<Params: PreparedParams> PreparedTotal<Params> for Prepared<Postgres, Params, Total> {
639 fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)> {
640 Ok(self.resolve(params)?)
641 }
642}
643
644pub trait PreparedCountExt {
647 fn count<'e, Params, E: sqlx::PgExecutor<'e>>(
648 &self,
649 executor: E,
650 params: Params,
651 ) -> impl std::future::Future<Output = Result<i64>>
652 where
653 Self: PreparedTotal<Params>,
654 {
655 let resolved = self.resolved_count(params);
656 async move { count_rows(executor, resolved?).await }
657 }
658}
659
660impl<D, Params, Output> PreparedCountExt for Prepared<D, Params, Output> {}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665
666 #[test]
670 fn unresolved_placeholder_is_a_typed_error_not_a_sqlx_configuration_string() {
671 let query = sqlx::query(sqlx::AssertSqlSafe("SELECT $1"));
672 let err = match bind_all(query, vec![Value::Placeholder("email")]) {
673 Err(e) => e,
674 Ok(_) => panic!("unresolved placeholder must fail to bind"),
675 };
676
677 assert!(matches!(
678 err,
679 Error::UnresolvedPlaceholder(qbrs_core::select::UnresolvedPlaceholder("email"))
680 ));
681 let _: &dyn std::error::Error = &err;
684 assert_eq!(err.to_string(), "no value provided for placeholder `email`");
685 }
686
687 #[test]
688 fn sqlx_errors_convert_via_from() {
689 let err: Error = sqlx::Error::RowNotFound.into();
690 assert!(matches!(err, Error::Sqlx(sqlx::Error::RowNotFound)));
691 }
692}