toasty_driver_integration_suite/tests/
crud_query_macro.rs

1use crate::prelude::*;
2
3#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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(sql))]
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), requires(sql))]
249pub async fn query_macro_filter_bool_literal(test: &mut Test) -> Result<()> {
250    #[derive(Debug, toasty::Model)]
251    struct Item {
252        #[key]
253        #[auto]
254        id: ID,
255
256        name: String,
257
258        #[index]
259        active: bool,
260    }
261
262    let mut db = test.setup_db(models!(Item)).await;
263
264    toasty::create!(Item::[
265        { name: "on", active: true },
266        { name: "off", active: false },
267    ])
268    .exec(&mut db)
269    .await?;
270
271    let items = toasty::query!(Item filter .active == true)
272        .exec(&mut db)
273        .await?;
274
275    assert_struct!(items, [{ name: "on" }]);
276
277    Ok(())
278}
279
280// --- ORDER BY, LIMIT, OFFSET tests ---
281
282#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
283pub async fn query_macro_order_by_asc(test: &mut Test) -> Result<()> {
284    let mut db = setup(test).await;
285
286    toasty::create!(User::[{ name: "Carl" }, { name: "Alice" }, { name: "Bob" }])
287        .exec(&mut db)
288        .await?;
289
290    let users = toasty::query!(User ORDER BY .name ASC)
291        .exec(&mut db)
292        .await?;
293    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }]);
294
295    Ok(())
296}
297
298#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
299pub async fn query_macro_order_by_desc(test: &mut Test) -> Result<()> {
300    let mut db = setup(test).await;
301
302    toasty::create!(User::[{ name: "Carl" }, { name: "Alice" }, { name: "Bob" }])
303        .exec(&mut db)
304        .await?;
305
306    let users = toasty::query!(User ORDER BY .name DESC)
307        .exec(&mut db)
308        .await?;
309    assert_struct!(users, [{ name: "Carl" }, { name: "Bob" }, { name: "Alice" }]);
310
311    Ok(())
312}
313
314#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
315pub async fn query_macro_limit(test: &mut Test) -> Result<()> {
316    let mut db = setup(test).await;
317
318    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
319        .exec(&mut db)
320        .await?;
321
322    let users = toasty::query!(User ORDER BY .name ASC LIMIT 2)
323        .exec(&mut db)
324        .await?;
325    assert_eq!(users.len(), 2);
326    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }]);
327
328    Ok(())
329}
330
331#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
332pub async fn query_macro_offset_and_limit(test: &mut Test) -> Result<()> {
333    let mut db = setup(test).await;
334
335    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }, { name: "Diana" }])
336        .exec(&mut db)
337        .await?;
338
339    // Skip 1, take 2 (ordered by name ascending)
340    let users = toasty::query!(User ORDER BY .name ASC OFFSET 1 LIMIT 2)
341        .exec(&mut db)
342        .await?;
343    assert_struct!(users, [{ name: "Bob" }, { name: "Carl" }]);
344
345    Ok(())
346}
347
348#[driver_test(id(ID), scenario(crate::scenarios::user_with_age), requires(sql))]
349pub async fn query_macro_filter_with_order_by_and_limit(test: &mut Test) -> Result<()> {
350    let mut db = setup(test).await;
351
352    toasty::create!(User::[
353        { name: "Alice", age: 30 },
354        { name: "Bob", age: 25 },
355        { name: "Carl", age: 35 },
356        { name: "Diana", age: 20 },
357    ])
358    .exec(&mut db)
359    .await?;
360
361    // Filter age > 20, order by name desc, limit 2
362    let users = toasty::query!(User FILTER .age > 20 ORDER BY .name DESC LIMIT 2)
363        .exec(&mut db)
364        .await?;
365    assert_struct!(users, [{ name: "Carl" }, { name: "Bob" }]);
366
367    Ok(())
368}
369
370#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
371pub async fn query_macro_limit_external_ref(test: &mut Test) -> Result<()> {
372    let mut db = setup(test).await;
373
374    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
375        .exec(&mut db)
376        .await?;
377
378    let n = 1;
379    let users = toasty::query!(User ORDER BY .name ASC LIMIT #n)
380        .exec(&mut db)
381        .await?;
382    assert_struct!(users, [{ name: "Alice" }]);
383
384    Ok(())
385}
386
387#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
388pub async fn query_macro_case_insensitive_order_limit(test: &mut Test) -> Result<()> {
389    let mut db = setup(test).await;
390
391    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
392        .exec(&mut db)
393        .await?;
394
395    // Case-insensitive: order, by, asc, limit
396    let users = toasty::query!(User order by .name asc limit 2)
397        .exec(&mut db)
398        .await?;
399    assert_struct!(users, [{ name: "Alice" }, { name: "Bob" }]);
400
401    Ok(())
402}
403
404// --- DynamoDB-compatible query macro tests ---
405// These use composite primary keys (partition + local) so queries can be served
406// by DynamoDB's key condition expressions.
407
408#[driver_test(id(ID))]
409pub async fn query_macro_partition_key_eq(test: &mut Test) -> Result<()> {
410    #[derive(Debug, toasty::Model)]
411    #[key(partition = league, local = name)]
412    struct Team {
413        league: String,
414
415        name: String,
416
417        founded: i64,
418    }
419
420    let mut db = test.setup_db(models!(Team)).await;
421
422    for (league, name, founded) in [
423        ("MLS", "Portland Timbers", 2009),
424        ("MLS", "Seattle Sounders FC", 2007),
425        ("EPL", "Arsenal", 1886),
426        ("EPL", "Chelsea", 1905),
427    ] {
428        toasty::create!(Team {
429            league: league,
430            name: name,
431            founded: founded
432        })
433        .exec(&mut db)
434        .await?;
435    }
436
437    // Filter on partition key only
438    let teams = toasty::query!(Team filter .league == "EPL")
439        .exec(&mut db)
440        .await?;
441
442    assert_struct!(teams, #({ name: "Arsenal" }, { name: "Chelsea" }));
443
444    Ok(())
445}
446
447#[driver_test(id(ID))]
448pub async fn query_macro_partition_and_local_key(test: &mut Test) -> Result<()> {
449    #[derive(Debug, toasty::Model)]
450    #[key(partition = league, local = name)]
451    struct Team {
452        league: String,
453
454        name: String,
455
456        founded: i64,
457    }
458
459    let mut db = test.setup_db(models!(Team)).await;
460
461    for (league, name, founded) in [
462        ("MLS", "Portland Timbers", 2009),
463        ("MLS", "Seattle Sounders FC", 2007),
464        ("EPL", "Arsenal", 1886),
465        ("EPL", "Chelsea", 1905),
466    ] {
467        toasty::create!(Team {
468            league: league,
469            name: name,
470            founded: founded
471        })
472        .exec(&mut db)
473        .await?;
474    }
475
476    // Filter on partition key + local key
477    let teams = toasty::query!(Team filter .league == "MLS" and .name == "Portland Timbers")
478        .exec(&mut db)
479        .await?;
480
481    assert_struct!(teams, [{ name: "Portland Timbers", founded: 2009 }]);
482
483    Ok(())
484}
485
486#[driver_test(id(ID))]
487pub async fn query_macro_local_key_comparison(test: &mut Test) -> Result<()> {
488    #[derive(Debug, toasty::Model)]
489    #[key(partition = kind, local = timestamp)]
490    struct Event {
491        kind: String,
492
493        timestamp: i64,
494    }
495
496    let mut db = test.setup_db(models!(Event)).await;
497
498    for (kind, ts) in [
499        ("info", 0),
500        ("info", 2),
501        ("info", 4),
502        ("info", 6),
503        ("info", 8),
504        ("info", 10),
505        ("warn", 1),
506        ("warn", 3),
507        ("warn", 5),
508    ] {
509        toasty::create!(Event {
510            kind: kind,
511            timestamp: ts
512        })
513        .exec(&mut db)
514        .await?;
515    }
516
517    // Partition key + greater-than on local key
518    let events = toasty::query!(Event filter .kind == "info" and .timestamp > 6)
519        .exec(&mut db)
520        .await?;
521
522    assert_struct!(events, #({ timestamp: 8 }, { timestamp: 10 }));
523
524    // Partition key + less-than-or-equal on local key
525    let events = toasty::query!(Event filter .kind == "info" and .timestamp <= 4)
526        .exec(&mut db)
527        .await?;
528
529    assert_struct!(events, #({ timestamp: 0 }, { timestamp: 2 }, { timestamp: 4 }));
530
531    Ok(())
532}
533
534#[driver_test(id(ID))]
535pub async fn query_macro_partition_key_external_ref(test: &mut Test) -> Result<()> {
536    #[derive(Debug, toasty::Model)]
537    #[key(partition = league, local = name)]
538    struct Team {
539        league: String,
540
541        name: String,
542
543        founded: i64,
544    }
545
546    let mut db = test.setup_db(models!(Team)).await;
547
548    for (league, name, founded) in [
549        ("MLS", "Portland Timbers", 2009),
550        ("MLS", "Seattle Sounders FC", 2007),
551        ("EPL", "Arsenal", 1886),
552    ] {
553        toasty::create!(Team {
554            league: league,
555            name: name,
556            founded: founded
557        })
558        .exec(&mut db)
559        .await?;
560    }
561
562    // Use external variable reference with partition key query
563    let target_league = "MLS";
564    let teams = toasty::query!(Team filter .league == #target_league)
565        .exec(&mut db)
566        .await?;
567
568    assert_struct!(teams, #({ name: "Portland Timbers" }, { name: "Seattle Sounders FC" }));
569
570    Ok(())
571}
572
573#[driver_test(id(ID))]
574pub async fn query_macro_partition_key_with_not(test: &mut Test) -> Result<()> {
575    #[derive(Debug, toasty::Model)]
576    #[key(partition = team, local = name)]
577    struct Player {
578        team: String,
579
580        name: String,
581
582        position: String,
583    }
584
585    let mut db = test.setup_db(models!(Player)).await;
586
587    for (team, name, position) in [
588        ("Timbers", "Diego Valeri", "Midfielder"),
589        ("Timbers", "Fanendo Adi", "Forward"),
590        ("Timbers", "Adam Kwarasey", "Goalkeeper"),
591        ("Sounders", "Clint Dempsey", "Forward"),
592    ] {
593        toasty::create!(Player {
594            team: team,
595            name: name,
596            position: position
597        })
598        .exec(&mut db)
599        .await?;
600    }
601
602    // Partition key + NOT on non-key field
603    let players =
604        toasty::query!(Player filter .team == "Timbers" and not .position == "Midfielder")
605            .exec(&mut db)
606            .await?;
607
608    assert_struct!(players, #({ name: "Adam Kwarasey" }, { name: "Fanendo Adi" }));
609
610    Ok(())
611}
612
613#[driver_test(id(ID))]
614pub async fn query_macro_partition_key_with_or(test: &mut Test) -> Result<()> {
615    #[derive(Debug, toasty::Model)]
616    #[key(partition = team, local = name)]
617    struct Player {
618        team: String,
619
620        name: String,
621
622        position: String,
623
624        number: i64,
625    }
626
627    let mut db = test.setup_db(models!(Player)).await;
628
629    for (team, name, position, number) in [
630        ("Timbers", "Diego Valeri", "Midfielder", 8),
631        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
632        ("Timbers", "Fanendo Adi", "Forward", 9),
633        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
634        ("Sounders", "Clint Dempsey", "Forward", 2),
635    ] {
636        toasty::create!(Player {
637            team: team,
638            name: name,
639            position: position,
640            number: number
641        })
642        .exec(&mut db)
643        .await?;
644    }
645
646    // Partition key + OR on non-key fields
647    let players = toasty::query!(Player filter .team == "Timbers" and (.position == "Forward" or .position == "Goalkeeper"))
648        .exec(&mut db)
649        .await?;
650
651    assert_struct!(players, #({ name: "Adam Kwarasey" }, { name: "Fanendo Adi" }));
652
653    Ok(())
654}
655
656#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
657pub async fn query_macro_filter_in_list(test: &mut Test) -> Result<()> {
658    let mut db = setup(test).await;
659
660    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
661        .exec(&mut db)
662        .await?;
663
664    // Literal list
665    let users = toasty::query!(User filter .name IN ["Alice", "Carl"])
666        .exec(&mut db)
667        .await?;
668
669    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
670
671    Ok(())
672}
673
674#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
675pub async fn query_macro_filter_in_list_external_ref(test: &mut Test) -> Result<()> {
676    let mut db = setup(test).await;
677
678    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
679        .exec(&mut db)
680        .await?;
681
682    // External variable reference
683    let names = vec!["Alice", "Bob"];
684    let users = toasty::query!(User filter .name IN #names)
685        .exec(&mut db)
686        .await?;
687
688    assert_struct!(users, #({ name: "Alice" }, { name: "Bob" }));
689
690    Ok(())
691}
692
693#[driver_test(id(ID), scenario(crate::scenarios::two_models), requires(sql))]
694pub async fn query_macro_filter_in_list_with_and(test: &mut Test) -> Result<()> {
695    let mut db = setup(test).await;
696
697    toasty::create!(User::[{ name: "Alice" }, { name: "Bob" }, { name: "Carl" }])
698        .exec(&mut db)
699        .await?;
700
701    // IN combined with AND
702    let users = toasty::query!(User filter .name IN ["Alice", "Bob", "Carl"] and .name != "Bob")
703        .exec(&mut db)
704        .await?;
705
706    assert_struct!(users, #({ name: "Alice" }, { name: "Carl" }));
707
708    Ok(())
709}
710
711#[driver_test(id(ID))]
712pub async fn query_macro_filter_in_list_by_pk(test: &mut Test) -> Result<()> {
713    #[derive(Debug, toasty::Model)]
714    struct Item {
715        #[key]
716        #[auto]
717        id: ID,
718
719        name: String,
720    }
721
722    let mut db = test.setup_db(models!(Item)).await;
723
724    // Create several items and collect their IDs
725    let mut ids = Vec::new();
726    for name in ["Alice", "Bob", "Carl", "Diana"] {
727        let item = Item::create().name(name).exec(&mut db).await?;
728        ids.push(item.id);
729    }
730
731    // Batch fetch a subset by primary key using IN
732    let target_ids = vec![ids[0], ids[2]]; // Alice and Carl
733    let items = toasty::query!(Item filter .id IN #target_ids)
734        .exec(&mut db)
735        .await?;
736
737    assert_eq!(items.len(), 2);
738    assert_struct!(items, #({ name: "Alice" }, { name: "Carl" }));
739
740    Ok(())
741}