diff --git a/sqlx-core/src/from_row.rs b/sqlx-core/src/from_row.rs new file mode 100644 index 00000000..529892d4 --- /dev/null +++ b/sqlx-core/src/from_row.rs @@ -0,0 +1,204 @@ +use crate::{Row, TypeDecodeOwned}; + +/// A type that can be built from a row returned by the database. +pub trait FromRow: Sized { + /// Build a value of `Self` from the given [`Row`]. + fn from_row(row: &R) -> crate::Result; +} + +macro_rules! impl_from_row_for_tuple { + ($( ($idx:tt) -> $T:ident );+;) => { + impl FromRow for ($($T,)+) + where + $($T: TypeDecodeOwned,)+ + { + fn from_row(row: &R) -> crate::Result { + Ok(($(row.try_get($idx)?,)+)) + } + } + }; +} + +impl_from_row_for_tuple!( + (0) -> T1; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + (9) -> T10; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + (9) -> T10; + (10) -> T11; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + (9) -> T10; + (10) -> T11; + (11) -> T12; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + (9) -> T10; + (10) -> T11; + (11) -> T12; + (12) -> T13; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + (9) -> T10; + (10) -> T11; + (11) -> T12; + (12) -> T13; + (13) -> T14; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + (9) -> T10; + (10) -> T11; + (11) -> T12; + (12) -> T13; + (13) -> T14; + (14) -> T15; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; + (9) -> T10; + (10) -> T11; + (11) -> T12; + (12) -> T13; + (13) -> T14; + (14) -> T15; + (15) -> T16; +); diff --git a/sqlx-core/src/lib.rs b/sqlx-core/src/lib.rs index e121e3b3..6f2e8913 100644 --- a/sqlx-core/src/lib.rs +++ b/sqlx-core/src/lib.rs @@ -32,6 +32,7 @@ mod error; mod execute; mod executor; mod isolation_level; +mod from_row; mod options; mod query_result; pub mod row; @@ -63,13 +64,14 @@ pub use connection::Connection; pub use database::Database; pub use decode::Decode; pub use encode::Encode; +pub use from_row::FromRow; pub use error::{DatabaseError, Error, Result}; pub use execute::Execute; pub use executor::Executor; pub use isolation_level::IsolationLevel; pub use options::ConnectOptions; pub use query_result::QueryResult; -pub use r#type::{Type, TypeDecode, TypeEncode}; +pub use r#type::{Type, TypeDecode, TypeEncode, TypeDecodeOwned}; pub use row::{ColumnIndex, Row}; #[cfg(feature = "actix")] pub use runtime::Actix; diff --git a/sqlx-core/src/type.rs b/sqlx-core/src/type.rs index 4d670791..3300f080 100644 --- a/sqlx-core/src/type.rs +++ b/sqlx-core/src/type.rs @@ -74,3 +74,8 @@ impl + Encode> TypeEncode for T { pub trait TypeDecode<'r, Db: Database>: Type + Decode<'r, Db> {} impl<'r, T: Type + Decode<'r, Db>, Db: Database> TypeDecode<'r, Db> for T {} + +#[allow(clippy::module_name_repetitions)] +pub trait TypeDecodeOwned: for<'r> TypeDecode<'r, Db> {} + +impl TypeDecodeOwned for T where T: for<'r> TypeDecode<'r, Db> {} diff --git a/sqlx/src/lib.rs b/sqlx/src/lib.rs index a501beed..44137ed8 100644 --- a/sqlx/src/lib.rs +++ b/sqlx/src/lib.rs @@ -47,6 +47,7 @@ pub mod blocking; mod query; +mod query_as; mod runtime; #[cfg(feature = "mysql")] @@ -58,6 +59,7 @@ pub mod mysql; #[cfg(feature = "blocking")] pub use blocking::Blocking; pub use query::{query, Query}; +pub use query_as::{query_as, QueryAs}; pub use runtime::DefaultRuntime; #[cfg(feature = "actix")] pub use sqlx_core::Actix; diff --git a/sqlx/src/query_as.rs b/sqlx/src/query_as.rs new file mode 100644 index 00000000..73e08a46 --- /dev/null +++ b/sqlx/src/query_as.rs @@ -0,0 +1,65 @@ +use std::borrow::Cow; +use std::marker::PhantomData; + +use sqlx_core::{Execute, Executor, FromRow, TypeEncode}; + +use crate::{query, Arguments, Database, DefaultRuntime, Query, Runtime}; + +pub struct QueryAs<'q, 'a, O, Db: Database, Rt: Runtime = DefaultRuntime> { + inner: Query<'q, 'a, Db, Rt>, + output: PhantomData, +} + +impl<'q, 'a, Db: Database, Rt: Runtime, O: Send + Sync> Execute<'q, 'a, Db> + for QueryAs<'q, 'a, O, Db, Rt> +{ + fn sql(&self) -> &str { + self.inner.sql() + } + + fn arguments(&self) -> Option<&Arguments<'a, Db>> { + self.inner.arguments() + } +} + +impl<'q, 'a, Db: Database, Rt: Runtime, O> QueryAs<'q, 'a, O, Db, Rt> { + pub fn bind>(&mut self, value: &'a T) -> &mut Self { + self.inner.bind(value); + self + } +} + +#[cfg(feature = "async")] +impl<'q, 'a, O, Db, Rt> QueryAs<'q, 'a, O, Db, Rt> +where + Db: Database, + Rt: crate::Async, + O: Send + Sync + FromRow, +{ + pub async fn fetch_optional(&self, mut executor: X) -> crate::Result> + where + X: Send + Executor, + { + executor.fetch_optional(&self.inner).await?.as_ref().map(O::from_row).transpose() + } + + pub async fn fetch_one(&self, mut executor: X) -> crate::Result + where + X: Send + Executor, + { + O::from_row(&executor.fetch_one(&self.inner).await?) + } + + pub async fn fetch_all(&self, mut executor: X) -> crate::Result> + where + X: Send + Executor, + { + executor.fetch_all(self).await?.iter().map(O::from_row).collect() + } +} + +pub fn query_as<'q, 'a, O, Db: Database, Rt: Runtime>( + sql: impl Into>, +) -> QueryAs<'q, 'a, O, Db, Rt> { + QueryAs::<'q, 'a, O, Db, Rt> { inner: query(sql), output: PhantomData } +}