Skip to main content

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