sqlx_askama_template/lib.rs
1#![doc = include_str!("../README.md")]
2use futures_core::stream::BoxStream;
3use sqlx::{
4 Arguments, Database, Either, Error, Execute, Executor, FromRow, IntoArguments,
5 database::HasStatementCache,
6 query,
7 query::{Map, Query, QueryAs},
8 query_as, query_as_with, query_with,
9};
10pub use sqlx_askama_template_macro::*;
11use std::cell::RefCell;
12/// Internal executor for SQL templates
13pub struct SqlTemplateExecute<'q, DB: Database> {
14 /// Reference to SQL query string
15 pub(crate) sql: &'q String,
16 /// SQL parameters
17 pub(crate) arguments: Option<DB::Arguments<'q>>,
18 /// Persistent flag
19 pub(crate) persistent: bool,
20}
21impl<'q, DB: Database> SqlTemplateExecute<'q, DB> {
22 /// Creates a new SQL template executor
23 pub fn new(sql: &'q String, arguments: Option<DB::Arguments<'q>>) -> Self {
24 SqlTemplateExecute {
25 sql,
26 arguments,
27 persistent: true,
28 }
29 }
30 /// If `true`, the statement will get prepared once and cached to the
31 /// connection's statement cache.
32 ///
33 /// If queried once with the flag set to `true`, all subsequent queries
34 /// matching the one with the flag will use the cached statement until the
35 /// cache is cleared.
36 ///
37 /// If `false`, the prepared statement will be closed after execution.
38 ///
39 /// Default: `true`.
40 pub fn set_persistent(mut self, persistent: bool) -> Self {
41 self.persistent = persistent;
42 self
43 }
44}
45impl<'q, DB: Database + HasStatementCache> SqlTemplateExecute<'q, DB>
46where
47 DB::Arguments<'q>: IntoArguments<'q, DB>,
48{
49 /// to sqlx::QueryAs
50 /// Converts the SQL template to a `QueryAs` object, which can be executed to fetch rows
51 #[inline]
52 pub fn to_query_as<O>(self) -> QueryAs<'q, DB, O, DB::Arguments<'q>>
53 where
54 O: Send + Unpin + for<'r> FromRow<'r, DB::Row>,
55 {
56 let q = match self.arguments {
57 Some(args) => query_as_with(self.sql, args),
58 None => query_as(self.sql),
59 };
60 q.persistent(self.persistent)
61 }
62 /// to sqlx::Query
63 /// Converts the SQL template to a `Query` object, which can be executed to fetch rows
64 #[inline]
65 pub fn to_query(self) -> Query<'q, DB, DB::Arguments<'q>> {
66 let q = match self.arguments {
67 Some(args) => {
68 // let wrap = ArgWrapper(args);
69 query_with(self.sql, args)
70 }
71 None => query(self.sql),
72 };
73 q.persistent(self.persistent)
74 }
75 /// call sqlx::Query::map
76 /// Map each row in the result to another type.
77 #[inline]
78 pub fn map<F, O>(
79 self,
80 f: F,
81 ) -> Map<'q, DB, impl FnMut(DB::Row) -> Result<O, Error> + Send, DB::Arguments<'q>>
82 where
83 F: FnMut(DB::Row) -> O + Send,
84 O: Unpin,
85 DB::Arguments<'q>: IntoArguments<'q, DB>,
86 {
87 self.to_query().map(f)
88 }
89
90 /// call sqlx::Query::try_map
91 /// Map each row in the result to another type, returning an error if the mapping fails.
92 #[inline]
93 pub fn try_map<F, O>(self, f: F) -> Map<'q, DB, F, DB::Arguments<'q>>
94 where
95 F: FnMut(DB::Row) -> Result<O, Error> + Send,
96 O: Unpin,
97 DB::Arguments<'q>: IntoArguments<'q, DB>,
98 {
99 self.to_query().try_map(f)
100 }
101
102 /// call sqlx::Query::execute
103 /// Execute the query and return the number of rows affected.
104 #[inline]
105 pub async fn execute<'e, 'c: 'e, E>(self, executor: E) -> Result<DB::QueryResult, Error>
106 where
107 'q: 'e,
108 E: Executor<'c, Database = DB>,
109 {
110 self.to_query().execute(executor).await
111 }
112 /// call sqlx::Query::execute_many
113 /// Execute multiple queries and return the rows affected from each query, in a stream.
114 #[inline]
115 pub async fn execute_many<'e, 'c: 'e, E>(
116 self,
117 executor: E,
118 ) -> BoxStream<'e, Result<DB::QueryResult, Error>>
119 where
120 'q: 'e,
121 E: Executor<'c, Database = DB>,
122 {
123 #[allow(deprecated)]
124 self.to_query().execute_many(executor).await
125 }
126 /// call sqlx::Query::fetch
127 /// Execute the query and return the generated results as a stream.
128 #[inline]
129 pub fn fetch<'e, 'c: 'e, E>(self, executor: E) -> BoxStream<'e, Result<DB::Row, Error>>
130 where
131 'q: 'e,
132 E: Executor<'c, Database = DB>,
133 {
134 self.to_query().fetch(executor)
135 }
136 /// call sqlx::Query::fetch_many
137 /// Execute multiple queries and return the generated results as a stream.
138 ///
139 /// For each query in the stream, any generated rows are returned first,
140 /// then the `QueryResult` with the number of rows affected.
141 #[inline]
142 #[allow(clippy::type_complexity)]
143 pub fn fetch_many<'e, 'c: 'e, E>(
144 self,
145 executor: E,
146 ) -> BoxStream<'e, Result<Either<DB::QueryResult, DB::Row>, Error>>
147 where
148 'q: 'e,
149 E: Executor<'c, Database = DB>,
150 {
151 #[allow(deprecated)]
152 self.to_query().fetch_many(executor)
153 }
154 /// call sqlx::Query::fetch_all
155 /// Execute the query and return all the resulting rows collected into a [`Vec`].
156 ///
157 /// ### Note: beware result set size.
158 /// This will attempt to collect the full result set of the query into memory.
159 ///
160 /// To avoid exhausting available memory, ensure the result set has a known upper bound,
161 /// e.g. using `LIMIT`.
162 #[inline]
163 pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result<Vec<DB::Row>, Error>
164 where
165 'q: 'e,
166 E: Executor<'c, Database = DB>,
167 {
168 self.to_query().fetch_all(executor).await
169 }
170 /// call sqlx::Query::fetch_one
171 /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise.
172 ///
173 /// ### Note: for best performance, ensure the query returns at most one row.
174 /// Depending on the driver implementation, if your query can return more than one row,
175 /// it may lead to wasted CPU time and bandwidth on the database server.
176 ///
177 /// Even when the driver implementation takes this into account, ensuring the query returns at most one row
178 /// can result in a more optimal query plan.
179 ///
180 /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
181 ///
182 /// Otherwise, you might want to add `LIMIT 1` to your query.
183 #[inline]
184 pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result<DB::Row, Error>
185 where
186 'q: 'e,
187 E: Executor<'c, Database = DB>,
188 {
189 self.to_query().fetch_one(executor).await
190 }
191 /// call sqlx::Query::fetch_optional
192 /// Execute the query, returning the first row or `None` otherwise.
193 ///
194 /// ### Note: for best performance, ensure the query returns at most one row.
195 /// Depending on the driver implementation, if your query can return more than one row,
196 /// it may lead to wasted CPU time and bandwidth on the database server.
197 ///
198 /// Even when the driver implementation takes this into account, ensuring the query returns at most one row
199 /// can result in a more optimal query plan.
200 ///
201 /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
202 ///
203 /// Otherwise, you might want to add `LIMIT 1` to your query.
204 #[inline]
205 pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result<Option<DB::Row>, Error>
206 where
207 'q: 'e,
208 E: Executor<'c, Database = DB>,
209 {
210 self.to_query().fetch_optional(executor).await
211 }
212
213 // QueryAs functions wrapp
214
215 /// call sqlx::QueryAs::fetch
216 /// Execute the query and return the generated results as a stream.
217 pub fn fetch_as<'e, 'c: 'e, O, E>(self, executor: E) -> BoxStream<'e, Result<O, Error>>
218 where
219 'q: 'e,
220 E: 'e + Executor<'c, Database = DB>,
221 DB: 'e,
222 O: Send + Unpin + for<'r> FromRow<'r, DB::Row> + 'e,
223 {
224 self.to_query_as().fetch(executor)
225 }
226 /// call sqlx::QueryAs::fetch_many
227 /// Execute multiple queries and return the generated results as a stream
228 /// from each query, in a stream.
229 pub fn fetch_many_as<'e, 'c: 'e, O, E>(
230 self,
231 executor: E,
232 ) -> BoxStream<'e, Result<Either<DB::QueryResult, O>, Error>>
233 where
234 'q: 'e,
235 E: 'e + Executor<'c, Database = DB>,
236 DB: 'e,
237 O: Send + Unpin + for<'r> FromRow<'r, DB::Row> + 'e,
238 {
239 #[allow(deprecated)]
240 self.to_query_as().fetch_many(executor)
241 }
242 /// call sqlx::QueryAs::fetch_all
243 /// Execute the query and return all the resulting rows collected into a [`Vec`].
244 ///
245 /// ### Note: beware result set size.
246 /// This will attempt to collect the full result set of the query into memory.
247 ///
248 /// To avoid exhausting available memory, ensure the result set has a known upper bound,
249 /// e.g. using `LIMIT`.
250 #[inline]
251 pub async fn fetch_all_as<'e, 'c: 'e, O, E>(self, executor: E) -> Result<Vec<O>, Error>
252 where
253 'q: 'e,
254 E: 'e + Executor<'c, Database = DB>,
255 DB: 'e,
256 O: Send + Unpin + for<'r> FromRow<'r, DB::Row> + 'e,
257 {
258 self.to_query_as().fetch_all(executor).await
259 }
260 /// call sqlx::QueryAs::fetch_one
261 /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise.
262 ///
263 /// ### Note: for best performance, ensure the query returns at most one row.
264 /// Depending on the driver implementation, if your query can return more than one row,
265 /// it may lead to wasted CPU time and bandwidth on the database server.
266 ///
267 /// Even when the driver implementation takes this into account, ensuring the query returns at most one row
268 /// can result in a more optimal query plan.
269 ///
270 /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
271 ///
272 /// Otherwise, you might want to add `LIMIT 1` to your query.
273 pub async fn fetch_one_as<'e, 'c: 'e, O, E>(self, executor: E) -> Result<O, Error>
274 where
275 'q: 'e,
276 E: 'e + Executor<'c, Database = DB>,
277 DB: 'e,
278 O: Send + Unpin + for<'r> FromRow<'r, DB::Row> + 'e,
279 {
280 self.to_query_as().fetch_one(executor).await
281 }
282 /// call sqlx::QueryAs::fetch_optional
283 /// Execute the query, returning the first row or `None` otherwise.
284 ///
285 /// ### Note: for best performance, ensure the query returns at most one row.
286 /// Depending on the driver implementation, if your query can return more than one row,
287 /// it may lead to wasted CPU time and bandwidth on the database server.
288 ///
289 /// Even when the driver implementation takes this into account, ensuring the query returns at most one row
290 /// can result in a more optimal query plan.
291 ///
292 /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good.
293 ///
294 /// Otherwise, you might want to add `LIMIT 1` to your query.
295 pub async fn fetch_optional_as<'e, 'c: 'e, O, E>(self, executor: E) -> Result<Option<O>, Error>
296 where
297 'q: 'e,
298 E: 'e + Executor<'c, Database = DB>,
299 DB: 'e,
300 O: Send + Unpin + for<'r> FromRow<'r, DB::Row> + 'e,
301 {
302 self.to_query_as().fetch_optional(executor).await
303 }
304}
305
306impl<'q, DB: Database> Execute<'q, DB> for SqlTemplateExecute<'q, DB> {
307 /// Returns the SQL query string
308 #[inline]
309 fn sql(&self) -> &'q str {
310 self.sql
311 }
312
313 /// Gets prepared statement (not supported in this implementation)
314 #[inline]
315 fn statement(&self) -> Option<&DB::Statement<'q>> {
316 None
317 }
318
319 /// Takes ownership of the bound arguments
320 #[inline]
321 fn take_arguments(&mut self) -> Result<Option<DB::Arguments<'q>>, sqlx::error::BoxDynError> {
322 Ok(self.arguments.take())
323 }
324
325 /// Checks if query is persistent
326 #[inline]
327 fn persistent(&self) -> bool {
328 self.persistent
329 }
330}
331
332/// SQL template argument processor
333///
334/// Handles parameter encoding and binding for SQL templates
335pub struct TemplateArg<'q, DB: Database> {
336 /// Stores any encoding errors
337 error: RefCell<Option<sqlx::error::BoxDynError>>,
338 /// Stores SQL parameters
339 arguments: RefCell<Option<DB::Arguments<'q>>>,
340}
341
342impl<DB: Database> Default for TemplateArg<'_, DB> {
343 /// Creates default TemplateArg
344 fn default() -> Self {
345 TemplateArg {
346 error: RefCell::new(None),
347 arguments: RefCell::new(None),
348 }
349 }
350}
351
352impl<'q, DB: Database> TemplateArg<'q, DB> {
353 /// Encodes a single parameter and returns its placeholder
354 ///
355 /// # Arguments
356 /// * `t` - Value to encode
357 ///
358 /// # Returns
359 /// Parameter placeholder string (e.g. "$1" or "?")
360 pub fn encode<T>(&self, t: T) -> String
361 where
362 T: sqlx::Encode<'q, DB> + sqlx::Type<DB> + 'q,
363 {
364 let mut err = self.error.borrow_mut();
365 let mut arguments = self.arguments.borrow_mut().take().unwrap_or_default();
366
367 if let Err(e) = arguments.add(t) {
368 if err.is_none() {
369 *err = Some(e);
370 }
371 }
372
373 let mut placeholder = String::new();
374 if let Err(e) = arguments.format_placeholder(&mut placeholder) {
375 if err.is_none() {
376 *err = Some(Box::new(e));
377 }
378 }
379
380 *self.arguments.borrow_mut() = Some(arguments);
381 placeholder
382 }
383
384 /// Encodes a parameter list and returns placeholder sequence
385 ///
386 /// # Arguments
387 /// * `args` - Iterator of values to encode
388 ///
389 /// # Returns
390 /// Parameter placeholder sequence (e.g. "($1,$2,$3)")
391 pub fn encode_list<T>(&self, args: impl Iterator<Item = T>) -> String
392 where
393 T: sqlx::Encode<'q, DB> + sqlx::Type<DB> + 'q,
394 {
395 let mut err = self.error.borrow_mut();
396 let mut arguments = self.arguments.borrow_mut().take().unwrap_or_default();
397 let mut placeholder = String::new();
398 placeholder.push('(');
399
400 for arg in args {
401 if let Err(e) = arguments.add(arg) {
402 if err.is_none() {
403 *err = Some(e);
404 }
405 }
406
407 if let Err(e) = arguments.format_placeholder(&mut placeholder) {
408 if err.is_none() {
409 *err = Some(Box::new(e));
410 }
411 }
412 placeholder.push(',');
413 }
414
415 if placeholder.ends_with(",") {
416 placeholder.pop();
417 }
418 placeholder.push(')');
419
420 *self.arguments.borrow_mut() = Some(arguments);
421 placeholder
422 }
423
424 /// Takes any encoding error that occurred
425 pub fn get_err(&self) -> Option<sqlx::error::BoxDynError> {
426 self.error.borrow_mut().take()
427 }
428
429 /// Takes ownership of the encoded arguments
430 pub fn get_arguments(&self) -> Option<DB::Arguments<'q>> {
431 self.arguments.borrow_mut().take()
432 }
433}
434
435/// SQL template trait
436///
437/// Defines basic operations for rendering SQL from templates
438pub trait SqlTemplate<'q, DB>: Sized
439where
440 DB: Database,
441{
442 /// Renders SQL template and returns query string with parameters
443 fn render_sql(self) -> Result<(String, Option<DB::Arguments<'q>>), askama::Error>;
444
445 /// Renders SQL template and returns executable query result
446 fn render_execute_able(
447 self,
448 sql_buffer: &'q mut String,
449 ) -> Result<SqlTemplateExecute<'q, DB>, askama::Error> {
450 let (sql, arguments) = self.render_sql()?;
451 *sql_buffer = sql;
452 Ok(SqlTemplateExecute {
453 sql: sql_buffer,
454
455 arguments,
456
457 persistent: true,
458 })
459 }
460}