use crate::{ ColumnTrait, Condition, ConnectionTrait, DbErr, EntityTrait, Identity, ModelTrait, QueryFilter, Related, RelationType, Select, Value, }; use async_trait::async_trait; use sea_query::{Expr, IntoColumnRef, SimpleExpr, ValueTuple}; use std::{collections::BTreeMap, fmt::Debug, str::FromStr}; /// A trait for basic Dataloader #[async_trait] pub trait LoaderTrait { /// Source model type Model: ModelTrait; /// /// Used to eager load has_one relations /// async fn load_one(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, R::Model: Send + Sync, <::Column as FromStr>::Err: Debug, <::Model as ModelTrait>::Entity: Related, <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::Err: Debug; /// /// Used to eager load has_many relations /// async fn load_many(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, R::Model: Send + Sync, <::Column as FromStr>::Err: Debug, <::Model as ModelTrait>::Entity: Related, <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::Err: Debug; } #[async_trait::async_trait] impl LoaderTrait for Vec where M: ModelTrait, Vec: Sync, { type Model = M; async fn load_one(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, R::Model: Send + Sync, <::Column as FromStr>::Err: Debug, <::Model as ModelTrait>::Entity: Related, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, { let rel_def = <<::Model as ModelTrait>::Entity as Related>::to(); // we verify that is has_one relation match (&rel_def).rel_type { RelationType::HasOne => (), RelationType::HasMany => { return Err(DbErr::Type("Relation is HasMany instead of HasOne".into())) } } let keys: Vec> = self .iter() .map(|model: &M| extract_key(&rel_def.from_col, model)) .collect(); let condition = prepare_condition::<::Model>(&rel_def.to_col, &keys); let stmt = as QueryFilter>::filter(stmt, condition); let data = stmt.all(db).await?; let mut hashmap: BTreeMap::Model> = data.into_iter().fold( BTreeMap::::Model>::new(), |mut acc: BTreeMap::Model>, value: ::Model| { { let key = extract_key(&rel_def.to_col, &value); acc.insert(format!("{:?}", key), value); } acc }, ); let result: Vec::Model>> = keys .iter() .map(|key| { let model = hashmap.remove(&format!("{:?}", key)); model }) .collect(); Ok(result) } async fn load_many(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, R::Model: Send + Sync, <::Column as FromStr>::Err: Debug, <::Model as ModelTrait>::Entity: Related, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, { let rel_def = <<::Model as ModelTrait>::Entity as Related>::to(); // we verify that is has_many relation match (&rel_def).rel_type { RelationType::HasMany => (), RelationType::HasOne => { return Err(DbErr::Type("Relation is HasOne instead of HasMany".into())) } } let keys: Vec> = self .iter() .map(|model: &M| extract_key(&rel_def.from_col, model)) .collect(); let condition = prepare_condition::<::Model>(&rel_def.to_col, &keys); let stmt = as QueryFilter>::filter(stmt, condition); let data = stmt.all(db).await?; let mut hashmap: BTreeMap::Model>> = keys.iter() .fold(BTreeMap::new(), |mut acc, key: &Vec| { acc.insert(format!("{:?}", key), Vec::new()); acc }); data.into_iter() .for_each(|value: ::Model| { let key = extract_key(&rel_def.to_col, &value); let vec = hashmap .get_mut(&format!("{:?}", key)) .expect("Failed at finding key on hashmap"); vec.push(value); }); let result: Vec> = keys .iter() .map(|key: &Vec| { hashmap .remove(&format!("{:?}", key)) .to_owned() .expect("Failed to convert key to owned") }) .collect(); Ok(result) } } fn extract_key(target_col: &Identity, model: &Model) -> Vec where Model: ModelTrait, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, { match target_col { Identity::Unary(a) => { let column_a = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) .expect("Failed at mapping string to column A:1"); vec![model.get(column_a)] } Identity::Binary(a, b) => { let column_a = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) .expect("Failed at mapping string to column A:2"); let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &b.to_string(), ) .expect("Failed at mapping string to column B:2"); vec![model.get(column_a), model.get(column_b)] } Identity::Ternary(a, b, c) => { let column_a = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) .expect("Failed at mapping string to column A:3"); let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &b.to_string(), ) .expect("Failed at mapping string to column B:3"); let column_c = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &c.to_string(), ) .expect("Failed at mapping string to column C:3"); vec![ model.get(column_a), model.get(column_b), model.get(column_c), ] } } } fn prepare_condition(col: &Identity, keys: &Vec>) -> Condition where M: ModelTrait, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, { match col { Identity::Unary(column_a) => { let column_a: ::Column = <::Column as FromStr>::from_str(&column_a.to_string()) .expect("Failed at mapping string to column *A:1"); Condition::all().add(ColumnTrait::is_in( &column_a, keys.iter() .map(|key| key[0].clone()) .collect::>(), )) } Identity::Binary(column_a, column_b) => { let column_a: ::Column = <::Column as FromStr>::from_str(&column_a.to_string()) .expect("Failed at mapping string to column *A:2"); let column_b: ::Column = <::Column as FromStr>::from_str(&column_b.to_string()) .expect("Failed at mapping string to column *B:2"); Condition::all().add( Expr::tuple([ SimpleExpr::Column(column_a.into_column_ref()), SimpleExpr::Column(column_b.into_column_ref()), ]) .in_tuples( keys.iter() .map(|key| ValueTuple::Two(key[0].clone(), key[1].clone())) .collect::>(), ), ) } Identity::Ternary(column_a, column_b, column_c) => { let column_a: ::Column = <::Column as FromStr>::from_str(&column_a.to_string()) .expect("Failed at mapping string to column *A:3"); let column_b: ::Column = <::Column as FromStr>::from_str(&column_b.to_string()) .expect("Failed at mapping string to column *B:3"); let column_c: ::Column = <::Column as FromStr>::from_str(&column_c.to_string()) .expect("Failed at mapping string to column *C:3"); Condition::all().add( Expr::tuple([ SimpleExpr::Column(column_a.into_column_ref()), SimpleExpr::Column(column_b.into_column_ref()), SimpleExpr::Column(column_c.into_column_ref()), ]) .in_tuples( keys.iter() .map(|key| { ValueTuple::Three(key[0].clone(), key[1].clone(), key[2].clone()) }) .collect::>(), ), ) } } } #[cfg(test)] mod tests { #[tokio::test] async fn test_load_many() { use crate::{ entity::prelude::*, tests_cfg::*, DbBackend, IntoMockRow, LoaderTrait, MockDatabase, }; let db = MockDatabase::new(DbBackend::Postgres) .append_query_results(vec![ vec![fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), } .into_mock_row()], ]) .into_connection(); let cakes = vec![ cake::Model { id: 1, name: "New York Cheese".to_owned(), }, cake::Model { id: 2, name: "London Cheese".to_owned(), }, ]; let fruits = cakes .load_many(fruit::Entity::find(), &db) .await .expect("Should return something"); assert_eq!( fruits, vec![ vec![fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), }], vec![] ] ); } }