Skip to main content

toasty_driver_integration_suite/tests/
scan_no_index.rs

1use crate::prelude::*;
2use toasty::stmt::Page;
3
4/// Scan with no filter predicate returns all rows.
5#[driver_test(id(ID))]
6pub async fn scan_no_filter(t: &mut Test) -> Result<()> {
7    #[derive(Debug, toasty::Model)]
8    struct Item {
9        #[key]
10        #[auto]
11        id: ID,
12        name: String,
13    }
14
15    let mut db = t.setup_db(models!(Item)).await;
16
17    toasty::create!(Item::[
18        { name: "Alice" },
19        { name: "Bob" },
20        { name: "Charlie" },
21    ])
22    .exec(&mut db)
23    .await?;
24
25    let mut results: Vec<Item> = Item::all().exec(&mut db).await?;
26    results.sort_by(|a, b| a.name.cmp(&b.name));
27
28    assert_eq!(3, results.len());
29    assert_eq!("Alice", results[0].name);
30    assert_eq!("Bob", results[1].name);
31    assert_eq!("Charlie", results[2].name);
32
33    Ok(())
34}
35
36/// Scan with an OR filter on non-indexed fields.
37#[driver_test(id(ID))]
38pub async fn scan_or_filter(t: &mut Test) -> Result<()> {
39    #[derive(Debug, toasty::Model)]
40    struct Item {
41        #[key]
42        #[auto]
43        id: ID,
44        name: String,
45    }
46
47    let mut db = t.setup_db(models!(Item)).await;
48
49    toasty::create!(Item::[
50        { name: "Alice" },
51        { name: "Bob" },
52        { name: "Charlie" },
53    ])
54    .exec(&mut db)
55    .await?;
56
57    let mut results: Vec<Item> = Item::filter(
58        Item::fields()
59            .name()
60            .eq("Alice")
61            .or(Item::fields().name().eq("Charlie")),
62    )
63    .exec(&mut db)
64    .await?;
65    results.sort_by(|a, b| a.name.cmp(&b.name));
66
67    assert_eq!(2, results.len());
68    assert_eq!("Alice", results[0].name);
69    assert_eq!("Charlie", results[1].name);
70
71    Ok(())
72}
73
74/// Scan respects a limit — at most `limit` rows are returned.
75#[driver_test(id(ID))]
76pub async fn scan_with_limit(t: &mut Test) -> Result<()> {
77    #[derive(Debug, toasty::Model)]
78    struct Item {
79        #[key]
80        #[auto]
81        id: ID,
82        name: String,
83    }
84
85    let mut db = t.setup_db(models!(Item)).await;
86
87    toasty::create!(Item::[
88        { name: "Alice" },
89        { name: "Bob" },
90        { name: "Charlie" },
91        { name: "Dave" },
92        { name: "Eve" },
93    ])
94    .exec(&mut db)
95    .await?;
96
97    let results: Vec<Item> = Item::all().limit(3).exec(&mut db).await?;
98
99    assert!(
100        results.len() <= 3,
101        "expected at most 3 results, got {}",
102        results.len()
103    );
104
105    Ok(())
106}
107
108/// Scan with a filter AND a limit returns exactly `limit` matching rows even
109/// when the table contains more non-matching rows than `limit`. This exercises
110/// the loop-with-ExclusiveStartKey path in the DynamoDB driver.
111#[driver_test(id(ID), requires(not(sql)))]
112pub async fn scan_limit_with_filter_returns_correct_count(t: &mut Test) -> Result<()> {
113    #[derive(Debug, toasty::Model)]
114    struct Item {
115        #[key]
116        #[auto]
117        id: ID,
118        category: String,
119    }
120
121    let mut db = t.setup_db(models!(Item)).await;
122
123    // Insert 20 rows: 10 "match" and 10 "other". With limit(5) and DynamoDB's
124    // pre-filter Limit semantics, a naive single-call implementation would
125    // frequently return fewer than 5 rows when the examined items are mostly
126    // "other".
127    for _i in 0..10_i64 {
128        toasty::create!(Item { category: "match" })
129            .exec(&mut db)
130            .await?;
131        toasty::create!(Item { category: "other" })
132            .exec(&mut db)
133            .await?;
134    }
135
136    let results: Vec<Item> = Item::filter(Item::fields().category().eq("match"))
137        .limit(5)
138        .exec(&mut db)
139        .await?;
140
141    assert_eq!(
142        5,
143        results.len(),
144        "expected exactly 5 matching rows, got {}",
145        results.len()
146    );
147    assert!(
148        results.iter().all(|r| r.category == "match"),
149        "all returned rows should have category 'match'"
150    );
151
152    Ok(())
153}
154
155/// Cursor-based pagination over a full-table scan returns all rows across
156/// multiple pages with no duplicates.
157#[driver_test(id(ID), requires(not(sql)))]
158pub async fn scan_paginate_multi_page(t: &mut Test) -> Result<()> {
159    #[derive(Debug, toasty::Model)]
160    struct Item {
161        #[key]
162        #[auto]
163        id: ID,
164        score: i64,
165    }
166
167    let mut db = t.setup_db(models!(Item)).await;
168
169    for score in 1_i64..=15 {
170        toasty::create!(Item { score }).exec(&mut db).await?;
171    }
172
173    // Paginate with page_size=5 — should yield 3 pages of 5 rows each.
174    let mut page: Page<Item> = Item::all().paginate(5).exec(&mut db).await?;
175
176    let mut all_items: Vec<Item> = std::mem::take(&mut page.items);
177    while let Some(mut next) = page.next(&mut db).await? {
178        all_items.append(&mut next.items);
179        page = next;
180    }
181
182    assert_eq!(
183        15,
184        all_items.len(),
185        "expected 15 total rows across all pages"
186    );
187
188    // No duplicate IDs.
189    let mut scores: Vec<i64> = all_items.iter().map(|r| r.score).collect();
190    scores.sort_unstable();
191    scores.dedup();
192    assert_eq!(15, scores.len(), "expected 15 unique scores");
193    assert_eq!((1..=15).collect::<Vec<_>>(), scores);
194
195    Ok(())
196}
197
198/// ORDER BY on a scan-path query is an error on DynamoDB — the Scan API
199/// returns items in an unspecified order so sorted results cannot be
200/// guaranteed.
201#[driver_test(id(ID), requires(not(sql)))]
202pub async fn scan_order_by_is_error(t: &mut Test) -> Result<()> {
203    #[derive(Debug, toasty::Model)]
204    struct Item {
205        #[key]
206        #[auto]
207        id: ID,
208        score: i64,
209    }
210
211    let mut db = t.setup_db(models!(Item)).await;
212
213    toasty::create!(Item::[
214        { score: 10_i64 },
215        { score: 30_i64 },
216        { score: 20_i64 },
217        { score: 50_i64 },
218        { score: 40_i64 },
219    ])
220    .exec(&mut db)
221    .await?;
222
223    let result: toasty::Result<Vec<Item>> = Item::all()
224        .order_by(Item::fields().score().desc())
225        .exec(&mut db)
226        .await;
227
228    assert!(
229        result.is_err(),
230        "expected error when using ORDER BY on a scan-path query on DynamoDB"
231    );
232
233    Ok(())
234}
235
236/// ORDER BY on a full-table scan works on SQL drivers — the database sorts
237/// natively via ORDER BY in the SQL query.
238#[driver_test(id(ID), requires(sql))]
239pub async fn scan_order_by_sql(t: &mut Test) -> Result<()> {
240    #[derive(Debug, toasty::Model)]
241    struct Item {
242        #[key]
243        #[auto]
244        id: ID,
245        score: i64,
246    }
247
248    let mut db = t.setup_db(models!(Item)).await;
249
250    toasty::create!(Item::[
251        { score: 10_i64 },
252        { score: 30_i64 },
253        { score: 20_i64 },
254        { score: 50_i64 },
255        { score: 40_i64 },
256    ])
257    .exec(&mut db)
258    .await?;
259
260    let results: Vec<Item> = Item::all()
261        .order_by(Item::fields().score().desc())
262        .exec(&mut db)
263        .await?;
264
265    assert_eq!(5, results.len());
266    for i in 0..4 {
267        assert!(
268            results[i].score >= results[i + 1].score,
269            "expected descending order: results[{}].score={} >= results[{}].score={}",
270            i,
271            results[i].score,
272            i + 1,
273            results[i + 1].score
274        );
275    }
276
277    Ok(())
278}