toasty_driver_integration_suite/tests/
one_model_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                            }),
331                            Expr::BinaryOp(_ {
332                                op: BinaryOp::Eq,
333                                *lhs: == Expr::column(name_column),
334                                *rhs: Expr::Value(Value::String("Alice")),
335                                ..
336                            }),
337                        ],
338                        */
339                        ..
340                    })),
341                    ..
342                }),
343                ..
344            }),
345            ..
346        }));
347    } else {
348        // DynamoDB requires key conditions for queries - OR filters without
349        // key conditions should return an error
350        assert!(
351            result.is_err(),
352            "Expected error for OR query without key condition on non-SQL database"
353        );
354    }
355    Ok(())
356}
357
358#[driver_test(id(ID))]
359pub async fn query_or_multiple(test: &mut Test) -> Result<()> {
360    #[derive(Debug, toasty::Model)]
361    struct User {
362        #[key]
363        #[auto]
364        id: ID,
365
366        name: String,
367
368        #[allow(dead_code)]
369        age: i64,
370    }
371
372    let mut db = test.setup_db(models!(User)).await;
373
374    // Create some users
375    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
376        User::create().name(name).age(age).exec(&mut db).await?;
377    }
378
379    // Query with multiple OR conditions: name = "Alice" OR age = 35 OR age = 40
380    let result = User::filter(
381        User::fields()
382            .name()
383            .eq("Alice")
384            .or(User::fields().age().eq(35))
385            .or(User::fields().age().eq(40)),
386    )
387    .exec(&mut db)
388    .await;
389
390    if test.capability().sql {
391        let users = result?;
392        assert_eq!(3, users.len());
393        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
394        names.sort();
395        assert_eq!(names, ["Alice", "Charlie", "Diana"]);
396    } else {
397        // DynamoDB requires key conditions for queries - OR filters without
398        // key conditions should return an error
399        assert!(
400            result.is_err(),
401            "Expected error for OR query without key condition on non-SQL database"
402        );
403    }
404    Ok(())
405}
406
407#[driver_test(id(ID))]
408pub async fn query_or_and_combined(test: &mut Test) -> Result<()> {
409    #[derive(Debug, toasty::Model)]
410    struct User {
411        #[key]
412        #[auto]
413        id: ID,
414
415        name: String,
416
417        #[allow(dead_code)]
418        age: i64,
419
420        #[allow(dead_code)]
421        active: bool,
422    }
423
424    let mut db = test.setup_db(models!(User)).await;
425
426    // Create some users
427    for (name, age, active) in [
428        ("Alice", 25, true),
429        ("Bob", 30, false),
430        ("Charlie", 35, true),
431        ("Diana", 40, false),
432        ("Eve", 25, false),
433    ] {
434        User::create()
435            .name(name)
436            .age(age)
437            .active(active)
438            .exec(&mut db)
439            .await?;
440    }
441
442    // Query with OR and AND: (name = "Alice" OR age = 35) AND active = true
443    let result = User::filter(
444        User::fields()
445            .name()
446            .eq("Alice")
447            .or(User::fields().age().eq(35))
448            .and(User::fields().active().eq(true)),
449    )
450    .exec(&mut db)
451    .await;
452
453    if test.capability().sql {
454        let users = result?;
455        assert_eq!(2, users.len());
456        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
457        names.sort();
458        assert_eq!(names, ["Alice", "Charlie"]);
459    } else {
460        // DynamoDB requires key conditions for queries - OR filters without
461        // key conditions should return an error
462        assert!(
463            result.is_err(),
464            "Expected error for OR query without key condition on non-SQL database"
465        );
466    }
467    Ok(())
468}
469
470#[driver_test(id(ID))]
471pub async fn query_or_with_index(test: &mut Test) -> Result<()> {
472    #[derive(Debug, toasty::Model)]
473    #[key(partition = team, local = name)]
474    struct Player {
475        team: String,
476
477        name: String,
478
479        #[allow(dead_code)]
480        position: String,
481
482        #[allow(dead_code)]
483        number: i64,
484    }
485
486    let mut db = test.setup_db(models!(Player)).await;
487
488    // Create some players on different teams
489    for (team, name, position, number) in [
490        ("Timbers", "Diego Valeri", "Midfielder", 8),
491        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
492        ("Timbers", "Diego Chara", "Midfielder", 21),
493        ("Timbers", "Fanendo Adi", "Forward", 9),
494        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
495        ("Sounders", "Clint Dempsey", "Forward", 2),
496        ("Sounders", "Obafemi Martins", "Forward", 9),
497        ("Sounders", "Osvaldo Alonso", "Midfielder", 6),
498    ] {
499        Player::create()
500            .team(team)
501            .name(name)
502            .position(position)
503            .number(number)
504            .exec(&mut db)
505            .await?;
506    }
507
508    // Query with partition key AND OR conditions on non-indexed fields
509    // This should work on both SQL and DynamoDB
510    let players = Player::filter(
511        Player::fields().team().eq("Timbers").and(
512            Player::fields()
513                .position()
514                .eq("Forward")
515                .or(Player::fields().position().eq("Goalkeeper")),
516        ),
517    )
518    .exec(&mut db)
519    .await?;
520
521    assert_eq!(2, players.len());
522    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
523    names.sort();
524    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
525
526    // Query with partition key AND more complex OR conditions
527    let players = Player::filter(
528        Player::fields().team().eq("Timbers").and(
529            Player::fields()
530                .number()
531                .eq(8)
532                .or(Player::fields().number().eq(21))
533                .or(Player::fields().number().eq(9)),
534        ),
535    )
536    .exec(&mut db)
537    .await?;
538
539    assert_eq!(3, players.len());
540    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
541    names.sort();
542    assert_eq!(names, ["Diego Chara", "Diego Valeri", "Fanendo Adi"]);
543    Ok(())
544}
545
546#[driver_test(id(ID))]
547pub async fn query_or_on_partition_key(test: &mut Test) -> Result<()> {
548    // OR directly on the partition key of a composite primary key.
549    //
550    // SQL: plain OR in WHERE clause.
551    // DynamoDB: fan-out into two QueryPk calls, one per partition key value,
552    //           because KeyConditionExpression does not support OR.
553    #[derive(Debug, toasty::Model)]
554    #[key(partition = team, local = name)]
555    struct Player {
556        team: String,
557
558        name: String,
559
560        #[allow(dead_code)]
561        position: String,
562    }
563
564    let mut db = test.setup_db(models!(Player)).await;
565
566    for (team, name, position) in [
567        ("Timbers", "Diego Valeri", "Midfielder"),
568        ("Timbers", "Fanendo Adi", "Forward"),
569        ("Sounders", "Clint Dempsey", "Forward"),
570        ("Sounders", "Osvaldo Alonso", "Midfielder"),
571        ("Galaxy", "Robbie Keane", "Forward"),
572    ] {
573        Player::create()
574            .team(team)
575            .name(name)
576            .position(position)
577            .exec(&mut db)
578            .await?;
579    }
580
581    // team = "Timbers" OR team = "Sounders"
582    let players = Player::filter(
583        Player::fields()
584            .team()
585            .eq("Timbers")
586            .or(Player::fields().team().eq("Sounders")),
587    )
588    .exec(&mut db)
589    .await?;
590
591    assert_eq!(4, players.len());
592    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
593    names.sort();
594    assert_eq!(
595        names,
596        [
597            "Clint Dempsey",
598            "Diego Valeri",
599            "Fanendo Adi",
600            "Osvaldo Alonso"
601        ]
602    );
603    Ok(())
604}
605
606#[driver_test(id(ID))]
607pub async fn query_or_on_composite_pk(test: &mut Test) -> Result<()> {
608    // OR where each branch fully specifies all composite primary key columns.
609    //
610    // SQL: plain OR in WHERE clause.
611    // DynamoDB: routes to GetByKey (BatchGetItem) because all key columns
612    //           have exact equality predicates, so key_values is populated.
613    #[derive(Debug, toasty::Model)]
614    #[key(partition = team, local = name)]
615    struct Player {
616        team: String,
617
618        name: String,
619
620        #[allow(dead_code)]
621        position: String,
622    }
623
624    let mut db = test.setup_db(models!(Player)).await;
625
626    for (team, name, position) in [
627        ("Timbers", "Diego Valeri", "Midfielder"),
628        ("Timbers", "Fanendo Adi", "Forward"),
629        ("Sounders", "Clint Dempsey", "Forward"),
630        ("Sounders", "Osvaldo Alonso", "Midfielder"),
631    ] {
632        Player::create()
633            .team(team)
634            .name(name)
635            .position(position)
636            .exec(&mut db)
637            .await?;
638    }
639
640    // (team = "Timbers" AND name = "Diego Valeri") OR (team = "Sounders" AND name = "Clint Dempsey")
641    let players = Player::filter(
642        Player::fields()
643            .team()
644            .eq("Timbers")
645            .and(Player::fields().name().eq("Diego Valeri"))
646            .or(Player::fields()
647                .team()
648                .eq("Sounders")
649                .and(Player::fields().name().eq("Clint Dempsey"))),
650    )
651    .exec(&mut db)
652    .await?;
653
654    assert_eq!(2, players.len());
655    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
656    names.sort();
657    assert_eq!(names, ["Clint Dempsey", "Diego Valeri"]);
658    Ok(())
659}
660
661#[driver_test(id(ID))]
662pub async fn query_or_with_comparisons(test: &mut Test) -> Result<()> {
663    #[derive(Debug, toasty::Model)]
664    #[key(partition = team, local = name)]
665    struct Player {
666        team: String,
667
668        name: String,
669
670        #[allow(dead_code)]
671        position: String,
672
673        #[allow(dead_code)]
674        number: i64,
675    }
676
677    let mut db = test.setup_db(models!(Player)).await;
678
679    // Create some players on different teams
680    for (team, name, position, number) in [
681        ("Timbers", "Diego Valeri", "Midfielder", 8),
682        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
683        ("Timbers", "Diego Chara", "Midfielder", 21),
684        ("Timbers", "Fanendo Adi", "Forward", 9),
685        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
686        ("Sounders", "Clint Dempsey", "Forward", 2),
687        ("Sounders", "Obafemi Martins", "Forward", 9),
688        ("Sounders", "Osvaldo Alonso", "Midfielder", 6),
689    ] {
690        Player::create()
691            .team(team)
692            .name(name)
693            .position(position)
694            .number(number)
695            .exec(&mut db)
696            .await?;
697    }
698
699    // Query with partition key AND OR conditions using comparisons (not equality)
700    // This won't be optimized to IN list, so tests actual OR expression handling
701    // Using gt/lt instead of ge/le to avoid boundary condition confusion
702    let players = Player::filter(
703        Player::fields().team().eq("Timbers").and(
704            Player::fields()
705                .number()
706                .gt(20)
707                .or(Player::fields().number().lt(2)),
708        ),
709    )
710    .exec(&mut db)
711    .await?;
712
713    assert_eq!(2, players.len());
714    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
715    names.sort();
716    assert_eq!(names, ["Adam Kwarasey", "Diego Chara"]);
717    Ok(())
718}
719
720#[driver_test(id(ID), requires(sql))]
721pub async fn query_arbitrary_constraint(test: &mut Test) -> Result<()> {
722    #[derive(Debug, toasty::Model)]
723    struct Event {
724        #[key]
725        #[auto]
726        id: ID,
727
728        kind: String,
729
730        timestamp: i64,
731    }
732
733    let mut db = test.setup_db(models!(Event)).await;
734
735    // Create a bunch of entries
736    for (kind, ts) in [
737        ("info", 0),
738        ("warn", 1),
739        ("info", 2),
740        ("warn", 3),
741        ("info", 4),
742        ("warn", 5),
743        ("info", 6),
744        ("warn", 7),
745        ("info", 8),
746        ("warn", 9),
747        ("info", 10),
748        ("warn", 11),
749        ("info", 12),
750        ("warn", 13),
751        ("info", 14),
752        ("warn", 15),
753        ("info", 16),
754        ("warn", 17),
755        ("info", 18),
756        ("warn", 19),
757    ] {
758        Event::create()
759            .kind(kind)
760            .timestamp(ts)
761            .exec(&mut db)
762            .await?;
763    }
764
765    let events: Vec<_> = Event::filter(Event::fields().timestamp().gt(12))
766        .exec(&mut db)
767        .await?;
768
769    assert_eq_unordered!(
770        events.iter().map(|event| event.timestamp),
771        [&13, &14, &15, &16, &17, &18, &19,]
772    );
773
774    let events: Vec<_> = Event::filter(
775        Event::fields()
776            .timestamp()
777            .gt(12)
778            .and(Event::fields().kind().ne("info")),
779    )
780    .exec(&mut db)
781    .await?;
782
783    assert!(events.iter().all(|event| event.kind != "info"));
784
785    assert_eq_unordered!(
786        events.iter().map(|event| event.timestamp),
787        [&13, &15, &17, &19,]
788    );
789
790    let events: Vec<_> = Event::filter(
791        Event::fields()
792            .kind()
793            .eq("info")
794            .and(Event::fields().timestamp().ne(10)),
795    )
796    .exec(&mut db)
797    .await?;
798
799    assert_eq_unordered!(
800        events.iter().map(|event| event.timestamp),
801        [&0, &2, &4, &6, &8, &12, &14, &16, &18,]
802    );
803
804    let events: Vec<_> = Event::filter(
805        Event::fields()
806            .kind()
807            .eq("info")
808            .and(Event::fields().timestamp().gt(10)),
809    )
810    .exec(&mut db)
811    .await?;
812
813    assert_eq_unordered!(
814        events.iter().map(|event| event.timestamp),
815        [&12, &14, &16, &18,]
816    );
817
818    let events: Vec<_> = Event::filter(
819        Event::fields()
820            .kind()
821            .eq("info")
822            .and(Event::fields().timestamp().ge(10)),
823    )
824    .exec(&mut db)
825    .await?;
826
827    assert_eq_unordered!(
828        events.iter().map(|event| event.timestamp),
829        [&10, &12, &14, &16, &18,]
830    );
831
832    let events: Vec<_> = Event::filter(
833        Event::fields()
834            .kind()
835            .eq("info")
836            .and(Event::fields().timestamp().lt(10)),
837    )
838    .exec(&mut db)
839    .await?;
840
841    assert_eq_unordered!(
842        events.iter().map(|event| event.timestamp),
843        [&0, &2, &4, &6, &8]
844    );
845
846    let events: Vec<_> = Event::filter(
847        Event::fields()
848            .kind()
849            .eq("info")
850            .and(Event::fields().timestamp().le(10)),
851    )
852    .exec(&mut db)
853    .await?;
854
855    assert_eq_unordered!(
856        events.iter().map(|event| event.timestamp),
857        [&0, &2, &4, &6, &8, &10]
858    );
859    Ok(())
860}
861
862#[driver_test(id(ID))]
863pub async fn query_not_basic(test: &mut Test) -> Result<()> {
864    #[derive(Debug, toasty::Model)]
865    struct User {
866        #[key]
867        #[auto]
868        id: ID,
869
870        name: String,
871
872        #[allow(dead_code)]
873        age: i64,
874    }
875
876    let mut db = test.setup_db(models!(User)).await;
877
878    // Create some users
879    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
880        User::create().name(name).age(age).exec(&mut db).await?;
881    }
882
883    // Clear the log after setup
884    test.log().clear();
885
886    // Query with NOT condition: NOT (name = "Alice")
887    let result = User::filter(User::fields().name().eq("Alice").not())
888        .exec(&mut db)
889        .await;
890
891    if test.capability().sql {
892        let users = result?;
893        assert_eq!(3, users.len());
894        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
895        names.sort();
896        assert_eq!(names, ["Bob", "Charlie", "Diana"]);
897    } else {
898        // DynamoDB requires key conditions for queries - NOT filters without
899        // key conditions should return an error
900        assert!(
901            result.is_err(),
902            "Expected error for NOT query without key condition on non-SQL database"
903        );
904    }
905    Ok(())
906}
907
908#[driver_test(id(ID))]
909pub async fn query_not_and_combined(test: &mut Test) -> Result<()> {
910    #[derive(Debug, toasty::Model)]
911    struct User {
912        #[key]
913        #[auto]
914        id: ID,
915
916        name: String,
917
918        #[allow(dead_code)]
919        age: i64,
920
921        #[allow(dead_code)]
922        active: bool,
923    }
924
925    let mut db = test.setup_db(models!(User)).await;
926
927    // Create some users
928    for (name, age, active) in [
929        ("Alice", 25, true),
930        ("Bob", 30, false),
931        ("Charlie", 35, true),
932        ("Diana", 40, false),
933        ("Eve", 25, false),
934    ] {
935        User::create()
936            .name(name)
937            .age(age)
938            .active(active)
939            .exec(&mut db)
940            .await?;
941    }
942
943    // Query with NOT combined with AND: active = true AND NOT (age = 25)
944    // Should return only Charlie (active=true, age=35)
945    let result = User::filter(
946        User::fields()
947            .active()
948            .eq(true)
949            .and(User::fields().age().eq(25).not()),
950    )
951    .exec(&mut db)
952    .await;
953
954    if test.capability().sql {
955        let users = result?;
956        assert_eq!(1, users.len());
957        assert_eq!("Charlie", users[0].name);
958    } else {
959        assert!(
960            result.is_err(),
961            "Expected error for NOT query without key condition on non-SQL database"
962        );
963    }
964    Ok(())
965}
966
967#[driver_test(id(ID))]
968pub async fn query_not_or_combined(test: &mut Test) -> Result<()> {
969    #[derive(Debug, toasty::Model)]
970    struct User {
971        #[key]
972        #[auto]
973        id: ID,
974
975        name: String,
976
977        #[allow(dead_code)]
978        age: i64,
979    }
980
981    let mut db = test.setup_db(models!(User)).await;
982
983    // Create some users
984    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
985        User::create().name(name).age(age).exec(&mut db).await?;
986    }
987
988    // Query with NOT combined with OR: NOT (name = "Alice" OR name = "Bob")
989    // Should return Charlie and Diana
990    let result = User::filter(
991        User::fields()
992            .name()
993            .eq("Alice")
994            .or(User::fields().name().eq("Bob"))
995            .not(),
996    )
997    .exec(&mut db)
998    .await;
999
1000    if test.capability().sql {
1001        let users = result?;
1002        assert_eq!(2, users.len());
1003        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
1004        names.sort();
1005        assert_eq!(names, ["Charlie", "Diana"]);
1006    } else {
1007        assert!(
1008            result.is_err(),
1009            "Expected error for NOT query without key condition on non-SQL database"
1010        );
1011    }
1012    Ok(())
1013}
1014
1015#[driver_test(id(ID))]
1016pub async fn query_not_with_index(test: &mut Test) -> Result<()> {
1017    #[derive(Debug, toasty::Model)]
1018    #[key(partition = team, local = name)]
1019    struct Player {
1020        team: String,
1021
1022        name: String,
1023
1024        #[allow(dead_code)]
1025        position: String,
1026
1027        #[allow(dead_code)]
1028        number: i64,
1029    }
1030
1031    let mut db = test.setup_db(models!(Player)).await;
1032
1033    // Create some players
1034    for (team, name, position, number) in [
1035        ("Timbers", "Diego Valeri", "Midfielder", 8),
1036        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
1037        ("Timbers", "Diego Chara", "Midfielder", 21),
1038        ("Timbers", "Fanendo Adi", "Forward", 9),
1039        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
1040        ("Sounders", "Clint Dempsey", "Forward", 2),
1041        ("Sounders", "Obafemi Martins", "Forward", 9),
1042        ("Sounders", "Osvaldo Alonso", "Midfielder", 6),
1043    ] {
1044        Player::create()
1045            .team(team)
1046            .name(name)
1047            .position(position)
1048            .number(number)
1049            .exec(&mut db)
1050            .await?;
1051    }
1052
1053    // Query with partition key AND NOT condition on non-indexed field
1054    // team = "Timbers" AND NOT (position = "Midfielder")
1055    // Should return Fanendo Adi (Forward) and Adam Kwarasey (Goalkeeper)
1056    let players = Player::filter(
1057        Player::fields()
1058            .team()
1059            .eq("Timbers")
1060            .and(Player::fields().position().eq("Midfielder").not()),
1061    )
1062    .exec(&mut db)
1063    .await?;
1064
1065    assert_eq!(2, players.len());
1066    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1067    names.sort();
1068    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
1069
1070    // Query with partition key AND NOT with comparison
1071    // team = "Timbers" AND NOT (number > 8)
1072    // Should return players with number <= 8: Diego Valeri (8), Darlington Nagbe (6), Adam Kwarasey (1)
1073    let players = Player::filter(
1074        Player::fields()
1075            .team()
1076            .eq("Timbers")
1077            .and(Player::fields().number().gt(8).not()),
1078    )
1079    .exec(&mut db)
1080    .await?;
1081
1082    assert_eq!(3, players.len());
1083    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1084    names.sort();
1085    assert_eq!(names, ["Adam Kwarasey", "Darlington Nagbe", "Diego Valeri"]);
1086    Ok(())
1087}
1088
1089#[driver_test(id(ID))]
1090pub async fn query_not_operator_syntax(test: &mut Test) -> Result<()> {
1091    #[derive(Debug, toasty::Model)]
1092    #[key(partition = team, local = name)]
1093    struct Player {
1094        team: String,
1095
1096        name: String,
1097
1098        #[allow(dead_code)]
1099        position: String,
1100
1101        #[allow(dead_code)]
1102        number: i64,
1103    }
1104
1105    let mut db = test.setup_db(models!(Player)).await;
1106
1107    for (team, name, position, number) in [
1108        ("Timbers", "Diego Valeri", "Midfielder", 8),
1109        ("Timbers", "Darlington Nagbe", "Midfielder", 6),
1110        ("Timbers", "Diego Chara", "Midfielder", 21),
1111        ("Timbers", "Fanendo Adi", "Forward", 9),
1112        ("Timbers", "Adam Kwarasey", "Goalkeeper", 1),
1113    ] {
1114        Player::create()
1115            .team(team)
1116            .name(name)
1117            .position(position)
1118            .number(number)
1119            .exec(&mut db)
1120            .await?;
1121    }
1122
1123    // Use the ! operator instead of .not()
1124    // team = "Timbers" AND !(position = "Midfielder")
1125    let players = Player::filter(
1126        Player::fields()
1127            .team()
1128            .eq("Timbers")
1129            .and(!Player::fields().position().eq("Midfielder")),
1130    )
1131    .exec(&mut db)
1132    .await?;
1133
1134    assert_eq!(2, players.len());
1135    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1136    names.sort();
1137    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
1138
1139    // ! on a compound expression: !(number > 8 OR position = "Goalkeeper")
1140    let players = Player::filter(
1141        Player::fields().team().eq("Timbers").and(
1142            !(Player::fields()
1143                .number()
1144                .gt(8)
1145                .or(Player::fields().position().eq("Goalkeeper"))),
1146        ),
1147    )
1148    .exec(&mut db)
1149    .await?;
1150
1151    // Excludes Diego Chara (21), Fanendo Adi (9), Adam Kwarasey (Goalkeeper)
1152    // Keeps Diego Valeri (8, Midfielder), Darlington Nagbe (6, Midfielder)
1153    assert_eq!(2, players.len());
1154    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
1155    names.sort();
1156    assert_eq!(names, ["Darlington Nagbe", "Diego Valeri"]);
1157    Ok(())
1158}