Merge pull request #211 from SeaQL/error-no-record-updated
Throw error if none of the db rows are affected
This commit is contained in:
parent
c7532bcc08
commit
30b90076ae
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
@ -42,15 +43,26 @@ where
|
||||
|
||||
impl Updater {
|
||||
pub fn new(query: UpdateStatement) -> Self {
|
||||
Self { query }
|
||||
Self {
|
||||
query,
|
||||
check_record_exists: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exec<'a, C>(self, db: &'a C) -> Result<UpdateResult, DbErr>
|
||||
pub fn check_record_exists(mut self) -> Self {
|
||||
self.check_record_exists = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exec<'a, C>(
|
||||
self,
|
||||
db: &'a C
|
||||
) -> impl Future<Output = Result<UpdateResult, DbErr>> + '_
|
||||
where
|
||||
C: ConnectionTrait<'a>,
|
||||
{
|
||||
let builder = db.get_database_backend();
|
||||
exec_update(builder.build(&self.query), db).await
|
||||
exec_update(builder.build(&self.query), db, self.check_record_exists)
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,17 +82,163 @@ where
|
||||
A: ActiveModelTrait,
|
||||
C: ConnectionTrait<'a>,
|
||||
{
|
||||
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<'a, C>(statement: Statement, db: &'a C) -> Result<UpdateResult, DbErr>
|
||||
async fn exec_update<'a, C>(
|
||||
statement: Statement,
|
||||
db: &'a C,
|
||||
check_record_exists: bool,
|
||||
) -> Result<UpdateResult, DbErr>
|
||||
where
|
||||
C: ConnectionTrait<'a>,
|
||||
{
|
||||
let result = db.execute(statement).await?;
|
||||
if check_record_exists && result.rows_affected() == 0 {
|
||||
return Err(DbErr::RecordNotFound(
|
||||
"None of the database rows are affected".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(UpdateResult {
|
||||
rows_affected: result.rows_affected(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{entity::prelude::*, tests_cfg::*, *};
|
||||
use pretty_assertions::assert_eq;
|
||||
use sea_query::Expr;
|
||||
|
||||
#[smol_potat::test]
|
||||
async fn update_record_not_found_1() -> Result<(), DbErr> {
|
||||
let db = MockDatabase::new(DbBackend::Postgres)
|
||||
.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,
|
||||
},
|
||||
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.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()
|
||||
})
|
||||
.exec(&db)
|
||||
.await,
|
||||
Err(DbErr::RecordNotFound(
|
||||
"None of the database rows are affected".to_owned()
|
||||
))
|
||||
);
|
||||
|
||||
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![
|
||||
MockTransaction::from_sql_and_values(
|
||||
DbBackend::Postgres,
|
||||
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
|
||||
vec!["Cheese Cake".into(), 1i32.into()]
|
||||
),
|
||||
MockTransaction::from_sql_and_values(
|
||||
DbBackend::Postgres,
|
||||
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
|
||||
vec!["Cheese Cake".into(), 2i32.into()]
|
||||
),
|
||||
MockTransaction::from_sql_and_values(
|
||||
DbBackend::Postgres,
|
||||
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
|
||||
vec!["Cheese Cake".into(), 2i32.into()]
|
||||
),
|
||||
MockTransaction::from_sql_and_values(
|
||||
DbBackend::Postgres,
|
||||
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
|
||||
vec!["Cheese Cake".into(), 2i32.into()]
|
||||
),
|
||||
MockTransaction::from_sql_and_values(
|
||||
DbBackend::Postgres,
|
||||
r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
|
||||
vec!["Cheese Cake".into(), 2i32.into()]
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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(
|
||||
@ -43,5 +43,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(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user