Merge pull request #104 from SeaQL/linked-api
Represent several relations between same types
This commit is contained in:
commit
5cbaa6b699
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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")]
|
||||
|
@ -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,
|
||||
|
@ -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")]
|
||||
|
@ -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,
|
||||
|
@ -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))),
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(" ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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::*;
|
||||
|
@ -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 {}
|
||||
|
@ -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 {}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user