Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion sea-orm-sync/src/entity/active_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{ActiveValue, ActiveValue::*};
use crate::{
ColumnTrait, Condition, ConnectionTrait, DbBackend, DeleteResult, EntityName, EntityTrait,
IdenStatic, Iterable, PrimaryKeyArity, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter,
Related, RelatedSelfVia, RelationDef, RelationTrait, Value,
Related, RelatedSelfVia, RelationDef, RelationTrait, UpdateResult, Value,
error::*,
query::{
clear_key_on_active_model, column_tuple_in_condition, get_key_from_active_model,
Expand Down Expand Up @@ -338,6 +338,17 @@ pub trait ActiveModelTrait: Clone + Debug {
Self::after_save(model, db, false)
}

/// Similar to [`update`], but without returning
/// It also won't execute [`ActiveModelTrait::after_save`]
fn update_without_returning<'a, C>(self, db: &'a C) -> Result<UpdateResult, DbErr>
where
Self: ActiveModelBehavior,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_save(self, db, false)?;
Self::Entity::update(am).exec_without_returning(db)
}

/// Insert the model if primary key is `NotSet`, update otherwise.
/// Only works if the entity has auto increment primary key.
fn save<'a, C>(self, db: &'a C) -> Result<Self, DbErr>
Expand Down
24 changes: 24 additions & 0 deletions sea-orm-sync/src/executor/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ impl<A> ValidatedUpdateOne<A>
where
A: ActiveModelTrait,
{
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
pub fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
where
C: ConnectionTrait,
{
Updater::new(self.query)
// If nothing is updated, return RecordNotUpdated error
.check_record_exists()
.exec(db)
}

/// Execute an UPDATE operation on an ActiveModel
pub fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
where
Expand All @@ -37,6 +48,14 @@ impl<A> UpdateOne<A>
where
A: ActiveModelTrait,
{
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
pub fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
where
C: ConnectionTrait,
{
self.0?.exec_without_returning(db)
}

/// Execute an UPDATE operation on an ActiveModel
pub fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
where
Expand Down Expand Up @@ -77,6 +96,11 @@ impl Updater {
}
}

fn check_record_exists(mut self) -> Self {
self.check_record_exists = true;
self
}

/// Execute an update operation
pub fn exec<C>(self, db: &C) -> Result<UpdateResult, DbErr>
where
Expand Down
105 changes: 105 additions & 0 deletions sea-orm-sync/tests/update_without_returning_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#![allow(unused_imports, dead_code)]

pub mod common;

pub use common::{TestContext, features::*, setup::*};
use pretty_assertions::assert_eq;
use sea_orm::{DatabaseConnection, entity::prelude::*, entity::*};
use serde_json::json;

#[sea_orm_macros::test]
fn main() -> Result<(), DbErr> {
let ctx = TestContext::new("update_without_returning_tests");
create_repository_table(&ctx.db)?;
create_edit_log_table(&ctx.db)?;
update_without_returning(&ctx.db)?;
update_without_returning_record_not_updated(&ctx.db)?;
ctx.delete();

Ok(())
}

// `update_without_returning` should update the row, run `before_save`, and
// intentionally skip `after_save`.
pub fn update_without_returning(db: &DatabaseConnection) -> Result<(), DbErr> {
let model = repository::Model {
id: "uwr-001".to_owned(),
owner: "GC".to_owned(),
name: "G.C.".to_owned(),
description: None,
};

// Instance insert runs `before_save` + `after_save` (edit_log id 1 and 2).
model.clone().into_active_model().insert(db)?;

let updated = repository::ActiveModel {
description: Set(Some("updated".to_owned())),
..model.clone().into_active_model()
};

let res = updated.update_without_returning(db)?;
assert_eq!(res.rows_affected, 1);

// The row is actually updated.
assert_eq!(
Repository::find_by_id("uwr-001".to_owned()).one(db)?,
Some(repository::Model {
description: Some("updated".to_owned()),
..model
})
);

// `before_save` ran for the update (id 3), but `after_save` did NOT.
assert_eq!(
edit_log::Entity::find().all(db)?,
[
edit_log::Model {
id: 1,
action: "before_save".into(),
values: json!({
"description": null,
"id": "uwr-001",
"name": "G.C.",
"owner": "GC",
}),
},
edit_log::Model {
id: 2,
action: "after_save".into(),
values: json!({
"description": null,
"id": "uwr-001",
"name": "G.C.",
"owner": "GC",
}),
},
edit_log::Model {
id: 3,
action: "before_save".into(),
values: json!({
"description": "updated",
"id": "uwr-001",
"name": "G.C.",
"owner": "GC",
}),
},
]
);

Ok(())
}

// Updating a row that does not exist returns `RecordNotUpdated`.
pub fn update_without_returning_record_not_updated(db: &DatabaseConnection) -> Result<(), DbErr> {
let missing = repository::ActiveModel {
id: Set("does-not-exist".to_owned()),
owner: Set("GC".to_owned()),
name: Set("G.C.".to_owned()),
description: Set(Some("nope".to_owned())),
};

let res = missing.update_without_returning(db);
assert_eq!(res.map(|_| ()), Err(DbErr::RecordNotUpdated));

Ok(())
}
13 changes: 12 additions & 1 deletion src/entity/active_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{ActiveValue, ActiveValue::*};
use crate::{
ColumnTrait, Condition, ConnectionTrait, DbBackend, DeleteResult, EntityName, EntityTrait,
IdenStatic, Iterable, PrimaryKeyArity, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter,
Related, RelatedSelfVia, RelationDef, RelationTrait, Value,
Related, RelatedSelfVia, RelationDef, RelationTrait, UpdateResult, Value,
error::*,
query::{
clear_key_on_active_model, column_tuple_in_condition, get_key_from_active_model,
Expand Down Expand Up @@ -345,6 +345,17 @@ pub trait ActiveModelTrait: Clone + Debug {
Self::after_save(model, db, false).await
}

/// Similar to [`update`], but without returning
/// It also won't execute [`ActiveModelTrait::after_save`]
async fn update_without_returning<'a, C>(self, db: &'a C) -> Result<UpdateResult, DbErr>
where
Self: ActiveModelBehavior,
C: ConnectionTrait,
{
let am = ActiveModelBehavior::before_save(self, db, false).await?;
Self::Entity::update(am).exec_without_returning(db).await
}

/// Insert the model if primary key is `NotSet`, update otherwise.
/// Only works if the entity has auto increment primary key.
async fn save<'a, C>(self, db: &'a C) -> Result<Self, DbErr>
Expand Down
25 changes: 25 additions & 0 deletions src/executor/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ impl<A> ValidatedUpdateOne<A>
where
A: ActiveModelTrait,
{
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
pub async fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
where
C: ConnectionTrait,
{
Updater::new(self.query)
// If nothing is updated, return RecordNotUpdated error
.check_record_exists()
.exec(db)
.await
}

/// Execute an UPDATE operation on an ActiveModel
pub async fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
where
Expand All @@ -39,6 +51,14 @@ impl<A> UpdateOne<A>
where
A: ActiveModelTrait,
{
/// Execute an UPDATE operation on an ActiveModel without returning the updated model
pub async fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
where
C: ConnectionTrait,
{
self.0?.exec_without_returning(db).await
}

/// Execute an UPDATE operation on an ActiveModel
pub async fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
where
Expand Down Expand Up @@ -81,6 +101,11 @@ impl Updater {
}
}

fn check_record_exists(mut self) -> Self {
self.check_record_exists = true;
self
}

/// Execute an update operation
pub async fn exec<C>(self, db: &C) -> Result<UpdateResult, DbErr>
where
Expand Down
107 changes: 107 additions & 0 deletions tests/update_without_returning_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#![allow(unused_imports, dead_code)]

pub mod common;

pub use common::{TestContext, features::*, setup::*};
use pretty_assertions::assert_eq;
use sea_orm::{DatabaseConnection, entity::prelude::*, entity::*};
use serde_json::json;

#[sea_orm_macros::test]
async fn main() -> Result<(), DbErr> {
let ctx = TestContext::new("update_without_returning_tests").await;
create_repository_table(&ctx.db).await?;
create_edit_log_table(&ctx.db).await?;
update_without_returning(&ctx.db).await?;
update_without_returning_record_not_updated(&ctx.db).await?;
ctx.delete().await;

Ok(())
}

// `update_without_returning` should update the row, run `before_save`, and
// intentionally skip `after_save`.
pub async fn update_without_returning(db: &DatabaseConnection) -> Result<(), DbErr> {
let model = repository::Model {
id: "uwr-001".to_owned(),
owner: "GC".to_owned(),
name: "G.C.".to_owned(),
description: None,
};

// Instance insert runs `before_save` + `after_save` (edit_log id 1 and 2).
model.clone().into_active_model().insert(db).await?;

let updated = repository::ActiveModel {
description: Set(Some("updated".to_owned())),
..model.clone().into_active_model()
};

let res = updated.update_without_returning(db).await?;
assert_eq!(res.rows_affected, 1);

// The row is actually updated.
assert_eq!(
Repository::find_by_id("uwr-001".to_owned()).one(db).await?,
Some(repository::Model {
description: Some("updated".to_owned()),
..model
})
);

// `before_save` ran for the update (id 3), but `after_save` did NOT.
assert_eq!(
edit_log::Entity::find().all(db).await?,
[
edit_log::Model {
id: 1,
action: "before_save".into(),
values: json!({
"description": null,
"id": "uwr-001",
"name": "G.C.",
"owner": "GC",
}),
},
edit_log::Model {
id: 2,
action: "after_save".into(),
values: json!({
"description": null,
"id": "uwr-001",
"name": "G.C.",
"owner": "GC",
}),
},
edit_log::Model {
id: 3,
action: "before_save".into(),
values: json!({
"description": "updated",
"id": "uwr-001",
"name": "G.C.",
"owner": "GC",
}),
},
]
);

Ok(())
}

// Updating a row that does not exist returns `RecordNotUpdated`.
pub async fn update_without_returning_record_not_updated(
db: &DatabaseConnection,
) -> Result<(), DbErr> {
let missing = repository::ActiveModel {
id: Set("does-not-exist".to_owned()),
owner: Set("GC".to_owned()),
name: Set("G.C.".to_owned()),
description: Set(Some("nope".to_owned())),
};

let res = missing.update_without_returning(db).await;
assert_eq!(res.map(|_| ()), Err(DbErr::RecordNotUpdated));

Ok(())
}
Loading