From f67421b50d4451e147109d0504d5b25e1bb247fe Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Wed, 28 Aug 2019 08:48:18 -0700 Subject: [PATCH] Enhance Executor definition and remove inherent Executor methods on Pool and Connection --- src/backend.rs | 11 ++- src/connection.rs | 65 ++++-------- src/executor.rs | 48 +++++---- src/lib.rs | 9 +- src/pool.rs | 59 +++-------- src/postgres/backend.rs | 3 +- src/query.rs | 214 ++++++++++++++++++---------------------- src/sql.rs | 80 +++++++++++++++ 8 files changed, 259 insertions(+), 230 deletions(-) create mode 100644 src/sql.rs diff --git a/src/backend.rs b/src/backend.rs index 755f65f4..08752d05 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -2,12 +2,15 @@ use crate::{connection::RawConnection, query::QueryParameters, row::Row}; /// A database backend. /// -/// This trait is used to both allow distinct implementations of traits ( -/// e.g., implementing `ToSql for Uuid` differently for MySQL and Postgres) and -/// to query capabilities within a database backend (e.g., with a specific -/// `Connection` can we `bind` a `i64`?). +/// This trait represents the concept of a backend (e.g. "MySQL" vs "SQLite"). pub trait Backend: Sized { + /// The concrete `QueryParameters` implementation for this backend. type QueryParameters: QueryParameters; + + /// The concrete `RawConnection` implementation for this backend. type RawConnection: RawConnection; + + /// The concrete `Row` implementation for this backend. This type is returned + /// from methods in the `RawConnection`. type Row: Row; } diff --git a/src/connection.rs b/src/connection.rs index 779dd170..36bfe4c5 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -19,7 +19,12 @@ use std::{ }, }; +/// A connection to the database. +/// +/// This trait is not intended to be used directly. Instead [sqlx::Connection] or [sqlx::Pool] should be used instead, which provide +/// concurrent access and typed retrieval of results. pub trait RawConnection: Send { + /// The database backend this type connects to. type Backend: Backend; /// Establish a new connection to the database server. @@ -28,6 +33,7 @@ pub trait RawConnection: Send { Self: Sized; /// Release resources for this database connection immediately. + /// /// This method is not required to be called. A database server will eventually notice /// and clean up not fully closed connections. fn finalize<'c>(&'c mut self) -> BoxFuture<'c, Result<(), Error>>; @@ -83,40 +89,6 @@ where async fn get(&self) -> ConnectionFairy<'_, DB> { ConnectionFairy::new(&self.0, self.0.acquire().await) } - - #[inline] - pub async fn execute(&self, query: &str, params: A) -> Result - where - A: IntoQueryParameters, - { - Executor::execute(self, query, params.into()).await - } - - #[inline] - pub fn fetch<'c, 'q: 'c, A: 'c, T: 'c>( - &'c self, - query: &'q str, - params: A, - ) -> BoxStream<'c, Result> - where - A: IntoQueryParameters + Send, - T: FromSqlRow + Send + Unpin, - { - Executor::fetch(self, query, params.into()) - } - - #[inline] - pub async fn fetch_optional<'c, 'q: 'c, A: 'c, T: 'c>( - &'c self, - query: &'q str, - params: A, - ) -> Result, Error> - where - A: IntoQueryParameters + Send, - T: FromSqlRow, - { - Executor::fetch_optional(self, query, params.into()).await - } } impl Executor for Connection @@ -125,28 +97,32 @@ where { type Backend = DB; - fn execute<'c, 'q: 'c>( + fn execute<'c, 'q: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, - ) -> BoxFuture<'c, Result> { + params: A, + ) -> BoxFuture<'c, Result> + where + A: IntoQueryParameters + Send, + { Box::pin(async move { let mut conn = self.get().await; - conn.execute(query, params).await + conn.execute(query, params.into()).await }) } - fn fetch<'c, 'q: 'c, T: 'c>( + fn fetch<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxStream<'c, Result> where + A: IntoQueryParameters + Send, T: FromSqlRow + Send + Unpin, { Box::pin(async_stream::try_stream! { let mut conn = self.get().await; - let mut s = conn.fetch(query, params); + let mut s = conn.fetch(query, params.into()); while let Some(row) = s.next().await.transpose()? { yield T::from_row(row); @@ -154,17 +130,18 @@ where }) } - fn fetch_optional<'c, 'q: 'c, T: 'c>( + fn fetch_optional<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxFuture<'c, Result, Error>> where + A: IntoQueryParameters + Send, T: FromSqlRow, { Box::pin(async move { let mut conn = self.get().await; - let row = conn.fetch_optional(query, params).await?; + let row = conn.fetch_optional(query, params.into()).await?; Ok(row.map(T::from_row)) }) diff --git a/src/executor.rs b/src/executor.rs index 20d596fa..ef6cca3c 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,31 +1,40 @@ -use crate::{backend::Backend, error::Error, query::QueryParameters, row::FromSqlRow}; +use crate::{ + backend::Backend, + error::Error, + query::{IntoQueryParameters, QueryParameters}, + row::FromSqlRow, +}; use futures_core::{future::BoxFuture, stream::BoxStream}; use std::io; pub trait Executor: Send { type Backend: Backend; - fn execute<'c, 'q: 'c>( + fn execute<'c, 'q: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, - ) -> BoxFuture<'c, Result>; + params: A, + ) -> BoxFuture<'c, Result> + where + A: IntoQueryParameters + Send; - fn fetch<'c, 'q: 'c, T: 'c>( + fn fetch<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxStream<'c, Result> where + A: IntoQueryParameters + Send, T: FromSqlRow + Send + Unpin; - fn fetch_optional<'c, 'q: 'c, T: 'c>( + fn fetch_optional<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxFuture<'c, Result, Error>> where - T: FromSqlRow; + A: IntoQueryParameters + Send, + T: FromSqlRow + Send; } impl<'e, E> Executor for &'e E @@ -35,32 +44,37 @@ where type Backend = E::Backend; #[inline] - fn execute<'c, 'q: 'c>( + fn execute<'c, 'q: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, - ) -> BoxFuture<'c, Result> { + params: A, + ) -> BoxFuture<'c, Result> + where + A: IntoQueryParameters + Send, + { (*self).execute(query, params) } - fn fetch<'c, 'q: 'c, T: 'c>( + fn fetch<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxStream<'c, Result> where + A: IntoQueryParameters + Send, T: FromSqlRow + Send + Unpin, { (*self).fetch(query, params) } - fn fetch_optional<'c, 'q: 'c, T: 'c>( + fn fetch_optional<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxFuture<'c, Result, Error>> where - T: FromSqlRow, + A: IntoQueryParameters + Send, + T: FromSqlRow + Send, { (*self).fetch_optional(query, params) } diff --git a/src/lib.rs b/src/lib.rs index ea288df8..7f4dde4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,15 +21,20 @@ mod connection; pub mod error; mod executor; mod pool; -mod query; + +#[macro_use] +pub mod query; + pub mod serialize; +mod sql; pub mod types; pub use self::{ connection::Connection, error::Error, + executor::Executor, pool::Pool, - query::{query, SqlQuery}, + sql::{query, SqlQuery}, }; #[cfg(feature = "mariadb")] diff --git a/src/pool.rs b/src/pool.rs index f143ffe1..bcf73470 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -62,40 +62,6 @@ where }, })) } - - #[inline] - pub async fn execute(&self, query: &str, params: A) -> Result - where - A: IntoQueryParameters, - { - Executor::execute(self, query, params.into()).await - } - - #[inline] - pub fn fetch<'c, 'q: 'c, A: 'c, T: 'c>( - &'c self, - query: &'q str, - params: A, - ) -> BoxStream<'c, Result> - where - A: IntoQueryParameters + Send, - T: FromSqlRow + Send + Unpin, - { - Executor::fetch(self, query, params.into()) - } - - #[inline] - pub async fn fetch_optional<'c, 'q: 'c, A: 'c, T: 'c>( - &'c self, - query: &'q str, - params: A, - ) -> Result, Error> - where - A: IntoQueryParameters + Send, - T: FromSqlRow, - { - Executor::fetch_optional(self, query, params.into()).await - } } struct SharedPool @@ -167,31 +133,35 @@ where { type Backend = DB; - fn execute<'c, 'q: 'c>( + fn execute<'c, 'q: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, - ) -> BoxFuture<'c, Result> { + params: A, + ) -> BoxFuture<'c, Result> + where + A: IntoQueryParameters + Send, + { Box::pin(async move { let live = self.0.acquire().await?; let mut conn = PooledConnection::new(&self.0, live); - conn.execute(query, params).await + conn.execute(query, params.into()).await }) } - fn fetch<'c, 'q: 'c, T: 'c>( + fn fetch<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxStream<'c, Result> where + A: IntoQueryParameters + Send, T: FromSqlRow + Send + Unpin, { Box::pin(async_stream::try_stream! { let live = self.0.acquire().await?; let mut conn = PooledConnection::new(&self.0, live); - let mut s = conn.fetch(query, params); + let mut s = conn.fetch(query, params.into()); while let Some(row) = s.next().await.transpose()? { yield T::from_row(row); @@ -199,18 +169,19 @@ where }) } - fn fetch_optional<'c, 'q: 'c, T: 'c>( + fn fetch_optional<'c, 'q: 'c, T: 'c, A: 'c>( &'c self, query: &'q str, - params: ::QueryParameters, + params: A, ) -> BoxFuture<'c, Result, Error>> where + A: IntoQueryParameters + Send, T: FromSqlRow, { Box::pin(async move { let live = self.0.acquire().await?; let mut conn = PooledConnection::new(&self.0, live); - let row = conn.fetch_optional(query, params).await?; + let row = conn.fetch_optional(query, params.into()).await?; Ok(row.map(T::from_row)) }) diff --git a/src/postgres/backend.rs b/src/postgres/backend.rs index fa8307a9..bf36e2af 100644 --- a/src/postgres/backend.rs +++ b/src/postgres/backend.rs @@ -8,5 +8,6 @@ impl Backend for Postgres { type Row = super::PostgresRow; } -// Generates tuple FromSqlRow impls for this backend +// Generates tuple impls for this backend impl_from_sql_row_tuples_for_backend!(Postgres); +impl_into_query_parameters_for_backend!(Postgres); diff --git a/src/query.rs b/src/query.rs index 0c9b288a..0a864c46 100644 --- a/src/query.rs +++ b/src/query.rs @@ -27,138 +27,116 @@ where #[allow(unused)] macro_rules! impl_into_query_parameters { - - ($( ($idx:tt) -> $T:ident );+;) => { - impl<$($T,)+ DB> IntoQueryParameters for ($($T,)+) + ($B:ident: $( ($idx:tt) -> $T:ident );+;) => { + impl<$($T,)+> crate::query::IntoQueryParameters<$B> for ($($T,)+) where - DB: Backend, - $(DB: crate::types::HasSqlType<$T>,)+ - $($T: crate::serialize::ToSql,)+ + $($B: crate::types::HasSqlType<$T>,)+ + $($T: crate::serialize::ToSql<$B>,)+ { - fn into(self) -> DB::QueryParameters { - let mut params = DB::QueryParameters::new(); - $(params.bind(self.$idx);)+ + fn into(self) -> <$B as crate::backend::Backend>::QueryParameters { + let mut params = <<$B as crate::backend::Backend>::QueryParameters + as crate::query::QueryParameters>::new(); + + $(crate::query::QueryParameters::bind(&mut params, self.$idx);)+ + params } } }; } -impl IntoQueryParameters for () +impl IntoQueryParameters for DB::QueryParameters where DB: Backend, { + #[inline] fn into(self) -> DB::QueryParameters { - DB::QueryParameters::new() - } -} - -impl_into_query_parameters!( - (0) -> T1; -); - -impl_into_query_parameters!( - (0) -> T1; - (1) -> T2; -); - -impl_into_query_parameters!( - (0) -> T1; - (1) -> T2; - (2) -> T3; -); - -impl_into_query_parameters!( - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; -); - -impl_into_query_parameters!( - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; -); - -impl_into_query_parameters!( - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; -); - -pub struct SqlQuery<'q, DB> -where - DB: Backend, -{ - query: &'q str, - params: DB::QueryParameters, -} - -impl<'q, DB> SqlQuery<'q, DB> -where - DB: Backend, -{ - #[inline] - pub fn new(query: &'q str) -> Self { - Self { - query, - params: DB::QueryParameters::new(), - } - } - - #[inline] - pub fn bind(mut self, value: T) -> Self - where - DB: HasSqlType, - T: ToSql, - { - self.params.bind(value); self } - - // TODO: These methods should go on a [Execute] trait (so more execut-able things can be defined) - - #[inline] - pub fn execute(self, executor: &'q E) -> BoxFuture<'q, Result> - where - E: Executor, - { - executor.execute(self.query, self.params) - } - - #[inline] - pub fn fetch(self, executor: &'q E) -> BoxStream<'q, Result> - where - E: Executor, - T: FromSqlRow + Send + Unpin, - { - executor.fetch(self.query, self.params) - } - - #[inline] - pub fn fetch_optional( - self, - executor: &'q E, - ) -> BoxFuture<'q, Result, Error>> - where - E: Executor, - T: FromSqlRow, - { - executor.fetch_optional(self.query, self.params) - } } -/// Construct a full SQL query using raw SQL. -#[inline] -pub fn query<'q, DB>(query: &'q str) -> SqlQuery<'q, DB> -where - DB: Backend, -{ - SqlQuery::new(query) +#[allow(unused)] +macro_rules! impl_into_query_parameters_for_backend { + ($B:ident) => { + impl crate::query::IntoQueryParameters<$B> for () + { + #[inline] + fn into(self) -> <$B as crate::backend::Backend>::QueryParameters { + <<$B as crate::backend::Backend>::QueryParameters + as crate::query::QueryParameters>::new() + } + } + + impl_into_query_parameters!($B: + (0) -> T1; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + (2) -> T3; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + ); + + impl_into_query_parameters!($B: + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + ); + } } diff --git a/src/sql.rs b/src/sql.rs new file mode 100644 index 00000000..79fad1e0 --- /dev/null +++ b/src/sql.rs @@ -0,0 +1,80 @@ +use crate::{ + backend::Backend, error::Error, executor::Executor, query::QueryParameters, row::FromSqlRow, + serialize::ToSql, types::HasSqlType, +}; +use futures_core::{future::BoxFuture, stream::BoxStream}; +use std::io; + +pub struct SqlQuery<'q, DB> +where + DB: Backend, +{ + query: &'q str, + params: DB::QueryParameters, +} + +impl<'q, DB> SqlQuery<'q, DB> +where + DB: Backend, +{ + #[inline] + pub fn new(query: &'q str) -> Self { + Self { + query, + params: DB::QueryParameters::new(), + } + } + + #[inline] + pub fn bind(mut self, value: T) -> Self + where + DB: HasSqlType, + T: ToSql, + { + self.params.bind(value); + self + } + + // TODO: These methods should go on a [Execute] trait (so more execut-able things can be defined) + + #[inline] + pub fn execute(self, executor: &'q E) -> BoxFuture<'q, Result> + where + E: Executor, + DB::QueryParameters: 'q, + { + executor.execute(self.query, self.params) + } + + #[inline] + pub fn fetch(self, executor: &'q E) -> BoxStream<'q, Result> + where + E: Executor, + T: FromSqlRow + Send + Unpin, + DB::QueryParameters: 'q, + { + executor.fetch(self.query, self.params) + } + + #[inline] + pub fn fetch_optional( + self, + executor: &'q E, + ) -> BoxFuture<'q, Result, Error>> + where + E: Executor, + T: FromSqlRow + Send, + DB::QueryParameters: 'q, + { + executor.fetch_optional(self.query, self.params) + } +} + +/// Construct a full SQL query using raw SQL. +#[inline] +pub fn query<'q, DB>(query: &'q str) -> SqlQuery<'q, DB> +where + DB: Backend, +{ + SqlQuery::new(query) +}