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