diff --git a/src/entity/base_entity.rs b/src/entity/base_entity.rs index 8d27b1c8e..22c687015 100644 --- a/src/entity/base_entity.rs +++ b/src/entity/base_entity.rs @@ -745,10 +745,124 @@ pub trait EntityTrait: EntityName { fn delete_many() -> DeleteMany { Delete::many(Self::default()) } + + /// Delete a model based on primary key + /// + /// # Example + /// + /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] + /// # #[cfg(feature = "mock")] + /// # pub async fn main() -> Result<(), DbErr> { + /// # + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 0, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::fruit}; + /// + /// let delete_result = fruit::Entity::delete_by_id(1).exec(&db).await?; + /// + /// assert_eq!(delete_result.rows_affected, 1); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"DELETE FROM "fruit" WHERE "fruit"."id" = $1"#, + /// vec![1i32.into()] + /// )] + /// ); + /// # + /// # Ok(()) + /// # } + /// ``` + /// Delete by composite key + /// ``` + /// # use sea_orm::{error::*, tests_cfg::*, *}; + /// # + /// # #[smol_potat::main] + /// # #[cfg(feature = "mock")] + /// # pub async fn main() -> Result<(), DbErr> { + /// + /// # let db = MockDatabase::new(DbBackend::Postgres) + /// # .append_exec_results(vec![ + /// # MockExecResult { + /// # last_insert_id: 0, + /// # rows_affected: 1, + /// # }, + /// # ]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake_filling}; + /// + /// let delete_result = cake_filling::Entity::delete_by_id((2, 3)).exec(&db).await?; + /// + /// assert_eq!(delete_result.rows_affected, 1); + /// + /// assert_eq!( + /// db.into_transaction_log(), + /// vec![Transaction::from_sql_and_values( + /// DbBackend::Postgres, + /// r#"DELETE FROM "cake_filling" WHERE "cake_filling"."cake_id" = $1 AND "cake_filling"."filling_id" = $2"#, + /// vec![2i32.into(), 3i32.into()] + /// )] + /// ); + /// # + /// # Ok(()) + /// # } + /// ``` + fn delete_by_id(values: ::ValueType) -> DeleteMany { + let mut delete = Self::delete_many(); + let mut keys = Self::PrimaryKey::iter(); + for v in values.into_value_tuple() { + if let Some(key) = keys.next() { + let col = key.into_column(); + delete = delete.filter(col.eq(v)); + } else { + panic!("primary key arity mismatch"); + } + } + if keys.next().is_some() { + panic!("primary key arity mismatch"); + } + delete + } } #[cfg(test)] mod tests { + #[test] + fn test_delete_by_id_1() { + use crate::tests_cfg::cake; + use crate::{entity::*, query::*, DbBackend}; + assert_eq!( + cake::Entity::delete_by_id(1) + .build(DbBackend::Sqlite) + .to_string(), + r#"DELETE FROM "cake" WHERE "cake"."id" = 1"#, + ); + } + + #[test] + fn test_delete_by_id_2() { + use crate::tests_cfg::cake_filling_price; + use crate::{entity::*, query::*, DbBackend}; + assert_eq!( + cake_filling_price::Entity::delete_by_id((1, 2)) + .build(DbBackend::Sqlite) + .to_string(), + r#"DELETE FROM "public"."cake_filling_price" WHERE "cake_filling_price"."cake_id" = 1 AND "cake_filling_price"."filling_id" = 2"#, + ); + } + #[test] #[cfg(feature = "macros")] fn entity_model_1() { diff --git a/src/executor/delete.rs b/src/executor/delete.rs index 25a1060e4..c69b90caf 100644 --- a/src/executor/delete.rs +++ b/src/executor/delete.rs @@ -11,7 +11,7 @@ pub struct Deleter { } /// The result of a DELETE operation -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct DeleteResult { /// The number of rows affected by the DELETE operation pub rows_affected: u64, diff --git a/tests/delete_by_id_tests.rs b/tests/delete_by_id_tests.rs new file mode 100644 index 000000000..d3142d686 --- /dev/null +++ b/tests/delete_by_id_tests.rs @@ -0,0 +1,57 @@ +pub mod common; +pub use common::{features::*, setup::*, TestContext}; +use sea_orm::{entity::prelude::*, DatabaseConnection, IntoActiveModel}; + +#[sea_orm_macros::test] +#[cfg(any( + feature = "sqlx-mysql", + feature = "sqlx-sqlite", + feature = "sqlx-postgres" +))] +async fn main() -> Result<(), DbErr> { + let ctx = TestContext::new("delete_by_id_tests").await; + create_tables(&ctx.db).await?; + create_and_delete_applog(&ctx.db).await?; + + ctx.delete().await; + + Ok(()) +} + +pub async fn create_and_delete_applog(db: &DatabaseConnection) -> Result<(), DbErr> { + let log1 = applog::Model { + id: 1, + action: "Testing".to_owned(), + json: Json::String("HI".to_owned()), + created_at: "2021-09-17T17:50:20+08:00".parse().unwrap(), + }; + + Applog::insert(log1.clone().into_active_model()) + .exec(db) + .await?; + + let log2 = applog::Model { + id: 2, + action: "Tests".to_owned(), + json: Json::String("HELLO".to_owned()), + created_at: "2022-09-17T17:50:20+08:00".parse().unwrap(), + }; + + Applog::insert(log2.clone().into_active_model()) + .exec(db) + .await?; + + let delete_res = Applog::delete_by_id(2).exec(db).await?; + assert_eq!(delete_res.rows_affected, 1); + + let find_res = Applog::find_by_id(1).all(db).await?; + assert_eq!(find_res, vec![log1]); + + let find_res = Applog::find_by_id(2).all(db).await?; + assert_eq!(find_res, vec![]); + + let delete_res = Applog::delete_by_id(3).exec(db).await?; + assert_eq!(delete_res.rows_affected, 0); + + Ok(()) +}