Skip to main content

drizzle_core/
pagination.rs

1use crate::SQL;
2use crate::expr::Nullability;
3use crate::placeholder::{Placeholder, TypedPlaceholder};
4use crate::prelude::Cow;
5use crate::traits::{SQLParam, ToSQL};
6use crate::types::Integral;
7
8mod private {
9    pub trait Sealed {}
10}
11
12/// Argument accepted by `LIMIT` and `OFFSET` clauses.
13///
14/// Numeric values render as SQL numeric literals, unless the dialect's value
15/// type opts into bound pagination parameters via
16/// [`SQLParam::pagination_param`] (`PostgreSQL` does, so `.limit(10)` renders
17/// as `LIMIT $n` there, keeping SQL text stable for statement caching).
18/// Placeholders render through the dialect's parameter syntax so prepared
19/// statements can bind pagination values.
20///
21/// # Panics
22///
23/// Numeric arguments panic during SQL construction when they are negative or
24/// too large to fit in `usize`.
25#[diagnostic::on_unimplemented(
26    message = "`{Self}` cannot be used as a LIMIT/OFFSET argument",
27    label = "expected a non-negative integer value or an integer placeholder"
28)]
29pub trait PaginationArg<'a, V: SQLParam + 'a>: private::Sealed {
30    #[track_caller]
31    fn into_pagination_sql(self) -> SQL<'a, V>;
32}
33
34/// Renders a validated pagination value either as a bound parameter (when the
35/// dialect's value type opts in) or as a numeric literal.
36fn pagination_value_sql<'a, V>(value: usize) -> SQL<'a, V>
37where
38    V: SQLParam + 'a,
39{
40    match V::pagination_param(value) {
41        Some(param) => SQL::param(Cow::Owned(param)),
42        None => SQL::number(value),
43    }
44}
45
46macro_rules! impl_unsigned_pagination_arg {
47    ($($ty:ty),+ $(,)?) => {
48        $(
49            impl private::Sealed for $ty {}
50
51            impl<'a, V> PaginationArg<'a, V> for $ty
52            where
53                V: SQLParam + 'a,
54            {
55                #[track_caller]
56                fn into_pagination_sql(self) -> SQL<'a, V> {
57                    let value =
58                        usize::try_from(self).expect("LIMIT/OFFSET value must fit usize");
59                    pagination_value_sql(value)
60                }
61            }
62        )+
63    };
64}
65
66macro_rules! impl_signed_pagination_arg {
67    ($($ty:ty),+ $(,)?) => {
68        $(
69            impl private::Sealed for $ty {}
70
71            impl<'a, V> PaginationArg<'a, V> for $ty
72            where
73                V: SQLParam + 'a,
74            {
75                #[track_caller]
76                fn into_pagination_sql(self) -> SQL<'a, V> {
77                    let value = usize::try_from(self)
78                        .expect("LIMIT/OFFSET value must be non-negative and fit usize");
79                    pagination_value_sql(value)
80                }
81            }
82        )+
83    };
84}
85
86impl_unsigned_pagination_arg!(usize, u8, u16, u32, u64);
87impl_signed_pagination_arg!(isize, i8, i16, i32, i64);
88
89impl private::Sealed for Placeholder {}
90
91impl<'a, V> PaginationArg<'a, V> for Placeholder
92where
93    V: SQLParam + 'a,
94{
95    #[track_caller]
96    fn into_pagination_sql(self) -> SQL<'a, V> {
97        self.to_sql()
98    }
99}
100
101impl<T, N> private::Sealed for TypedPlaceholder<T, N>
102where
103    T: Integral,
104    N: Nullability,
105{
106}
107
108impl<'a, V, T, N> PaginationArg<'a, V> for TypedPlaceholder<T, N>
109where
110    V: SQLParam + 'a,
111    T: Integral,
112    N: Nullability,
113{
114    #[track_caller]
115    fn into_pagination_sql(self) -> SQL<'a, V> {
116        self.to_sql()
117    }
118}