From abc11753a8f2b2ec6eefa75e3a4828d8484627ca Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sat, 15 May 2021 15:00:07 +0800 Subject: [PATCH] Select Both --- examples/sqlx-mysql/src/example_cake.rs | 2 +- examples/sqlx-mysql/src/example_fruit.rs | 2 +- examples/sqlx-mysql/src/main.rs | 40 ++++++-- src/connector/select.rs | 64 +++++++++++- src/entity/model.rs | 6 +- src/query/combine.rs | 123 +++++++++++++++++++++++ src/query/join.rs | 23 ++--- src/query/mod.rs | 2 + src/query/select.rs | 72 ++++++------- src/tests_cfg/cake.rs | 2 +- src/tests_cfg/fruit.rs | 2 +- 11 files changed, 262 insertions(+), 76 deletions(-) create mode 100644 src/query/combine.rs diff --git a/examples/sqlx-mysql/src/example_cake.rs b/examples/sqlx-mysql/src/example_cake.rs index 7f507724..ad893a56 100644 --- a/examples/sqlx-mysql/src/example_cake.rs +++ b/examples/sqlx-mysql/src/example_cake.rs @@ -104,7 +104,7 @@ impl ModelTrait for Model { } } - fn from_query_result(row: QueryResult, pre: &str) -> Result { + fn from_query_result(row: &QueryResult, pre: &str) -> Result { Ok(Self { id: row.try_get(pre, Column::Id.as_str())?, name: row.try_get(pre, Column::Name.as_str())?, diff --git a/examples/sqlx-mysql/src/example_fruit.rs b/examples/sqlx-mysql/src/example_fruit.rs index a0774833..077616e3 100644 --- a/examples/sqlx-mysql/src/example_fruit.rs +++ b/examples/sqlx-mysql/src/example_fruit.rs @@ -90,7 +90,7 @@ impl ModelTrait for Model { } } - fn from_query_result(row: QueryResult, pre: &str) -> Result { + fn from_query_result(row: &QueryResult, pre: &str) -> Result { Ok(Self { id: row.try_get(pre, Column::Id.as_str())?, name: row.try_get(pre, Column::Name.as_str())?, diff --git a/examples/sqlx-mysql/src/main.rs b/examples/sqlx-mysql/src/main.rs index a9682663..a98f538f 100644 --- a/examples/sqlx-mysql/src/main.rs +++ b/examples/sqlx-mysql/src/main.rs @@ -14,13 +14,22 @@ async fn main() { .await .unwrap(); - println!("{:?}", db); - println!(); + println!("{:?}\n", db); + + println!("===== =====\n"); find_all(&db).await.unwrap(); + println!("===== =====\n"); + + find_together(&db).await.unwrap(); + + println!("===== =====\n"); + find_one(&db).await.unwrap(); + println!("===== =====\n"); + count_fruits_by_cake(&db).await.unwrap(); } @@ -31,8 +40,7 @@ async fn find_all(db: &Database) -> Result<(), QueryErr> { println!(); for cc in cakes.iter() { - println!("{:?}", cc); - println!(); + println!("{:?}\n", cc); } print!("find all fruits: "); @@ -41,8 +49,20 @@ async fn find_all(db: &Database) -> Result<(), QueryErr> { println!(); for ff in fruits.iter() { - println!("{:?}", ff); - println!(); + println!("{:?}\n", ff); + } + + Ok(()) +} + +async fn find_together(db: &Database) -> Result<(), QueryErr> { + print!("find cakes and fruits: "); + + let both = cake::Entity::find().left_join_and_select(fruit::Entity).all(db).await?; + + println!(); + for bb in both.iter() { + println!("{:?}\n", bb); } Ok(()) @@ -74,8 +94,7 @@ async fn find_one(db: &Database) -> Result<(), QueryErr> { println!(); for ff in fruits.iter() { - println!("{:?}", ff); - println!(); + println!("{:?}\n", ff); } Ok(()) @@ -93,7 +112,7 @@ async fn count_fruits_by_cake(db: &Database) -> Result<(), QueryErr> { use sea_orm::{FromQueryResult, QueryResult, TypeErr}; impl FromQueryResult for SelectResult { - fn from_query_result(row: QueryResult, pre: &str) -> Result { + fn from_query_result(row: &QueryResult, pre: &str) -> Result { Ok(Self { name: row.try_get(pre, "name")?, num_of_fruits: row.try_get(pre, "num_of_fruits")?, @@ -115,8 +134,7 @@ async fn count_fruits_by_cake(db: &Database) -> Result<(), QueryErr> { println!(); for rr in results.iter() { - println!("{:?}", rr); - println!(); + println!("{:?}\n", rr); } Ok(()) diff --git a/src/connector/select.rs b/src/connector/select.rs index 7f2745bb..ecb7285c 100644 --- a/src/connector/select.rs +++ b/src/connector/select.rs @@ -1,4 +1,5 @@ -use crate::{Connection, Database, EntityTrait, FromQueryResult, QueryErr, Select, Statement}; +use crate::query::combine; +use crate::{Connection, Database, EntityTrait, FromQueryResult, QueryErr, Select, SelectTwo, Statement}; use sea_query::{QueryBuilder, SelectStatement}; use std::marker::PhantomData; @@ -21,7 +22,7 @@ where model: PhantomData<(M, N)>, } -impl Select +impl Select where E: EntityTrait, { @@ -44,6 +45,31 @@ where } } +impl SelectTwo +where + E: EntityTrait, + F: EntityTrait, +{ + fn into_model(self) -> SelectTwoModel + where + M: FromQueryResult, + N: FromQueryResult, + { + SelectTwoModel { + query: self.query, + model: PhantomData, + } + } + + pub async fn one(self, db: &Database) -> Result<(E::Model, F::Model), QueryErr> { + self.into_model::().one(db).await + } + + pub async fn all(self, db: &Database) -> Result, QueryErr> { + self.into_model::().all(db).await + } +} + impl SelectModel where M: FromQueryResult, @@ -59,7 +85,7 @@ where let builder = db.get_query_builder_backend(); self.query.limit(1); let row = db.get_connection().query_one(self.build(builder)).await?; - Ok(M::from_query_result(row, "")?) + Ok(M::from_query_result(&row, "")?) } pub async fn all(self, db: &Database) -> Result, QueryErr> { @@ -67,7 +93,37 @@ where let rows = db.get_connection().query_all(self.build(builder)).await?; let mut models = Vec::new(); for row in rows.into_iter() { - models.push(M::from_query_result(row, "")?); + models.push(M::from_query_result(&row, "")?); + } + Ok(models) + } +} + +impl SelectTwoModel +where + M: FromQueryResult, + N: FromQueryResult, +{ + pub fn build(&self, builder: B) -> Statement + where + B: QueryBuilder, + { + self.query.build(builder).into() + } + + pub async fn one(mut self, db: &Database) -> Result<(M, N), QueryErr> { + let builder = db.get_query_builder_backend(); + self.query.limit(1); + let row = db.get_connection().query_one(self.build(builder)).await?; + Ok((M::from_query_result(&row, combine::SELECT_A)?, N::from_query_result(&row, combine::SELECT_B)?)) + } + + pub async fn all(self, db: &Database) -> Result, QueryErr> { + let builder = db.get_query_builder_backend(); + let rows = db.get_connection().query_all(self.build(builder)).await?; + let mut models = Vec::new(); + for row in rows.into_iter() { + models.push((M::from_query_result(&row, combine::SELECT_A)?, N::from_query_result(&row, combine::SELECT_B)?)); } Ok(models) } diff --git a/src/entity/model.rs b/src/entity/model.rs index 0bc68c31..7874c377 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -9,13 +9,13 @@ pub trait ModelTrait: Clone + Debug + Default { fn set(&mut self, c: Self::Column, v: Value); - fn from_query_result(row: QueryResult, pre: &str) -> Result + fn from_query_result(row: &QueryResult, pre: &str) -> Result where Self: Sized; } pub trait FromQueryResult { - fn from_query_result(row: QueryResult, pre: &str) -> Result + fn from_query_result(row: &QueryResult, pre: &str) -> Result where Self: Sized; } @@ -24,7 +24,7 @@ impl FromQueryResult for M where M: ModelTrait + Sized, { - fn from_query_result(row: QueryResult, pre: &str) -> Result { + fn from_query_result(row: &QueryResult, pre: &str) -> Result { ::from_query_result(row, pre) } } diff --git a/src/query/combine.rs b/src/query/combine.rs new file mode 100644 index 00000000..d94572e3 --- /dev/null +++ b/src/query/combine.rs @@ -0,0 +1,123 @@ +use crate::{EntityTrait, IntoSimpleExpr, Iterable, Select, SelectTwo}; +use core::marker::PhantomData; +pub use sea_query::JoinType; +use sea_query::{Alias, ColumnRef, Iden, SelectExpr, SelectStatement, SimpleExpr}; +use std::rc::Rc; + +pub const SELECT_A: &str = "A_"; +pub const SELECT_B: &str = "B_"; + +impl Select +where + E: EntityTrait, +{ + fn apply_alias(mut self, pre: &str) -> Self { + self.query().exprs_mut_for_each(|sel| { + match &sel.alias { + Some(alias) => { + let alias = format!("{}{}", pre, alias.to_string().as_str()); + sel.alias = Some(Rc::new(Alias::new(&alias))); + } + None => { + let col = match &sel.expr { + SimpleExpr::Column(col_ref) => match &col_ref { + ColumnRef::Column(col) => col, + ColumnRef::TableColumn(_, col) => col, + }, + _ => panic!("cannot apply alias for expr other than Column"), + }; + let alias = format!("{}{}", pre, col.to_string().as_str()); + sel.alias = Some(Rc::new(Alias::new(&alias))); + } + }; + }); + self + } + + pub fn select_also(mut self, _: F) -> SelectTwo + where + F: EntityTrait, + { + self = self.apply_alias(SELECT_A); + SelectTwo::new(self.into_query()) + } +} + +impl SelectTwo +where + E: EntityTrait, + F: EntityTrait, +{ + pub(crate) fn new(query: SelectStatement) -> Self { + let myself = Self { + query, + entity: PhantomData, + }; + myself.prepare_select() + } + + fn prepare_select(mut self) -> Self { + for col in ::iter() { + let alias = format!("{}{}", SELECT_B, col.to_string().as_str()); + self.query.expr(SelectExpr { + expr: col.into_simple_expr(), + alias: Some(Rc::new(Alias::new(&alias))), + }); + } + self + } +} + +#[cfg(test)] +mod tests { + use crate::tests_cfg::{cake, fruit}; + use crate::{EntityTrait, ColumnTrait, QueryHelper}; + use sea_query::MysqlQueryBuilder; + + #[test] + fn alias_1() { + assert_eq!( + cake::Entity::find() + .column_as(cake::Column::Id, "B") + .apply_alias("A_") + .build(MysqlQueryBuilder) + .to_string(), + "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `cake`.`id` AS `A_B` FROM `cake`", + ); + } + + #[test] + fn select_also_1() { + assert_eq!( + cake::Entity::find() + .left_join(fruit::Entity) + .select_also(fruit::Entity) + .build(MysqlQueryBuilder) + .to_string(), + [ + "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", + "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", + "FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`", + ].join(" ") + ); + } + + #[test] + fn select_also_2() { + assert_eq!( + cake::Entity::find() + .left_join(fruit::Entity) + .select_also(fruit::Entity) + .filter(cake::Column::Id.eq(1)) + .filter(fruit::Column::Id.eq(2)) + .build(MysqlQueryBuilder) + .to_string(), + [ + "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", + "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", + "FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`", + "WHERE `cake`.`id` = 1 AND `fruit`.`id` = 2", + ].join(" ") + ); + } +} diff --git a/src/query/join.rs b/src/query/join.rs index 33cefb24..626ddc28 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -1,6 +1,6 @@ use crate::{ ColumnTrait, EntityTrait, Identity, Iterable, ModelTrait, PrimaryKeyOfModel, QueryHelper, - Related, RelationDef, Select, + Related, RelationDef, Select, SelectTwo }; pub use sea_query::JoinType; @@ -91,6 +91,15 @@ where { self.join_rev(JoinType::InnerJoin, R::to()) } + + /// Left Join with a Related Entity and select both Entity. + pub fn left_join_and_select(self, r: R) -> SelectTwo + where + R: EntityTrait, + E: Related, + { + self.left_join(r).select_also(r) + } } #[cfg(test)] @@ -182,16 +191,4 @@ mod tests { .join(" ") ); } - - #[test] - fn alias_1() { - assert_eq!( - cake::Entity::find() - .column_as(cake::Column::Id, "B") - .apply_alias("A_") - .build(MysqlQueryBuilder) - .to_string(), - "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `cake`.`id` AS `A_B` FROM `cake`", - ); - } } diff --git a/src/query/mod.rs b/src/query/mod.rs index f48b7dca..27bebe51 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -1,8 +1,10 @@ +pub(crate) mod combine; mod helper; mod join; mod result; mod select; +pub use combine::*; pub use helper::*; pub use join::*; pub use result::*; diff --git a/src/query/select.rs b/src/query/select.rs index 1a8e1747..f140ad7c 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -2,9 +2,7 @@ use crate::{ColumnTrait, EntityTrait, Iterable, QueryHelper, Statement}; use core::fmt::Debug; use core::marker::PhantomData; pub use sea_query::JoinType; -use sea_query::{ - Alias, ColumnRef, Iden, IntoColumnRef, IntoIden, QueryBuilder, SelectStatement, SimpleExpr, -}; +use sea_query::{Iden, IntoColumnRef, IntoIden, QueryBuilder, SelectStatement, SimpleExpr}; use std::rc::Rc; #[derive(Clone, Debug)] @@ -91,30 +89,12 @@ where self.query.from(E::default().into_iden()); self } +} - pub(crate) fn apply_alias(mut self, pre: &str) -> Self { - self.query().exprs_mut_for_each(|sel| { - match &sel.alias { - Some(alias) => { - let alias = format!("{}{}", pre, alias.to_string().as_str()); - sel.alias = Some(Rc::new(Alias::new(&alias))); - } - None => { - let col = match &sel.expr { - SimpleExpr::Column(col_ref) => match &col_ref { - ColumnRef::Column(col) => col, - ColumnRef::TableColumn(_, col) => col, - }, - _ => panic!("cannot apply alias for expr other than Column"), - }; - let alias = format!("{}{}", pre, col.to_string().as_str()); - sel.alias = Some(Rc::new(Alias::new(&alias))); - } - }; - }); - self - } - +impl Select +where + E: EntityTrait, +{ /// Get a mutable ref to the query builder pub fn query(&mut self) -> &mut SelectStatement { &mut self.query @@ -139,21 +119,31 @@ where } } -#[cfg(test)] -mod tests { - use crate::tests_cfg::cake; - use crate::{EntityTrait, QueryHelper}; - use sea_query::MysqlQueryBuilder; +impl SelectTwo +where + E: EntityTrait, + F: EntityTrait, +{ + /// Get a mutable ref to the query builder + pub fn query(&mut self) -> &mut SelectStatement { + &mut self.query + } - #[test] - fn alias_1() { - assert_eq!( - cake::Entity::find() - .column_as(cake::Column::Id, "B") - .apply_alias("A_") - .build(MysqlQueryBuilder) - .to_string(), - "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `cake`.`id` AS `A_B` FROM `cake`", - ); + /// Get an immutable ref to the query builder + pub fn as_query(&self) -> &SelectStatement { + &self.query + } + + /// Take ownership of the query builder + pub fn into_query(self) -> SelectStatement { + self.query + } + + /// Build the query as [`Statement`] + pub fn build(&self, builder: B) -> Statement + where + B: QueryBuilder, + { + self.as_query().build(builder).into() } } diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index 6c5eb6f2..74c127fd 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -104,7 +104,7 @@ impl ModelTrait for Model { } } - fn from_query_result(row: QueryResult, pre: &str) -> Result { + fn from_query_result(row: &QueryResult, pre: &str) -> Result { Ok(Self { id: row.try_get(pre, Column::Id.as_str())?, name: row.try_get(pre, Column::Name.as_str())?, diff --git a/src/tests_cfg/fruit.rs b/src/tests_cfg/fruit.rs index ce068129..e32e05b0 100644 --- a/src/tests_cfg/fruit.rs +++ b/src/tests_cfg/fruit.rs @@ -90,7 +90,7 @@ impl ModelTrait for Model { } } - fn from_query_result(row: QueryResult, pre: &str) -> Result { + fn from_query_result(row: &QueryResult, pre: &str) -> Result { Ok(Self { id: row.try_get(pre, Column::Id.as_str())?, name: row.try_get(pre, Column::Name.as_str())?,