Repository: SeaQL/sea-orm (rust)

Issue:
Represent several relations between same types
Example problematic cases:

1. Entities A et B have several different many-to-many relations
2. Entity A has several one-to-many relations to entity B (Eg: `COUPLE.member1 -> USER` and `COUPLE.member2 -> USER`)

In these cases there are several issues:

1. the codegen uses A/B entity names to generate `Relation` variants, so we obtain duplicate variants. I guess it could use the join table or foreign key entity name
2. the `Related` trait is typed on A/B entities, so same issue. So I believe fixing this requires a breaking conception change...

Pull request description:
Represent several relations between same types
Resolve #89 

Patch:
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<T> IntoIdentity for T
 where
     T: IdenStatic,
diff --git a/src/entity/model.rs b/src/entity/model.rs
index 15ebdb58..4774e1dc 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 {
     {
         <Self::Entity as Related<R>>::find_related().belongs_to(self)
     }
+
+    fn find_linked<L>(&self, l: L) -> Select<L::ToEntity>
+    where
+        L: Linked<FromEntity = Self::Entity>,
+    {
+        l.find_linked()
+    }
 }
 
 pub trait FromQueryResult {
diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs
index c0c0f5b5..6720f916 100644
--- a/src/entity/prelude.rs
+++ b/src/entity/prelude.rs
@@ -2,8 +2,8 @@ pub use crate::{
     error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType,
     DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity,
     DeriveModel, DerivePrimaryKey, EntityName, EntityTrait, EnumIter, ForeignKeyAction, Iden,
-    IdenStatic, ModelTrait, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related,
-    RelationDef, RelationTrait, Select, Value,
+    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 6e90975a..4a35b8ba 100644
--- a/src/entity/relation.rs
+++ b/src/entity/relation.rs
@@ -30,6 +30,22 @@ where
     }
 }
 
+pub trait Linked {
+    type FromEntity: EntityTrait;
+
+    type ToEntity: EntityTrait;
+
+    fn link(&self) -> Vec<RelationDef>;
+
+    fn find_linked(&self) -> Select<Self::ToEntity> {
+        let mut select = Select::new();
+        for rel in self.link().into_iter().rev() {
+            select = select.join_rev(JoinType::InnerJoin, rel);
+        }
+        select
+    }
+}
+
 pub struct RelationDef {
     pub rel_type: RelationType,
     pub from_tbl: TableRef,
diff --git a/src/executor/query.rs b/src/executor/query.rs
index 08728081..b935bfc3 100644
--- a/src/executor/query.rs
+++ b/src/executor/query.rs
@@ -264,9 +264,12 @@ impl TryGetable for Decimal {
                     .map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
                 use rust_decimal::prelude::FromPrimitive;
                 match val {
-                    Some(v) => Decimal::from_f64(v)
-                        .ok_or_else(|| TryGetError::DbErr(DbErr::Query("Failed to convert f64 into Decimal".to_owned()))),
-                    None => Err(TryGetError::Null)
+                    Some(v) => Decimal::from_f64(v).ok_or_else(|| {
+                        TryGetError::DbErr(DbErr::Query(
+                            "Failed to convert f64 into Decimal".to_owned(),
+                        ))
+                    }),
+                    None => Err(TryGetError::Null),
                 }
             }
             #[cfg(feature = "mock")]
diff --git a/src/executor/select.rs b/src/executor/select.rs
index 6196ec73..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<Self::Item, DbErr> {
         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())?,
         ))
     }
 }
@@ -128,7 +128,7 @@ where
     E: EntityTrait,
     F: EntityTrait,
 {
-    fn into_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
+    pub fn into_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
     where
         M: FromQueryResult,
         N: FromQueryResult,
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<E> Select<E>
 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<QueryStatement = SelectStatement>,
 {
     for col in <F::Column as Iterable>::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 57fafe3f..cb1ca28d 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, 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,13 +53,14 @@ pub trait QuerySelect: Sized {
     ///     r#"SELECT COUNT("cake"."id") AS "count" FROM "cake""#
     /// );
     /// ```
-    fn column_as<C>(mut self, col: C, alias: &str) -> Self
+    fn column_as<C, I>(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))),
+            alias: Some(SeaRc::new(alias.into_identity())),
         });
         self
     }
diff --git a/src/query/join.rs b/src/query/join.rs
index 72726d14..42bc993c 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<E> Select<E>
@@ -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<L, T>(self, l: L) -> SelectTwo<E, T>
+    where
+        L: Linked<FromEntity = E, ToEntity = T>,
+        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_filling`.`filling_id` = `filling`.`id`"#,
+                r#"INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_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/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::*;


Test patch:
diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs
index f8a35d6c..32cc22fd 100644
--- a/src/tests_cfg/cake.rs
+++ b/src/tests_cfg/cake.rs
@@ -73,4 +73,19 @@ impl Related<super::filling::Entity> for Entity {
     }
 }
 
+pub struct CakeToFilling;
+
+impl Linked for CakeToFilling {
+    type FromEntity = Entity;
+
+    type ToEntity = super::filling::Entity;
+
+    fn link(&self) -> Vec<RelationDef> {
+        vec![
+            super::cake_filling::Relation::Cake.def().rev(),
+            super::cake_filling::Relation::Filling.def(),
+        ]
+    }
+}
+
 impl ActiveModelBehavior for ActiveModel {}
diff --git a/tests/common/bakery_chain/baker.rs b/tests/common/bakery_chain/baker.rs
index 3dff66aa..d111a311 100644
--- a/tests/common/bakery_chain/baker.rs
+++ b/tests/common/bakery_chain/baker.rs
@@ -83,4 +83,22 @@ impl Related<super::cake::Entity> for Entity {
     }
 }
 
+pub struct BakedForCustomer;
+
+impl Linked for BakedForCustomer {
+    type FromEntity = Entity;
+
+    type ToEntity = super::customer::Entity;
+
+    fn link(&self) -> Vec<RelationDef> {
+        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..198d4b24 100644
--- a/tests/relational_tests.rs
+++ b/tests/relational_tests.rs
@@ -1,13 +1,14 @@
 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};
 
 // 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",
@@ -474,3 +475,240 @@ 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;
+    use sea_orm::{SelectA, SelectB};
+
+    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?;
+
+    #[derive(Debug, FromQueryResult, PartialEq)]
+    struct BakerLite {
+        name: String,
+    }
+
+    #[derive(Debug, FromQueryResult, PartialEq)]
+    struct CustomerLite {
+        name: String,
+    }
+
+    let baked_for_customers: Vec<(BakerLite, Option<CustomerLite>)> = Baker::find()
+        .find_also_linked(baker::BakedForCustomer)
+        .select_only()
+        .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)
+        .group_by(customer::Column::Name)
+        .order_by_asc(baker::Column::Id)
+        .order_by_asc(customer::Column::Id)
+        .into_model()
+        .all(&ctx.db)
+        .await?;
+
+    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;
+
+    Ok(())
+}