Skip to main content

toasty_driver_integration_suite/tests/
batch_associations.rs

1//! Test batching association-scoped statements (create, query, update, delete)
2//! through `toasty::batch()`.
3
4use crate::prelude::*;
5
6/// Batch two association-scoped creates on the same relation.
7#[driver_test(
8    id(ID),
9    requires(scan),
10    scenario(crate::scenarios::has_many_belongs_to)
11)]
12pub async fn batch_two_scoped_creates_same_relation(t: &mut Test) -> Result<()> {
13    let mut db = setup(t).await;
14    let user = User::create().name("Alice").exec(&mut db).await?;
15
16    let (t1, t2): (Todo, Todo) = toasty::batch((
17        user.todos().create().title("first"),
18        user.todos().create().title("second"),
19    ))
20    .exec(&mut db)
21    .await?;
22
23    assert_eq!(t1.title, "first");
24    assert_eq!(t2.title, "second");
25    assert_eq!(t1.user_id, user.id);
26    assert_eq!(t2.user_id, user.id);
27
28    let all: Vec<Todo> = user.todos().exec(&mut db).await?;
29    assert_eq!(all.len(), 2);
30
31    Ok(())
32}
33
34/// Batch two association-scoped queries on the same relation.
35#[driver_test(
36    id(ID),
37    requires(scan),
38    scenario(crate::scenarios::has_many_belongs_to)
39)]
40pub async fn batch_two_scoped_queries_same_relation(t: &mut Test) -> Result<()> {
41    let mut db = setup(t).await;
42
43    let u1 = User::create().name("u1").exec(&mut db).await?;
44    let u2 = User::create().name("u2").exec(&mut db).await?;
45    u1.todos().create().title("u1 todo").exec(&mut db).await?;
46    u2.todos().create().title("u2 todo").exec(&mut db).await?;
47
48    let (u1_todos, u2_todos): (Vec<Todo>, Vec<Todo>) = toasty::batch((u1.todos(), u2.todos()))
49        .exec(&mut db)
50        .await?;
51
52    assert_struct!(u1_todos, [{ title: "u1 todo" }]);
53    assert_struct!(u2_todos, [{ title: "u2 todo" }]);
54
55    Ok(())
56}
57
58/// Batch association-scoped update and delete on the same relation.
59#[driver_test(
60    id(ID),
61    requires(scan),
62    scenario(crate::scenarios::has_many_belongs_to)
63)]
64pub async fn batch_scoped_update_and_delete_same_relation(t: &mut Test) -> Result<()> {
65    let mut db = setup(t).await;
66    let user = User::create().name("Alice").exec(&mut db).await?;
67    let todo_keep = user.todos().create().title("keep").exec(&mut db).await?;
68    let todo_drop = user.todos().create().title("drop").exec(&mut db).await?;
69
70    let ((), ()): ((), ()) = toasty::batch((
71        user.todos()
72            .filter_by_id(todo_keep.id)
73            .update()
74            .title("kept"),
75        user.todos().filter_by_id(todo_drop.id).delete(),
76    ))
77    .exec(&mut db)
78    .await?;
79
80    let remaining: Vec<Todo> = user.todos().exec(&mut db).await?;
81    assert_eq!(remaining.len(), 1);
82    assert_eq!(remaining[0].title, "kept");
83
84    Ok(())
85}
86
87/// Batch all four CRUD operations through association scope.
88#[driver_test(
89    id(ID),
90    requires(scan),
91    scenario(crate::scenarios::has_many_belongs_to)
92)]
93pub async fn batch_scoped_all_four_crud(t: &mut Test) -> Result<()> {
94    let mut db = setup(t).await;
95    let user = User::create().name("Alice").exec(&mut db).await?;
96    let existing = user
97        .todos()
98        .create()
99        .title("existing")
100        .exec(&mut db)
101        .await?;
102    let doomed = user.todos().create().title("doomed").exec(&mut db).await?;
103
104    let (queried, created, (), ()): (Vec<Todo>, Todo, (), ()) = toasty::batch((
105        user.todos(),
106        user.todos().create().title("new"),
107        user.todos()
108            .filter_by_id(existing.id)
109            .update()
110            .title("updated"),
111        user.todos().filter_by_id(doomed.id).delete(),
112    ))
113    .exec(&mut db)
114    .await?;
115
116    // Query ran before the update/delete in this batch, so sees original state
117    assert_eq!(queried.len(), 2);
118    assert_eq!(created.title, "new");
119
120    // Verify final state
121    let final_todos: Vec<Todo> = user.todos().exec(&mut db).await?;
122    assert_eq!(final_todos.len(), 2); // "updated" + "new", "doomed" deleted
123
124    let titles: Vec<&str> = final_todos.iter().map(|t| t.title.as_str()).collect();
125    assert!(titles.contains(&"updated"));
126    assert!(titles.contains(&"new"));
127
128    Ok(())
129}
130
131/// Batch association-scoped statements mixed with root-level statements.
132#[driver_test(
133    id(ID),
134    requires(scan),
135    scenario(crate::scenarios::has_many_belongs_to)
136)]
137pub async fn batch_scoped_with_root_statements(t: &mut Test) -> Result<()> {
138    let mut db = setup(t).await;
139    let user = User::create().name("Alice").exec(&mut db).await?;
140
141    // Mix: root-level query + scoped create + root-level create
142    let (users, todo, new_user): (Vec<User>, Todo, User) = toasty::batch((
143        User::filter_by_name("Alice"),
144        user.todos().create().title("from batch"),
145        User::create().name("Bob"),
146    ))
147    .exec(&mut db)
148    .await?;
149
150    assert_struct!(users, [{ name: "Alice" }]);
151    assert_eq!(todo.title, "from batch");
152    assert_eq!(todo.user_id, user.id);
153    assert_eq!(new_user.name, "Bob");
154
155    Ok(())
156}
157
158/// Batch association statements across different relations of the same parent.
159#[driver_test(id(ID), requires(scan))]
160pub async fn batch_scoped_across_relations(t: &mut Test) -> Result<()> {
161    #[derive(Debug, toasty::Model)]
162    struct User {
163        #[key]
164        #[auto]
165        id: ID,
166        #[has_many]
167        todos: toasty::HasMany<Todo>,
168        #[has_many]
169        posts: toasty::HasMany<Post>,
170    }
171
172    #[derive(Debug, toasty::Model)]
173    struct Todo {
174        #[key]
175        #[auto]
176        id: ID,
177        #[index]
178        user_id: ID,
179        #[belongs_to(key = user_id, references = id)]
180        user: toasty::BelongsTo<User>,
181        title: String,
182    }
183
184    #[derive(Debug, toasty::Model)]
185    struct Post {
186        #[key]
187        #[auto]
188        id: ID,
189        #[index]
190        user_id: ID,
191        #[belongs_to(key = user_id, references = id)]
192        user: toasty::BelongsTo<User>,
193        body: String,
194    }
195
196    let mut db = t.setup_db(models!(User, Todo, Post)).await;
197    let user = User::create().exec(&mut db).await?;
198
199    // Create across two different relations in one batch
200    let (todo, post): (Todo, Post) = toasty::batch((
201        user.todos().create().title("my todo"),
202        user.posts().create().body("my post"),
203    ))
204    .exec(&mut db)
205    .await?;
206
207    assert_eq!(todo.title, "my todo");
208    assert_eq!(todo.user_id, user.id);
209    assert_eq!(post.body, "my post");
210    assert_eq!(post.user_id, user.id);
211
212    Ok(())
213}
214
215/// Batch queries across different relations of the same parent.
216#[driver_test(id(ID), requires(scan))]
217pub async fn batch_query_across_relations(t: &mut Test) -> Result<()> {
218    #[derive(Debug, toasty::Model)]
219    struct User {
220        #[key]
221        #[auto]
222        id: ID,
223        #[has_many]
224        todos: toasty::HasMany<Todo>,
225        #[has_many]
226        posts: toasty::HasMany<Post>,
227    }
228
229    #[derive(Debug, toasty::Model)]
230    struct Todo {
231        #[key]
232        #[auto]
233        id: ID,
234        #[index]
235        user_id: ID,
236        #[belongs_to(key = user_id, references = id)]
237        user: toasty::BelongsTo<User>,
238        title: String,
239    }
240
241    #[derive(Debug, toasty::Model)]
242    struct Post {
243        #[key]
244        #[auto]
245        id: ID,
246        #[index]
247        user_id: ID,
248        #[belongs_to(key = user_id, references = id)]
249        user: toasty::BelongsTo<User>,
250        body: String,
251    }
252
253    let mut db = t.setup_db(models!(User, Todo, Post)).await;
254    let user = User::create().exec(&mut db).await?;
255    user.todos().create().title("t1").exec(&mut db).await?;
256    user.todos().create().title("t2").exec(&mut db).await?;
257    user.posts().create().body("p1").exec(&mut db).await?;
258
259    let (todos, posts): (Vec<Todo>, Vec<Post>) = toasty::batch((user.todos(), user.posts()))
260        .exec(&mut db)
261        .await?;
262
263    assert_eq!(todos.len(), 2);
264    assert_eq!(posts.len(), 1);
265    assert_eq!(posts[0].body, "p1");
266
267    Ok(())
268}
269
270/// Batch scoped operations from different parents.
271#[driver_test(
272    id(ID),
273    requires(scan),
274    scenario(crate::scenarios::has_many_belongs_to)
275)]
276pub async fn batch_scoped_different_parents(t: &mut Test) -> Result<()> {
277    let mut db = setup(t).await;
278    let alice = User::create().name("Alice").exec(&mut db).await?;
279    let bob = User::create().name("Bob").exec(&mut db).await?;
280
281    // Create todos for different users in one batch
282    let (alice_todo, bob_todo): (Todo, Todo) = toasty::batch((
283        alice.todos().create().title("alice task"),
284        bob.todos().create().title("bob task"),
285    ))
286    .exec(&mut db)
287    .await?;
288
289    assert_eq!(alice_todo.user_id, alice.id);
290    assert_eq!(bob_todo.user_id, bob.id);
291
292    // Query both scopes in one batch
293    let (alice_todos, bob_todos): (Vec<Todo>, Vec<Todo>) =
294        toasty::batch((alice.todos(), bob.todos()))
295            .exec(&mut db)
296            .await?;
297
298    assert_struct!(alice_todos, [{ title: "alice task" }]);
299    assert_struct!(bob_todos, [{ title: "bob task" }]);
300
301    Ok(())
302}
303
304/// Batch a scoped delete together with a root-level update.
305#[driver_test(
306    id(ID),
307    requires(scan),
308    scenario(crate::scenarios::has_many_belongs_to)
309)]
310pub async fn batch_scoped_delete_with_root_update(t: &mut Test) -> Result<()> {
311    let mut db = setup(t).await;
312    let user = User::create().name("Alice").exec(&mut db).await?;
313    let todo = user.todos().create().title("done").exec(&mut db).await?;
314
315    let ((), ()): ((), ()) = toasty::batch((
316        user.todos().filter_by_id(todo.id).delete(),
317        User::filter_by_name("Alice").update().name("Alice2"),
318    ))
319    .exec(&mut db)
320    .await?;
321
322    // Todo deleted
323    let remaining: Vec<Todo> = user.todos().exec(&mut db).await?;
324    assert!(remaining.is_empty());
325
326    // User updated
327    let updated: Vec<User> = User::filter_by_name("Alice2").exec(&mut db).await?;
328    assert_eq!(updated.len(), 1);
329
330    Ok(())
331}