rdb_pagination_core/
pagination_options.rs

1use crate::OrderByOptions;
2
3/// Struct representing pagination options.
4///
5/// # Examples
6///
7/// ```rust
8/// # use rdb_pagination_core::PaginationOptions;
9/// #
10/// let options = PaginationOptions::new().page(1).items_per_page(20);
11/// ```
12#[derive(Debug, Clone)]
13pub struct PaginationOptions<T: OrderByOptions = ()> {
14    /// Page number.
15    ///
16    /// * If the value is `0`, it will default to `1`.
17    /// * If the value exceeds the maximum page number, it will be considered as the maximum page number.
18    ///
19    ///  Default: `1`.
20    pub page:           usize,
21    /// Number of items per page.
22    ///
23    /// * If the value is `0`, it means **query all items in a single page**.
24    ///
25    ///  Default: `0`.
26    pub items_per_page: usize,
27    /// Ordering options which has to implement the `OrderByOptions` trait.
28    ///
29    /// Default: `()`.
30    pub order_by:       T,
31}
32
33impl PaginationOptions {
34    /// Create a new `PaginationOptions`.
35    ///
36    /// ```rust
37    /// # use rdb_pagination_core::PaginationOptions;
38    /// #
39    /// let options = PaginationOptions::new();
40    /// // equals to
41    /// let options = PaginationOptions {
42    ///     page:           1,
43    ///     items_per_page: 0,
44    ///     order_by:       (),
45    /// };
46    /// ```
47    #[inline]
48    pub const fn new() -> Self {
49        Self {
50            page: 1, items_per_page: 0, order_by: ()
51        }
52    }
53}
54
55impl<T: OrderByOptions> Default for PaginationOptions<T> {
56    /// Create a new `PaginationOptions<T>`.
57    ///
58    /// ```rust
59    /// # use rdb_pagination_core::PaginationOptions;
60    /// #
61    /// let options = <PaginationOptions<()>>::default();
62    /// // equals to
63    /// let options = PaginationOptions {
64    ///     page:           1,
65    ///     items_per_page: 0,
66    ///     order_by:       (),
67    /// };
68    /// ```
69    #[inline]
70    fn default() -> Self {
71        Self {
72            page: 1, items_per_page: 0, order_by: T::default()
73        }
74    }
75}
76
77impl<T: OrderByOptions> PaginationOptions<T> {
78    /// Set the page number.
79    ///
80    /// * If the value is `0`, it will be considered as `1`.
81    /// * If the value exceeds the maximum page number, it will be considered as the maximum page number.
82    #[inline]
83    pub const fn page(mut self, page: usize) -> Self {
84        self.page = page;
85
86        self
87    }
88
89    /// Set the number of items per page.
90    ///
91    /// * If the value is `0`, it means **query all items in a single page**.
92    #[inline]
93    pub const fn items_per_page(mut self, items_per_page: usize) -> Self {
94        self.items_per_page = items_per_page;
95
96        self
97    }
98
99    /// Set the ordering options which has to implement the `OrderByOptions` trait.
100    #[inline]
101    pub fn order_by(mut self, order_by: T) -> Self {
102        self.order_by = order_by;
103
104        self
105    }
106
107    /// Compute the offset for pagination.
108    #[inline]
109    pub const fn offset(&self) -> u64 {
110        if self.items_per_page == 0 {
111            0
112        } else {
113            match self.page {
114                0 | 1 => 0,
115                _ => (self.items_per_page * (self.page - 1)) as u64,
116            }
117        }
118    }
119
120    /// Compute the limit for pagination. `None` means **unlimited**.
121    #[inline]
122    pub const fn limit(&self) -> Option<usize> {
123        if self.items_per_page == 0 {
124            None
125        } else {
126            Some(self.items_per_page)
127        }
128    }
129}
130
131#[cfg(any(feature = "mysql", feature = "sqlite"))]
132impl<T: OrderByOptions> PaginationOptions<T> {
133    fn to_sql_limit_offset<'a>(&self, s: &'a mut String) -> &'a str {
134        use std::{fmt::Write, str::from_utf8_unchecked};
135
136        let len = s.len();
137
138        let limit = self.limit();
139
140        if let Some(limit) = limit {
141            s.write_fmt(format_args!("LIMIT {limit}")).unwrap();
142        }
143
144        let offset = self.offset();
145
146        if offset > 0 {
147            if !s.is_empty() {
148                s.push(' ');
149            }
150
151            s.write_fmt(format_args!("OFFSET {offset}")).unwrap();
152        }
153
154        unsafe { from_utf8_unchecked(&s.as_bytes()[len..]) }
155    }
156}
157
158#[cfg(feature = "mysql")]
159impl<T: OrderByOptions> PaginationOptions<T> {
160    /// Generate a `LIMIT` with `OFFSET` clause for MySQL.
161    ///
162    /// If `limit()` is `Some(n)`,
163    ///
164    /// ```sql
165    /// LIMIT <limit()> [OFFSET <offset()>]
166    /// ```
167    ///
168    /// If `offset()` is not zero,
169    ///
170    /// ```sql
171    /// [LIMIT <limit()>] OFFSET <offset()>
172    /// ```
173    #[inline]
174    pub fn to_mysql_limit_offset<'a>(&self, s: &'a mut String) -> &'a str {
175        self.to_sql_limit_offset(s)
176    }
177}
178
179#[cfg(feature = "sqlite")]
180impl<T: OrderByOptions> PaginationOptions<T> {
181    /// Generate a `LIMIT` with `OFFSET` clause for SQLite.
182    ///
183    /// If `limit()` is `Some(n)`,
184    ///
185    /// ```sql
186    /// LIMIT <limit()> [OFFSET <offset()>]
187    /// ```
188    ///
189    /// If `offset()` is not zero,
190    ///
191    /// ```sql
192    /// [LIMIT <limit()>] OFFSET <offset()>
193    /// ```
194    #[inline]
195    pub fn to_sqlite_limit_offset<'a>(&self, s: &'a mut String) -> &'a str {
196        self.to_sql_limit_offset(s)
197    }
198}
199
200#[cfg(feature = "mssql")]
201impl<T: OrderByOptions> PaginationOptions<T> {
202    /// Generate a `OFFSET` with `FETCH` clause for Microsoft SQL Server.
203    ///
204    /// If `limit()` is `Some(n)` or `offset()` is not zero,
205    ///
206    /// ```sql
207    /// OFFSET <offset()> ROWS [FETCH NEXT <limit()> ROWS ONLY]
208    /// ```
209    #[inline]
210    pub fn to_mssql_limit_offset<'a>(&self, s: &'a mut String) -> &'a str {
211        use std::{fmt::Write, str::from_utf8_unchecked};
212
213        let len = s.len();
214
215        let limit = self.limit();
216
217        let offset = self.offset();
218
219        if let Some(limit) = limit {
220            s.write_fmt(format_args!("OFFSET {offset} ROWS FETCH NEXT {limit} ROWS ONLY")).unwrap();
221        } else if offset > 0 {
222            s.write_fmt(format_args!("OFFSET {offset} ROWS")).unwrap();
223        }
224
225        unsafe { from_utf8_unchecked(&s.as_bytes()[len..]) }
226    }
227}
228
229#[cfg(feature = "mssql2008")]
230impl<T: OrderByOptions> PaginationOptions<T> {
231    /// Generate a `WHERE` clause for Microsoft SQL Server 2008 and earlier (used for check the row number).
232    ///
233    /// If `limit()` is `Some(n)`,
234    ///
235    /// ```sql
236    /// WHERE [<row_number_column_name>] <= <limit()>
237    /// ```
238    ///
239    /// If `offset()` is not zero,
240    ///
241    /// ```sql
242    /// WHERE [<row_number_column_name>] > <offset()>
243    /// ```
244    ///
245    /// If both above are true,
246    ///
247    /// ```sql
248    /// WHERE [<row_number_column_name>] BETWEEN (<offset() + 1>) AND <offset() + limit()>
249    /// ```
250    #[inline]
251    pub fn to_mssql2008_limit_offset<'a>(
252        &self,
253        row_number_column_name: impl AsRef<str>,
254        s: &'a mut String,
255    ) -> &'a str {
256        use std::{fmt::Write, str::from_utf8_unchecked};
257
258        let row_number_column_name = row_number_column_name.as_ref();
259
260        let len = s.len();
261
262        let offset = self.offset();
263
264        if let Some(limit) = self.limit() {
265            if offset > 0 {
266                s.write_fmt(format_args!(
267                    "WHERE [{row_number_column_name}] BETWEEN {} AND {}",
268                    offset + 1,
269                    offset + limit as u64
270                ))
271                .unwrap();
272            } else {
273                s.write_fmt(format_args!("WHERE [{row_number_column_name}] <= {limit}")).unwrap();
274            }
275        } else if offset > 0 {
276            s.write_fmt(format_args!("WHERE [{row_number_column_name}] > {offset}")).unwrap();
277        }
278
279        unsafe { from_utf8_unchecked(&s.as_bytes()[len..]) }
280    }
281}
282
283#[cfg(feature = "serde")]
284mod serde_trait {
285    use core::{fmt, fmt::Formatter, marker::PhantomData};
286
287    use serde::{
288        de::{MapAccess, Visitor},
289        ser::SerializeStruct,
290        Deserialize, Deserializer, Serialize, Serializer,
291    };
292
293    use super::PaginationOptions;
294    use crate::OrderByOptions;
295
296    impl<'de, T: OrderByOptions + Deserialize<'de>> Deserialize<'de> for PaginationOptions<T> {
297        #[inline]
298        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
299        where
300            D: Deserializer<'de>, {
301            struct MyVisitor<T>(PhantomData<T>);
302
303            impl<'de, T: OrderByOptions + Deserialize<'de>> Visitor<'de> for MyVisitor<T> {
304                type Value = PaginationOptions<T>;
305
306                #[inline]
307                fn expecting(&self, f: &mut Formatter) -> fmt::Result {
308                    f.write_str("a map of options")
309                }
310
311                #[inline]
312                fn visit_map<V>(self, mut v: V) -> Result<Self::Value, V::Error>
313                where
314                    V: MapAccess<'de>, {
315                    let mut page: Option<usize> = None;
316                    let mut items_per_page: Option<usize> = None;
317                    let mut order_by: Option<T> = None;
318
319                    while let Some(key) = v.next_key::<&str>()? {
320                        match key {
321                            "page" => {
322                                page = Some(v.next_value()?);
323                            },
324                            "items_per_page" => {
325                                items_per_page = Some(v.next_value()?);
326                            },
327                            "order_by" => {
328                                order_by = Some(v.next_value()?);
329                            },
330                            _ => continue,
331                        }
332                    }
333
334                    let page = page.unwrap_or(1);
335                    let items_per_page = items_per_page.unwrap_or(0);
336                    let order_by = order_by.unwrap_or_default();
337
338                    Ok(PaginationOptions {
339                        page,
340                        items_per_page,
341                        order_by,
342                    })
343                }
344            }
345
346            deserializer.deserialize_map(MyVisitor(PhantomData))
347        }
348    }
349
350    impl<T: OrderByOptions + Serialize> Serialize for PaginationOptions<T> {
351        #[inline]
352        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
353        where
354            S: Serializer, {
355            let mut s = serializer.serialize_struct("PaginationOptions", 3)?;
356
357            s.serialize_field("page", &self.page)?;
358            s.serialize_field("items_per_page", &self.items_per_page)?;
359            s.serialize_field("order_by", &self.order_by)?;
360
361            s.end()
362        }
363    }
364}