From f4d1a52791d1a62c134ea317fa30d005fec8b5a5 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 7 Sep 2021 11:11:23 +0800 Subject: [PATCH 1/5] Change update behavior, add ActiveModel insert & update --- README.md | 2 +- examples/async-std/src/operation.rs | 2 +- sea-orm-macros/src/derives/active_model.rs | 8 ++++++ src/entity/active_model.rs | 9 ++++--- src/entity/base_entity.rs | 2 +- src/lib.rs | 2 +- src/query/update.rs | 30 ++++++++++++++-------- tests/crud/updates.rs | 12 ++++----- 8 files changed, 43 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index ef69b5d1c..4dbf6110c 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); // update one -let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; +let pear: fruit::ActiveModel = pear.update(db).await?; // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%' Fruit::update_many() diff --git a/examples/async-std/src/operation.rs b/examples/async-std/src/operation.rs index 3fa5cc850..34e5441fa 100644 --- a/examples/async-std/src/operation.rs +++ b/examples/async-std/src/operation.rs @@ -33,7 +33,7 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), DbErr> { let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); - let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; + let pear: fruit::ActiveModel = pear.update(db).await?; println!(); println!("Updated: {:?}\n", pear); diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 98bc0aec4..bf04421b9 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -37,6 +37,14 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result Result { + sea_orm::insert_and_select_active_model::(self, db).await + } + + pub async fn update(self, db: &sea_orm::DatabaseConnection) -> Result { + sea_orm::update_active_model::(self, db).await + } + pub async fn save(self, db: &sea_orm::DatabaseConnection) -> Result { sea_orm::save_active_model::(self, db).await } diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index f823411cd..7eee81f33 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -212,7 +212,10 @@ where Ok(am) } -async fn insert_and_select_active_model(am: A, db: &DatabaseConnection) -> Result +pub async fn insert_and_select_active_model( + am: A, + db: &DatabaseConnection, +) -> Result where A: ActiveModelTrait, E::Model: IntoActiveModel, @@ -240,12 +243,12 @@ where } } -async fn update_active_model(am: A, db: &DatabaseConnection) -> Result +pub async fn update_active_model(am: A, db: &DatabaseConnection) -> Result where A: ActiveModelTrait, E: EntityTrait, { - let exec = E::update(am).exec(db); + let exec = E::update(am).prepare_filters().exec(db); exec.await } diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index e0f92b093..e09fa0a8e 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -386,7 +386,7 @@ pub trait EntityTrait: EntityName { /// # let _: Result<(), DbErr> = smol::block_on(async { /// # /// assert_eq!( - /// fruit::Entity::update(orange.clone()).exec(&db).await?, // Clone here because we need to assert_eq + /// orange.clone().update(&db).await?, // Clone here because we need to assert_eq /// orange /// ); /// # diff --git a/src/lib.rs b/src/lib.rs index c1caa60ea..44c1be644 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,7 +126,7 @@ //! pear.name = Set("Sweet pear".to_owned()); //! //! // update one -//! let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; +//! let pear: fruit::ActiveModel = pear.update(db).await?; //! //! // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%' //! Fruit::update_many() diff --git a/src/query/update.rs b/src/query/update.rs index 8cd66c37f..3500b7594 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -37,6 +37,7 @@ impl Update { /// id: ActiveValue::set(1), /// name: ActiveValue::set("Apple Pie".to_owned()), /// }) + /// .filter(cake::Column::Id.eq(1)) /// .build(DbBackend::Postgres) /// .to_string(), /// r#"UPDATE "cake" SET "name" = 'Apple Pie' WHERE "cake"."id" = 1"#, @@ -53,7 +54,7 @@ impl Update { .to_owned(), model, }; - myself.prepare() + myself.prepare_values() } /// Update many ActiveModel @@ -85,16 +86,7 @@ impl UpdateOne where A: ActiveModelTrait, { - pub(crate) fn prepare(mut self) -> Self { - for key in ::PrimaryKey::iter() { - let col = key.into_column(); - let av = self.model.get(col); - if av.is_set() || av.is_unchanged() { - self = self.filter(col.eq(av.unwrap())); - } else { - panic!("PrimaryKey is not set"); - } - } + pub(crate) fn prepare_values(mut self) -> Self { for col in ::Column::iter() { if ::PrimaryKey::from_column(col).is_some() { continue; @@ -106,6 +98,19 @@ where } self } + + pub(crate) fn prepare_filters(mut self) -> Self { + for key in ::PrimaryKey::iter() { + let col = key.into_column(); + let av = self.model.get(col); + if av.is_set() || av.is_unchanged() { + self = self.filter(col.eq(av.unwrap())); + } else { + panic!("PrimaryKey is not set"); + } + } + self + } } impl QueryFilter for UpdateOne @@ -194,6 +199,7 @@ mod tests { id: ActiveValue::set(1), name: ActiveValue::set("Apple Pie".to_owned()), }) + .filter(cake::Column::Id.eq(1)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "cake" SET "name" = 'Apple Pie' WHERE "cake"."id" = 1"#, @@ -208,6 +214,7 @@ mod tests { name: ActiveValue::set("Orange".to_owned()), cake_id: ActiveValue::unset(), }) + .filter(fruit::Column::Id.eq(1)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "fruit" SET "name" = 'Orange' WHERE "fruit"."id" = 1"#, @@ -222,6 +229,7 @@ mod tests { name: ActiveValue::unchanged("Apple".to_owned()), cake_id: ActiveValue::set(Some(3)), }) + .filter(fruit::Column::Id.eq(2)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "fruit" SET "cake_id" = 3 WHERE "fruit"."id" = 2"#, diff --git a/tests/crud/updates.rs b/tests/crud/updates.rs index 83b9a5d39..f4570e5d1 100644 --- a/tests/crud/updates.rs +++ b/tests/crud/updates.rs @@ -42,8 +42,8 @@ pub async fn test_update_cake(db: &DbConn) { cake_am.name = Set("Extra chocolate mud cake".to_owned()); cake_am.price = Set(dec!(20.00)); - let _cake_update_res: cake::ActiveModel = Cake::update(cake_am) - .exec(db) + let _cake_update_res: cake::ActiveModel = cake_am + .update(db) .await .expect("could not update cake"); @@ -81,8 +81,8 @@ pub async fn test_update_bakery(db: &DbConn) { bakery_am.name = Set("SeaBreeze Bakery".to_owned()); bakery_am.profit_margin = Set(12.00); - let _bakery_update_res: bakery::ActiveModel = Bakery::update(bakery_am) - .exec(db) + let _bakery_update_res: bakery::ActiveModel = bakery_am + .update(db) .await .expect("could not update bakery"); @@ -123,8 +123,8 @@ pub async fn test_update_deleted_customer(db: &DbConn) { ..Default::default() }; - let _customer_update_res: customer::ActiveModel = Customer::update(customer) - .exec(db) + let _customer_update_res: customer::ActiveModel = customer + .update(db) .await .expect("could not update customer"); From dcaedd7d3a357773ba210820e8a1ae7c4fd2a492 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 7 Sep 2021 11:39:01 +0800 Subject: [PATCH 2/5] cargo fmt --- tests/crud/updates.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/crud/updates.rs b/tests/crud/updates.rs index f4570e5d1..c2048f9ba 100644 --- a/tests/crud/updates.rs +++ b/tests/crud/updates.rs @@ -42,10 +42,8 @@ pub async fn test_update_cake(db: &DbConn) { cake_am.name = Set("Extra chocolate mud cake".to_owned()); cake_am.price = Set(dec!(20.00)); - let _cake_update_res: cake::ActiveModel = cake_am - .update(db) - .await - .expect("could not update cake"); + let _cake_update_res: cake::ActiveModel = + cake_am.update(db).await.expect("could not update cake"); let cake: Option = Cake::find_by_id(cake_insert_res.last_insert_id) .one(db) @@ -81,10 +79,8 @@ pub async fn test_update_bakery(db: &DbConn) { bakery_am.name = Set("SeaBreeze Bakery".to_owned()); bakery_am.profit_margin = Set(12.00); - let _bakery_update_res: bakery::ActiveModel = bakery_am - .update(db) - .await - .expect("could not update bakery"); + let _bakery_update_res: bakery::ActiveModel = + bakery_am.update(db).await.expect("could not update bakery"); let bakery: Option = Bakery::find_by_id(bakery_insert_res.last_insert_id) .one(db) From 11208d627b78456e1f4f070f3f047c12f2ac8cd1 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 9 Sep 2021 22:10:46 +0800 Subject: [PATCH 3/5] Move ActiveModel CRUD actions into ActiveModelTrait --- Cargo.toml | 1 + sea-orm-macros/src/derives/active_model.rs | 18 --- src/entity/active_model.rs | 150 +++++++++------------ src/entity/model.rs | 2 +- src/entity/primary_key.rs | 1 + 5 files changed, 70 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d11485463..65adce04e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ path = "src/lib.rs" [dependencies] async-stream = { version = "^0.3" } +async-trait = { version = "^0.1" } chrono = { version = "^0", optional = true } futures = { version = "^0.3" } futures-util = { version = "^0.3" } diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index bf04421b9..16e09f2ad 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -36,24 +36,6 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result),* } - impl ActiveModel { - pub async fn insert(self, db: &sea_orm::DatabaseConnection) -> Result { - sea_orm::insert_and_select_active_model::(self, db).await - } - - pub async fn update(self, db: &sea_orm::DatabaseConnection) -> Result { - sea_orm::update_active_model::(self, db).await - } - - pub async fn save(self, db: &sea_orm::DatabaseConnection) -> Result { - sea_orm::save_active_model::(self, db).await - } - - pub async fn delete(self, db: &sea_orm::DatabaseConnection) -> Result { - sea_orm::delete_active_model::(self, db).await - } - } - impl std::default::Default for ActiveModel { fn default() -> Self { ::new() diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 7eee81f33..983082bec 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -2,6 +2,7 @@ use crate::{ error::*, DatabaseConnection, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, Value, }; +use async_trait::async_trait; use std::fmt::Debug; #[derive(Clone, Debug, Default)] @@ -50,6 +51,7 @@ where ActiveValue::unchanged(value) } +#[async_trait] pub trait ActiveModelTrait: Clone + Debug { type Entity: EntityTrait; @@ -65,9 +67,71 @@ pub trait ActiveModelTrait: Clone + Debug { fn default() -> Self; - // below is not yet possible. right now we define these methods in DeriveActiveModel - // fn save(self, db: &DatabaseConnection) -> impl Future>; - // fn delete(self, db: &DatabaseConnection) -> impl Future>; + async fn insert(self, db: &DatabaseConnection) -> Result + where + ::Model: IntoActiveModel, + { + let am = self; + let exec = ::insert(am).exec(db); + let res = exec.await?; + // TODO: if the entity does not have auto increment primary key, then last_insert_id is a wrong value + // FIXME: Assumed valid last_insert_id is not equals to Default::default() + if <::PrimaryKey as PrimaryKeyTrait>::auto_increment() + && res.last_insert_id != <::PrimaryKey as PrimaryKeyTrait>::ValueType::default() + { + let find = ::find_by_id(res.last_insert_id).one(db); + let found = find.await; + let model: Option<::Model> = found?; + match model { + Some(model) => Ok(model.into_active_model()), + None => Err(DbErr::Exec("Failed to find inserted item".to_owned())), + } + } else { + Ok(Self::default()) + } + } + + async fn update(self, db: &DatabaseConnection) -> Result { + let exec = Self::Entity::update(self).prepare_filters().exec(db); + exec.await + } + + /// Insert the model if primary key is unset, update otherwise. + /// Only works if the entity has auto increment primary key. + async fn save(self, db: &DatabaseConnection) -> Result + where + Self: ActiveModelBehavior, + ::Model: IntoActiveModel, + { + let mut am = self; + am = ActiveModelBehavior::before_save(am); + let mut is_update = true; + for key in ::PrimaryKey::iter() { + let col = key.into_column(); + if am.is_unset(col) { + is_update = false; + break; + } + } + if !is_update { + am = am.insert(db).await?; + } else { + am = am.update(db).await?; + } + am = ActiveModelBehavior::after_save(am); + Ok(am) + } + + /// Delete an active model by its primary key + async fn delete(self, db: &DatabaseConnection) -> Result + where + Self: ActiveModelBehavior, + { + let mut am = self; + am = ActiveModelBehavior::before_delete(am); + let exec = Self::Entity::delete(am).exec(db); + exec.await + } } /// Behaviors for users to override @@ -185,83 +249,3 @@ where self.value.as_ref() == other.value.as_ref() } } - -/// Insert the model if primary key is unset, update otherwise. -/// Only works if the entity has auto increment primary key. -pub async fn save_active_model(mut am: A, db: &DatabaseConnection) -> Result -where - A: ActiveModelBehavior + ActiveModelTrait, - E::Model: IntoActiveModel, - E: EntityTrait, -{ - am = ActiveModelBehavior::before_save(am); - let mut is_update = true; - for key in E::PrimaryKey::iter() { - let col = key.into_column(); - if am.is_unset(col) { - is_update = false; - break; - } - } - if !is_update { - am = insert_and_select_active_model::(am, db).await?; - } else { - am = update_active_model::(am, db).await?; - } - am = ActiveModelBehavior::after_save(am); - Ok(am) -} - -pub async fn insert_and_select_active_model( - am: A, - db: &DatabaseConnection, -) -> Result -where - A: ActiveModelTrait, - E::Model: IntoActiveModel, - E: EntityTrait, -{ - let exec = E::insert(am).exec(db); - let res = exec.await?; - // TODO: if the entity does not have auto increment primary key, then last_insert_id is a wrong value - // FIXME: Assumed valid last_insert_id is not equals to Default::default() - if ::auto_increment() - && res.last_insert_id != ::ValueType::default() - { - let find = E::find_by_id(res.last_insert_id).one(db); - let found = find.await; - let model: Option = found?; - match model { - Some(model) => Ok(model.into_active_model()), - None => Err(DbErr::Exec(format!( - "Failed to find inserted item: {}", - E::default().to_string(), - ))), - } - } else { - Ok(A::default()) - } -} - -pub async fn update_active_model(am: A, db: &DatabaseConnection) -> Result -where - A: ActiveModelTrait, - E: EntityTrait, -{ - let exec = E::update(am).prepare_filters().exec(db); - exec.await -} - -/// Delete an active model by its primary key -pub async fn delete_active_model( - mut am: A, - db: &DatabaseConnection, -) -> Result -where - A: ActiveModelBehavior + ActiveModelTrait, - E: EntityTrait, -{ - am = ActiveModelBehavior::before_delete(am); - let exec = E::delete(am).exec(db); - exec.await -} diff --git a/src/entity/model.rs b/src/entity/model.rs index 4774e1dc8..a2d1868f0 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -2,7 +2,7 @@ use crate::{DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Selec pub use sea_query::Value; use std::fmt::Debug; -pub trait ModelTrait: Clone + Debug { +pub trait ModelTrait: Clone + Send + Debug { type Entity: EntityTrait; fn get(&self, c: ::Column) -> Value; diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index b9c381d40..463f1482e 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -6,6 +6,7 @@ use std::fmt::Debug; //LINT: composite primary key cannot auto increment pub trait PrimaryKeyTrait: IdenStatic + Iterable { type ValueType: Sized + + Send + Default + Debug + PartialEq From 19ec35f1b507823ed2637b43897288e6b89b8083 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 9 Sep 2021 23:16:19 +0800 Subject: [PATCH 4/5] Revert breaking changes --- src/entity/active_model.rs | 2 +- src/query/update.rs | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 983082bec..ea83d97bc 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -92,7 +92,7 @@ pub trait ActiveModelTrait: Clone + Debug { } async fn update(self, db: &DatabaseConnection) -> Result { - let exec = Self::Entity::update(self).prepare_filters().exec(db); + let exec = Self::Entity::update(self).exec(db); exec.await } diff --git a/src/query/update.rs b/src/query/update.rs index 3500b7594..6eba2db86 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -37,7 +37,6 @@ impl Update { /// id: ActiveValue::set(1), /// name: ActiveValue::set("Apple Pie".to_owned()), /// }) - /// .filter(cake::Column::Id.eq(1)) /// .build(DbBackend::Postgres) /// .to_string(), /// r#"UPDATE "cake" SET "name" = 'Apple Pie' WHERE "cake"."id" = 1"#, @@ -48,12 +47,13 @@ impl Update { E: EntityTrait, A: ActiveModelTrait, { - let myself = UpdateOne { + let mut myself = UpdateOne { query: UpdateStatement::new() .table(A::Entity::default().table_ref()) .to_owned(), model, }; + myself = myself.prepare_filters(); myself.prepare_values() } @@ -86,19 +86,6 @@ impl UpdateOne where A: ActiveModelTrait, { - pub(crate) fn prepare_values(mut self) -> Self { - for col in ::Column::iter() { - if ::PrimaryKey::from_column(col).is_some() { - continue; - } - let av = self.model.get(col); - if av.is_set() { - self.query.value(col, av.unwrap()); - } - } - self - } - pub(crate) fn prepare_filters(mut self) -> Self { for key in ::PrimaryKey::iter() { let col = key.into_column(); @@ -111,6 +98,19 @@ where } self } + + pub(crate) fn prepare_values(mut self) -> Self { + for col in ::Column::iter() { + if ::PrimaryKey::from_column(col).is_some() { + continue; + } + let av = self.model.get(col); + if av.is_set() { + self.query.value(col, av.unwrap()); + } + } + self + } } impl QueryFilter for UpdateOne @@ -199,7 +199,6 @@ mod tests { id: ActiveValue::set(1), name: ActiveValue::set("Apple Pie".to_owned()), }) - .filter(cake::Column::Id.eq(1)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "cake" SET "name" = 'Apple Pie' WHERE "cake"."id" = 1"#, @@ -214,7 +213,6 @@ mod tests { name: ActiveValue::set("Orange".to_owned()), cake_id: ActiveValue::unset(), }) - .filter(fruit::Column::Id.eq(1)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "fruit" SET "name" = 'Orange' WHERE "fruit"."id" = 1"#, @@ -229,7 +227,6 @@ mod tests { name: ActiveValue::unchanged("Apple".to_owned()), cake_id: ActiveValue::set(Some(3)), }) - .filter(fruit::Column::Id.eq(2)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "fruit" SET "cake_id" = 3 WHERE "fruit"."id" = 2"#, From c4cdfa1add2bdd5a4b5c4e164d34fd00a2e9ecbe Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 10 Sep 2021 13:15:45 +0800 Subject: [PATCH 5/5] Refactor --- src/query/update.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/update.rs b/src/query/update.rs index 6eba2db86..2f8055111 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -47,14 +47,14 @@ impl Update { E: EntityTrait, A: ActiveModelTrait, { - let mut myself = UpdateOne { + UpdateOne { query: UpdateStatement::new() .table(A::Entity::default().table_ref()) .to_owned(), model, - }; - myself = myself.prepare_filters(); - myself.prepare_values() + } + .prepare_filters() + .prepare_values() } /// Update many ActiveModel