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