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)]
14pub struct Updater {
15 query: UpdateStatement,
16 check_record_exists: bool,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq, Default)]
22#[non_exhaustive]
23pub struct UpdateResult {
24 pub rows_affected: u64,
26}
27
28impl<A> ValidatedUpdateOne<A>
29where
30 A: ActiveModelTrait,
31{
32 pub fn exec_without_returning<C>(self, db: &C) -> Result<UpdateResult, DbErr>
36 where
37 C: ConnectionTrait,
38 {
39 Updater::new(self.query)
40 .check_record_exists()
42 .exec(db)
43 }
44
45 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 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 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 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 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 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 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 match found {
160 Some(model) => Ok(model),
161 None => Err(DbErr::RecordNotUpdated),
162 }
163 }
164 false => {
165 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 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}