#![allow(unused_imports, dead_code)] pub mod common; pub use common::{TestContext, bakery_chain::*, setup::*}; use sea_orm::{DbConn, DbErr, LoaderTraitEx, RuntimeErr, entity::*, query::*}; mod enum_pk_models { use crate::common::features::Tea; use sea_orm::entity::prelude::*; pub mod tea_inventory { use super::tea_order; use super::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "tea_inventory")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub tea: Tea, pub stock: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::tea_order::Entity")] TeaOrder, } impl Related for Entity { fn to() -> RelationDef { Relation::TeaOrder.def() } } impl ActiveModelBehavior for ActiveModel {} } pub mod tea_order { use super::tea_inventory; use super::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "tea_orders")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub order_id: i32, #[sea_orm(primary_key, auto_increment = false)] pub tea: Tea, pub quantity: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::tea_inventory::Entity", from = "Column::Tea", to = "super::tea_inventory::Column::Tea" )] TeaInventory, } impl Related for Entity { fn to() -> RelationDef { Relation::TeaInventory.def() } } impl ActiveModelBehavior for ActiveModel {} } } use enum_pk_models::{tea_inventory, tea_order}; #[sea_orm_macros::test] async fn loader_load_one() -> Result<(), DbErr> { let ctx = TestContext::new("loader_test_load_one").await; create_tables(&ctx.db).await?; let bakery_0 = insert_bakery(&ctx.db, "SeaSide Bakery").await?; let baker_1 = insert_baker(&ctx.db, "Baker 1", bakery_0.id).await?; let baker_2 = insert_baker(&ctx.db, "Baker 2", bakery_0.id).await?; let baker_3 = baker::ActiveModel { name: Set("Baker 3".to_owned()), contact_details: Set(serde_json::json!({})), bakery_id: Set(None), ..Default::default() } .insert(&ctx.db) .await?; let bakers = baker::Entity::find().all(&ctx.db).await?; let bakeries = bakers.load_one(bakery::Entity, &ctx.db).await?; assert_eq!(bakers, [baker_1.clone(), baker_2.clone(), baker_3.clone()]); assert_eq!( bakeries, [Some(bakery_0.clone()), Some(bakery_0.clone()), None] ); let bakers = vec![ Some(baker_1.clone().into_ex()), None, Some(baker_2.clone().into_ex()), Some(baker_3.clone().into_ex()), None, ]; let bakeries = bakers .as_slice() .load_one_ex(bakery::Entity, &ctx.db) .await?; assert_eq!( bakeries, [ Some(bakery_0.clone()), None, Some(bakery_0.clone()), None, None ] ); // has many find, should use load_many instead let bakeries = bakery::Entity::find().all(&ctx.db).await?; let bakers = bakeries.load_one(baker::Entity, &ctx.db).await; assert_eq!( bakers, Err(DbErr::Query(RuntimeErr::Internal( "Relation is HasMany instead of HasOne".to_string() ))) ); Ok(()) } #[sea_orm_macros::test] async fn loader_load_many() -> Result<(), DbErr> { let ctx = TestContext::new("loader_test_load_many").await; create_tables(&ctx.db).await?; let bakery_1 = insert_bakery(&ctx.db, "SeaSide Bakery").await?; let bakery_2 = insert_bakery(&ctx.db, "Offshore Bakery").await?; let bakery_3 = insert_bakery(&ctx.db, "Rocky Bakery").await?; let baker_1 = insert_baker(&ctx.db, "Baker 1", bakery_1.id).await?; let baker_2 = insert_baker(&ctx.db, "Baker 2", bakery_1.id).await?; let baker_3 = insert_baker(&ctx.db, "John", bakery_2.id).await?; let baker_4 = insert_baker(&ctx.db, "Baker 4", bakery_2.id).await?; let bakeries = bakery::Entity::find().all(&ctx.db).await?; let bakers = bakeries.load_many(baker::Entity, &ctx.db).await?; assert_eq!( bakeries, [bakery_1.clone(), bakery_2.clone(), bakery_3.clone()] ); assert_eq!( bakers, [ vec![baker_1.clone(), baker_2.clone()], vec![baker_3.clone(), baker_4.clone()], vec![] ] ); // test interlaced loader let bakeries_sparse = vec![ Some(bakery_1.clone().into_ex()), None, Some(bakery_2.clone().into_ex()), None, ]; let bakers = bakeries_sparse .as_slice() .load_many_ex(baker::Entity, &ctx.db) .await?; assert_eq!( bakers, [ vec![baker_1.clone(), baker_2.clone()], vec![], vec![baker_3.clone(), baker_4.clone()], vec![], ] ); // load bakers again but with additional condition let bakers = bakeries .load_many( baker::Entity::find().filter(baker::Column::Name.like("Baker%")), &ctx.db, ) .await?; assert_eq!( bakers, [ vec![baker_1.clone(), baker_2.clone()], vec![baker_4.clone()], vec![] ] ); // now, start from baker let bakers = baker::Entity::find().all(&ctx.db).await?; let bakeries = bakers.load_one(bakery::Entity::find(), &ctx.db).await?; // note that two bakers share the same bakery assert_eq!(bakers, [baker_1, baker_2, baker_3, baker_4]); assert_eq!( bakeries, [ Some(bakery_1.clone()), Some(bakery_1.clone()), Some(bakery_2.clone()), Some(bakery_2.clone()) ] ); // following should be equivalent let bakeries = bakers.load_many(bakery::Entity::find(), &ctx.db).await?; assert_eq!( bakeries, [ vec![bakery_1.clone()], vec![bakery_1.clone()], vec![bakery_2.clone()], vec![bakery_2.clone()], ] ); Ok(()) } #[sea_orm_macros::test] async fn loader_load_many_multi() -> Result<(), DbErr> { let ctx = TestContext::new("loader_test_load_many_multi").await; create_tables(&ctx.db).await?; let bakery_1 = insert_bakery(&ctx.db, "SeaSide Bakery").await?; let bakery_2 = insert_bakery(&ctx.db, "Offshore Bakery").await?; let baker_1 = insert_baker(&ctx.db, "John", bakery_1.id).await?; let baker_2 = insert_baker(&ctx.db, "Jane", bakery_1.id).await?; let baker_3 = insert_baker(&ctx.db, "Peter", bakery_2.id).await?; let cake_1 = insert_cake(&ctx.db, "Cheesecake", Some(bakery_1.id)).await?; let cake_2 = insert_cake(&ctx.db, "Chocolate", Some(bakery_2.id)).await?; let cake_3 = insert_cake(&ctx.db, "Chiffon", Some(bakery_2.id)).await?; let _cake_4 = insert_cake(&ctx.db, "Apple Pie", None).await?; // no one makes apple pie let bakeries = bakery::Entity::find().all(&ctx.db).await?; let bakers = bakeries.load_many(baker::Entity, &ctx.db).await?; let cakes = bakeries.load_many(cake::Entity, &ctx.db).await?; assert_eq!(bakeries, [bakery_1, bakery_2]); assert_eq!(bakers, [vec![baker_1, baker_2], vec![baker_3]]); assert_eq!(cakes, [vec![cake_1], vec![cake_2, cake_3]]); Ok(()) } #[sea_orm_macros::test] async fn loader_load_many_enum_pk_postgres() -> Result<(), DbErr> { use crate::common::features::Tea; use sea_orm::{ActiveValue::Set, ConnectionTrait, DbBackend, Schema}; use sea_query::{Alias, extension::postgres::Type}; use tea_inventory::{ ActiveModel as TeaInventoryActiveModel, Column as TeaInventoryColumn, Entity as TeaInventoryEntity, Model as TeaInventoryModel, }; use tea_order::{ ActiveModel as TeaOrderActiveModel, Column as TeaOrderColumn, Entity as TeaOrderEntity, Model as TeaOrderModel, }; let ctx = TestContext::new("loader_enum_pk_postgres").await; let db = &ctx.db; if db.get_database_backend() != DbBackend::Postgres { return Ok(()); } let mut drop_type = Type::drop(); drop_type.if_exists().name("tea").cascade(); db.execute(&drop_type).await?; db.get_schema_builder() .register(TeaInventoryEntity) .register(TeaOrderEntity) .apply(db) .await?; TeaInventoryActiveModel { tea: Set(Tea::EverydayTea), stock: Set(10), } .insert(db) .await?; TeaInventoryActiveModel { tea: Set(Tea::BreakfastTea), stock: Set(4), } .insert(db) .await?; TeaOrderActiveModel { order_id: Set(1), tea: Set(Tea::EverydayTea), quantity: Set(2), } .insert(db) .await?; TeaOrderActiveModel { order_id: Set(2), tea: Set(Tea::EverydayTea), quantity: Set(5), } .insert(db) .await?; TeaOrderActiveModel { order_id: Set(1), tea: Set(Tea::BreakfastTea), quantity: Set(1), } .insert(db) .await?; let teas = TeaInventoryEntity::find() .order_by_asc(TeaInventoryColumn::Tea) .all(db) .await?; let loaded_orders = teas .load_many( TeaOrderEntity::find().order_by_asc(TeaOrderColumn::OrderId), db, ) .await?; assert_eq!(teas.len(), 2); assert_eq!(loaded_orders.len(), 2); for (inventory, orders) in teas.iter().zip(&loaded_orders) { match inventory.tea { Tea::EverydayTea => { assert_eq!( orders, &vec![ TeaOrderModel { order_id: 1, tea: Tea::EverydayTea, quantity: 2, }, TeaOrderModel { order_id: 2, tea: Tea::EverydayTea, quantity: 5, } ] ); } Tea::BreakfastTea => { assert_eq!( orders, &vec![TeaOrderModel { order_id: 1, tea: Tea::BreakfastTea, quantity: 1, }] ); } Tea::AfternoonTea => {} } } Ok(()) } #[sea_orm_macros::test] async fn loader_load_many_to_many() -> Result<(), DbErr> { let ctx = TestContext::new("loader_test_load_many_to_many").await; create_tables(&ctx.db).await?; let bakery_1 = insert_bakery(&ctx.db, "SeaSide Bakery").await?; let baker_1 = insert_baker(&ctx.db, "Jane", bakery_1.id).await?; let baker_2 = insert_baker(&ctx.db, "Peter", bakery_1.id).await?; let baker_3 = insert_baker(&ctx.db, "Fred", bakery_1.id).await?; // does not make cake let cake_1 = insert_cake(&ctx.db, "Cheesecake", None).await?; let cake_2 = insert_cake(&ctx.db, "Coffee", None).await?; let cake_3 = insert_cake(&ctx.db, "Chiffon", None).await?; let cake_4 = insert_cake(&ctx.db, "Apple Pie", None).await?; // no one makes apple pie insert_cake_baker(&ctx.db, baker_1.id, cake_1.id).await?; insert_cake_baker(&ctx.db, baker_1.id, cake_2.id).await?; insert_cake_baker(&ctx.db, baker_2.id, cake_2.id).await?; insert_cake_baker(&ctx.db, baker_2.id, cake_3.id).await?; let bakers = baker::Entity::find().all(&ctx.db).await?; let cakes = bakers .load_many_to_many(cake::Entity, cakes_bakers::Entity, &ctx.db) .await?; assert_eq!(bakers, [baker_1.clone(), baker_2.clone(), baker_3.clone()]); assert_eq!( cakes, [ vec![cake_1.clone(), cake_2.clone()], vec![cake_2.clone(), cake_3.clone()], vec![] ] ); // same, but apply restrictions on cakes let cakes = bakers .load_many_to_many( cake::Entity::find().filter(cake::Column::Name.like("Ch%")), cakes_bakers::Entity, &ctx.db, ) .await?; assert_eq!(cakes, [vec![cake_1.clone()], vec![cake_3.clone()], vec![]]); // now, start again from cakes let cakes = cake::Entity::find().all(&ctx.db).await?; let bakers = cakes .load_many_to_many(baker::Entity, cakes_bakers::Entity, &ctx.db) .await?; assert_eq!(cakes, [cake_1, cake_2, cake_3, cake_4]); assert_eq!( bakers, [ vec![baker_1.clone()], vec![baker_1.clone(), baker_2.clone()], vec![baker_2.clone()], vec![] ] ); Ok(()) } #[sea_orm_macros::test] async fn loader_load_many_to_many_dyn() -> Result<(), DbErr> { let ctx = TestContext::new("loader_test_load_many_to_many_dyn").await; create_tables(&ctx.db).await?; let bakery_1 = insert_bakery(&ctx.db, "SeaSide Bakery").await?; let baker_1 = insert_baker(&ctx.db, "Jane", bakery_1.id).await?; let baker_2 = insert_baker(&ctx.db, "Peter", bakery_1.id).await?; let baker_3 = insert_baker(&ctx.db, "Fred", bakery_1.id).await?; // does not make cake let cake_1 = insert_cake(&ctx.db, "Cheesecake", None).await?; let cake_2 = insert_cake(&ctx.db, "Coffee", None).await?; let cake_3 = insert_cake(&ctx.db, "Chiffon", None).await?; let cake_4 = insert_cake(&ctx.db, "Apple Pie", None).await?; // no one makes apple pie insert_cake_baker(&ctx.db, baker_1.id, cake_1.id).await?; insert_cake_baker(&ctx.db, baker_1.id, cake_2.id).await?; insert_cake_baker(&ctx.db, baker_2.id, cake_2.id).await?; insert_cake_baker(&ctx.db, baker_2.id, cake_3.id).await?; let bakers = baker::Entity::find().all(&ctx.db).await?; let cakes = bakers.load_many(cake::Entity, &ctx.db).await?; assert_eq!(bakers, [baker_1.clone(), baker_2.clone(), baker_3.clone()]); assert_eq!( cakes, [ vec![cake_1.clone(), cake_2.clone()], vec![cake_2.clone(), cake_3.clone()], vec![] ] ); // same, but apply restrictions on cakes let cakes = bakers .load_many_to_many( cake::Entity::find().filter(cake::Column::Name.like("Ch%")), cakes_bakers::Entity, &ctx.db, ) .await?; assert_eq!(cakes, [vec![cake_1.clone()], vec![cake_3.clone()], vec![]]); // now, start again from cakes let cakes = cake::Entity::find().all(&ctx.db).await?; let bakers = cakes.load_many(baker::Entity, &ctx.db).await?; assert_eq!(cakes, [cake_1, cake_2, cake_3, cake_4]); assert_eq!( bakers, [ vec![baker_1.clone()], vec![baker_1.clone(), baker_2.clone()], vec![baker_2.clone()], vec![] ] ); Ok(()) } #[sea_orm_macros::test] async fn loader_self_join() -> Result<(), DbErr> { use common::blogger::{user, user_follower}; use common::film_store::{staff, staff_compact}; let ctx = TestContext::new("test_loader_self_join").await; let db = &ctx.db; db.get_schema_builder() .register(staff::Entity) .register(user::Entity) .register(user_follower::Entity) .apply(db) .await?; let alan = staff::ActiveModel { name: Set("Alan".into()), reports_to_id: Set(None), ..Default::default() } .insert(db) .await?; staff::ActiveModel { name: Set("Ben".into()), reports_to_id: Set(Some(alan.id)), ..Default::default() } .insert(db) .await?; staff::ActiveModel { name: Set("Alice".into()), reports_to_id: Set(Some(alan.id)), ..Default::default() } .insert(db) .await?; staff::ActiveModel { name: Set("Elle".into()), reports_to_id: Set(None), ..Default::default() } .insert(db) .await?; let staff = staff::Entity::find() .order_by_asc(staff::Column::Id) .all(db) .await?; let reports_to = staff .load_self(staff::Entity, staff::Relation::ReportsTo, db) .await?; assert_eq!(staff[0].name, "Alan"); assert_eq!(reports_to[0], None); assert_eq!(staff[1].name, "Ben"); assert_eq!(reports_to[1].as_ref().unwrap().name, "Alan"); assert_eq!(staff[2].name, "Alice"); assert_eq!(reports_to[2].as_ref().unwrap().name, "Alan"); assert_eq!(staff[3].name, "Elle"); assert_eq!(reports_to[3], None); let manages = staff .load_self_many( staff::Entity::find().order_by_asc(staff::COLUMN.id), staff::Relation::ReportsTo, db, ) .await?; assert_eq!( manages, staff .load_self_many( staff::Entity::find().order_by_asc(staff::COLUMN.id), staff::Relation::Manages, db, ) .await? ); assert_eq!(staff[0].name, "Alan"); assert_eq!(manages[0].len(), 2); assert_eq!(manages[0][0].name, "Ben"); assert_eq!(manages[0][1].name, "Alice"); assert_eq!(staff[1].name, "Ben"); assert_eq!(manages[1].len(), 0); assert_eq!(staff[2].name, "Alice"); assert_eq!(manages[2].len(), 0); assert_eq!(staff[3].name, "Elle"); assert_eq!(manages[3].len(), 0); // test self_ref on compact_model let staff = staff_compact::Entity::find() .order_by_asc(staff_compact::COLUMN.id) .all(db) .await?; let reports_to = staff .load_self( staff_compact::Entity, staff_compact::Relation::ReportsTo, db, ) .await?; let manages = staff .load_self_many( staff_compact::Entity::find().order_by_asc(staff_compact::COLUMN.id), staff_compact::Relation::ReportsTo, db, ) .await?; assert_eq!( manages, staff .load_self_many( staff_compact::Entity::find().order_by_asc(staff_compact::COLUMN.id), staff_compact::Relation::Manages, db, ) .await? ); assert_eq!(staff[0].name, "Alan"); assert_eq!(reports_to[0], None); assert_eq!(manages[0].len(), 2); assert_eq!(manages[0][0].name, "Ben"); assert_eq!(manages[0][1].name, "Alice"); assert_eq!(staff[1].name, "Ben"); assert_eq!(reports_to.get(1).unwrap().as_ref().unwrap().name, "Alan"); assert_eq!(manages[1].len(), 0); assert_eq!(staff[2].name, "Alice"); assert_eq!(reports_to.get(2).unwrap().as_ref().unwrap().name, "Alan"); assert_eq!(manages[2].len(), 0); assert_eq!(staff[3].name, "Elle"); assert_eq!(reports_to[3], None); assert_eq!(manages[3].len(), 0); // self_ref + via let alice = user::ActiveModel { name: Set("Alice".into()), email: Set("@1".into()), ..Default::default() } .insert(db) .await?; let bob = user::ActiveModel { name: Set("Bob".into()), email: Set("@2".into()), ..Default::default() } .insert(db) .await?; let sam = user::ActiveModel { name: Set("Sam".into()), email: Set("@3".into()), ..Default::default() } .insert(db) .await?; user_follower::ActiveModel { user_id: Set(alice.id), follower_id: Set(bob.id), } .insert(db) .await?; user_follower::ActiveModel { user_id: Set(alice.id), follower_id: Set(sam.id), } .insert(db) .await?; user_follower::ActiveModel { user_id: Set(bob.id), follower_id: Set(sam.id), } .insert(db) .await?; let users = user::Entity::find().all(db).await?; let followers = users.load_self_via(user_follower::Entity, db).await?; let following = users.load_self_via_rev(user_follower::Entity, db).await?; assert_eq!(users[0], alice); assert_eq!(followers[0], [bob.clone(), sam.clone()]); assert!(following[0].is_empty()); assert_eq!(users[1], bob); assert_eq!(followers[1], [sam.clone()]); assert_eq!(following[1], [alice.clone()]); assert_eq!(users[2], sam); assert!(followers[2].is_empty()); assert_eq!(following[2], [alice, bob]); Ok(()) } pub async fn insert_bakery(db: &DbConn, name: &str) -> Result { bakery::ActiveModel { name: Set(name.to_owned()), profit_margin: Set(1.0), ..Default::default() } .insert(db) .await } pub async fn insert_baker(db: &DbConn, name: &str, bakery_id: i32) -> Result { baker::ActiveModel { name: Set(name.to_owned()), contact_details: Set(serde_json::json!({})), bakery_id: Set(Some(bakery_id)), ..Default::default() } .insert(db) .await } pub async fn insert_cake( db: &DbConn, name: &str, bakery_id: Option, ) -> Result { cake::ActiveModel { name: Set(name.to_owned()), price: Set(rust_decimal::Decimal::ONE), gluten_free: Set(false), bakery_id: Set(bakery_id), ..Default::default() } .insert(db) .await } pub async fn insert_cake_baker( db: &DbConn, baker_id: i32, cake_id: i32, ) -> Result { cakes_bakers::ActiveModel { cake_id: Set(cake_id), baker_id: Set(baker_id), } .insert(db) .await }