From 24b25a7b5aea446f54b1316786853be427999160 Mon Sep 17 00:00:00 2001 From: huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Sun, 1 Dec 2024 05:09:04 +0800 Subject: [PATCH 1/3] Insert many with returning many Co-authored-by: Ivan Porto Wigner --- src/executor/insert.rs | 84 ++++++++++++++++++++++++++++++++++++++++ tests/returning_tests.rs | 75 ++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 665fa5bee..d8983c79d 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -102,6 +102,28 @@ where Err(err) => Err(err), } } + + /// Execute an insert many operation and return the inserted model (use `RETURNING` syntax if database supported) + pub async fn exec_with_returning_many<'a, C>( + self, + db: &'a C, + ) -> Result::Model>>, DbErr> + where + ::Model: IntoActiveModel, + C: ConnectionTrait, + A: 'a, + { + if self.insert_struct.columns.is_empty() { + return Ok(TryInsertResult::Empty); + } + + let res = self.insert_struct.exec_with_returning_many(db).await; + match res { + Ok(res) => Ok(TryInsertResult::Inserted(res)), + Err(DbErr::RecordNotInserted) => Ok(TryInsertResult::Conflicted), + Err(err) => Err(err), + } + } } impl Insert @@ -155,6 +177,25 @@ where { Inserter::::new(self.primary_key, self.query).exec_with_returning(db) } + + /// Execute an insert many operation and return the inserted model (use `RETURNING` syntax if supported) + pub async fn exec_with_returning_many<'a, C>( + self, + db: &'a C, + ) -> Result::Model>, DbErr> + where + ::Model: IntoActiveModel, + C: ConnectionTrait, + A: 'a, + { + if self.columns.is_empty() { + Ok(vec![]) + } else { + Inserter::::new(self.primary_key, self.query) + .exec_with_returning_many(db) + .await + } + } } impl Inserter @@ -203,6 +244,19 @@ where { exec_insert_with_returning::(self.primary_key, self.query, db) } + + /// Execute an insert many operation and return the inserted model (use `RETURNING` syntax if supported) + pub fn exec_with_returning_many<'a, C>( + self, + db: &'a C, + ) -> impl Future::Model>, DbErr>> + '_ + where + ::Model: IntoActiveModel, + C: ConnectionTrait, + A: 'a, + { + exec_insert_many_with_returning::(self.query, db) + } } async fn exec_insert( @@ -313,3 +367,33 @@ where )), } } + +async fn exec_insert_many_with_returning( + mut insert_statement: InsertStatement, + db: &C, +) -> Result::Model>, DbErr> +where + ::Model: IntoActiveModel, + C: ConnectionTrait, + A: ActiveModelTrait, +{ + match db.support_returning() { + true => { + let db_backend = db.get_database_backend(); + let returning = Query::returning().exprs( + ::Column::iter() + .map(|c| c.select_as(c.into_returning_expr(db_backend))), + ); + insert_statement.returning(returning); + let insert_statement = db_backend.build(&insert_statement); + let models: Vec<::Model> = + SelectorRaw::::Model>>::from_statement( + insert_statement, + ) + .all(db) + .await?; + Ok(models) + } + false => unimplemented!("Database backend doesn't support RETURNING"), + } +} diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 0f7958a4a..7bbe56304 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -3,7 +3,7 @@ pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; -use sea_orm::{entity::prelude::*, IntoActiveModel, Set}; +use sea_orm::{entity::prelude::*, IntoActiveModel, NotSet, Set}; pub use sea_query::{Expr, Query}; use serde_json::json; @@ -67,6 +67,79 @@ async fn main() -> Result<(), DbErr> { Ok(()) } +#[sea_orm_macros::test] +#[cfg_attr( + any( + feature = "sqlx-mysql", + all( + feature = "sqlx-sqlite", + not(feature = "sqlite-use-returning-for-3_35") + ) + ), + should_panic(expected = "Database backend doesn't support RETURNING") +)] +async fn insert_many() { + pub use common::{features::*, TestContext}; + use edit_log::*; + + let run = || async { + let ctx = TestContext::new("returning_tests_insert_many").await; + let db = &ctx.db; + + create_tables(db).await?; + + // Insert many with returning + assert_eq!( + Entity::insert_many(vec![ + ActiveModel { + id: NotSet, + action: Set("before_save".to_string()), + values: Set(json!({ "id": "unique-id-001" })), + }, + ActiveModel { + id: NotSet, + action: Set("before_save".to_string()), + values: Set(json!({ "id": "unique-id-002" })), + }, + ]) + .exec_with_returning_many(db) + .await?, + [ + Model { + id: 1, + action: "before_save".to_string(), + values: json!({ "id": "unique-id-001" }), + }, + Model { + id: 2, + action: "before_save".to_string(), + values: json!({ "id": "unique-id-002" }), + }, + ] + ); + + // No-op + assert_eq!( + Entity::insert_many( + vec![ActiveModel { + id: NotSet, + action: Set("before_save".to_string()), + values: Set(json!({ "id": "unique-id-001" })), + }] + .into_iter() + .filter(|_| false) + ) + .exec_with_returning_many(db) + .await?, + [] + ); + + Result::<(), DbErr>::Ok(()) + }; + + run().await.unwrap(); +} + #[sea_orm_macros::test] #[cfg_attr( any( From 61cdf8d7fc10e10b42aa7b385581e03e42195bd8 Mon Sep 17 00:00:00 2001 From: huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Sun, 1 Dec 2024 06:44:36 +0800 Subject: [PATCH 2/3] Returning on delete --- src/executor/delete.rs | 80 ++++++++++++++++++++++++++++++++++ tests/returning_tests.rs | 94 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) diff --git a/src/executor/delete.rs b/src/executor/delete.rs index e1663a6c8..fb6cf8470 100644 --- a/src/executor/delete.rs +++ b/src/executor/delete.rs @@ -2,6 +2,8 @@ use crate::{error::*, ActiveModelTrait, ConnectionTrait, DeleteMany, DeleteOne, use sea_query::DeleteStatement; use std::future::Future; +use super::{SelectModel, SelectorRaw}; + /// Handles DELETE operations in a ActiveModel using [DeleteStatement] #[derive(Clone, Debug)] pub struct Deleter { @@ -27,6 +29,17 @@ where // so that self is dropped before entering await exec_delete_only(self.query, db) } + + /// Execute an delete operation and return the deleted model (use `RETURNING` syntax if supported) + pub fn exec_with_returning( + self, + db: &'a C, + ) -> impl Future::Model>, DbErr>> + '_ + where + C: ConnectionTrait, + { + exec_delete_with_returning_one::(self.query, db) + } } impl<'a, E> DeleteMany @@ -41,6 +54,18 @@ where // so that self is dropped before entering await exec_delete_only(self.query, db) } + + /// Execute an delete operation and return the deleted model (use `RETURNING` syntax if supported) + pub fn exec_with_returning( + self, + db: &C, + ) -> impl Future, DbErr>> + '_ + where + E: EntityTrait, + C: ConnectionTrait, + { + exec_delete_with_returning_many::(self.query, db) + } } impl Deleter { @@ -56,6 +81,18 @@ impl Deleter { { exec_delete(self.query, db) } + + /// Execute an delete operation and return the deleted model (use `RETURNING` syntax if supported) + pub fn exec_with_returning( + self, + db: &C, + ) -> impl Future, DbErr>> + '_ + where + E: EntityTrait, + C: ConnectionTrait, + { + exec_delete_with_returning_many::(self.query, db) + } } async fn exec_delete_only(query: DeleteStatement, db: &C) -> Result @@ -77,3 +114,46 @@ where rows_affected: result.rows_affected(), }) } + +async fn exec_delete_with_returning_one( + mut query: DeleteStatement, + db: &C, +) -> Result, DbErr> +where + E: EntityTrait, + C: ConnectionTrait, +{ + let models = match db.support_returning() { + true => { + let db_backend = db.get_database_backend(); + let delete_statement = db_backend.build(&query.returning_all().to_owned()); + SelectorRaw::::Model>>::from_statement(delete_statement) + .one(db) + .await? + } + false => unimplemented!("Database backend doesn't support RETURNING"), + }; + Ok(models) +} + +async fn exec_delete_with_returning_many( + mut query: DeleteStatement, + db: &C, +) -> Result, DbErr> +where + E: EntityTrait, + C: ConnectionTrait, +{ + let models = match db.support_returning() { + true => { + let db_backend = db.get_database_backend(); + let query = query.returning_all(); + let delete_statement = db_backend.build(&query.to_owned()); + SelectorRaw::::Model>>::from_statement(delete_statement) + .all(db) + .await? + } + false => unimplemented!("Database backend doesn't support RETURNING"), + }; + Ok(models) +} diff --git a/tests/returning_tests.rs b/tests/returning_tests.rs index 7bbe56304..04c2e7454 100644 --- a/tests/returning_tests.rs +++ b/tests/returning_tests.rs @@ -258,3 +258,97 @@ async fn update_many() { run().await.unwrap(); } + +#[sea_orm_macros::test] +#[cfg_attr( + any( + feature = "sqlx-mysql", + all( + feature = "sqlx-sqlite", + not(feature = "sqlite-use-returning-for-3_35") + ) + ), + should_panic(expected = "Database backend doesn't support RETURNING") +)] +async fn delete_many() { + pub use common::{features::*, TestContext}; + use edit_log::*; + + let run = || async { + let ctx = TestContext::new("returning_tests_delete_many").await; + let db = &ctx.db; + + create_tables(db).await?; + + let inserted_models = [ + Model { + id: 1, + action: "before_save".to_string(), + values: json!({ "id": "unique-id-001" }), + }, + Model { + id: 2, + action: "before_save".to_string(), + values: json!({ "id": "unique-id-002" }), + }, + ]; + // Delete many with returning + assert_eq!( + Entity::insert_many(vec![ + ActiveModel { + id: NotSet, + action: Set("before_save".to_string()), + values: Set(json!({ "id": "unique-id-001" })), + }, + ActiveModel { + id: NotSet, + action: Set("before_save".to_string()), + values: Set(json!({ "id": "unique-id-002" })), + }, + ]) + .exec_with_returning_many(db) + .await?, + inserted_models + ); + + assert_eq!( + Entity::delete_many() + .filter(Column::Action.eq("before_save")) + .exec_with_returning(db) + .await?, + inserted_models + ); + + let inserted_model_3 = Model { + id: 3, + action: "before_save".to_string(), + values: json!({ "id": "unique-id-003" }), + }; + + Entity::insert(ActiveModel { + id: NotSet, + action: Set("before_save".to_string()), + values: Set(json!({ "id": "unique-id-003" })), + }) + .exec(db) + .await?; + + // One + assert_eq!( + Entity::delete(ActiveModel { + id: Set(3), + ..Default::default() + }) + .exec_with_returning(db) + .await?, + Some(inserted_model_3) + ); + + // No-op + assert_eq!(Entity::delete_many().exec_with_returning(db).await?, []); + + Result::<(), DbErr>::Ok(()) + }; + + run().await.unwrap(); +} From 5db1916072ecd5a38f1ad6ef3a81c5f9cd6264ae Mon Sep 17 00:00:00 2001 From: huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Mon, 2 Dec 2024 02:49:04 +0800 Subject: [PATCH 3/3] Returning only what entity needs --- src/executor/delete.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/executor/delete.rs b/src/executor/delete.rs index fb6cf8470..9bd8752db 100644 --- a/src/executor/delete.rs +++ b/src/executor/delete.rs @@ -1,5 +1,8 @@ -use crate::{error::*, ActiveModelTrait, ConnectionTrait, DeleteMany, DeleteOne, EntityTrait}; -use sea_query::DeleteStatement; +use crate::{ + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, DeleteMany, DeleteOne, EntityTrait, + Iterable, +}; +use sea_query::{DeleteStatement, Query}; use std::future::Future; use super::{SelectModel, SelectorRaw}; @@ -147,7 +150,10 @@ where let models = match db.support_returning() { true => { let db_backend = db.get_database_backend(); - let query = query.returning_all(); + let returning = Query::returning().exprs( + E::Column::iter().map(|c| c.select_enum_as(c.into_returning_expr(db_backend))), + ); + let query = query.returning(returning); let delete_statement = db_backend.build(&query.to_owned()); SelectorRaw::::Model>>::from_statement(delete_statement) .all(db)