Skip to main content

toasty_driver_integration_suite/tests/
crud_batch_create.rs

1//! Test batch creation of models
2
3use crate::prelude::*;
4
5use toasty_core::driver::{Operation, operation::Transaction};
6
7#[driver_test(scenario(crate::scenarios::two_models))]
8pub async fn batch_create_empty(test: &mut Test) -> Result<()> {
9    let mut db = setup(test).await;
10
11    let res = Post::create_many().exec(&mut db).await?;
12    assert!(res.is_empty());
13    Ok(())
14}
15
16#[driver_test(scenario(crate::scenarios::two_models))]
17pub async fn batch_create_one(test: &mut Test) -> Result<()> {
18    let mut db = setup(test).await;
19
20    test.log().clear();
21    let res = Post::create_many()
22        .item(Post::create().title("hello"))
23        .exec(&mut db)
24        .await?;
25
26    assert_eq!(1, res.len());
27    assert_eq!(res[0].title, "hello");
28
29    // Single-row batch: no transaction wrapping needed
30    if test.capability().sql() {
31        assert_struct!(test.log().pop_op(), Operation::Insert(_));
32        assert!(test.log().is_empty());
33    }
34
35    let reloaded: Vec<_> = Post::filter_by_id(res[0].id).exec(&mut db).await?;
36    assert_eq!(1, reloaded.len());
37    assert_eq!(reloaded[0].id, res[0].id);
38    Ok(())
39}
40
41#[driver_test(scenario(crate::scenarios::two_models))]
42pub async fn batch_create_many(test: &mut Test) -> Result<()> {
43    let mut db = setup(test).await;
44
45    test.log().clear();
46    let res = Post::create_many()
47        .item(Post::create().title("todo 1"))
48        .item(Post::create().title("todo 2"))
49        .exec(&mut db)
50        .await?;
51
52    assert_eq!(2, res.len());
53    assert_eq!(res[0].title, "todo 1");
54    assert_eq!(res[1].title, "todo 2");
55
56    // Multi-row batch in a single INSERT statement: no transaction wrapping
57    // needed because single SQL statements are inherently atomic.
58    if test.capability().sql() {
59        assert_struct!(test.log().pop_op(), Operation::Insert(_));
60        assert!(test.log().is_empty());
61    }
62
63    for post in &res {
64        let reloaded: Vec<_> = Post::filter_by_id(post.id).exec(&mut db).await?;
65        assert_eq!(1, reloaded.len());
66        assert_eq!(reloaded[0].id, post.id);
67    }
68    Ok(())
69}
70
71#[driver_test(requires(and(auto_increment, returning_from_mutation)))]
72pub async fn batch_create_many_auto_increment(test: &mut Test) -> Result<()> {
73    #[derive(Debug, toasty::Model)]
74    struct Item {
75        #[key]
76        #[auto]
77        id: u64,
78
79        name: String,
80    }
81
82    let mut db = test.setup_db(models!(Item)).await;
83    let items = Item::create_many()
84        .item(Item::create().name("one"))
85        .item(Item::create().name("two"))
86        .exec(&mut db)
87        .await?;
88
89    assert_struct!(items, [{ id: 1, name: "one" }, { id: 2, name: "two" }]);
90
91    Ok(())
92}
93
94#[driver_test(requires(and(auto_increment, not(returning_from_mutation))))]
95pub async fn batch_create_many_auto_increment_requires_returning(test: &mut Test) -> Result<()> {
96    #[derive(Debug, toasty::Model)]
97    struct Generated {
98        #[key]
99        #[auto]
100        id: u64,
101
102        name: String,
103    }
104
105    #[derive(Debug, toasty::Model)]
106    struct Manual {
107        #[key]
108        id: u64,
109
110        name: String,
111    }
112
113    let mut db = test.setup_db(models!(Generated, Manual)).await;
114
115    test.log().clear();
116    let err = assert_err!(
117        Generated::create_many()
118            .item(Generated::create().name("one"))
119            .item(Generated::create().name("two"))
120            .exec(&mut db)
121            .await
122    );
123
124    assert!(err.is_unsupported_feature());
125    assert!(err.to_string().contains(test.capability().driver_name));
126    assert!(err.to_string().contains("generateds.id"));
127    assert!(test.log().is_empty());
128
129    let items = Manual::create_many()
130        .item(Manual::create().id(10).name("one"))
131        .item(Manual::create().id(20).name("two"))
132        .exec(&mut db)
133        .await?;
134
135    assert_struct!(items, [{ id: 10, name: "one" }, { id: 20, name: "two" }]);
136    assert_struct!(test.log().pop_op(), Operation::Insert({ ret: None, .. }));
137    assert!(test.log().is_empty());
138
139    Ok(())
140}
141
142// TODO: is a batch supposed to be atomic? Probably not.
143#[driver_test]
144#[should_panic]
145pub async fn batch_create_fails_if_any_record_missing_fields(test: &mut Test) -> Result<()> {
146    #[derive(Debug, toasty::Model)]
147    struct User {
148        #[key]
149        email: String,
150
151        #[allow(dead_code)]
152        name: String,
153    }
154
155    let mut db = test.setup_db(models!(User)).await;
156
157    let res = User::create_many()
158        .item(User::create().email("user1@example.com").name("User 1"))
159        .item(User::create().email("user2@example.com"))
160        .exec(&mut db)
161        .await?;
162
163    assert!(res.is_empty());
164
165    let users: Vec<_> = User::filter_by_email("me@carllerche.com")
166        .exec(&mut db)
167        .await?;
168
169    assert!(users.is_empty());
170    Ok(())
171}
172
173#[driver_test(scenario(crate::scenarios::user_unique_email))]
174pub async fn batch_create_model_with_unique_field_index_all_unique(test: &mut Test) -> Result<()> {
175    let mut db = setup(test).await;
176
177    let mut res = User::create_many()
178        .item(User::create().email("user1@example.com"))
179        .item(User::create().email("user2@example.com"))
180        .exec(&mut db)
181        .await?;
182
183    assert_eq!(2, res.len());
184
185    res.sort_by_key(|user| user.email.clone());
186
187    assert_eq!(res[0].email, "user1@example.com");
188    assert_eq!(res[1].email, "user2@example.com");
189
190    // We can fetch the user by ID and email
191    for user in &res {
192        let found = User::get_by_id(&mut db, user.id).await?;
193        assert_eq!(found.id, user.id);
194        assert_eq!(found.email, user.email);
195
196        let found = User::get_by_email(&mut db, &user.email).await?;
197        assert_eq!(found.id, user.id);
198        assert_eq!(found.email, user.email);
199    }
200    Ok(())
201}
202
203#[driver_test(scenario(crate::scenarios::user_unique_email))]
204#[should_panic]
205pub async fn batch_create_model_with_unique_field_index_all_dups(test: &mut Test) -> Result<()> {
206    let mut db = setup(test).await;
207
208    let _res = User::create_many()
209        .item(User::create().email("user@example.com"))
210        .item(User::create().email("user@example.com"))
211        .exec(&mut db)
212        .await?;
213    Ok(())
214}
215
216/// Unique constraint violation on a multi-row batch is atomic because a single
217/// INSERT statement is inherently atomic in SQL databases.
218#[driver_test(requires(sql), scenario(crate::scenarios::user_unique_email))]
219pub async fn batch_create_unique_violation_rolls_back(t: &mut Test) -> Result<()> {
220    let mut db = setup(t).await;
221
222    // Seed the duplicate
223    User::create()
224        .email("taken@example.com")
225        .exec(&mut db)
226        .await?;
227
228    t.log().clear();
229    assert_err!(
230        User::create_many()
231            .item(User::create().email("new@example.com"))
232            .item(User::create().email("taken@example.com"))
233            .exec(&mut db)
234            .await
235    );
236
237    // No transaction wrapper — the single INSERT fails atomically
238    assert!(t.log().is_empty());
239
240    // Only the seeded user remains
241    let users = User::all().exec(&mut db).await?;
242    assert_eq!(1, users.len());
243
244    Ok(())
245}
246
247/// Multi-row batch inside an explicit transaction executes as a single INSERT
248/// without extra savepoint wrapping (the statement is inherently atomic).
249#[driver_test(requires(sql), scenario(crate::scenarios::two_models))]
250pub async fn batch_create_inside_transaction_uses_savepoints(t: &mut Test) -> Result<()> {
251    let mut db = setup(t).await;
252
253    t.log().clear();
254    let mut tx = db.transaction().await?;
255
256    assert_struct!(
257        t.log().pop_op(),
258        Operation::Transaction(Transaction::Start {
259            isolation: None,
260            read_only: false,
261            ..
262        })
263    );
264
265    Post::create_many()
266        .item(Post::create().title("a"))
267        .item(Post::create().title("b"))
268        .exec(&mut tx)
269        .await?;
270
271    // Single INSERT statement — no savepoint needed
272    assert_struct!(t.log().pop_op(), Operation::Insert(_));
273
274    tx.commit().await?;
275
276    assert_struct!(
277        t.log().pop_op(),
278        Operation::Transaction(Transaction::Commit)
279    );
280    assert!(t.log().is_empty());
281
282    Ok(())
283}