diff --git a/examples/sqlx-mysql/Readme.md b/examples/sqlx-mysql/Readme.md index f7e5d7bc..77a90bd6 100644 --- a/examples/sqlx-mysql/Readme.md +++ b/examples/sqlx-mysql/Readme.md @@ -32,15 +32,27 @@ Model { id: 2, name: "Rasberry", cake_id: Some(1) } Model { id: 3, name: "Strawberry", cake_id: Some(2) } +Model { id: 4, name: "Apple", cake_id: None } + +Model { id: 5, name: "Banana", cake_id: None } + +Model { id: 6, name: "Cherry", cake_id: None } + +Model { id: 7, name: "Lemon", cake_id: None } + +Model { id: 8, name: "Orange", cake_id: None } + +Model { id: 9, name: "Pineapple", cake_id: None } + ===== ===== find cakes and fruits: 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` -(Model { id: 1, name: "New York Cheese" }, Model { id: 2, name: "Rasberry", cake_id: Some(1) }) +(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 1, name: "Blueberry", cake_id: Some(1) })) -(Model { id: 1, name: "New York Cheese" }, Model { id: 1, name: "Blueberry", cake_id: Some(1) }) +(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 2, name: "Rasberry", cake_id: Some(1) })) -(Model { id: 2, name: "Chocolate Forest" }, Model { id: 3, name: "Strawberry", cake_id: Some(2) }) +(Model { id: 2, name: "Chocolate Forest" }, Some(Model { id: 3, name: "Strawberry", cake_id: Some(2) })) ===== ===== @@ -48,7 +60,7 @@ find one by primary key: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `ca Model { id: 1, name: "New York Cheese" } -find one by like: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%chocolate%' LIMIT 1 +find one by name: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%chocolate%' LIMIT 1 Some(Model { id: 2, name: "Chocolate Forest" }) @@ -68,15 +80,11 @@ SelectResult { name: "Chocolate Forest", num_of_fruits: 1 } ===== ===== -find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` +find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` ORDER BY `cake`.`id` ASC -(Model { id: 1, name: "New York Cheese" }, Model { id: 1, name: "Vanilla" }) +(Model { id: 1, name: "New York Cheese" }, [Model { id: 1, name: "Vanilla" }, Model { id: 2, name: "Lemon" }]) -(Model { id: 1, name: "New York Cheese" }, Model { id: 2, name: "Lemon" }) - -(Model { id: 2, name: "Chocolate Forest" }, Model { id: 2, name: "Lemon" }) - -(Model { id: 2, name: "Chocolate Forest" }, Model { id: 3, name: "Mango" }) +(Model { id: 2, name: "Chocolate Forest" }, [Model { id: 2, name: "Lemon" }, Model { id: 3, name: "Mango" }]) find fillings for cheese cake: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 1 LIMIT 1 SELECT `filling`.`id`, `filling`.`name` FROM `filling` INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id` INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id` WHERE `cake`.`id` = 1 diff --git a/examples/sqlx-mysql/src/select.rs b/examples/sqlx-mysql/src/select.rs index 8224caf1..fc4f2d07 100644 --- a/examples/sqlx-mysql/src/select.rs +++ b/examples/sqlx-mysql/src/select.rs @@ -66,7 +66,10 @@ async fn find_all(db: &DbConn) -> Result<(), QueryErr> { async fn find_together(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); - let both = Cake::find().left_join_and_select_also(Fruit).all(db).await?; + let both = Cake::find() + .left_join_and_select_also(Fruit) + .all(db) + .await?; println!(); for bb in both.iter() { @@ -141,7 +144,10 @@ async fn count_fruits_by_cake(db: &DbConn) -> Result<(), QueryErr> { async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fillings: "); - let both = Cake::find().left_join_and_select_also(Filling).all(db).await?; + let both = Cake::find() + .left_join_and_select_with(Filling) + .all(db) + .await?; println!(); for bb in both.iter() { @@ -211,7 +217,7 @@ async fn find_together_json(db: &DbConn) -> Result<(), QueryErr> { print!("find cakes and fruits: "); let cakes_fruits = Cake::find() - .left_join_and_select_also(Fruit) + .left_join_and_select_with(Fruit) .into_json() .all(db) .await?; diff --git a/src/entity/model.rs b/src/entity/model.rs index acf21c0d..81821c68 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -15,7 +15,7 @@ pub trait FromQueryResult { where Self: Sized; - fn from_query_result_opt(res: &QueryResult, pre: &str) -> Result, TypeErr> + fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result, TypeErr> where Self: Sized, { diff --git a/src/executor/select.rs b/src/executor/select.rs index 9a65f4b1..6c74c8cb 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,6 +1,7 @@ use crate::{ query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, JsonValue, - ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo, TypeErr, + ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo, + SelectTwoMany, TypeErr, }; use sea_query::SelectStatement; use std::marker::PhantomData; @@ -57,7 +58,7 @@ where fn from_raw_query_result(res: QueryResult) -> Result { Ok(( M::from_query_result(&res, combine::SELECT_A)?, - N::from_query_result_opt(&res, combine::SELECT_B)?, + N::from_query_result_optional(&res, combine::SELECT_B)?, )) } } @@ -132,11 +133,54 @@ where self.into_model::().one(db).await } - pub async fn all(self, db: &DatabaseConnection) -> Result)>, QueryErr> { + pub async fn all( + self, + db: &DatabaseConnection, + ) -> Result)>, QueryErr> { self.into_model::().all(db).await } } +impl SelectTwoMany +where + E: EntityTrait, + F: EntityTrait, +{ + fn into_model(self) -> Selector> + where + M: FromQueryResult, + N: FromQueryResult, + { + Selector { + query: self.query, + selector: SelectTwoModel { model: PhantomData }, + } + } + + #[cfg(feature = "with-json")] + pub fn into_json(self) -> Selector> { + Selector { + query: self.query, + selector: SelectTwoModel { model: PhantomData }, + } + } + + pub async fn one( + self, + db: &DatabaseConnection, + ) -> Result)>, QueryErr> { + self.into_model::().one(db).await + } + + pub async fn all( + self, + db: &DatabaseConnection, + ) -> Result)>, QueryErr> { + let rows = self.into_model::().all(db).await?; + Ok(consolidate_query_result::(rows)) + } +} + impl Selector where S: SelectorTrait, @@ -172,11 +216,14 @@ where } } -fn parse_query_result(rows: Vec<(L::Model, Option)>) -> Vec<(L::Model, Vec)> +fn consolidate_query_result( + rows: Vec<(L::Model, Option)>, +) -> Vec<(L::Model, Vec)> where L: EntityTrait, + R: EntityTrait, { - let mut acc: Vec<(L::Model, Vec)> = Vec::new(); + let mut acc: Vec<(L::Model, Vec)> = Vec::new(); for (l, r) in rows { if let Some((last_l, last_r)) = acc.last_mut() { let mut same_l = true; @@ -203,4 +250,4 @@ where } } acc -} \ No newline at end of file +} diff --git a/src/query/combine.rs b/src/query/combine.rs index 50f000d4..f52b7925 100644 --- a/src/query/combine.rs +++ b/src/query/combine.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo}; +use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo, SelectTwoMany}; use core::marker::PhantomData; pub use sea_query::JoinType; use sea_query::{Alias, ColumnRef, Iden, Order, SeaRc, SelectExpr, SelectStatement, SimpleExpr}; @@ -40,9 +40,36 @@ where self = self.apply_alias(SELECT_A); SelectTwo::new(self.into_query()) } + + pub fn select_with(mut self, _: F) -> SelectTwoMany + where + F: EntityTrait, + { + self = self.apply_alias(SELECT_A); + SelectTwoMany::new(self.into_query()) + } } impl SelectTwo +where + E: EntityTrait, + F: EntityTrait, +{ + pub(crate) fn new(query: SelectStatement) -> Self { + Self { + query, + entity: PhantomData, + } + .prepare_select() + } + + fn prepare_select(mut self) -> Self { + prepare_select_two::(&mut self); + self + } +} + +impl SelectTwoMany where E: EntityTrait, F: EntityTrait, @@ -57,13 +84,7 @@ where } 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(SeaRc::new(Alias::new(&alias))), - }); - } + prepare_select_two::(&mut self); self } @@ -75,6 +96,20 @@ where } } +fn prepare_select_two(selector: &mut S) +where + F: EntityTrait, + S: QueryTrait, +{ + for col in ::iter() { + let alias = format!("{}{}", SELECT_B, col.to_string().as_str()); + selector.query().expr(SelectExpr { + expr: col.into_simple_expr(), + alias: Some(SeaRc::new(Alias::new(&alias))), + }); + } +} + #[cfg(test)] mod tests { use crate::tests_cfg::{cake, fruit}; @@ -101,6 +136,22 @@ mod tests { .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_with_1() { + assert_eq!( + cake::Entity::find() + .left_join(fruit::Entity) + .select_with(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`", @@ -120,6 +171,25 @@ mod tests { .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(" ") + ); + } + + #[test] + fn select_with_2() { + assert_eq!( + cake::Entity::find() + .left_join(fruit::Entity) + .select_with(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`", diff --git a/src/query/join.rs b/src/query/join.rs index 01947216..12d7ef58 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo}; +use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo, SelectTwoMany}; pub use sea_query::JoinType; impl Select @@ -48,6 +48,15 @@ where { self.left_join(r).select_also(r) } + + /// Left Join with a Related Entity and select the related Entity as a `Vec` + pub fn left_join_and_select_with(self, r: R) -> SelectTwoMany + where + R: EntityTrait, + E: Related, + { + self.left_join(r).select_with(r) + } } #[cfg(test)] diff --git a/src/query/select.rs b/src/query/select.rs index af483908..91db2907 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -23,6 +23,16 @@ where pub(crate) entity: PhantomData<(E, F)>, } +#[derive(Clone, Debug)] +pub struct SelectTwoMany +where + E: EntityTrait, + F: EntityTrait, +{ + pub(crate) query: SelectStatement, + pub(crate) entity: PhantomData<(E, F)>, +} + pub trait IntoSimpleExpr { fn into_simple_expr(self) -> SimpleExpr; } @@ -51,6 +61,18 @@ macro_rules! impl_trait { &mut self.query } } + + impl $trait for SelectTwoMany + where + E: EntityTrait, + F: EntityTrait, + { + type QueryStatement = SelectStatement; + + fn query(&mut self) -> &mut SelectStatement { + &mut self.query + } + } }; } @@ -118,19 +140,26 @@ where } } -impl QueryTrait for SelectTwo -where - E: EntityTrait, - F: EntityTrait, -{ - type QueryStatement = SelectStatement; - fn query(&mut self) -> &mut SelectStatement { - &mut self.query - } - fn as_query(&self) -> &SelectStatement { - &self.query - } - fn into_query(self) -> SelectStatement { - self.query - } +macro_rules! select_two { + ( $selector: ident ) => { + impl QueryTrait for $selector + where + E: EntityTrait, + F: EntityTrait, + { + type QueryStatement = SelectStatement; + fn query(&mut self) -> &mut SelectStatement { + &mut self.query + } + fn as_query(&self) -> &SelectStatement { + &self.query + } + fn into_query(self) -> SelectStatement { + self.query + } + } + }; } + +select_two!(SelectTwo); +select_two!(SelectTwoMany);