Skip to main content

toasty_driver_integration_suite/tests/
crud_composite_key_pagination.rs

1//! Test pagination on composite-key models.
2//!
3//! These tests exercise `paginate()`, `limit()`, and `order_by()` on models
4//! with a partition + local key, which is the pattern DynamoDB uses for
5//! `QueryPk`. They intentionally have **no** `requires(sql)` gate so they run
6//! on every driver, including DynamoDB.
7
8use crate::prelude::*;
9use toasty::stmt::Page;
10use toasty_core::driver::{Operation, Rows};
11
12#[driver_test(requires(sql))]
13pub async fn paginate_composite_key(test: &mut Test) -> Result<()> {
14    #[derive(Debug, toasty::Model)]
15    #[key(partition = kind, local = seq)]
16    struct Event {
17        kind: String,
18        seq: i64,
19    }
20
21    let mut db = test.setup_db(models!(Event)).await;
22
23    // Seed 20 events under the same partition key so we can paginate over them.
24    for i in 0..20 {
25        Event::create().kind("info").seq(i).exec(&mut db).await?;
26    }
27
28    test.log().clear();
29
30    // First page (descending): should return seq 19..10
31    let page: Page<_> = Event::filter_by_kind("info")
32        .order_by(Event::fields().seq().desc())
33        .paginate(10)
34        .exec(&mut db)
35        .await?;
36
37    assert_eq!(page.len(), 10);
38    for (i, expected) in (10..20).rev().enumerate() {
39        assert_eq!(page[i].seq, expected);
40    }
41
42    // Verify the driver operation type
43    let (op, resp) = test.log().pop();
44    if test.capability().sql {
45        assert_struct!(op, Operation::QuerySql(_));
46    } else {
47        assert_struct!(op, Operation::QueryPk(_));
48    }
49    assert_struct!(resp.values, Rows::Stream(_));
50
51    // Second page via .next()
52    let page: Page<_> = page.next(&mut db).await?.unwrap();
53    assert_eq!(page.len(), 10);
54    for (i, expected) in (0..10).rev().enumerate() {
55        assert_eq!(page[i].seq, expected);
56    }
57
58    // Go back to the first page via .prev()
59    let page: Page<_> = page.prev(&mut db).await?.unwrap();
60    assert_eq!(page.len(), 10);
61    for (i, expected) in (10..20).rev().enumerate() {
62        assert_eq!(page[i].seq, expected);
63    }
64
65    Ok(())
66}
67
68#[driver_test]
69pub async fn paginate_composite_key_asc(test: &mut Test) -> Result<()> {
70    #[derive(Debug, toasty::Model)]
71    #[key(partition = kind, local = seq)]
72    struct Event {
73        kind: String,
74        seq: i64,
75    }
76
77    let mut db = test.setup_db(models!(Event)).await;
78
79    for i in 0..20 {
80        Event::create().kind("info").seq(i).exec(&mut db).await?;
81    }
82
83    test.log().clear();
84
85    // First page (ascending): should return seq 0..9
86    let page: Page<_> = Event::filter_by_kind("info")
87        .order_by(Event::fields().seq().asc())
88        .paginate(5)
89        .exec(&mut db)
90        .await?;
91
92    assert_eq!(page.len(), 5);
93    for (i, expected) in (0..5).enumerate() {
94        assert_eq!(page[i].seq, expected);
95    }
96
97    let (op, _) = test.log().pop();
98    if test.capability().sql {
99        assert_struct!(op, Operation::QuerySql(_));
100    } else {
101        assert_struct!(op, Operation::QueryPk(_));
102    }
103
104    // Walk forward through all pages and collect every seq value.
105    let mut all_seqs: Vec<i64> = page.iter().map(|e| e.seq).collect();
106    let mut current = page;
107    while let Some(next) = current.next(&mut db).await? {
108        all_seqs.extend(next.iter().map(|e| e.seq));
109        current = next;
110    }
111
112    assert_eq!(all_seqs, (0..20).collect::<Vec<_>>());
113
114    Ok(())
115}
116
117#[driver_test]
118pub async fn limit_offset_composite_key(test: &mut Test) -> Result<()> {
119    #[derive(Debug, toasty::Model)]
120    #[key(partition = kind, local = seq)]
121    struct Event {
122        kind: String,
123        seq: i64,
124    }
125
126    let mut db = test.setup_db(models!(Event)).await;
127
128    for i in 0..20 {
129        Event::create().kind("info").seq(i).exec(&mut db).await?;
130    }
131
132    let events: Vec<_> = Event::filter_by_kind("info").limit(5).exec(&mut db).await?;
133    assert_eq!(events.len(), 5);
134
135    test.log().clear();
136
137    let events: Vec<_> = Event::filter_by_kind("info")
138        .order_by(Event::fields().seq().asc())
139        .limit(7)
140        .offset(5)
141        .exec(&mut db)
142        .await?;
143    assert_eq!(events.len(), 7);
144    assert_eq!(events[0].seq, 5);
145    assert_eq!(events[6].seq, 11);
146
147    // A `.limit().offset()` should route through the same driver operation as a
148    // plain `.limit()` — a future refactor accidentally producing a different
149    // operation type for the offset path would be silent without this assert.
150    let (op, _) = test.log().pop();
151    if test.capability().sql {
152        assert_struct!(op, Operation::QuerySql(_));
153    } else {
154        assert_struct!(op, Operation::QueryPk(_));
155    }
156
157    // offset = 0 should behave identically to a plain limit.
158    let events: Vec<_> = Event::filter_by_kind("info")
159        .order_by(Event::fields().seq().asc())
160        .limit(5)
161        .offset(0)
162        .exec(&mut db)
163        .await?;
164    assert_eq!(events.len(), 5);
165    assert_eq!(events[0].seq, 0);
166    assert_eq!(events[4].seq, 4);
167
168    // offset past the end of the result set returns empty, not an error.
169    let events: Vec<_> = Event::filter_by_kind("info")
170        .limit(5)
171        .offset(100)
172        .exec(&mut db)
173        .await?;
174    assert!(events.is_empty());
175
176    let events: Vec<_> = Event::filter_by_kind("info")
177        .limit(100)
178        .exec(&mut db)
179        .await?;
180    assert_eq!(events.len(), 20);
181
182    Ok(())
183}
184
185#[driver_test]
186pub async fn limit_offset_gsi(test: &mut Test) -> Result<()> {
187    #[derive(Debug, toasty::Model)]
188    struct Item {
189        #[key]
190        #[auto]
191        id: uuid::Uuid,
192
193        #[index]
194        category: String,
195
196        value: i64,
197    }
198
199    let mut db = test.setup_db(models!(Item)).await;
200
201    for i in 0..20 {
202        Item::create().category("x").value(i).exec(&mut db).await?;
203    }
204
205    let items: Vec<_> = Item::filter_by_category("x").limit(5).exec(&mut db).await?;
206    assert_eq!(items.len(), 5);
207
208    // Ordering by a non-sort-key field is not guaranteed on DDB GSIs; assert count only.
209    let items: Vec<_> = Item::filter_by_category("x")
210        .limit(7)
211        .offset(3)
212        .exec(&mut db)
213        .await?;
214    assert_eq!(items.len(), 7);
215
216    let items: Vec<_> = Item::filter_by_category("x")
217        .limit(100)
218        .exec(&mut db)
219        .await?;
220    assert_eq!(items.len(), 20);
221
222    Ok(())
223}
224
225#[driver_test]
226pub async fn limit_composite_key(test: &mut Test) -> Result<()> {
227    #[derive(Debug, toasty::Model)]
228    #[key(partition = kind, local = seq)]
229    struct Event {
230        kind: String,
231        seq: i64,
232    }
233
234    let mut db = test.setup_db(models!(Event)).await;
235
236    for i in 0..20 {
237        Event::create().kind("info").seq(i).exec(&mut db).await?;
238    }
239
240    test.log().clear();
241
242    // Limit without explicit ordering
243    let events: Vec<_> = Event::filter_by_kind("info").limit(7).exec(&mut db).await?;
244    assert_eq!(events.len(), 7);
245
246    let (op, _) = test.log().pop();
247    if test.capability().sql {
248        assert_struct!(op, Operation::QuerySql(_));
249    } else {
250        assert_struct!(op, Operation::QueryPk(_));
251    }
252
253    test.log().clear();
254
255    // Limit combined with descending order
256    let events: Vec<_> = Event::filter_by_kind("info")
257        .order_by(Event::fields().seq().desc())
258        .limit(5)
259        .exec(&mut db)
260        .await?;
261    assert_eq!(events.len(), 5);
262    for i in 0..4 {
263        assert!(events[i].seq > events[i + 1].seq);
264    }
265    // The first item should be the highest seq
266    assert_eq!(events[0].seq, 19);
267
268    test.log().clear();
269
270    // Limit larger than result set returns all results
271    let events: Vec<_> = Event::filter_by_kind("info")
272        .limit(100)
273        .exec(&mut db)
274        .await?;
275    assert_eq!(events.len(), 20);
276
277    Ok(())
278}
279
280#[driver_test]
281pub async fn sort_composite_key(test: &mut Test) -> Result<()> {
282    #[derive(Debug, toasty::Model)]
283    #[key(partition = kind, local = seq)]
284    struct Event {
285        kind: String,
286        seq: i64,
287    }
288
289    let mut db = test.setup_db(models!(Event)).await;
290
291    for i in 0..20 {
292        Event::create().kind("info").seq(i).exec(&mut db).await?;
293    }
294
295    test.log().clear();
296
297    // Ascending sort
298    let events: Vec<_> = Event::filter_by_kind("info")
299        .order_by(Event::fields().seq().asc())
300        .exec(&mut db)
301        .await?;
302
303    assert_eq!(events.len(), 20);
304    for i in 0..19 {
305        assert!(events[i].seq < events[i + 1].seq);
306    }
307
308    let (op, resp) = test.log().pop();
309    if test.capability().sql {
310        assert_struct!(op, Operation::QuerySql(_));
311    } else {
312        assert_struct!(op, Operation::QueryPk(_));
313    }
314    assert_struct!(resp.values, Rows::Stream(_));
315
316    test.log().clear();
317
318    // Descending sort
319    let events: Vec<_> = Event::filter_by_kind("info")
320        .order_by(Event::fields().seq().desc())
321        .exec(&mut db)
322        .await?;
323
324    assert_eq!(events.len(), 20);
325    for i in 0..19 {
326        assert!(events[i].seq > events[i + 1].seq);
327    }
328
329    let (op, resp) = test.log().pop();
330    if test.capability().sql {
331        assert_struct!(op, Operation::QuerySql(_));
332    } else {
333        assert_struct!(op, Operation::QueryPk(_));
334    }
335    assert_struct!(resp.values, Rows::Stream(_));
336
337    Ok(())
338}