From 9107c9010a45ddc1f931b5df77489b788fefc277 Mon Sep 17 00:00:00 2001 From: Muhannad Alrusayni Date: Thu, 16 Sep 2021 11:47:31 +0300 Subject: [PATCH 1/2] add `into_model()` for `Statement` --- src/database/statement.rs | 71 ++++++++++++++++++++++++++++++++++++++- src/executor/select.rs | 14 ++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/database/statement.rs b/src/database/statement.rs index 12b074873..530325c46 100644 --- a/src/database/statement.rs +++ b/src/database/statement.rs @@ -1,4 +1,4 @@ -use crate::DbBackend; +use crate::{DbBackend, FromQueryResult, SelectModel, SelectorRaw}; use sea_query::{inject_parameters, MysqlQueryBuilder, PostgresQueryBuilder, SqliteQueryBuilder}; pub use sea_query::{Value, Values}; use std::fmt; @@ -43,6 +43,75 @@ impl Statement { db_backend, } } + + /// ``` + /// # #[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, FromQueryResult}; + /// + /// #[derive(Debug, PartialEq, FromQueryResult)] + /// struct SelectResult { + /// name: String, + /// num_of_cakes: i32, + /// } + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let res: Vec = Statement::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, + /// vec![], + /// ) + /// .into_model::() + /// .all(&db) + /// .await?; + /// + /// assert_eq!( + /// res, + /// vec![ + /// SelectResult { + /// name: "Chocolate Forest".to_owned(), + /// num_of_cakes: 1, + /// }, + /// SelectResult { + /// name: "New York Cheese".to_owned(), + /// num_of_cakes: 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![] + /// ),] + /// ); + /// ``` + pub fn into_model(self) -> SelectorRaw> + where + M: FromQueryResult, + { + SelectorRaw::>::from_statement(self) + } } impl fmt::Display for Statement { diff --git a/src/executor/select.rs b/src/executor/select.rs index ef30bb3d1..961ec37b5 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -263,6 +263,20 @@ impl SelectorRaw where S: SelectorTrait, { + // Create `SelectorRaw` from Statment. Executing this `SelectorRaw` will + // return a type `M` which implement `FromQueryResult`. + // + // Helper function used by `Statment.into_model()` + pub(crate) fn from_statement(stmt: Statement) -> SelectorRaw> + where + M: FromQueryResult, + { + SelectorRaw { + stmt, + selector: SelectModel { model: PhantomData }, + } + } + /// ``` /// # #[cfg(feature = "mock")] /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; From e71a74b0c1cf4bc391733d35a1e8c2ac12269a04 Mon Sep 17 00:00:00 2001 From: Muhannad Alrusayni Date: Thu, 16 Sep 2021 18:06:02 +0300 Subject: [PATCH 2/2] Improve working with raw SQL - Add `find_by_statement` to `T: FromQueryResult` - Add `SelectorRaw::from_statement` constructor --- src/database/statement.rs | 71 +------------------------------------ src/entity/model.rs | 74 ++++++++++++++++++++++++++++++++++++++- src/entity/prelude.rs | 5 +-- src/executor/select.rs | 8 ++--- 4 files changed, 80 insertions(+), 78 deletions(-) diff --git a/src/database/statement.rs b/src/database/statement.rs index 530325c46..12b074873 100644 --- a/src/database/statement.rs +++ b/src/database/statement.rs @@ -1,4 +1,4 @@ -use crate::{DbBackend, FromQueryResult, SelectModel, SelectorRaw}; +use crate::DbBackend; use sea_query::{inject_parameters, MysqlQueryBuilder, PostgresQueryBuilder, SqliteQueryBuilder}; pub use sea_query::{Value, Values}; use std::fmt; @@ -43,75 +43,6 @@ impl Statement { db_backend, } } - - /// ``` - /// # #[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, FromQueryResult}; - /// - /// #[derive(Debug, PartialEq, FromQueryResult)] - /// struct SelectResult { - /// name: String, - /// num_of_cakes: i32, - /// } - /// - /// # let _: Result<(), DbErr> = smol::block_on(async { - /// # - /// let res: Vec = Statement::from_sql_and_values( - /// DbBackend::Postgres, - /// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#, - /// vec![], - /// ) - /// .into_model::() - /// .all(&db) - /// .await?; - /// - /// assert_eq!( - /// res, - /// vec![ - /// SelectResult { - /// name: "Chocolate Forest".to_owned(), - /// num_of_cakes: 1, - /// }, - /// SelectResult { - /// name: "New York Cheese".to_owned(), - /// num_of_cakes: 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![] - /// ),] - /// ); - /// ``` - pub fn into_model(self) -> SelectorRaw> - where - M: FromQueryResult, - { - SelectorRaw::>::from_statement(self) - } } impl fmt::Display for Statement { diff --git a/src/entity/model.rs b/src/entity/model.rs index 25a437539..25b46719c 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -1,4 +1,7 @@ -use crate::{DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Select}; +use crate::{ + DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Select, SelectModel, + SelectorRaw, Statement, +}; pub use sea_query::Value; use std::fmt::Debug; @@ -37,3 +40,72 @@ pub trait FromQueryResult { Ok(Self::from_query_result(res, pre).ok()) } } + +pub trait FromQueryResultExt: FromQueryResult + Sized { + /// ``` + /// # #[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, FromQueryResult}; + /// + /// #[derive(Debug, PartialEq, FromQueryResult)] + /// struct SelectResult { + /// name: String, + /// num_of_cakes: i32, + /// } + /// + /// # let _: Result<(), DbErr> = smol::block_on(async { + /// # + /// let res: Vec = SelectResult::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![ + /// SelectResult { + /// name: "Chocolate Forest".to_owned(), + /// num_of_cakes: 1, + /// }, + /// SelectResult { + /// name: "New York Cheese".to_owned(), + /// num_of_cakes: 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> { + SelectorRaw::>::from_statement(stmt) + } +} + +impl FromQueryResultExt for T {} diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 8d87a4b20..22cfeeb05 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -2,8 +2,9 @@ pub use crate::{ error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, EntityName, EntityTrait, - EnumIter, ForeignKeyAction, Iden, IdenStatic, Linked, ModelTrait, PrimaryKeyToColumn, - PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, RelationTrait, Select, Value, + EnumIter, ForeignKeyAction, FromQueryResultExt, Iden, IdenStatic, Linked, ModelTrait, + PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, + RelationTrait, Select, Value, }; #[cfg(feature = "with-json")] diff --git a/src/executor/select.rs b/src/executor/select.rs index 961ec37b5..959091c01 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -263,11 +263,9 @@ impl SelectorRaw where S: SelectorTrait, { - // Create `SelectorRaw` from Statment. Executing this `SelectorRaw` will - // return a type `M` which implement `FromQueryResult`. - // - // Helper function used by `Statment.into_model()` - pub(crate) fn from_statement(stmt: Statement) -> SelectorRaw> + /// Create `SelectorRaw` from Statment. Executing this `SelectorRaw` will + /// return a type `M` which implement `FromQueryResult`. + pub fn from_statement(stmt: Statement) -> SelectorRaw> where M: FromQueryResult, {