From 53caf94af95aa72063bcca1b0b9331417830b54e Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 30 May 2023 11:29:02 +0800 Subject: [PATCH 1/2] Update many with returning --- src/executor/update.rs | 40 +++++++++++++ tests/returning_tests.rs | 123 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index 26fe58a83..d2d772ec9 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -45,6 +45,16 @@ where { Updater::new(self.query).exec(db).await } + + /// Execute an update operation and return the updated model (use `RETURNING` syntax if database supported) + pub async fn exec_with_returning(self, db: &'a C) -> Result, DbErr> + where + C: ConnectionTrait, + { + Updater::new(self.query) + .exec_update_with_returning::(db) + .await + } } impl Updater { @@ -123,6 +133,36 @@ impl Updater { } } + async fn exec_update_with_returning(mut self, db: &C) -> Result, DbErr> + where + E: EntityTrait, + C: ConnectionTrait, + { + if self.is_noop() { + return Ok(vec![]); + } + + match db.support_returning() { + true => { + let returning = + Query::returning().exprs(E::Column::iter().map(|c| c.select_as(Expr::col(c)))); + self.query.returning(returning); + let db_backend = db.get_database_backend(); + let models: Vec = SelectorRaw::>::from_statement( + db_backend.build(&self.query), + ) + .all(db) + .await?; + // If we got an empty Vec then we are updating a row that does not exist. + match models.is_empty() { + true => Err(DbErr::RecordNotUpdated), + false => Ok(models), + } + } + false => unimplemented!("Database backend doesn't support RETURNING"), + } + } + fn is_noop(&self) -> bool { self.query.get_values().is_empty() } diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index f6025c740..fe0ca4583 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -1,8 +1,9 @@ pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; -use sea_orm::entity::prelude::*; -use sea_query::Query; +pub use sea_orm::{entity::prelude::*, *}; +pub use sea_query::{Expr, Query}; +use serde_json::json; #[sea_orm_macros::test] #[cfg(any( @@ -66,3 +67,121 @@ async fn main() -> Result<(), DbErr> { Ok(()) } + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +#[cfg_attr( + any(feature = "sqlx-mysql", feature = "sqlx-sqlite"), + should_panic(expected = "Database backend doesn't support RETURNING") +)] +async fn update_many() { + pub use common::{features::*, setup::*, TestContext}; + use edit_log::*; + + let run = || async { + let ctx = TestContext::new("returning_tests_update_many").await; + let db = &ctx.db; + + create_tables(db).await?; + + Entity::insert( + Model { + id: 1, + action: "before_save".into(), + values: json!({ "id": "unique-id-001" }), + } + .into_active_model(), + ) + .exec(db) + .await?; + + Entity::insert( + Model { + id: 2, + action: "before_save".into(), + values: json!({ "id": "unique-id-002" }), + } + .into_active_model(), + ) + .exec(db) + .await?; + + Entity::insert( + Model { + id: 3, + action: "before_save".into(), + values: json!({ "id": "unique-id-003" }), + } + .into_active_model(), + ) + .exec(db) + .await?; + + assert_eq!( + Entity::find().all(db).await?, + [ + Model { + id: 1, + action: "before_save".into(), + values: json!({ "id": "unique-id-001" }), + }, + Model { + id: 2, + action: "before_save".into(), + values: json!({ "id": "unique-id-002" }), + }, + Model { + id: 3, + action: "before_save".into(), + values: json!({ "id": "unique-id-003" }), + }, + ] + ); + + // Update many with returning + assert_eq!( + Entity::update_many() + .col_expr( + Column::Values, + Expr::value(json!({ "remarks": "save log" })) + ) + .filter(Column::Action.eq("before_save")) + .exec_with_returning(db) + .await?, + [ + Model { + id: 1, + action: "before_save".into(), + values: json!({ "remarks": "save log" }), + }, + Model { + id: 2, + action: "before_save".into(), + values: json!({ "remarks": "save log" }), + }, + Model { + id: 3, + action: "before_save".into(), + values: json!({ "remarks": "save log" }), + }, + ] + ); + + // No-op + assert_eq!( + Entity::update_many() + .filter(Column::Action.eq("before_save")) + .exec_with_returning(db) + .await?, + [] + ); + + Result::<(), DbErr>::Ok(()) + }; + + run().await.unwrap(); +} From d7a978642ef60253414cf3b5f5c599a26ebfa373 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 10 Jul 2023 19:51:33 +0800 Subject: [PATCH 2/2] Do not throw RecordNotUpdated error --- src/executor/update.rs | 6 +----- tests/returning_tests.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index d2d772ec9..aef8b4d5e 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -153,11 +153,7 @@ impl Updater { ) .all(db) .await?; - // If we got an empty Vec then we are updating a row that does not exist. - match models.is_empty() { - true => Err(DbErr::RecordNotUpdated), - false => Ok(models), - } + Ok(models) } false => unimplemented!("Database backend doesn't support RETURNING"), } diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index fe0ca4583..bcdcc38d6 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -1,7 +1,7 @@ pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; -pub use sea_orm::{entity::prelude::*, *}; +use sea_orm::{entity::prelude::*, IntoActiveModel}; pub use sea_query::{Expr, Query}; use serde_json::json;