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(id(ID), 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(id(ID), 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::QuerySql(_));
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(id(ID), 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::QuerySql(_));
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// TODO: is a batch supposed to be atomic? Probably not.
72#[driver_test(id(ID))]
73#[should_panic]
74pub async fn batch_create_fails_if_any_record_missing_fields(test: &mut Test) -> Result<()> {
75    #[derive(Debug, toasty::Model)]
76    struct User {
77        #[key]
78        email: String,
79
80        #[allow(dead_code)]
81        name: String,
82    }
83
84    let mut db = test.setup_db(models!(User)).await;
85
86    let res = User::create_many()
87        .item(User::create().email("user1@example.com").name("User 1"))
88        .item(User::create().email("user2@example.com"))
89        .exec(&mut db)
90        .await?;
91
92    assert!(res.is_empty());
93
94    let users: Vec<_> = User::filter_by_email("me@carllerche.com")
95        .exec(&mut db)
96        .await?;
97
98    assert!(users.is_empty());
99    Ok(())
100}
101
102#[driver_test(id(ID), scenario(crate::scenarios::user_unique_email))]
103pub async fn batch_create_model_with_unique_field_index_all_unique(test: &mut Test) -> Result<()> {
104    let mut db = setup(test).await;
105
106    let mut res = User::create_many()
107        .item(User::create().email("user1@example.com"))
108        .item(User::create().email("user2@example.com"))
109        .exec(&mut db)
110        .await?;
111
112    assert_eq!(2, res.len());
113
114    res.sort_by_key(|user| user.email.clone());
115
116    assert_eq!(res[0].email, "user1@example.com");
117    assert_eq!(res[1].email, "user2@example.com");
118
119    // We can fetch the user by ID and email
120    for user in &res {
121        let found = User::get_by_id(&mut db, user.id).await?;
122        assert_eq!(found.id, user.id);
123        assert_eq!(found.email, user.email);
124
125        let found = User::get_by_email(&mut db, &user.email).await?;
126        assert_eq!(found.id, user.id);
127        assert_eq!(found.email, user.email);
128    }
129    Ok(())
130}
131
132#[driver_test(id(ID), scenario(crate::scenarios::user_unique_email))]
133#[should_panic]
134pub async fn batch_create_model_with_unique_field_index_all_dups(test: &mut Test) -> Result<()> {
135    let mut db = setup(test).await;
136
137    let _res = User::create_many()
138        .item(User::create().email("user@example.com"))
139        .item(User::create().email("user@example.com"))
140        .exec(&mut db)
141        .await?;
142    Ok(())
143}
144
145/// Unique constraint violation on a multi-row batch is atomic because a single
146/// INSERT statement is inherently atomic in SQL databases.
147#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::user_unique_email))]
148pub async fn batch_create_unique_violation_rolls_back(t: &mut Test) -> Result<()> {
149    let mut db = setup(t).await;
150
151    // Seed the duplicate
152    User::create()
153        .email("taken@example.com")
154        .exec(&mut db)
155        .await?;
156
157    t.log().clear();
158    assert_err!(
159        User::create_many()
160            .item(User::create().email("new@example.com"))
161            .item(User::create().email("taken@example.com"))
162            .exec(&mut db)
163            .await
164    );
165
166    // No transaction wrapper — the single INSERT fails atomically
167    assert!(t.log().is_empty());
168
169    // Only the seeded user remains
170    let users = User::all().exec(&mut db).await?;
171    assert_eq!(1, users.len());
172
173    Ok(())
174}
175
176/// Multi-row batch inside an explicit transaction executes as a single INSERT
177/// without extra savepoint wrapping (the statement is inherently atomic).
178#[driver_test(id(ID), requires(sql), scenario(crate::scenarios::two_models))]
179pub async fn batch_create_inside_transaction_uses_savepoints(t: &mut Test) -> Result<()> {
180    let mut db = setup(t).await;
181
182    t.log().clear();
183    let mut tx = db.transaction().await?;
184
185    assert_struct!(
186        t.log().pop_op(),
187        Operation::Transaction(Transaction::Start {
188            isolation: None,
189            read_only: false,
190            ..
191        })
192    );
193
194    Post::create_many()
195        .item(Post::create().title("a"))
196        .item(Post::create().title("b"))
197        .exec(&mut tx)
198        .await?;
199
200    // Single INSERT statement — no savepoint needed
201    assert_struct!(t.log().pop_op(), Operation::QuerySql(_));
202
203    tx.commit().await?;
204
205    assert_struct!(
206        t.log().pop_op(),
207        Operation::Transaction(Transaction::Commit)
208    );
209    assert!(t.log().is_empty());
210
211    Ok(())
212}