Skip to content

Commit 85af367

Browse files
Huliiiiiityt2y3
andauthored
Add Update without returning (#2965)
* Update without returning * Fix update_without_returning compile error; add tests ActiveModelTrait::update_without_returning called the non-existent Self::Entity::update_without_returning(am); use Self::Entity::update(am), whose UpdateOne gains the new exec_without_returning. Add integration tests covering a successful update, RecordNotUpdated on a non-existent row, before_save running, and after_save being skipped. --------- Co-authored-by: Chris Tsang <chris.2y3@outlook.com>
1 parent 4b2b075 commit 85af367

6 files changed

Lines changed: 285 additions & 2 deletions

File tree

sea-orm-sync/src/entity/active_model.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::{ActiveValue, ActiveValue::*};
22
use crate::{
33
ColumnTrait, Condition, ConnectionTrait, DbBackend, DeleteResult, EntityName, EntityTrait,
44
IdenStatic, Iterable, PrimaryKeyArity, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter,
5-
Related, RelatedSelfVia, RelationDef, RelationTrait, Value,
5+
Related, RelatedSelfVia, RelationDef, RelationTrait, UpdateResult, Value,
66
error::*,
77
query::{
88
clear_key_on_active_model, column_tuple_in_condition, get_key_from_active_model,
@@ -338,6 +338,17 @@ pub trait ActiveModelTrait: Clone + Debug {
338338
Self::after_save(model, db, false)
339339
}
340340

341+
/// Similar to [`update`], but without returning
342+
/// It also won't execute [`ActiveModelTrait::after_save`]
343+
fn update_without_returning<'a, C>(self, db: &'a C) -> Result<UpdateResult, DbErr>
344+
where
345+
Self: ActiveModelBehavior,
346+
C: ConnectionTrait,
347+
{
348+
let am = ActiveModelBehavior::before_save(self, db, false)?;
349+
Self::Entity::update(am).exec_without_returning(db)
350+
}
351+
341352
/// Insert the model if primary key is `NotSet`, update otherwise.
342353
/// Only works if the entity has auto increment primary key.
343354
fn save<'a, C>(self, db: &'a C) -> Result<Self, DbErr>

sea-orm-sync/src/executor/update.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ impl<A> ValidatedUpdateOne<A>
2323
where
2424
A: ActiveModelTrait,
2525
{
26+
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
27+
pub fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
28+
where
29+
C: ConnectionTrait,
30+
{
31+
Updater::new(self.query)
32+
// If nothing is updated, return RecordNotUpdated error
33+
.check_record_exists()
34+
.exec(db)
35+
}
36+
2637
/// Execute an UPDATE operation on an ActiveModel
2738
pub fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
2839
where
@@ -37,6 +48,14 @@ impl<A> UpdateOne<A>
3748
where
3849
A: ActiveModelTrait,
3950
{
51+
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
52+
pub fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
53+
where
54+
C: ConnectionTrait,
55+
{
56+
self.0?.exec_without_returning(db)
57+
}
58+
4059
/// Execute an UPDATE operation on an ActiveModel
4160
pub fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
4261
where
@@ -77,6 +96,11 @@ impl Updater {
7796
}
7897
}
7998

99+
fn check_record_exists(mut self) -> Self {
100+
self.check_record_exists = true;
101+
self
102+
}
103+
80104
/// Execute an update operation
81105
pub fn exec<C>(self, db: &C) -> Result<UpdateResult, DbErr>
82106
where
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#![allow(unused_imports, dead_code)]
2+
3+
pub mod common;
4+
5+
pub use common::{TestContext, features::*, setup::*};
6+
use pretty_assertions::assert_eq;
7+
use sea_orm::{DatabaseConnection, entity::prelude::*, entity::*};
8+
use serde_json::json;
9+
10+
#[sea_orm_macros::test]
11+
fn main() -> Result<(), DbErr> {
12+
let ctx = TestContext::new("update_without_returning_tests");
13+
create_repository_table(&ctx.db)?;
14+
create_edit_log_table(&ctx.db)?;
15+
update_without_returning(&ctx.db)?;
16+
update_without_returning_record_not_updated(&ctx.db)?;
17+
ctx.delete();
18+
19+
Ok(())
20+
}
21+
22+
// `update_without_returning` should update the row, run `before_save`, and
23+
// intentionally skip `after_save`.
24+
pub fn update_without_returning(db: &DatabaseConnection) -> Result<(), DbErr> {
25+
let model = repository::Model {
26+
id: "uwr-001".to_owned(),
27+
owner: "GC".to_owned(),
28+
name: "G.C.".to_owned(),
29+
description: None,
30+
};
31+
32+
// Instance insert runs `before_save` + `after_save` (edit_log id 1 and 2).
33+
model.clone().into_active_model().insert(db)?;
34+
35+
let updated = repository::ActiveModel {
36+
description: Set(Some("updated".to_owned())),
37+
..model.clone().into_active_model()
38+
};
39+
40+
let res = updated.update_without_returning(db)?;
41+
assert_eq!(res.rows_affected, 1);
42+
43+
// The row is actually updated.
44+
assert_eq!(
45+
Repository::find_by_id("uwr-001".to_owned()).one(db)?,
46+
Some(repository::Model {
47+
description: Some("updated".to_owned()),
48+
..model
49+
})
50+
);
51+
52+
// `before_save` ran for the update (id 3), but `after_save` did NOT.
53+
assert_eq!(
54+
edit_log::Entity::find().all(db)?,
55+
[
56+
edit_log::Model {
57+
id: 1,
58+
action: "before_save".into(),
59+
values: json!({
60+
"description": null,
61+
"id": "uwr-001",
62+
"name": "G.C.",
63+
"owner": "GC",
64+
}),
65+
},
66+
edit_log::Model {
67+
id: 2,
68+
action: "after_save".into(),
69+
values: json!({
70+
"description": null,
71+
"id": "uwr-001",
72+
"name": "G.C.",
73+
"owner": "GC",
74+
}),
75+
},
76+
edit_log::Model {
77+
id: 3,
78+
action: "before_save".into(),
79+
values: json!({
80+
"description": "updated",
81+
"id": "uwr-001",
82+
"name": "G.C.",
83+
"owner": "GC",
84+
}),
85+
},
86+
]
87+
);
88+
89+
Ok(())
90+
}
91+
92+
// Updating a row that does not exist returns `RecordNotUpdated`.
93+
pub fn update_without_returning_record_not_updated(db: &DatabaseConnection) -> Result<(), DbErr> {
94+
let missing = repository::ActiveModel {
95+
id: Set("does-not-exist".to_owned()),
96+
owner: Set("GC".to_owned()),
97+
name: Set("G.C.".to_owned()),
98+
description: Set(Some("nope".to_owned())),
99+
};
100+
101+
let res = missing.update_without_returning(db);
102+
assert_eq!(res.map(|_| ()), Err(DbErr::RecordNotUpdated));
103+
104+
Ok(())
105+
}

src/entity/active_model.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::{ActiveValue, ActiveValue::*};
22
use crate::{
33
ColumnTrait, Condition, ConnectionTrait, DbBackend, DeleteResult, EntityName, EntityTrait,
44
IdenStatic, Iterable, PrimaryKeyArity, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter,
5-
Related, RelatedSelfVia, RelationDef, RelationTrait, Value,
5+
Related, RelatedSelfVia, RelationDef, RelationTrait, UpdateResult, Value,
66
error::*,
77
query::{
88
clear_key_on_active_model, column_tuple_in_condition, get_key_from_active_model,
@@ -345,6 +345,17 @@ pub trait ActiveModelTrait: Clone + Debug {
345345
Self::after_save(model, db, false).await
346346
}
347347

348+
/// Similar to [`update`], but without returning
349+
/// It also won't execute [`ActiveModelTrait::after_save`]
350+
async fn update_without_returning<'a, C>(self, db: &'a C) -> Result<UpdateResult, DbErr>
351+
where
352+
Self: ActiveModelBehavior,
353+
C: ConnectionTrait,
354+
{
355+
let am = ActiveModelBehavior::before_save(self, db, false).await?;
356+
Self::Entity::update(am).exec_without_returning(db).await
357+
}
358+
348359
/// Insert the model if primary key is `NotSet`, update otherwise.
349360
/// Only works if the entity has auto increment primary key.
350361
async fn save<'a, C>(self, db: &'a C) -> Result<Self, DbErr>

src/executor/update.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ impl<A> ValidatedUpdateOne<A>
2323
where
2424
A: ActiveModelTrait,
2525
{
26+
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
27+
pub async fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
28+
where
29+
C: ConnectionTrait,
30+
{
31+
Updater::new(self.query)
32+
// If nothing is updated, return RecordNotUpdated error
33+
.check_record_exists()
34+
.exec(db)
35+
.await
36+
}
37+
2638
/// Execute an UPDATE operation on an ActiveModel
2739
pub async fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
2840
where
@@ -39,6 +51,14 @@ impl<A> UpdateOne<A>
3951
where
4052
A: ActiveModelTrait,
4153
{
54+
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
55+
pub async fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
56+
where
57+
C: ConnectionTrait,
58+
{
59+
self.0?.exec_without_returning(db).await
60+
}
61+
4262
/// Execute an UPDATE operation on an ActiveModel
4363
pub async fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
4464
where
@@ -81,6 +101,11 @@ impl Updater {
81101
}
82102
}
83103

104+
fn check_record_exists(mut self) -> Self {
105+
self.check_record_exists = true;
106+
self
107+
}
108+
84109
/// Execute an update operation
85110
pub async fn exec<C>(self, db: &C) -> Result<UpdateResult, DbErr>
86111
where
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#![allow(unused_imports, dead_code)]
2+
3+
pub mod common;
4+
5+
pub use common::{TestContext, features::*, setup::*};
6+
use pretty_assertions::assert_eq;
7+
use sea_orm::{DatabaseConnection, entity::prelude::*, entity::*};
8+
use serde_json::json;
9+
10+
#[sea_orm_macros::test]
11+
async fn main() -> Result<(), DbErr> {
12+
let ctx = TestContext::new("update_without_returning_tests").await;
13+
create_repository_table(&ctx.db).await?;
14+
create_edit_log_table(&ctx.db).await?;
15+
update_without_returning(&ctx.db).await?;
16+
update_without_returning_record_not_updated(&ctx.db).await?;
17+
ctx.delete().await;
18+
19+
Ok(())
20+
}
21+
22+
// `update_without_returning` should update the row, run `before_save`, and
23+
// intentionally skip `after_save`.
24+
pub async fn update_without_returning(db: &DatabaseConnection) -> Result<(), DbErr> {
25+
let model = repository::Model {
26+
id: "uwr-001".to_owned(),
27+
owner: "GC".to_owned(),
28+
name: "G.C.".to_owned(),
29+
description: None,
30+
};
31+
32+
// Instance insert runs `before_save` + `after_save` (edit_log id 1 and 2).
33+
model.clone().into_active_model().insert(db).await?;
34+
35+
let updated = repository::ActiveModel {
36+
description: Set(Some("updated".to_owned())),
37+
..model.clone().into_active_model()
38+
};
39+
40+
let res = updated.update_without_returning(db).await?;
41+
assert_eq!(res.rows_affected, 1);
42+
43+
// The row is actually updated.
44+
assert_eq!(
45+
Repository::find_by_id("uwr-001".to_owned()).one(db).await?,
46+
Some(repository::Model {
47+
description: Some("updated".to_owned()),
48+
..model
49+
})
50+
);
51+
52+
// `before_save` ran for the update (id 3), but `after_save` did NOT.
53+
assert_eq!(
54+
edit_log::Entity::find().all(db).await?,
55+
[
56+
edit_log::Model {
57+
id: 1,
58+
action: "before_save".into(),
59+
values: json!({
60+
"description": null,
61+
"id": "uwr-001",
62+
"name": "G.C.",
63+
"owner": "GC",
64+
}),
65+
},
66+
edit_log::Model {
67+
id: 2,
68+
action: "after_save".into(),
69+
values: json!({
70+
"description": null,
71+
"id": "uwr-001",
72+
"name": "G.C.",
73+
"owner": "GC",
74+
}),
75+
},
76+
edit_log::Model {
77+
id: 3,
78+
action: "before_save".into(),
79+
values: json!({
80+
"description": "updated",
81+
"id": "uwr-001",
82+
"name": "G.C.",
83+
"owner": "GC",
84+
}),
85+
},
86+
]
87+
);
88+
89+
Ok(())
90+
}
91+
92+
// Updating a row that does not exist returns `RecordNotUpdated`.
93+
pub async fn update_without_returning_record_not_updated(
94+
db: &DatabaseConnection,
95+
) -> Result<(), DbErr> {
96+
let missing = repository::ActiveModel {
97+
id: Set("does-not-exist".to_owned()),
98+
owner: Set("GC".to_owned()),
99+
name: Set("G.C.".to_owned()),
100+
description: Set(Some("nope".to_owned())),
101+
};
102+
103+
let res = missing.update_without_returning(db).await;
104+
assert_eq!(res.map(|_| ()), Err(DbErr::RecordNotUpdated));
105+
106+
Ok(())
107+
}

0 commit comments

Comments
 (0)