From 31941d3af678eb46d510c31c941fa056d5d01015 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 23 Aug 2021 23:11:08 +0800 Subject: [PATCH 1/7] WIP: Linked (#89) --- src/entity/column.rs | 2 +- src/entity/model.rs | 9 ++++++- src/entity/prelude.rs | 6 ++--- src/entity/relation.rs | 16 ++++++++++++ src/query/join.rs | 55 +++++++++++++++++++++++++++++++++++++++++- src/tests_cfg/cake.rs | 15 ++++++++++++ 6 files changed, 97 insertions(+), 6 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 16546057..611950f5 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; use crate::{EntityName, IdenStatic, Iterable}; use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; +use std::str::FromStr; #[derive(Debug, Clone)] pub struct ColumnDef { diff --git a/src/entity/model.rs b/src/entity/model.rs index 15ebdb58..2672d8cc 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -1,4 +1,4 @@ -use crate::{DbErr, EntityTrait, QueryFilter, QueryResult, Related, Select}; +use crate::{DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Select}; pub use sea_query::Value; use std::fmt::Debug; @@ -16,6 +16,13 @@ pub trait ModelTrait: Clone + Debug { { >::find_related().belongs_to(self) } + + fn find_linked(&self, _: L) -> Select + where + L: Linked, + { + L::find_linked() + } } pub trait FromQueryResult { diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 447117b7..cda217d9 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -1,9 +1,9 @@ pub use crate::{ error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, Iden, IdenStatic, ModelTrait, - PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, - RelationTrait, Select, Value, + DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, Iden, IdenStatic, Linked, + ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, + RelationDef, RelationTrait, Select, Value, }; #[cfg(feature = "with-json")] diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 955660e2..f75b3a89 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -28,6 +28,22 @@ where } } +pub trait Linked { + type FromEntity: EntityTrait; + + type ToEntity: EntityTrait; + + fn link() -> Vec; + + fn find_linked() -> Select { + let mut select = Select::new(); + for rel in Self::link() { + select = select.join(JoinType::InnerJoin, rel); + } + select + } +} + pub struct RelationDef { pub rel_type: RelationType, pub from_tbl: TableRef, diff --git a/src/query/join.rs b/src/query/join.rs index 72726d14..12999db8 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo, SelectTwoMany}; +use crate::{EntityTrait, Linked, QuerySelect, Related, Select, SelectTwo, SelectTwoMany}; pub use sea_query::JoinType; impl Select @@ -57,6 +57,19 @@ where { self.left_join(r).select_with(r) } + + /// Left Join with a Linked Entity and select both Entity. + pub fn find_also_linked(self, _: L) -> SelectTwo + where + L: Linked, + T: EntityTrait, + { + let mut slf = self; + for rel in L::link() { + slf = slf.join(JoinType::LeftJoin, rel); + } + slf.select_also(T::default()) + } } #[cfg(test)] @@ -220,4 +233,44 @@ mod tests { .join(" ") ); } + + #[test] + fn join_10() { + let cake_model = cake::Model { + id: 12, + name: "".to_owned(), + }; + + assert_eq!( + cake_model + .find_linked(cake::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + r#"SELECT `filling`.`id`, `filling`.`name`"#, + r#"FROM `filling`"#, + r#"INNER JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#, + r#"INNER JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + ] + .join(" ") + ); + } + + #[test] + fn join_11() { + assert_eq!( + cake::Entity::find() + .find_also_linked(cake::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#, + r#"`filling`.`id` AS `B_id`, `filling`.`name` AS `B_name`"#, + r#"FROM `cake`"#, + r#"LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#, + r#"LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + ] + .join(" ") + ); + } } diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index f8a35d6c..56ca8797 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -73,4 +73,19 @@ impl Related for Entity { } } +pub struct CakeToFilling; + +impl Linked for CakeToFilling { + type FromEntity = Entity; + + type ToEntity = super::filling::Entity; + + fn link() -> Vec { + vec![ + super::cake_filling::Relation::Cake.def().rev(), + super::cake_filling::Relation::Filling.def(), + ] + } +} + impl ActiveModelBehavior for ActiveModel {} From 447947e3556c36ec441e844fd8e0db7bfa29d311 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 26 Aug 2021 23:28:27 +0800 Subject: [PATCH 2/7] Fix find_linked join direction --- src/entity/relation.rs | 4 ++-- src/query/join.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/entity/relation.rs b/src/entity/relation.rs index f75b3a89..441a0524 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -37,8 +37,8 @@ pub trait Linked { fn find_linked() -> Select { let mut select = Select::new(); - for rel in Self::link() { - select = select.join(JoinType::InnerJoin, rel); + for rel in Self::link().into_iter().rev() { + select = select.join_rev(JoinType::InnerJoin, rel); } select } diff --git a/src/query/join.rs b/src/query/join.rs index 12999db8..be759e97 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -249,8 +249,8 @@ mod tests { [ r#"SELECT `filling`.`id`, `filling`.`name`"#, r#"FROM `filling`"#, - r#"INNER JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#, - r#"INNER JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + r#"INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, + r#"INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id`"#, ] .join(" ") ); From bfaa7d4539afc033cb3f240fa4fed4ff1344c7c0 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 26 Aug 2021 23:28:44 +0800 Subject: [PATCH 3/7] Test Linked --- tests/common/bakery_chain/baker.rs | 18 +++ tests/relational_tests.rs | 202 ++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 1 deletion(-) diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs index 0c63e721..287e5deb 100644 --- a/tests/common/bakery_chain/baker.rs +++ b/tests/common/bakery_chain/baker.rs @@ -81,4 +81,22 @@ impl Related for Entity { } } +pub struct BakedForCustomer; + +impl Linked for BakedForCustomer { + type FromEntity = Entity; + + type ToEntity = super::customer::Entity; + + fn link() -> Vec { + vec![ + super::cakes_bakers::Relation::Baker.def().rev(), + super::cakes_bakers::Relation::Cake.def(), + super::lineitem::Relation::Cake.def().rev(), + super::lineitem::Relation::Order.def(), + super::order::Relation::Customer.def(), + ] + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index ae1236a2..1ddee902 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -1,7 +1,8 @@ use chrono::offset::Utc; use rust_decimal::prelude::*; use rust_decimal_macros::dec; -use sea_orm::{entity::*, query::*, FromQueryResult}; +use sea_orm::{entity::*, query::*, DbErr, FromQueryResult}; +use uuid::Uuid; pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; @@ -474,3 +475,202 @@ pub async fn having() { ctx.delete().await; } + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +pub async fn linked() -> Result<(), DbErr> { + use common::bakery_chain::Order; + + let ctx = TestContext::new("test_linked").await; + + // SeaSide Bakery + let seaside_bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + }; + let seaside_bakery_res: InsertResult = Bakery::insert(seaside_bakery).exec(&ctx.db).await?; + + // Bob's Baker, Cake & Cake Baker + let baker_bob = baker::ActiveModel { + name: Set("Baker Bob".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+61424000000", + "home": "0395555555", + "address": "12 Test St, Testville, Vic, Australia" + })), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let baker_bob_res: InsertResult = Baker::insert(baker_bob).exec(&ctx.db).await?; + let mud_cake = cake::ActiveModel { + name: Set("Mud Cake".to_owned()), + price: Set(dec!(10.25)), + gluten_free: Set(false), + serial: Set(Uuid::new_v4()), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let mud_cake_res: InsertResult = Cake::insert(mud_cake).exec(&ctx.db).await?; + let bob_cakes_bakers = cakes_bakers::ActiveModel { + cake_id: Set(mud_cake_res.last_insert_id as i32), + baker_id: Set(baker_bob_res.last_insert_id as i32), + ..Default::default() + }; + CakesBakers::insert(bob_cakes_bakers).exec(&ctx.db).await?; + + // Bobby's Baker, Cake & Cake Baker + let baker_bobby = baker::ActiveModel { + name: Set("Baker Bobby".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+85212345678", + })), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let baker_bobby_res: InsertResult = Baker::insert(baker_bobby).exec(&ctx.db).await?; + let cheese_cake = cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + price: Set(dec!(20.5)), + gluten_free: Set(false), + serial: Set(Uuid::new_v4()), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let cheese_cake_res: InsertResult = Cake::insert(cheese_cake).exec(&ctx.db).await?; + let bobby_cakes_bakers = cakes_bakers::ActiveModel { + cake_id: Set(cheese_cake_res.last_insert_id as i32), + baker_id: Set(baker_bobby_res.last_insert_id as i32), + ..Default::default() + }; + CakesBakers::insert(bobby_cakes_bakers) + .exec(&ctx.db) + .await?; + let chocolate_cake = cake::ActiveModel { + name: Set("Chocolate Cake".to_owned()), + price: Set(dec!(30.15)), + gluten_free: Set(false), + serial: Set(Uuid::new_v4()), + bakery_id: Set(Some(seaside_bakery_res.last_insert_id as i32)), + ..Default::default() + }; + let chocolate_cake_res: InsertResult = Cake::insert(chocolate_cake).exec(&ctx.db).await?; + let bobby_cakes_bakers = cakes_bakers::ActiveModel { + cake_id: Set(chocolate_cake_res.last_insert_id as i32), + baker_id: Set(baker_bobby_res.last_insert_id as i32), + ..Default::default() + }; + CakesBakers::insert(bobby_cakes_bakers) + .exec(&ctx.db) + .await?; + + // Kate's Customer, Order & Line Item + let customer_kate = customer::ActiveModel { + name: Set("Kate".to_owned()), + notes: Set(Some("Loves cheese cake".to_owned())), + ..Default::default() + }; + let customer_kate_res: InsertResult = Customer::insert(customer_kate).exec(&ctx.db).await?; + let kate_order_1 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kate_res.last_insert_id as i32), + total: Set(dec!(15.10)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kate_order_1_res: InsertResult = Order::insert(kate_order_1).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(cheese_cake_res.last_insert_id as i32), + order_id: Set(kate_order_1_res.last_insert_id as i32), + price: Set(dec!(7.55)), + quantity: Set(2), + ..Default::default() + } + .save(&ctx.db) + .await?; + let kate_order_2 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kate_res.last_insert_id as i32), + total: Set(dec!(29.7)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kate_order_2_res: InsertResult = Order::insert(kate_order_2).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(chocolate_cake_res.last_insert_id as i32), + order_id: Set(kate_order_2_res.last_insert_id as i32), + price: Set(dec!(9.9)), + quantity: Set(3), + ..Default::default() + } + .save(&ctx.db) + .await?; + + // Kara's Customer, Order & Line Item + let customer_kara = customer::ActiveModel { + name: Set("Kara".to_owned()), + notes: Set(Some("Loves all cakes".to_owned())), + ..Default::default() + }; + let customer_kara_res: InsertResult = Customer::insert(customer_kara).exec(&ctx.db).await?; + let kara_order_1 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kara_res.last_insert_id as i32), + total: Set(dec!(15.10)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kara_order_1_res: InsertResult = Order::insert(kara_order_1).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(mud_cake_res.last_insert_id as i32), + order_id: Set(kara_order_1_res.last_insert_id as i32), + price: Set(dec!(7.55)), + quantity: Set(2), + ..Default::default() + } + .save(&ctx.db) + .await?; + let kara_order_2 = order::ActiveModel { + bakery_id: Set(seaside_bakery_res.last_insert_id as i32), + customer_id: Set(customer_kara_res.last_insert_id as i32), + total: Set(dec!(29.7)), + placed_at: Set(Utc::now().naive_utc()), + ..Default::default() + }; + let kara_order_2_res: InsertResult = Order::insert(kara_order_2).exec(&ctx.db).await?; + lineitem::ActiveModel { + cake_id: Set(cheese_cake_res.last_insert_id as i32), + order_id: Set(kara_order_2_res.last_insert_id as i32), + price: Set(dec!(9.9)), + quantity: Set(3), + ..Default::default() + } + .save(&ctx.db) + .await?; + + /* + SELECT `baker`.`id` AS `A_id`, `baker`.`name` AS `A_name`, + `baker`.`contact_details` AS `A_contact_details`, + `baker`.`bakery_id` AS `A_bakery_id`, `customer`.`id` AS `B_id`, + `customer`.`name` AS `B_name`, `customer`.`notes` AS `B_notes` + FROM `baker` + LEFT JOIN `cakes_bakers` ON `baker`.`id` = `cakes_bakers`.`baker_id` + LEFT JOIN `cake` ON `cakes_bakers`.`cake_id` = `cake`.`id` + LEFT JOIN `lineitem` ON `cake`.`id` = `lineitem`.`cake_id` + LEFT JOIN `order` ON `lineitem`.`order_id` = `order`.`id` + LEFT JOIN `customer` ON `order`.`customer_id` = `customer`.`id` + */ + let baked_for_customers = Baker::find() + .find_also_linked(baker::BakedForCustomer) + .all(&ctx.db) + .await?; + println!("{:#?}", baked_for_customers); + + ctx.delete().await; + + Ok(()) +} From 9db0748c64844ac6d63b63aae691bd610125716d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Sat, 28 Aug 2021 20:20:34 +0800 Subject: [PATCH 4/7] Non-static link --- src/entity/model.rs | 4 ++-- src/entity/relation.rs | 6 +++--- src/query/join.rs | 4 ++-- src/tests_cfg/cake.rs | 2 +- tests/common/bakery_chain/baker.rs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/entity/model.rs b/src/entity/model.rs index 2672d8cc..4774e1dc 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -17,11 +17,11 @@ pub trait ModelTrait: Clone + Debug { >::find_related().belongs_to(self) } - fn find_linked(&self, _: L) -> Select + fn find_linked(&self, l: L) -> Select where L: Linked, { - L::find_linked() + l.find_linked() } } diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 441a0524..29378f2a 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -33,11 +33,11 @@ pub trait Linked { type ToEntity: EntityTrait; - fn link() -> Vec; + fn link(&self) -> Vec; - fn find_linked() -> Select { + fn find_linked(&self) -> Select { let mut select = Select::new(); - for rel in Self::link().into_iter().rev() { + for rel in self.link().into_iter().rev() { select = select.join_rev(JoinType::InnerJoin, rel); } select diff --git a/src/query/join.rs b/src/query/join.rs index be759e97..42bc993c 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -59,13 +59,13 @@ where } /// Left Join with a Linked Entity and select both Entity. - pub fn find_also_linked(self, _: L) -> SelectTwo + pub fn find_also_linked(self, l: L) -> SelectTwo where L: Linked, T: EntityTrait, { let mut slf = self; - for rel in L::link() { + for rel in l.link() { slf = slf.join(JoinType::LeftJoin, rel); } slf.select_also(T::default()) diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index 56ca8797..32cc22fd 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -80,7 +80,7 @@ impl Linked for CakeToFilling { type ToEntity = super::filling::Entity; - fn link() -> Vec { + fn link(&self) -> Vec { vec![ super::cake_filling::Relation::Cake.def().rev(), super::cake_filling::Relation::Filling.def(), diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs index 287e5deb..d3b01651 100644 --- a/tests/common/bakery_chain/baker.rs +++ b/tests/common/bakery_chain/baker.rs @@ -88,7 +88,7 @@ impl Linked for BakedForCustomer { type ToEntity = super::customer::Entity; - fn link() -> Vec { + fn link(&self) -> Vec { vec![ super::cakes_bakers::Relation::Baker.def().rev(), super::cakes_bakers::Relation::Cake.def(), From 0db9021da08d776919a5781d4748f8a9710898cf Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Sep 2021 16:30:57 +0800 Subject: [PATCH 5/7] Update tests --- src/executor/select.rs | 2 +- src/query/helper.rs | 29 +++++++++++++++++- tests/relational_tests.rs | 63 +++++++++++++++++++++++++++++++-------- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/executor/select.rs b/src/executor/select.rs index 6196ec73..836ea495 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -128,7 +128,7 @@ where E: EntityTrait, F: EntityTrait, { - fn into_model(self) -> Selector> + pub fn into_model(self) -> Selector> where M: FromQueryResult, N: FromQueryResult, diff --git a/src/query/helper.rs b/src/query/helper.rs index 6ade581a..4ca44905 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -3,7 +3,7 @@ use crate::{ RelationDef, }; use sea_query::{ - Alias, Expr, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef, + Alias, Expr, Iden, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef, }; pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement}; @@ -66,6 +66,33 @@ pub trait QuerySelect: Sized { self } + /// Add a select column with prefixed alias + /// ``` + /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + /// + /// assert_eq!( + /// cake::Entity::find() + /// .select_only() + /// .column_as_prefixed(cake::Column::Id.count(), "A_", cake::Column::Id) + /// .build(DbBackend::Postgres) + /// .to_string(), + /// r#"SELECT COUNT("cake"."id") AS "A_id" FROM "cake""# + /// ); + /// ``` + fn column_as_prefixed(mut self, col: C, prefix: &str, alias: I) -> Self + where + C: IntoSimpleExpr, + I: Iden, + { + self.query().expr(SelectExpr { + expr: col.into_simple_expr(), + alias: Some(SeaRc::new(Alias::new( + vec![prefix, alias.to_string().as_str()].join("").as_str(), + ))), + }); + self + } + /// Add a group by column /// ``` /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index 1ddee902..f67d4e88 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -652,23 +652,60 @@ pub async fn linked() -> Result<(), DbErr> { .save(&ctx.db) .await?; - /* - SELECT `baker`.`id` AS `A_id`, `baker`.`name` AS `A_name`, - `baker`.`contact_details` AS `A_contact_details`, - `baker`.`bakery_id` AS `A_bakery_id`, `customer`.`id` AS `B_id`, - `customer`.`name` AS `B_name`, `customer`.`notes` AS `B_notes` - FROM `baker` - LEFT JOIN `cakes_bakers` ON `baker`.`id` = `cakes_bakers`.`baker_id` - LEFT JOIN `cake` ON `cakes_bakers`.`cake_id` = `cake`.`id` - LEFT JOIN `lineitem` ON `cake`.`id` = `lineitem`.`cake_id` - LEFT JOIN `order` ON `lineitem`.`order_id` = `order`.`id` - LEFT JOIN `customer` ON `order`.`customer_id` = `customer`.`id` - */ + #[derive(Debug, FromQueryResult, PartialEq)] + struct BakerLite { + name: String, + } + + #[derive(Debug, FromQueryResult, PartialEq)] + struct CustomerLite { + name: String, + } + let baked_for_customers = Baker::find() .find_also_linked(baker::BakedForCustomer) + .select_only() + .column_as_prefixed(baker::Column::Name, "A_", baker::Column::Name) + .column_as_prefixed(customer::Column::Name, "B_", customer::Column::Name) + .group_by(baker::Column::Id) + .group_by(customer::Column::Id) + .group_by(baker::Column::Name) + .group_by(customer::Column::Name) + .order_by_asc(baker::Column::Id) + .order_by_asc(customer::Column::Id) + .into_model::() .all(&ctx.db) .await?; - println!("{:#?}", baked_for_customers); + + assert_eq!( + baked_for_customers, + vec![ + ( + BakerLite { + name: "Baker Bob".to_owned(), + }, + Some(CustomerLite { + name: "Kara".to_owned(), + }) + ), + ( + BakerLite { + name: "Baker Bobby".to_owned(), + }, + Some(CustomerLite { + name: "Kate".to_owned(), + }) + ), + ( + BakerLite { + name: "Baker Bobby".to_owned(), + }, + Some(CustomerLite { + name: "Kara".to_owned(), + }) + ), + ] + ); ctx.delete().await; From cd43ff7143b50b339e0b01df5b74b1ddce77aec0 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 2 Sep 2021 17:43:29 +0800 Subject: [PATCH 6/7] Remove turbofish --- tests/relational_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index f67d4e88..2d129d2d 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -8,7 +8,7 @@ pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; // Run the test locally: -// DATABASE_URL="mysql://root:@localhost" cargo test --features sqlx-mysql,runtime-async-std --test relational_tests +// DATABASE_URL="mysql://root:@localhost" cargo test --features sqlx-mysql,runtime-async-std-native-tls --test relational_tests #[sea_orm_macros::test] #[cfg(any( feature = "sqlx-mysql", @@ -662,7 +662,7 @@ pub async fn linked() -> Result<(), DbErr> { name: String, } - let baked_for_customers = Baker::find() + let baked_for_customers: Vec<(BakerLite, Option)> = Baker::find() .find_also_linked(baker::BakedForCustomer) .select_only() .column_as_prefixed(baker::Column::Name, "A_", baker::Column::Name) @@ -673,7 +673,7 @@ pub async fn linked() -> Result<(), DbErr> { .group_by(customer::Column::Name) .order_by_asc(baker::Column::Id) .order_by_asc(customer::Column::Id) - .into_model::() + .into_model() .all(&ctx.db) .await?; From 541b94f15d6b968733485a7e3ffde1c33d26dafc Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Sep 2021 11:23:29 +0800 Subject: [PATCH 7/7] Update query helper column_as --- src/entity/identity.rs | 34 ++++++++++++++++++++++++++++++++- src/executor/select.rs | 8 ++++---- src/query/combine.rs | 33 ++++++++++++++++++++++++++------ src/query/helper.rs | 40 ++++++--------------------------------- src/query/mod.rs | 2 +- tests/relational_tests.rs | 5 +++-- 6 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/entity/identity.rs b/src/entity/identity.rs index d1cc3170..d8623b7d 100644 --- a/src/entity/identity.rs +++ b/src/entity/identity.rs @@ -1,5 +1,6 @@ use crate::{ColumnTrait, EntityTrait, IdenStatic}; -use sea_query::{DynIden, IntoIden}; +use sea_query::{Alias, DynIden, Iden, IntoIden, SeaRc}; +use std::fmt; #[derive(Debug, Clone)] pub enum Identity { @@ -8,6 +9,25 @@ pub enum Identity { Ternary(DynIden, DynIden, DynIden), } +impl Iden for Identity { + fn unquoted(&self, s: &mut dyn fmt::Write) { + match self { + Identity::Unary(iden) => { + write!(s, "{}", iden.to_string()).unwrap(); + } + Identity::Binary(iden1, iden2) => { + write!(s, "{}", iden1.to_string()).unwrap(); + write!(s, "{}", iden2.to_string()).unwrap(); + } + Identity::Ternary(iden1, iden2, iden3) => { + write!(s, "{}", iden1.to_string()).unwrap(); + write!(s, "{}", iden2.to_string()).unwrap(); + write!(s, "{}", iden3.to_string()).unwrap(); + } + } + } +} + pub trait IntoIdentity { fn into_identity(self) -> Identity; } @@ -19,6 +39,18 @@ where fn identity_of(self) -> Identity; } +impl IntoIdentity for String { + fn into_identity(self) -> Identity { + self.as_str().into_identity() + } +} + +impl IntoIdentity for &str { + fn into_identity(self) -> Identity { + Identity::Unary(SeaRc::new(Alias::new(self))) + } +} + impl IntoIdentity for T where T: IdenStatic, diff --git a/src/executor/select.rs b/src/executor/select.rs index 836ea495..fc3a970b 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,6 +1,6 @@ use crate::{ - error::*, query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, - JsonValue, ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectTwo, + error::*, DatabaseConnection, EntityTrait, FromQueryResult, IdenStatic, Iterable, JsonValue, + ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, SelectTwoMany, Statement, }; use sea_query::SelectStatement; @@ -66,8 +66,8 @@ where fn from_raw_query_result(res: QueryResult) -> Result { Ok(( - M::from_query_result(&res, combine::SELECT_A)?, - N::from_query_result_optional(&res, combine::SELECT_B)?, + M::from_query_result(&res, SelectA.as_str())?, + N::from_query_result_optional(&res, SelectB.as_str())?, )) } } diff --git a/src/query/combine.rs b/src/query/combine.rs index 8cce0510..0c0f151f 100644 --- a/src/query/combine.rs +++ b/src/query/combine.rs @@ -1,10 +1,31 @@ -use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo, SelectTwoMany}; +use crate::{ + EntityTrait, IdenStatic, 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}; -pub const SELECT_A: &str = "A_"; -pub const SELECT_B: &str = "B_"; +macro_rules! select_def { + ( $ident: ident, $str: expr ) => { + #[derive(Debug, Clone, Copy)] + pub struct $ident; + + impl Iden for $ident { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!(s, "{}", self.as_str()).unwrap(); + } + } + + impl IdenStatic for $ident { + fn as_str(&self) -> &str { + $str + } + } + }; +} + +select_def!(SelectA, "A_"); +select_def!(SelectB, "B_"); impl Select where @@ -37,7 +58,7 @@ where where F: EntityTrait, { - self = self.apply_alias(SELECT_A); + self = self.apply_alias(SelectA.as_str()); SelectTwo::new(self.into_query()) } @@ -45,7 +66,7 @@ where where F: EntityTrait, { - self = self.apply_alias(SELECT_A); + self = self.apply_alias(SelectA.as_str()); SelectTwoMany::new(self.into_query()) } } @@ -102,7 +123,7 @@ where S: QueryTrait, { for col in ::iter() { - let alias = format!("{}{}", SELECT_B, col.to_string().as_str()); + let alias = format!("{}{}", SelectB.as_str(), col.as_str()); selector.query().expr(SelectExpr { expr: col.into_simple_expr(), alias: Some(SeaRc::new(Alias::new(&alias))), diff --git a/src/query/helper.rs b/src/query/helper.rs index 4ca44905..88a5c55c 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -1,11 +1,9 @@ use crate::{ - ColumnTrait, EntityTrait, Identity, IntoSimpleExpr, Iterable, ModelTrait, PrimaryKeyToColumn, - RelationDef, -}; -use sea_query::{ - Alias, Expr, Iden, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef, + ColumnTrait, EntityTrait, Identity, IntoIdentity, IntoSimpleExpr, Iterable, ModelTrait, + PrimaryKeyToColumn, RelationDef, }; pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement}; +use sea_query::{Expr, IntoCondition, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef}; // LINT: when the column does not appear in tables selected from // LINT: when there is a group by clause, but some columns don't have aggregate functions @@ -55,40 +53,14 @@ pub trait QuerySelect: Sized { /// r#"SELECT COUNT("cake"."id") AS "count" FROM "cake""# /// ); /// ``` - fn column_as(mut self, col: C, alias: &str) -> Self + fn column_as(mut self, col: C, alias: I) -> Self where C: IntoSimpleExpr, + I: IntoIdentity, { self.query().expr(SelectExpr { expr: col.into_simple_expr(), - alias: Some(SeaRc::new(Alias::new(alias))), - }); - self - } - - /// Add a select column with prefixed alias - /// ``` - /// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; - /// - /// assert_eq!( - /// cake::Entity::find() - /// .select_only() - /// .column_as_prefixed(cake::Column::Id.count(), "A_", cake::Column::Id) - /// .build(DbBackend::Postgres) - /// .to_string(), - /// r#"SELECT COUNT("cake"."id") AS "A_id" FROM "cake""# - /// ); - /// ``` - fn column_as_prefixed(mut self, col: C, prefix: &str, alias: I) -> Self - where - C: IntoSimpleExpr, - I: Iden, - { - self.query().expr(SelectExpr { - expr: col.into_simple_expr(), - alias: Some(SeaRc::new(Alias::new( - vec![prefix, alias.to_string().as_str()].join("").as_str(), - ))), + alias: Some(SeaRc::new(alias.into_identity())), }); self } diff --git a/src/query/mod.rs b/src/query/mod.rs index 899882ba..54cc12dd 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -9,7 +9,7 @@ mod select; mod traits; mod update; -// pub use combine::*; +pub use combine::{SelectA, SelectB}; pub use delete::*; pub use helper::*; pub use insert::*; diff --git a/tests/relational_tests.rs b/tests/relational_tests.rs index 2d129d2d..198d4b24 100644 --- a/tests/relational_tests.rs +++ b/tests/relational_tests.rs @@ -484,6 +484,7 @@ pub async fn having() { ))] pub async fn linked() -> Result<(), DbErr> { use common::bakery_chain::Order; + use sea_orm::{SelectA, SelectB}; let ctx = TestContext::new("test_linked").await; @@ -665,8 +666,8 @@ pub async fn linked() -> Result<(), DbErr> { let baked_for_customers: Vec<(BakerLite, Option)> = Baker::find() .find_also_linked(baker::BakedForCustomer) .select_only() - .column_as_prefixed(baker::Column::Name, "A_", baker::Column::Name) - .column_as_prefixed(customer::Column::Name, "B_", customer::Column::Name) + .column_as(baker::Column::Name, (SelectA, baker::Column::Name)) + .column_as(customer::Column::Name, (SelectB, customer::Column::Name)) .group_by(baker::Column::Id) .group_by(customer::Column::Id) .group_by(baker::Column::Name)