sqlx_named_bind/query_as.rs
1use crate::builder::build_query;
2use regex::Regex;
3use sqlx::{
4 mysql::{MySqlArguments, MySqlRow},
5 query::QueryAs,
6 Executor, MySql,
7};
8
9/// Type alias for SQLx QueryAs with MySQL arguments
10pub type QA<'q, R> = QueryAs<'q, MySql, R, MySqlArguments>;
11
12/// A prepared query builder that returns typed results from named placeholders.
13///
14/// `PreparedQueryAs` is similar to `PreparedQuery` but returns strongly-typed results
15/// using SQLx's `FromRow` trait. It supports `fetch_all`, `fetch_one`, and `fetch_optional`.
16///
17/// # Type Parameters
18///
19/// * `R` - The result type that implements `FromRow`
20/// * `F` - A binder function that binds values to placeholders
21///
22/// # Examples
23///
24/// ```rust,no_run
25/// use sqlx::{MySqlPool, FromRow};
26/// use sqlx_named_bind::PreparedQueryAs;
27///
28/// #[derive(FromRow)]
29/// struct User {
30/// id: i32,
31/// name: String,
32/// }
33///
34/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
35/// # let pool = MySqlPool::connect("mysql://localhost/test").await?;
36/// let user_id = 42;
37///
38/// let mut query = PreparedQueryAs::<User, _>::new(
39/// "SELECT id, name FROM users WHERE id = :id",
40/// |q, key| match key {
41/// ":id" => q.bind(user_id),
42/// _ => q,
43/// }
44/// )?;
45///
46/// let user: User = query.fetch_one(&pool).await?;
47/// println!("User: {} ({})", user.name, user.id);
48/// # Ok(())
49/// # }
50/// ```
51pub struct PreparedQueryAs<R, F>
52where
53 F: for<'q> FnMut(QA<'q, R>, &str) -> QA<'q, R>,
54{
55 sql: String,
56 order: Vec<String>,
57 binder: F,
58 _pd: std::marker::PhantomData<R>,
59}
60
61impl<R, F> PreparedQueryAs<R, F>
62where
63 for<'row> R: sqlx::FromRow<'row, MySqlRow> + Send + Unpin,
64 F: for<'q> FnMut(QA<'q, R>, &str) -> QA<'q, R>,
65{
66 /// Creates a new `PreparedQueryAs` from an SQL template and binder function.
67 ///
68 /// # Arguments
69 ///
70 /// * `template` - SQL query template with named placeholders
71 /// * `binder` - Function that binds values to placeholders
72 ///
73 /// # Errors
74 ///
75 /// Returns an error if the SQL template cannot be parsed.
76 ///
77 /// # Examples
78 ///
79 /// ```rust
80 /// use sqlx::FromRow;
81 /// use sqlx_named_bind::PreparedQueryAs;
82 ///
83 /// #[derive(FromRow)]
84 /// struct User {
85 /// id: i32,
86 /// name: String,
87 /// }
88 ///
89 /// let query = PreparedQueryAs::<User, _>::new(
90 /// "SELECT id, name FROM users WHERE id = :id",
91 /// |q, key| match key {
92 /// ":id" => q.bind(42),
93 /// _ => q,
94 /// }
95 /// )?;
96 /// # Ok::<(), sqlx_named_bind::Error>(())
97 /// ```
98 pub fn new<T>(template: T, binder: F) -> crate::Result<Self>
99 where
100 T: Into<String>,
101 {
102 let template = template.into();
103 let order = Regex::new(r":[a-zA-Z0-9_]+")?
104 .find_iter(&template)
105 .map(|m| m.as_str().to_owned())
106 .collect();
107 let sql = build_query(&template)?;
108 Ok(Self {
109 sql,
110 order,
111 binder,
112 _pd: std::marker::PhantomData,
113 })
114 }
115
116 /// Executes the query and returns all matching rows.
117 ///
118 /// # Arguments
119 ///
120 /// * `executor` - Any SQLx executor (pool, transaction, etc.)
121 ///
122 /// # Returns
123 ///
124 /// Returns a vector of all rows matching the query.
125 ///
126 /// # Errors
127 ///
128 /// Returns an error if the query fails or if any row cannot be converted to type `R`.
129 ///
130 /// # Examples
131 ///
132 /// ```rust,no_run
133 /// use sqlx::{MySqlPool, FromRow};
134 /// use sqlx_named_bind::PreparedQueryAs;
135 ///
136 /// #[derive(FromRow)]
137 /// struct User {
138 /// id: i32,
139 /// name: String,
140 /// }
141 ///
142 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
143 /// # let pool = MySqlPool::connect("mysql://localhost/test").await?;
144 /// let mut query = PreparedQueryAs::<User, _>::new(
145 /// "SELECT id, name FROM users WHERE age > :min_age",
146 /// |q, key| match key {
147 /// ":min_age" => q.bind(18),
148 /// _ => q,
149 /// }
150 /// )?;
151 ///
152 /// let users: Vec<User> = query.fetch_all(&pool).await?;
153 /// println!("Found {} users", users.len());
154 /// # Ok(())
155 /// # }
156 /// ```
157 pub async fn fetch_all<'e, E>(&mut self, executor: E) -> crate::Result<Vec<R>>
158 where
159 E: Executor<'e, Database = MySql>,
160 {
161 let &mut PreparedQueryAs {
162 ref sql,
163 ref order,
164 ref mut binder,
165 _pd,
166 } = self;
167
168 let mut q = sqlx::query_as(sql);
169 for key in order.iter() {
170 q = binder(q, key);
171 }
172 Ok(q.fetch_all(executor).await?)
173 }
174
175 /// Executes the query and returns exactly one row.
176 ///
177 /// # Arguments
178 ///
179 /// * `executor` - Any SQLx executor (pool, transaction, etc.)
180 ///
181 /// # Returns
182 ///
183 /// Returns the single row matching the query.
184 ///
185 /// # Errors
186 ///
187 /// Returns an error if:
188 /// - No rows are found
189 /// - More than one row is found
190 /// - The query fails
191 /// - The row cannot be converted to type `R`
192 ///
193 /// # Examples
194 ///
195 /// ```rust,no_run
196 /// use sqlx::{MySqlPool, FromRow};
197 /// use sqlx_named_bind::PreparedQueryAs;
198 ///
199 /// #[derive(FromRow)]
200 /// struct User {
201 /// id: i32,
202 /// name: String,
203 /// }
204 ///
205 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
206 /// # let pool = MySqlPool::connect("mysql://localhost/test").await?;
207 /// let mut query = PreparedQueryAs::<User, _>::new(
208 /// "SELECT id, name FROM users WHERE id = :id",
209 /// |q, key| match key {
210 /// ":id" => q.bind(42),
211 /// _ => q,
212 /// }
213 /// )?;
214 ///
215 /// let user: User = query.fetch_one(&pool).await?;
216 /// println!("Found user: {}", user.name);
217 /// # Ok(())
218 /// # }
219 /// ```
220 pub async fn fetch_one<'e, E>(&mut self, executor: E) -> crate::Result<R>
221 where
222 E: Executor<'e, Database = MySql>,
223 {
224 let &mut PreparedQueryAs {
225 ref sql,
226 ref order,
227 ref mut binder,
228 _pd,
229 } = self;
230
231 let mut q = sqlx::query_as(sql);
232 for key in order.iter() {
233 q = binder(q, key);
234 }
235 Ok(q.fetch_one(executor).await?)
236 }
237
238 /// Executes the query and returns at most one row.
239 ///
240 /// # Arguments
241 ///
242 /// * `executor` - Any SQLx executor (pool, transaction, etc.)
243 ///
244 /// # Returns
245 ///
246 /// Returns `Some(row)` if exactly one row matches, `None` if no rows match.
247 ///
248 /// # Errors
249 ///
250 /// Returns an error if:
251 /// - More than one row is found
252 /// - The query fails
253 /// - The row cannot be converted to type `R`
254 ///
255 /// # Examples
256 ///
257 /// ```rust,no_run
258 /// use sqlx::{MySqlPool, FromRow};
259 /// use sqlx_named_bind::PreparedQueryAs;
260 ///
261 /// #[derive(FromRow)]
262 /// struct User {
263 /// id: i32,
264 /// name: String,
265 /// }
266 ///
267 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
268 /// # let pool = MySqlPool::connect("mysql://localhost/test").await?;
269 /// let mut query = PreparedQueryAs::<User, _>::new(
270 /// "SELECT id, name FROM users WHERE email = :email",
271 /// |q, key| match key {
272 /// ":email" => q.bind("user@example.com"),
273 /// _ => q,
274 /// }
275 /// )?;
276 ///
277 /// match query.fetch_optional(&pool).await? {
278 /// Some(user) => println!("Found user: {}", user.name),
279 /// None => println!("User not found"),
280 /// }
281 /// # Ok(())
282 /// # }
283 /// ```
284 pub async fn fetch_optional<'e, E>(&mut self, executor: E) -> crate::Result<Option<R>>
285 where
286 E: Executor<'e, Database = MySql>,
287 {
288 let &mut PreparedQueryAs {
289 ref sql,
290 ref order,
291 ref mut binder,
292 _pd,
293 } = self;
294
295 let mut q = sqlx::query_as(sql);
296 for key in order.iter() {
297 q = binder(q, key);
298 }
299 Ok(q.fetch_optional(executor).await?)
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 // Mock struct for testing (requires sqlx::FromRow)
308 // In real tests, this would use a real database connection
309
310 #[test]
311 fn test_prepared_query_as_new() {
312 #[derive(sqlx::FromRow)]
313 struct TestRow {
314 #[allow(dead_code)]
315 id: i32,
316 }
317
318 let result = PreparedQueryAs::<TestRow, _>::new(
319 "SELECT id FROM users WHERE id = :id",
320 |q, _| q,
321 );
322 assert!(result.is_ok());
323 }
324
325 #[test]
326 fn test_prepared_query_as_placeholder_order() {
327 #[derive(sqlx::FromRow)]
328 struct TestRow {
329 #[allow(dead_code)]
330 id: i32,
331 }
332
333 let query = PreparedQueryAs::<TestRow, _>::new(
334 "SELECT id FROM users WHERE id = :id AND name = :name",
335 |q, _| q,
336 ).unwrap();
337
338 assert_eq!(query.order, vec![":id", ":name"]);
339 assert_eq!(query.sql, "SELECT id FROM users WHERE id = ? AND name = ?");
340 }
341}