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}
339
340#[driver_test(requires(backward_pagination))]
341pub async fn paginate_composite_key_prev(test: &mut Test) -> Result<()> {
342    #[derive(Debug, toasty::Model)]
343    #[key(partition = kind, local = seq)]
344    struct Event {
345        kind: String,
346        seq: i64,
347    }
348
349    let mut db = test.setup_db(models!(Event)).await;
350
351    for i in 0..20 {
352        Event::create().kind("info").seq(i).exec(&mut db).await?;
353    }
354
355    test.log().clear();
356
357    // Retrieve two pages
358    let page1: Page<_> = Event::filter_by_kind("info")
359        .order_by(Event::fields().seq().asc())
360        .paginate(10)
361        .exec(&mut db)
362        .await?;
363    let page2: Page<_> = page1.next(&mut db).await?.unwrap();
364
365    // Check that it is possible to access the previous page
366    assert!(page2.has_prev());
367    let prev: Option<Page<_>> = page2.prev(&mut db).await?;
368    assert!(prev.is_some());
369
370    // Check the number and order of the items in the previous page
371    let prev_page = prev.unwrap();
372    assert_eq!(prev_page.len(), 10);
373    for i in 0..10 {
374        assert_eq!(prev_page[i].seq, i as i64);
375    }
376
377    Ok(())
378}
379
380#[driver_test(requires(backward_pagination))]
381pub async fn paginate_composite_key_prev_desc(test: &mut Test) -> Result<()> {
382    #[derive(Debug, toasty::Model)]
383    #[key(partition = kind, local = seq)]
384    struct Event {
385        kind: String,
386        seq: i64,
387    }
388
389    let mut db = test.setup_db(models!(Event)).await;
390
391    for i in 0..20 {
392        Event::create().kind("info").seq(i).exec(&mut db).await?;
393    }
394
395    test.log().clear();
396
397    // Retrieve two pages in descending order
398    let page1: Page<_> = Event::filter_by_kind("info")
399        .order_by(Event::fields().seq().desc())
400        .paginate(10)
401        .exec(&mut db)
402        .await?;
403    let page2: Page<_> = page1.next(&mut db).await?.unwrap();
404
405    // Check the number and order of the items in the previous page
406    assert!(page2.has_prev());
407    let prev: Option<Page<_>> = page2.prev(&mut db).await?;
408    assert!(prev.is_some());
409    let prev_page: Page<Event> = prev.unwrap();
410    assert_eq!(prev_page.len(), 10);
411    for (i, expected) in (10..20).rev().enumerate() {
412        assert_eq!(prev_page[i].seq, expected);
413    }
414
415    Ok(())
416}
417
418#[driver_test(requires(not(backward_pagination)))]
419pub async fn paginate_composite_key_no_prev(test: &mut Test) -> Result<()> {
420    #[derive(Debug, toasty::Model)]
421    #[key(partition = kind, local = seq)]
422    struct Event {
423        kind: String,
424        seq: i64,
425    }
426
427    let mut db = test.setup_db(models!(Event)).await;
428
429    for i in 0..20 {
430        Event::create().kind("info").seq(i).exec(&mut db).await?;
431    }
432
433    test.log().clear();
434
435    // Retrieve two pages
436    let page1: Page<_> = Event::filter_by_kind("info")
437        .order_by(Event::fields().seq().asc())
438        .paginate(10)
439        .exec(&mut db)
440        .await?;
441    let page2: Page<_> = page1.next(&mut db).await?.unwrap();
442
443    // Check that it is not possible to access the previous page
444    assert!(!page2.has_prev());
445    let prev: Option<Page<_>> = page2.prev(&mut db).await?;
446    assert!(prev.is_none());
447
448    Ok(())
449}