Skip to main content

toasty_driver_integration_suite/tests/
crud_query_macro.rs

1use crate::prelude::*;
2
3#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
4pub async fn query_macro_all(test: &mut Test) -> Result<()> {
5    let mut db = setup(test).await;
6
7    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
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" }));
14
15    Ok(())
16}
17
18#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
19pub async fn query_macro_filter_eq(test: &mut Test) -> Result<()> {
20    let mut db = setup(test).await;
21
22    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
23        .exec(&mut db)
24        .await?;
25
26    // query!(User filter .name == "Alice") expands to User::filter(User::fields().name().eq("Alice"))
27    let users = toasty::query!(User filter .name == "Alice")
28        .exec(&mut db)
29        .await?;
30
31    assert_struct!(users, [{ name: "Alice" }]);
32
33    Ok(())
34}
35
36#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
37pub async fn query_macro_filter_ne(test: &mut Test) -> Result<()> {
38    let mut db = setup(test).await;
39
40    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
41        .exec(&mut db)
42        .await?;
43
44    let users = toasty::query!(User filter .name != "Alice")
45        .exec(&mut db)
46        .await?;
47
48    assert_struct!(users, [{ name: "Bob" }]);
49
50    Ok(())
51}
52
53#[driver_test(id(ID), scenario(crate::scenarios::user_with_age), requires(scan))]
54pub async fn query_macro_filter_numeric_comparisons(test: &mut Test) -> Result<()> {
55    let mut db = setup(test).await;
56
57    toasty::create!(User::[
58        { name: "Young", age: 15 },
59        { name: "Adult", age: 25 },
60        { name: "Senior", age: 65 },
61    ])
62    .exec(&mut db)
63    .await?;
64
65    // Greater than
66    let users = toasty::query!(User filter .age > 20).exec(&mut db).await?;
67    assert_struct!(users, #({ name: "Adult" }, { name: "Senior" }));
68
69    // Greater than or equal
70    let users = toasty::query!(User filter .age >= 25).exec(&mut db).await?;
71    assert_struct!(users, #({ name: "Adult" }, { name: "Senior" }));
72
73    // Less than
74    let users = toasty::query!(User filter .age < 25).exec(&mut db).await?;
75    assert_struct!(users, [{ name: "Young" }]);
76
77    // Less than or equal
78    let users = toasty::query!(User filter .age <= 25).exec(&mut db).await?;
79    assert_struct!(users, #({ name: "Young" }, { name: "Adult" }));
80
81    Ok(())
82}
83
84#[driver_test(id(ID), scenario(crate::scenarios::user_with_age), requires(scan))]
85pub async fn query_macro_filter_and(test: &mut Test) -> Result<()> {
86    let mut db = setup(test).await;
87
88    toasty::create!(User::[
89        { name: "Alice", age: 30 },
90        { name: "Bob", age: 30 },
91        { name: "Alice", age: 20 },
92    ])
93    .exec(&mut db)
94    .await?;
95
96    let users = toasty::query!(User filter .name == "Alice" and .age == 30)
97        .exec(&mut db)
98        .await?;
99
100    assert_struct!(users, [{ name: "Alice", age: 30 }]);
101
102    Ok(())
103}
104
105#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
106pub async fn query_macro_filter_or(test: &mut Test) -> Result<()> {
107    let mut db = setup(test).await;
108
109    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
110        .exec(&mut db)
111        .await?;
112
113    let users = toasty::query!(User filter .name == "Alice" or .name == "Bob")
114        .exec(&mut db)
115        .await?;
116
117    assert_struct!(users, #({ name: "Alice" }, { name: "Bob" }));
118
119    Ok(())
120}
121
122#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
123pub async fn query_macro_filter_not(test: &mut Test) -> Result<()> {
124    let mut db = setup(test).await;
125
126    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
127        .exec(&mut db)
128        .await?;
129
130    let users = toasty::query!(User filter not .name == "Alice")
131        .exec(&mut db)
132        .await?;
133
134    assert_struct!(users, [{ name: "Bob" }]);
135
136    Ok(())
137}
138
139#[driver_test(id(ID), scenario(crate::scenarios::user_with_age), requires(scan))]
140pub async fn query_macro_filter_parens(test: &mut Test) -> Result<()> {
141    let mut db = setup(test).await;
142
143    toasty::create!(User::[
144        { name: "Alice", age: 30 },
145        { name: "Bob", age: 20 },
146        { name: "Carl", age: 40 },
147    ])
148    .exec(&mut db)
149    .await?;
150
151    // AND binds tighter than OR, so parentheses change the grouping:
152    // .name == "Alice" AND (.age > 25 OR .age < 15)
153    let users = toasty::query!(User filter .name == "Alice" and (.age > 25 or .age < 15))
154        .exec(&mut db)
155        .await?;
156
157    assert_struct!(users, [{ name: "Alice" }]);
158
159    Ok(())
160}
161
162#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
163pub async fn query_macro_filter_external_ref(test: &mut Test) -> Result<()> {
164    let mut db = setup(test).await;
165
166    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
167        .exec(&mut db)
168        .await?;
169
170    let target_name = "Alice";
171    let users = toasty::query!(User filter .name == #target_name)
172        .exec(&mut db)
173        .await?;
174
175    assert_struct!(users, [{ name: "Alice" }]);
176
177    Ok(())
178}
179
180#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
181pub async fn query_macro_filter_external_expr(test: &mut Test) -> Result<()> {
182    let mut db = setup(test).await;
183
184    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
185        .exec(&mut db)
186        .await?;
187
188    fn get_name() -> &'static str {
189        "Bob"
190    }
191
192    let users = toasty::query!(User filter .name == #(get_name()))
193        .exec(&mut db)
194        .await?;
195
196    assert_struct!(users, [{ name: "Bob" }]);
197
198    Ok(())
199}
200
201#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
202pub async fn query_macro_case_insensitive_keywords(test: &mut Test) -> Result<()> {
203    let mut db = setup(test).await;
204
205    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }])
206        .exec(&mut db)
207        .await?;
208
209    // FILTER (uppercase)
210    let users = toasty::query!(User FILTER .name == "Alice")
211        .exec(&mut db)
212        .await?;
213    assert_struct!(users, [{ name: "Alice" }]);
214
215    // Filter (mixed case), AND, OR
216    let users = toasty::query!(User Filter .name == "Alice" AND .name == "Alice")
217        .exec(&mut db)
218        .await?;
219    assert_struct!(users, [{ name: "Alice" }]);
220
221    Ok(())
222}
223
224#[driver_test(id(ID), scenario(crate::scenarios::user_with_age), requires(scan))]
225pub async fn query_macro_complex_boolean(test: &mut Test) -> Result<()> {
226    let mut db = setup(test).await;
227
228    toasty::create!(User::[
229        { name: "Alice", age: 30 },
230        { name: "Bob", age: 20 },
231        { name: "Carl", age: 40 },
232        { name: "Diana", age: 10 },
233    ])
234    .exec(&mut db)
235    .await?;
236
237    // Complex: NOT (.age < 18) AND (.name == "Alice" OR .name == "Carl")
238    let users =
239        toasty::query!(User filter not (.age < 18) and (.name == "Alice" or .name == "Carl"))
240            .exec(&mut db)
241            .await?;
242
243    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
244
245    Ok(())
246}
247
248#[driver_test(id(ID), scenario(crate::scenarios::widget_mixed_types))]
249pub async fn query_macro_filter_bool_literal(test: &mut Test) -> Result<()> {
250    let mut db = setup(test).await;
251
252    toasty::create!(Widget::[
253        { label: "on", count: 0, active: true, description: "" },
254        { label: "off", count: 0, active: false, description: "" },
255    ])
256    .exec(&mut db)
257    .await?;
258
259    let widgets = toasty::query!(Widget filter .active == true)
260        .exec(&mut db)
261        .await?;
262
263    assert_struct!(widgets, [{ label: "on" }]);
264
265    Ok(())
266}
267
268// --- ORDER BY, LIMIT, OFFSET tests ---
269
270#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
271pub async fn query_macro_order_by_asc(test: &mut Test) -> Result<()> {
272    let mut db = setup(test).await;
273
274    toasty::create!(User::[{ name: "Carl" }, { name: "Alice" }, { name: "Bob" }])
275        .exec(&mut db)
276        .await?;
277
278    let users = toasty::query!(User ORDER BY .name ASC)
279        .exec(&mut db)
280        .await?;
281    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }]);
282
283    Ok(())
284}
285
286#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
287pub async fn query_macro_order_by_desc(test: &mut Test) -> Result<()> {
288    let mut db = setup(test).await;
289
290    toasty::create!(User::[{ name: "Carl" }, { name: "Alice" }, { name: "Bob" }])
291        .exec(&mut db)
292        .await?;
293
294    let users = toasty::query!(User ORDER BY .name DESC)
295        .exec(&mut db)
296        .await?;
297    assert_struct!(users, [{ name: "Carl" }, { name: "Bob" }, { name: "Alice" }]);
298
299    Ok(())
300}
301
302#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
303pub async fn query_macro_limit(test: &mut Test) -> Result<()> {
304    let mut db = setup(test).await;
305
306    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
307        .exec(&mut db)
308        .await?;
309
310    let users = toasty::query!(User ORDER BY .name ASC LIMIT 2)
311        .exec(&mut db)
312        .await?;
313    assert_eq!(users.len(), 2);
314    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }]);
315
316    Ok(())
317}
318
319#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
320pub async fn query_macro_offset_and_limit(test: &mut Test) -> Result<()> {
321    let mut db = setup(test).await;
322
323    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }, { name: "Diana" }])
324        .exec(&mut db)
325        .await?;
326
327    // Skip 1, take 2 (ordered by name ascending)
328    let users = toasty::query!(User ORDER BY .name ASC OFFSET 1 LIMIT 2)
329        .exec(&mut db)
330        .await?;
331    assert_struct!(users, [{ name: "Bob" }, { name: "Carl" }]);
332
333    Ok(())
334}
335
336#[driver_test(id(ID), scenario(crate::scenarios::user_with_age), requires(sql))]
337pub async fn query_macro_filter_with_order_by_and_limit(test: &mut Test) -> Result<()> {
338    let mut db = setup(test).await;
339
340    toasty::create!(User::[
341        { name: "Alice", age: 30 },
342        { name: "Bob", age: 25 },
343        { name: "Carl", age: 35 },
344        { name: "Diana", age: 20 },
345    ])
346    .exec(&mut db)
347    .await?;
348
349    // Filter age > 20, order by name desc, limit 2
350    let users = toasty::query!(User FILTER .age > 20 ORDER BY .name DESC LIMIT 2)
351        .exec(&mut db)
352        .await?;
353    assert_struct!(users, [{ name: "Carl" }, { name: "Bob" }]);
354
355    Ok(())
356}
357
358#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
359pub async fn query_macro_limit_external_ref(test: &mut Test) -> Result<()> {
360    let mut db = setup(test).await;
361
362    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
363        .exec(&mut db)
364        .await?;
365
366    let n = 1;
367    let users = toasty::query!(User ORDER BY .name ASC LIMIT #n)
368        .exec(&mut db)
369        .await?;
370    assert_struct!(users, [{ name: "Alice" }]);
371
372    Ok(())
373}
374
375#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
376pub async fn query_macro_case_insensitive_order_limit(test: &mut Test) -> Result<()> {
377    let mut db = setup(test).await;
378
379    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
380        .exec(&mut db)
381        .await?;
382
383    // Case-insensitive: order, by, asc, limit
384    let users = toasty::query!(User order by .name asc limit 2)
385        .exec(&mut db)
386        .await?;
387    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }]);
388
389    Ok(())
390}
391
392// --- DynamoDB-compatible query macro tests ---
393// These use composite primary keys (partition + local) so queries can be served
394// by DynamoDB's key condition expressions.
395
396#[driver_test(id(ID))]
397pub async fn query_macro_partition_key_eq(test: &mut Test) -> Result<()> {
398    #[derive(Debug, toasty::Model)]
399    #[key(partition = league, local = name)]
400    struct Team {
401        league: String,
402
403        name: String,
404
405        founded: i64,
406    }
407
408    let mut db = test.setup_db(models!(Team)).await;
409
410    for (league, name, founded) in [
411        ("MLS", "Portland Timbers", 2009),
412        ("MLS", "Seattle Sounders FC", 2007),
413        ("EPL", "Arsenal", 1886),
414        ("EPL", "Chelsea", 1905),
415    ] {
416        toasty::create!(Team {
417            league: league,
418            name: name,
419            founded: founded
420        })
421        .exec(&mut db)
422        .await?;
423    }
424
425    // Filter on partition key only
426    let teams = toasty::query!(Team filter .league == "EPL")
427        .exec(&mut db)
428        .await?;
429
430    assert_struct!(teams, #({ name: "Arsenal" }, { name: "Chelsea" }));
431
432    Ok(())
433}
434
435#[driver_test(id(ID))]
436pub async fn query_macro_partition_and_local_key(test: &mut Test) -> Result<()> {
437    #[derive(Debug, toasty::Model)]
438    #[key(partition = league, local = name)]
439    struct Team {
440        league: String,
441
442        name: String,
443
444        founded: i64,
445    }
446
447    let mut db = test.setup_db(models!(Team)).await;
448
449    for (league, name, founded) in [
450        ("MLS", "Portland Timbers", 2009),
451        ("MLS", "Seattle Sounders FC", 2007),
452        ("EPL", "Arsenal", 1886),
453        ("EPL", "Chelsea", 1905),
454    ] {
455        toasty::create!(Team {
456            league: league,
457            name: name,
458            founded: founded
459        })
460        .exec(&mut db)
461        .await?;
462    }
463
464    // Filter on partition key + local key
465    let teams = toasty::query!(Team filter .league == "MLS" and .name == "Portland Timbers")
466        .exec(&mut db)
467        .await?;
468
469    assert_struct!(teams, [{ name: "Portland Timbers", founded: 2009 }]);
470
471    Ok(())
472}
473
474#[driver_test(id(ID))]
475pub async fn query_macro_local_key_comparison(test: &mut Test) -> Result<()> {
476    #[derive(Debug, toasty::Model)]
477    #[key(partition = kind, local = timestamp)]
478    struct Event {
479        kind: String,
480
481        timestamp: i64,
482    }
483
484    let mut db = test.setup_db(models!(Event)).await;
485
486    for (kind, ts) in [
487        ("info", 0),
488        ("info", 2),
489        ("info", 4),
490        ("info", 6),
491        ("info", 8),
492        ("info", 10),
493        ("warn", 1),
494        ("warn", 3),
495        ("warn", 5),
496    ] {
497        toasty::create!(Event {
498            kind: kind,
499            timestamp: ts
500        })
501        .exec(&mut db)
502        .await?;
503    }
504
505    // Partition key + greater-than on local key
506    let events = toasty::query!(Event filter .kind == "info" and .timestamp > 6)
507        .exec(&mut db)
508        .await?;
509
510    assert_struct!(events, #({ timestamp: 8 }, { timestamp: 10 }));
511
512    // Partition key + less-than-or-equal on local key
513    let events = toasty::query!(Event filter .kind == "info" and .timestamp <= 4)
514        .exec(&mut db)
515        .await?;
516
517    assert_struct!(events, #({ timestamp: 0 }, { timestamp: 2 }, { timestamp: 4 }));
518
519    Ok(())
520}
521
522#[driver_test(id(ID))]
523pub async fn query_macro_partition_key_external_ref(test: &mut Test) -> Result<()> {
524    #[derive(Debug, toasty::Model)]
525    #[key(partition = league, local = name)]
526    struct Team {
527        league: String,
528
529        name: String,
530
531        founded: i64,
532    }
533
534    let mut db = test.setup_db(models!(Team)).await;
535
536    for (league, name, founded) in [
537        ("MLS", "Portland Timbers", 2009),
538        ("MLS", "Seattle Sounders FC", 2007),
539        ("EPL", "Arsenal", 1886),
540    ] {
541        toasty::create!(Team {
542            league: league,
543            name: name,
544            founded: founded
545        })
546        .exec(&mut db)
547        .await?;
548    }
549
550    // Use external variable reference with partition key query
551    let target_league = "MLS";
552    let teams = toasty::query!(Team filter .league == #target_league)
553        .exec(&mut db)
554        .await?;
555
556    assert_struct!(teams, #({ name: "Portland Timbers" }, { name: "Seattle Sounders FC" }));
557
558    Ok(())
559}
560
561#[driver_test(id(ID))]
562pub async fn query_macro_partition_key_with_not(test: &mut Test) -> Result<()> {
563    #[derive(Debug, toasty::Model)]
564    #[key(partition = team, local = name)]
565    struct Player {
566        team: String,
567
568        name: String,
569
570        position: String,
571    }
572
573    let mut db = test.setup_db(models!(Player)).await;
574
575    for (team, name, position) in [
576        ("Timbers", "Diego Valeri", "Midfielder"),
577        ("Timbers", "Fanendo Adi", "Forward"),
578        ("Timbers", "Adam Kwarasey", "Goalkeeper"),
579        ("Sounders", "Clint Dempsey", "Forward"),
580    ] {
581        toasty::create!(Player {
582            team: team,
583            name: name,
584            position: position
585        })
586        .exec(&mut db)
587        .await?;
588    }
589
590    // Partition key + NOT on non-key field
591    let players =
592        toasty::query!(Player filter .team == "Timbers" and not .position == "Midfielder")
593            .exec(&mut db)
594            .await?;
595
596    assert_struct!(players, #({ name: "Adam Kwarasey" }, { name: "Fanendo Adi" }));
597
598    Ok(())
599}
600
601#[driver_test(id(ID))]
602pub async fn query_macro_partition_key_with_or(test: &mut Test) -> Result<()> {
603    #[derive(Debug, toasty::Model)]
604    #[key(partition = team, local = name)]
605    struct Player {
606        team: String,
607
608        name: String,
609
610        position: String,
611
612        number: i64,
613    }
614
615    let mut db = test.setup_db(models!(Player)).await;
616
617    for (team, name, position, number) in [
618        ("Timbers", "Diego Valeri", "Midfielder", 8),
619        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
620        ("Timbers", "Fanendo Adi", "Forward", 9),
621        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
622        ("Sounders", "Clint Dempsey", "Forward", 2),
623    ] {
624        toasty::create!(Player {
625            team: team,
626            name: name,
627            position: position,
628            number: number
629        })
630        .exec(&mut db)
631        .await?;
632    }
633
634    // Partition key + OR on non-key fields
635    let players = toasty::query!(Player filter .team == "Timbers" and (.position == "Forward" or .position == "Goalkeeper"))
636        .exec(&mut db)
637        .await?;
638
639    assert_struct!(players, #({ name: "Adam Kwarasey" }, { name: "Fanendo Adi" }));
640
641    Ok(())
642}
643
644#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
645pub async fn query_macro_filter_in_list(test: &mut Test) -> Result<()> {
646    let mut db = setup(test).await;
647
648    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
649        .exec(&mut db)
650        .await?;
651
652    // Literal list
653    let users = toasty::query!(User filter .name IN ["Alice", "Carl"])
654        .exec(&mut db)
655        .await?;
656
657    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
658
659    Ok(())
660}
661
662#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
663pub async fn query_macro_filter_in_list_external_ref(test: &mut Test) -> Result<()> {
664    let mut db = setup(test).await;
665
666    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
667        .exec(&mut db)
668        .await?;
669
670    // External variable reference
671    let names = vec!["Alice", "Bob"];
672    let users = toasty::query!(User filter .name IN #names)
673        .exec(&mut db)
674        .await?;
675
676    assert_struct!(users, #({ name: "Alice" }, { name: "Bob" }));
677
678    Ok(())
679}
680
681#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(scan))]
682pub async fn query_macro_filter_in_list_with_and(test: &mut Test) -> Result<()> {
683    let mut db = setup(test).await;
684
685    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
686        .exec(&mut db)
687        .await?;
688
689    // IN combined with AND
690    let users = toasty::query!(User filter .name IN ["Alice", "Bob", "Carl"] and .name != "Bob")
691        .exec(&mut db)
692        .await?;
693
694    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
695
696    Ok(())
697}
698
699#[driver_test(id(ID), scenario(crate::scenarios::two_models))]
700pub async fn query_macro_filter_in_list_by_pk(test: &mut Test) -> Result<()> {
701    let mut db = setup(test).await;
702
703    // Create several users and collect their IDs
704    let mut ids = Vec::new();
705    for name in ["Alice", "Bob", "Carl", "Diana"] {
706        let user = User::create().name(name).exec(&mut db).await?;
707        ids.push(user.id);
708    }
709
710    // Batch fetch a subset by primary key using IN
711    let target_ids = vec![ids[0], ids[2]]; // Alice and Carl
712    let users = toasty::query!(User filter .id IN #target_ids)
713        .exec(&mut db)
714        .await?;
715
716    assert_eq!(users.len(), 2);
717    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
718
719    Ok(())
720}