Skip to main content

toasty_driver_integration_suite/tests/
crud_query.rs

1//! Test querying models with various filters and constraints
2
3use crate::prelude::*;
4use toasty_core::{
5    driver::Operation,
6    stmt::{Expr, ExprSet, Statement},
7};
8
9#[driver_test(id(ID))]
10pub async fn query_index_eq(test: &mut Test) -> Result<()> {
11    #[derive(Debug, toasty::Model)]
12    struct User {
13        #[key]
14        #[auto]
15        id: ID,
16
17        #[index]
18        name: String,
19
20        email: String,
21    }
22
23    let mut db = test.setup_db(models!(User)).await;
24
25    // Create a few users
26    for &(name, email) in &[
27        ("one", "one@example.com"),
28        ("two", "two@example.com"),
29        ("three", "three@example.com"),
30    ] {
31        User::create().name(name).email(email).exec(&mut db).await?;
32    }
33
34    let users = User::filter_by_name("one").exec(&mut db).await?;
35
36    assert_eq!(1, users.len());
37    assert_eq!("one", users[0].name);
38
39    // Create a second user named "one"
40
41    User::create()
42        .name("one")
43        .email("one-two@example.com")
44        .exec(&mut db)
45        .await?;
46
47    let mut users = User::filter_by_name("one").exec(&mut db).await?;
48
49    users.sort_by_key(|u| u.email.clone());
50
51    assert_eq!(2, users.len());
52    assert_eq!("one", users[0].name);
53    assert_eq!("one-two@example.com", users[0].email);
54
55    assert_eq!("one", users[1].name);
56    assert_eq!("one@example.com", users[1].email);
57    Ok(())
58}
59
60#[driver_test(id(ID))]
61pub async fn query_partition_key_string_eq(test: &mut Test) -> Result<()> {
62    #[derive(Debug, toasty::Model)]
63    #[key(partition = league, local = name)]
64    struct Team {
65        league: String,
66
67        name: String,
68
69        founded: i64,
70    }
71
72    let mut db = test.setup_db(models!(Team)).await;
73
74    // Create some teams
75    for (league, name, founded) in [
76        ("MLS", "Portland Timbers", 2009),
77        ("MLS", "Seattle Sounders FC", 2007),
78        ("MLS", "Vancouver Whitecaps FC", 2009),
79        ("MLS", "Los Angeles Football Club", 2014),
80        ("MLS", "San Jose Earthquakes", 1994),
81        ("MLS", "LA Galaxy", 1994),
82        ("EPL", "Arsenal", 1886),
83        ("EPL", "Chelsea", 1905),
84        ("EPL", "Manchester United", 1878),
85        ("EPL", "Tottenham", 1882),
86        ("La Liga", "FC Barcelona", 1899),
87        ("La Liga", "Girona FC", 1930),
88        ("La Liga", "Real Madrid", 1902),
89        ("La Liga", "Atlético Madrid", 1903),
90    ]
91    .into_iter()
92    {
93        Team::create()
94            .league(league)
95            .name(name)
96            .founded(founded)
97            .exec(&mut db)
98            .await?;
99    }
100
101    // Query on the partition key only
102    let teams = Team::filter(Team::fields().league().eq("EPL"))
103        .exec(&mut db)
104        .await?;
105
106    let mut names = teams.iter().map(|team| &team.name).collect::<Vec<_>>();
107    names.sort();
108
109    assert_eq!(
110        names,
111        ["Arsenal", "Chelsea", "Manchester United", "Tottenham"]
112    );
113
114    // Query on the partition key and local key
115    let teams = Team::filter(
116        Team::fields()
117            .league()
118            .eq("MLS")
119            .and(Team::fields().name().eq("Portland Timbers")),
120    )
121    .exec(&mut db)
122    .await?;
123
124    let mut names = teams.iter().map(|team| &team.name).collect::<Vec<_>>();
125    names.sort();
126
127    assert_eq!(names, ["Portland Timbers"]);
128
129    // Query on the partition key and a non-index field
130    let teams = Team::filter(
131        Team::fields()
132            .league()
133            .eq("MLS")
134            .and(Team::fields().founded().eq(2009)),
135    )
136    .exec(&mut db)
137    .await?;
138
139    let mut names = teams.iter().map(|team| &team.name).collect::<Vec<_>>();
140    names.sort();
141
142    assert_eq!(names, ["Portland Timbers", "Vancouver Whitecaps FC"]);
143
144    // Query on the partition key, local key, and a non-index field with a match
145    let teams = Team::filter(
146        Team::fields()
147            .league()
148            .eq("MLS")
149            .and(Team::fields().founded().eq(2009))
150            .and(Team::fields().name().eq("Portland Timbers")),
151    )
152    .exec(&mut db)
153    .await?;
154
155    assert_eq!(1, teams.len());
156    assert!(teams.iter().all(|team| team.founded == 2009));
157
158    let mut names = teams.iter().map(|team| &team.name).collect::<Vec<_>>();
159    names.sort();
160
161    assert_eq!(names, ["Portland Timbers"]);
162
163    // Query on the partition key, local key, and a non-index field without a match
164    let teams = Team::filter(
165        Team::fields()
166            .league()
167            .eq("MLS")
168            .and(Team::fields().founded().eq(2009))
169            .and(Team::fields().name().eq("LA Galaxy")),
170    )
171    .exec(&mut db)
172    .await?;
173
174    assert!(teams.is_empty());
175    Ok(())
176}
177
178#[driver_test(id(ID))]
179pub async fn query_local_key_cmp(test: &mut Test) -> Result<()> {
180    #[derive(Debug, toasty::Model)]
181    #[key(partition = kind, local = timestamp)]
182    struct Event {
183        kind: String,
184
185        timestamp: i64,
186    }
187
188    let mut db = test.setup_db(models!(Event)).await;
189
190    // Create a bunch of entries
191    for (kind, ts) in [
192        ("info", 0),
193        ("warn", 1),
194        ("info", 2),
195        ("warn", 3),
196        ("info", 4),
197        ("warn", 5),
198        ("info", 6),
199        ("warn", 7),
200        ("info", 8),
201        ("warn", 9),
202        ("info", 10),
203        ("warn", 11),
204        ("info", 12),
205        ("warn", 13),
206        ("info", 14),
207        ("warn", 15),
208        ("info", 16),
209        ("warn", 17),
210        ("info", 18),
211        ("warn", 19),
212    ] {
213        Event::create()
214            .kind(kind)
215            .timestamp(ts)
216            .exec(&mut db)
217            .await?;
218    }
219
220    let events: Vec<_> = Event::filter_by_kind("info")
221        .filter(Event::fields().timestamp().ne(10))
222        .exec(&mut db)
223        .await?;
224
225    assert_eq_unordered!(
226        events.iter().map(|event| event.timestamp),
227        [&0, &2, &4, &6, &8, &12, &14, &16, &18,]
228    );
229
230    let events: Vec<_> = Event::filter_by_kind("info")
231        .filter(Event::fields().timestamp().gt(10))
232        .exec(&mut db)
233        .await?;
234
235    assert_eq_unordered!(
236        events.iter().map(|event| event.timestamp),
237        [&12, &14, &16, &18,]
238    );
239
240    let events: Vec<_> = Event::filter_by_kind("info")
241        .filter(Event::fields().timestamp().ge(10))
242        .exec(&mut db)
243        .await?;
244
245    assert_eq_unordered!(
246        events.iter().map(|event| event.timestamp),
247        [&10, &12, &14, &16, &18,]
248    );
249
250    let events: Vec<_> = Event::filter_by_kind("info")
251        .filter(Event::fields().timestamp().lt(10))
252        .exec(&mut db)
253        .await?;
254
255    assert_eq_unordered!(
256        events.iter().map(|event| event.timestamp),
257        [&0, &2, &4, &6, &8]
258    );
259
260    let events: Vec<_> = Event::filter_by_kind("info")
261        .filter(Event::fields().timestamp().le(10))
262        .exec(&mut db)
263        .await?;
264
265    assert_eq_unordered!(
266        events.iter().map(|event| event.timestamp),
267        [&0, &2, &4, &6, &8, &10]
268    );
269    Ok(())
270}
271
272#[driver_test(id(ID))]
273pub async fn query_or_basic(test: &mut Test) -> Result<()> {
274    #[derive(Debug, toasty::Model)]
275    struct User {
276        #[key]
277        #[auto]
278        id: ID,
279
280        name: String,
281
282        #[allow(dead_code)]
283        age: i64,
284    }
285
286    let mut db = test.setup_db(models!(User)).await;
287    let _name_column = db.schema().table_for(User::id()).columns[1].id;
288    let _age_column = db.schema().table_for(User::id()).columns[2].id;
289
290    // Create some users
291    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
292        User::create().name(name).age(age).exec(&mut db).await?;
293    }
294
295    // Clear the log after setup
296    test.log().clear();
297
298    // Query with OR condition: name = "Alice" OR age = 35
299    let result = User::filter(
300        User::fields()
301            .name()
302            .eq("Alice")
303            .or(User::fields().age().eq(35)),
304    )
305    .exec(&mut db)
306    .await;
307
308    if test.capability().sql {
309        let users = result?;
310        assert_eq!(2, users.len());
311        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
312        names.sort();
313        assert_eq!(names, ["Alice", "Charlie"]);
314
315        // Verify the driver operation contains the expected OR filter
316        let (op, _) = test.log().pop();
317
318        assert_struct!(&op, Operation::QuerySql({
319            stmt: Statement::Query({
320                body: ExprSet::Select({
321                    filter.expr: Some(Expr::Or({
322                        // TODO: assert_struct! needs a set matcher
323                        /*
324                        operands: [
325                            Expr::BinaryOp({
326                                op: BinaryOp::Eq,
327                                *lhs: == Expr::column(age_column),
328                                *rhs: Expr::Value(Value::I64(35)),
329                            }),
330                            Expr::BinaryOp({
331                                op: BinaryOp::Eq,
332                                *lhs: == Expr::column(name_column),
333                                *rhs: Expr::Value(Value::String("Alice")),
334                            }),
335                        ],
336                        */
337                    })),
338                }),
339            }),
340        }));
341    } else if test.capability().scan {
342        let users = result?;
343        assert_eq!(2, users.len());
344        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
345        names.sort();
346        assert_eq!(names, ["Alice", "Charlie"]);
347    } else {
348        assert!(
349            result.is_err(),
350            "Expected error for OR query without key condition on non-SQL database"
351        );
352    }
353    Ok(())
354}
355
356#[driver_test(id(ID))]
357pub async fn query_or_multiple(test: &mut Test) -> Result<()> {
358    #[derive(Debug, toasty::Model)]
359    struct User {
360        #[key]
361        #[auto]
362        id: ID,
363
364        name: String,
365
366        #[allow(dead_code)]
367        age: i64,
368    }
369
370    let mut db = test.setup_db(models!(User)).await;
371
372    // Create some users
373    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
374        User::create().name(name).age(age).exec(&mut db).await?;
375    }
376
377    // Query with multiple OR conditions: name = "Alice" OR age = 35 OR age = 40
378    let result = User::filter(
379        User::fields()
380            .name()
381            .eq("Alice")
382            .or(User::fields().age().eq(35))
383            .or(User::fields().age().eq(40)),
384    )
385    .exec(&mut db)
386    .await;
387
388    if test.capability().sql || test.capability().scan {
389        let users = result?;
390        assert_eq!(3, users.len());
391        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
392        names.sort();
393        assert_eq!(names, ["Alice", "Charlie", "Diana"]);
394    } else {
395        assert!(
396            result.is_err(),
397            "Expected error for OR query without key condition on non-SQL database"
398        );
399    }
400    Ok(())
401}
402
403#[driver_test(id(ID))]
404pub async fn query_or_and_combined(test: &mut Test) -> Result<()> {
405    #[derive(Debug, toasty::Model)]
406    struct User {
407        #[key]
408        #[auto]
409        id: ID,
410
411        name: String,
412
413        #[allow(dead_code)]
414        age: i64,
415
416        #[allow(dead_code)]
417        active: bool,
418    }
419
420    let mut db = test.setup_db(models!(User)).await;
421
422    // Create some users
423    for (name, age, active) in [
424        ("Alice", 25, true),
425        ("Bob", 30, false),
426        ("Charlie", 35, true),
427        ("Diana", 40, false),
428        ("Eve", 25, false),
429    ] {
430        User::create()
431            .name(name)
432            .age(age)
433            .active(active)
434            .exec(&mut db)
435            .await?;
436    }
437
438    // Query with OR and AND: (name = "Alice" OR age = 35) AND active = true
439    let result = User::filter(
440        User::fields()
441            .name()
442            .eq("Alice")
443            .or(User::fields().age().eq(35))
444            .and(User::fields().active().eq(true)),
445    )
446    .exec(&mut db)
447    .await;
448
449    if test.capability().sql || test.capability().scan {
450        let users = result?;
451        assert_eq!(2, users.len());
452        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
453        names.sort();
454        assert_eq!(names, ["Alice", "Charlie"]);
455    } else {
456        assert!(
457            result.is_err(),
458            "Expected error for OR query without key condition on non-SQL database"
459        );
460    }
461    Ok(())
462}
463
464#[driver_test(id(ID))]
465pub async fn query_or_with_index(test: &mut Test) -> Result<()> {
466    #[derive(Debug, toasty::Model)]
467    #[key(partition = team, local = name)]
468    struct Player {
469        team: String,
470
471        name: String,
472
473        #[allow(dead_code)]
474        position: String,
475
476        #[allow(dead_code)]
477        number: i64,
478    }
479
480    let mut db = test.setup_db(models!(Player)).await;
481
482    // Create some players on different teams
483    for (team, name, position, number) in [
484        ("Timbers", "Diego Valeri", "Midfielder", 8),
485        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
486        ("Timbers", "Diego Chara", "Midfielder", 21),
487        ("Timbers", "Fanendo Adi", "Forward", 9),
488        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
489        ("Sounders", "Clint Dempsey", "Forward", 2),
490        ("Sounders", "Obafemi Martins", "Forward", 9),
491        ("Sounders", "Osvaldo Alonso", "Midfielder", 6),
492    ] {
493        Player::create()
494            .team(team)
495            .name(name)
496            .position(position)
497            .number(number)
498            .exec(&mut db)
499            .await?;
500    }
501
502    // Query with partition key AND OR conditions on non-indexed fields
503    // This should work on both SQL and DynamoDB
504    let players = Player::filter(
505        Player::fields().team().eq("Timbers").and(
506            Player::fields()
507                .position()
508                .eq("Forward")
509                .or(Player::fields().position().eq("Goalkeeper")),
510        ),
511    )
512    .exec(&mut db)
513    .await?;
514
515    assert_eq!(2, players.len());
516    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
517    names.sort();
518    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
519
520    // Query with partition key AND more complex OR conditions
521    let players = Player::filter(
522        Player::fields().team().eq("Timbers").and(
523            Player::fields()
524                .number()
525                .eq(8)
526                .or(Player::fields().number().eq(21))
527                .or(Player::fields().number().eq(9)),
528        ),
529    )
530    .exec(&mut db)
531    .await?;
532
533    assert_eq!(3, players.len());
534    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
535    names.sort();
536    assert_eq!(names, ["Diego Chara", "Diego Valeri", "Fanendo Adi"]);
537    Ok(())
538}
539
540#[driver_test(id(ID))]
541pub async fn query_or_on_partition_key(test: &mut Test) -> Result<()> {
542    // OR directly on the partition key of a composite primary key.
543    //
544    // SQL: plain OR in WHERE clause.
545    // DynamoDB: fan-out into two QueryPk calls, one per partition key value,
546    //           because KeyConditionExpression does not support OR.
547    #[derive(Debug, toasty::Model)]
548    #[key(partition = team, local = name)]
549    struct Player {
550        team: String,
551
552        name: String,
553
554        #[allow(dead_code)]
555        position: String,
556    }
557
558    let mut db = test.setup_db(models!(Player)).await;
559
560    for (team, name, position) in [
561        ("Timbers", "Diego Valeri", "Midfielder"),
562        ("Timbers", "Fanendo Adi", "Forward"),
563        ("Sounders", "Clint Dempsey", "Forward"),
564        ("Sounders", "Osvaldo Alonso", "Midfielder"),
565        ("Galaxy", "Robbie Keane", "Forward"),
566    ] {
567        Player::create()
568            .team(team)
569            .name(name)
570            .position(position)
571            .exec(&mut db)
572            .await?;
573    }
574
575    // team = "Timbers" OR team = "Sounders"
576    let players = Player::filter(
577        Player::fields()
578            .team()
579            .eq("Timbers")
580            .or(Player::fields().team().eq("Sounders")),
581    )
582    .exec(&mut db)
583    .await?;
584
585    assert_eq!(4, players.len());
586    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
587    names.sort();
588    assert_eq!(
589        names,
590        [
591            "Clint Dempsey",
592            "Diego Valeri",
593            "Fanendo Adi",
594            "Osvaldo Alonso"
595        ]
596    );
597    Ok(())
598}
599
600#[driver_test(id(ID))]
601pub async fn query_or_on_composite_pk(test: &mut Test) -> Result<()> {
602    // OR where each branch fully specifies all composite primary key columns.
603    //
604    // SQL: plain OR in WHERE clause.
605    // DynamoDB: routes to GetByKey (BatchGetItem) because all key columns
606    //           have exact equality predicates, so key_values is populated.
607    #[derive(Debug, toasty::Model)]
608    #[key(partition = team, local = name)]
609    struct Player {
610        team: String,
611
612        name: String,
613
614        #[allow(dead_code)]
615        position: String,
616    }
617
618    let mut db = test.setup_db(models!(Player)).await;
619
620    for (team, name, position) in [
621        ("Timbers", "Diego Valeri", "Midfielder"),
622        ("Timbers", "Fanendo Adi", "Forward"),
623        ("Sounders", "Clint Dempsey", "Forward"),
624        ("Sounders", "Osvaldo Alonso", "Midfielder"),
625    ] {
626        Player::create()
627            .team(team)
628            .name(name)
629            .position(position)
630            .exec(&mut db)
631            .await?;
632    }
633
634    // (team = "Timbers" AND name = "Diego Valeri") OR (team = "Sounders" AND name = "Clint Dempsey")
635    let players = Player::filter(
636        Player::fields()
637            .team()
638            .eq("Timbers")
639            .and(Player::fields().name().eq("Diego Valeri"))
640            .or(Player::fields()
641                .team()
642                .eq("Sounders")
643                .and(Player::fields().name().eq("Clint Dempsey"))),
644    )
645    .exec(&mut db)
646    .await?;
647
648    assert_eq!(2, players.len());
649    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
650    names.sort();
651    assert_eq!(names, ["Clint Dempsey", "Diego Valeri"]);
652    Ok(())
653}
654
655#[driver_test(id(ID))]
656pub async fn query_or_with_comparisons(test: &mut Test) -> Result<()> {
657    #[derive(Debug, toasty::Model)]
658    #[key(partition = team, local = name)]
659    struct Player {
660        team: String,
661
662        name: String,
663
664        #[allow(dead_code)]
665        position: String,
666
667        #[allow(dead_code)]
668        number: i64,
669    }
670
671    let mut db = test.setup_db(models!(Player)).await;
672
673    // Create some players on different teams
674    for (team, name, position, number) in [
675        ("Timbers", "Diego Valeri", "Midfielder", 8),
676        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
677        ("Timbers", "Diego Chara", "Midfielder", 21),
678        ("Timbers", "Fanendo Adi", "Forward", 9),
679        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
680        ("Sounders", "Clint Dempsey", "Forward", 2),
681        ("Sounders", "Obafemi Martins", "Forward", 9),
682        ("Sounders", "Osvaldo Alonso", "Midfielder", 6),
683    ] {
684        Player::create()
685            .team(team)
686            .name(name)
687            .position(position)
688            .number(number)
689            .exec(&mut db)
690            .await?;
691    }
692
693    // Query with partition key AND OR conditions using comparisons (not equality)
694    // This won't be optimized to IN list, so tests actual OR expression handling
695    // Using gt/lt instead of ge/le to avoid boundary condition confusion
696    let players = Player::filter(
697        Player::fields().team().eq("Timbers").and(
698            Player::fields()
699                .number()
700                .gt(20)
701                .or(Player::fields().number().lt(2)),
702        ),
703    )
704    .exec(&mut db)
705    .await?;
706
707    assert_eq!(2, players.len());
708    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
709    names.sort();
710    assert_eq!(names, ["Adam Kwarasey", "Diego Chara"]);
711    Ok(())
712}
713
714#[driver_test(id(ID), requires(scan))]
715pub async fn query_arbitrary_constraint(test: &mut Test) -> Result<()> {
716    #[derive(Debug, toasty::Model)]
717    struct Event {
718        #[key]
719        #[auto]
720        id: ID,
721
722        kind: String,
723
724        timestamp: i64,
725    }
726
727    let mut db = test.setup_db(models!(Event)).await;
728
729    // Create a bunch of entries
730    for (kind, ts) in [
731        ("info", 0),
732        ("warn", 1),
733        ("info", 2),
734        ("warn", 3),
735        ("info", 4),
736        ("warn", 5),
737        ("info", 6),
738        ("warn", 7),
739        ("info", 8),
740        ("warn", 9),
741        ("info", 10),
742        ("warn", 11),
743        ("info", 12),
744        ("warn", 13),
745        ("info", 14),
746        ("warn", 15),
747        ("info", 16),
748        ("warn", 17),
749        ("info", 18),
750        ("warn", 19),
751    ] {
752        Event::create()
753            .kind(kind)
754            .timestamp(ts)
755            .exec(&mut db)
756            .await?;
757    }
758
759    let events: Vec<_> = Event::filter(Event::fields().timestamp().gt(12))
760        .exec(&mut db)
761        .await?;
762
763    assert_eq_unordered!(
764        events.iter().map(|event| event.timestamp),
765        [&13, &14, &15, &16, &17, &18, &19,]
766    );
767
768    let events: Vec<_> = Event::filter(
769        Event::fields()
770            .timestamp()
771            .gt(12)
772            .and(Event::fields().kind().ne("info")),
773    )
774    .exec(&mut db)
775    .await?;
776
777    assert!(events.iter().all(|event| event.kind != "info"));
778
779    assert_eq_unordered!(
780        events.iter().map(|event| event.timestamp),
781        [&13, &15, &17, &19,]
782    );
783
784    let events: Vec<_> = Event::filter(
785        Event::fields()
786            .kind()
787            .eq("info")
788            .and(Event::fields().timestamp().ne(10)),
789    )
790    .exec(&mut db)
791    .await?;
792
793    assert_eq_unordered!(
794        events.iter().map(|event| event.timestamp),
795        [&0, &2, &4, &6, &8, &12, &14, &16, &18,]
796    );
797
798    let events: Vec<_> = Event::filter(
799        Event::fields()
800            .kind()
801            .eq("info")
802            .and(Event::fields().timestamp().gt(10)),
803    )
804    .exec(&mut db)
805    .await?;
806
807    assert_eq_unordered!(
808        events.iter().map(|event| event.timestamp),
809        [&12, &14, &16, &18,]
810    );
811
812    let events: Vec<_> = Event::filter(
813        Event::fields()
814            .kind()
815            .eq("info")
816            .and(Event::fields().timestamp().ge(10)),
817    )
818    .exec(&mut db)
819    .await?;
820
821    assert_eq_unordered!(
822        events.iter().map(|event| event.timestamp),
823        [&10, &12, &14, &16, &18,]
824    );
825
826    let events: Vec<_> = Event::filter(
827        Event::fields()
828            .kind()
829            .eq("info")
830            .and(Event::fields().timestamp().lt(10)),
831    )
832    .exec(&mut db)
833    .await?;
834
835    assert_eq_unordered!(
836        events.iter().map(|event| event.timestamp),
837        [&0, &2, &4, &6, &8]
838    );
839
840    let events: Vec<_> = Event::filter(
841        Event::fields()
842            .kind()
843            .eq("info")
844            .and(Event::fields().timestamp().le(10)),
845    )
846    .exec(&mut db)
847    .await?;
848
849    assert_eq_unordered!(
850        events.iter().map(|event| event.timestamp),
851        [&0, &2, &4, &6, &8, &10]
852    );
853    Ok(())
854}
855
856#[driver_test(id(ID))]
857pub async fn query_not_basic(test: &mut Test) -> Result<()> {
858    #[derive(Debug, toasty::Model)]
859    struct User {
860        #[key]
861        #[auto]
862        id: ID,
863
864        name: String,
865
866        #[allow(dead_code)]
867        age: i64,
868    }
869
870    let mut db = test.setup_db(models!(User)).await;
871
872    // Create some users
873    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
874        User::create().name(name).age(age).exec(&mut db).await?;
875    }
876
877    // Clear the log after setup
878    test.log().clear();
879
880    // Query with NOT condition: NOT (name = "Alice")
881    let result = User::filter(User::fields().name().eq("Alice").not())
882        .exec(&mut db)
883        .await;
884
885    if test.capability().sql || test.capability().scan {
886        let users = result?;
887        assert_eq!(3, users.len());
888        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
889        names.sort();
890        assert_eq!(names, ["Bob", "Charlie", "Diana"]);
891    } else {
892        assert!(
893            result.is_err(),
894            "Expected error for NOT query without key condition on non-SQL database"
895        );
896    }
897    Ok(())
898}
899
900#[driver_test(id(ID))]
901pub async fn query_not_and_combined(test: &mut Test) -> Result<()> {
902    #[derive(Debug, toasty::Model)]
903    struct User {
904        #[key]
905        #[auto]
906        id: ID,
907
908        name: String,
909
910        #[allow(dead_code)]
911        age: i64,
912
913        #[allow(dead_code)]
914        active: bool,
915    }
916
917    let mut db = test.setup_db(models!(User)).await;
918
919    // Create some users
920    for (name, age, active) in [
921        ("Alice", 25, true),
922        ("Bob", 30, false),
923        ("Charlie", 35, true),
924        ("Diana", 40, false),
925        ("Eve", 25, false),
926    ] {
927        User::create()
928            .name(name)
929            .age(age)
930            .active(active)
931            .exec(&mut db)
932            .await?;
933    }
934
935    // Query with NOT combined with AND: active = true AND NOT (age = 25)
936    // Should return only Charlie (active=true, age=35)
937    let result = User::filter(
938        User::fields()
939            .active()
940            .eq(true)
941            .and(User::fields().age().eq(25).not()),
942    )
943    .exec(&mut db)
944    .await;
945
946    if test.capability().sql || test.capability().scan {
947        let users = result?;
948        assert_eq!(1, users.len());
949        assert_eq!("Charlie", users[0].name);
950    } else {
951        assert!(
952            result.is_err(),
953            "Expected error for NOT query without key condition on non-SQL database"
954        );
955    }
956    Ok(())
957}
958
959#[driver_test(id(ID))]
960pub async fn query_not_or_combined(test: &mut Test) -> Result<()> {
961    #[derive(Debug, toasty::Model)]
962    struct User {
963        #[key]
964        #[auto]
965        id: ID,
966
967        name: String,
968
969        #[allow(dead_code)]
970        age: i64,
971    }
972
973    let mut db = test.setup_db(models!(User)).await;
974
975    // Create some users
976    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
977        User::create().name(name).age(age).exec(&mut db).await?;
978    }
979
980    // Query with NOT combined with OR: NOT (name = "Alice" OR name = "Bob")
981    // Should return Charlie and Diana
982    let result = User::filter(
983        User::fields()
984            .name()
985            .eq("Alice")
986            .or(User::fields().name().eq("Bob"))
987            .not(),
988    )
989    .exec(&mut db)
990    .await;
991
992    if test.capability().sql || test.capability().scan {
993        let users = result?;
994        assert_eq!(2, users.len());
995        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
996        names.sort();
997        assert_eq!(names, ["Charlie", "Diana"]);
998    } else {
999        assert!(
1000            result.is_err(),
1001            "Expected error for NOT query without key condition on non-SQL database"
1002        );
1003    }
1004    Ok(())
1005}
1006
1007#[driver_test(id(ID))]
1008pub async fn query_not_with_index(test: &mut Test) -> Result<()> {
1009    #[derive(Debug, toasty::Model)]
1010    #[key(partition = team, local = name)]
1011    struct Player {
1012        team: String,
1013
1014        name: String,
1015
1016        #[allow(dead_code)]
1017        position: String,
1018
1019        #[allow(dead_code)]
1020        number: i64,
1021    }
1022
1023    let mut db = test.setup_db(models!(Player)).await;
1024
1025    // Create some players
1026    for (team, name, position, number) in [
1027        ("Timbers", "Diego Valeri", "Midfielder", 8),
1028        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
1029        ("Timbers", "Diego Chara", "Midfielder", 21),
1030        ("Timbers", "Fanendo Adi", "Forward", 9),
1031        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
1032        ("Sounders", "Clint Dempsey", "Forward", 2),
1033        ("Sounders", "Obafemi Martins", "Forward", 9),
1034        ("Sounders", "Osvaldo Alonso", "Midfielder", 6),
1035    ] {
1036        Player::create()
1037            .team(team)
1038            .name(name)
1039            .position(position)
1040            .number(number)
1041            .exec(&mut db)
1042            .await?;
1043    }
1044
1045    // Query with partition key AND NOT condition on non-indexed field
1046    // team = "Timbers" AND NOT (position = "Midfielder")
1047    // Should return Fanendo Adi (Forward) and Adam Kwarasey (Goalkeeper)
1048    let players = Player::filter(
1049        Player::fields()
1050            .team()
1051            .eq("Timbers")
1052            .and(Player::fields().position().eq("Midfielder").not()),
1053    )
1054    .exec(&mut db)
1055    .await?;
1056
1057    assert_eq!(2, players.len());
1058    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1059    names.sort();
1060    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
1061
1062    // Query with partition key AND NOT with comparison
1063    // team = "Timbers" AND NOT (number > 8)
1064    // Should return players with number <= 8: Diego Valeri (8), Darlington Nagbe (6), Adam Kwarasey (1)
1065    let players = Player::filter(
1066        Player::fields()
1067            .team()
1068            .eq("Timbers")
1069            .and(Player::fields().number().gt(8).not()),
1070    )
1071    .exec(&mut db)
1072    .await?;
1073
1074    assert_eq!(3, players.len());
1075    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1076    names.sort();
1077    assert_eq!(names, ["Adam Kwarasey", "Darlington Nagbe", "Diego Valeri"]);
1078    Ok(())
1079}
1080
1081#[driver_test(id(ID))]
1082pub async fn query_not_operator_syntax(test: &mut Test) -> Result<()> {
1083    #[derive(Debug, toasty::Model)]
1084    #[key(partition = team, local = name)]
1085    struct Player {
1086        team: String,
1087
1088        name: String,
1089
1090        #[allow(dead_code)]
1091        position: String,
1092
1093        #[allow(dead_code)]
1094        number: i64,
1095    }
1096
1097    let mut db = test.setup_db(models!(Player)).await;
1098
1099    for (team, name, position, number) in [
1100        ("Timbers", "Diego Valeri", "Midfielder", 8),
1101        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
1102        ("Timbers", "Diego Chara", "Midfielder", 21),
1103        ("Timbers", "Fanendo Adi", "Forward", 9),
1104        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
1105    ] {
1106        Player::create()
1107            .team(team)
1108            .name(name)
1109            .position(position)
1110            .number(number)
1111            .exec(&mut db)
1112            .await?;
1113    }
1114
1115    // Use the ! operator instead of .not()
1116    // team = "Timbers" AND !(position = "Midfielder")
1117    let players = Player::filter(
1118        Player::fields()
1119            .team()
1120            .eq("Timbers")
1121            .and(!Player::fields().position().eq("Midfielder")),
1122    )
1123    .exec(&mut db)
1124    .await?;
1125
1126    assert_eq!(2, players.len());
1127    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1128    names.sort();
1129    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
1130
1131    // ! on a compound expression: !(number > 8 OR position = "Goalkeeper")
1132    let players = Player::filter(
1133        Player::fields().team().eq("Timbers").and(
1134            !(Player::fields()
1135                .number()
1136                .gt(8)
1137                .or(Player::fields().position().eq("Goalkeeper"))),
1138        ),
1139    )
1140    .exec(&mut db)
1141    .await?;
1142
1143    // Excludes Diego Chara (21), Fanendo Adi (9), Adam Kwarasey (Goalkeeper)
1144    // Keeps Diego Valeri (8, Midfielder), Darlington Nagbe (6, Midfielder)
1145    assert_eq!(2, players.len());
1146    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1147    names.sort();
1148    assert_eq!(names, ["Darlington Nagbe", "Diego Valeri"]);
1149    Ok(())
1150}