toasty_driver_integration_suite/tests/
tx_atomic_stmt.rs1use crate::prelude::*;
2
3use toasty_core::driver::{operation::Transaction, Operation};
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 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
30 t.log().pop_op(),
31 Operation::Transaction(Transaction::Commit)
32 );
33 assert!(t.log().is_empty());
34
35 let todos = user.todos().exec(&mut db).await?;
36 assert_eq!(1, todos.len());
37
38 Ok(())
39}
40
41#[driver_test(id(ID), requires(sql))]
44pub async fn single_op_skips_transaction(t: &mut Test) -> Result<()> {
45 #[derive(Debug, toasty::Model)]
46 struct User {
47 #[key]
48 #[auto]
49 id: ID,
50 }
51
52 let mut db = t.setup_db(models!(User)).await;
53
54 t.log().clear();
55 User::create().exec(&mut db).await?;
56
57 assert_struct!(t.log().pop_op(), Operation::QuerySql(_));
59 assert!(t.log().is_empty());
60
61 Ok(())
62}
63
64#[driver_test(requires(sql))]
76pub async fn create_with_has_many_rolls_back_on_failure(t: &mut Test) -> Result<()> {
77 #[derive(Debug, toasty::Model)]
78 struct User {
79 #[key]
80 #[auto]
81 id: u64,
82
83 #[has_many]
84 todos: toasty::HasMany<Todo>,
85 }
86
87 #[derive(Debug, toasty::Model)]
88 struct Todo {
89 #[key]
90 #[auto]
91 id: u64,
92
93 #[index]
94 user_id: u64,
95
96 #[belongs_to(key = user_id, references = id)]
97 user: toasty::BelongsTo<User>,
98
99 #[unique]
100 title: String,
101 }
102
103 let mut db = t.setup_db(models!(User, Todo)).await;
104
105 User::create()
107 .todo(Todo::create().title("taken"))
108 .exec(&mut db)
109 .await?;
110
111 t.log().clear();
112 assert_err!(
113 User::create()
114 .todo(Todo::create().title("taken"))
115 .exec(&mut db)
116 .await
117 );
118
119 assert_struct!(
122 t.log().pop_op(),
123 Operation::Transaction(Transaction::Start {
124 isolation: None,
125 read_only: false
126 })
127 );
128 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
130 t.log().pop_op(),
131 Operation::Transaction(Transaction::Rollback)
132 );
133 assert!(t.log().is_empty());
134
135 let users = User::all().exec(&mut db).await?;
137 assert_eq!(1, users.len());
138
139 Ok(())
140}
141
142#[driver_test(requires(sql))]
150pub async fn create_with_has_one_rolls_back_on_failure(t: &mut Test) -> Result<()> {
151 #[derive(Debug, toasty::Model)]
152 struct User {
153 #[key]
154 #[auto]
155 id: u64,
156
157 #[has_one]
158 profile: toasty::HasOne<Option<Profile>>,
159 }
160
161 #[derive(Debug, toasty::Model)]
162 struct Profile {
163 #[key]
164 #[auto]
165 id: u64,
166
167 #[unique]
168 bio: String,
169
170 #[unique]
171 user_id: u64,
172
173 #[belongs_to(key = user_id, references = id)]
174 user: toasty::BelongsTo<User>,
175 }
176
177 let mut db = t.setup_db(models!(User, Profile)).await;
178
179 User::create()
181 .profile(Profile::create().bio("taken"))
182 .exec(&mut db)
183 .await?;
184
185 t.log().clear();
186 assert_err!(
187 User::create()
188 .profile(Profile::create().bio("taken"))
189 .exec(&mut db)
190 .await
191 );
192
193 assert_struct!(
194 t.log().pop_op(),
195 Operation::Transaction(Transaction::Start {
196 isolation: None,
197 read_only: false
198 })
199 );
200 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
202 t.log().pop_op(),
203 Operation::Transaction(Transaction::Rollback)
204 );
205 assert!(t.log().is_empty());
206
207 let users = User::all().exec(&mut db).await?;
209 assert_eq!(1, users.len());
210
211 Ok(())
212}
213
214#[driver_test(id(ID), requires(sql))]
222pub async fn update_with_new_association_rolls_back_on_failure(t: &mut Test) -> Result<()> {
223 #[derive(Debug, toasty::Model)]
224 struct User {
225 #[key]
226 #[auto]
227 id: ID,
228
229 #[unique]
230 name: String,
231
232 #[has_many]
233 todos: toasty::HasMany<Todo>,
234 }
235
236 #[derive(Debug, toasty::Model)]
237 struct Todo {
238 #[key]
239 #[auto]
240 id: ID,
241
242 #[index]
243 user_id: ID,
244
245 #[belongs_to(key = user_id, references = id)]
246 user: toasty::BelongsTo<User>,
247
248 title: String,
249 }
250
251 let mut db = t.setup_db(models!(User, Todo)).await;
252
253 let mut user = User::create().name("original").exec(&mut db).await?;
254 User::create().name("taken").exec(&mut db).await?;
256
257 t.log().clear();
258 assert_err!(
259 user.update()
260 .name("taken") .todo(Todo::create().title("new-todo")) .exec(&mut db)
263 .await
264 );
265
266 assert_struct!(
269 t.log().pop_op(),
270 Operation::Transaction(Transaction::Start {
271 isolation: None,
272 read_only: false
273 })
274 );
275 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
277 t.log().pop_op(),
278 Operation::Transaction(Transaction::Rollback)
279 );
280 assert!(t.log().is_empty());
281
282 let todos = user.todos().exec(&mut db).await?;
284 assert!(todos.is_empty());
285
286 Ok(())
287}
288
289#[driver_test(id(ID), requires(sql))]
296pub async fn rmw_uses_savepoints(t: &mut Test) -> Result<()> {
297 #[derive(Debug, toasty::Model)]
298 struct User {
299 #[key]
300 #[auto]
301 id: ID,
302
303 #[has_many]
304 todos: toasty::HasMany<Todo>,
305 }
306
307 #[derive(Debug, toasty::Model)]
308 struct Todo {
309 #[key]
310 #[auto]
311 id: ID,
312
313 #[index]
314 user_id: Option<ID>,
315
316 #[belongs_to(key = user_id, references = id)]
317 user: toasty::BelongsTo<Option<User>>,
318 }
319
320 let mut db = t.setup_db(models!(User, Todo)).await;
321
322 let user = User::create().todo(Todo::create()).exec(&mut db).await?;
323 let todos: Vec<_> = user.todos().exec(&mut db).await?;
324
325 t.log().clear();
326 user.todos().remove(&mut db, &todos[0]).await?;
327
328 if t.capability().cte_with_update {
329 assert_struct!(t.log().pop_op(), Operation::QuerySql(_));
331 } else {
332 assert_struct!(
334 t.log().pop_op(),
335 Operation::Transaction(Transaction::Start {
336 isolation: None,
337 read_only: false
338 })
339 );
340 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
343 t.log().pop_op(),
344 Operation::Transaction(Transaction::Commit)
345 );
346 }
347 assert!(t.log().is_empty());
348
349 Ok(())
350}
351
352#[driver_test(id(ID), requires(sql))]
356pub async fn rmw_condition_failure_issues_rollback_to_savepoint(t: &mut Test) -> Result<()> {
357 #[derive(Debug, toasty::Model)]
358 struct User {
359 #[key]
360 #[auto]
361 id: ID,
362
363 #[has_many]
364 todos: toasty::HasMany<Todo>,
365 }
366
367 #[derive(Debug, toasty::Model)]
368 struct Todo {
369 #[key]
370 #[auto]
371 id: ID,
372
373 #[index]
374 user_id: Option<ID>,
375
376 #[belongs_to(key = user_id, references = id)]
377 user: toasty::BelongsTo<Option<User>>,
378 }
379
380 let mut db = t.setup_db(models!(User, Todo)).await;
381
382 let user1 = User::create().exec(&mut db).await?;
383 let user2 = User::create().todo(Todo::create()).exec(&mut db).await?;
384 let u2_todos: Vec<_> = user2.todos().exec(&mut db).await?;
385
386 t.log().clear();
387 assert_err!(user1.todos().remove(&mut db, &u2_todos[0]).await);
389
390 if t.capability().cte_with_update {
391 assert_struct!(t.log().pop_op(), Operation::QuerySql(_));
393 } else {
394 assert_struct!(
397 t.log().pop_op(),
398 Operation::Transaction(Transaction::Start {
399 isolation: None,
400 read_only: false
401 })
402 );
403 assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); assert_struct!(
405 t.log().pop_op(),
406 Operation::Transaction(Transaction::Rollback)
407 );
408 }
409 assert!(t.log().is_empty());
410
411 let reloaded = Todo::get_by_id(&mut db, u2_todos[0].id).await?;
413 assert_struct!(reloaded, _ { user_id: Some(== user2.id), .. });
414
415 Ok(())
416}