From 0fdb875c208b584bfbae35087906645110ef23ae Mon Sep 17 00:00:00 2001 From: Austin Bonander Date: Wed, 15 Jan 2020 00:03:05 -0800 Subject: [PATCH] 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 8186833e..0c6cf5b5 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 00dfd185..ee1f540a 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 5b0b011b..b620441f 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 d39a556e..8b76759e 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 7f35aed7..31d57df8 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 ce1dedf1..cc593793 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 7ae7742f..fe45acd3 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 e6013cb8..0d6ce0cb 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 bf493819..3e4628e7 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 d5d9a0f1..e33d3e4e 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(()) +}