1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
use sqlx;

macro_rules! push_where {
    ($self:expr, $builder:expr, $($cursor:expr),+) => {{
        $builder.push(" ");
        $builder.push("(");

        // (col1, col2, ...)
        let mut sep = $builder.separated(", ");
        for col in &$self.columns {
            sep.push(col);
        }

        // operator, < or >
        $builder.push(" ");
        $builder.push(&$self.compare);
        $builder.push(" ");

        // ($_, $_, ...)
        let mut sep = $builder.separated(", ");
        $(
            sep.push_bind($cursor);
        )+

        $builder.push(")");
    }}
}


/// The struct used to perform pagination
pub struct Page {
    columns: Vec<String>,
    compare: &'static str,
    sort: &'static str,
    size: u32,
}

impl Page {

    /// Create a new `Page`.
    ///
    /// `smaller`: Controls the direction of the pagination.
    ///
    /// - `true`: Select the rows towards the direction in which the cursor becomes smaller.
    ///   The returned rows will be sorted `desc`.
    /// - `false`: Select the rows towards the direction in which the cursor becomes bigger.
    ///   The returned rows will be sorted `asc`.
    ///
    /// For example, if the sorting columns are `(time_of_insertion, table_pkey)`,
    /// and you want to scroll to the past, you should set `smaller = true`.
    ///
    /// `size`: Size of the page.
    ///
    /// `columns`:
    /// Sort rows using these columns (up to 5).
    /// Note that the joint of these columns should uniquely identifies a row.
    pub fn new(smaller: bool, size: u32, columns: Vec<String>) -> Self {
        // When smaller: 7 6 5 4 3 => order desc, col < 7
        // When larger : 3 4 5 6 7 => order asc,  col > 3
        let compare = if smaller { "<" } else { ">" };
        let sort = if smaller { "desc" } else { "asc" };

        Page {
            columns,
            compare,
            sort,
            size,
        }
    }


    /// Push the pagination condition to the `builder: QueryBuilder`.
    ///
    /// Roughly, `push_whereN` pushes and binds `(col_1, col_2, ..., col_N) op ($_, $_, ..., $_)`
    /// to the `builder`, where `op` is chosen according to the pagination direction,
    /// and `N` is the number of cursor columns.
    ///
    /// Note, internally this calls `builder::push/push_bind`,
    /// so it is as secure as the `QueryBuilder`.
    pub fn push_where1<'args, T1>(&self, builder: &mut sqlx::QueryBuilder<'args, sqlx::Postgres>, cursors: Option<T1>)
        where
            T1: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send
    {
        match cursors {
            None => {
                builder.push(" ");
                builder.push("true");
            }
            Some(t1) => {
                push_where!(self, builder, t1);
            }
        }
    }

    pub fn push_where2<'args, T1, T2>(&self, builder: &mut sqlx::QueryBuilder<'args, sqlx::Postgres>, cursors: Option<(T1, T2)>)
        where
            T1: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T2: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
    {
        match cursors {
            None => {
                builder.push(" ");
                builder.push("true");
            }
            Some((t1, t2)) => {
                push_where!(self, builder, t1, t2);
            }
        }
    }

    pub fn push_where3<'args, T1, T2, T3>(&self, builder: &mut sqlx::QueryBuilder<'args, sqlx::Postgres>, cursors: Option<(T1, T2, T3)>)
        where
            T1: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T2: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T3: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
    {
        match cursors {
            None => {
                builder.push(" ");
                builder.push("true");
            }
            Some((t1, t2, t3)) => {
                push_where!(self, builder, t1, t2, t3);
            }
        }
    }

    pub fn push_where4<'args, T1, T2, T3, T4>(&self, builder: &mut sqlx::QueryBuilder<'args, sqlx::Postgres>, cursors: Option<(T1, T2, T3, T4)>)
        where
            T1: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T2: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T3: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T4: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
    {
        match cursors {
            None => {
                builder.push(" ");
                builder.push("true");
            }
            Some((t1, t2, t3, t4)) => {
                push_where!(self, builder, t1, t2, t3, t4);
            }
        }
    }

    pub fn push_where5<'args, T1, T2, T3, T4, T5>(&self, builder: &mut sqlx::QueryBuilder<'args, sqlx::Postgres>, cursors: Option<(T1, T2, T3, T4, T5)>)
        where
            T1: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T2: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T3: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T4: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
            T5: 'args + sqlx::Encode<'args, sqlx::Postgres> + sqlx::Type <sqlx::Postgres> + Send,
    {
        match cursors {
            None => {
                builder.push(" ");
                builder.push("true");
            }
            Some((t1, t2, t3, t4, t5)) => {
                push_where!(self, builder, t1, t2, t3, t4, t5);
            }
        }
    }

    /// Push the order by clause `order by (col_1, ...) asc/desc`.
    pub fn push_order_by(&self, builder: &mut sqlx::QueryBuilder <sqlx::Postgres>) {
        builder.push(" ");
        builder.push("order by");

        // col1 desc, col2 desc, ...
        builder.push(" ");
        let mut sep = builder.separated(", ");
        for col in &self.columns {
            sep.push(col);
            sep.push_unseparated(" ");
            sep.push_unseparated(&self.sort);
        }
    }

    /// Push the limit clause `limit k`.
    pub fn push_limit(&self, builder: &mut sqlx::QueryBuilder <sqlx::Postgres>) {
        builder.push(" ");
        builder.push("limit");

        builder.push(" ");
        builder.push_bind(self.size);
    }
}