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