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(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]
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]
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(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(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(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(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]
424pub async fn query_boolean_filters_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
497    // OR conditions using comparisons are not optimized to an IN list.
498    let players = Player::filter(
499        Player::fields().team().eq("Timbers").and(
500            Player::fields()
501                .number()
502                .gt(20)
503                .or(Player::fields().number().lt(2)),
504        ),
505    )
506    .exec(&mut db)
507    .await?;
508    assert_eq!(2, players.len());
509    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
510    names.sort();
511    assert_eq!(names, ["Adam Kwarasey", "Diego Chara"]);
512
513    // NOT on a non-indexed field.
514    let players = Player::filter(
515        Player::fields()
516            .team()
517            .eq("Timbers")
518            .and(Player::fields().position().eq("Midfielder").not()),
519    )
520    .exec(&mut db)
521    .await?;
522    assert_eq!(2, players.len());
523    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
524    names.sort();
525    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
526
527    // NOT wrapping a comparison.
528    let players = Player::filter(
529        Player::fields()
530            .team()
531            .eq("Timbers")
532            .and(Player::fields().number().gt(8).not()),
533    )
534    .exec(&mut db)
535    .await?;
536    assert_eq!(3, players.len());
537    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
538    names.sort();
539    assert_eq!(names, ["Adam Kwarasey", "Darlington Nagbe", "Diego Valeri"]);
540
541    // The `!` operator is equivalent to `.not()`.
542    let players = Player::filter(
543        Player::fields()
544            .team()
545            .eq("Timbers")
546            .and(!Player::fields().position().eq("Midfielder")),
547    )
548    .exec(&mut db)
549    .await?;
550    assert_eq!(2, players.len());
551    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
552    names.sort();
553    assert_eq!(names, ["Adam Kwarasey", "Fanendo Adi"]);
554
555    // `!` on a compound expression.
556    let players = Player::filter(
557        Player::fields().team().eq("Timbers").and(
558            !(Player::fields()
559                .number()
560                .gt(8)
561                .or(Player::fields().position().eq("Goalkeeper"))),
562        ),
563    )
564    .exec(&mut db)
565    .await?;
566    assert_eq!(2, players.len());
567    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
568    names.sort();
569    assert_eq!(names, ["Darlington Nagbe", "Diego Valeri"]);
570    Ok(())
571}
572
573#[driver_test]
574pub async fn query_or_on_keys(test: &mut Test) -> Result<()> {
575    // OR directly on the partition key of a composite primary key.
576    //
577    // SQL: plain OR in WHERE clause.
578    // DynamoDB: fan-out into two QueryPk calls, one per partition key value,
579    //           because KeyConditionExpression does not support OR.
580    #[derive(Debug, toasty::Model)]
581    #[key(partition = team, local = name)]
582    struct Player {
583        team: String,
584
585        name: String,
586
587        #[allow(dead_code)]
588        position: String,
589    }
590
591    let mut db = test.setup_db(models!(Player)).await;
592
593    for (team, name, position) in [
594        ("Timbers", "Diego Valeri", "Midfielder"),
595        ("Timbers", "Fanendo Adi", "Forward"),
596        ("Sounders", "Clint Dempsey", "Forward"),
597        ("Sounders", "Osvaldo Alonso", "Midfielder"),
598        ("Galaxy", "Robbie Keane", "Forward"),
599    ] {
600        Player::create()
601            .team(team)
602            .name(name)
603            .position(position)
604            .exec(&mut db)
605            .await?;
606    }
607
608    // team = "Timbers" OR team = "Sounders"
609    let players = Player::filter(
610        Player::fields()
611            .team()
612            .eq("Timbers")
613            .or(Player::fields().team().eq("Sounders")),
614    )
615    .exec(&mut db)
616    .await?;
617
618    assert_eq!(4, players.len());
619    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
620    names.sort();
621    assert_eq!(
622        names,
623        [
624            "Clint Dempsey",
625            "Diego Valeri",
626            "Fanendo Adi",
627            "Osvaldo Alonso"
628        ]
629    );
630
631    // Each OR branch specifies a full composite key. DynamoDB can therefore
632    // route this through GetByKey instead of partition-query fan-out.
633    let players = Player::filter(
634        Player::fields()
635            .team()
636            .eq("Timbers")
637            .and(Player::fields().name().eq("Diego Valeri"))
638            .or(Player::fields()
639                .team()
640                .eq("Sounders")
641                .and(Player::fields().name().eq("Clint Dempsey"))),
642    )
643    .exec(&mut db)
644    .await?;
645
646    assert_eq!(2, players.len());
647    let mut names: Vec<_> = players.iter().map(|p| p.name.as_str()).collect();
648    names.sort();
649    assert_eq!(names, ["Clint Dempsey", "Diego Valeri"]);
650    Ok(())
651}
652
653#[driver_test(requires(scan), scenario(crate::scenarios::user_with_active))]
654pub async fn query_arbitrary_constraint(test: &mut Test) -> Result<()> {
655    let mut db = setup(test).await;
656
657    // Create a bunch of entries. kind -> name, timestamp -> age; active is unused.
658    for (kind, ts) in [
659        ("info", 0),
660        ("warn", 1),
661        ("info", 2),
662        ("warn", 3),
663        ("info", 4),
664        ("warn", 5),
665        ("info", 6),
666        ("warn", 7),
667        ("info", 8),
668        ("warn", 9),
669        ("info", 10),
670        ("warn", 11),
671        ("info", 12),
672        ("warn", 13),
673        ("info", 14),
674        ("warn", 15),
675        ("info", 16),
676        ("warn", 17),
677        ("info", 18),
678        ("warn", 19),
679    ] {
680        User::create()
681            .name(kind)
682            .age(ts)
683            .active(true)
684            .exec(&mut db)
685            .await?;
686    }
687
688    let events: Vec<_> = User::filter(User::fields().age().gt(12))
689        .exec(&mut db)
690        .await?;
691
692    assert_eq_unordered!(
693        events.iter().map(|event| event.age),
694        [&13, &14, &15, &16, &17, &18, &19,]
695    );
696
697    let events: Vec<_> = User::filter(
698        User::fields()
699            .age()
700            .gt(12)
701            .and(User::fields().name().ne("info")),
702    )
703    .exec(&mut db)
704    .await?;
705
706    assert!(events.iter().all(|event| event.name != "info"));
707
708    assert_eq_unordered!(events.iter().map(|event| event.age), [&13, &15, &17, &19,]);
709
710    let events: Vec<_> = User::filter(
711        User::fields()
712            .name()
713            .eq("info")
714            .and(User::fields().age().ne(10)),
715    )
716    .exec(&mut db)
717    .await?;
718
719    assert_eq_unordered!(
720        events.iter().map(|event| event.age),
721        [&0, &2, &4, &6, &8, &12, &14, &16, &18,]
722    );
723
724    let events: Vec<_> = User::filter(
725        User::fields()
726            .name()
727            .eq("info")
728            .and(User::fields().age().gt(10)),
729    )
730    .exec(&mut db)
731    .await?;
732
733    assert_eq_unordered!(events.iter().map(|event| event.age), [&12, &14, &16, &18,]);
734
735    let events: Vec<_> = User::filter(
736        User::fields()
737            .name()
738            .eq("info")
739            .and(User::fields().age().ge(10)),
740    )
741    .exec(&mut db)
742    .await?;
743
744    assert_eq_unordered!(
745        events.iter().map(|event| event.age),
746        [&10, &12, &14, &16, &18,]
747    );
748
749    let events: Vec<_> = User::filter(
750        User::fields()
751            .name()
752            .eq("info")
753            .and(User::fields().age().lt(10)),
754    )
755    .exec(&mut db)
756    .await?;
757
758    assert_eq_unordered!(events.iter().map(|event| event.age), [&0, &2, &4, &6, &8]);
759
760    let events: Vec<_> = User::filter(
761        User::fields()
762            .name()
763            .eq("info")
764            .and(User::fields().age().le(10)),
765    )
766    .exec(&mut db)
767    .await?;
768
769    assert_eq_unordered!(
770        events.iter().map(|event| event.age),
771        [&0, &2, &4, &6, &8, &10]
772    );
773    Ok(())
774}
775
776#[driver_test(scenario(crate::scenarios::user_with_age))]
777pub async fn query_not_basic(test: &mut Test) -> Result<()> {
778    let mut db = setup(test).await;
779
780    // Create some users
781    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
782        User::create().name(name).age(age).exec(&mut db).await?;
783    }
784
785    // Clear the log after setup
786    test.log().clear();
787
788    // Query with NOT condition: NOT (name = "Alice")
789    let result = User::filter(User::fields().name().eq("Alice").not())
790        .exec(&mut db)
791        .await;
792
793    if test.capability().sql() || test.capability().scan {
794        let users = result?;
795        assert_eq!(3, users.len());
796        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
797        names.sort();
798        assert_eq!(names, ["Bob", "Charlie", "Diana"]);
799    } else {
800        assert!(
801            result.is_err(),
802            "Expected error for NOT query without key condition on non-SQL database"
803        );
804    }
805    Ok(())
806}
807
808#[driver_test(scenario(crate::scenarios::user_with_active))]
809pub async fn query_not_and_combined(test: &mut Test) -> Result<()> {
810    let mut db = setup(test).await;
811
812    // Create some users
813    for (name, age, active) in [
814        ("Alice", 25, true),
815        ("Bob", 30, false),
816        ("Charlie", 35, true),
817        ("Diana", 40, false),
818        ("Eve", 25, false),
819    ] {
820        User::create()
821            .name(name)
822            .age(age)
823            .active(active)
824            .exec(&mut db)
825            .await?;
826    }
827
828    // Query with NOT combined with AND: active = true AND NOT (age = 25)
829    // Should return only Charlie (active=true, age=35)
830    let result = User::filter(
831        User::fields()
832            .active()
833            .eq(true)
834            .and(User::fields().age().eq(25).not()),
835    )
836    .exec(&mut db)
837    .await;
838
839    if test.capability().sql() || test.capability().scan {
840        let users = result?;
841        assert_eq!(1, users.len());
842        assert_eq!("Charlie", users[0].name);
843    } else {
844        assert!(
845            result.is_err(),
846            "Expected error for NOT query without key condition on non-SQL database"
847        );
848    }
849    Ok(())
850}
851
852#[driver_test(scenario(crate::scenarios::user_with_age))]
853pub async fn query_not_or_combined(test: &mut Test) -> Result<()> {
854    let mut db = setup(test).await;
855
856    // Create some users
857    for (name, age) in [("Alice", 25), ("Bob", 30), ("Charlie", 35), ("Diana", 40)] {
858        User::create().name(name).age(age).exec(&mut db).await?;
859    }
860
861    // Query with NOT combined with OR: NOT (name = "Alice" OR name = "Bob")
862    // Should return Charlie and Diana
863    let result = User::filter(
864        User::fields()
865            .name()
866            .eq("Alice")
867            .or(User::fields().name().eq("Bob"))
868            .not(),
869    )
870    .exec(&mut db)
871    .await;
872
873    if test.capability().sql() || test.capability().scan {
874        let users = result?;
875        assert_eq!(2, users.len());
876        let mut names: Vec<_> = users.iter().map(|u| u.name.as_str()).collect();
877        names.sort();
878        assert_eq!(names, ["Charlie", "Diana"]);
879    } else {
880        assert!(
881            result.is_err(),
882            "Expected error for NOT query without key condition on non-SQL database"
883        );
884    }
885    Ok(())
886}