Add query_as_unchecked! and query_file_as_unchecked! to use the macro system with unchecked input and output

This commit is contained in:
Ryan Leckey 2020-03-27 16:37:28 -07:00
parent a2d82d0ac1
commit 2f80621279
8 changed files with 104 additions and 9 deletions

View file

@ -141,13 +141,26 @@ pub fn query_file(input: TokenStream) -> TokenStream {
#[allow(unused_variables)]
pub fn query_as(input: TokenStream) -> TokenStream {
#[allow(unused_variables)]
async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db))
async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db, true))
}
#[proc_macro]
#[allow(unused_variables)]
pub fn query_file_as(input: TokenStream) -> TokenStream {
async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db))
async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db, true))
}
#[proc_macro]
#[allow(unused_variables)]
pub fn query_as_unchecked(input: TokenStream) -> TokenStream {
#[allow(unused_variables)]
async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db, false))
}
#[proc_macro]
#[allow(unused_variables)]
pub fn query_file_as_unchecked(input: TokenStream) -> TokenStream {
async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db, false))
}
#[proc_macro_derive(Encode, attributes(sqlx))]

View file

@ -13,6 +13,7 @@ use crate::query_macros::QueryMacroInput;
pub fn quote_args<DB: DatabaseExt>(
input: &QueryMacroInput,
describe: &Describe<DB>,
checked: bool,
) -> crate::Result<TokenStream> {
let db_path = DB::db_path();
@ -24,7 +25,7 @@ pub fn quote_args<DB: DatabaseExt>(
let arg_name = &input.arg_names;
let args_check = if DB::PARAM_CHECKING == ParamChecking::Strong {
let args_check = if checked && DB::PARAM_CHECKING == ParamChecking::Strong {
describe
.param_types
.iter()

View file

@ -30,6 +30,7 @@ where
pub async fn expand_query_as<C: Connection>(
input: QueryAsMacroInput,
mut conn: C,
checked: bool,
) -> crate::Result<TokenStream>
where
C::Database: DatabaseExt + Sized,
@ -45,7 +46,7 @@ where
.into());
}
let args_tokens = args::quote_args(&input.query_input, &describe)?;
let args_tokens = args::quote_args(&input.query_input, &describe, checked)?;
let query_args = format_ident!("query_args");
@ -55,6 +56,7 @@ where
&input.as_ty.path,
&query_args,
&columns,
checked,
);
let arg_names = &input.query_input.arg_names;
@ -75,10 +77,11 @@ where
pub async fn expand_query_file_as<C: Connection>(
input: QueryAsMacroInput,
conn: C,
checked: bool,
) -> crate::Result<TokenStream>
where
C::Database: DatabaseExt + Sized,
<C::Database as Database>::TypeInfo: Display,
{
expand_query_as(input.expand_file_src().await?, conn).await
expand_query_as(input.expand_file_src().await?, conn, checked).await
}

View file

@ -101,6 +101,7 @@ pub fn quote_query_as<DB: DatabaseExt>(
out_ty: &Path,
bind_args: &Ident,
columns: &[RustColumn],
checked: bool,
) -> TokenStream {
let instantiations = columns.iter().enumerate().map(
|(
@ -110,7 +111,16 @@ pub fn quote_query_as<DB: DatabaseExt>(
ref type_,
..
},
)| { quote!( #ident: row.try_get::<#type_, _>(#i).try_unwrap_optional()? ) },
)| {
// For "checked" queries, the macro checks these at compile time and using "try_get"
// would also perform pointless runtime checks
if checked {
quote!( #ident: row.try_get_unchecked::<#type_, _>(#i).try_unwrap_optional()? )
} else {
quote!( #ident: row.try_get_unchecked(#i).try_unwrap_optional()? )
}
},
);
let db_path = DB::db_path();

View file

@ -23,7 +23,7 @@ where
let describe = input.describe_validate(&mut conn).await?;
let sql = &input.source;
let args = args::quote_args(&input, &describe)?;
let args = args::quote_args(&input, &describe, true)?;
let arg_names = &input.arg_names;
let db_path = <C::Database as DatabaseExt>::db_path();
@ -57,7 +57,8 @@ where
.collect::<TokenStream>();
let query_args = format_ident!("query_args");
let output = output::quote_query_as::<C::Database>(sql, &record_type, &query_args, &columns);
let output =
output::quote_query_as::<C::Database>(sql, &record_type, &query_args, &columns, true);
Ok(quote! {
macro_rules! macro_result {

View file

@ -284,3 +284,48 @@ macro_rules! query_file_as (
macro_result!($($args),*)
})
);
/// A variant of [query_as!] which does not check the input or output types. This still does parse
/// the query to ensure it's syntactically and semantically valid for the current database.
#[macro_export]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
macro_rules! query_as_unchecked (
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
#[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_as_unchecked!($out_struct, $query);
}
macro_result!()
});
($out_struct:path, $query:literal, $($args:expr),*$(,)?) => (#[allow(dead_code)] {
#[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_as_unchecked!($out_struct, $query, $($args),*);
}
macro_result!($($args),*)
})
);
/// A variant of [query_file_as!] which does not check the input or output types. This
/// still does parse the query to ensure it's syntactically and semantically valid
/// for the current database.
#[macro_export]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
macro_rules! query_file_as_unchecked (
($out_struct:path, $query:literal) => (#[allow(dead_code)] {
#[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_file_as_unchecked!($out_struct, $query);
}
macro_result!()
});
($out_struct:path, $query:literal, $($args:tt),*$(,)?) => (#[allow(dead_code)] {
#[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_file_as_unchecked!($out_struct, $query, $($args),*);
}
macro_result!($($args),*)
})
);

View file

@ -59,6 +59,29 @@ async fn test_query_as_raw() -> anyhow::Result<()> {
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn test_query_as_bool() -> anyhow::Result<()> {
let mut conn = new::<MySql>().await?;
struct Article {
id: i32,
deleted: bool,
}
let article = sqlx::query_as_unchecked!(
Article,
"select * from (select 51 as id, true as deleted) articles"
)
.fetch_one(&mut conn)
.await?;
assert_eq!(51, article.id);
assert_eq!(true, article.deleted);
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn test_query_bytes() -> anyhow::Result<()> {

View file

@ -1,5 +1,4 @@
use futures::TryStreamExt;
use sqlx::error::DatabaseError;
use sqlx::{mysql::MySqlQueryAs, Connection, Executor, MySql, MySqlPool};
use sqlx_test::new;
use std::time::Duration;