Skip to main content

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