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}