chain_builder/fetch.rs
1//! Typed fetch / execution helpers for v2 (`feature = "sqlx_*"`).
2//!
3//! These are thin async delegations on top of the sqlx query objects already
4//! produced by [`try_to_sqlx_query`](QueryBuilder::try_to_sqlx_query) and
5//! [`try_to_sqlx_query_as`](QueryBuilder::try_to_sqlx_query_as) in
6//! [`crate::sqlx_bind`]. They let any [`QueryBuilder<D>`] whose
7//! `D: SqlxDialect` run against a sqlx [`Executor`](sqlx::Executor) and decode
8//! rows into a `T: FromRow`, or pull a single scalar column.
9//!
10//! Every helper returns the unified [`Error`]: an invalid builder surfaces as
11//! [`Error::Build`] **before** touching the database, and a database/driver
12//! failure surfaces as [`Error::Sqlx`] — never a panic.
13//!
14//! [`count`](QueryBuilder::count) mirrors 1.x `ChainBuilder::count`: it wraps the
15//! built SQL in `SELECT COUNT(*) FROM (<sql>) AS __cb_count`, binds the same
16//! arguments, and fetches a single `i64`.
17
18use crate::builder::QueryBuilder;
19use crate::error::Error;
20use crate::sqlx_bind::SqlxDialect;
21
22impl<D: SqlxDialect> QueryBuilder<D> {
23 /// Fetch all rows, decoding each into `T`.
24 ///
25 /// Delegates to `self.try_to_sqlx_query_as::<T>()?.fetch_all(executor)`.
26 pub async fn fetch_all<'e, T, E>(&self, executor: E) -> Result<Vec<T>, Error>
27 where
28 T: for<'r> sqlx::FromRow<'r, <D::Database as sqlx::Database>::Row> + Send + Unpin,
29 E: sqlx::Executor<'e, Database = D::Database>,
30 {
31 Ok(self
32 .try_to_sqlx_query_as::<T>()?
33 .fetch_all(executor)
34 .await?)
35 }
36
37 /// Fetch exactly one row, decoding it into `T`.
38 ///
39 /// Errors with [`sqlx::Error::RowNotFound`] (as [`Error::Sqlx`]) if no row
40 /// is returned.
41 pub async fn fetch_one<'e, T, E>(&self, executor: E) -> Result<T, Error>
42 where
43 T: for<'r> sqlx::FromRow<'r, <D::Database as sqlx::Database>::Row> + Send + Unpin,
44 E: sqlx::Executor<'e, Database = D::Database>,
45 {
46 Ok(self
47 .try_to_sqlx_query_as::<T>()?
48 .fetch_one(executor)
49 .await?)
50 }
51
52 /// Fetch at most one row, decoding it into `T`.
53 pub async fn fetch_optional<'e, T, E>(&self, executor: E) -> Result<Option<T>, Error>
54 where
55 T: for<'r> sqlx::FromRow<'r, <D::Database as sqlx::Database>::Row> + Send + Unpin,
56 E: sqlx::Executor<'e, Database = D::Database>,
57 {
58 Ok(self
59 .try_to_sqlx_query_as::<T>()?
60 .fetch_optional(executor)
61 .await?)
62 }
63
64 /// Execute the query (INSERT / UPDATE / DELETE / DDL), returning the
65 /// database's `QueryResult` (affected rows, last insert id, …).
66 ///
67 /// Delegates to `self.try_to_sqlx_query()?.execute(executor)`.
68 pub async fn execute<'e, E>(
69 &self,
70 executor: E,
71 ) -> Result<<D::Database as sqlx::Database>::QueryResult, Error>
72 where
73 E: sqlx::Executor<'e, Database = D::Database>,
74 {
75 Ok(self.try_to_sqlx_query()?.execute(executor).await?)
76 }
77
78 /// Count the rows the query would return.
79 ///
80 /// Wraps the built SQL as `SELECT COUNT(*) FROM (<sql>) AS __cb_count`, binds
81 /// the same arguments, and fetches a single `i64`. Mirrors 1.x
82 /// `ChainBuilder::count`.
83 pub async fn count<'e, E>(&self, executor: E) -> Result<i64, Error>
84 where
85 E: sqlx::Executor<'e, Database = D::Database>,
86 // `query_scalar_with` decodes `(i64,)` from the row, which needs `i64` to
87 // be decodable for this database and a positional column index. These hold
88 // for every sqlx built-in backend but are not implied by `SqlxDialect`.
89 i64: for<'r> sqlx::Decode<'r, D::Database> + sqlx::Type<D::Database>,
90 usize: sqlx::ColumnIndex<<D::Database as sqlx::Database>::Row>,
91 {
92 let (sql, binds) = self.try_to_sql()?;
93 let wrapped = format!("SELECT COUNT(*) FROM ({sql}) AS __cb_count");
94 let args = D::bind_arguments(&binds);
95 Ok(
96 sqlx::query_scalar_with::<D::Database, i64, _>(sqlx::AssertSqlSafe(wrapped), args)
97 .fetch_one(executor)
98 .await?,
99 )
100 }
101
102 /// Fetch the first column of the first row, decoded into `T`.
103 ///
104 /// Errors with [`sqlx::Error::RowNotFound`] (as [`Error::Sqlx`]) if no row
105 /// is returned. Useful for `pluck`/aggregate-style queries.
106 pub async fn fetch_scalar<'e, T, E>(&self, executor: E) -> Result<T, Error>
107 where
108 T: for<'r> sqlx::Decode<'r, D::Database> + sqlx::Type<D::Database> + Send + Unpin,
109 E: sqlx::Executor<'e, Database = D::Database>,
110 // `query_scalar_with` decodes `(T,)`, which requires a positional column
111 // index on the backend's row type.
112 usize: sqlx::ColumnIndex<<D::Database as sqlx::Database>::Row>,
113 {
114 let (sql, binds) = self.try_to_sql()?;
115 let args = D::bind_arguments(&binds);
116 Ok(
117 sqlx::query_scalar_with::<D::Database, T, _>(sqlx::AssertSqlSafe(sql), args)
118 .fetch_one(executor)
119 .await?,
120 )
121 }
122
123 /// Fetch the first column of the first row (if any), decoded into `T`.
124 pub async fn fetch_optional_scalar<'e, T, E>(&self, executor: E) -> Result<Option<T>, Error>
125 where
126 T: for<'r> sqlx::Decode<'r, D::Database> + sqlx::Type<D::Database> + Send + Unpin,
127 E: sqlx::Executor<'e, Database = D::Database>,
128 usize: sqlx::ColumnIndex<<D::Database as sqlx::Database>::Row>,
129 {
130 let (sql, binds) = self.try_to_sql()?;
131 let args = D::bind_arguments(&binds);
132 Ok(
133 sqlx::query_scalar_with::<D::Database, T, _>(sqlx::AssertSqlSafe(sql), args)
134 .fetch_optional(executor)
135 .await?,
136 )
137 }
138}