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