Skip to main content

toasty_driver_integration_suite/tests/
crud_query_macro.rs

1use crate::prelude::*;
2
3#[driver_test(scenario(crate::scenarios::two_models), requires(scan))]
4pub async fn query_macro_scan_filters(test: &mut Test) -> Result<()> {
5    let mut db = setup(test).await;
6
7    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
8        .exec(&mut db)
9        .await?;
10
11    // query!(User) expands to User::all()
12    let users = toasty::query!(User).exec(&mut db).await?;
13    assert_struct!(users, #({ name: "Alice" }, { name: "Bob" }, { name: "Carl" }));
14
15    let users = toasty::query!(User filter .name == "Alice")
16        .exec(&mut db)
17        .await?;
18    assert_struct!(users, [{ name: "Alice" }]);
19
20    let users = toasty::query!(User filter .name != "Alice")
21        .exec(&mut db)
22        .await?;
23    assert_struct!(users, #({ name: "Bob" }, { name: "Carl" }));
24
25    let users = toasty::query!(User filter .name == "Alice" or .name == "Bob")
26        .exec(&mut db)
27        .await?;
28    assert_struct!(users, #({ name: "Alice" }, { name: "Bob" }));
29
30    let users = toasty::query!(User filter not .name == "Alice")
31        .exec(&mut db)
32        .await?;
33    assert_struct!(users, #({ name: "Bob" }, { name: "Carl" }));
34
35    let target_name = "Alice";
36    let users = toasty::query!(User filter .name == #target_name)
37        .exec(&mut db)
38        .await?;
39    assert_struct!(users, [{ name: "Alice" }]);
40
41    fn get_name() -> &'static str {
42        "Bob"
43    }
44    let users = toasty::query!(User filter .name == #(get_name()))
45        .exec(&mut db)
46        .await?;
47    assert_struct!(users, [{ name: "Bob" }]);
48
49    let users = toasty::query!(User FILTER .name == "Alice")
50        .exec(&mut db)
51        .await?;
52    assert_struct!(users, [{ name: "Alice" }]);
53
54    let users = toasty::query!(User Filter .name == "Alice" AND .name == "Alice")
55        .exec(&mut db)
56        .await?;
57    assert_struct!(users, [{ name: "Alice" }]);
58
59    let users = toasty::query!(User filter .name IN ["Alice", "Carl"])
60        .exec(&mut db)
61        .await?;
62    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
63
64    let names = vec!["Alice", "Bob"];
65    let users = toasty::query!(User filter .name IN #names)
66        .exec(&mut db)
67        .await?;
68    assert_struct!(users, #({ name: "Alice" }, { name: "Bob" }));
69
70    let users = toasty::query!(User filter .name IN ["Alice", "Bob", "Carl"] and .name != "Bob")
71        .exec(&mut db)
72        .await?;
73    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
74
75    Ok(())
76}
77
78#[driver_test(scenario(crate::scenarios::user_with_age), requires(scan))]
79pub async fn query_macro_numeric_and_boolean_filters(test: &mut Test) -> Result<()> {
80    let mut db = setup(test).await;
81
82    toasty::create!(User::[
83        { name: "Young", age: 15 },
84        { name: "Adult", age: 25 },
85        { name: "Senior", age: 65 },
86        { name: "Alice", age: 30 },
87        { name: "Bob", age: 30 },
88        { name: "Alice", age: 20 },
89        { name: "Carl", age: 40 },
90        { name: "Diana", age: 10 },
91    ])
92    .exec(&mut db)
93    .await?;
94
95    // Greater than
96    let users = toasty::query!(User filter .age > 20).exec(&mut db).await?;
97    assert_struct!(users, #(
98        { name: "Adult" },
99        { name: "Senior" },
100        { name: "Alice" },
101        { name: "Bob" },
102        { name: "Carl" },
103    ));
104
105    // Greater than or equal
106    let users = toasty::query!(User filter .age >= 25).exec(&mut db).await?;
107    assert_struct!(users, #(
108        { name: "Adult" },
109        { name: "Senior" },
110        { name: "Alice" },
111        { name: "Bob" },
112        { name: "Carl" },
113    ));
114
115    // Less than
116    let users = toasty::query!(User filter .age < 25).exec(&mut db).await?;
117    assert_struct!(users, #(
118        { name: "Young" },
119        { name: "Alice", age: 20 },
120        { name: "Diana" },
121    ));
122
123    // Less than or equal
124    let users = toasty::query!(User filter .age <= 25).exec(&mut db).await?;
125    assert_struct!(users, #(
126        { name: "Young" },
127        { name: "Adult" },
128        { name: "Alice", age: 20 },
129        { name: "Diana" },
130    ));
131
132    let users = toasty::query!(User filter .name == "Alice" and .age == 30)
133        .exec(&mut db)
134        .await?;
135    assert_struct!(users, [{ name: "Alice", age: 30 }]);
136
137    // AND binds tighter than OR, so parentheses change the grouping:
138    // .name == "Alice" AND (.age > 25 OR .age < 15)
139    let users = toasty::query!(User filter .name == "Alice" and (.age > 25 or .age < 15))
140        .exec(&mut db)
141        .await?;
142    assert_struct!(users, [{ name: "Alice" }]);
143
144    // Complex: NOT (.age < 18) AND (.name == "Alice" OR .name == "Carl")
145    let users =
146        toasty::query!(User filter not (.age < 18) and (.name == "Alice" or .name == "Carl"))
147            .exec(&mut db)
148            .await?;
149    assert_struct!(users, #(
150        { name: "Alice", age: 30 },
151        { name: "Alice", age: 20 },
152        { name: "Carl" },
153    ));
154
155    Ok(())
156}
157
158#[driver_test(scenario(crate::scenarios::widget_mixed_types))]
159pub async fn query_macro_filter_bool_literal(test: &mut Test) -> Result<()> {
160    let mut db = setup(test).await;
161
162    toasty::create!(Widget::[
163        { label: "on", count: 0, active: true, description: "" },
164        { label: "off", count: 0, active: false, description: "" },
165    ])
166    .exec(&mut db)
167    .await?;
168
169    let widgets = toasty::query!(Widget filter .active == true)
170        .exec(&mut db)
171        .await?;
172
173    assert_struct!(widgets, [{ label: "on" }]);
174
175    Ok(())
176}
177
178// --- ORDER BY, LIMIT, OFFSET tests ---
179
180#[driver_test(scenario(crate::scenarios::two_models), requires(sql))]
181pub async fn query_macro_ordering_and_pagination(test: &mut Test) -> Result<()> {
182    let mut db = setup(test).await;
183
184    toasty::create!(User::[{ name: "Carl" }, { name: "Alice" }, { name: "Bob" }, { name: "Diana" }])
185        .exec(&mut db)
186        .await?;
187
188    let users = toasty::query!(User ORDER BY .name ASC)
189        .exec(&mut db)
190        .await?;
191    assert_struct!(users, [
192        { name: "Alice" },
193        { name: "Bob" },
194        { name: "Carl" },
195        { name: "Diana" },
196    ]);
197
198    let users = toasty::query!(User ORDER BY .name DESC)
199        .exec(&mut db)
200        .await?;
201    assert_struct!(users, [
202        { name: "Diana" },
203        { name: "Carl" },
204        { name: "Bob" },
205        { name: "Alice" },
206    ]);
207
208    let users = toasty::query!(User ORDER BY .name ASC LIMIT 2)
209        .exec(&mut db)
210        .await?;
211    assert_eq!(users.len(), 2);
212    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }]);
213
214    // Skip 1, take 2 (ordered by name ascending)
215    let users = toasty::query!(User ORDER BY .name ASC OFFSET 1 LIMIT 2)
216        .exec(&mut db)
217        .await?;
218    assert_struct!(users, [{ name: "Bob" }, { name: "Carl" }]);
219
220    let n = 1;
221    let users = toasty::query!(User ORDER BY .name ASC LIMIT #n)
222        .exec(&mut db)
223        .await?;
224    assert_struct!(users, [{ name: "Alice" }]);
225
226    let users = toasty::query!(User order by .name asc limit 2)
227        .exec(&mut db)
228        .await?;
229    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }]);
230
231    Ok(())
232}
233
234#[driver_test(scenario(crate::scenarios::user_with_age), requires(sql))]
235pub async fn query_macro_filter_with_order_by_and_limit(test: &mut Test) -> Result<()> {
236    let mut db = setup(test).await;
237
238    toasty::create!(User::[
239        { name: "Alice", age: 30 },
240        { name: "Bob", age: 25 },
241        { name: "Carl", age: 35 },
242        { name: "Diana", age: 20 },
243    ])
244    .exec(&mut db)
245    .await?;
246
247    // Filter age > 20, order by name desc, limit 2
248    let users = toasty::query!(User FILTER .age > 20 ORDER BY .name DESC LIMIT 2)
249        .exec(&mut db)
250        .await?;
251    assert_struct!(users, [{ name: "Carl" }, { name: "Bob" }]);
252
253    Ok(())
254}
255
256// --- DynamoDB-compatible query macro tests ---
257// These use composite primary keys (partition + local) so queries can be served
258// by DynamoDB's key condition expressions.
259
260#[driver_test]
261pub async fn query_macro_partition_key_filters(test: &mut Test) -> Result<()> {
262    #[derive(Debug, toasty::Model)]
263    #[key(partition = league, local = name)]
264    struct Team {
265        league: String,
266
267        name: String,
268
269        founded: i64,
270    }
271
272    let mut db = test.setup_db(models!(Team)).await;
273
274    for (league, name, founded) in [
275        ("MLS", "Portland Timbers", 2009),
276        ("MLS", "Seattle Sounders FC", 2007),
277        ("EPL", "Arsenal", 1886),
278        ("EPL", "Chelsea", 1905),
279    ] {
280        toasty::create!(Team {
281            league: league,
282            name: name,
283            founded: founded
284        })
285        .exec(&mut db)
286        .await?;
287    }
288
289    // Filter on partition key only
290    let teams = toasty::query!(Team filter .league == "EPL")
291        .exec(&mut db)
292        .await?;
293
294    assert_struct!(teams, #({ name: "Arsenal" }, { name: "Chelsea" }));
295
296    // Filter on partition key + local key
297    let teams = toasty::query!(Team filter .league == "MLS" and .name == "Portland Timbers")
298        .exec(&mut db)
299        .await?;
300
301    assert_struct!(teams, [{ name: "Portland Timbers", founded: 2009 }]);
302
303    let target_league = "MLS";
304    let teams = toasty::query!(Team filter .league == #target_league)
305        .exec(&mut db)
306        .await?;
307    assert_struct!(teams, #({ name: "Portland Timbers" }, { name: "Seattle Sounders FC" }));
308
309    Ok(())
310}
311
312#[driver_test]
313pub async fn query_macro_local_key_comparison(test: &mut Test) -> Result<()> {
314    #[derive(Debug, toasty::Model)]
315    #[key(partition = kind, local = timestamp)]
316    struct Event {
317        kind: String,
318
319        timestamp: i64,
320    }
321
322    let mut db = test.setup_db(models!(Event)).await;
323
324    for (kind, ts) in [
325        ("info", 0),
326        ("info", 2),
327        ("info", 4),
328        ("info", 6),
329        ("info", 8),
330        ("info", 10),
331        ("warn", 1),
332        ("warn", 3),
333        ("warn", 5),
334    ] {
335        toasty::create!(Event {
336            kind: kind,
337            timestamp: ts
338        })
339        .exec(&mut db)
340        .await?;
341    }
342
343    // Partition key + greater-than on local key
344    let events = toasty::query!(Event filter .kind == "info" and .timestamp > 6)
345        .exec(&mut db)
346        .await?;
347
348    assert_struct!(events, #({ timestamp: 8 }, { timestamp: 10 }));
349
350    // Partition key + less-than-or-equal on local key
351    let events = toasty::query!(Event filter .kind == "info" and .timestamp <= 4)
352        .exec(&mut db)
353        .await?;
354
355    assert_struct!(events, #({ timestamp: 0 }, { timestamp: 2 }, { timestamp: 4 }));
356
357    Ok(())
358}
359
360#[driver_test]
361pub async fn query_macro_partition_key_with_not(test: &mut Test) -> Result<()> {
362    #[derive(Debug, toasty::Model)]
363    #[key(partition = team, local = name)]
364    struct Player {
365        team: String,
366
367        name: String,
368
369        position: String,
370    }
371
372    let mut db = test.setup_db(models!(Player)).await;
373
374    for (team, name, position) in [
375        ("Timbers", "Diego Valeri", "Midfielder"),
376        ("Timbers", "Fanendo Adi", "Forward"),
377        ("Timbers", "Adam Kwarasey", "Goalkeeper"),
378        ("Sounders", "Clint Dempsey", "Forward"),
379    ] {
380        toasty::create!(Player {
381            team: team,
382            name: name,
383            position: position
384        })
385        .exec(&mut db)
386        .await?;
387    }
388
389    // Partition key + NOT on non-key field
390    let players =
391        toasty::query!(Player filter .team == "Timbers" and not .position == "Midfielder")
392            .exec(&mut db)
393            .await?;
394
395    assert_struct!(players, #({ name: "Adam Kwarasey" }, { name: "Fanendo Adi" }));
396
397    Ok(())
398}
399
400#[driver_test]
401pub async fn query_macro_partition_key_with_or(test: &mut Test) -> Result<()> {
402    #[derive(Debug, toasty::Model)]
403    #[key(partition = team, local = name)]
404    struct Player {
405        team: String,
406
407        name: String,
408
409        position: String,
410
411        number: i64,
412    }
413
414    let mut db = test.setup_db(models!(Player)).await;
415
416    for (team, name, position, number) in [
417        ("Timbers", "Diego Valeri", "Midfielder", 8),
418        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
419        ("Timbers", "Fanendo Adi", "Forward", 9),
420        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
421        ("Sounders", "Clint Dempsey", "Forward", 2),
422    ] {
423        toasty::create!(Player {
424            team: team,
425            name: name,
426            position: position,
427            number: number
428        })
429        .exec(&mut db)
430        .await?;
431    }
432
433    // Partition key + OR on non-key fields
434    let players = toasty::query!(Player filter .team == "Timbers" and (.position == "Forward" or .position == "Goalkeeper"))
435        .exec(&mut db)
436        .await?;
437
438    assert_struct!(players, #({ name: "Adam Kwarasey" }, { name: "Fanendo Adi" }));
439
440    Ok(())
441}
442
443#[driver_test(scenario(crate::scenarios::two_models))]
444pub async fn query_macro_filter_in_list_by_pk(test: &mut Test) -> Result<()> {
445    let mut db = setup(test).await;
446
447    // Create several users and collect their IDs
448    let mut ids = Vec::new();
449    for name in ["Alice", "Bob", "Carl", "Diana"] {
450        let user = User::create().name(name).exec(&mut db).await?;
451        ids.push(user.id);
452    }
453
454    // Batch fetch a subset by primary key using IN
455    let target_ids = vec![ids[0], ids[2]]; // Alice and Carl
456    let users = toasty::query!(User filter .id IN #target_ids)
457        .exec(&mut db)
458        .await?;
459
460    assert_eq!(users.len(), 2);
461    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
462
463    Ok(())
464}