use crate::builder::build_query;
use regex::Regex;
use sqlx::{
mysql::{MySqlArguments, MySqlRow},
query::QueryAs,
Executor, MySql,
};
pub type QA<'q, R> = QueryAs<'q, MySql, R, MySqlArguments>;
pub struct PreparedQueryAs<R, F>
where
F: for<'q> FnMut(QA<'q, R>, &str) -> QA<'q, R>,
{
sql: String,
order: Vec<String>,
binder: F,
_pd: std::marker::PhantomData<R>,
}
impl<R, F> PreparedQueryAs<R, F>
where
for<'row> R: sqlx::FromRow<'row, MySqlRow> + Send + Unpin,
F: for<'q> FnMut(QA<'q, R>, &str) -> QA<'q, R>,
{
pub fn new<T>(template: T, binder: F) -> crate::Result<Self>
where
T: Into<String>,
{
let template = template.into();
let order = Regex::new(r":[a-zA-Z0-9_]+")?
.find_iter(&template)
.map(|m| m.as_str().to_owned())
.collect();
let sql = build_query(&template)?;
Ok(Self {
sql,
order,
binder,
_pd: std::marker::PhantomData,
})
}
pub async fn fetch_all<'e, E>(&mut self, executor: E) -> crate::Result<Vec<R>>
where
E: Executor<'e, Database = MySql>,
{
let &mut PreparedQueryAs {
ref sql,
ref order,
ref mut binder,
_pd,
} = self;
let mut q = sqlx::query_as(sql);
for key in order.iter() {
q = binder(q, key);
}
Ok(q.fetch_all(executor).await?)
}
pub async fn fetch_one<'e, E>(&mut self, executor: E) -> crate::Result<R>
where
E: Executor<'e, Database = MySql>,
{
let &mut PreparedQueryAs {
ref sql,
ref order,
ref mut binder,
_pd,
} = self;
let mut q = sqlx::query_as(sql);
for key in order.iter() {
q = binder(q, key);
}
Ok(q.fetch_one(executor).await?)
}
pub async fn fetch_optional<'e, E>(&mut self, executor: E) -> crate::Result<Option<R>>
where
E: Executor<'e, Database = MySql>,
{
let &mut PreparedQueryAs {
ref sql,
ref order,
ref mut binder,
_pd,
} = self;
let mut q = sqlx::query_as(sql);
for key in order.iter() {
q = binder(q, key);
}
Ok(q.fetch_optional(executor).await?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prepared_query_as_new() {
#[derive(sqlx::FromRow)]
struct TestRow {
#[allow(dead_code)]
id: i32,
}
let result = PreparedQueryAs::<TestRow, _>::new(
"SELECT id FROM users WHERE id = :id",
|q, _| q,
);
assert!(result.is_ok());
}
#[test]
fn test_prepared_query_as_placeholder_order() {
#[derive(sqlx::FromRow)]
struct TestRow {
#[allow(dead_code)]
id: i32,
}
let query = PreparedQueryAs::<TestRow, _>::new(
"SELECT id FROM users WHERE id = :id AND name = :name",
|q, _| q,
).unwrap();
assert_eq!(query.order, vec![":id", ":name"]);
assert_eq!(query.sql, "SELECT id FROM users WHERE id = ? AND name = ?");
}
}