toasty_driver_integration_suite/tests/
tx_atomic_stmt.rs1use crate::prelude::*;
2
3use toasty_core::driver::{Operation, operation::Transaction};
4
5#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::has_many_belongs_to))]
10pub async fn multi_op_create_wraps_in_transaction(t: &mut Test) -> Result<()> {
11 let mut db = setup(t).await;
12
13 t.log().clear();
14 let user = User::create()
15 .name("Alice")
16 .todo(Todo::create().title("task"))
17 .exec(&mut db)
18 .await?;
19
20 assert_struct!(
21 t.log().pop_op(),
22 Operation::Transaction(Transaction::Start {
23 isolation: None,
24 read_only: false,
25 ..
26 })
27 );
28 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
31 t.log().pop_op(),
32 Operation::Transaction(Transaction::Commit)
33 );
34 assert!(t.log().is_empty());
35
36 let todos = user.todos().exec(&mut db).await?;
37 assert_eq!(1, todos.len());
38
39 Ok(())
40}
41
42#[driver_test(id(ID), requires(scan), scenario(crate::scenarios::two_models))]
45pub async fn single_op_skips_transaction(t: &mut Test) -> Result<()> {
46 let mut db = setup(t).await;
47
48 t.log().clear();
49 User::create().name("x").exec(&mut db).await?;
50
51 assert_struct!(t.log().pop_op(), Operation::QuerySql(_));
53 assert!(t.log().is_empty());
54
55 Ok(())
56}
57
58#[driver_test(requires(and(sql, auto_increment)))]
70pub async fn create_with_has_many_rolls_back_on_failure(t: &mut Test) -> Result<()> {
71 #[derive(Debug, toasty::Model)]
72 struct User {
73 #[key]
74 #[auto]
75 id: u64,
76
77 #[has_many]
78 todos: toasty::HasMany<Todo>,
79 }
80
81 #[derive(Debug, toasty::Model)]
82 struct Todo {
83 #[key]
84 #[auto]
85 id: u64,
86
87 #[index]
88 user_id: u64,
89
90 #[belongs_to(key = user_id, references = id)]
91 user: toasty::BelongsTo<User>,
92
93 #[unique]
94 title: String,
95 }
96
97 let mut db = t.setup_db(models!(User, Todo)).await;
98
99 User::create()
101 .todo(Todo::create().title("taken"))
102 .exec(&mut db)
103 .await?;
104
105 t.log().clear();
106 assert_err!(
107 User::create()
108 .todo(Todo::create().title("taken"))
109 .exec(&mut db)
110 .await
111 );
112
113 assert_struct!(
116 t.log().pop_op(),
117 Operation::Transaction(Transaction::Start {
118 isolation: None,
119 read_only: false,
120 ..
121 })
122 );
123 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
125 t.log().pop_op(),
126 Operation::Transaction(Transaction::Rollback)
127 );
128 assert!(t.log().is_empty());
129
130 let users = User::all().exec(&mut db).await?;
132 assert_eq!(1, users.len());
133
134 Ok(())
135}
136
137#[driver_test(requires(and(sql, auto_increment)))]
145pub async fn create_with_has_one_rolls_back_on_failure(t: &mut Test) -> Result<()> {
146 #[derive(Debug, toasty::Model)]
147 struct User {
148 #[key]
149 #[auto]
150 id: u64,
151
152 #[has_one]
153 profile: toasty::HasOne<Option<Profile>>,
154 }
155
156 #[derive(Debug, toasty::Model)]
157 struct Profile {
158 #[key]
159 #[auto]
160 id: u64,
161
162 #[unique]
163 bio: String,
164
165 #[unique]
166 user_id: u64,
167
168 #[belongs_to(key = user_id, references = id)]
169 user: toasty::BelongsTo<User>,
170 }
171
172 let mut db = t.setup_db(models!(User, Profile)).await;
173
174 User::create()
176 .profile(Profile::create().bio("taken"))
177 .exec(&mut db)
178 .await?;
179
180 t.log().clear();
181 assert_err!(
182 User::create()
183 .profile(Profile::create().bio("taken"))
184 .exec(&mut db)
185 .await
186 );
187
188 assert_struct!(
189 t.log().pop_op(),
190 Operation::Transaction(Transaction::Start {
191 isolation: None,
192 read_only: false,
193 ..
194 })
195 );
196 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
198 t.log().pop_op(),
199 Operation::Transaction(Transaction::Rollback)
200 );
201 assert!(t.log().is_empty());
202
203 let users = User::all().exec(&mut db).await?;
205 assert_eq!(1, users.len());
206
207 Ok(())
208}
209
210#[driver_test(id(ID), requires(sql))]
218pub async fn update_with_new_association_rolls_back_on_failure(t: &mut Test) -> Result<()> {
219 #[derive(Debug, toasty::Model)]
220 struct User {
221 #[key]
222 #[auto]
223 id: ID,
224
225 #[unique]
226 name: String,
227
228 #[has_many]
229 todos: toasty::HasMany<Todo>,
230 }
231
232 #[derive(Debug, toasty::Model)]
233 struct Todo {
234 #[key]
235 #[auto]
236 id: ID,
237
238 #[index]
239 user_id: ID,
240
241 #[belongs_to(key = user_id, references = id)]
242 user: toasty::BelongsTo<User>,
243
244 title: String,
245 }
246
247 let mut db = t.setup_db(models!(User, Todo)).await;
248
249 let mut user = User::create().name("original").exec(&mut db).await?;
250 User::create().name("taken").exec(&mut db).await?;
252
253 t.log().clear();
254 assert_err!(
255 user.update()
256 .name("taken") .todos(toasty::stmt::insert(Todo::create().title("new-todo"))) .exec(&mut db)
259 .await
260 );
261
262 assert_struct!(
265 t.log().pop_op(),
266 Operation::Transaction(Transaction::Start {
267 isolation: None,
268 read_only: false,
269 ..
270 })
271 );
272 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
274 t.log().pop_op(),
275 Operation::Transaction(Transaction::Rollback)
276 );
277 assert!(t.log().is_empty());
278
279 let todos = user.todos().exec(&mut db).await?;
281 assert!(todos.is_empty());
282
283 Ok(())
284}
285
286#[driver_test(id(ID), requires(sql))]
293pub async fn rmw_uses_savepoints(t: &mut Test) -> Result<()> {
294 #[derive(Debug, toasty::Model)]
295 struct User {
296 #[key]
297 #[auto]
298 id: ID,
299
300 #[has_many]
301 todos: toasty::HasMany<Todo>,
302 }
303
304 #[derive(Debug, toasty::Model)]
305 struct Todo {
306 #[key]
307 #[auto]
308 id: ID,
309
310 #[index]
311 user_id: Option<ID>,
312
313 #[belongs_to(key = user_id, references = id)]
314 user: toasty::BelongsTo<Option<User>>,
315 }
316
317 let mut db = t.setup_db(models!(User, Todo)).await;
318
319 let user = User::create().todo(Todo::create()).exec(&mut db).await?;
320 let todos: Vec<_> = user.todos().exec(&mut db).await?;
321
322 t.log().clear();
323 user.todos().remove(&mut db, &todos[0]).await?;
324
325 if t.capability().cte_with_update {
326 assert_struct!(t.log().pop_op(), Operation::QuerySql(_));
328 } else {
329 assert_struct!(
331 t.log().pop_op(),
332 Operation::Transaction(Transaction::Start {
333 isolation: None,
334 read_only: false,
335 ..
336 })
337 );
338 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
341 t.log().pop_op(),
342 Operation::Transaction(Transaction::Commit)
343 );
344 }
345 assert!(t.log().is_empty());
346
347 Ok(())
348}
349
350#[driver_test(id(ID), requires(sql))]
354pub async fn rmw_condition_failure_issues_rollback_to_savepoint(t: &mut Test) -> Result<()> {
355 #[derive(Debug, toasty::Model)]
356 struct User {
357 #[key]
358 #[auto]
359 id: ID,
360
361 #[has_many]
362 todos: toasty::HasMany<Todo>,
363 }
364
365 #[derive(Debug, toasty::Model)]
366 struct Todo {
367 #[key]
368 #[auto]
369 id: ID,
370
371 #[index]
372 user_id: Option<ID>,
373
374 #[belongs_to(key = user_id, references = id)]
375 user: toasty::BelongsTo<Option<User>>,
376 }
377
378 let mut db = t.setup_db(models!(User, Todo)).await;
379
380 let user1 = User::create().exec(&mut db).await?;
381 let user2 = User::create().todo(Todo::create()).exec(&mut db).await?;
382 let u2_todos: Vec<_> = user2.todos().exec(&mut db).await?;
383
384 t.log().clear();
385 assert_err!(user1.todos().remove(&mut db, &u2_todos[0]).await);
387
388 if t.capability().cte_with_update {
389 assert_struct!(t.log().pop_op(), Operation::QuerySql(_));
391 } else {
392 assert_struct!(
395 t.log().pop_op(),
396 Operation::Transaction(Transaction::Start {
397 isolation: None,
398 read_only: false,
399 ..
400 })
401 );
402 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
404 t.log().pop_op(),
405 Operation::Transaction(Transaction::Rollback)
406 );
407 }
408 assert!(t.log().is_empty());
409
410 let reloaded = Todo::get_by_id(&mut db, u2_todos[0].id).await?;
412 assert_struct!(reloaded, { user_id: Some(== user2.id) });
413
414 Ok(())
415}