sea_orm/executor/
update.rs

1use super::ReturningSelector;
2use crate::{
3    ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, IntoActiveModel, Iterable,
4    PrimaryKeyTrait, SelectModel, UpdateMany, UpdateOne, ValidatedUpdateOne, error::*,
5};
6use sea_query::{FromValueTuple, Query, UpdateStatement};
7
8/// Defines an update operation
9#[derive(Clone, Debug)]
10pub struct Updater {
11    query: UpdateStatement,
12    check_record_exists: bool,
13}
14
15/// The result of an update operation on an ActiveModel
16#[derive(Clone, Debug, PartialEq, Eq, Default)]
17pub struct UpdateResult {
18    /// The rows affected by the update operation
19    pub rows_affected: u64,
20}
21
22impl<A> ValidatedUpdateOne<A>
23where
24    A: ActiveModelTrait,
25{
26    /// Execute an UPDATE operation on an ActiveModel
27    pub fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
28    where
29        <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
30        C: ConnectionTrait,
31    {
32        Updater::new(self.query).exec_update_and_return_updated(self.model, db)
33    }
34}
35
36impl<A> UpdateOne<A>
37where
38    A: ActiveModelTrait,
39{
40    /// Execute an UPDATE operation on an ActiveModel
41    pub fn exec<C>(self, db: &C) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
42    where
43        <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
44        C: ConnectionTrait,
45    {
46        self.0?.exec(db)
47    }
48}
49
50impl<'a, E> UpdateMany<E>
51where
52    E: EntityTrait,
53{
54    /// Execute an update operation on multiple ActiveModels
55    pub fn exec<C>(self, db: &'a C) -> Result<UpdateResult, DbErr>
56    where
57        C: ConnectionTrait,
58    {
59        Updater::new(self.query).exec(db)
60    }
61
62    /// Execute an update operation and return the updated model (use `RETURNING` syntax if supported)
63    pub fn exec_with_returning<C>(self, db: &'a C) -> Result<Vec<E::Model>, DbErr>
64    where
65        C: ConnectionTrait,
66    {
67        Updater::new(self.query).exec_update_with_returning::<E, _>(db)
68    }
69}
70
71impl Updater {
72    /// Instantiate an update using an [UpdateStatement]
73    fn new(query: UpdateStatement) -> Self {
74        Self {
75            query,
76            check_record_exists: false,
77        }
78    }
79
80    /// Execute an update operation
81    pub fn exec<C>(self, db: &C) -> Result<UpdateResult, DbErr>
82    where
83        C: ConnectionTrait,
84    {
85        if self.is_noop() {
86            return Ok(UpdateResult::default());
87        }
88        let result = db.execute(&self.query)?;
89        if self.check_record_exists && result.rows_affected() == 0 {
90            return Err(DbErr::RecordNotUpdated);
91        }
92        Ok(UpdateResult {
93            rows_affected: result.rows_affected(),
94        })
95    }
96
97    fn exec_update_and_return_updated<A, C>(
98        mut self,
99        model: A,
100        db: &C,
101    ) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
102    where
103        A: ActiveModelTrait,
104        C: ConnectionTrait,
105    {
106        type Entity<A> = <A as ActiveModelTrait>::Entity;
107        type Model<A> = <Entity<A> as EntityTrait>::Model;
108        type Column<A> = <Entity<A> as EntityTrait>::Column;
109
110        if self.is_noop() {
111            return find_updated_model_by_id(model, db);
112        }
113
114        match db.support_returning() {
115            true => {
116                let db_backend = db.get_database_backend();
117                let returning = Query::returning().exprs(
118                    Column::<A>::iter().map(|c| c.select_as(c.into_returning_expr(db_backend))),
119                );
120                self.query.returning(returning);
121                let found: Option<Model<A>> =
122                    ReturningSelector::<SelectModel<Model<A>>, _>::from_query(self.query)
123                        .one(db)?;
124                // If we got `None` then we are updating a row that does not exist.
125                match found {
126                    Some(model) => Ok(model),
127                    None => Err(DbErr::RecordNotUpdated),
128                }
129            }
130            false => {
131                // If we updating a row that does not exist then an error will be thrown here.
132                self.check_record_exists = true;
133                self.exec(db)?;
134                find_updated_model_by_id(model, db)
135            }
136        }
137    }
138
139    fn exec_update_with_returning<E, C>(mut self, db: &C) -> Result<Vec<E::Model>, DbErr>
140    where
141        E: EntityTrait,
142        C: ConnectionTrait,
143    {
144        if self.is_noop() {
145            return Ok(vec![]);
146        }
147
148        let db_backend = db.get_database_backend();
149        match db.support_returning() {
150            true => {
151                let returning = Query::returning().exprs(
152                    E::Column::iter().map(|c| c.select_as(c.into_returning_expr(db_backend))),
153                );
154                self.query.returning(returning);
155                let models: Vec<E::Model> =
156                    ReturningSelector::<SelectModel<E::Model>, _>::from_query(self.query)
157                        .all(db)?;
158                Ok(models)
159            }
160            false => Err(DbErr::BackendNotSupported {
161                db: db_backend.as_str(),
162                ctx: "UPDATE RETURNING",
163            }),
164        }
165    }
166
167    fn is_noop(&self) -> bool {
168        self.query.get_values().is_empty()
169    }
170}
171
172fn find_updated_model_by_id<A, C>(
173    model: A,
174    db: &C,
175) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
176where
177    A: ActiveModelTrait,
178    C: ConnectionTrait,
179{
180    type Entity<A> = <A as ActiveModelTrait>::Entity;
181    type ValueType<A> = <<Entity<A> as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType;
182
183    let primary_key_value = match model.get_primary_key_value() {
184        Some(val) => ValueType::<A>::from_value_tuple(val),
185        None => return Err(DbErr::UpdateGetPrimaryKey),
186    };
187    let found = Entity::<A>::find_by_id(primary_key_value).one(db)?;
188    // If we cannot select the updated row from db by the cached primary key
189    match found {
190        Some(model) => Ok(model),
191        None => Err(DbErr::RecordNotFound(
192            "Failed to find updated item".to_owned(),
193        )),
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use crate::{
200        ColumnTrait, DbBackend, DbErr, EntityTrait, IntoActiveModel, MockDatabase, MockExecResult,
201        QueryFilter, Set, Transaction, Update, UpdateResult, tests_cfg::cake,
202    };
203    use pretty_assertions::assert_eq;
204    use sea_query::Expr;
205
206    #[test]
207    fn update_record_not_found_1() -> Result<(), DbErr> {
208        use crate::ActiveModelTrait;
209
210        let updated_cake = cake::Model {
211            id: 1,
212            name: "Cheese Cake".to_owned(),
213        };
214
215        let db = MockDatabase::new(DbBackend::Postgres)
216            .append_query_results([
217                vec![updated_cake.clone()],
218                vec![],
219                vec![],
220                vec![],
221                vec![updated_cake.clone()],
222                vec![updated_cake.clone()],
223                vec![updated_cake.clone()],
224            ])
225            .append_exec_results([MockExecResult {
226                last_insert_id: 0,
227                rows_affected: 0,
228            }])
229            .into_connection();
230
231        let model = cake::Model {
232            id: 1,
233            name: "New York Cheese".to_owned(),
234        };
235
236        assert_eq!(
237            cake::ActiveModel {
238                name: Set("Cheese Cake".to_owned()),
239                ..model.clone().into_active_model()
240            }
241            .update(&db)?,
242            cake::Model {
243                id: 1,
244                name: "Cheese Cake".to_owned(),
245            }
246        );
247
248        let model = cake::Model {
249            id: 2,
250            name: "New York Cheese".to_owned(),
251        };
252
253        assert_eq!(
254            cake::ActiveModel {
255                name: Set("Cheese Cake".to_owned()),
256                ..model.clone().into_active_model()
257            }
258            .update(&db),
259            Err(DbErr::RecordNotUpdated)
260        );
261
262        assert_eq!(
263            cake::Entity::update(cake::ActiveModel {
264                name: Set("Cheese Cake".to_owned()),
265                ..model.clone().into_active_model()
266            })
267            .exec(&db),
268            Err(DbErr::RecordNotUpdated)
269        );
270
271        assert_eq!(
272            Update::one(cake::ActiveModel {
273                name: Set("Cheese Cake".to_owned()),
274                ..model.clone().into_active_model()
275            })
276            .exec(&db),
277            Err(DbErr::RecordNotUpdated)
278        );
279
280        assert_eq!(
281            Update::many(cake::Entity)
282                .col_expr(cake::Column::Name, Expr::value("Cheese Cake".to_owned()))
283                .filter(cake::Column::Id.eq(2))
284                .exec(&db),
285            Ok(UpdateResult { rows_affected: 0 })
286        );
287
288        assert_eq!(
289            updated_cake.clone().into_active_model().save(&db)?,
290            updated_cake.clone().into_active_model()
291        );
292
293        assert_eq!(
294            updated_cake.clone().into_active_model().update(&db)?,
295            updated_cake
296        );
297
298        assert_eq!(
299            cake::Entity::update(updated_cake.clone().into_active_model()).exec(&db)?,
300            updated_cake
301        );
302
303        assert_eq!(cake::Entity::update_many().exec(&db)?.rows_affected, 0);
304
305        assert_eq!(
306            db.into_transaction_log(),
307            [
308                Transaction::from_sql_and_values(
309                    DbBackend::Postgres,
310                    r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
311                    ["Cheese Cake".into(), 1i32.into()]
312                ),
313                Transaction::from_sql_and_values(
314                    DbBackend::Postgres,
315                    r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
316                    ["Cheese Cake".into(), 2i32.into()]
317                ),
318                Transaction::from_sql_and_values(
319                    DbBackend::Postgres,
320                    r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
321                    ["Cheese Cake".into(), 2i32.into()]
322                ),
323                Transaction::from_sql_and_values(
324                    DbBackend::Postgres,
325                    r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2 RETURNING "id", "name""#,
326                    ["Cheese Cake".into(), 2i32.into()]
327                ),
328                Transaction::from_sql_and_values(
329                    DbBackend::Postgres,
330                    r#"UPDATE "cake" SET "name" = $1 WHERE "cake"."id" = $2"#,
331                    ["Cheese Cake".into(), 2i32.into()]
332                ),
333                Transaction::from_sql_and_values(
334                    DbBackend::Postgres,
335                    r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = $1 LIMIT $2"#,
336                    [1.into(), 1u64.into()]
337                ),
338                Transaction::from_sql_and_values(
339                    DbBackend::Postgres,
340                    r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = $1 LIMIT $2"#,
341                    [1.into(), 1u64.into()]
342                ),
343                Transaction::from_sql_and_values(
344                    DbBackend::Postgres,
345                    r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = $1 LIMIT $2"#,
346                    [1.into(), 1u64.into()]
347                ),
348            ]
349        );
350
351        Ok(())
352    }
353
354    #[test]
355    fn update_error() {
356        use crate::{DbBackend, DbErr, MockDatabase};
357
358        let db = MockDatabase::new(DbBackend::MySql).into_connection();
359
360        assert!(matches!(
361            Update::one(cake::ActiveModel {
362                ..Default::default()
363            })
364            .exec(&db),
365            Err(DbErr::PrimaryKeyNotSet { .. })
366        ));
367
368        assert!(matches!(
369            cake::Entity::update(cake::ActiveModel::default()).exec(&db),
370            Err(DbErr::PrimaryKeyNotSet { .. })
371        ));
372    }
373}