Skip to main content

toasty_driver_integration_suite/tests/
batch_rollback.rs

1use crate::prelude::*;
2
3use toasty_core::driver::{Operation, operation::Transaction};
4
5/// When a batch of two creates fails on the second INSERT (unique constraint
6/// violation), the entire batch is rolled back — the first INSERT must not
7/// persist.
8#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::user_unique_email))]
9pub async fn batch_two_creates_rolls_back_on_second_failure(t: &mut Test) -> Result<()> {
10    let mut db = setup(t).await;
11
12    // Seed the email that will cause the second create to fail.
13    User::create()
14        .email("taken@example.com")
15        .exec(&mut db)
16        .await?;
17
18    t.log().clear();
19    assert_err!(
20        toasty::batch((
21            User::create().email("new@example.com"),
22            User::create().email("taken@example.com"),
23        ))
24        .exec(&mut db)
25        .await
26    );
27
28    // BEGIN → INSERT (succeeds) → INSERT (fails, not logged) → ROLLBACK
29    assert_struct!(
30        t.log().pop_op(),
31        Operation::Transaction(Transaction::Start {
32            isolation: None,
33            read_only: false,
34            ..
35        })
36    );
37    assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); // first INSERT
38    assert_struct!(
39        t.log().pop_op(),
40        Operation::Transaction(Transaction::Rollback)
41    );
42    assert!(t.log().is_empty());
43
44    // Only the seeded user remains — "new@example.com" was rolled back
45    let users: Vec<User> = User::all().exec(&mut db).await?;
46    assert_eq!(1, users.len());
47    assert_eq!(users[0].email, "taken@example.com");
48
49    Ok(())
50}
51
52/// When a batch of a create + update fails on the update (unique constraint),
53/// the successful create is rolled back.
54#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::user_unique_email))]
55pub async fn batch_create_and_update_rolls_back_on_update_failure(t: &mut Test) -> Result<()> {
56    let mut db = setup(t).await;
57
58    User::create()
59        .email("alice@example.com")
60        .exec(&mut db)
61        .await?;
62    User::create()
63        .email("taken@example.com")
64        .exec(&mut db)
65        .await?;
66
67    t.log().clear();
68    assert_err!(
69        toasty::batch((
70            User::create().email("bob@example.com"),
71            User::filter_by_email("alice@example.com")
72                .update()
73                .email("taken@example.com"), // fails: unique
74        ))
75        .exec(&mut db)
76        .await
77    );
78
79    // BEGIN → INSERT bob (succeeds) → UPDATE alice (fails) → ROLLBACK
80    assert_struct!(
81        t.log().pop_op(),
82        Operation::Transaction(Transaction::Start {
83            isolation: None,
84            read_only: false,
85            ..
86        })
87    );
88    assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); // INSERT
89    assert_struct!(
90        t.log().pop_op(),
91        Operation::Transaction(Transaction::Rollback)
92    );
93    assert!(t.log().is_empty());
94
95    // "bob" was rolled back and "alice" was not renamed
96    let all: Vec<User> = User::all().exec(&mut db).await?;
97    assert_eq!(2, all.len());
98    let emails: hashbrown::HashSet<_> = all.iter().map(|u| u.email.as_str()).collect();
99    assert!(emails.contains("alice@example.com"));
100    assert!(emails.contains("taken@example.com"));
101
102    Ok(())
103}
104
105/// When a batch of an update + create fails on the create (unique constraint),
106/// the successful update is rolled back.
107#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::user_unique_email))]
108pub async fn batch_update_and_create_rolls_back_on_create_failure(t: &mut Test) -> Result<()> {
109    let mut db = setup(t).await;
110
111    User::create()
112        .email("alice@example.com")
113        .exec(&mut db)
114        .await?;
115    User::create()
116        .email("taken@example.com")
117        .exec(&mut db)
118        .await?;
119
120    t.log().clear();
121    assert_err!(
122        toasty::batch((
123            User::filter_by_email("alice@example.com")
124                .update()
125                .email("alice2@example.com"),
126            User::create().email("taken@example.com"), // fails: unique
127        ))
128        .exec(&mut db)
129        .await
130    );
131
132    // BEGIN → UPDATE (succeeds) → INSERT (fails) → ROLLBACK
133    assert_struct!(
134        t.log().pop_op(),
135        Operation::Transaction(Transaction::Start {
136            isolation: None,
137            read_only: false,
138            ..
139        })
140    );
141    assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); // UPDATE
142    assert_struct!(
143        t.log().pop_op(),
144        Operation::Transaction(Transaction::Rollback)
145    );
146    assert!(t.log().is_empty());
147
148    // Update was rolled back — "alice" still has her original email
149    let alice: Vec<User> = User::filter_by_email("alice@example.com")
150        .exec(&mut db)
151        .await?;
152    assert_eq!(1, alice.len());
153
154    // No "alice2@example.com" exists
155    let alice2: Vec<User> = User::filter_by_email("alice2@example.com")
156        .exec(&mut db)
157        .await?;
158    assert!(alice2.is_empty());
159
160    Ok(())
161}
162
163/// When a batch of array creates fails on one element (unique constraint),
164/// all prior successful creates are rolled back.
165#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::user_unique_email))]
166pub async fn batch_array_creates_rolls_back_on_failure(t: &mut Test) -> Result<()> {
167    let mut db = setup(t).await;
168
169    // Seed the collision
170    User::create()
171        .email("taken@example.com")
172        .exec(&mut db)
173        .await?;
174
175    t.log().clear();
176    assert_err!(
177        toasty::batch([
178            User::create().email("first@example.com"),
179            User::create().email("second@example.com"),
180            User::create().email("taken@example.com"), // fails: unique
181        ])
182        .exec(&mut db)
183        .await
184    );
185
186    // BEGIN → INSERT first → INSERT second → INSERT taken (fails) → ROLLBACK
187    assert_struct!(
188        t.log().pop_op(),
189        Operation::Transaction(Transaction::Start {
190            isolation: None,
191            read_only: false,
192            ..
193        })
194    );
195    assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); // INSERT first
196    assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); // INSERT second
197    assert_struct!(
198        t.log().pop_op(),
199        Operation::Transaction(Transaction::Rollback)
200    );
201    assert!(t.log().is_empty());
202
203    // Only the seeded user remains
204    let users: Vec<User> = User::all().exec(&mut db).await?;
205    assert_eq!(1, users.len());
206    assert_eq!(users[0].email, "taken@example.com");
207
208    Ok(())
209}
210
211/// When a batch of different models fails on the second create, the first
212/// model's create is rolled back too.
213#[driver_test(id(ID), requires(sql))]
214pub async fn batch_different_models_rolls_back_on_failure(t: &mut Test) -> Result<()> {
215    #[derive(Debug, toasty::Model)]
216    struct User {
217        #[key]
218        #[auto]
219        id: ID,
220        name: String,
221    }
222
223    #[derive(Debug, toasty::Model)]
224    struct Post {
225        #[key]
226        #[auto]
227        id: ID,
228
229        #[unique]
230        title: String,
231    }
232
233    let mut db = t.setup_db(models!(User, Post)).await;
234
235    // Seed the collision
236    Post::create().title("taken").exec(&mut db).await?;
237
238    t.log().clear();
239    assert_err!(
240        toasty::batch((
241            User::create().name("alice"),
242            Post::create().title("taken"), // fails: unique
243        ))
244        .exec(&mut db)
245        .await
246    );
247
248    // BEGIN → INSERT user (succeeds) → INSERT post (fails) → ROLLBACK
249    assert_struct!(
250        t.log().pop_op(),
251        Operation::Transaction(Transaction::Start {
252            isolation: None,
253            read_only: false,
254            ..
255        })
256    );
257    assert_struct!(t.log().pop_op(), Operation::QuerySql(_)); // INSERT user
258    assert_struct!(
259        t.log().pop_op(),
260        Operation::Transaction(Transaction::Rollback)
261    );
262    assert!(t.log().is_empty());
263
264    // No user was persisted
265    let users: Vec<User> = User::all().exec(&mut db).await?;
266    assert!(users.is_empty());
267
268    // Only the seeded post remains
269    let posts: Vec<Post> = Post::all().exec(&mut db).await?;
270    assert_eq!(1, posts.len());
271
272    Ok(())
273}