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 async 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 .await
44 }
45
46 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 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 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 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 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 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 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 match found {
166 Some(model) => Ok(model),
167 None => Err(DbErr::RecordNotUpdated),
168 }
169 }
170 false => {
171 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 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}