From 7742eb2cec1fb09897c7a9245ab72d55ed0de1c1 Mon Sep 17 00:00:00 2001 From: Muhannad Alrusayni Date: Mon, 27 Sep 2021 12:47:14 +0300 Subject: [PATCH 1/2] Impl TryGetableMany for (T) --- src/executor/query.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/executor/query.rs b/src/executor/query.rs index ae09f97c7..73b9aae4f 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -314,6 +314,15 @@ where } } +impl TryGetableMany for (T,) +where + T: TryGetableMany, +{ + fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { + T::try_get_many(res, pre, cols).map(|r| (r,)) + } +} + impl TryGetableMany for (A, B) where A: TryGetable, From 5379a23630cfb47ba4167d8a486dffec13ec4697 Mon Sep 17 00:00:00 2001 From: Muhannad Alrusayni Date: Tue, 28 Sep 2021 00:10:13 +0300 Subject: [PATCH 2/2] Add `find_by_statement` for `T: TryGetableMany` Add `into_values` for `Select` --- src/executor/query.rs | 75 ++++++++++++++++++++++++- src/executor/select.rs | 124 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/src/executor/query.rs b/src/executor/query.rs index 73b9aae4f..c39c22112 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -1,6 +1,6 @@ #[cfg(feature = "mock")] use crate::debug_print; -use crate::DbErr; +use crate::{DbErr, SelectGetableValue, SelectorRaw, Statement}; use std::fmt; #[derive(Debug)] @@ -302,6 +302,79 @@ try_getable_all!(uuid::Uuid); pub trait TryGetableMany: Sized { fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result; + + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![vec![ + /// # maplit::btreemap! { + /// # "name" => Into::::into("Chocolate Forest"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # maplit::btreemap! { + /// # "name" => Into::::into("New York Cheese"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # ]]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany}; + /// + /// #[derive(EnumIter)] + /// enum ResultCol { + /// Name, + /// NumOfCakes, + /// } + /// + /// // this can be derived using derive_more crate + /// impl std::fmt::Display for ResultCol { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// match self { + /// Self::Name => write!(f, "name"), + /// Self::NumOfCakes => write!(f, "num_of_cakes"), + /// } + /// } + /// } + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let res: Vec<(String, i32)> = + /// <(String, i32)>::find_by_statement::(Statement::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// vec![], + /// )) + /// .all(&db) + /// .await?; + /// + /// assert_eq!( + /// res, + /// vec![ + /// ("Chocolate Forest".to_owned(), 1), + /// ("New York Cheese".to_owned(), 1), + /// ] + /// ); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// vec![] + /// ),] + /// ); + /// ``` + fn find_by_statement(stmt: Statement) -> SelectorRaw> + where + C: sea_strum::IntoEnumIterator + ToString, + { + SelectorRaw::>::with_columns(stmt) + } } impl TryGetableMany for T diff --git a/src/executor/select.rs b/src/executor/select.rs index 959091c01..ebc5ad11a 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,7 +1,7 @@ use crate::{ error::*, DatabaseConnection, EntityTrait, FromQueryResult, IdenStatic, Iterable, JsonValue, ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, - SelectTwoMany, Statement, + SelectTwoMany, Statement, TryGetableMany, }; use sea_query::SelectStatement; use std::marker::PhantomData; @@ -30,6 +30,16 @@ pub trait SelectorTrait { fn from_raw_query_result(res: QueryResult) -> Result; } +#[derive(Debug)] +pub struct SelectGetableValue +where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, +{ + columns: PhantomData, + model: PhantomData, +} + #[derive(Debug)] pub struct SelectModel where @@ -47,6 +57,19 @@ where model: PhantomData<(M, N)>, } +impl SelectorTrait for SelectGetableValue +where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, +{ + type Item = T; + + fn from_raw_query_result(res: QueryResult) -> Result { + let cols: Vec = C::iter().map(|col| col.to_string()).collect(); + T::try_get_many(&res, "", &cols).map_err(Into::into) + } +} + impl SelectorTrait for SelectModel where M: FromQueryResult + Sized, @@ -103,6 +126,73 @@ where } } + /// ``` + /// # #[cfg(feature = "mock")] + /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_query_results(vec![vec![ + /// # maplit::btreemap! { + /// # "name" => Into::::into("Chocolate Forest"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # maplit::btreemap! { + /// # "name" => Into::::into("New York Cheese"), + /// # "num_of_cakes" => Into::::into(1), + /// # }, + /// # ]]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, EnumIter, TryGetableMany}; + /// + /// #[derive(EnumIter)] + /// enum ResultCol { + /// Name, + /// } + /// + /// // this can be derived using derive_more crate + /// impl std::fmt::Display for ResultCol { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// match self { + /// Self::Name => write!(f, "name"), + /// } + /// } + /// } + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let res: Vec = cake::Entity::find() + /// .select_only() + /// .column(cake::Column::Name) + /// .into_values::<_, ResultCol>() + /// .all(&db) + /// .await?; + /// + /// assert_eq!( + /// res, + /// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned(),] + /// ); + /// # + /// # Ok(()) + /// # }); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name" FROM "cake""#, + /// vec![] + /// ),] + /// ); + /// ``` + pub fn into_values(self) -> Selector> + where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, + { + Selector::>::with_columns(self.query) + } + pub async fn one(self, db: &DatabaseConnection) -> Result, DbErr> { self.into_model().one(db).await } @@ -228,6 +318,22 @@ impl Selector where S: SelectorTrait, { + /// Create `Selector` from Statment and columns. Executing this `Selector` + /// will return a type `T` which implement `TryGetableMany`. + pub fn with_columns(query: SelectStatement) -> Selector> + where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, + { + Selector { + query, + selector: SelectGetableValue { + columns: PhantomData, + model: PhantomData, + }, + } + } + pub async fn one(mut self, db: &DatabaseConnection) -> Result, DbErr> { let builder = db.get_database_backend(); self.query.limit(1); @@ -275,6 +381,22 @@ where } } + /// Create `SelectorRaw` from Statment and columns. Executing this `SelectorRaw` will + /// return a type `T` which implement `TryGetableMany`. + pub fn with_columns(stmt: Statement) -> SelectorRaw> + where + T: TryGetableMany, + C: sea_strum::IntoEnumIterator + ToString, + { + SelectorRaw { + stmt, + selector: SelectGetableValue { + columns: PhantomData, + model: PhantomData, + }, + } + } + /// ``` /// # #[cfg(feature = "mock")] /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};