Skip to main content

aster_forge_db/
pagination.rs

1//! SeaORM repository helpers for offset pagination.
2//!
3//! The helper executes the same select query for total count and page items, letting service code
4//! build filters once and reuse them consistently. It stays generic over entities and connections
5//! so product repositories can keep their own model types.
6
7use sea_orm::{ConnectionTrait, EntityTrait, PaginatorTrait, QuerySelect, Select};
8
9use crate::DbError;
10
11/// Fetches an offset page and total count from a SeaORM select query.
12pub async fn fetch_offset_page<C, Entity, Error>(
13    db: &C,
14    query: Select<Entity>,
15    limit: u64,
16    offset: u64,
17) -> std::result::Result<(Vec<Entity::Model>, u64), Error>
18where
19    C: ConnectionTrait,
20    Entity: EntityTrait,
21    Error: From<DbError>,
22    Select<Entity>: QuerySelect,
23    for<'db> Select<Entity>: PaginatorTrait<'db, C>,
24{
25    let total = query
26        .clone()
27        .count(db)
28        .await
29        .map_err(DbError::from)
30        .map_err(Error::from)?;
31    let items = query
32        .limit(limit)
33        .offset(offset)
34        .all(db)
35        .await
36        .map_err(DbError::from)
37        .map_err(Error::from)?;
38    Ok((items, total))
39}
40
41#[cfg(test)]
42mod tests {
43    use super::fetch_offset_page;
44    use sea_orm::{
45        ActiveModelBehavior, ConnectionTrait, Database, DeriveEntityModel, DerivePrimaryKey,
46        DeriveRelation, EntityTrait, EnumIter, PrimaryKeyTrait, QueryOrder,
47    };
48
49    #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
50    #[sea_orm(table_name = "pagination_items")]
51    pub struct Model {
52        #[sea_orm(primary_key)]
53        pub id: i32,
54        pub name: String,
55    }
56
57    #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
58    pub enum Relation {}
59
60    impl ActiveModelBehavior for ActiveModel {}
61
62    #[tokio::test]
63    async fn fetch_offset_page_returns_items_and_total_count() {
64        let db = Database::connect("sqlite::memory:")
65            .await
66            .expect("sqlite memory database should connect");
67        db.execute_unprepared(
68            "CREATE TABLE pagination_items (id INTEGER PRIMARY KEY, name TEXT NOT NULL);",
69        )
70        .await
71        .expect("table should be created");
72        db.execute_unprepared(
73            "INSERT INTO pagination_items (id, name) VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma');",
74        )
75        .await
76        .expect("rows should be inserted");
77
78        let (items, total) = fetch_offset_page::<_, _, crate::DbError>(
79            &db,
80            Entity::find().order_by_asc(Column::Id),
81            2,
82            1,
83        )
84        .await
85        .expect("page should load");
86
87        assert_eq!(total, 3);
88        assert_eq!(
89            items.into_iter().map(|item| item.name).collect::<Vec<_>>(),
90            vec!["beta".to_string(), "gamma".to_string()]
91        );
92    }
93}