toasty_driver_integration_suite/tests/
has_many_batch_create.rs

1use crate::prelude::*;
2
3#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
4pub async fn user_batch_create_todos_one_level_basic_fk(test: &mut Test) -> Result<()> {
5    let mut db = setup(test).await;
6
7    // Create a user with some todos
8    let user = User::create()
9        .name("Ann Chovey")
10        .todo(Todo::create().title("Make pizza"))
11        .exec(&mut db)
12        .await?;
13
14    assert_eq!(user.name, "Ann Chovey");
15
16    // There are associated TODOs
17    let todos: Vec<_> = user.todos().exec(&mut db).await?;
18    assert_eq!(1, todos.len());
19    assert_eq!("Make pizza", todos[0].title);
20
21    // Find the todo by ID
22    let todo = Todo::get_by_id(&mut db, &todos[0].id).await?;
23    assert_eq!("Make pizza", todo.title);
24    Ok(())
25}
26
27#[driver_test(id(ID))]
28pub async fn user_batch_create_todos_two_levels_basic_fk(test: &mut Test) -> Result<()> {
29    #[derive(Debug, toasty::Model)]
30    struct User {
31        #[key]
32        #[auto]
33        id: ID,
34
35        name: String,
36
37        #[has_many]
38        todos: toasty::HasMany<Todo>,
39    }
40
41    #[derive(Debug, toasty::Model)]
42    struct Todo {
43        #[key]
44        #[auto]
45        id: ID,
46
47        #[index]
48        user_id: ID,
49
50        #[belongs_to(key = user_id, references = id)]
51        user: toasty::BelongsTo<User>,
52
53        #[index]
54        category_id: ID,
55
56        #[belongs_to(key = category_id, references = id)]
57        category: toasty::BelongsTo<Category>,
58
59        title: String,
60    }
61
62    #[derive(Debug, toasty::Model)]
63    struct Category {
64        #[key]
65        #[auto]
66        id: ID,
67
68        name: String,
69
70        #[has_many]
71        todos: toasty::HasMany<Todo>,
72    }
73
74    let mut db = test.setup_db(models!(User, Todo, Category)).await;
75
76    // Create a user with some todos
77    let user = User::create()
78        .name("Ann Chovey")
79        .todo(
80            Todo::create()
81                .title("Make pizza")
82                .category(Category::create().name("Eating")),
83        )
84        .exec(&mut db)
85        .await?;
86    assert_eq!(user.name, "Ann Chovey");
87
88    // There are associated TODOs
89    let todos: Vec<_> = user.todos().exec(&mut db).await?;
90    assert_eq!(1, todos.len());
91    assert_eq!("Make pizza", todos[0].title);
92
93    // Find the todo by ID
94    let todo = Todo::get_by_id(&mut db, &todos[0].id).await?;
95    assert_eq!("Make pizza", todo.title);
96
97    // Find the category by ID
98    let category = Category::get_by_id(&mut db, &todo.category_id).await?;
99    assert_eq!(category.name, "Eating");
100
101    // Create more than one todo per user
102    let user = User::create()
103        .name("John Doe")
104        .todo(
105            Todo::create()
106                .title("do something")
107                .category(Category::create().name("things")),
108        )
109        .todo(
110            Todo::create()
111                .title("do something else")
112                .category(Category::create().name("other things")),
113        )
114        .exec(&mut db)
115        .await?;
116
117    // There are associated TODOs
118    let todos: Vec<_> = user.todos().exec(&mut db).await?;
119    assert_eq_unordered!(
120        todos.iter().map(|todo| &todo.title[..]),
121        ["do something", "do something else"]
122    );
123
124    let mut categories = vec![];
125
126    for todo in &todos {
127        categories.push(todo.category().get(&mut db).await?);
128    }
129
130    assert_eq_unordered!(
131        categories.iter().map(|category| &category.name[..]),
132        ["things", "other things"]
133    );
134
135    let todos: Vec<_> = category.todos().exec(&mut db).await?;
136    assert_eq!(1, todos.len());
137    Ok(())
138}
139
140#[driver_test(id(ID))]
141pub async fn user_batch_create_todos_set_category_by_value(test: &mut Test) -> Result<()> {
142    #[derive(Debug, toasty::Model)]
143    struct User {
144        #[key]
145        #[auto]
146        id: ID,
147
148        name: String,
149
150        #[has_many]
151        todos: toasty::HasMany<Todo>,
152    }
153
154    #[derive(Debug, toasty::Model)]
155    struct Todo {
156        #[key]
157        #[auto]
158        id: ID,
159
160        #[index]
161        user_id: ID,
162
163        #[belongs_to(key = user_id, references = id)]
164        user: toasty::BelongsTo<User>,
165
166        #[index]
167        category_id: ID,
168
169        #[belongs_to(key = category_id, references = id)]
170        category: toasty::BelongsTo<Category>,
171
172        title: String,
173    }
174
175    #[derive(Debug, toasty::Model)]
176    struct Category {
177        #[key]
178        #[auto]
179        id: ID,
180
181        name: String,
182
183        #[has_many]
184        todos: toasty::HasMany<Todo>,
185    }
186
187    let mut db = test.setup_db(models!(User, Todo, Category)).await;
188
189    let category = Category::create().name("Eating").exec(&mut db).await?;
190    assert_eq!(category.name, "Eating");
191
192    let user = User::create()
193        .name("John Doe")
194        .todo(Todo::create().title("Pizza").category(&category))
195        .todo(Todo::create().title("Hamburger").category(&category))
196        .exec(&mut db)
197        .await?;
198
199    assert_eq!(user.name, "John Doe");
200
201    // There are associated TODOs
202    let todos: Vec<_> = user.todos().exec(&mut db).await?;
203    assert_eq_unordered!(
204        todos.iter().map(|todo| &todo.title[..]),
205        ["Pizza", "Hamburger"]
206    );
207
208    for todo in &todos {
209        assert_eq!(todo.category_id, category.id);
210    }
211
212    let todos: Vec<_> = category.todos().exec(&mut db).await?;
213    assert_eq_unordered!(
214        todos.iter().map(|todo| &todo.title[..]),
215        ["Pizza", "Hamburger"]
216    );
217    Ok(())
218}
219
220/// Regression test for batch creation with optional fields
221///
222/// This test reproduces a panic that occurs when:
223/// 1. A parent model has an optional field (e.g., `moto: Option<String>`)
224/// 2. The parent has a has_many relationship with auto-increment IDs
225/// 3. You batch-create multiple associated records in a single operation
226///
227/// The panic occurs at crates/toasty/src/engine/lower/insert.rs:192 with:
228/// "not yet implemented: expr=ExprStmt { ... }"
229///
230/// The issue is in the RETURNING clause constantization code path where
231/// batch inserts with auto-increment fields encounter an Expr::Stmt (nested insert)
232/// that is not yet handled.
233#[driver_test(id(ID))]
234pub async fn user_batch_create_todos_with_optional_field(test: &mut Test) -> Result<()> {
235    #[derive(Debug, toasty::Model)]
236    struct User {
237        #[key]
238        #[auto]
239        id: ID,
240
241        name: String,
242
243        #[has_many]
244        todos: toasty::HasMany<Todo>,
245
246        // This optional field triggers the unimplemented code path!
247        // Without it, the batch create works fine.
248        #[allow(dead_code)]
249        moto: Option<String>,
250    }
251
252    #[derive(Debug, toasty::Model)]
253    struct Todo {
254        #[key]
255        #[auto]
256        id: ID,
257
258        #[index]
259        user_id: ID,
260
261        #[belongs_to(key = user_id, references = id)]
262        user: toasty::BelongsTo<User>,
263
264        title: String,
265    }
266
267    let mut db = test.setup_db(models!(User, Todo)).await;
268
269    // This operation currently panics due to unimplemented code path
270    let user = User::create()
271        .name("Ann Chovey")
272        .todo(Todo::create().title("Make pizza"))
273        .todo(Todo::create().title("Sleep"))
274        .exec(&mut db)
275        .await?;
276
277    assert_eq!(user.name, "Ann Chovey");
278
279    // Verify both todos were created
280    let todos: Vec<_> = user.todos().exec(&mut db).await?;
281    assert_eq!(2, todos.len());
282
283    let mut titles: Vec<_> = todos.iter().map(|t| &t.title[..]).collect();
284    titles.sort();
285    assert_eq!(titles, vec!["Make pizza", "Sleep"]);
286    Ok(())
287}
288
289#[driver_test(id(ID))]
290pub async fn user_batch_create_two_todos_simple(test: &mut Test) -> Result<()> {
291    #[derive(Debug, toasty::Model)]
292    struct User {
293        #[key]
294        #[auto]
295        id: ID,
296
297        name: String,
298
299        #[unique]
300        #[allow(dead_code)]
301        email: String,
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: ID,
315
316        #[belongs_to(key = user_id, references = id)]
317        user: toasty::BelongsTo<User>,
318
319        title: String,
320    }
321
322    let mut db = test.setup_db(models!(User, Todo)).await;
323
324    // Create a user with two todos in a single operation
325    let user = User::create()
326        .name("Ann Chovey")
327        .email("ann.chovey@example.com")
328        .todo(Todo::create().title("Make pizza"))
329        .todo(Todo::create().title("Sleep"))
330        .exec(&mut db)
331        .await?;
332
333    assert_eq!(user.name, "Ann Chovey");
334
335    // There should be 2 associated TODOs
336    let todos: Vec<_> = user.todos().exec(&mut db).await?;
337    assert_eq!(2, todos.len());
338
339    // Verify the titles
340    let mut titles: Vec<_> = todos.iter().map(|t| &t.title[..]).collect();
341    titles.sort();
342    assert_eq!(titles, vec!["Make pizza", "Sleep"]);
343    Ok(())
344}