Skip to main content

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}