From da5c538d22beef1169656ee1d87c38b1dc07631a Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Tue, 14 Jan 2020 12:57:55 -0800 Subject: [PATCH 1/2] strip out `proc-macro-hack` --- Cargo.lock | 2 - Cargo.toml | 3 +- sqlx-macros/Cargo.toml | 1 - sqlx-macros/src/lib.rs | 46 ++++++++------ sqlx-macros/src/query_macros/args.rs | 10 +-- sqlx-macros/src/query_macros/input.rs | 88 +++++++++++++++++++++------ sqlx-macros/src/query_macros/mod.rs | 13 ++-- sqlx-macros/src/query_macros/query.rs | 21 ++++--- src/lib.rs | 30 ++------- src/macros.rs | 68 ++++++++++++++------- tests/mysql.rs | 15 +++++ 11 files changed, 190 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82ea7eaf7..53110d27a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1308,7 +1308,6 @@ dependencies = [ "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "sqlx-core 0.1.4", "sqlx-macros 0.1.1", ] @@ -1368,7 +1367,6 @@ dependencies = [ "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "sqlx-core 0.1.4", diff --git a/Cargo.toml b/Cargo.toml index d78f1ffc9..855d7b56c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ all-features = true [features] default = [ "macros" ] -macros = [ "sqlx-macros", "proc-macro-hack" ] +macros = [ "sqlx-macros" ] tls = ["sqlx-core/tls"] # database @@ -43,7 +43,6 @@ uuid = [ "sqlx-core/uuid", "sqlx-macros/uuid" ] [dependencies] sqlx-core = { version = "=0.1.4", path = "sqlx-core" } sqlx-macros = { version = "0.1.1", path = "sqlx-macros", optional = true } -proc-macro-hack = { version = "0.5.11", optional = true } [dev-dependencies] anyhow = "1.0.26" diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 622703b38..1ead68f3f 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -33,7 +33,6 @@ uuid = [ "sqlx/uuid" ] async-std = { version = "1.4.0", default-features = false } dotenv = { version = "0.15.0", default-features = false } futures = { version = "0.3.1", default-features = false } -proc-macro-hack = { version = "0.5.11", default-features = false } proc-macro2 = { version = "1.0.6", default-features = false } sqlx = { version = "0.1.1", default-features = false, path = "../sqlx-core", package = "sqlx-core" } syn = { version = "1.0.11", default-features = false, features = [ "full" ] } diff --git a/sqlx-macros/src/lib.rs b/sqlx-macros/src/lib.rs index ea997c3ef..ce1dedf16 100644 --- a/sqlx-macros/src/lib.rs +++ b/sqlx-macros/src/lib.rs @@ -6,8 +6,6 @@ extern crate proc_macro; use proc_macro::TokenStream; -use proc_macro_hack::proc_macro_hack; - use quote::quote; use syn::parse_macro_input; @@ -26,8 +24,22 @@ mod query_macros; use query_macros::*; +fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream { + quote!( + macro_rules! macro_result { + ($($args:tt)*) => (#tokens) + } + ) + .into() +} + macro_rules! async_macro ( - ($db:ident => $expr:expr) => {{ + ($db:ident, $input:ident: $ty:ty => $expr:expr) => {{ + let $input = match syn::parse::<$ty>($input) { + Ok(input) => input, + Err(e) => return macro_result(e.to_compile_error()), + }; + let res: Result = task::block_on(async { use sqlx::Connection; @@ -70,40 +82,36 @@ macro_rules! async_macro ( Ok(ts) => ts.into(), Err(e) => { if let Some(parse_err) = e.downcast_ref::() { - return dbg!(parse_err).to_compile_error().into(); + macro_result(parse_err.to_compile_error()) + } else { + let msg = format!("{:?}", e); + macro_result(quote!(compile_error(#msg))) } - - let msg = format!("{:?}", e); - quote!(compile_error!(#msg);).into() } } }} ); -#[proc_macro_hack] +#[proc_macro] pub fn query(input: TokenStream) -> TokenStream { #[allow(unused_variables)] - let input = parse_macro_input!(input as QueryMacroInput); - async_macro!(db => expand_query(input, db)) + async_macro!(db, input: QueryMacroInput => expand_query(input, db)) } -#[proc_macro_hack] +#[proc_macro] pub fn query_file(input: TokenStream) -> TokenStream { #[allow(unused_variables)] - let input = parse_macro_input!(input as QueryMacroInput); - async_macro!(db => expand_query_file(input, db)) + async_macro!(db, input: QueryMacroInput => expand_query_file(input, db)) } -#[proc_macro_hack] +#[proc_macro] pub fn query_as(input: TokenStream) -> TokenStream { #[allow(unused_variables)] - let input = parse_macro_input!(input as QueryAsMacroInput); - async_macro!(db => expand_query_as(input, db)) + async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db)) } -#[proc_macro_hack] +#[proc_macro] pub fn query_file_as(input: TokenStream) -> TokenStream { #[allow(unused_variables)] - let input = parse_macro_input!(input as QueryAsMacroInput); - async_macro!(db => expand_query_file_as(input, db)) + async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db)) } diff --git a/sqlx-macros/src/query_macros/args.rs b/sqlx-macros/src/query_macros/args.rs index f0766c36b..7ae7742f8 100644 --- a/sqlx-macros/src/query_macros/args.rs +++ b/sqlx-macros/src/query_macros/args.rs @@ -12,7 +12,7 @@ pub fn quote_args( input: &QueryMacroInput, describe: &Describe, ) -> crate::Result { - if input.args.is_empty() { + if input.arg_names.is_empty() { return Ok(quote! { let args = (); }); @@ -22,7 +22,7 @@ pub fn quote_args( let param_types = describe .param_types .iter() - .zip(&*input.args) + .zip(&*input.arg_exprs) .map(|(type_, expr)| { get_type_override(expr) .or_else(|| { @@ -36,7 +36,7 @@ pub fn quote_args( }) .collect::>>()?; - let args_ty_cons = input.args.iter().enumerate().map(|(i, expr)| { + let args_ty_cons = input.arg_names.iter().enumerate().map(|(i, expr)| { // required or `quote!()` emits it as `Nusize` let i = syn::Index::from(i); quote_spanned!( expr.span() => { @@ -56,10 +56,10 @@ pub fn quote_args( TokenStream::new() }; - let args = input.args.iter(); + let args = input.arg_names.iter(); Ok(quote! { - let args = (#(&#args),*,); + let args = (#(&$#args),*,); #args_check }) } diff --git a/sqlx-macros/src/query_macros/input.rs b/sqlx-macros/src/query_macros/input.rs index 3cbae5dee..5c6f912e8 100644 --- a/sqlx-macros/src/query_macros/input.rs +++ b/sqlx-macros/src/query_macros/input.rs @@ -1,11 +1,15 @@ use std::env; use async_std::fs; -use proc_macro2::Span; +use proc_macro2::{Ident, Span, TokenStream}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::Token; +use syn::spanned::Spanned; +use syn::token::Group; use syn::{Expr, ExprLit, ExprPath, Lit}; +use syn::{ExprGroup, Token}; + +use quote::{format_ident, quote, ToTokens}; use sqlx::describe::Describe; use sqlx::Connection; @@ -14,28 +18,55 @@ use sqlx::Connection; pub struct QueryMacroInput { pub(super) source: String, pub(super) source_span: Span, - pub(super) args: Vec, + // `arg0 .. argN` for N arguments + pub(super) arg_names: Vec, + pub(super) arg_exprs: Vec, } impl QueryMacroInput { fn from_exprs(input: ParseStream, mut args: impl Iterator) -> syn::Result { - let sql = match args.next() { + fn lit_err(span: Span, unexpected: Expr) -> syn::Result { + Err(syn::Error::new( + span, + format!( + "expected string literal, got {}", + unexpected.to_token_stream() + ), + )) + } + + let (source, source_span) = match args.next() { Some(Expr::Lit(ExprLit { lit: Lit::Str(sql), .. - })) => sql, - Some(other_expr) => { - return Err(syn::Error::new_spanned( - other_expr, - "expected string literal", - )); + })) => (sql.value(), sql.span()), + Some(Expr::Group(ExprGroup { + expr, + group_token: Group { span }, + .. + })) => { + // this duplication with the above is necessary because `expr` is `Box` here + // which we can't directly pattern-match without `box_patterns` + match *expr { + Expr::Lit(ExprLit { + lit: Lit::Str(sql), .. + }) => (sql.value(), span), + other_expr => return lit_err(span, other_expr), + } } + Some(other_expr) => return lit_err(other_expr.span(), other_expr), None => return Err(input.error("expected SQL string literal")), }; + let arg_exprs: Vec<_> = args.collect(); + let arg_names = (0..arg_exprs.len()) + .map(|i| format_ident!("arg{}", i)) + .collect(); + Ok(Self { - source: sql.value(), - source_span: sql.span(), - args: args.collect(), + source, + source_span, + arg_exprs, + arg_names, }) } @@ -56,13 +87,13 @@ impl QueryMacroInput { .await .map_err(|e| syn::Error::new(self.source_span, e))?; - if self.args.len() != describe.param_types.len() { + if self.arg_names.len() != describe.param_types.len() { return Err(syn::Error::new( Span::call_site(), format!( "expected {} parameters, got {}", describe.param_types.len(), - self.args.len() + self.arg_names.len() ), ) .into()); @@ -97,16 +128,33 @@ impl QueryAsMacroInput { impl Parse for QueryAsMacroInput { fn parse(input: ParseStream) -> syn::Result { + fn path_err(span: Span, unexpected: Expr) -> syn::Result { + Err(syn::Error::new( + span, + format!( + "expected path to a type, got {}", + unexpected.to_token_stream() + ), + )) + } + let mut args = Punctuated::::parse_terminated(input)?.into_iter(); let as_ty = match args.next() { Some(Expr::Path(path)) => path, - Some(other_expr) => { - return Err(syn::Error::new_spanned( - other_expr, - "expected path to a type", - )); + Some(Expr::Group(ExprGroup { + expr, + group_token: Group { span }, + .. + })) => { + // this duplication with the above is necessary because `expr` is `Box` here + // which we can't directly pattern-match without `box_patterns` + match *expr { + Expr::Path(path) => path, + other_expr => return path_err(span, other_expr), + } } + Some(other_expr) => return path_err(other_expr.span(), other_expr), None => return Err(input.error("expected path to SQL file")), }; diff --git a/sqlx-macros/src/query_macros/mod.rs b/sqlx-macros/src/query_macros/mod.rs index 144d42859..e6013cb8c 100644 --- a/sqlx-macros/src/query_macros/mod.rs +++ b/sqlx-macros/src/query_macros/mod.rs @@ -46,6 +46,7 @@ where } let args_tokens = args::quote_args(&input.query_input, &describe)?; + let arg_names = &input.query_input.arg_names; let columns = output::columns_to_rust(&describe)?; let output = output::quote_query_as::( @@ -54,10 +55,14 @@ where &columns, ); - Ok(quote! {{ - #args_tokens - #output.bind_all(args) - }}) + Ok(quote! { + macro_rules! macro_result { + (#($#arg_names:expr),*) => {{ + #args_tokens + #output.bind_all(args) + }} + } + }) } pub async fn expand_query_file_as( diff --git a/sqlx-macros/src/query_macros/query.rs b/sqlx-macros/src/query_macros/query.rs index 5b50b26c4..bf493819c 100644 --- a/sqlx-macros/src/query_macros/query.rs +++ b/sqlx-macros/src/query_macros/query.rs @@ -50,15 +50,20 @@ where .collect::(); let output = output::quote_query_as::(sql, &record_type, &columns); + let arg_names = &input.arg_names; - Ok(quote! {{ - #[derive(Debug)] - struct #record_type { - #record_fields - } + Ok(quote! { + macro_rules! macro_result { + (#($#arg_names:expr),*) => {{ + #[derive(Debug)] + struct #record_type { + #record_fields + } - #args + #args - #output.bind_all(args) - }}) + #output.bind_all(args) + } + }} + }) } diff --git a/src/lib.rs b/src/lib.rs index 17a9c085e..2df1ea045 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,34 +20,14 @@ pub use sqlx_core::mysql::{self, MySql, MySqlConnection, MySqlPool}; #[cfg(feature = "postgres")] pub use sqlx_core::postgres::{self, PgConnection, PgPool, Postgres}; -#[allow(unused_attributes)] +#[cfg(feature = "macros")] +#[doc(hidden)] +pub extern crate sqlx_macros; + +#[cfg(feature = "macros")] #[macro_export] mod macros; -#[cfg(feature = "macros")] -#[doc(hidden)] -#[proc_macro_hack::proc_macro_hack(fake_call_site)] -#[allow(dead_code)] -pub use sqlx_macros::query as query_; - -#[cfg(feature = "macros")] -#[doc(hidden)] -#[proc_macro_hack::proc_macro_hack(fake_call_site)] -#[allow(dead_code)] -pub use sqlx_macros::query_as as query_as_; - -#[cfg(feature = "macros")] -#[doc(hidden)] -#[proc_macro_hack::proc_macro_hack(fake_call_site)] -#[allow(dead_code)] -pub use sqlx_macros::query_file as query_file_; - -#[cfg(feature = "macros")] -#[doc(hidden)] -#[proc_macro_hack::proc_macro_hack(fake_call_site)] -#[allow(dead_code)] -pub use sqlx_macros::query_file_as as query_file_as_; - // macro support #[cfg(feature = "macros")] #[doc(hidden)] diff --git a/src/macros.rs b/src/macros.rs index afbef6148..9086bffd7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -82,18 +82,23 @@ /// * [query_as!] if you want to use a struct you can name, /// * [query_file!] if you want to define the SQL query out-of-line, /// * [query_file_as!] if you want both of the above. -#[cfg(feature = "macros")] #[macro_export] macro_rules! query ( - // the emitted item for `#[proc_macro_hack]` doesn't look great in docs - // plus this might let IDEs hint at the syntax - // `#[allow(dead_code)]` to silence the `enum ProcMacroHack` error - ($query:literal) => (#[allow(dead_code)] { - $crate::query_!($query) + // by emitting a macro definition from our proc-macro containing the result tokens, + // we no longer have a need for `proc-macro-hack` + ($query:literal) => ({ + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query!($query); + } + macro_result!() }); - ($query:literal, $($args:tt)*) => (#[allow(dead_code)]{ - #![allow(dead_code)] - $crate::query_!($query, $($args)*) + ($query:literal, $($args:expr),*) => ({ + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query!($query, $($args),*); + } + macro_result!($($args),*) }) ); @@ -139,14 +144,21 @@ macro_rules! query ( /// # #[cfg(not(feature = "mysql"))] /// # fn main() {} /// ``` -#[cfg(feature = "macros")] #[macro_export] macro_rules! query_file ( ($query:literal) => (#[allow(dead_code)]{ - $crate::query_file_!($query) + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query_file!($query); + } + macro_result!() }); - ($query:literal, $($args:tt)*) => (#[allow(dead_code)]{ - $crate::query_file_!($query, $($args)*) + ($query:literal, $($args:expr),*) => (#[allow(dead_code)]{ + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query_file!($query, $($args),*); + } + macro_result!($($args),*) }) ); @@ -197,14 +209,21 @@ macro_rules! query_file ( /// # #[cfg(not(feature = "mysql"))] /// # fn main() {} /// ``` -#[cfg(feature = "macros")] #[macro_export] macro_rules! query_as ( ($out_struct:path, $query:literal) => (#[allow(dead_code)] { - $crate::query_as_!($out_struct, $query) + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query_as!($out_struct, $query); + } + macro_result!() }); - ($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] { - $crate::query_as_!($out_struct, $query, $($args)*) + ($out_struct:path, $query:literal, $($args:expr),*) => (#[allow(dead_code)] { + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query_as!($out_struct, $query, $($args),*); + } + macro_result!($($args),*) }) ); @@ -240,13 +259,20 @@ macro_rules! query_as ( /// # #[cfg(not(feature = "mysql"))] /// # fn main() {} /// ``` -#[cfg(feature = "macros")] #[macro_export] macro_rules! query_file_as ( ($out_struct:path, $query:literal) => (#[allow(dead_code)] { - $crate::query_file_as_!($out_struct, $query) + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query_file_as!($out_struct, $query); + } + macro_result!() }); - ($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] { - $crate::query_file_as_!($out_struct, $query, $($args)*) + ($out_struct:path, $query:literal, $($args:expr),*) => (#[allow(dead_code)] { + #[macro_use] + mod _macro_result { + $crate::sqlx_macros::query_file_as!($out_struct, $query, $($args),*); + } + macro_result!($($args),*) }) ); diff --git a/tests/mysql.rs b/tests/mysql.rs index b13741bdb..0085b2eb0 100644 --- a/tests/mysql.rs +++ b/tests/mysql.rs @@ -73,6 +73,21 @@ async fn pool_immediately_fails_with_db_error() -> anyhow::Result<()> { #[cfg(feature = "macros")] #[async_std::test] async fn macro_select_from_cte() -> anyhow::Result<()> { + let mut conn = connect().await?; + let account = + sqlx::query!("select * from (select (1) as id, 'Herp Derpinson' as name) accounts") + .fetch_one(&mut conn) + .await?; + + println!("{:?}", account); + println!("{}: {}", account.id, account.name); + + Ok(()) +} + +#[cfg(feature = "macros")] +#[async_std::test] +async fn macro_select_from_cte_bind() -> anyhow::Result<()> { let mut conn = connect().await?; let account = sqlx::query!( "select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?", From 0fdb875c208b584bfbae35087906645110ef23ae Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Wed, 15 Jan 2020 00:03:05 -0800 Subject: [PATCH 2/2] support arbitrary numbers of bind parameters in `query!()` et al --- sqlx-core/src/arguments.rs | 119 ++------------------------ sqlx-core/src/mysql/database.rs | 2 - sqlx-core/src/postgres/arguments.rs | 2 +- sqlx-core/src/postgres/database.rs | 2 - sqlx-core/src/query_as.rs | 9 +- sqlx-macros/src/lib.rs | 2 +- sqlx-macros/src/query_macros/args.rs | 1 + sqlx-macros/src/query_macros/mod.rs | 17 +++- sqlx-macros/src/query_macros/query.rs | 16 +++- tests/postgres-macros.rs | 20 +++++ 10 files changed, 66 insertions(+), 124 deletions(-) diff --git a/sqlx-core/src/arguments.rs b/sqlx-core/src/arguments.rs index 8186833ea..0c6cf5b5a 100644 --- a/sqlx-core/src/arguments.rs +++ b/sqlx-core/src/arguments.rs @@ -38,123 +38,22 @@ where fn into_arguments(self) -> DB::Arguments; } -impl IntoArguments for DB::Arguments +impl IntoArguments for A where - DB: Database, + A: Arguments, + A::Database: Database + Sized, { #[inline] - fn into_arguments(self) -> DB::Arguments { + fn into_arguments(self) -> Self { self } } -#[allow(unused)] -macro_rules! impl_into_arguments { - ($B:ident: $( ($idx:tt) -> $T:ident );+;) => { - impl<$($T,)+> crate::arguments::IntoArguments<$B> for ($($T,)+) - where - $($B: crate::types::HasSqlType<$T>,)+ - $($T: crate::encode::Encode<$B>,)+ - { - fn into_arguments(self) -> <$B as crate::database::Database>::Arguments { - use crate::arguments::Arguments; +#[doc(hidden)] +pub struct ImmutableArguments(pub DB::Arguments); - let mut arguments = <$B as crate::database::Database>::Arguments::default(); - - let binds = 0 $(+ { $idx; 1 } )+; - let bytes = 0 $(+ crate::encode::Encode::size_hint(&self.$idx))+; - - arguments.reserve(binds, bytes); - - $(crate::arguments::Arguments::add(&mut arguments, self.$idx);)+ - - arguments - } - } - }; -} - -#[allow(unused)] -macro_rules! impl_into_arguments_for_database { - ($B:ident) => { - impl crate::arguments::IntoArguments<$B> for () - { - #[inline] - fn into_arguments(self) -> <$B as crate::database::Database>::Arguments { - Default::default() - } - } - - impl_into_arguments!($B: - (0) -> T1; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - (2) -> T3; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - (6) -> T7; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - (6) -> T7; - (7) -> T8; - ); - - impl_into_arguments!($B: - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - (6) -> T7; - (7) -> T8; - (8) -> T9; - ); +impl IntoArguments for ImmutableArguments { + fn into_arguments(self) -> ::Arguments { + self.0 } } diff --git a/sqlx-core/src/mysql/database.rs b/sqlx-core/src/mysql/database.rs index 00dfd1859..ee1f540a5 100644 --- a/sqlx-core/src/mysql/database.rs +++ b/sqlx-core/src/mysql/database.rs @@ -14,5 +14,3 @@ impl Database for MySql { type TableId = Box; } - -impl_into_arguments_for_database!(MySql); diff --git a/sqlx-core/src/postgres/arguments.rs b/sqlx-core/src/postgres/arguments.rs index 5b0b011b3..b620441f4 100644 --- a/sqlx-core/src/postgres/arguments.rs +++ b/sqlx-core/src/postgres/arguments.rs @@ -1,6 +1,6 @@ use byteorder::{ByteOrder, NetworkEndian}; -use crate::arguments::Arguments; +use crate::arguments::{Arguments, IntoArguments}; use crate::encode::{Encode, IsNull}; use crate::io::BufMut; use crate::types::HasSqlType; diff --git a/sqlx-core/src/postgres/database.rs b/sqlx-core/src/postgres/database.rs index d39a556e5..8b76759e8 100644 --- a/sqlx-core/src/postgres/database.rs +++ b/sqlx-core/src/postgres/database.rs @@ -14,5 +14,3 @@ impl Database for Postgres { type TableId = u32; } - -impl_into_arguments_for_database!(Postgres); diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs index 7f35aed72..31d57df87 100644 --- a/sqlx-core/src/query_as.rs +++ b/sqlx-core/src/query_as.rs @@ -1,7 +1,7 @@ use futures_core::Stream; use futures_util::{future, TryStreamExt}; -use crate::arguments::Arguments; +use crate::arguments::{Arguments, ImmutableArguments}; use crate::{ arguments::IntoArguments, database::Database, encode::Encode, executor::Executor, row::FromRow, types::HasSqlType, @@ -128,13 +128,10 @@ where // used by query!() and friends #[doc(hidden)] - pub fn bind_all(self, values: I) -> QueryAs<'q, DB, R, I> - where - I: IntoArguments, - { + pub fn bind_all(self, values: DB::Arguments) -> QueryAs<'q, DB, R, ImmutableArguments> { QueryAs { query: self.query, - args: values, + args: ImmutableArguments(values), map_row: self.map_row, } } diff --git a/sqlx-macros/src/lib.rs b/sqlx-macros/src/lib.rs index ce1dedf16..cc5937933 100644 --- a/sqlx-macros/src/lib.rs +++ b/sqlx-macros/src/lib.rs @@ -85,7 +85,7 @@ macro_rules! async_macro ( macro_result(parse_err.to_compile_error()) } else { let msg = format!("{:?}", e); - macro_result(quote!(compile_error(#msg))) + macro_result(quote!(compile_error!(#msg))) } } } diff --git a/sqlx-macros/src/query_macros/args.rs b/sqlx-macros/src/query_macros/args.rs index 7ae7742f8..fe45acd3c 100644 --- a/sqlx-macros/src/query_macros/args.rs +++ b/sqlx-macros/src/query_macros/args.rs @@ -59,6 +59,7 @@ pub fn quote_args( let args = input.arg_names.iter(); Ok(quote! { + // emit as a tuple first so each expression is only evaluated once let args = (#(&$#args),*,); #args_check }) diff --git a/sqlx-macros/src/query_macros/mod.rs b/sqlx-macros/src/query_macros/mod.rs index e6013cb8c..0d6ce0cbc 100644 --- a/sqlx-macros/src/query_macros/mod.rs +++ b/sqlx-macros/src/query_macros/mod.rs @@ -55,11 +55,26 @@ where &columns, ); + let db_path = ::quotable_path(); + let args_count = arg_names.len(); + let arg_indices = (0..args_count).map(|i| syn::Index::from(i)); + let arg_indices_2 = arg_indices.clone(); + Ok(quote! { macro_rules! macro_result { (#($#arg_names:expr),*) => {{ + use sqlx::arguments::Arguments as _; + #args_tokens - #output.bind_all(args) + + let mut query_args = <#db_path as sqlx::Database>::Arguments::default(); + query_args.reserve( + #args_count, + 0 #(+ sqlx::encode::Encode::<#db_path>::size_hint(args.#arg_indices))* + ); + #(query_args.add(args.#arg_indices_2);)* + + #output.bind_all(query_args) }} } }) diff --git a/sqlx-macros/src/query_macros/query.rs b/sqlx-macros/src/query_macros/query.rs index bf493819c..3e4628e79 100644 --- a/sqlx-macros/src/query_macros/query.rs +++ b/sqlx-macros/src/query_macros/query.rs @@ -51,10 +51,16 @@ where let output = output::quote_query_as::(sql, &record_type, &columns); let arg_names = &input.arg_names; + let args_count = arg_names.len(); + let arg_indices = (0..args_count).map(|i| syn::Index::from(i)); + let arg_indices_2 = arg_indices.clone(); + let db_path = ::quotable_path(); Ok(quote! { macro_rules! macro_result { (#($#arg_names:expr),*) => {{ + use sqlx::arguments::Arguments as _; + #[derive(Debug)] struct #record_type { #record_fields @@ -62,7 +68,15 @@ where #args - #output.bind_all(args) + let mut query_args = <#db_path as sqlx::Database>::Arguments::default(); + query_args.reserve( + #args_count, + 0 #(+ sqlx::encode::Encode::<#db_path>::size_hint(args.#arg_indices))* + ); + + #(query_args.add(args.#arg_indices_2);)* + + #output.bind_all(query_args) } }} }) diff --git a/tests/postgres-macros.rs b/tests/postgres-macros.rs index d5d9a0f1f..e33d3e4eb 100644 --- a/tests/postgres-macros.rs +++ b/tests/postgres-macros.rs @@ -108,3 +108,23 @@ async fn test_nullable_err() -> sqlx::Result<()> { panic!("expected `UnexpectedNull`, got {}", err) } } + +#[async_std::test] +async fn test_many_args() -> sqlx::Result<()> { + let mut conn = sqlx::postgres::connect(&dotenv::var("DATABASE_URL").unwrap()).await?; + + // previous implementation would only have supported 10 bind parameters + // (this is really gross to test in MySQL) + let rows = sqlx::query!( + "SELECT * from unnest(array[$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12]::int[]) ids(id);", + 0i32, 1i32, 2i32, 3i32, 4i32, 5i32, 6i32, 7i32, 8i32, 9i32, 10i32, 11i32 + ) + .fetch_all(&mut conn) + .await?; + + for (i, row) in rows.iter().enumerate() { + assert_eq!(i as i32, row.id); + } + + Ok(()) +}