mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-04-10 04:05:49 +00:00
Merge remote-tracking branch 'origin/ab/macro-refresh'
This commit is contained in:
@@ -16,15 +16,6 @@ mod database;
|
||||
mod derives;
|
||||
mod query;
|
||||
|
||||
fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream {
|
||||
quote!(
|
||||
macro_rules! macro_result {
|
||||
($($args:tt)*) => (#tokens)
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn expand_query(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(input as query::QueryMacroInput);
|
||||
@@ -33,10 +24,10 @@ pub fn expand_query(input: TokenStream) -> TokenStream {
|
||||
Ok(ts) => ts.into(),
|
||||
Err(e) => {
|
||||
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
|
||||
macro_result(parse_err.to_compile_error())
|
||||
parse_err.to_compile_error().into()
|
||||
} else {
|
||||
let msg = e.to_string();
|
||||
macro_result(quote!(compile_error!(#msg)))
|
||||
quote!(compile_error!(#msg)).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ use crate::database::DatabaseExt;
|
||||
use crate::query::QueryMacroInput;
|
||||
use either::Either;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use sqlx_core::describe::Describe;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Expr, Type};
|
||||
use syn::{Expr, ExprCast, ExprGroup, ExprType, Type};
|
||||
|
||||
/// Returns a tokenstream which typechecks the arguments passed to the macro
|
||||
/// and binds them to `DB::Arguments` with the ident `query_args`.
|
||||
@@ -15,13 +15,22 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
) -> crate::Result<TokenStream> {
|
||||
let db_path = DB::db_path();
|
||||
|
||||
if input.arg_names.is_empty() {
|
||||
if input.arg_exprs.is_empty() {
|
||||
return Ok(quote! {
|
||||
let query_args = <#db_path as sqlx::database::HasArguments>::Arguments::default();
|
||||
});
|
||||
}
|
||||
|
||||
let arg_name = &input.arg_names;
|
||||
let arg_names = (0..input.arg_exprs.len())
|
||||
.map(|i| format_ident!("arg{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let arg_name = &arg_names;
|
||||
let arg_expr = input.arg_exprs.iter().cloned().map(strip_wildcard);
|
||||
|
||||
let arg_bindings = quote! {
|
||||
#(let ref #arg_name = #arg_expr;)*
|
||||
};
|
||||
|
||||
let args_check = match info.parameters() {
|
||||
None | Some(Either::Right(_)) => {
|
||||
@@ -29,22 +38,20 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
TokenStream::new()
|
||||
}
|
||||
|
||||
Some(Either::Left(_)) if !input.checked => {
|
||||
// this is an `*_unchecked!()` macro invocation
|
||||
TokenStream::new()
|
||||
}
|
||||
|
||||
Some(Either::Left(params)) => {
|
||||
params
|
||||
.iter()
|
||||
.zip(input.arg_names.iter().zip(&input.arg_exprs))
|
||||
.zip(arg_names.iter().zip(&input.arg_exprs))
|
||||
.enumerate()
|
||||
.map(|(i, (param_ty, (name, expr)))| -> crate::Result<_> {
|
||||
let param_ty = match get_type_override(expr) {
|
||||
// TODO: enable this in 1.45 when we can strip `as _`
|
||||
// without stripping these we get some pretty nasty type errors
|
||||
Some(Type::Infer(_)) => return Err(
|
||||
syn::Error::new_spanned(
|
||||
expr,
|
||||
"casts to `_` are not allowed in bind parameters yet"
|
||||
).into()
|
||||
),
|
||||
// cast or type ascription will fail to compile if the type does not match
|
||||
// and we strip casts to wildcard
|
||||
Some(_) => return Ok(quote!()),
|
||||
None => {
|
||||
DB::param_type_for_id(¶m_ty)
|
||||
@@ -72,14 +79,14 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
use sqlx::ty_match::{WrapSameExt as _, MatchBorrowExt as _};
|
||||
|
||||
// evaluate the expression only once in case it contains moves
|
||||
let _expr = sqlx::ty_match::dupe_value(&$#name);
|
||||
let _expr = sqlx::ty_match::dupe_value(#name);
|
||||
|
||||
// if `_expr` is `Option<T>`, get `Option<$ty>`, otherwise `$ty`
|
||||
let ty_check = sqlx::ty_match::WrapSame::<#param_ty, _>::new(&_expr).wrap_same();
|
||||
// if `_expr` is `&str`, convert `String` to `&str`
|
||||
let (mut ty_check, match_borrow) = sqlx::ty_match::MatchBorrow::new(ty_check, &_expr);
|
||||
let (mut _ty_check, match_borrow) = sqlx::ty_match::MatchBorrow::new(ty_check, &_expr);
|
||||
|
||||
ty_check = match_borrow.match_borrow();
|
||||
_ty_check = match_borrow.match_borrow();
|
||||
|
||||
// this causes move-analysis to effectively ignore this block
|
||||
panic!();
|
||||
@@ -90,13 +97,13 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
}
|
||||
};
|
||||
|
||||
let args_count = input.arg_names.len();
|
||||
let args_count = input.arg_exprs.len();
|
||||
|
||||
Ok(quote! {
|
||||
#arg_bindings
|
||||
|
||||
#args_check
|
||||
|
||||
// bind as a local expression, by-ref
|
||||
#(let #arg_name = &$#arg_name;)*
|
||||
let mut query_args = <#db_path as sqlx::database::HasArguments>::Arguments::default();
|
||||
query_args.reserve(
|
||||
#args_count,
|
||||
@@ -114,3 +121,36 @@ fn get_type_override(expr: &Expr) -> Option<&Type> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_wildcard(expr: Expr) -> Expr {
|
||||
match expr {
|
||||
Expr::Group(ExprGroup {
|
||||
attrs,
|
||||
group_token,
|
||||
expr,
|
||||
}) => Expr::Group(ExprGroup {
|
||||
attrs,
|
||||
group_token,
|
||||
expr: Box::new(strip_wildcard(*expr)),
|
||||
}),
|
||||
// type ascription syntax is experimental so we always strip it
|
||||
Expr::Type(ExprType { expr, .. }) => *expr,
|
||||
// we want to retain casts if they semantically matter
|
||||
Expr::Cast(ExprCast {
|
||||
attrs,
|
||||
expr,
|
||||
as_token,
|
||||
ty,
|
||||
}) => match *ty {
|
||||
// cast to wildcard `_` will produce weird errors; we interpret it as taking the value as-is
|
||||
Type::Infer(_) => *expr,
|
||||
_ => Expr::Cast(ExprCast {
|
||||
attrs,
|
||||
expr,
|
||||
as_token,
|
||||
ty,
|
||||
}),
|
||||
},
|
||||
_ => expr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::env;
|
||||
use std::fs;
|
||||
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::format_ident;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Expr, LitBool, LitStr, Token};
|
||||
@@ -17,8 +16,6 @@ pub struct QueryMacroInput {
|
||||
|
||||
pub(super) record_type: RecordType,
|
||||
|
||||
// `arg0 .. argN` for N arguments
|
||||
pub(super) arg_names: Vec<Ident>,
|
||||
pub(super) arg_exprs: Vec<Expr>,
|
||||
|
||||
pub(super) checked: bool,
|
||||
@@ -82,15 +79,11 @@ impl Parse for QueryMacroInput {
|
||||
query_src.ok_or_else(|| input.error("expected `source` or `source_file` key"))?;
|
||||
|
||||
let arg_exprs = args.unwrap_or_default();
|
||||
let arg_names = (0..arg_exprs.len())
|
||||
.map(|i| format_ident!("arg{}", i))
|
||||
.collect();
|
||||
|
||||
Ok(QueryMacroInput {
|
||||
src: src.resolve(src_span)?,
|
||||
src_span,
|
||||
record_type,
|
||||
arg_names,
|
||||
arg_exprs,
|
||||
checked,
|
||||
})
|
||||
|
||||
@@ -195,10 +195,10 @@ where
|
||||
};
|
||||
|
||||
if let Some(num) = num_parameters {
|
||||
if num != input.arg_names.len() {
|
||||
if num != input.arg_exprs.len() {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
format!("expected {} parameters, got {}", num, input.arg_names.len()),
|
||||
format!("expected {} parameters, got {}", num, input.arg_exprs.len()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
@@ -260,19 +260,13 @@ where
|
||||
record_tokens
|
||||
};
|
||||
|
||||
let arg_names = &input.arg_names;
|
||||
let ret_tokens = quote! {{
|
||||
use sqlx::Arguments as _;
|
||||
|
||||
let ret_tokens = quote! {
|
||||
macro_rules! macro_result {
|
||||
(#($#arg_names:expr),*) => {{
|
||||
use sqlx::Arguments as _;
|
||||
#args_tokens
|
||||
|
||||
#args_tokens
|
||||
|
||||
#output
|
||||
}}
|
||||
}
|
||||
};
|
||||
#output
|
||||
}};
|
||||
|
||||
#[cfg(feature = "offline")]
|
||||
{
|
||||
|
||||
@@ -98,16 +98,21 @@ pub fn quote_query_as<DB: DatabaseExt>(
|
||||
match (input.checked, type_) {
|
||||
// we guarantee the type is valid so we can skip the runtime check
|
||||
(true, Some(type_)) => quote! {
|
||||
#ident: row.try_get_unchecked::<#type_, _>(#i).try_unwrap_optional()?
|
||||
// binding to a `let` avoids confusing errors about
|
||||
// "try expression alternatives have incompatible types"
|
||||
// it doesn't seem to hurt inference in the other branches
|
||||
let #ident = row.try_get_unchecked::<#type_, _>(#i)?;
|
||||
},
|
||||
// type was overridden to be a wildcard so we fallback to the runtime check
|
||||
(true, None) => quote! ( #ident: row.try_get(#i)? ),
|
||||
(true, None) => quote! ( let #ident = row.try_get(#i)?; ),
|
||||
// macro is the `_unchecked!()` variant so this will die in decoding if it's wrong
|
||||
(false, _) => quote!( #ident: row.try_get_unchecked(#i)? ),
|
||||
(false, _) => quote!( let #ident = row.try_get_unchecked(#i)?; ),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let ident = columns.iter().map(|col| &col.ident);
|
||||
|
||||
let db_path = DB::db_path();
|
||||
let row_path = DB::row_path();
|
||||
let sql = &input.src;
|
||||
@@ -115,9 +120,10 @@ pub fn quote_query_as<DB: DatabaseExt>(
|
||||
quote! {
|
||||
sqlx::query_with::<#db_path, _>(#sql, #bind_args).try_map(|row: #row_path| {
|
||||
use sqlx::Row as _;
|
||||
use sqlx::result_ext::ResultExt as _;
|
||||
|
||||
Ok(#out_ty { #(#instantiations),* })
|
||||
#(#instantiations)*
|
||||
|
||||
Ok(#out_ty { #(#ident: #ident),* })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user