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