toasty_driver_integration_suite/tests/
batch_rollback.rs

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