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 {
342        // DynamoDB requires key conditions for queries - OR filters without
343        // key conditions should return an error
344        assert!(
345            result.is_err(),
346            "Expected error for OR query without key condition on non-SQL database"
347        );
348    }
349    Ok(())
350}
351
352#[driver_test(id(ID))]
353pub async fn query_or_multiple(test: &mut Test) -> Result<()> {
354    #[derive(Debug, toasty::Model)]
355    struct User {
356        #[key]
357        #[auto]
358        id: ID,
359
360        name: String,
361
362        #[allow(dead_code)]
363        age: i64,
364    }
365
366    let mut db = test.setup_db(models!(User)).await;
367
368    // Create some users
369    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
370        User::create().name(name).age(age).exec(&mut db).await?;
371    }
372
373    // Query with multiple OR conditions: name = "Alice" OR age = 35 OR age = 40
374    let result = User::filter(
375        User::fields()
376            .name()
377            .eq("Alice")
378            .or(User::fields().age().eq(35))
379            .or(User::fields().age().eq(40)),
380    )
381    .exec(&mut db)
382    .await;
383
384    if test.capability().sql {
385        let users = result?;
386        assert_eq!(3, users.len());
387        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
388        names.sort();
389        assert_eq!(names, ["Alice", "Charlie", "Diana"]);
390    } else {
391        // DynamoDB requires key conditions for queries - OR filters without
392        // key conditions should return an error
393        assert!(
394            result.is_err(),
395            "Expected error for OR query without key condition on non-SQL database"
396        );
397    }
398    Ok(())
399}
400
401#[driver_test(id(ID))]
402pub async fn query_or_and_combined(test: &mut Test) -> Result<()> {
403    #[derive(Debug, toasty::Model)]
404    struct User {
405        #[key]
406        #[auto]
407        id: ID,
408
409        name: String,
410
411        #[allow(dead_code)]
412        age: i64,
413
414        #[allow(dead_code)]
415        active: bool,
416    }
417
418    let mut db = test.setup_db(models!(User)).await;
419
420    // Create some users
421    for (name, age, active) in [
422        ("Alice", 25, true),
423        ("Bob", 30, false),
424        ("Charlie", 35, true),
425        ("Diana", 40, false),
426        ("Eve", 25, false),
427    ] {
428        User::create()
429            .name(name)
430            .age(age)
431            .active(active)
432            .exec(&mut db)
433            .await?;
434    }
435
436    // Query with OR and AND: (name = "Alice" OR age = 35) AND active = true
437    let result = User::filter(
438        User::fields()
439            .name()
440            .eq("Alice")
441            .or(User::fields().age().eq(35))
442            .and(User::fields().active().eq(true)),
443    )
444    .exec(&mut db)
445    .await;
446
447    if test.capability().sql {
448        let users = result?;
449        assert_eq!(2, users.len());
450        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
451        names.sort();
452        assert_eq!(names, ["Alice", "Charlie"]);
453    } else {
454        // DynamoDB requires key conditions for queries - OR filters without
455        // key conditions should return an error
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(sql))]
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 {
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        // DynamoDB requires key conditions for queries - NOT filters without
893        // key conditions should return an error
894        assert!(
895            result.is_err(),
896            "Expected error for NOT query without key condition on non-SQL database"
897        );
898    }
899    Ok(())
900}
901
902#[driver_test(id(ID))]
903pub async fn query_not_and_combined(test: &mut Test) -> Result<()> {
904    #[derive(Debug, toasty::Model)]
905    struct User {
906        #[key]
907        #[auto]
908        id: ID,
909
910        name: String,
911
912        #[allow(dead_code)]
913        age: i64,
914
915        #[allow(dead_code)]
916        active: bool,
917    }
918
919    let mut db = test.setup_db(models!(User)).await;
920
921    // Create some users
922    for (name, age, active) in [
923        ("Alice", 25, true),
924        ("Bob", 30, false),
925        ("Charlie", 35, true),
926        ("Diana", 40, false),
927        ("Eve", 25, false),
928    ] {
929        User::create()
930            .name(name)
931            .age(age)
932            .active(active)
933            .exec(&mut db)
934            .await?;
935    }
936
937    // Query with NOT combined with AND: active = true AND NOT (age = 25)
938    // Should return only Charlie (active=true, age=35)
939    let result = User::filter(
940        User::fields()
941            .active()
942            .eq(true)
943            .and(User::fields().age().eq(25).not()),
944    )
945    .exec(&mut db)
946    .await;
947
948    if test.capability().sql {
949        let users = result?;
950        assert_eq!(1, users.len());
951        assert_eq!("Charlie", users[0].name);
952    } else {
953        assert!(
954            result.is_err(),
955            "Expected error for NOT query without key condition on non-SQL database"
956        );
957    }
958    Ok(())
959}
960
961#[driver_test(id(ID))]
962pub async fn query_not_or_combined(test: &mut Test) -> Result<()> {
963    #[derive(Debug, toasty::Model)]
964    struct User {
965        #[key]
966        #[auto]
967        id: ID,
968
969        name: String,
970
971        #[allow(dead_code)]
972        age: i64,
973    }
974
975    let mut db = test.setup_db(models!(User)).await;
976
977    // Create some users
978    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
979        User::create().name(name).age(age).exec(&mut db).await?;
980    }
981
982    // Query with NOT combined with OR: NOT (name = "Alice" OR name = "Bob")
983    // Should return Charlie and Diana
984    let result = User::filter(
985        User::fields()
986            .name()
987            .eq("Alice")
988            .or(User::fields().name().eq("Bob"))
989            .not(),
990    )
991    .exec(&mut db)
992    .await;
993
994    if test.capability().sql {
995        let users = result?;
996        assert_eq!(2, users.len());
997        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
998        names.sort();
999        assert_eq!(names, ["Charlie", "Diana"]);
1000    } else {
1001        assert!(
1002            result.is_err(),
1003            "Expected error for NOT query without key condition on non-SQL database"
1004        );
1005    }
1006    Ok(())
1007}
1008
1009#[driver_test(id(ID))]
1010pub async fn query_not_with_index(test: &mut Test) -> Result<()> {
1011    #[derive(Debug, toasty::Model)]
1012    #[key(partition = team, local = name)]
1013    struct Player {
1014        team: String,
1015
1016        name: String,
1017
1018        #[allow(dead_code)]
1019        position: String,
1020
1021        #[allow(dead_code)]
1022        number: i64,
1023    }
1024
1025    let mut db = test.setup_db(models!(Player)).await;
1026
1027    // Create some players
1028    for (team, name, position, number) in [
1029        ("Timbers", "Diego Valeri", "Midfielder", 8),
1030        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
1031        ("Timbers", "Diego Chara", "Midfielder", 21),
1032        ("Timbers", "Fanendo Adi", "Forward", 9),
1033        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
1034        ("Sounders", "Clint Dempsey", "Forward", 2),
1035        ("Sounders", "Obafemi Martins", "Forward", 9),
1036        ("Sounders", "Osvaldo Alonso", "Midfielder", 6),
1037    ] {
1038        Player::create()
1039            .team(team)
1040            .name(name)
1041            .position(position)
1042            .number(number)
1043            .exec(&mut db)
1044            .await?;
1045    }
1046
1047    // Query with partition key AND NOT condition on non-indexed field
1048    // team = "Timbers" AND NOT (position = "Midfielder")
1049    // Should return Fanendo Adi (Forward) and Adam Kwarasey (Goalkeeper)
1050    let players = Player::filter(
1051        Player::fields()
1052            .team()
1053            .eq("Timbers")
1054            .and(Player::fields().position().eq("Midfielder").not()),
1055    )
1056    .exec(&mut db)
1057    .await?;
1058
1059    assert_eq!(2, players.len());
1060    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1061    names.sort();
1062    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
1063
1064    // Query with partition key AND NOT with comparison
1065    // team = "Timbers" AND NOT (number > 8)
1066    // Should return players with number <= 8: Diego Valeri (8), Darlington Nagbe (6), Adam Kwarasey (1)
1067    let players = Player::filter(
1068        Player::fields()
1069            .team()
1070            .eq("Timbers")
1071            .and(Player::fields().number().gt(8).not()),
1072    )
1073    .exec(&mut db)
1074    .await?;
1075
1076    assert_eq!(3, players.len());
1077    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1078    names.sort();
1079    assert_eq!(names, ["Adam Kwarasey", "Darlington Nagbe", "Diego Valeri"]);
1080    Ok(())
1081}
1082
1083#[driver_test(id(ID))]
1084pub async fn query_not_operator_syntax(test: &mut Test) -> Result<()> {
1085    #[derive(Debug, toasty::Model)]
1086    #[key(partition = team, local = name)]
1087    struct Player {
1088        team: String,
1089
1090        name: String,
1091
1092        #[allow(dead_code)]
1093        position: String,
1094
1095        #[allow(dead_code)]
1096        number: i64,
1097    }
1098
1099    let mut db = test.setup_db(models!(Player)).await;
1100
1101    for (team, name, position, number) in [
1102        ("Timbers", "Diego Valeri", "Midfielder", 8),
1103        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
1104        ("Timbers", "Diego Chara", "Midfielder", 21),
1105        ("Timbers", "Fanendo Adi", "Forward", 9),
1106        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
1107    ] {
1108        Player::create()
1109            .team(team)
1110            .name(name)
1111            .position(position)
1112            .number(number)
1113            .exec(&mut db)
1114            .await?;
1115    }
1116
1117    // Use the ! operator instead of .not()
1118    // team = "Timbers" AND !(position = "Midfielder")
1119    let players = Player::filter(
1120        Player::fields()
1121            .team()
1122            .eq("Timbers")
1123            .and(!Player::fields().position().eq("Midfielder")),
1124    )
1125    .exec(&mut db)
1126    .await?;
1127
1128    assert_eq!(2, players.len());
1129    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1130    names.sort();
1131    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
1132
1133    // ! on a compound expression: !(number > 8 OR position = "Goalkeeper")
1134    let players = Player::filter(
1135        Player::fields().team().eq("Timbers").and(
1136            !(Player::fields()
1137                .number()
1138                .gt(8)
1139                .or(Player::fields().position().eq("Goalkeeper"))),
1140        ),
1141    )
1142    .exec(&mut db)
1143    .await?;
1144
1145    // Excludes Diego Chara (21), Fanendo Adi (9), Adam Kwarasey (Goalkeeper)
1146    // Keeps Diego Valeri (8, Midfielder), Darlington Nagbe (6, Midfielder)
1147    assert_eq!(2, players.len());
1148    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1149    names.sort();
1150    assert_eq!(names, ["Darlington Nagbe", "Diego Valeri"]);
1151    Ok(())
1152}