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), scenario(crate::scenarios::user_two_children))]
160pub async fn batch_scoped_across_relations(t: &mut Test) -> Result<()> {
161    let mut db = setup(t).await;
162    let user = User::create().name("Alice").exec(&mut db).await?;
163
164    // Create across two different relations in one batch
165    let (post, comment): (Post, Comment) = toasty::batch((
166        user.posts().create().title("my post"),
167        user.comments().create().text("my comment"),
168    ))
169    .exec(&mut db)
170    .await?;
171
172    assert_eq!(post.title, "my post");
173    assert_eq!(post.user_id, user.id);
174    assert_eq!(comment.text, "my comment");
175    assert_eq!(comment.user_id, user.id);
176
177    Ok(())
178}
179
180/// Batch queries across different relations of the same parent.
181#[driver_test(id(ID), requires(scan), scenario(crate::scenarios::user_two_children))]
182pub async fn batch_query_across_relations(t: &mut Test) -> Result<()> {
183    let mut db = setup(t).await;
184    let user = User::create().name("Alice").exec(&mut db).await?;
185    user.posts().create().title("t1").exec(&mut db).await?;
186    user.posts().create().title("t2").exec(&mut db).await?;
187    user.comments().create().text("p1").exec(&mut db).await?;
188
189    let (posts, comments): (Vec<Post>, Vec<Comment>) =
190        toasty::batch((user.posts(), user.comments()))
191            .exec(&mut db)
192            .await?;
193
194    assert_eq!(posts.len(), 2);
195    assert_eq!(comments.len(), 1);
196    assert_eq!(comments[0].text, "p1");
197
198    Ok(())
199}
200
201/// Batch scoped operations from different parents.
202#[driver_test(
203    id(ID),
204    requires(scan),
205    scenario(crate::scenarios::has_many_belongs_to)
206)]
207pub async fn batch_scoped_different_parents(t: &mut Test) -> Result<()> {
208    let mut db = setup(t).await;
209    let alice = User::create().name("Alice").exec(&mut db).await?;
210    let bob = User::create().name("Bob").exec(&mut db).await?;
211
212    // Create todos for different users in one batch
213    let (alice_todo, bob_todo): (Todo, Todo) = toasty::batch((
214        alice.todos().create().title("alice task"),
215        bob.todos().create().title("bob task"),
216    ))
217    .exec(&mut db)
218    .await?;
219
220    assert_eq!(alice_todo.user_id, alice.id);
221    assert_eq!(bob_todo.user_id, bob.id);
222
223    // Query both scopes in one batch
224    let (alice_todos, bob_todos): (Vec<Todo>, Vec<Todo>) =
225        toasty::batch((alice.todos(), bob.todos()))
226            .exec(&mut db)
227            .await?;
228
229    assert_struct!(alice_todos, [{ title: "alice task" }]);
230    assert_struct!(bob_todos, [{ title: "bob task" }]);
231
232    Ok(())
233}
234
235/// Batch a scoped delete together with a root-level update.
236#[driver_test(
237    id(ID),
238    requires(scan),
239    scenario(crate::scenarios::has_many_belongs_to)
240)]
241pub async fn batch_scoped_delete_with_root_update(t: &mut Test) -> Result<()> {
242    let mut db = setup(t).await;
243    let user = User::create().name("Alice").exec(&mut db).await?;
244    let todo = user.todos().create().title("done").exec(&mut db).await?;
245
246    let ((), ()): ((), ()) = toasty::batch((
247        user.todos().filter_by_id(todo.id).delete(),
248        User::filter_by_name("Alice").update().name("Alice2"),
249    ))
250    .exec(&mut db)
251    .await?;
252
253    // Todo deleted
254    let remaining: Vec<Todo> = user.todos().exec(&mut db).await?;
255    assert!(remaining.is_empty());
256
257    // User updated
258    let updated: Vec<User> = User::filter_by_name("Alice2").exec(&mut db).await?;
259    assert_eq!(updated.len(), 1);
260
261    Ok(())
262}