Skip to main content

qbrs_sqlx/
lib.rs

1//! Execution integration between qbrs's query builder and a real Postgres
2//! via `sqlx`. This crate owns only the value-binding and row-decoding glue;
3//! query building, SQL rendering, and every compile-time guarantee live in
4//! `qbrs-core`, which stays independent of any async runtime or driver.
5
6/// Re-exported so a caller can name what `LoadExt::stream` returns and
7/// consume it — `StreamExt::next` is how a stream is read — without taking
8/// a `futures` dependency of their own.
9pub 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/// Errors from executing a qbrs query against Postgres via `sqlx`. An enum
22/// rather than a bare `sqlx::Error` so a qbrs-level misuse is distinguishable
23/// from a driver/database error without string-matching a message.
24#[derive(Debug, thiserror::Error)]
25pub enum Error {
26    /// A real error from Postgres or the `sqlx` driver: a failed
27    /// connection, constraint violation, decode failure, etc.
28    #[error(transparent)]
29    Sqlx(#[from] sqlx::Error),
30
31    /// A `prepare!{}` placeholder reached execution unresolved: either
32    /// `Prepared::resolve()` found no matching field in `Params`, or the
33    /// query was executed directly instead of through `.prepare()`.
34    #[error(transparent)]
35    UnresolvedPlaceholder(#[from] qbrs_core::select::UnresolvedPlaceholder),
36
37    /// An `*Update` describing no assignment, or an insert of no rows —
38    /// caught where the request-shaped data is read (`Assignments::from_row`,
39    /// `.values_all`), never at a statement. Here so a handler returning
40    /// this crate's `Result` can `?` on that as readily as on a query.
41    #[error(transparent)]
42    NothingToSet(#[from] qbrs_core::update::NothingToSet),
43
44    #[error(transparent)]
45    NothingToInsert(#[from] qbrs_core::insert::NothingToInsert),
46
47    /// A column type is enabled on `qbrs` but not on `qbrs-sqlx`, so the
48    /// value renders and has nothing to bind it. The two crates carry the
49    /// same feature names for exactly this reason — turn it on in both.
50    #[error("`{0}` values need the matching feature on `qbrs-sqlx` too")]
51    FeatureNotEnabled(&'static str),
52}
53
54/// This crate's `Result`: the same shape as `sqlx::Result`, with
55/// `qbrs_sqlx::Error` as the fixed error type.
56pub type Result<T> = std::result::Result<T, Error>;
57
58/// Every extension trait that puts a terminal method on a builder, plus the
59/// error type a caller's own signatures have to name and the `DecodeRow`
60/// bound a generic helper over `RowQuery` has to spell. `Result` is
61/// deliberately absent: a glob-imported alias of that name shadows
62/// `std::result::Result` in every module that follows, and a service layer
63/// has its own error type in most of them — write `qbrs_sqlx::Result<T>`
64/// where the alias is wanted. Which trait applies
65/// depends on the builder, so importing them one at a time is bookkeeping
66/// with no decision in it — and `count` in particular resolves against
67/// `Iterator::count` with a confusing message until `CountExt` is in scope.
68pub 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
76/// Binds a `Value` to a Postgres query parameter. `Value`'s typed `NullX`
77/// variants carry the parameter type a NULL bind still has to declare.
78///
79/// Fallible only for `Value::Placeholder`: an unresolved named placeholder
80/// is a misuse no compile-time check here can catch, so it surfaces as
81/// `Error::UnresolvedPlaceholder` rather than a wrong bind or a panic.
82fn 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        // Reachable only when a column type is on in `qbrs-core` and off
133        // here: the variant exists, the arm that binds it doesn't.
134        #[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
149/// Generic over `E: sqlx::PgExecutor` so every `.load()`/`.execute()` works
150/// against a `&PgPool` or a transaction alike. sqlx implements `Executor` for
151/// `&mut PgConnection`, not `Transaction`, so callers pass `&mut *tx`.
152async 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
178/// The streaming counterpart: rows are decoded as they arrive rather than
179/// collected first. The query owns its SQL and its binds, so the stream
180/// borrows only the executor.
181fn 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/// What a row-producing query renders to, and what its rows decode to: a
204/// `SELECT`, a `RETURNING` clause, an erased `DynSelect`, a `UNION` chain.
205/// `LoadExt` is the methods over it, and the split is load-bearing
206/// — with the validity bound on the impl instead, an invalid selection
207/// makes `.load(..)` not exist, and the scope error the builder wanted to
208/// report is replaced by a method-resolution failure that never mentions
209/// the table.
210///
211/// `Idx` is threaded through the trait's parameter list for the reason
212/// `scope::Superset` explains. Callers never see it; it's inferred.
213#[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
224/// `load` for the rows, `load_one` for the first of them, `stream` for
225/// them one at a time, and `ExecuteExt::execute` where there are none to
226/// decode. One trait for
227/// every row-producing builder keeps the terminal vocabulary tied to what a
228/// statement yields rather than to which builder happens to be in hand.
229///
230/// Implemented for every builder, satisfiable by the ones that produce
231/// rows: what a builder can't do is then reported by `RowQuery`, which says
232/// so, rather than by the method not existing — which rustc answers with a
233/// list of unsatisfied bounds or, worse, by suggesting `Iterator`. Not a
234/// blanket impl, since `load`/`count`/`execute` are names other traits in a
235/// caller's scope have too. A builder added here needs its three empty
236/// impls, or its terminal goes back to reporting nothing.
237pub 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    /// The rows one at a time, for a result too large to hold: an export
261    /// that writes as it reads rather than collecting first. Yields the
262    /// same values `.load(..)` would, decoded as each row arrives.
263    ///
264    /// Not a cursor: the server still produces the whole result, and the
265    /// connection is held until the stream is dropped or exhausted. What
266    /// this bounds is the client's memory, which is what a `Vec` of every
267    /// row costs.
268    ///
269    /// Returns a `Result` around the stream rather than as its first item,
270    /// because what can fail before a row arrives — an unresolved
271    /// placeholder, a value whose feature is on in `qbrs` and off here —
272    /// is a misuse of this crate rather than a row that didn't decode.
273    /// Everything the database has to say arrives as an item.
274    ///
275    /// `Send` and `Unpin` are promised, so the stream can be spawned and
276    /// polled without pinning it first — a generic caller cannot ask for
277    /// either otherwise.
278    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
313/// A `SELECT` with no `FROM`: the seed is the whole statement, so it is
314/// what carries the terminal.
315impl<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/// `SELECT count(*)` over a query's `FROM`/`JOIN`/`WHERE`/`GROUP BY`, with
328/// its `ORDER BY`/`LIMIT`/`OFFSET` dropped — a total counts the rows that
329/// match, not the page being shown. Returns a number rather than an
330/// `Option`, since a count query always produces exactly one row.
331#[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
340/// The bound is on the method, and the impls are per-builder, for the two
341/// reasons `LoadExt` explains.
342pub 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
370/// Erasure is for a query whose joins depend on a condition, and such a
371/// query is paged like any other, so it counts like any other. The same
372/// goes for a set-operation chain.
373impl<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/// Every writing statement, rendered: what `execute` returns is rows
396/// affected, whichever of the three it was.
397#[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
413/// The bound is on the method, and the impls are per-builder, for the two
414/// reasons `LoadExt` explains.
415pub 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
438/// One impl for every `RETURNING`: what a statement returns is decided by
439/// its selection, not by which statement it was.
440impl<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/// Decodes a query's `Output` positionally out of a `PgRow`. Keyed on the
452/// plain-Rust type a selection produces rather than on the selection
453/// itself: erasure leaves only `Output`, with no `Selection` impl left to
454/// hang decoding off, so this is implemented directly against the closed set
455/// of native types.
456#[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
525/// An erased query and a set-op chain were both rendered before their
526/// selection type was gone, leaving nothing for `Idx` to index — hence
527/// `RowQuery<()>`, the same trait with an empty proof.
528impl<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/// Runs a `prepare!{}`-built query, resolving its named placeholders from
543/// `params` first. Separate from `LoadExt` only because the values arrive at
544/// the call rather than being baked into the query: one `Prepared` is meant
545/// to serve many calls, and `.resolve()` clones the template rather than
546/// re-rendering it.
547#[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
557/// The bound is on the method, and the impls are per-builder, for the two
558/// reasons `LoadExt` explains.
559pub 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    /// The rows one at a time, as `LoadExt::stream` gives them — with the
592    /// `Params` that arrive at the call, which is what a reusable export
593    /// query wants.
594    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
612// A prepared query's `load`/`count` are told apart from the plain ones by
613// arity, but `execute` is not — without this, it is the one terminal on the
614// one builder that reports nothing.
615impl<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/// A prepared total. Separate from `PreparedExt` for the reason `CountExt`
629/// is separate from `LoadExt`: a count produces a number, not rows.
630#[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
645/// The bound is on the method, and the impls are per-builder, for the two
646/// reasons `LoadExt` explains.
647pub 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    // No real Postgres needed: binding inspects the `Value` enum before
668    // anything reaches the network, so an unresolved placeholder is
669    // reachable, and its error checkable, without a live DB.
670    #[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        // A real `std::error::Error`, so it composes with
683        // `anyhow`/`Box<dyn Error>`.
684        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}