Skip to main content

mas_storage_pg/
pagination.rs

1// Copyright 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8//! Utilities to manage paginated queries.
9
10use mas_storage::{Pagination, pagination::PaginationDirection};
11use sea_query::{ExprTrait, IntoColumnRef};
12use uuid::Uuid;
13
14/// An extension trait to the `sqlx` [`QueryBuilder`], to help adding pagination
15/// to a query
16pub trait QueryBuilderExt {
17    /// Add cursor-based pagination to a query, as used in paginated GraphQL
18    /// connections
19    fn generate_pagination<C: IntoColumnRef>(
20        &mut self,
21        column: C,
22        pagination: Pagination,
23    ) -> &mut Self;
24}
25
26impl QueryBuilderExt for sea_query::SelectStatement {
27    fn generate_pagination<C: IntoColumnRef>(
28        &mut self,
29        column: C,
30        pagination: Pagination,
31    ) -> &mut Self {
32        let id_field = column.into_column_ref();
33
34        // ref: https://github.com/graphql/graphql-relay-js/issues/94#issuecomment-232410564
35        // 1. Start from the greedy query: SELECT * FROM table
36
37        // 2. If the after argument is provided, add `id > parsed_cursor` to the `WHERE`
38        // clause
39        if let Some(after) = pagination.after {
40            self.and_where(sea_query::Expr::col(id_field.clone()).gt(Uuid::from(after)));
41        }
42
43        // 3. If the before argument is provided, add `id < parsed_cursor` to the
44        // `WHERE` clause
45        if let Some(before) = pagination.before {
46            self.and_where(sea_query::Expr::col(id_field.clone()).lt(Uuid::from(before)));
47        }
48
49        match pagination.direction {
50            // 4. If the first argument is provided, add `ORDER BY id ASC LIMIT first+1` to the
51            // query
52            PaginationDirection::Forward => {
53                self.order_by(id_field, sea_query::Order::Asc)
54                    .limit((pagination.count + 1) as u64);
55            }
56            // 5. If the first argument is provided, add `ORDER BY id DESC LIMIT last+1` to the
57            // query
58            PaginationDirection::Backward => {
59                self.order_by(id_field, sea_query::Order::Desc)
60                    .limit((pagination.count + 1) as u64);
61            }
62        }
63
64        self
65    }
66}