Skip to main content

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