From 11781082ba52806d4e33957069d2979bf16de1a4 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 19:00:55 +0800 Subject: [PATCH 01/13] Throw error if none of the db rows are affected --- src/error.rs | 2 ++ src/executor/insert.rs | 4 ++-- src/executor/update.rs | 5 +++++ tests/crud/updates.rs | 13 +++++++++---- tests/uuid_tests.rs | 17 ++++++++++++++++- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index 09f80b0a..f8aff775 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,7 @@ pub enum DbErr { Conn(String), Exec(String), Query(String), + RecordNotFound(String), } impl std::error::Error for DbErr {} @@ -13,6 +14,7 @@ impl std::fmt::Display for DbErr { Self::Conn(s) => write!(f, "Connection Error: {}", s), Self::Exec(s) => write!(f, "Execution Error: {}", s), Self::Query(s) => write!(f, "Query Error: {}", s), + Self::RecordNotFound(s) => write!(f, "RecordNotFound Error: {}", s), } } } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index d580f110..a44867f7 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,6 +1,6 @@ use crate::{ - error::*, ActiveModelTrait, DatabaseConnection, DbBackend, EntityTrait, Insert, PrimaryKeyTrait, - Statement, TryFromU64, + error::*, ActiveModelTrait, DatabaseConnection, DbBackend, EntityTrait, Insert, + PrimaryKeyTrait, Statement, TryFromU64, }; use sea_query::InsertStatement; use std::{future::Future, marker::PhantomData}; diff --git a/src/executor/update.rs b/src/executor/update.rs index 6c7a9873..c6668d6b 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -73,6 +73,11 @@ where // Only Statement impl Send async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result { let result = db.execute(statement).await?; + if result.rows_affected() <= 0 { + return Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned(), + )); + } Ok(UpdateResult { rows_affected: result.rows_affected(), }) diff --git a/tests/crud/updates.rs b/tests/crud/updates.rs index c2048f9b..262031ef 100644 --- a/tests/crud/updates.rs +++ b/tests/crud/updates.rs @@ -1,5 +1,6 @@ pub use super::*; use rust_decimal_macros::dec; +use sea_orm::DbErr; use uuid::Uuid; pub async fn test_update_cake(db: &DbConn) { @@ -119,10 +120,14 @@ pub async fn test_update_deleted_customer(db: &DbConn) { ..Default::default() }; - let _customer_update_res: customer::ActiveModel = customer - .update(db) - .await - .expect("could not update customer"); + let customer_update_res = customer.update(db).await; + + assert_eq!( + customer_update_res, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); assert_eq!(Customer::find().count(db).await.unwrap(), init_n_customers); diff --git a/tests/uuid_tests.rs b/tests/uuid_tests.rs index e58daca4..052f57d7 100644 --- a/tests/uuid_tests.rs +++ b/tests/uuid_tests.rs @@ -1,7 +1,7 @@ pub mod common; pub use common::{bakery_chain::*, setup::*, TestContext}; -use sea_orm::{entity::prelude::*, DatabaseConnection, IntoActiveModel}; +use sea_orm::{entity::prelude::*, DatabaseConnection, IntoActiveModel, Set}; #[sea_orm_macros::test] #[cfg(any( @@ -40,5 +40,20 @@ pub async fn create_metadata(db: &DatabaseConnection) -> Result<(), DbErr> { } ); + let update_res = Metadata::update(metadata::ActiveModel { + value: Set("0.22".to_owned()), + ..metadata.clone().into_active_model() + }) + .filter(metadata::Column::Uuid.eq(Uuid::default())) + .exec(db) + .await; + + assert_eq!( + update_res, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); + Ok(()) } From 966f7ff9a85fcb133fb863ca1af709da5ed2c7fb Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 28 Sep 2021 19:06:02 +0800 Subject: [PATCH 02/13] Fix clippy warning --- src/executor/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index c6668d6b..7cb60f3e 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -73,7 +73,7 @@ where // Only Statement impl Send async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result { let result = db.execute(statement).await?; - if result.rows_affected() <= 0 { + if result.rows_affected() == 0 { return Err(DbErr::RecordNotFound( "None of the database rows are affected".to_owned(), )); From f4218dec56b0745b75f88171ee7e0202115e9397 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 30 Sep 2021 12:47:01 +0800 Subject: [PATCH 03/13] Test mock connection --- src/executor/update.rs | 105 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/src/executor/update.rs b/src/executor/update.rs index 7cb60f3e..bcbafefc 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -82,3 +82,108 @@ async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result Result<(), DbErr> { + let db = MockDatabase::new(DbBackend::Postgres) + .append_query_results(vec![ + vec![cake::Model { + id: 1, + name: "Cheese Cake".to_owned(), + }], + vec![], + vec![], + ]) + .append_exec_results(vec![ + MockExecResult { + last_insert_id: 0, + rows_affected: 1, + }, + MockExecResult { + last_insert_id: 0, + rows_affected: 0, + }, + MockExecResult { + last_insert_id: 0, + rows_affected: 0, + }, + ]) + .into_connection(); + + let model = cake::Model { + id: 1, + name: "New York Cheese".to_owned(), + }; + + assert_eq!( + cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + ..model.into_active_model() + } + .update(&db) + .await?, + cake::Model { + id: 1, + name: "Cheese Cake".to_owned(), + } + .into_active_model() + ); + + let model = cake::Model { + id: 2, + name: "New York Cheese".to_owned(), + }; + + assert_eq!( + cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + ..model.clone().into_active_model() + } + .update(&db) + .await, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); + + assert_eq!( + cake::Entity::update(cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + ..model.into_active_model() + }) + .exec(&db) + .await, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); + + assert_eq!( + db.into_transaction_log(), + vec![ + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + vec!["Cheese Cake".into(), 1i32.into()] + ), + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + vec!["Cheese Cake".into(), 2i32.into()] + ), + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + vec!["Cheese Cake".into(), 2i32.into()] + ), + ] + ); + + Ok(()) + } +} From 602690e9a7c8c281cc33a185234fe1ee09492c47 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 30 Sep 2021 19:16:55 +0800 Subject: [PATCH 04/13] Remove unneeded --- src/executor/update.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index bcbafefc..2bc5ed80 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -91,14 +91,6 @@ mod tests { #[smol_potat::test] async fn update_record_not_found_1() -> Result<(), DbErr> { let db = MockDatabase::new(DbBackend::Postgres) - .append_query_results(vec![ - vec![cake::Model { - id: 1, - name: "Cheese Cake".to_owned(), - }], - vec![], - vec![], - ]) .append_exec_results(vec![ MockExecResult { last_insert_id: 0, From 91fb97c12ae1e4ce4ebf87977f960198f475e365 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 4 Oct 2021 11:18:42 +0800 Subject: [PATCH 05/13] `Update::many()` will not raise `DbErr::RecordNotFound` error --- src/executor/update.rs | 65 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/executor/update.rs b/src/executor/update.rs index 2bc5ed80..b564165c 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -7,9 +7,10 @@ use std::future::Future; #[derive(Clone, Debug)] pub struct Updater { query: UpdateStatement, + check_record_exists: bool, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct UpdateResult { pub rows_affected: u64, } @@ -39,7 +40,15 @@ where impl Updater { pub fn new(query: UpdateStatement) -> Self { - Self { query } + Self { + query, + check_record_exists: false, + } + } + + pub fn check_record_exists(mut self) -> Self { + self.check_record_exists = true; + self } pub fn exec( @@ -47,7 +56,7 @@ impl Updater { db: &DatabaseConnection, ) -> impl Future> + '_ { let builder = db.get_database_backend(); - exec_update(builder.build(&self.query), db) + exec_update(builder.build(&self.query), db, self.check_record_exists) } } @@ -66,14 +75,18 @@ async fn exec_update_and_return_original( where A: ActiveModelTrait, { - Updater::new(query).exec(db).await?; + Updater::new(query).check_record_exists().exec(db).await?; Ok(model) } // Only Statement impl Send -async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result { +async fn exec_update( + statement: Statement, + db: &DatabaseConnection, + check_record_exists: bool, +) -> Result { let result = db.execute(statement).await?; - if result.rows_affected() == 0 { + if check_record_exists && result.rows_affected() == 0 { return Err(DbErr::RecordNotFound( "None of the database rows are affected".to_owned(), )); @@ -87,6 +100,7 @@ async fn exec_update(statement: Statement, db: &DatabaseConnection) -> Result Result<(), DbErr> { @@ -104,6 +118,14 @@ mod tests { last_insert_id: 0, rows_affected: 0, }, + MockExecResult { + last_insert_id: 0, + rows_affected: 0, + }, + MockExecResult { + last_insert_id: 0, + rows_affected: 0, + }, ]) .into_connection(); @@ -145,6 +167,18 @@ mod tests { assert_eq!( cake::Entity::update(cake::ActiveModel { + name: Set("Cheese Cake".to_owned()), + ..model.clone().into_active_model() + }) + .exec(&db) + .await, + Err(DbErr::RecordNotFound( + "None of the database rows are affected".to_owned() + )) + ); + + assert_eq!( + Update::one(cake::ActiveModel { name: Set("Cheese Cake".to_owned()), ..model.into_active_model() }) @@ -155,6 +189,15 @@ mod tests { )) ); + assert_eq!( + Update::many(cake::Entity) + .col_expr(cake::Column::Name, Expr::value("Cheese Cake".to_owned())) + .filter(cake::Column::Id.eq(2)) + .exec(&db) + .await, + Ok(UpdateResult { rows_affected: 0 }) + ); + assert_eq!( db.into_transaction_log(), vec![ @@ -173,6 +216,16 @@ mod tests { r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, vec!["Cheese Cake".into(), 2i32.into()] ), + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + vec!["Cheese Cake".into(), 2i32.into()] + ), + Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#, + vec!["Cheese Cake".into(), 2i32.into()] + ), ] ); From 8990261d703cbeecb6e1292efd7e58f50d060eaf Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 6 Oct 2021 12:28:46 +0800 Subject: [PATCH 06/13] Bind null custom types --- Cargo.toml | 2 +- tests/common/bakery_chain/metadata.rs | 4 ++-- tests/common/setup/schema.rs | 4 ++-- tests/parallel_tests.rs | 12 ++++++------ tests/uuid_tests.rs | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a4466152..b9675c38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } sea-orm-macros = { version = "^0.2.4", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.16.5", features = ["thread-safe"] } +sea-query = { version = "^0.17.0", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } diff --git a/tests/common/bakery_chain/metadata.rs b/tests/common/bakery_chain/metadata.rs index de513a22..2c297cd3 100644 --- a/tests/common/bakery_chain/metadata.rs +++ b/tests/common/bakery_chain/metadata.rs @@ -10,8 +10,8 @@ pub struct Model { pub key: String, pub value: String, pub bytes: Vec, - pub date: Date, - pub time: Time, + pub date: Option, + pub time: Option