toasty_driver_integration_suite/tests/
relation_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), scenario(crate::scenarios::has_many_multi_relation))]
28pub async fn user_batch_create_todos_two_levels_basic_fk(test: &mut Test) -> Result<()> {
29    let mut db = setup(test).await;
30
31    // Create a user with some todos
32    let user = User::create()
33        .name("Ann Chovey")
34        .todo(
35            Todo::create()
36                .title("Make pizza")
37                .category(Category::create().name("Eating")),
38        )
39        .exec(&mut db)
40        .await?;
41    assert_eq!(user.name, "Ann Chovey");
42
43    // There are associated TODOs
44    let todos: Vec<_> = user.todos().exec(&mut db).await?;
45    assert_eq!(1, todos.len());
46    assert_eq!("Make pizza", todos[0].title);
47
48    // Find the todo by ID
49    let todo = Todo::get_by_id(&mut db, &todos[0].id).await?;
50    assert_eq!("Make pizza", todo.title);
51
52    // Find the category by ID
53    let category = Category::get_by_id(&mut db, &todo.category_id).await?;
54    assert_eq!(category.name, "Eating");
55
56    // Create more than one todo per user
57    let user = User::create()
58        .name("John Doe")
59        .todo(
60            Todo::create()
61                .title("do something")
62                .category(Category::create().name("things")),
63        )
64        .todo(
65            Todo::create()
66                .title("do something else")
67                .category(Category::create().name("other things")),
68        )
69        .exec(&mut db)
70        .await?;
71
72    // There are associated TODOs
73    let todos: Vec<_> = user.todos().exec(&mut db).await?;
74    assert_eq_unordered!(
75        todos.iter().map(|todo| &todo.title[..]),
76        ["do something", "do something else"]
77    );
78
79    let mut categories = vec![];
80
81    for todo in &todos {
82        categories.push(todo.category().exec(&mut db).await?);
83    }
84
85    assert_eq_unordered!(
86        categories.iter().map(|category| &category.name[..]),
87        ["things", "other things"]
88    );
89
90    let todos: Vec<_> = category.todos().exec(&mut db).await?;
91    assert_eq!(1, todos.len());
92    Ok(())
93}
94
95#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
96pub async fn user_batch_create_todos_set_category_by_value(test: &mut Test) -> Result<()> {
97    let mut db = setup(test).await;
98
99    let category = Category::create().name("Eating").exec(&mut db).await?;
100    assert_eq!(category.name, "Eating");
101
102    let user = User::create()
103        .name("John Doe")
104        .todo(Todo::create().title("Pizza").category(&category))
105        .todo(Todo::create().title("Hamburger").category(&category))
106        .exec(&mut db)
107        .await?;
108
109    assert_eq!(user.name, "John Doe");
110
111    // There are associated TODOs
112    let todos: Vec<_> = user.todos().exec(&mut db).await?;
113    assert_eq_unordered!(
114        todos.iter().map(|todo| &todo.title[..]),
115        ["Pizza", "Hamburger"]
116    );
117
118    for todo in &todos {
119        assert_eq!(todo.category_id, category.id);
120    }
121
122    let todos: Vec<_> = category.todos().exec(&mut db).await?;
123    assert_eq_unordered!(
124        todos.iter().map(|todo| &todo.title[..]),
125        ["Pizza", "Hamburger"]
126    );
127    Ok(())
128}
129
130/// Regression test for batch creation with optional fields
131///
132/// This test reproduces a panic that occurs when:
133/// 1. A parent model has an optional field (e.g., `moto: Option<String>`)
134/// 2. The parent has a has_many relationship with auto-increment IDs
135/// 3. You batch-create multiple associated records in a single operation
136///
137/// The panic occurs at crates/toasty/src/engine/lower/insert.rs:192 with:
138/// "not yet implemented: expr=ExprStmt { ... }"
139///
140/// The issue is in the RETURNING clause constantization code path where
141/// batch inserts with auto-increment fields encounter an Expr::Stmt (nested insert)
142/// that is not yet handled.
143#[driver_test(id(ID))]
144pub async fn user_batch_create_todos_with_optional_field(test: &mut Test) -> Result<()> {
145    #[derive(Debug, toasty::Model)]
146    struct User {
147        #[key]
148        #[auto]
149        id: ID,
150
151        name: String,
152
153        #[has_many]
154        todos: toasty::HasMany<Todo>,
155
156        // This optional field triggers the unimplemented code path!
157        // Without it, the batch create works fine.
158        #[allow(dead_code)]
159        moto: Option<String>,
160    }
161
162    #[derive(Debug, toasty::Model)]
163    struct Todo {
164        #[key]
165        #[auto]
166        id: ID,
167
168        #[index]
169        user_id: ID,
170
171        #[belongs_to(key = user_id, references = id)]
172        user: toasty::BelongsTo<User>,
173
174        title: String,
175    }
176
177    let mut db = test.setup_db(models!(User, Todo)).await;
178
179    // This operation currently panics due to unimplemented code path
180    let user = User::create()
181        .name("Ann Chovey")
182        .todo(Todo::create().title("Make pizza"))
183        .todo(Todo::create().title("Sleep"))
184        .exec(&mut db)
185        .await?;
186
187    assert_eq!(user.name, "Ann Chovey");
188
189    // Verify both todos were created
190    let todos: Vec<_> = user.todos().exec(&mut db).await?;
191    assert_eq!(2, todos.len());
192
193    let mut titles: Vec<_> = todos.iter().map(|t| &t.title[..]).collect();
194    titles.sort();
195    assert_eq!(titles, vec!["Make pizza", "Sleep"]);
196    Ok(())
197}
198
199#[driver_test(id(ID))]
200pub async fn user_batch_create_two_todos_simple(test: &mut Test) -> Result<()> {
201    #[derive(Debug, toasty::Model)]
202    struct User {
203        #[key]
204        #[auto]
205        id: ID,
206
207        name: String,
208
209        #[unique]
210        #[allow(dead_code)]
211        email: String,
212
213        #[has_many]
214        todos: toasty::HasMany<Todo>,
215    }
216
217    #[derive(Debug, toasty::Model)]
218    struct Todo {
219        #[key]
220        #[auto]
221        id: ID,
222
223        #[index]
224        user_id: ID,
225
226        #[belongs_to(key = user_id, references = id)]
227        user: toasty::BelongsTo<User>,
228
229        title: String,
230    }
231
232    let mut db = test.setup_db(models!(User, Todo)).await;
233
234    // Create a user with two todos in a single operation
235    let user = User::create()
236        .name("Ann Chovey")
237        .email("ann.chovey@example.com")
238        .todo(Todo::create().title("Make pizza"))
239        .todo(Todo::create().title("Sleep"))
240        .exec(&mut db)
241        .await?;
242
243    assert_eq!(user.name, "Ann Chovey");
244
245    // There should be 2 associated TODOs
246    let todos: Vec<_> = user.todos().exec(&mut db).await?;
247    assert_eq!(2, todos.len());
248
249    // Verify the titles
250    let mut titles: Vec<_> = todos.iter().map(|t| &t.title[..]).collect();
251    titles.sort();
252    assert_eq!(titles, vec!["Make pizza", "Sleep"]);
253    Ok(())
254}