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