diff --git a/sqlx-core/src/connection.rs b/sqlx-core/src/connection.rs index d32c863f..b9dd308b 100644 --- a/sqlx-core/src/connection.rs +++ b/sqlx-core/src/connection.rs @@ -41,36 +41,33 @@ where { type Backend = DB; - fn execute<'e, 'q: 'e, I: 'e>( + fn execute<'e, 'q: 'e>( &'e mut self, query: &'q str, - params: I, - ) -> BoxFuture<'e, crate::Result> - where - I: IntoQueryParameters + Send, - { + params: ::QueryParameters, + ) -> BoxFuture<'e, crate::Result> { self.live.execute(query, params) } - fn fetch<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: ::QueryParameters, ) -> BoxStream<'e, crate::Result> where - I: IntoQueryParameters + Send, + T: FromRow + Send + Unpin, { self.live.fetch(query, params) } - fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch_optional<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: ::QueryParameters, ) -> BoxFuture<'e, crate::Result>> where - I: IntoQueryParameters + Send, + T: FromRow + Send, { self.live.fetch_optional(query, params) diff --git a/sqlx-core/src/encode.rs b/sqlx-core/src/encode.rs index 3bf39e84..0367f8c5 100644 --- a/sqlx-core/src/encode.rs +++ b/sqlx-core/src/encode.rs @@ -1,6 +1,8 @@ //! Types and traits related to serializing values for the database. use crate::{backend::Backend, types::HasSqlType}; +use std::mem; + /// Annotates the result of [Encode] to differentiate between an empty value and a null value. pub enum IsNull { /// The value was null (and no data was written to the buffer). @@ -26,6 +28,11 @@ pub trait Encode { /// The return value indicates if this value should be represented as `NULL`. /// If this is the case, implementations **must not** write anything to `out`. fn encode(&self, buf: &mut Vec) -> IsNull; + + /// Calculate the number of bytes this type will use when encoded. + fn size_hint(&self) -> usize { + mem::size_of_val(self) + } } /// [Encode] is implemented for `Option` where `T` implements `Encode`. An `Option` @@ -43,6 +50,10 @@ where IsNull::Yes } } + + fn size_hint(&self) -> usize { + if self.is_some() { mem::size_of::() } else { 0 } + } } impl Encode for &'_ T @@ -54,4 +65,8 @@ where fn encode(&self, buf: &mut Vec) -> IsNull { (*self).encode(buf) } + + fn size_hint(&self) -> usize { + (*self).size_hint() + } } diff --git a/sqlx-core/src/executor.rs b/sqlx-core/src/executor.rs index dc8cab0a..5b918acf 100644 --- a/sqlx-core/src/executor.rs +++ b/sqlx-core/src/executor.rs @@ -16,57 +16,51 @@ pub trait Executor: Send { Box::pin( self.execute( "SELECT 1", - ::QueryParameters::new(), + Default::default(), ) .map_ok(|_| ()), ) } - fn execute<'e, 'q: 'e, I: 'e>( + fn execute<'e, 'q: 'e>( &'e mut self, query: &'q str, - params: I, - ) -> BoxFuture<'e, crate::Result> - where - I: IntoQueryParameters + Send; + params: ::QueryParameters, + ) -> BoxFuture<'e, crate::Result>; - fn fetch<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: ::QueryParameters, ) -> BoxStream<'e, crate::Result> where - I: IntoQueryParameters + Send, T: FromRow + Send + Unpin; - fn fetch_all<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch_all<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: ::QueryParameters, ) -> BoxFuture<'e, crate::Result>> where - I: IntoQueryParameters + Send, T: FromRow + Send + Unpin, { Box::pin(self.fetch(query, params).try_collect()) } - fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch_optional<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: ::QueryParameters, ) -> BoxFuture<'e, crate::Result>> where - I: IntoQueryParameters + Send, T: FromRow + Send; - fn fetch_one<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch_one<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: ::QueryParameters, ) -> BoxFuture<'e, crate::Result> where - I: IntoQueryParameters + Send, T: FromRow + Send, { let fut = self.fetch_optional(query, params); diff --git a/sqlx-core/src/mariadb/connection.rs b/sqlx-core/src/mariadb/connection.rs index 5c907379..1de390f2 100644 --- a/sqlx-core/src/mariadb/connection.rs +++ b/sqlx-core/src/mariadb/connection.rs @@ -248,7 +248,7 @@ impl MariaDb { self.write(ComStmtExecute { statement_id, params: ¶ms.params, - null: ¶ms.null, + null: ¶ms.null_bitmap, flags: StmtExecFlag::NO_CURSOR, param_types: ¶ms.param_types, }); diff --git a/sqlx-core/src/mariadb/executor.rs b/sqlx-core/src/mariadb/executor.rs index da6e559f..1f798ade 100644 --- a/sqlx-core/src/mariadb/executor.rs +++ b/sqlx-core/src/mariadb/executor.rs @@ -19,14 +19,11 @@ use futures_core::{future::BoxFuture, stream::BoxStream}; impl Executor for MariaDb { type Backend = Self; - fn execute<'e, 'q: 'e, I: 'e>( + fn execute<'e, 'q: 'e>( &'e mut self, query: &'q str, - params: I, - ) -> BoxFuture<'e, crate::Result> - where - I: IntoQueryParameters + Send, - { + params: MariaDbQueryParameters, + ) -> BoxFuture<'e, crate::Result> { let params = params.into_params(); Box::pin(async move { @@ -67,13 +64,12 @@ impl Executor for MariaDb { }) } - fn fetch<'e, 'q: 'e, I: 'e, O: 'e, T: 'e>( + fn fetch<'e, 'q: 'e, O: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: MariaDbQueryParameters, ) -> BoxStream<'e, crate::Result> where - I: IntoQueryParameters + Send, T: FromRow + Send + Unpin, { let params = params.into_params(); @@ -108,13 +104,12 @@ impl Executor for MariaDb { }) } - fn fetch_optional<'e, 'q: 'e, I: 'e, O: 'e, T: 'e>( + fn fetch_optional<'e, 'q: 'e, O: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: MariaDbQueryParameters, ) -> BoxFuture<'e, crate::Result>> where - I: IntoQueryParameters + Send, T: FromRow + Send, { let params = params.into_params(); diff --git a/sqlx-core/src/mariadb/query.rs b/sqlx-core/src/mariadb/query.rs index 4a223ebb..0a798937 100644 --- a/sqlx-core/src/mariadb/query.rs +++ b/sqlx-core/src/mariadb/query.rs @@ -6,21 +6,25 @@ use crate::{ types::HasSqlType, }; +#[derive(Default)] pub struct MariaDbQueryParameters { pub(crate) param_types: Vec, pub(crate) params: Vec, - pub(crate) null: Vec, + pub(crate) null_bitmap: Vec, } impl QueryParameters for MariaDbQueryParameters { type Backend = MariaDb; - fn new() -> Self { - Self { - param_types: Vec::with_capacity(4), - params: Vec::with_capacity(32), - null: vec![0], - } + fn reserve(&mut self, binds: usize, bytes: usize) { + self.param_types.reserve(binds); + self.params.reserve(bytes); + + // ensure we have enough bytes in the bitmap to hold at least `binds` extra bits + // the second `& 7` gives us 0 spare bits when param_types.len() is a multiple of 8 + let spare_bits = (8 - (self.param_types.len()) & 7) & 7; + // ensure that if there are no spare bits left, `binds = 1` reserves another byte + self.null_bitmap.reserve( (binds + 7 - spare_bits) / 8); } fn bind(&mut self, value: T) @@ -33,9 +37,10 @@ impl QueryParameters for MariaDbQueryParameters { let index = self.param_types.len(); self.param_types.push(metadata); + self.null_bitmap.resize(index / 8, 0); if let IsNull::Yes = value.encode(&mut self.params) { - self.null[index / 8] = self.null[index / 8] & (1 << index % 8); + self.null_bitmap[index / 8] &= (1 << index % 8); } } } diff --git a/sqlx-core/src/params.rs b/sqlx-core/src/params.rs index 08c7c25b..20f377ae 100644 --- a/sqlx-core/src/params.rs +++ b/sqlx-core/src/params.rs @@ -1,11 +1,9 @@ use crate::{backend::Backend, encode::Encode, types::HasSqlType}; -pub trait QueryParameters: Send { +pub trait QueryParameters: Default + Send { type Backend: Backend; - fn new() -> Self - where - Self: Sized; + fn reserve(&mut self, binds: usize, bytes: usize); fn bind(&mut self, value: T) where @@ -29,8 +27,12 @@ macro_rules! impl_into_query_parameters { $($T: crate::encode::Encode<$B>,)+ { fn into_params(self) -> <$B as crate::backend::Backend>::QueryParameters { - let mut params = <<$B as crate::backend::Backend>::QueryParameters - as crate::params::QueryParameters>::new(); + let mut params = <$B as crate::backend::Backend>::QueryParameters::default(); + + let binds = 0 $(+ { $idx; 1 } )+; + let bytes = 0 $(+ crate::encode::Encode::size_hint(&self.$idx))+; + + params.reserve(binds, bytes); $(crate::params::QueryParameters::bind(&mut params, self.$idx);)+ @@ -57,8 +59,7 @@ macro_rules! impl_into_query_parameters_for_backend { { #[inline] fn into_params(self) -> <$B as crate::backend::Backend>::QueryParameters { - <<$B as crate::backend::Backend>::QueryParameters - as crate::params::QueryParameters>::new() + Default::default() } } diff --git a/sqlx-core/src/pool/executor.rs b/sqlx-core/src/pool/executor.rs index 1800e99a..ff8861ea 100644 --- a/sqlx-core/src/pool/executor.rs +++ b/sqlx-core/src/pool/executor.rs @@ -11,24 +11,20 @@ where { type Backend = DB; - fn execute<'e, 'q: 'e, I: 'e>( + fn execute<'e, 'q: 'e>( &'e mut self, query: &'q str, - params: I, - ) -> BoxFuture<'e, crate::Result> - where - I: IntoQueryParameters + Send, - { + params: DB::QueryParameters, + ) -> BoxFuture<'e, crate::Result> { Box::pin(async move { <&Pool as Executor>::execute(&mut &*self, query, params).await }) } - fn fetch<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: DB::QueryParameters, ) -> BoxStream<'e, crate::Result> where - I: IntoQueryParameters + Send, T: FromRow + Send + Unpin, { Box::pin(async_stream::try_stream! { @@ -41,13 +37,12 @@ where }) } - fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch_optional<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: DB::QueryParameters, ) -> BoxFuture<'e, crate::Result>> where - I: IntoQueryParameters + Send, T: FromRow + Send, { Box::pin(async move { @@ -69,24 +64,20 @@ where { type Backend = DB; - fn execute<'e, 'q: 'e, I: 'e>( + fn execute<'e, 'q: 'e>( &'e mut self, query: &'q str, - params: I, - ) -> BoxFuture<'e, crate::Result> - where - I: IntoQueryParameters + Send, - { + params: DB::QueryParameters, + ) -> BoxFuture<'e, crate::Result> { Box::pin(async move { self.0.acquire().await?.execute(query, params).await }) } - fn fetch<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: DB::QueryParameters, ) -> BoxStream<'e, crate::Result> where - I: IntoQueryParameters + Send, T: FromRow + Send + Unpin, { Box::pin(async_stream::try_stream! { @@ -99,13 +90,12 @@ where }) } - fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch_optional<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: DB::QueryParameters, ) -> BoxFuture<'e, crate::Result>> where - I: IntoQueryParameters + Send, T: FromRow + Send, { Box::pin(async move { self.0.acquire().await?.fetch_optional(query, params).await }) diff --git a/sqlx-core/src/postgres/executor.rs b/sqlx-core/src/postgres/executor.rs index c575ed5c..85bff554 100644 --- a/sqlx-core/src/postgres/executor.rs +++ b/sqlx-core/src/postgres/executor.rs @@ -8,21 +8,17 @@ use crate::{ url::Url, }; use futures_core::{future::BoxFuture, stream::BoxStream}; +use crate::postgres::query::PostgresQueryParameters; impl Executor for Postgres { type Backend = Self; - fn execute<'e, 'q: 'e, I: 'e>( + fn execute<'e, 'q: 'e>( &'e mut self, query: &'q str, - params: I, - ) -> BoxFuture<'e, crate::Result> - where - I: IntoQueryParameters + Send, - { + params: PostgresQueryParameters, + ) -> BoxFuture<'e, crate::Result> { Box::pin(async move { - let params = params.into_params(); - self.parse("", query, ¶ms); self.bind("", "", ¶ms); self.execute("", 1); @@ -40,17 +36,14 @@ impl Executor for Postgres { }) } - fn fetch<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: PostgresQueryParameters, ) -> BoxStream<'e, crate::Result> where - I: IntoQueryParameters + Send, T: FromRow + Send + Unpin, { - let params = params.into_params(); - self.parse("", query, ¶ms); self.bind("", "", ¶ms); self.execute("", 0); @@ -66,18 +59,15 @@ impl Executor for Postgres { }) } - fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>( + fn fetch_optional<'e, 'q: 'e, T: 'e>( &'e mut self, query: &'q str, - params: I, + params: PostgresQueryParameters, ) -> BoxFuture<'e, crate::Result>> where - I: IntoQueryParameters + Send, T: FromRow + Send, { Box::pin(async move { - let params = params.into_params(); - self.parse("", query, ¶ms); self.bind("", "", ¶ms); self.execute("", 2); @@ -104,7 +94,7 @@ impl Executor for Postgres { query: &'q str, ) -> BoxFuture<'e, crate::Result>> { Box::pin(async move { - self.parse("", query, &QueryParameters::new()); + self.parse("", query, &Default::default()); self.describe(""); self.sync().await?; diff --git a/sqlx-core/src/postgres/query.rs b/sqlx-core/src/postgres/query.rs index 254136ca..4ab83c36 100644 --- a/sqlx-core/src/postgres/query.rs +++ b/sqlx-core/src/postgres/query.rs @@ -7,6 +7,7 @@ use crate::{ }; use byteorder::{BigEndian, ByteOrder, NetworkEndian}; +#[derive(Default)] pub struct PostgresQueryParameters { // OIDs of the bind parameters pub(super) types: Vec, @@ -17,13 +18,9 @@ pub struct PostgresQueryParameters { impl QueryParameters for PostgresQueryParameters { type Backend = Postgres; - fn new() -> Self { - Self { - // Estimates for average number of bind parameters were - // chosen from sampling from internal projects - types: Vec::with_capacity(4), - buf: Vec::with_capacity(32), - } + fn reserve(&mut self, binds: usize, bytes: usize) { + self.types.reserve(binds); + self.buf.reserve(bytes); } fn bind(&mut self, value: T) diff --git a/sqlx-core/src/postgres/types/binary.rs b/sqlx-core/src/postgres/types/binary.rs index f58a2143..72d329bf 100644 --- a/sqlx-core/src/postgres/types/binary.rs +++ b/sqlx-core/src/postgres/types/binary.rs @@ -31,6 +31,10 @@ impl Encode for Vec { fn encode(&self, buf: &mut Vec) -> IsNull { <[u8] as Encode>::encode(self, buf) } + + fn size_hint(&self) -> usize { + self.len() + } } impl Decode for Vec { diff --git a/sqlx-core/src/postgres/types/character.rs b/sqlx-core/src/postgres/types/character.rs index 83914cd1..d01bef7f 100644 --- a/sqlx-core/src/postgres/types/character.rs +++ b/sqlx-core/src/postgres/types/character.rs @@ -38,6 +38,10 @@ impl Encode for String { fn encode(&self, buf: &mut Vec) -> IsNull { >::encode(self.as_str(), buf) } + + fn size_hint(&self) -> usize { + self.len() + } } impl Decode for String { diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index 36d0a207..d6065ecf 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -1,84 +1,68 @@ -use crate::{ - backend::Backend, - encode::Encode, - error::Error, - executor::Executor, - params::{IntoQueryParameters, QueryParameters}, - row::FromRow, - types::HasSqlType, - Row, -}; +use crate::{backend::Backend, encode::Encode, error::Error, executor::Executor, params::{IntoQueryParameters, QueryParameters}, row::FromRow, types::HasSqlType, Row, Decode}; use bitflags::_core::marker::PhantomData; use futures_core::{future::BoxFuture, stream::BoxStream}; -pub struct Query<'q, DB, I = ::QueryParameters, O = ::Row> +pub struct Query<'q, DB, P = ::QueryParameters, R = ::Row> where DB: Backend, { - #[doc(hidden)] - pub query: &'q str, - - #[doc(hidden)] - pub input: I, - - #[doc(hidden)] - pub output: PhantomData, - - #[doc(hidden)] - pub backend: PhantomData, + query: &'q str, + params: P, + record: PhantomData, + backend: PhantomData, } -impl<'q, DB, I: 'q, O: 'q> Query<'q, DB, I, O> +impl<'q, DB, P: 'q, R: 'q> Query<'q, DB, P, R> where DB: Backend, DB::QueryParameters: 'q, - I: IntoQueryParameters + Send, - O: FromRow + Send + Unpin, + P: IntoQueryParameters + Send, + R: FromRow + Send + Unpin, { #[inline] pub fn execute(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result> where E: Executor, { - executor.execute(self.query, self.input) + executor.execute(self.query, self.params.into_params()) } - pub fn fetch(self, executor: &'q mut E) -> BoxStream<'q, crate::Result> + pub fn fetch(self, executor: &'q mut E) -> BoxStream<'q, crate::Result> where E: Executor, { - executor.fetch(self.query, self.input) + executor.fetch(self.query, self.params.into_params()) } - pub fn fetch_all(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result>> + pub fn fetch_all(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result>> where E: Executor, { - executor.fetch_all(self.query, self.input) + executor.fetch_all(self.query, self.params.into_params()) } - pub fn fetch_optional(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result>> + pub fn fetch_optional(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result>> where E: Executor, { - executor.fetch_optional(self.query, self.input) + executor.fetch_optional(self.query, self.params.into_params()) } - pub fn fetch_one(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result> + pub fn fetch_one(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result> where E: Executor, { - executor.fetch_one(self.query, self.input) + executor.fetch_one(self.query, self.params.into_params()) } } -impl Query<'_, DB> +impl<'q, DB> Query<'q, DB> where DB: Backend, { /// Bind a value for use with this SQL query. /// - /// # Safety + /// # Logic Safety /// /// This function should be used with care, as SQLx cannot validate /// that the value is of the right type nor can it validate that you have @@ -88,17 +72,47 @@ where DB: HasSqlType, T: Encode, { - self.input.bind(value); + self.params.bind(value); self } -} -impl<'q, DB, I, O> Query<'q, DB, I, O> where DB: Backend { - pub fn with_output_type(self) -> Query<'q, DB, I, O_> { + /// Bind all query parameters at once. + /// + /// If any parameters were previously bound with `.bind()` they are discarded. + /// + /// # Logic Safety + /// + /// This function should be used with care, as SQLx cannot validate + /// that the value is of the right type nor can it validate that you have + /// passed the correct number of parameters. + pub fn bind_all(self, values: I) -> Query<'q, DB, I> where I: IntoQueryParameters { Query { query: self.query, - input: self.input, - output: PhantomData, + params: values, + record: PhantomData, + backend: PhantomData + } + } +} +//noinspection RsSelfConvention +impl<'q, DB, I, R> Query<'q, DB, I, R> where DB: Backend { + + /// Change the expected output type of the query to a single scalar value. + pub fn as_scalar(self) -> Query<'q, DB, I, R_> where R_: Decode { + Query { + query: self.query, + params: self.params, + record: PhantomData, + backend: PhantomData, + } + } + + /// Change the expected output of the query to a new type implementing `FromRow`. + pub fn as_record(self) -> Query<'q, DB, I, R_> where R_: FromRow { + Query { + query: self.query, + params: self.params, + record: PhantomData, backend: PhantomData, } } @@ -112,8 +126,8 @@ where { Query { query, - input: DB::QueryParameters::new(), - output: PhantomData, + params: Default::default(), + record: PhantomData, backend: PhantomData, } } diff --git a/sqlx-core/src/row.rs b/sqlx-core/src/row.rs index f9ff6eb8..f3fc5765 100644 --- a/sqlx-core/src/row.rs +++ b/sqlx-core/src/row.rs @@ -39,6 +39,13 @@ macro_rules! impl_from_row { }; } +/// Scalar conversions for rows +impl FromRow for T where DB: Backend + HasSqlType, T: Decode { + fn from_row(row: ::Row) -> Self { + row.get(0) + } +} + #[allow(unused)] macro_rules! impl_from_row_for_backend { ($B:ident, $row:ident) => { diff --git a/sqlx-macros/src/query.rs b/sqlx-macros/src/query.rs index e1eda460..181c3b8a 100644 --- a/sqlx-macros/src/query.rs +++ b/sqlx-macros/src/query.rs @@ -132,12 +132,9 @@ pub async fn process_sql( #params - sqlx::Query::<#backend_path, _, (#record_type)> { - query: #query, - input: params, - output: ::core::marker::PhantomData, - backend: ::core::marker::PhantomData, - } + sqlx::query::<#backend_path>(#query) + .bind_all(params) + .as_record::<#record_type>() }}) } diff --git a/tests/macros.rs b/tests/macros.rs index e6ca7bc0..431e29ed 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -6,12 +6,12 @@ async fn postgres_query() -> sqlx::Result<()> { sqlx::Connection::::open(&dotenv::var("DATABASE_URL").unwrap()).await?; let uuid: Uuid = "256ba9c8-0048-11ea-b0f0-8f04859d047e".parse().unwrap(); - let account = sqlx::query!("SELECT * from accounts where id != $1", uuid) + let account_id = sqlx::query!("SELECT id from accounts where id != $1", uuid) + .as_scalar::() .fetch_one(&mut conn) .await?; - println!("account ID: {:?}", account.id); - println!("account name: {}", account.name); + println!("account ID: {:?}", account_id); Ok(()) }