From f87f6a8e711f493dadfa49140ab8064e5e735bb6 Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Fri, 18 Nov 2022 16:08:53 +0200 Subject: [PATCH 01/16] WIP add loader skeleton --- src/entity/loader.rs | 161 +++++++++++++++++++++++++++++++++++++++++++ src/entity/mod.rs | 2 + 2 files changed, 163 insertions(+) create mode 100644 src/entity/loader.rs diff --git a/src/entity/loader.rs b/src/entity/loader.rs new file mode 100644 index 000000000..e9af2cf97 --- /dev/null +++ b/src/entity/loader.rs @@ -0,0 +1,161 @@ +use crate::{DbErr, EntityTrait, ModelTrait, QueryFilter, Select, Related, RelationType, Identity, Condition, Value, ColumnTrait, ConnectionTrait}; +use std::{fmt::Debug, str::FromStr, collections::BTreeMap}; + +#[async_trait::async_trait] +pub trait LoaderTrait { + type Model: ModelTrait; + + async fn load_one(&self, 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 fn load_many(&self, 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, 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())) + } + } + + 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()).unwrap(); + vec![model.get(column_a)] + }, + Identity::Binary(a, b) => { + let column_a = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); + let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); + 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()).unwrap(); + let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); + let column_c = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&c.to_string()).unwrap(); + vec![model.get(column_a), model.get(column_b), model.get(column_c)] + }, + } + } + + let keys: Vec> = self + .iter() + .map(|model: &M| { + extract_key(&rel_def.from_col, model) + }) + .collect(); + + let condition = match &rel_def.to_col { + Identity::Unary(a) => { + let column_a: ::Column = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); + Condition::all().add(ColumnTrait::is_in( + &column_a, + keys.iter().map(|key| key[0].clone()).collect::>(), + )) + } + Identity::Binary(a, b) => { + let column_a: <<::Model as ModelTrait>::Entity as EntityTrait>::Column = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); + let column_b: <<::Model as ModelTrait>::Entity as EntityTrait>::Column = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); + // TODO + // Condition::all().add( + // sea_query::Expr::tuple([column_a.to_string(), column_b]).is_in(keys.iter().map(|key| (key[0].clone(), key[1].clone())).collect::>()) + // ) + // TODO + Condition::all().add(ColumnTrait::is_in( + &column_a, + keys.iter().map(|key| key[0].clone()).collect::>(), + )) + } + Identity::Ternary(a, b, c) => { + let column_a = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); + let column_b = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); + let column_c = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&c.to_string()).unwrap(); + // TODO + Condition::all().add(ColumnTrait::is_in( + &column_a, + keys.iter().map(|key| key[0].clone()).collect::>(), + )) + } + }; + + let stmt = ::find(); + + 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, 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, + { + // we should verify this is a has_many relation + Ok(vec![]) + } +} diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 6b413bdf1..103871430 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -106,6 +106,7 @@ mod model; pub mod prelude; mod primary_key; mod relation; +mod loader; pub use active_enum::*; pub use active_model::*; @@ -117,3 +118,4 @@ pub use model::*; // pub use prelude::*; pub use primary_key::*; pub use relation::*; +pub use loader::*; From 9dfef65a48f82ac273f249ea559c2104127b1197 Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Mon, 21 Nov 2022 13:54:39 +0200 Subject: [PATCH 02/16] Add loader load_many * modify signature to accept statement outside * add simple test --- src/entity/loader.rs | 293 ++++++++++++++++++++++++++++++++----------- 1 file changed, 217 insertions(+), 76 deletions(-) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index e9af2cf97..b1c11084e 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -1,11 +1,23 @@ -use crate::{DbErr, EntityTrait, ModelTrait, QueryFilter, Select, Related, RelationType, Identity, Condition, Value, ColumnTrait, ConnectionTrait}; -use std::{fmt::Debug, str::FromStr, collections::BTreeMap}; +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}; -#[async_trait::async_trait] +/// A trait for basic Dataloader +#[async_trait] pub trait LoaderTrait { + /// Source model type Model: ModelTrait; - async fn load_one(&self, db: &C) -> Result>, DbErr> + /// Used to eager load has_one relations + /// + /// + /// + /// + async fn load_one(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, @@ -14,7 +26,36 @@ pub trait LoaderTrait { <::Model as ModelTrait>::Entity: Related, <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::Err: Debug; - async fn load_many(&self, db: &C) -> Result>, DbErr> + /// Used to eager load has_many relations + /// + /// # Example + /// + /// ``` + /// use sea_orm::{tests_cfg::*, entity::loader::*}; + /// + /// let db = MockDatabase::new(DbBackend::Postgres) + /// .append_query_results(vec![ + /// vec![cake::Model { + /// id: 1, + /// name: "New York Cheese".to_owned(), + /// } + /// .into_mock_row()], + /// 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(), }]; + /// + /// let fruits = cakes.load_many(fruit::Entity::find(), &db); + /// + /// assert_eq!(fruits, vec![fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), }]); + /// ``` + async fn load_many(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, @@ -32,7 +73,7 @@ where { type Model = M; - async fn load_one(&self, db: &C) -> Result>, DbErr> + async fn load_one(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, @@ -41,8 +82,7 @@ where <::Model as ModelTrait>::Entity: Related, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, { - let rel_def = - <<::Model as ModelTrait>::Entity as Related>::to(); + let rel_def = <<::Model as ModelTrait>::Entity as Related>::to(); // we verify that is has_one relation match (&rel_def).rel_type { @@ -52,79 +92,21 @@ where } } - 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()).unwrap(); - vec![model.get(column_a)] - }, - Identity::Binary(a, b) => { - let column_a = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); - let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); - 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()).unwrap(); - let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); - let column_c = <<::Entity as EntityTrait>::Column as FromStr>::from_str(&c.to_string()).unwrap(); - vec![model.get(column_a), model.get(column_b), model.get(column_c)] - }, - } - } - let keys: Vec> = self .iter() - .map(|model: &M| { - extract_key(&rel_def.from_col, model) - }) + .map(|model: &M| extract_key(&rel_def.from_col, model)) .collect(); - let condition = match &rel_def.to_col { - Identity::Unary(a) => { - let column_a: ::Column = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); - Condition::all().add(ColumnTrait::is_in( - &column_a, - keys.iter().map(|key| key[0].clone()).collect::>(), - )) - } - Identity::Binary(a, b) => { - let column_a: <<::Model as ModelTrait>::Entity as EntityTrait>::Column = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); - let column_b: <<::Model as ModelTrait>::Entity as EntityTrait>::Column = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); - // TODO - // Condition::all().add( - // sea_query::Expr::tuple([column_a.to_string(), column_b]).is_in(keys.iter().map(|key| (key[0].clone(), key[1].clone())).collect::>()) - // ) - // TODO - Condition::all().add(ColumnTrait::is_in( - &column_a, - keys.iter().map(|key| key[0].clone()).collect::>(), - )) - } - Identity::Ternary(a, b, c) => { - let column_a = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&a.to_string()).unwrap(); - let column_b = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&b.to_string()).unwrap(); - let column_c = <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::from_str(&c.to_string()).unwrap(); - // TODO - Condition::all().add(ColumnTrait::is_in( - &column_a, - keys.iter().map(|key| key[0].clone()).collect::>(), - )) - } - }; - - let stmt = ::find(); + let condition = prepare_condition::(&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 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); @@ -132,7 +114,8 @@ where } acc - }); + }, + ); let result: Vec::Model>> = keys .iter() @@ -146,7 +129,7 @@ where Ok(result) } - async fn load_many(&self, db: &C) -> Result>, DbErr> + async fn load_many(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, R: EntityTrait, @@ -155,7 +138,165 @@ where <::Model as ModelTrait>::Entity: Related, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, { - // we should verify this is a has_many relation - Ok(vec![]) + 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::(&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)).unwrap(); + + vec.push(value); + }); + + let result: Vec> = keys + .iter() + .map(|key: &Vec| hashmap.remove(&format!("{:?}", key)).to_owned().unwrap()) + .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(), + ) + .unwrap(); + vec![model.get(column_a)] + } + Identity::Binary(a, b) => { + let column_a = + <<::Entity as EntityTrait>::Column as FromStr>::from_str( + &a.to_string(), + ) + .unwrap(); + let column_b = + <<::Entity as EntityTrait>::Column as FromStr>::from_str( + &b.to_string(), + ) + .unwrap(); + 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(), + ) + .unwrap(); + let column_b = + <<::Entity as EntityTrait>::Column as FromStr>::from_str( + &b.to_string(), + ) + .unwrap(); + let column_c = + <<::Entity as EntityTrait>::Column as FromStr>::from_str( + &c.to_string(), + ) + .unwrap(); + 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()) + .unwrap(); + 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()) + .unwrap(); + let column_b: ::Column = + <::Column as FromStr>::from_str(&column_b.to_string()) + .unwrap(); + 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()) + .unwrap(); + let column_b: ::Column = + <::Column as FromStr>::from_str(&column_b.to_string()) + .unwrap(); + let column_c: ::Column = + <::Column as FromStr>::from_str(&column_c.to_string()) + .unwrap(); + 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::>(), + ), + ) + } } } From 94650bf5c6f29a5d646fa69aacaaa8aa93b64cc5 Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Mon, 21 Nov 2022 14:23:36 +0200 Subject: [PATCH 03/16] Fix example --- src/entity/loader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index b1c11084e..ede55008f 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -53,7 +53,7 @@ pub trait LoaderTrait { /// /// let fruits = cakes.load_many(fruit::Entity::find(), &db); /// - /// assert_eq!(fruits, vec![fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), }]); + /// assert_eq!(fruits, vec![[fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), }]]); /// ``` async fn load_many(&self, stmt: Select, db: &C) -> Result>, DbErr> where From f7f90cd7c5f37b8491d550f108541813df5915fd Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Tue, 22 Nov 2022 10:38:54 +0200 Subject: [PATCH 04/16] Add load_many unit test --- src/entity/loader.rs | 121 ++++++++++++++++++++++++++---------------- src/entity/prelude.rs | 5 +- 2 files changed, 78 insertions(+), 48 deletions(-) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index ede55008f..3946e3bb5 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -12,10 +12,8 @@ pub trait LoaderTrait { /// Source model type Model: ModelTrait; - /// Used to eager load has_one relations - /// - /// /// + /// Used to eager load has_one relations /// async fn load_one(&self, stmt: Select, db: &C) -> Result>, DbErr> where @@ -26,35 +24,9 @@ pub trait LoaderTrait { <::Model as ModelTrait>::Entity: Related, <<<::Model as ModelTrait>::Entity as EntityTrait>::Column as FromStr>::Err: Debug; - /// Used to eager load has_many relations - /// - /// # Example - /// - /// ``` - /// use sea_orm::{tests_cfg::*, entity::loader::*}; /// - /// let db = MockDatabase::new(DbBackend::Postgres) - /// .append_query_results(vec![ - /// vec![cake::Model { - /// id: 1, - /// name: "New York Cheese".to_owned(), - /// } - /// .into_mock_row()], - /// vec![fruit::Model { - /// id: 1, - /// name: "Apple".to_owned(), - /// cake_id: Some(1), - /// } - /// .into_mock_row()], - /// ]) - /// .into_connection(); + /// Used to eager load has_many relations /// - /// let cakes = vec![cake::Model { id: 1, name: "New York Cheese".to_owned(), }]; - /// - /// let fruits = cakes.load_many(fruit::Entity::find(), &db); - /// - /// assert_eq!(fruits, vec![[fruit::Model { id: 1, name: "Apple".to_owned(), cake_id: Some(1), }]]); - /// ``` async fn load_many(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, @@ -97,7 +69,7 @@ where .map(|model: &M| extract_key(&rel_def.from_col, model)) .collect(); - let condition = prepare_condition::(&rel_def.to_col, &keys); + let condition = prepare_condition::<::Model>(&rel_def.to_col, &keys); let stmt = as QueryFilter>::filter(stmt, condition); @@ -153,7 +125,7 @@ where .map(|model: &M| extract_key(&rel_def.from_col, model)) .collect(); - let condition = prepare_condition::(&rel_def.to_col, &keys); + let condition = prepare_condition::<::Model>(&rel_def.to_col, &keys); let stmt = as QueryFilter>::filter(stmt, condition); @@ -171,14 +143,21 @@ where .for_each(|value: ::Model| { let key = extract_key(&rel_def.to_col, &value); - let vec = hashmap.get_mut(&format!("{:?}", key)).unwrap(); + 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().unwrap()) + .map(|key: &Vec| { + hashmap + .remove(&format!("{:?}", key)) + .to_owned() + .expect("Failed to convert key to owned") + }) .collect(); Ok(result) @@ -196,7 +175,7 @@ where <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) - .unwrap(); + .expect("Failed at mapping string to column A:1"); vec![model.get(column_a)] } Identity::Binary(a, b) => { @@ -204,12 +183,12 @@ where <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) - .unwrap(); + .expect("Failed at mapping string to column A:2"); let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &b.to_string(), ) - .unwrap(); + .expect("Failed at mapping string to column B:2"); vec![model.get(column_a), model.get(column_b)] } Identity::Ternary(a, b, c) => { @@ -217,17 +196,17 @@ where <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) - .unwrap(); + .expect("Failed at mapping string to column A:3"); let column_b = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &b.to_string(), ) - .unwrap(); + .expect("Failed at mapping string to column B:3"); let column_c = <<::Entity as EntityTrait>::Column as FromStr>::from_str( &c.to_string(), ) - .unwrap(); + .expect("Failed at mapping string to column C:3"); vec![ model.get(column_a), model.get(column_b), @@ -246,7 +225,7 @@ where Identity::Unary(column_a) => { let column_a: ::Column = <::Column as FromStr>::from_str(&column_a.to_string()) - .unwrap(); + .expect("Failed at mapping string to column *A:1"); Condition::all().add(ColumnTrait::is_in( &column_a, keys.iter() @@ -257,10 +236,10 @@ where Identity::Binary(column_a, column_b) => { let column_a: ::Column = <::Column as FromStr>::from_str(&column_a.to_string()) - .unwrap(); + .expect("Failed at mapping string to column *A:2"); let column_b: ::Column = <::Column as FromStr>::from_str(&column_b.to_string()) - .unwrap(); + .expect("Failed at mapping string to column *B:2"); Condition::all().add( Expr::tuple([ SimpleExpr::Column(column_a.into_column_ref()), @@ -276,13 +255,13 @@ where Identity::Ternary(column_a, column_b, column_c) => { let column_a: ::Column = <::Column as FromStr>::from_str(&column_a.to_string()) - .unwrap(); + .expect("Failed at mapping string to column *A:3"); let column_b: ::Column = <::Column as FromStr>::from_str(&column_b.to_string()) - .unwrap(); + .expect("Failed at mapping string to column *B:3"); let column_c: ::Column = <::Column as FromStr>::from_str(&column_c.to_string()) - .unwrap(); + .expect("Failed at mapping string to column *C:3"); Condition::all().add( Expr::tuple([ SimpleExpr::Column(column_a.into_column_ref()), @@ -300,3 +279,53 @@ where } } } + +#[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![] + ] + ); + } +} diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 1d68cba14..d724551e1 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -1,8 +1,9 @@ pub use crate::{ error::*, ActiveEnum, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, CursorTrait, DatabaseConnection, DbConn, EntityName, EntityTrait, EnumIter, - ForeignKeyAction, Iden, IdenStatic, Linked, ModelTrait, PaginatorTrait, PrimaryKeyToColumn, - PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, RelationTrait, Select, Value, + ForeignKeyAction, Iden, IdenStatic, Linked, LoaderTrait, ModelTrait, PaginatorTrait, + PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, + RelationTrait, Select, Value, }; #[cfg(feature = "macros")] From 2bba146e9bb31052d8d9ac37e5050cf1b56725db Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Tue, 22 Nov 2022 12:24:18 +0200 Subject: [PATCH 05/16] Add load_one tests --- src/entity/loader.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index 3946e3bb5..3b325e7a0 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -284,6 +284,54 @@ where mod tests { #[tokio::test] + async fn test_load_one() { + use crate::{ + entity::prelude::*, tests_cfg::*, DbBackend, IntoMockRow, LoaderTrait, MockDatabase, + }; + + let db = MockDatabase::new(DbBackend::Postgres) + .append_query_results(vec![ + vec![ + cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }.into_mock_row(), + cake::Model { + id: 2, + name: "London Cheese".to_owned(), + }.into_mock_row() + ], + ]) + .into_connection(); + + let fruits = vec![ + fruit::Model { + id: 1, + name: "Apple".to_owned(), + cake_id: Some(1), + } + ]; + + let cakes = fruits + .load_one(cake::Entity::find(), &db) + .await + .expect("Should return something"); + + assert_eq!( + cakes, + vec![ + Some( + cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + } + ) + ] + ); + } + + #[tokio::test] + async fn test_load_many() { use crate::{ entity::prelude::*, tests_cfg::*, DbBackend, IntoMockRow, LoaderTrait, MockDatabase, From 4323a0cb0707eadb1fd329946d7dcb3b8da1dd85 Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Tue, 22 Nov 2022 13:46:22 +0200 Subject: [PATCH 06/16] Add integration tests --- tests/loader_tests.rs | 156 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 tests/loader_tests.rs diff --git a/tests/loader_tests.rs b/tests/loader_tests.rs new file mode 100644 index 000000000..6d446f792 --- /dev/null +++ b/tests/loader_tests.rs @@ -0,0 +1,156 @@ +pub mod common; + +pub use common::{bakery_chain::*, setup::*, TestContext}; +pub use sea_orm::{entity::*, query::*, DbErr, FromQueryResult}; + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn loader_load_one() -> Result<(), DbErr> { + let ctx = TestContext::new("loader_test_load_one").await; + create_tables(&ctx.db).await?; + + let bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert bakery"); + + let baker_1 = baker::ActiveModel { + name: Set("Baker 1".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+61424000000", + "home": "0395555555", + "address": "12 Test St, Testville, Vic, Australia" + })), + bakery_id: Set(Some(bakery.id)), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let baker_2 = baker::ActiveModel { + name: Set("Baker 2".to_owned()), + contact_details: Set(serde_json::json!({})), + bakery_id: Set(None), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let bakers = baker::Entity::find() + .all(&ctx.db) + .await + .expect("Should load bakers"); + + let bakeries = bakers + .load_one(bakery::Entity::find(), &ctx.db) + .await + .expect("Should load bakeries"); + + assert_eq!(bakers, vec![baker_1, baker_2]); + + assert_eq!(bakeries, vec![Some(bakery), None]); + + Ok(()) +} + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn loader_load_many() -> Result<(), DbErr> { + let ctx = TestContext::new("loader_test_load_many").await; + create_tables(&ctx.db).await?; + + let bakery_1 = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert bakery"); + + let bakery_2 = bakery::ActiveModel { + name: Set("Offshore Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert bakery"); + + let baker_1 = baker::ActiveModel { + name: Set("Baker 1".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+61424000000", + "home": "0395555555", + "address": "12 Test St, Testville, Vic, Australia" + })), + bakery_id: Set(Some(bakery_1.id)), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let baker_2 = baker::ActiveModel { + name: Set("Baker 2".to_owned()), + contact_details: Set(serde_json::json!({})), + bakery_id: Set(Some(bakery_1.id)), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let _baker_3 = baker::ActiveModel { + name: Set("John".to_owned()), + contact_details: Set(serde_json::json!({})), + bakery_id: Set(Some(bakery_1.id)), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let _baker_4 = baker::ActiveModel { + name: Set("Baker 4".to_owned()), + contact_details: Set(serde_json::json!({})), + bakery_id: Set(None), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let bakeries = bakery::Entity::find() + .all(&ctx.db) + .await + .expect("Should load bakeries"); + + let bakers = bakeries + .load_many(baker::Entity::find().filter(baker::Column::Name.like("Baker%")), &ctx.db) + .await + .expect("Should load bakers"); + + println!("A: {:?}", bakers); + println!("B: {:?}", bakeries); + + assert_eq!(bakeries, vec![bakery_1, bakery_2]); + + assert_eq!(bakers, vec![vec![baker_1, baker_2], vec![]]); + + Ok(()) +} From 6c7a162f055e50131c10beea1daddd22679ff98f Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 23 Nov 2022 22:47:41 +0800 Subject: [PATCH 07/16] cargo fmt --- src/entity/loader.rs | 62 +++++++++++++++++++------------------------ src/entity/mod.rs | 4 +-- tests/loader_tests.rs | 5 +++- 3 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index 3b325e7a0..4052ef22b 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -290,27 +290,25 @@ mod tests { }; let db = MockDatabase::new(DbBackend::Postgres) - .append_query_results(vec![ - vec![ - cake::Model { - id: 1, - name: "New York Cheese".to_owned(), - }.into_mock_row(), - cake::Model { - id: 2, - name: "London Cheese".to_owned(), - }.into_mock_row() - ], - ]) + .append_query_results(vec![vec![ + cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + } + .into_mock_row(), + cake::Model { + id: 2, + name: "London Cheese".to_owned(), + } + .into_mock_row(), + ]]) .into_connection(); - let fruits = vec![ - fruit::Model { - id: 1, - name: "Apple".to_owned(), - cake_id: Some(1), - } - ]; + let fruits = vec![fruit::Model { + id: 1, + name: "Apple".to_owned(), + cake_id: Some(1), + }]; let cakes = fruits .load_one(cake::Entity::find(), &db) @@ -319,14 +317,10 @@ mod tests { assert_eq!( cakes, - vec![ - Some( - cake::Model { - id: 1, - name: "New York Cheese".to_owned(), - } - ) - ] + vec![Some(cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + })] ); } @@ -338,14 +332,12 @@ mod tests { }; 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()], - ]) + .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![ diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 103871430..c58052476 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -101,12 +101,12 @@ mod base_entity; mod column; mod identity; mod link; +mod loader; mod model; /// Re-export common types from the entity pub mod prelude; mod primary_key; mod relation; -mod loader; pub use active_enum::*; pub use active_model::*; @@ -116,6 +116,6 @@ pub use identity::*; pub use link::*; pub use model::*; // pub use prelude::*; +pub use loader::*; pub use primary_key::*; pub use relation::*; -pub use loader::*; diff --git a/tests/loader_tests.rs b/tests/loader_tests.rs index 6d446f792..f66f44aa1 100644 --- a/tests/loader_tests.rs +++ b/tests/loader_tests.rs @@ -141,7 +141,10 @@ async fn loader_load_many() -> Result<(), DbErr> { .expect("Should load bakeries"); let bakers = bakeries - .load_many(baker::Entity::find().filter(baker::Column::Name.like("Baker%")), &ctx.db) + .load_many( + baker::Entity::find().filter(baker::Column::Name.like("Baker%")), + &ctx.db, + ) .await .expect("Should load bakers"); From 0abe9c96577953a747e5f69e94099dc810602d52 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 23 Nov 2022 23:12:18 +0800 Subject: [PATCH 08/16] Use ValueTuple to replace Vec --- src/entity/loader.rs | 51 +++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index 4052ef22b..bfd1f29d7 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -1,6 +1,6 @@ use crate::{ ColumnTrait, Condition, ConnectionTrait, DbErr, EntityTrait, Identity, ModelTrait, QueryFilter, - Related, RelationType, Select, Value, + Related, RelationType, Select, }; use async_trait::async_trait; use sea_query::{Expr, IntoColumnRef, SimpleExpr, ValueTuple}; @@ -57,14 +57,14 @@ where let rel_def = <<::Model as ModelTrait>::Entity as Related>::to(); // we verify that is has_one relation - match (&rel_def).rel_type { + match (rel_def).rel_type { RelationType::HasOne => (), RelationType::HasMany => { return Err(DbErr::Type("Relation is HasMany instead of HasOne".into())) } } - let keys: Vec> = self + let keys: Vec = self .iter() .map(|model: &M| extract_key(&rel_def.from_col, model)) .collect(); @@ -91,11 +91,7 @@ where let result: Vec::Model>> = keys .iter() - .map(|key| { - let model = hashmap.remove(&format!("{:?}", key)); - - model - }) + .map(|key| hashmap.remove(&format!("{:?}", key))) .collect(); Ok(result) @@ -113,14 +109,14 @@ where let rel_def = <<::Model as ModelTrait>::Entity as Related>::to(); // we verify that is has_many relation - match (&rel_def).rel_type { + match (rel_def).rel_type { RelationType::HasMany => (), RelationType::HasOne => { return Err(DbErr::Type("Relation is HasOne instead of HasMany".into())) } } - let keys: Vec> = self + let keys: Vec = self .iter() .map(|model: &M| extract_key(&rel_def.from_col, model)) .collect(); @@ -133,7 +129,7 @@ where let mut hashmap: BTreeMap::Model>> = keys.iter() - .fold(BTreeMap::new(), |mut acc, key: &Vec| { + .fold(BTreeMap::new(), |mut acc, key: &ValueTuple| { acc.insert(format!("{:?}", key), Vec::new()); acc @@ -152,10 +148,9 @@ where let result: Vec> = keys .iter() - .map(|key: &Vec| { + .map(|key: &ValueTuple| { hashmap .remove(&format!("{:?}", key)) - .to_owned() .expect("Failed to convert key to owned") }) .collect(); @@ -164,7 +159,7 @@ where } } -fn extract_key(target_col: &Identity, model: &Model) -> Vec +fn extract_key(target_col: &Identity, model: &Model) -> ValueTuple where Model: ModelTrait, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, @@ -176,7 +171,7 @@ where &a.to_string(), ) .expect("Failed at mapping string to column A:1"); - vec![model.get(column_a)] + ValueTuple::One(model.get(column_a)) } Identity::Binary(a, b) => { let column_a = @@ -189,7 +184,7 @@ where &b.to_string(), ) .expect("Failed at mapping string to column B:2"); - vec![model.get(column_a), model.get(column_b)] + ValueTuple::Two(model.get(column_a), model.get(column_b)) } Identity::Ternary(a, b, c) => { let column_a = @@ -207,16 +202,16 @@ where &c.to_string(), ) .expect("Failed at mapping string to column C:3"); - vec![ + ValueTuple::Three( model.get(column_a), model.get(column_b), model.get(column_c), - ] + ) } } } -fn prepare_condition(col: &Identity, keys: &Vec>) -> Condition +fn prepare_condition(col: &Identity, keys: &[ValueTuple]) -> Condition where M: ModelTrait, <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, @@ -228,9 +223,7 @@ where .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::>(), + keys.iter().cloned().flatten(), )) } Identity::Binary(column_a, column_b) => { @@ -245,11 +238,7 @@ where 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::>(), - ), + .in_tuples(keys.iter().cloned()), ) } Identity::Ternary(column_a, column_b, column_c) => { @@ -268,13 +257,7 @@ where 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::>(), - ), + .in_tuples(keys.iter().cloned()), ) } } From dcfba6f685d32a43cc7fea9955941bff7ed8116c Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 23 Nov 2022 23:31:28 +0800 Subject: [PATCH 09/16] Remove Debug trait bounds --- src/entity/loader.rs | 50 ++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index bfd1f29d7..f2b1bdd28 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -4,7 +4,7 @@ use crate::{ }; use async_trait::async_trait; use sea_query::{Expr, IntoColumnRef, SimpleExpr, ValueTuple}; -use std::{collections::BTreeMap, fmt::Debug, str::FromStr}; +use std::{collections::HashMap, str::FromStr}; /// A trait for basic Dataloader #[async_trait] @@ -20,9 +20,7 @@ pub trait LoaderTrait { 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; + <::Model as ModelTrait>::Entity: Related; /// /// Used to eager load has_many relations @@ -32,9 +30,7 @@ pub trait LoaderTrait { 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; + <::Model as ModelTrait>::Entity: Related; } #[async_trait::async_trait] @@ -50,9 +46,7 @@ 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(); @@ -75,9 +69,9 @@ where let data = stmt.all(db).await?; - let mut hashmap: BTreeMap::Model> = data.into_iter().fold( - BTreeMap::::Model>::new(), - |mut acc: BTreeMap::Model>, + let mut hashmap: HashMap::Model> = data.into_iter().fold( + HashMap::::Model>::new(), + |mut acc: HashMap::Model>, value: ::Model| { { let key = extract_key(&rel_def.to_col, &value); @@ -102,9 +96,7 @@ 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(); @@ -127,9 +119,9 @@ where let data = stmt.all(db).await?; - let mut hashmap: BTreeMap::Model>> = + let mut hashmap: HashMap::Model>> = keys.iter() - .fold(BTreeMap::new(), |mut acc, key: &ValueTuple| { + .fold(HashMap::new(), |mut acc, key: &ValueTuple| { acc.insert(format!("{:?}", key), Vec::new()); acc @@ -162,7 +154,6 @@ where fn extract_key(target_col: &Identity, model: &Model) -> ValueTuple where Model: ModelTrait, - <<::Entity as EntityTrait>::Column as FromStr>::Err: Debug, { match target_col { Identity::Unary(a) => { @@ -170,7 +161,7 @@ where <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) - .expect("Failed at mapping string to column A:1"); + .unwrap_or_else(|_| panic!("Failed at mapping string to column A:1")); ValueTuple::One(model.get(column_a)) } Identity::Binary(a, b) => { @@ -178,12 +169,12 @@ where <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) - .expect("Failed at mapping string to column A:2"); + .unwrap_or_else(|_| panic!("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"); + .unwrap_or_else(|_| panic!("Failed at mapping string to column B:2")); ValueTuple::Two(model.get(column_a), model.get(column_b)) } Identity::Ternary(a, b, c) => { @@ -191,17 +182,17 @@ where <<::Entity as EntityTrait>::Column as FromStr>::from_str( &a.to_string(), ) - .expect("Failed at mapping string to column A:3"); + .unwrap_or_else(|_| panic!("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"); + .unwrap_or_else(|_| panic!("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"); + .unwrap_or_else(|_| panic!("Failed at mapping string to column C:3")); ValueTuple::Three( model.get(column_a), model.get(column_b), @@ -214,13 +205,12 @@ where fn prepare_condition(col: &Identity, keys: &[ValueTuple]) -> 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"); + .unwrap_or_else(|_| panic!("Failed at mapping string to column *A:1")); Condition::all().add(ColumnTrait::is_in( &column_a, keys.iter().cloned().flatten(), @@ -229,10 +219,10 @@ where 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"); + .unwrap_or_else(|_| panic!("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"); + .unwrap_or_else(|_| panic!("Failed at mapping string to column *B:2")); Condition::all().add( Expr::tuple([ SimpleExpr::Column(column_a.into_column_ref()), @@ -244,13 +234,13 @@ where 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"); + .unwrap_or_else(|_| panic!("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"); + .unwrap_or_else(|_| panic!("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"); + .unwrap_or_else(|_| panic!("Failed at mapping string to column *C:3")); Condition::all().add( Expr::tuple([ SimpleExpr::Column(column_a.into_column_ref()), From 45b391f4348e53f4f31df76aa88d9a876710e542 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 23 Nov 2022 23:47:14 +0800 Subject: [PATCH 10/16] Improve testcase --- tests/loader_tests.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/loader_tests.rs b/tests/loader_tests.rs index f66f44aa1..39bbdd834 100644 --- a/tests/loader_tests.rs +++ b/tests/loader_tests.rs @@ -115,20 +115,20 @@ async fn loader_load_many() -> Result<(), DbErr> { .await .expect("could not insert baker"); - let _baker_3 = baker::ActiveModel { + let baker_3 = baker::ActiveModel { name: Set("John".to_owned()), contact_details: Set(serde_json::json!({})), - bakery_id: Set(Some(bakery_1.id)), + bakery_id: Set(Some(bakery_2.id)), ..Default::default() } .insert(&ctx.db) .await .expect("could not insert baker"); - let _baker_4 = baker::ActiveModel { + let baker_4 = baker::ActiveModel { name: Set("Baker 4".to_owned()), contact_details: Set(serde_json::json!({})), - bakery_id: Set(None), + bakery_id: Set(Some(bakery_2.id)), ..Default::default() } .insert(&ctx.db) @@ -153,7 +153,20 @@ async fn loader_load_many() -> Result<(), DbErr> { assert_eq!(bakeries, vec![bakery_1, bakery_2]); - assert_eq!(bakers, vec![vec![baker_1, baker_2], vec![]]); + assert_eq!( + bakers, + vec![ + vec![baker_1.clone(), baker_2.clone()], + vec![baker_4.clone()] + ] + ); + + let bakers = bakeries + .load_many(baker::Entity::find(), &ctx.db) + .await + .expect("Should load bakers"); + + assert_eq!(bakers, vec![vec![baker_1, baker_2], vec![baker_3, baker_4]]); Ok(()) } From 319f64fe6435841822904be4c89b0ad68860537e Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 23 Nov 2022 23:55:24 +0800 Subject: [PATCH 11/16] cargo fmt --- src/entity/loader.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/entity/loader.rs b/src/entity/loader.rs index f2b1bdd28..0e0f6977f 100644 --- a/src/entity/loader.rs +++ b/src/entity/loader.rs @@ -12,9 +12,7 @@ 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, @@ -22,9 +20,7 @@ pub trait LoaderTrait { R::Model: Send + Sync, <::Model as ModelTrait>::Entity: Related; - /// /// Used to eager load has_many relations - /// async fn load_many(&self, stmt: Select, db: &C) -> Result>, DbErr> where C: ConnectionTrait, From 19b1da175df23fb0448613f4fb0329ca785090d6 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 24 Nov 2022 00:03:29 +0800 Subject: [PATCH 12/16] Move file --- src/entity/mod.rs | 2 -- src/{entity => query}/loader.rs | 0 src/query/mod.rs | 2 ++ 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{entity => query}/loader.rs (100%) diff --git a/src/entity/mod.rs b/src/entity/mod.rs index c58052476..6b413bdf1 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -101,7 +101,6 @@ mod base_entity; mod column; mod identity; mod link; -mod loader; mod model; /// Re-export common types from the entity pub mod prelude; @@ -116,6 +115,5 @@ pub use identity::*; pub use link::*; pub use model::*; // pub use prelude::*; -pub use loader::*; pub use primary_key::*; pub use relation::*; diff --git a/src/entity/loader.rs b/src/query/loader.rs similarity index 100% rename from src/entity/loader.rs rename to src/query/loader.rs diff --git a/src/query/mod.rs b/src/query/mod.rs index 2de0e7908..559eba176 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -5,6 +5,7 @@ mod insert; mod join; #[cfg(feature = "with-json")] mod json; +mod loader; mod select; mod traits; mod update; @@ -17,6 +18,7 @@ pub use insert::*; pub use join::*; #[cfg(feature = "with-json")] pub use json::*; +pub use loader::*; pub use select::*; pub use traits::*; pub use update::*; From 085091c7df44106a1f8fc7778313185c7faf9fb6 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 24 Nov 2022 16:24:49 +0800 Subject: [PATCH 13/16] Apply suggestions from code review --- src/query/loader.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/query/loader.rs b/src/query/loader.rs index 0e0f6977f..61537a144 100644 --- a/src/query/loader.rs +++ b/src/query/loader.rs @@ -252,7 +252,6 @@ where #[cfg(test)] mod tests { #[tokio::test] - async fn test_load_one() { use crate::{ entity::prelude::*, tests_cfg::*, DbBackend, IntoMockRow, LoaderTrait, MockDatabase, @@ -294,7 +293,6 @@ mod tests { } #[tokio::test] - async fn test_load_many() { use crate::{ entity::prelude::*, tests_cfg::*, DbBackend, IntoMockRow, LoaderTrait, MockDatabase, From f3910c329b7e0d45e72842f08ab5525218a549ca Mon Sep 17 00:00:00 2001 From: Panagiotis Karatakis Date: Fri, 25 Nov 2022 12:01:58 +0200 Subject: [PATCH 14/16] Fix shared related item --- src/query/loader.rs | 13 +++++++--- tests/loader_tests.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/query/loader.rs b/src/query/loader.rs index 61537a144..3f7414d80 100644 --- a/src/query/loader.rs +++ b/src/query/loader.rs @@ -65,7 +65,7 @@ where let data = stmt.all(db).await?; - let mut hashmap: HashMap::Model> = data.into_iter().fold( + let hashmap: HashMap::Model> = data.into_iter().fold( HashMap::::Model>::new(), |mut acc: HashMap::Model>, value: ::Model| { @@ -81,7 +81,11 @@ where let result: Vec::Model>> = keys .iter() - .map(|key| hashmap.remove(&format!("{:?}", key))) + .map(|key| { + hashmap + .get(&format!("{:?}", key)) + .and_then(|val| Some(val.clone())) + }) .collect(); Ok(result) @@ -138,8 +142,9 @@ where .iter() .map(|key: &ValueTuple| { hashmap - .remove(&format!("{:?}", key)) - .expect("Failed to convert key to owned") + .get(&format!("{:?}", key)) + .and_then(|val| Some(val.clone())) + .unwrap_or_else(|| vec![]) }) .collect(); diff --git a/tests/loader_tests.rs b/tests/loader_tests.rs index 39bbdd834..3d38922a2 100644 --- a/tests/loader_tests.rs +++ b/tests/loader_tests.rs @@ -63,6 +63,66 @@ async fn loader_load_one() -> Result<(), DbErr> { Ok(()) } +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn loader_load_one_complex() -> Result<(), DbErr> { + let ctx = TestContext::new("loader_test_load_one").await; + create_tables(&ctx.db).await?; + + let bakery = bakery::ActiveModel { + name: Set("SeaSide Bakery".to_owned()), + profit_margin: Set(10.4), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert bakery"); + + let baker_1 = baker::ActiveModel { + name: Set("Baker 1".to_owned()), + contact_details: Set(serde_json::json!({ + "mobile": "+61424000000", + "home": "0395555555", + "address": "12 Test St, Testville, Vic, Australia" + })), + bakery_id: Set(Some(bakery.id)), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let baker_2 = baker::ActiveModel { + name: Set("Baker 2".to_owned()), + contact_details: Set(serde_json::json!({})), + bakery_id: Set(Some(bakery.id)), + ..Default::default() + } + .insert(&ctx.db) + .await + .expect("could not insert baker"); + + let bakers = baker::Entity::find() + .all(&ctx.db) + .await + .expect("Should load bakers"); + + let bakeries = bakers + .load_one(bakery::Entity::find(), &ctx.db) + .await + .expect("Should load bakeries"); + + assert_eq!(bakers, vec![baker_1, baker_2]); + + assert_eq!(bakeries, vec![Some(bakery.clone()), Some(bakery.clone())]); + + Ok(()) +} + #[sea_orm_macros::test] #[cfg(any( feature = "sqlx-mysql", From 1b4e1670f7ca61f9e522d5638bc2979c12668584 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 28 Nov 2022 13:04:25 +0800 Subject: [PATCH 15/16] clippy --- src/query/loader.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/query/loader.rs b/src/query/loader.rs index 3f7414d80..14c11b72a 100644 --- a/src/query/loader.rs +++ b/src/query/loader.rs @@ -81,11 +81,7 @@ where let result: Vec::Model>> = keys .iter() - .map(|key| { - hashmap - .get(&format!("{:?}", key)) - .and_then(|val| Some(val.clone())) - }) + .map(|key| hashmap.get(&format!("{:?}", key)).cloned()) .collect(); Ok(result) @@ -143,8 +139,8 @@ where .map(|key: &ValueTuple| { hashmap .get(&format!("{:?}", key)) - .and_then(|val| Some(val.clone())) - .unwrap_or_else(|| vec![]) + .cloned() + .unwrap_or_default() }) .collect(); From 7f96418fa3f107ff0c821476165eeee57376a3f2 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Wed, 28 Dec 2022 15:57:40 +0800 Subject: [PATCH 16/16] Small tweak --- src/query/loader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/loader.rs b/src/query/loader.rs index 14c11b72a..68ac1d4a4 100644 --- a/src/query/loader.rs +++ b/src/query/loader.rs @@ -29,7 +29,7 @@ pub trait LoaderTrait { <::Model as ModelTrait>::Entity: Related; } -#[async_trait::async_trait] +#[async_trait] impl LoaderTrait for Vec where M: ModelTrait,