sea_orm/query/
update.rs

1use crate::{
2    ActiveModelTrait, ActiveValue, ColumnTrait, DbErr, EntityTrait, Iterable, PrimaryKeyToColumn,
3    QueryFilter, QueryTrait,
4};
5use core::marker::PhantomData;
6use sea_query::{Expr, IntoIden, SimpleExpr, UpdateStatement};
7
8/// Defines a structure to perform UPDATE query operations on a ActiveModel
9#[derive(Clone, Debug)]
10pub struct Update;
11
12/// A request to update an [`ActiveModel`](ActiveModelTrait).
13///
14/// The primary key must be set.
15/// Otherwise, it's impossible to generate the SQL condition and find the record.
16/// In that case, [`exec`][Self::exec] will return an error and not send any queries to the database.
17///
18/// If you want to use [`QueryTrait`] and access the generated SQL query,
19/// you need to convert into [`ValidatedUpdateOne`] first.
20#[derive(Clone, Debug)]
21pub struct UpdateOne<A: ActiveModelTrait>(pub(crate) Result<ValidatedUpdateOne<A>, DbErr>);
22
23/// A validated [`UpdateOne`] request, where the primary key is set
24/// and it's possible to generate the right SQL condition.
25#[derive(Clone, Debug)]
26pub struct ValidatedUpdateOne<A: ActiveModelTrait> {
27    pub(crate) query: UpdateStatement,
28    pub(crate) model: A,
29}
30
31impl<A: ActiveModelTrait> TryFrom<UpdateOne<A>> for ValidatedUpdateOne<A> {
32    type Error = DbErr;
33
34    fn try_from(value: UpdateOne<A>) -> Result<Self, Self::Error> {
35        value.0
36    }
37}
38
39impl<A: ActiveModelTrait> UpdateOne<A> {
40    /// Check whether the primary key is set and we can proceed with the operation.
41    pub fn validate(self) -> Result<ValidatedUpdateOne<A>, DbErr> {
42        self.try_into()
43    }
44}
45
46/// Defines an UPDATE operation on multiple ActiveModels
47#[derive(Clone, Debug)]
48pub struct UpdateMany<E>
49where
50    E: EntityTrait,
51{
52    pub(crate) query: UpdateStatement,
53    pub(crate) entity: PhantomData<E>,
54}
55
56impl Update {
57    /// Update one ActiveModel
58    ///
59    /// ```
60    /// use sea_orm::{DbBackend, entity::*, query::*, tests_cfg::cake};
61    ///
62    /// assert_eq!(
63    ///     Update::one(cake::ActiveModel {
64    ///         id: ActiveValue::set(1),
65    ///         name: ActiveValue::set("Apple Pie".to_owned()),
66    ///     })
67    ///     .validate()
68    ///     .unwrap()
69    ///     .build(DbBackend::Postgres)
70    ///     .to_string(),
71    ///     r#"UPDATE "cake" SET "name" = 'Apple Pie' WHERE "cake"."id" = 1"#,
72    /// );
73    /// ```
74    //
75    // (non-doc comment for maintainers)
76    // Ideally, we would make this method fallible instead of stashing and delaying the error.
77    // But that's a bigger breaking change.
78    pub fn one<E, A>(model: A) -> UpdateOne<A>
79    where
80        E: EntityTrait,
81        A: ActiveModelTrait<Entity = E>,
82    {
83        let mut myself = ValidatedUpdateOne {
84            query: UpdateStatement::new()
85                .table(A::Entity::default().table_ref())
86                .to_owned(),
87            model,
88        };
89        // Build the SQL condition from the primary key columns.
90        for key in <A::Entity as EntityTrait>::PrimaryKey::iter() {
91            let col = key.into_column();
92            match myself.model.get(col) {
93                ActiveValue::Set(value) | ActiveValue::Unchanged(value) => {
94                    myself = myself.filter(col.eq(value));
95                }
96                ActiveValue::NotSet => {
97                    return UpdateOne(Err(DbErr::PrimaryKeyNotSet { ctx: "UpdateOne" }));
98                }
99            }
100        }
101        // Set the values to update (from the other columns).
102        for col in <A::Entity as EntityTrait>::Column::iter() {
103            if <A::Entity as EntityTrait>::PrimaryKey::from_column(col).is_some() {
104                continue;
105            }
106            match myself.model.get(col) {
107                ActiveValue::Set(value) => {
108                    let expr = col.save_as(Expr::val(value));
109                    myself.query.value(col, expr);
110                }
111                ActiveValue::Unchanged(_) | ActiveValue::NotSet => {}
112            }
113        }
114        UpdateOne(Ok(myself))
115    }
116
117    /// Update many ActiveModel
118    ///
119    /// ```
120    /// use sea_orm::{DbBackend, entity::*, query::*, sea_query::Expr, tests_cfg::fruit};
121    ///
122    /// assert_eq!(
123    ///     Update::many(fruit::Entity)
124    ///         .col_expr(fruit::Column::Name, Expr::value("Golden Apple"))
125    ///         .filter(fruit::Column::Name.contains("Apple"))
126    ///         .build(DbBackend::Postgres)
127    ///         .to_string(),
128    ///     r#"UPDATE "fruit" SET "name" = 'Golden Apple' WHERE "fruit"."name" LIKE '%Apple%'"#,
129    /// );
130    /// ```
131    pub fn many<E>(entity: E) -> UpdateMany<E>
132    where
133        E: EntityTrait,
134    {
135        UpdateMany {
136            query: UpdateStatement::new().table(entity.table_ref()).to_owned(),
137            entity: PhantomData,
138        }
139    }
140}
141
142impl<A> QueryFilter for ValidatedUpdateOne<A>
143where
144    A: ActiveModelTrait,
145{
146    type QueryStatement = UpdateStatement;
147
148    fn query(&mut self) -> &mut UpdateStatement {
149        &mut self.query
150    }
151}
152
153impl<E> QueryFilter for UpdateMany<E>
154where
155    E: EntityTrait,
156{
157    type QueryStatement = UpdateStatement;
158
159    fn query(&mut self) -> &mut UpdateStatement {
160        &mut self.query
161    }
162}
163
164impl<A> QueryTrait for ValidatedUpdateOne<A>
165where
166    A: ActiveModelTrait,
167{
168    type QueryStatement = UpdateStatement;
169
170    fn query(&mut self) -> &mut UpdateStatement {
171        &mut self.query
172    }
173
174    fn as_query(&self) -> &UpdateStatement {
175        &self.query
176    }
177
178    fn into_query(self) -> UpdateStatement {
179        self.query
180    }
181}
182
183impl<E> QueryTrait for UpdateMany<E>
184where
185    E: EntityTrait,
186{
187    type QueryStatement = UpdateStatement;
188
189    fn query(&mut self) -> &mut UpdateStatement {
190        &mut self.query
191    }
192
193    fn as_query(&self) -> &UpdateStatement {
194        &self.query
195    }
196
197    fn into_query(self) -> UpdateStatement {
198        self.query
199    }
200}
201
202impl<E> UpdateMany<E>
203where
204    E: EntityTrait,
205{
206    /// Add the models to update to Self
207    pub fn set<A>(mut self, model: A) -> Self
208    where
209        A: ActiveModelTrait<Entity = E>,
210    {
211        for col in E::Column::iter() {
212            match model.get(col) {
213                ActiveValue::Set(value) => {
214                    let expr = col.save_as(Expr::val(value));
215                    self.query.value(col, expr);
216                }
217                ActiveValue::Unchanged(_) | ActiveValue::NotSet => {}
218            }
219        }
220        self
221    }
222
223    /// Creates a [SimpleExpr] from a column
224    pub fn col_expr<T>(mut self, col: T, expr: SimpleExpr) -> Self
225    where
226        T: IntoIden,
227    {
228        self.query.value(col, expr);
229        self
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use crate::tests_cfg::{cake, fruit, lunch_set, sea_orm_active_enums::Tea};
236    use crate::{DbBackend, entity::*, query::*};
237    use sea_query::{Expr, Value};
238
239    #[test]
240    fn update_1() {
241        assert_eq!(
242            Update::one(cake::ActiveModel {
243                id: ActiveValue::set(1),
244                name: ActiveValue::set("Apple Pie".to_owned()),
245            })
246            .validate()
247            .unwrap()
248            .build(DbBackend::Postgres)
249            .to_string(),
250            r#"UPDATE "cake" SET "name" = 'Apple Pie' WHERE "cake"."id" = 1"#,
251        );
252    }
253
254    #[test]
255    fn update_2() {
256        assert_eq!(
257            Update::one(fruit::ActiveModel {
258                id: ActiveValue::set(1),
259                name: ActiveValue::set("Orange".to_owned()),
260                cake_id: ActiveValue::not_set(),
261            })
262            .validate()
263            .unwrap()
264            .build(DbBackend::Postgres)
265            .to_string(),
266            r#"UPDATE "fruit" SET "name" = 'Orange' WHERE "fruit"."id" = 1"#,
267        );
268    }
269
270    #[test]
271    fn update_3() {
272        assert_eq!(
273            Update::one(fruit::ActiveModel {
274                id: ActiveValue::set(2),
275                name: ActiveValue::unchanged("Apple".to_owned()),
276                cake_id: ActiveValue::set(Some(3)),
277            })
278            .validate()
279            .unwrap()
280            .build(DbBackend::Postgres)
281            .to_string(),
282            r#"UPDATE "fruit" SET "cake_id" = 3 WHERE "fruit"."id" = 2"#,
283        );
284    }
285
286    #[test]
287    fn update_4() {
288        assert_eq!(
289            Update::many(fruit::Entity)
290                .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None)))
291                .filter(fruit::Column::Id.eq(2))
292                .build(DbBackend::Postgres)
293                .to_string(),
294            r#"UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."id" = 2"#,
295        );
296    }
297
298    #[test]
299    fn update_5() {
300        assert_eq!(
301            Update::many(fruit::Entity)
302                .set(fruit::ActiveModel {
303                    name: ActiveValue::set("Apple".to_owned()),
304                    cake_id: ActiveValue::set(Some(3)),
305                    ..Default::default()
306                })
307                .filter(fruit::Column::Id.eq(2))
308                .build(DbBackend::Postgres)
309                .to_string(),
310            r#"UPDATE "fruit" SET "name" = 'Apple', "cake_id" = 3 WHERE "fruit"."id" = 2"#,
311        );
312    }
313
314    #[test]
315    fn update_6() {
316        assert_eq!(
317            Update::many(fruit::Entity)
318                .set(fruit::ActiveModel {
319                    id: ActiveValue::set(3),
320                    ..Default::default()
321                })
322                .filter(fruit::Column::Id.eq(2))
323                .build(DbBackend::Postgres)
324                .to_string(),
325            r#"UPDATE "fruit" SET "id" = 3 WHERE "fruit"."id" = 2"#,
326        );
327    }
328
329    #[test]
330    fn update_7() {
331        assert_eq!(
332            Update::many(lunch_set::Entity)
333                .set(lunch_set::ActiveModel {
334                    tea: Set(Tea::EverydayTea),
335                    ..Default::default()
336                })
337                .filter(lunch_set::Column::Tea.eq(Tea::BreakfastTea))
338                .build(DbBackend::Postgres)
339                .to_string(),
340            r#"UPDATE "lunch_set" SET "tea" = CAST('EverydayTea' AS "tea") WHERE "lunch_set"."tea" = (CAST('BreakfastTea' AS "tea"))"#,
341        );
342    }
343
344    #[test]
345    fn update_8() {
346        assert_eq!(
347            Update::one(lunch_set::ActiveModel {
348                id: Unchanged(1),
349                tea: Set(Tea::EverydayTea),
350                ..Default::default()
351            })
352            .validate()
353            .unwrap()
354            .build(DbBackend::Postgres)
355            .to_string(),
356            r#"UPDATE "lunch_set" SET "tea" = CAST('EverydayTea' AS "tea") WHERE "lunch_set"."id" = 1"#,
357        );
358    }
359}