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(
214 message = "`{Self}` isn't a query this crate can run",
215 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"
216)]
217pub trait RowQuery<Idx> {
218 type Output: DecodeRow;
219
220 #[doc(hidden)]
221 fn rendered(&self) -> (String, Vec<Value>);
222}
223
224pub trait LoadExt {
238 fn load<'e, Idx, E: sqlx::PgExecutor<'e>>(
239 &self,
240 executor: E,
241 ) -> impl std::future::Future<Output = Result<Vec<<Self as RowQuery<Idx>>::Output>>>
242 where
243 Self: RowQuery<Idx>,
244 {
245 let (sql, params) = self.rendered();
246 async move { fetch_all::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
247 }
248
249 fn load_one<'e, Idx, E: sqlx::PgExecutor<'e>>(
250 &self,
251 executor: E,
252 ) -> impl std::future::Future<Output = Result<Option<<Self as RowQuery<Idx>>::Output>>>
253 where
254 Self: RowQuery<Idx>,
255 {
256 let (sql, params) = self.rendered();
257 async move { fetch_optional::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
258 }
259
260 fn stream<'e, Idx, E: sqlx::PgExecutor<'e>>(
279 &self,
280 executor: E,
281 ) -> Result<impl Stream<Item = Result<<Self as RowQuery<Idx>>::Output>> + Send + Unpin + 'e>
282 where
283 Self: RowQuery<Idx>,
284 <Self as RowQuery<Idx>>::Output: Send + 'e,
285 {
286 let (sql, params) = self.rendered();
287 fetch_stream::<<Self as RowQuery<Idx>>::Output, E>(executor, sql, params)
288 }
289}
290
291impl<D, Scope, Sel, Outer> LoadExt for Select<D, Scope, Sel, Outer> {}
292impl<Sel> LoadExt for qbrs_core::select::SelectSeed<Sel> {}
293impl<S, Sel> LoadExt for Returning<S, Sel> {}
294impl<D, Output> LoadExt for DynSelect<D, Output> {}
295impl<D, Output> LoadExt for SetOp<D, Output> {}
296impl<D, R: qbrs_core::insert::InsertRow> LoadExt for Insert<D, R> {}
297impl<D, T: qbrs_core::scope::Table> LoadExt for qbrs_core::insert::InsertSelect<D, T> {}
298impl<D, T: qbrs_core::scope::Table> LoadExt for Update<D, T> {}
299impl<D, T: qbrs_core::scope::Table> LoadExt for Delete<D, T> {}
300
301impl<Scope, Sel, Idx> RowQuery<Idx> for Select<Postgres, Scope, Sel>
302where
303 Sel: Selection<Scope, Idx>,
304 Sel::Output: DecodeRow,
305{
306 type Output = Sel::Output;
307
308 fn rendered(&self) -> (String, Vec<Value>) {
309 self.to_sql::<Idx>(Postgres)
310 }
311}
312
313impl<Sel, Idx> RowQuery<Idx> for qbrs_core::select::SelectSeed<Sel>
316where
317 Sel: Selection<qbrs_core::scope::Nil, Idx>,
318 Sel::Output: DecodeRow,
319{
320 type Output = Sel::Output;
321
322 fn rendered(&self) -> (String, Vec<Value>) {
323 self.to_sql::<Postgres, Idx>(Postgres)
324 }
325}
326
327#[diagnostic::on_unimplemented(
332 message = "`{Self}` isn't a query this crate can count",
333 label = "a `Select`, a `DynSelect` or a set operation in the `Postgres` dialect is; a writing statement reports rows affected through `.execute(..)` instead, and a `SELECT` with no `FROM` returns one row — a query missing its `.from(..)` is what this usually means"
334)]
335pub trait CountQuery<Idx> {
336 #[doc(hidden)]
337 fn count_rendered(&self) -> (String, Vec<Value>);
338}
339
340pub trait CountExt {
343 fn count<'e, Idx, E: sqlx::PgExecutor<'e>>(
344 &self,
345 executor: E,
346 ) -> impl std::future::Future<Output = Result<i64>>
347 where
348 Self: CountQuery<Idx>,
349 {
350 count_rows(executor, self.count_rendered())
351 }
352}
353
354impl<D, Scope, Sel, Outer> CountExt for Select<D, Scope, Sel, Outer> {}
355impl<Sel> CountExt for qbrs_core::select::SelectSeed<Sel> {}
356impl<S, Sel> CountExt for Returning<S, Sel> {}
357impl<D, Output> CountExt for DynSelect<D, Output> {}
358impl<D, Output> CountExt for SetOp<D, Output> {}
359impl<D, R: qbrs_core::insert::InsertRow> CountExt for Insert<D, R> {}
360impl<D, T: qbrs_core::scope::Table> CountExt for qbrs_core::insert::InsertSelect<D, T> {}
361impl<D, T: qbrs_core::scope::Table> CountExt for Update<D, T> {}
362impl<D, T: qbrs_core::scope::Table> CountExt for Delete<D, T> {}
363
364impl<Scope, Sel: Selection<Scope, Idx>, Idx> CountQuery<Idx> for Select<Postgres, Scope, Sel> {
365 fn count_rendered(&self) -> (String, Vec<Value>) {
366 self.count_sql::<Idx>(Postgres)
367 }
368}
369
370impl<Output> CountQuery<()> for DynSelect<Postgres, Output> {
374 fn count_rendered(&self) -> (String, Vec<Value>) {
375 self.count_sql(Postgres)
376 }
377}
378
379impl<Output> CountQuery<()> for SetOp<Postgres, Output> {
380 fn count_rendered(&self) -> (String, Vec<Value>) {
381 self.count_sql(Postgres)
382 }
383}
384
385async fn count_rows<'e, E: sqlx::PgExecutor<'e>>(
386 executor: E,
387 (sql, params): (String, Vec<Value>),
388) -> Result<i64> {
389 let row = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql.as_str())), params)?
390 .fetch_one(executor)
391 .await?;
392 Ok(row.try_get::<i64, _>(0)?)
393}
394
395#[diagnostic::on_unimplemented(
398 message = "`{Self}` isn't a statement this crate can execute",
399 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)`"
400)]
401pub trait WriteStatement {
402 #[doc(hidden)]
403 fn write_rendered(&self) -> (String, Vec<Value>);
404}
405
406#[diagnostic::do_not_recommend]
407impl<S: Statement<Dialect = Postgres>> WriteStatement for S {
408 fn write_rendered(&self) -> (String, Vec<Value>) {
409 self.to_sql(Postgres)
410 }
411}
412
413pub trait ExecuteExt {
416 fn execute<'e, E: sqlx::PgExecutor<'e>>(
417 &self,
418 executor: E,
419 ) -> impl std::future::Future<Output = Result<u64>>
420 where
421 Self: WriteStatement,
422 {
423 let (sql, params) = self.write_rendered();
424 async move { execute_only(executor, &sql, params).await }
425 }
426}
427
428impl<D, Scope, Sel, Outer> ExecuteExt for Select<D, Scope, Sel, Outer> {}
429impl<Sel> ExecuteExt for qbrs_core::select::SelectSeed<Sel> {}
430impl<S, Sel> ExecuteExt for Returning<S, Sel> {}
431impl<D, Output> ExecuteExt for DynSelect<D, Output> {}
432impl<D, Output> ExecuteExt for SetOp<D, Output> {}
433impl<D, R: qbrs_core::insert::InsertRow> ExecuteExt for Insert<D, R> {}
434impl<D, T: qbrs_core::scope::Table> ExecuteExt for qbrs_core::insert::InsertSelect<D, T> {}
435impl<D, T: qbrs_core::scope::Table> ExecuteExt for Update<D, T> {}
436impl<D, T: qbrs_core::scope::Table> ExecuteExt for Delete<D, T> {}
437
438impl<S: Statement<Dialect = Postgres>, Sel, Idx> RowQuery<Idx> for Returning<S, Sel>
441where
442 Sel: Selection<WrittenTable<S::Table>, Idx>,
443 Sel::Output: DecodeRow,
444{
445 type Output = Sel::Output;
446 fn rendered(&self) -> (String, Vec<Value>) {
447 self.to_sql(Postgres)
448 }
449}
450
451#[diagnostic::on_unimplemented(
457 message = "`{Self}` isn't a value this crate can decode",
458 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`",
459 note = "`chrono`/`uuid`/`decimal`/`json` have to be enabled on `qbrs-sqlx` too — they are separate `cfg`s over one `Value`"
460)]
461pub trait DecodeRow: Sized {
462 #[doc(hidden)]
463 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self>;
464}
465
466macro_rules! decode_row_leaf {
467 ($ty:ty) => {
468 impl DecodeRow for $ty {
469 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
470 let v = row.try_get::<$ty, _>(*idx)?;
471 *idx += 1;
472 Ok(v)
473 }
474 }
475 impl DecodeRow for Option<$ty> {
476 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
477 let v = row.try_get::<Option<$ty>, _>(*idx)?;
478 *idx += 1;
479 Ok(v)
480 }
481 }
482 };
483}
484decode_row_leaf!(i32);
485decode_row_leaf!(i64);
486decode_row_leaf!(f64);
487decode_row_leaf!(String);
488decode_row_leaf!(bool);
489decode_row_leaf!(Vec<u8>);
490decode_row_leaf!(Vec<String>);
491decode_row_leaf!(Vec<i32>);
492decode_row_leaf!(Vec<i64>);
493#[cfg(feature = "uuid")]
494decode_row_leaf!(Vec<uuid::Uuid>);
495#[cfg(feature = "json")]
496decode_row_leaf!(serde_json::Value);
497#[cfg(feature = "chrono")]
498decode_row_leaf!(chrono::DateTime<chrono::Utc>);
499#[cfg(feature = "chrono")]
500decode_row_leaf!(chrono::NaiveDate);
501#[cfg(feature = "uuid")]
502decode_row_leaf!(uuid::Uuid);
503#[cfg(feature = "decimal")]
504decode_row_leaf!(rust_decimal::Decimal);
505
506impl DecodeRow for RowNil {
507 fn decode_at(_row: &PgRow, _idx: &mut usize) -> sqlx::Result<Self> {
508 Ok(RowNil)
509 }
510}
511
512impl<K, V: DecodeRow, Tail: DecodeRow> DecodeRow for RowCons<K, V, Tail> {
513 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
514 let value = V::decode_at(row, idx)?;
515 Ok(RowCons::new(value, Tail::decode_at(row, idx)?))
516 }
517}
518
519impl<L: DecodeRow> DecodeRow for Row<L> {
520 fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
521 Ok(Row::new(L::decode_at(row, idx)?))
522 }
523}
524
525impl<Output: DecodeRow> RowQuery<()> for DynSelect<Postgres, Output> {
529 type Output = Output;
530 fn rendered(&self) -> (String, Vec<Value>) {
531 self.to_sql(Postgres)
532 }
533}
534
535impl<Output: DecodeRow> RowQuery<()> for SetOp<Postgres, Output> {
536 type Output = Output;
537 fn rendered(&self) -> (String, Vec<Value>) {
538 self.to_sql(Postgres)
539 }
540}
541
542#[diagnostic::on_unimplemented(
548 message = "`{Self}` isn't a prepared query this crate can run",
549 label = "a `.prepare()`-built query is — `Prepared<D, Params, Output>`, params before output — and its `Params` have to be the ones it declared"
550)]
551pub trait PreparedQuery<Params> {
552 type Output: DecodeRow;
553 #[doc(hidden)]
554 fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)>;
555}
556
557pub trait PreparedExt {
560 fn load<'e, Params, E: sqlx::PgExecutor<'e>>(
561 &self,
562 executor: E,
563 params: Params,
564 ) -> impl std::future::Future<Output = Result<Vec<<Self as PreparedQuery<Params>>::Output>>>
565 where
566 Self: PreparedQuery<Params>,
567 {
568 let resolved = self.resolved(params);
569 async move {
570 let (sql, values) = resolved?;
571 fetch_all::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values).await
572 }
573 }
574
575 fn load_one<'e, Params, E: sqlx::PgExecutor<'e>>(
576 &self,
577 executor: E,
578 params: Params,
579 ) -> impl std::future::Future<Output = Result<Option<<Self as PreparedQuery<Params>>::Output>>>
580 where
581 Self: PreparedQuery<Params>,
582 {
583 let resolved = self.resolved(params);
584 async move {
585 let (sql, values) = resolved?;
586 fetch_optional::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values)
587 .await
588 }
589 }
590
591 fn stream<'e, Params, E: sqlx::PgExecutor<'e>>(
595 &self,
596 executor: E,
597 params: Params,
598 ) -> Result<
599 impl Stream<Item = Result<<Self as PreparedQuery<Params>>::Output>> + Send + Unpin + 'e,
600 >
601 where
602 Self: PreparedQuery<Params>,
603 <Self as PreparedQuery<Params>>::Output: Send + 'e,
604 {
605 let (sql, values) = self.resolved(params)?;
606 fetch_stream::<<Self as PreparedQuery<Params>>::Output, E>(executor, sql, values)
607 }
608}
609
610impl<D, Params, Output> PreparedExt for Prepared<D, Params, Output> {}
611
612impl<D, Params, Output> ExecuteExt for Prepared<D, Params, Output> {}
616
617#[diagnostic::do_not_recommend]
618impl<Params: PreparedParams, Output: DecodeRow> PreparedQuery<Params>
619 for Prepared<Postgres, Params, Output>
620{
621 type Output = Output;
622
623 fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)> {
624 Ok(self.resolve(params)?)
625 }
626}
627
628#[diagnostic::on_unimplemented(
631 message = "`{Self}` isn't a prepared total this crate can run",
632 label = "`.prepare_count()` builds one; `.prepare()` builds a query whose rows go through `.load(..)`"
633)]
634pub trait PreparedTotal<Params> {
635 #[doc(hidden)]
636 fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)>;
637}
638
639impl<Params: PreparedParams> PreparedTotal<Params> for Prepared<Postgres, Params, Total> {
640 fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)> {
641 Ok(self.resolve(params)?)
642 }
643}
644
645pub trait PreparedCountExt {
648 fn count<'e, Params, E: sqlx::PgExecutor<'e>>(
649 &self,
650 executor: E,
651 params: Params,
652 ) -> impl std::future::Future<Output = Result<i64>>
653 where
654 Self: PreparedTotal<Params>,
655 {
656 let resolved = self.resolved_count(params);
657 async move { count_rows(executor, resolved?).await }
658 }
659}
660
661impl<D, Params, Output> PreparedCountExt for Prepared<D, Params, Output> {}
662
663#[cfg(test)]
664mod tests {
665 use super::*;
666
667 #[test]
671 fn unresolved_placeholder_is_a_typed_error_not_a_sqlx_configuration_string() {
672 let query = sqlx::query(sqlx::AssertSqlSafe("SELECT $1"));
673 let err = match bind_all(query, vec![Value::Placeholder("email")]) {
674 Err(e) => e,
675 Ok(_) => panic!("unresolved placeholder must fail to bind"),
676 };
677
678 assert!(matches!(
679 err,
680 Error::UnresolvedPlaceholder(qbrs_core::select::UnresolvedPlaceholder("email"))
681 ));
682 let _: &dyn std::error::Error = &err;
685 assert_eq!(err.to_string(), "no value provided for placeholder `email`");
686 }
687
688 #[test]
689 fn sqlx_errors_convert_via_from() {
690 let err: Error = sqlx::Error::RowNotFound.into();
691 assert!(matches!(err, Error::Sqlx(sqlx::Error::RowNotFound)));
692 }
693}