Skip to main content

toasty_driver_integration_suite/tests/
relation_preload_options.rs

1use crate::prelude::*;
2
3/// Tuple and chained ordering apply independently to each parent's relation.
4#[driver_test(
5    requires(sql),
6    scenario(crate::scenarios::has_many_belongs_to_with_flags)
7)]
8pub async fn preload_has_many_ordered_per_parent(test: &mut Test) -> Result<()> {
9    let mut db = setup(test).await;
10
11    toasty::create!(User::[
12        {
13            name: "alice",
14            todos: [
15                { title: "b", complete: false, priority: 2 },
16                { title: "a", complete: false, priority: 2 },
17                { title: "c", complete: false, priority: 1 },
18            ]
19        },
20        {
21            name: "bob",
22            todos: [
23                { title: "z", complete: false, priority: 1 },
24                { title: "y", complete: false, priority: 1 },
25                { title: "x", complete: false, priority: 3 },
26            ]
27        },
28    ])
29    .exec(&mut db)
30    .await?;
31
32    let tuple_ordered: Vec<User> = User::all()
33        .order_by(User::fields().name().asc())
34        .include(User::fields().todos().order_by((
35            Todo::fields().priority().desc(),
36            Todo::fields().title().asc(),
37        )))
38        .exec(&mut db)
39        .await?;
40
41    assert_struct!(tuple_ordered, [
42        { name: "alice", todos.get(): [{ title: "a" }, { title: "b" }, { title: "c" }] },
43        { name: "bob", todos.get(): [{ title: "x" }, { title: "y" }, { title: "z" }] },
44    ]);
45
46    let chained_ordered: Vec<User> = User::all()
47        .order_by(User::fields().name().asc())
48        .include(
49            User::fields()
50                .todos()
51                .order_by(Todo::fields().priority().desc())
52                .order_by(Todo::fields().title().asc()),
53        )
54        .exec(&mut db)
55        .await?;
56
57    assert_struct!(chained_ordered, [
58        { name: "alice", todos.get(): [{ title: "a" }, { title: "b" }, { title: "c" }] },
59        { name: "bob", todos.get(): [{ title: "x" }, { title: "y" }, { title: "z" }] },
60    ]);
61
62    Ok(())
63}
64
65/// Include filters run before ordering.
66#[driver_test(
67    requires(sql),
68    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
69)]
70pub async fn preload_has_many_filter_and_order(test: &mut Test) -> Result<()> {
71    let mut db = setup(test).await;
72
73    let user = toasty::create!(User {
74        name: "alice",
75        todos: [{ title: "a" }, { title: "c" }, { title: "b" }]
76    })
77    .exec(&mut db)
78    .await?;
79
80    let loaded = User::filter_by_id(user.id)
81        .include(
82            User::fields()
83                .todos()
84                .filter(Todo::fields().title().ne("c"))
85                .order_by(Todo::fields().title().desc()),
86        )
87        .get(&mut db)
88        .await?;
89
90    assert_struct!(loaded.todos.get(), [
91        { title: "b" },
92        { title: "a" },
93    ]);
94
95    Ok(())
96}
97
98/// Ordering applies at each terminal relation in a nested include path.
99#[driver_test(requires(sql), scenario(crate::scenarios::user_post_comment))]
100pub async fn preload_nested_relations_ordered_at_each_level(test: &mut Test) -> Result<()> {
101    let mut db = setup(test).await;
102
103    let user = toasty::create!(User {
104        name: "alice",
105        posts: [
106            {
107                title: "b",
108                comments: [{ body: "a" }, { body: "c" }, { body: "b" }]
109            },
110            {
111                title: "a",
112                comments: [{ body: "x" }, { body: "z" }, { body: "y" }]
113            },
114        ]
115    })
116    .exec(&mut db)
117    .await?;
118
119    let loaded = User::filter_by_id(user.id)
120        .include(
121            User::fields()
122                .posts()
123                .order_by(Post::fields().title().asc()),
124        )
125        .include(
126            User::fields()
127                .posts()
128                .comments()
129                .order_by(Comment::fields().body().desc()),
130        )
131        .get(&mut db)
132        .await?;
133
134    assert_struct!(loaded.posts.get(), [
135        { title: "a", comments.get(): [{ body: "z" }, { body: "y" }, { body: "x" }] },
136        { title: "b", comments.get(): [{ body: "c" }, { body: "b" }, { body: "a" }] },
137    ]);
138
139    Ok(())
140}
141
142/// Include modifiers take precedence over fields with the same names.
143#[driver_test(requires(sql))]
144pub async fn preload_modifiers_with_reserved_field_names(test: &mut Test) -> Result<()> {
145    #[derive(Debug, toasty::Model)]
146    struct Parent {
147        #[key]
148        #[auto]
149        id: uuid::Uuid,
150
151        #[has_many]
152        children: toasty::Deferred<Vec<Child>>,
153    }
154
155    #[derive(Debug, toasty::Model)]
156    struct Child {
157        #[key]
158        #[auto]
159        id: uuid::Uuid,
160
161        filter: String,
162        order_by: String,
163        rank: i64,
164
165        #[index]
166        parent_id: uuid::Uuid,
167
168        #[belongs_to(key = parent_id, references = id)]
169        parent: toasty::Deferred<Parent>,
170    }
171
172    let mut db = test.setup_db(models!(Parent, Child)).await;
173
174    let parent = toasty::create!(Parent {
175        children: [
176            { filter: "a", order_by: "a", rank: 1 },
177            { filter: "b", order_by: "b", rank: 3 },
178            { filter: "c", order_by: "c", rank: 2 },
179        ]
180    })
181    .exec(&mut db)
182    .await?;
183
184    let parent = Parent::filter_by_id(parent.id)
185        .include(
186            Parent::fields()
187                .children()
188                .filter(Child::fields().rank().gt(1))
189                .order_by(Child::fields().rank().desc()),
190        )
191        .get(&mut db)
192        .await?;
193
194    assert_struct!(parent.children.get(), [
195        { rank: 3 },
196        { rank: 2 },
197    ]);
198
199    Ok(())
200}
201
202/// The last include supplies the complete ordering for a repeated relation path.
203#[driver_test(
204    requires(sql),
205    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
206)]
207pub async fn preload_has_many_repeated_ordering_last_wins(test: &mut Test) -> Result<()> {
208    use toasty_core::{driver::Operation, stmt::Statement};
209
210    let mut db = setup(test).await;
211
212    let user = toasty::create!(User {
213        name: "alice",
214        todos: [{ title: "a" }, { title: "c" }, { title: "b" }]
215    })
216    .exec(&mut db)
217    .await?;
218
219    let loaded = User::filter_by_id(user.id)
220        .include(
221            User::fields()
222                .todos()
223                .order_by(Todo::fields().title().asc()),
224        )
225        .include(
226            User::fields()
227                .todos()
228                .order_by(Todo::fields().title().desc()),
229        )
230        .get(&mut db)
231        .await?;
232
233    assert_struct!(loaded.todos.get(), [
234        { title: "c" },
235        { title: "b" },
236        { title: "a" },
237    ]);
238
239    test.log().clear();
240
241    let loaded = User::filter_by_id(user.id)
242        .include(
243            User::fields()
244                .todos()
245                .order_by(Todo::fields().title().desc()),
246        )
247        .include(User::fields().todos())
248        .get(&mut db)
249        .await?;
250
251    assert_eq!(loaded.todos.get().len(), 3);
252
253    let _ = test.log().pop_last_op(); // transaction commit
254    assert_struct!(test.log().pop_last_op(), Operation::QuerySql({
255        stmt: Statement::Query({ order_by: None }),
256    }));
257
258    Ok(())
259}
260
261/// Include ordering is rejected when the terminal relation is singular.
262#[driver_test(scenario(crate::scenarios::has_one_optional_belongs_to::id_uuid))]
263pub async fn preload_has_one_ordering_rejected(test: &mut Test) -> Result<()> {
264    let mut db = setup(test).await;
265
266    let err = assert_err!(
267        User::all()
268            .include(
269                User::fields()
270                    .profile()
271                    .order_by(Profile::fields().bio().asc()),
272            )
273            .exec(&mut db)
274            .await
275    );
276
277    assert!(err.is_invalid_statement(), "unexpected error: {err}");
278
279    Ok(())
280}