Skip to main content

sea_orm/executor/
cursor.rs

1use crate::{
2    ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Identity, IdentityOf, IntoIdentity,
3    PartialModelTrait, PrimaryKeyToColumn, QuerySelect, Select, SelectModel, SelectThree,
4    SelectThreeModel, SelectTwo, SelectTwoModel, SelectorTrait, Topology,
5};
6use sea_query::{
7    Condition, DynIden, Expr, ExprTrait, IntoValueTuple, Order, SeaRc, SelectStatement, SimpleExpr,
8    Value, ValueTuple,
9};
10use std::marker::PhantomData;
11use strum::IntoEnumIterator as Iterable;
12
13#[cfg(feature = "with-json")]
14use crate::JsonValue;
15
16/// Keyset (cursor) pagination over an ordered query.
17///
18/// Unlike [`Paginator`](crate::Paginator) which uses `LIMIT` / `OFFSET`,
19/// `Cursor` paginates by filtering on the configured order column(s) — fast
20/// and stable across concurrent inserts. Build one with
21/// [`Select::cursor_by`](crate::Select::cursor_by) (or
22/// [`CursorTrait`]-aware helpers on other selects), then drive it with
23/// [`before`](Self::before) / [`after`](Self::after) /
24/// [`first`](Self::first) / [`last`](Self::last) before calling
25/// `.all(db)` / `.one(db)`.
26#[derive(Debug, Clone)]
27pub struct Cursor<S>
28where
29    S: SelectorTrait,
30{
31    query: SelectStatement,
32    table: DynIden,
33    order_columns: Identity,
34    secondary_order_by: Vec<(DynIden, Identity)>,
35    first: Option<u64>,
36    last: Option<u64>,
37    before: Option<ValueTuple>,
38    after: Option<ValueTuple>,
39    sort_asc: bool,
40    is_result_reversed: bool,
41    phantom: PhantomData<S>,
42}
43
44impl<S> Cursor<S>
45where
46    S: SelectorTrait,
47{
48    pub(crate) fn new<C>(query: SelectStatement, table: DynIden, order_columns: C) -> Self
49    where
50        C: IntoIdentity,
51    {
52        Self {
53            query,
54            table,
55            order_columns: order_columns.into_identity(),
56            secondary_order_by: Default::default(),
57            last: None,
58            first: None,
59            after: None,
60            before: None,
61            sort_asc: true,
62            is_result_reversed: false,
63            phantom: PhantomData,
64        }
65    }
66
67    /// Filter paginated result with corresponding column less than the input value
68    pub fn before<V>(&mut self, values: V) -> &mut Self
69    where
70        V: IntoValueTuple,
71    {
72        self.before = Some(values.into_value_tuple());
73        self
74    }
75
76    /// Filter paginated result with corresponding column greater than the input value
77    pub fn after<V>(&mut self, values: V) -> &mut Self
78    where
79        V: IntoValueTuple,
80    {
81        self.after = Some(values.into_value_tuple());
82        self
83    }
84
85    fn apply_filters(&mut self) -> Result<&mut Self, DbErr> {
86        if let Some(values) = self.after.clone() {
87            if self.order_columns.arity() != values.arity() {
88                return Err(DbErr::KeyArityMismatch {
89                    expected: self.order_columns.arity() as u8,
90                    received: values.arity() as u8,
91                });
92            }
93            let condition = self.apply_filter(values, |c, v| {
94                let exp = Expr::col((self.table.clone(), c.clone()));
95                if self.sort_asc { exp.gt(v) } else { exp.lt(v) }
96            });
97            self.query.cond_where(condition);
98        }
99
100        if let Some(values) = self.before.clone() {
101            if self.order_columns.arity() != values.arity() {
102                return Err(DbErr::KeyArityMismatch {
103                    expected: self.order_columns.arity() as u8,
104                    received: values.arity() as u8,
105                });
106            }
107            let condition = self.apply_filter(values, |c, v| {
108                let exp = Expr::col((self.table.clone(), c.clone()));
109                if self.sort_asc { exp.lt(v) } else { exp.gt(v) }
110            });
111            self.query.cond_where(condition);
112        }
113
114        Ok(self)
115    }
116
117    fn apply_filter<F>(&self, values: ValueTuple, f: F) -> Condition
118    where
119        F: Fn(&DynIden, Value) -> SimpleExpr,
120    {
121        match (&self.order_columns, values) {
122            (Identity::Unary(c1), ValueTuple::One(v1)) => Condition::all().add(f(c1, v1)),
123            (Identity::Binary(c1, c2), ValueTuple::Two(v1, v2)) => Condition::any()
124                .add(
125                    Condition::all()
126                        .add(Expr::col((self.table.clone(), c1.clone())).eq(v1.clone()))
127                        .add(f(c2, v2)),
128                )
129                .add(f(c1, v1)),
130            (Identity::Ternary(c1, c2, c3), ValueTuple::Three(v1, v2, v3)) => Condition::any()
131                .add(
132                    Condition::all()
133                        .add(Expr::col((self.table.clone(), c1.clone())).eq(v1.clone()))
134                        .add(Expr::col((self.table.clone(), c2.clone())).eq(v2.clone()))
135                        .add(f(c3, v3)),
136                )
137                .add(
138                    Condition::all()
139                        .add(Expr::col((self.table.clone(), c1.clone())).eq(v1.clone()))
140                        .add(f(c2, v2)),
141                )
142                .add(f(c1, v1)),
143            (Identity::Many(col_vec), ValueTuple::Many(val_vec))
144                if col_vec.len() == val_vec.len() =>
145            {
146                // The length of `col_vec` and `val_vec` should be equal and is denoted by "n".
147                //
148                // The elements of `col_vec` and `val_vec` are denoted by:
149                //   - `col_vec`: "col_1", "col_2", ..., "col_n-1", "col_n"
150                //   - `val_vec`: "val_1", "val_2", ..., "val_n-1", "val_n"
151                //
152                // The general form of the where condition should have "n" number of inner-AND-condition chained by an outer-OR-condition.
153                // The "n"-th inner-AND-condition should have exactly "n" number of column value expressions,
154                // to construct the expression we take the first "n" number of column and value from the respected vector.
155                //   - if it's not the last element, then we construct a "col_1 = val_1" equal expression
156                //   - otherwise, for the last element, we should construct a "col_n > val_n" greater than or "col_n < val_n" less than expression.
157                // i.e.
158                // WHERE
159                //   (col_1 = val_1 AND col_2 = val_2 AND ... AND col_n > val_n)
160                //   OR (col_1 = val_1 AND col_2 = val_2 AND ... AND col_n-1 > val_n-1)
161                //   OR (col_1 = val_1 AND col_2 = val_2 AND ... AND col_n-2 > val_n-2)
162                //   OR ...
163                //   OR (col_1 = val_1 AND col_2 > val_2)
164                //   OR (col_1 > val_1)
165
166                // Counting from 1 to "n" (inclusive) but in reverse, i.e. n, n-1, ..., 2, 1
167                (1..=col_vec.len())
168                    .rev()
169                    .fold(Condition::any(), |cond_any, n| {
170                        // Construct the inner-AND-condition
171                        let inner_cond_all =
172                            // Take the first "n" elements from the column and value vector respectively
173                            col_vec.iter().zip(val_vec.iter()).enumerate().take(n).fold(
174                                Condition::all(),
175                                |inner_cond_all, (i, (col, val))| {
176                                    let val = val.clone();
177                                    // Construct a equal expression,
178                                    // except for the last one being greater than or less than expression
179                                    let expr = if i != (n - 1) {
180                                        Expr::col((self.table.clone(), col.clone()))
181                                            .eq(val)
182                                    } else {
183                                        f(col, val)
184                                    };
185                                    // Chain it with AND operator
186                                    inner_cond_all.add(expr)
187                                },
188                            );
189                        // Chain inner-AND-condition with OR operator
190                        cond_any.add(inner_cond_all)
191                    })
192            }
193            _ => unreachable!("checked by caller"),
194        }
195    }
196
197    /// Use ascending sort order
198    pub fn asc(&mut self) -> &mut Self {
199        self.sort_asc = true;
200        self
201    }
202
203    /// Use descending sort order
204    pub fn desc(&mut self) -> &mut Self {
205        self.sort_asc = false;
206        self
207    }
208
209    /// Limit result set to only first N rows in ascending order of the order by column
210    pub fn first(&mut self, num_rows: u64) -> &mut Self {
211        self.last = None;
212        self.first = Some(num_rows);
213        self
214    }
215
216    /// Limit result set to only last N rows in ascending order of the order by column
217    pub fn last(&mut self, num_rows: u64) -> &mut Self {
218        self.first = None;
219        self.last = Some(num_rows);
220        self
221    }
222
223    fn resolve_sort_order(&mut self) -> Order {
224        let should_reverse_order = self.last.is_some();
225        self.is_result_reversed = should_reverse_order;
226
227        if (self.sort_asc && !should_reverse_order) || (!self.sort_asc && should_reverse_order) {
228            Order::Asc
229        } else {
230            Order::Desc
231        }
232    }
233
234    fn apply_limit(&mut self) -> &mut Self {
235        if let Some(num_rows) = self.first {
236            self.query.limit(num_rows);
237        } else if let Some(num_rows) = self.last {
238            self.query.limit(num_rows);
239        }
240
241        self
242    }
243
244    fn apply_order_by(&mut self) -> &mut Self {
245        self.query.clear_order_by();
246        let ord = self.resolve_sort_order();
247
248        let query = &mut self.query;
249        let order = |query: &mut SelectStatement, col: &DynIden| {
250            query.order_by((self.table.clone(), col.clone()), ord.clone());
251        };
252        match &self.order_columns {
253            Identity::Unary(c1) => {
254                order(query, c1);
255            }
256            Identity::Binary(c1, c2) => {
257                order(query, c1);
258                order(query, c2);
259            }
260            Identity::Ternary(c1, c2, c3) => {
261                order(query, c1);
262                order(query, c2);
263                order(query, c3);
264            }
265            Identity::Many(vec) => {
266                for col in vec.iter() {
267                    order(query, col);
268                }
269            }
270        }
271
272        for (tbl, col) in self.secondary_order_by.iter().cloned() {
273            if let Identity::Unary(c1) = col {
274                query.order_by((tbl, c1), ord.clone());
275            }
276        }
277
278        self
279    }
280
281    /// Fetch the paginated result
282    pub async fn all<C>(&mut self, db: &C) -> Result<Vec<S::Item>, DbErr>
283    where
284        C: ConnectionTrait,
285    {
286        self.apply_limit();
287        self.apply_order_by();
288        self.apply_filters()?;
289
290        let rows = db.query_all(&self.query).await?;
291        let mut buffer = Vec::with_capacity(rows.len());
292        for row in rows.into_iter() {
293            buffer.push(S::from_raw_query_result(row)?);
294        }
295        if self.is_result_reversed {
296            buffer.reverse()
297        }
298        Ok(buffer)
299    }
300
301    /// Construct a [Cursor] that fetch any custom struct
302    pub fn into_model<M>(self) -> Cursor<SelectModel<M>>
303    where
304        M: FromQueryResult,
305    {
306        Cursor {
307            query: self.query,
308            table: self.table,
309            order_columns: self.order_columns,
310            secondary_order_by: self.secondary_order_by,
311            last: self.last,
312            first: self.first,
313            after: self.after,
314            before: self.before,
315            sort_asc: self.sort_asc,
316            is_result_reversed: self.is_result_reversed,
317            phantom: PhantomData,
318        }
319    }
320
321    /// Restrict the cursor's projection to a [`PartialModelTrait`] type `M`,
322    /// returning a [`Cursor`] that yields `M` instead of the full model.
323    pub fn into_partial_model<M>(self) -> Cursor<SelectModel<M>>
324    where
325        M: PartialModelTrait,
326    {
327        M::select_cols(QuerySelect::select_only(self)).into_model::<M>()
328    }
329
330    /// Construct a [Cursor] that fetch JSON value
331    #[cfg(feature = "with-json")]
332    pub fn into_json(self) -> Cursor<SelectModel<JsonValue>> {
333        Cursor {
334            query: self.query,
335            table: self.table,
336            order_columns: self.order_columns,
337            secondary_order_by: self.secondary_order_by,
338            last: self.last,
339            first: self.first,
340            after: self.after,
341            before: self.before,
342            sort_asc: self.sort_asc,
343            is_result_reversed: self.is_result_reversed,
344            phantom: PhantomData,
345        }
346    }
347
348    /// Set the cursor ordering for another table when dealing with SelectTwo
349    pub fn set_secondary_order_by(&mut self, tbl_col: Vec<(DynIden, Identity)>) -> &mut Self {
350        self.secondary_order_by = tbl_col;
351        self
352    }
353}
354
355impl<S> QuerySelect for Cursor<S>
356where
357    S: SelectorTrait,
358{
359    type QueryStatement = SelectStatement;
360
361    fn query(&mut self) -> &mut SelectStatement {
362        &mut self.query
363    }
364}
365
366/// Extension trait that adds `.cursor_by(...)` to anything that can be
367/// paginated with a keyset cursor — implemented for [`Select`],
368/// [`SelectTwo`], [`SelectThree`].
369pub trait CursorTrait {
370    /// The [`SelectorTrait`] used to materialise each row of the cursor.
371    type Selector: SelectorTrait + Send + Sync;
372}
373
374impl<E, M> CursorTrait for Select<E>
375where
376    E: EntityTrait<Model = M>,
377    M: FromQueryResult + Sized + Send + Sync,
378{
379    type Selector = SelectModel<M>;
380}
381
382impl<E, M> Select<E>
383where
384    E: EntityTrait<Model = M>,
385    M: FromQueryResult + Sized + Send + Sync,
386{
387    /// Convert into a cursor
388    pub fn cursor_by<C>(self, order_columns: C) -> Cursor<SelectModel<M>>
389    where
390        C: IntoIdentity,
391    {
392        Cursor::new(self.query, SeaRc::new(E::default()), order_columns)
393    }
394}
395
396impl<E, F, M, N> CursorTrait for SelectTwo<E, F>
397where
398    E: EntityTrait<Model = M>,
399    F: EntityTrait<Model = N>,
400    M: FromQueryResult + Sized + Send + Sync,
401    N: FromQueryResult + Sized + Send + Sync,
402{
403    type Selector = SelectTwoModel<M, N>;
404}
405
406impl<E, F, G, M, N, O, TOP> CursorTrait for SelectThree<E, F, G, TOP>
407where
408    E: EntityTrait<Model = M>,
409    F: EntityTrait<Model = N>,
410    G: EntityTrait<Model = O>,
411    M: FromQueryResult + Sized + Send + Sync,
412    N: FromQueryResult + Sized + Send + Sync,
413    O: FromQueryResult + Sized + Send + Sync,
414    TOP: Topology,
415{
416    type Selector = SelectThreeModel<M, N, O>;
417}
418
419impl<E, F, M, N> SelectTwo<E, F>
420where
421    E: EntityTrait<Model = M>,
422    F: EntityTrait<Model = N>,
423    M: FromQueryResult + Sized + Send + Sync,
424    N: FromQueryResult + Sized + Send + Sync,
425{
426    /// Convert into a cursor using column of first entity
427    pub fn cursor_by<C>(self, order_columns: C) -> Cursor<SelectTwoModel<M, N>>
428    where
429        C: IdentityOf<E>,
430    {
431        let primary_keys: Vec<(DynIden, Identity)> = <F::PrimaryKey as Iterable>::iter()
432            .map(|pk| {
433                (
434                    SeaRc::new(F::default()),
435                    Identity::Unary(SeaRc::new(pk.into_column())),
436                )
437            })
438            .collect();
439        let mut cursor = Cursor::new(
440            self.query,
441            SeaRc::new(E::default()),
442            order_columns.identity_of(),
443        );
444        cursor.set_secondary_order_by(primary_keys);
445        cursor
446    }
447
448    /// Convert into a cursor using column of second entity
449    pub fn cursor_by_other<C>(self, order_columns: C) -> Cursor<SelectTwoModel<M, N>>
450    where
451        C: IdentityOf<F>,
452    {
453        let primary_keys: Vec<(DynIden, Identity)> = <E::PrimaryKey as Iterable>::iter()
454            .map(|pk| {
455                (
456                    SeaRc::new(E::default()),
457                    Identity::Unary(SeaRc::new(pk.into_column())),
458                )
459            })
460            .collect();
461        let mut cursor = Cursor::new(
462            self.query,
463            SeaRc::new(F::default()),
464            order_columns.identity_of(),
465        );
466        cursor.set_secondary_order_by(primary_keys);
467        cursor
468    }
469}
470
471impl<E, F, G, M, N, O, TOP> SelectThree<E, F, G, TOP>
472where
473    E: EntityTrait<Model = M>,
474    F: EntityTrait<Model = N>,
475    G: EntityTrait<Model = O>,
476    M: FromQueryResult + Sized + Send + Sync,
477    N: FromQueryResult + Sized + Send + Sync,
478    O: FromQueryResult + Sized + Send + Sync,
479    TOP: Topology,
480{
481    /// Convert into a cursor using column of first entity
482    pub fn cursor_by<C>(self, order_columns: C) -> Cursor<SelectThreeModel<M, N, O>>
483    where
484        C: IdentityOf<E>,
485    {
486        let mut cursor = Cursor::new(
487            self.query,
488            SeaRc::new(E::default()),
489            order_columns.identity_of(),
490        );
491        {
492            let primary_keys: Vec<(DynIden, Identity)> = <F::PrimaryKey as Iterable>::iter()
493                .map(|pk| {
494                    (
495                        SeaRc::new(F::default()),
496                        Identity::Unary(SeaRc::new(pk.into_column())),
497                    )
498                })
499                .collect();
500            cursor.set_secondary_order_by(primary_keys);
501        }
502        {
503            let primary_keys: Vec<(DynIden, Identity)> = <G::PrimaryKey as Iterable>::iter()
504                .map(|pk| {
505                    (
506                        SeaRc::new(G::default()),
507                        Identity::Unary(SeaRc::new(pk.into_column())),
508                    )
509                })
510                .collect();
511            cursor.set_secondary_order_by(primary_keys);
512        }
513        cursor
514    }
515}
516
517#[cfg(test)]
518#[cfg(feature = "mock")]
519mod tests {
520    use crate::entity::prelude::*;
521    use crate::tests_cfg::*;
522    use crate::{DbBackend, MockDatabase, Statement, Transaction};
523    use pretty_assertions::assert_eq;
524
525    #[smol_potat::test]
526    async fn first_2_before_10() -> Result<(), DbErr> {
527        use fruit::*;
528
529        let models = [
530            Model {
531                id: 1,
532                name: "Blueberry".into(),
533                cake_id: Some(1),
534            },
535            Model {
536                id: 2,
537                name: "Raspberry".into(),
538                cake_id: Some(1),
539            },
540        ];
541
542        let db = MockDatabase::new(DbBackend::Postgres)
543            .append_query_results([models.clone()])
544            .into_connection();
545
546        assert_eq!(
547            Entity::find()
548                .cursor_by(Column::Id)
549                .before(10)
550                .first(2)
551                .all(&db)
552                .await?,
553            models
554        );
555
556        assert_eq!(
557            db.into_transaction_log(),
558            [Transaction::many([Statement::from_sql_and_values(
559                DbBackend::Postgres,
560                [
561                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id""#,
562                    r#"FROM "fruit""#,
563                    r#"WHERE "fruit"."id" < $1"#,
564                    r#"ORDER BY "fruit"."id" ASC"#,
565                    r#"LIMIT $2"#,
566                ]
567                .join(" ")
568                .as_str(),
569                [10_i32.into(), 2_u64.into()]
570            ),])]
571        );
572
573        Ok(())
574    }
575
576    #[smol_potat::test]
577    async fn last_2_after_10_desc() -> Result<(), DbErr> {
578        use fruit::*;
579
580        let mut models = [
581            Model {
582                id: 1,
583                name: "Blueberry".into(),
584                cake_id: Some(1),
585            },
586            Model {
587                id: 2,
588                name: "Raspberry".into(),
589                cake_id: Some(1),
590            },
591        ];
592
593        let db = MockDatabase::new(DbBackend::Postgres)
594            .append_query_results([models.clone()])
595            .into_connection();
596
597        models.reverse();
598
599        assert_eq!(
600            Entity::find()
601                .cursor_by(Column::Id)
602                .after(10)
603                .last(2)
604                .desc()
605                .all(&db)
606                .await?,
607            models
608        );
609
610        assert_eq!(
611            db.into_transaction_log(),
612            [Transaction::many([Statement::from_sql_and_values(
613                DbBackend::Postgres,
614                [
615                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id""#,
616                    r#"FROM "fruit""#,
617                    r#"WHERE "fruit"."id" < $1"#,
618                    r#"ORDER BY "fruit"."id" ASC"#,
619                    r#"LIMIT $2"#,
620                ]
621                .join(" ")
622                .as_str(),
623                [10_i32.into(), 2_u64.into()]
624            ),])]
625        );
626
627        Ok(())
628    }
629
630    #[smol_potat::test]
631    async fn first_2_before_10_also_related_select() -> Result<(), DbErr> {
632        let models = [
633            (
634                cake::Model {
635                    id: 1,
636                    name: "Blueberry Cheese Cake".into(),
637                },
638                Some(fruit::Model {
639                    id: 9,
640                    name: "Blueberry".into(),
641                    cake_id: Some(1),
642                }),
643            ),
644            (
645                cake::Model {
646                    id: 2,
647                    name: "Raspberry Cheese Cake".into(),
648                },
649                Some(fruit::Model {
650                    id: 10,
651                    name: "Raspberry".into(),
652                    cake_id: Some(1),
653                }),
654            ),
655        ];
656
657        let db = MockDatabase::new(DbBackend::Postgres)
658            .append_query_results([models.clone()])
659            .into_connection();
660
661        assert_eq!(
662            cake::Entity::find()
663                .find_also_related(Fruit)
664                .cursor_by(cake::Column::Id)
665                .before(10)
666                .first(2)
667                .all(&db)
668                .await?,
669            models
670        );
671
672        assert_eq!(
673            db.into_transaction_log(),
674            [Transaction::many([Statement::from_sql_and_values(
675                DbBackend::Postgres,
676                [
677                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
678                    r#""fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id""#,
679                    r#"FROM "cake""#,
680                    r#"LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
681                    r#"WHERE "cake"."id" < $1"#,
682                    r#"ORDER BY "cake"."id" ASC, "fruit"."id" ASC LIMIT $2"#,
683                ]
684                .join(" ")
685                .as_str(),
686                [10_i32.into(), 2_u64.into()]
687            ),])]
688        );
689
690        Ok(())
691    }
692
693    #[smol_potat::test]
694    async fn last_2_after_10_also_related_select_desc() -> Result<(), DbErr> {
695        let mut models = [
696            (
697                cake::Model {
698                    id: 2,
699                    name: "Raspberry Cheese Cake".into(),
700                },
701                Some(fruit::Model {
702                    id: 10,
703                    name: "Raspberry".into(),
704                    cake_id: Some(1),
705                }),
706            ),
707            (
708                cake::Model {
709                    id: 1,
710                    name: "Blueberry Cheese Cake".into(),
711                },
712                Some(fruit::Model {
713                    id: 9,
714                    name: "Blueberry".into(),
715                    cake_id: Some(1),
716                }),
717            ),
718        ];
719
720        let db = MockDatabase::new(DbBackend::Postgres)
721            .append_query_results([models.clone()])
722            .into_connection();
723
724        models.reverse();
725
726        assert_eq!(
727            cake::Entity::find()
728                .find_also_related(Fruit)
729                .cursor_by(cake::Column::Id)
730                .after(10)
731                .last(2)
732                .desc()
733                .all(&db)
734                .await?,
735            models
736        );
737
738        assert_eq!(
739            db.into_transaction_log(),
740            [Transaction::many([Statement::from_sql_and_values(
741                DbBackend::Postgres,
742                [
743                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
744                    r#""fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id""#,
745                    r#"FROM "cake""#,
746                    r#"LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
747                    r#"WHERE "cake"."id" < $1"#,
748                    r#"ORDER BY "cake"."id" ASC, "fruit"."id" ASC LIMIT $2"#,
749                ]
750                .join(" ")
751                .as_str(),
752                [10_i32.into(), 2_u64.into()]
753            ),])]
754        );
755
756        Ok(())
757    }
758
759    #[smol_potat::test]
760    async fn first_2_before_10_also_related_select_cursor_other() -> Result<(), DbErr> {
761        let models = [(
762            cake::Model {
763                id: 1,
764                name: "Blueberry Cheese Cake".into(),
765            },
766            Some(fruit::Model {
767                id: 9,
768                name: "Blueberry".into(),
769                cake_id: Some(1),
770            }),
771        )];
772
773        let db = MockDatabase::new(DbBackend::Postgres)
774            .append_query_results([models.clone()])
775            .into_connection();
776
777        assert_eq!(
778            cake::Entity::find()
779                .find_also_related(Fruit)
780                .cursor_by_other(fruit::Column::Id)
781                .before(10)
782                .first(2)
783                .all(&db)
784                .await?,
785            models
786        );
787
788        assert_eq!(
789            db.into_transaction_log(),
790            [Transaction::many([Statement::from_sql_and_values(
791                DbBackend::Postgres,
792                [
793                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
794                    r#""fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id""#,
795                    r#"FROM "cake""#,
796                    r#"LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
797                    r#"WHERE "fruit"."id" < $1"#,
798                    r#"ORDER BY "fruit"."id" ASC, "cake"."id" ASC LIMIT $2"#,
799                ]
800                .join(" ")
801                .as_str(),
802                [10_i32.into(), 2_u64.into()]
803            ),])]
804        );
805
806        Ok(())
807    }
808
809    #[smol_potat::test]
810    async fn last_2_after_10_also_related_select_cursor_other_desc() -> Result<(), DbErr> {
811        let models = [(
812            cake::Model {
813                id: 1,
814                name: "Blueberry Cheese Cake".into(),
815            },
816            Some(fruit::Model {
817                id: 9,
818                name: "Blueberry".into(),
819                cake_id: Some(1),
820            }),
821        )];
822
823        let db = MockDatabase::new(DbBackend::Postgres)
824            .append_query_results([models.clone()])
825            .into_connection();
826
827        assert_eq!(
828            cake::Entity::find()
829                .find_also_related(Fruit)
830                .cursor_by_other(fruit::Column::Id)
831                .after(10)
832                .last(2)
833                .desc()
834                .all(&db)
835                .await?,
836            models
837        );
838
839        assert_eq!(
840            db.into_transaction_log(),
841            [Transaction::many([Statement::from_sql_and_values(
842                DbBackend::Postgres,
843                [
844                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
845                    r#""fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id""#,
846                    r#"FROM "cake""#,
847                    r#"LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
848                    r#"WHERE "fruit"."id" < $1"#,
849                    r#"ORDER BY "fruit"."id" ASC, "cake"."id" ASC LIMIT $2"#,
850                ]
851                .join(" ")
852                .as_str(),
853                [10_i32.into(), 2_u64.into()]
854            ),])]
855        );
856
857        Ok(())
858    }
859
860    #[smol_potat::test]
861    async fn first_2_before_10_also_linked_select() -> Result<(), DbErr> {
862        let models = [
863            (
864                cake::Model {
865                    id: 1,
866                    name: "Blueberry Cheese Cake".into(),
867                },
868                Some(vendor::Model {
869                    id: 9,
870                    name: "Blueberry".into(),
871                }),
872            ),
873            (
874                cake::Model {
875                    id: 2,
876                    name: "Raspberry Cheese Cake".into(),
877                },
878                Some(vendor::Model {
879                    id: 10,
880                    name: "Raspberry".into(),
881                }),
882            ),
883        ];
884
885        let db = MockDatabase::new(DbBackend::Postgres)
886            .append_query_results([models.clone()])
887            .into_connection();
888
889        assert_eq!(
890            cake::Entity::find()
891                .find_also_linked(entity_linked::CakeToFillingVendor)
892                .cursor_by(cake::Column::Id)
893                .before(10)
894                .first(2)
895                .all(&db)
896                .await?,
897            models
898        );
899
900        assert_eq!(
901            db.into_transaction_log(),
902            [Transaction::many([Statement::from_sql_and_values(
903                DbBackend::Postgres,
904                [
905                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
906                    r#""r2"."id" AS "B_id", "r2"."name" AS "B_name""#,
907                    r#"FROM "cake""#,
908                    r#"LEFT JOIN "cake_filling" AS "r0" ON "cake"."id" = "r0"."cake_id""#,
909                    r#"LEFT JOIN "filling" AS "r1" ON "r0"."filling_id" = "r1"."id""#,
910                    r#"LEFT JOIN "vendor" AS "r2" ON "r1"."vendor_id" = "r2"."id""#,
911                    r#"WHERE "cake"."id" < $1 ORDER BY "cake"."id" ASC, "vendor"."id" ASC LIMIT $2"#,
912                ]
913                .join(" ")
914                .as_str(),
915                [10_i32.into(), 2_u64.into()]
916            ),])]
917        );
918
919        Ok(())
920    }
921
922    #[smol_potat::test]
923    async fn last_2_after_10_also_linked_select_desc() -> Result<(), DbErr> {
924        let mut models = [
925            (
926                cake::Model {
927                    id: 2,
928                    name: "Raspberry Cheese Cake".into(),
929                },
930                Some(vendor::Model {
931                    id: 10,
932                    name: "Raspberry".into(),
933                }),
934            ),
935            (
936                cake::Model {
937                    id: 1,
938                    name: "Blueberry Cheese Cake".into(),
939                },
940                Some(vendor::Model {
941                    id: 9,
942                    name: "Blueberry".into(),
943                }),
944            ),
945        ];
946
947        let db = MockDatabase::new(DbBackend::Postgres)
948            .append_query_results([models.clone()])
949            .into_connection();
950
951        models.reverse();
952
953        assert_eq!(
954            cake::Entity::find()
955                .find_also_linked(entity_linked::CakeToFillingVendor)
956                .cursor_by(cake::Column::Id)
957                .after(10)
958                .last(2)
959                .desc()
960                .all(&db)
961                .await?,
962            models
963        );
964
965        assert_eq!(
966            db.into_transaction_log(),
967            [Transaction::many([Statement::from_sql_and_values(
968                DbBackend::Postgres,
969                [
970                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
971                    r#""r2"."id" AS "B_id", "r2"."name" AS "B_name""#,
972                    r#"FROM "cake""#,
973                    r#"LEFT JOIN "cake_filling" AS "r0" ON "cake"."id" = "r0"."cake_id""#,
974                    r#"LEFT JOIN "filling" AS "r1" ON "r0"."filling_id" = "r1"."id""#,
975                    r#"LEFT JOIN "vendor" AS "r2" ON "r1"."vendor_id" = "r2"."id""#,
976                    r#"WHERE "cake"."id" < $1 ORDER BY "cake"."id" ASC, "vendor"."id" ASC LIMIT $2"#,
977                ]
978                .join(" ")
979                .as_str(),
980                [10_i32.into(), 2_u64.into()]
981            ),])]
982        );
983
984        Ok(())
985    }
986
987    #[smol_potat::test]
988    async fn first_2_before_10_also_linked_select_cursor_other() -> Result<(), DbErr> {
989        let models = [(
990            cake::Model {
991                id: 1,
992                name: "Blueberry Cheese Cake".into(),
993            },
994            Some(vendor::Model {
995                id: 9,
996                name: "Blueberry".into(),
997            }),
998        )];
999
1000        let db = MockDatabase::new(DbBackend::Postgres)
1001            .append_query_results([models.clone()])
1002            .into_connection();
1003
1004        assert_eq!(
1005            cake::Entity::find()
1006                .find_also_linked(entity_linked::CakeToFillingVendor)
1007                .cursor_by_other(vendor::Column::Id)
1008                .before(10)
1009                .first(2)
1010                .all(&db)
1011                .await?,
1012            models
1013        );
1014
1015        assert_eq!(
1016            db.into_transaction_log(),
1017            [Transaction::many([Statement::from_sql_and_values(
1018                DbBackend::Postgres,
1019                [
1020                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
1021                    r#""r2"."id" AS "B_id", "r2"."name" AS "B_name""#,
1022                    r#"FROM "cake""#,
1023                    r#"LEFT JOIN "cake_filling" AS "r0" ON "cake"."id" = "r0"."cake_id""#,
1024                    r#"LEFT JOIN "filling" AS "r1" ON "r0"."filling_id" = "r1"."id""#,
1025                    r#"LEFT JOIN "vendor" AS "r2" ON "r1"."vendor_id" = "r2"."id""#,
1026                    r#"WHERE "vendor"."id" < $1 ORDER BY "vendor"."id" ASC, "cake"."id" ASC LIMIT $2"#,
1027                ]
1028                .join(" ")
1029                .as_str(),
1030                [10_i32.into(), 2_u64.into()]
1031            ),])]
1032        );
1033
1034        Ok(())
1035    }
1036
1037    #[smol_potat::test]
1038    async fn last_2_after_10_also_linked_select_cursor_other_desc() -> Result<(), DbErr> {
1039        let mut models = [(
1040            cake::Model {
1041                id: 1,
1042                name: "Blueberry Cheese Cake".into(),
1043            },
1044            Some(vendor::Model {
1045                id: 9,
1046                name: "Blueberry".into(),
1047            }),
1048        )];
1049
1050        let db = MockDatabase::new(DbBackend::Postgres)
1051            .append_query_results([models.clone()])
1052            .into_connection();
1053
1054        models.reverse();
1055
1056        assert_eq!(
1057            cake::Entity::find()
1058                .find_also_linked(entity_linked::CakeToFillingVendor)
1059                .cursor_by_other(vendor::Column::Id)
1060                .after(10)
1061                .last(2)
1062                .desc()
1063                .all(&db)
1064                .await?,
1065            models
1066        );
1067
1068        assert_eq!(
1069            db.into_transaction_log(),
1070            [Transaction::many([Statement::from_sql_and_values(
1071                DbBackend::Postgres,
1072                [
1073                    r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
1074                    r#""r2"."id" AS "B_id", "r2"."name" AS "B_name""#,
1075                    r#"FROM "cake""#,
1076                    r#"LEFT JOIN "cake_filling" AS "r0" ON "cake"."id" = "r0"."cake_id""#,
1077                    r#"LEFT JOIN "filling" AS "r1" ON "r0"."filling_id" = "r1"."id""#,
1078                    r#"LEFT JOIN "vendor" AS "r2" ON "r1"."vendor_id" = "r2"."id""#,
1079                    r#"WHERE "vendor"."id" < $1 ORDER BY "vendor"."id" ASC, "cake"."id" ASC LIMIT $2"#,
1080                ]
1081                .join(" ")
1082                .as_str(),
1083                [10_i32.into(), 2_u64.into()]
1084            ),])]
1085        );
1086
1087        Ok(())
1088    }
1089
1090    #[smol_potat::test]
1091    async fn last_2_after_10() -> Result<(), DbErr> {
1092        use fruit::*;
1093
1094        let db = MockDatabase::new(DbBackend::Postgres)
1095            .append_query_results([[
1096                Model {
1097                    id: 22,
1098                    name: "Raspberry".into(),
1099                    cake_id: Some(1),
1100                },
1101                Model {
1102                    id: 21,
1103                    name: "Blueberry".into(),
1104                    cake_id: Some(1),
1105                },
1106            ]])
1107            .into_connection();
1108
1109        assert_eq!(
1110            Entity::find()
1111                .cursor_by(Column::Id)
1112                .after(10)
1113                .last(2)
1114                .all(&db)
1115                .await?,
1116            [
1117                Model {
1118                    id: 21,
1119                    name: "Blueberry".into(),
1120                    cake_id: Some(1),
1121                },
1122                Model {
1123                    id: 22,
1124                    name: "Raspberry".into(),
1125                    cake_id: Some(1),
1126                },
1127            ]
1128        );
1129
1130        assert_eq!(
1131            db.into_transaction_log(),
1132            [Transaction::many([Statement::from_sql_and_values(
1133                DbBackend::Postgres,
1134                [
1135                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id""#,
1136                    r#"FROM "fruit""#,
1137                    r#"WHERE "fruit"."id" > $1"#,
1138                    r#"ORDER BY "fruit"."id" DESC"#,
1139                    r#"LIMIT $2"#,
1140                ]
1141                .join(" ")
1142                .as_str(),
1143                [10_i32.into(), 2_u64.into()]
1144            ),])]
1145        );
1146
1147        Ok(())
1148    }
1149
1150    #[smol_potat::test]
1151    async fn first_2_before_10_desc() -> Result<(), DbErr> {
1152        use fruit::*;
1153
1154        let models = [
1155            Model {
1156                id: 22,
1157                name: "Raspberry".into(),
1158                cake_id: Some(1),
1159            },
1160            Model {
1161                id: 21,
1162                name: "Blueberry".into(),
1163                cake_id: Some(1),
1164            },
1165        ];
1166
1167        let db = MockDatabase::new(DbBackend::Postgres)
1168            .append_query_results([models.clone()])
1169            .into_connection();
1170
1171        assert_eq!(
1172            Entity::find()
1173                .cursor_by(Column::Id)
1174                .before(10)
1175                .first(2)
1176                .desc()
1177                .all(&db)
1178                .await?,
1179            models
1180        );
1181
1182        assert_eq!(
1183            db.into_transaction_log(),
1184            [Transaction::many([Statement::from_sql_and_values(
1185                DbBackend::Postgres,
1186                [
1187                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id""#,
1188                    r#"FROM "fruit""#,
1189                    r#"WHERE "fruit"."id" > $1"#,
1190                    r#"ORDER BY "fruit"."id" DESC"#,
1191                    r#"LIMIT $2"#,
1192                ]
1193                .join(" ")
1194                .as_str(),
1195                [10_i32.into(), 2_u64.into()]
1196            ),])]
1197        );
1198
1199        Ok(())
1200    }
1201
1202    #[smol_potat::test]
1203    async fn last_2_after_25_before_30() -> Result<(), DbErr> {
1204        use fruit::*;
1205
1206        let db = MockDatabase::new(DbBackend::Postgres)
1207            .append_query_results([[
1208                Model {
1209                    id: 27,
1210                    name: "Raspberry".into(),
1211                    cake_id: Some(1),
1212                },
1213                Model {
1214                    id: 26,
1215                    name: "Blueberry".into(),
1216                    cake_id: Some(1),
1217                },
1218            ]])
1219            .into_connection();
1220
1221        assert_eq!(
1222            Entity::find()
1223                .cursor_by(Column::Id)
1224                .after(25)
1225                .before(30)
1226                .last(2)
1227                .all(&db)
1228                .await?,
1229            [
1230                Model {
1231                    id: 26,
1232                    name: "Blueberry".into(),
1233                    cake_id: Some(1),
1234                },
1235                Model {
1236                    id: 27,
1237                    name: "Raspberry".into(),
1238                    cake_id: Some(1),
1239                },
1240            ]
1241        );
1242
1243        assert_eq!(
1244            db.into_transaction_log(),
1245            [Transaction::many([Statement::from_sql_and_values(
1246                DbBackend::Postgres,
1247                [
1248                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id""#,
1249                    r#"FROM "fruit""#,
1250                    r#"WHERE "fruit"."id" > $1"#,
1251                    r#"AND "fruit"."id" < $2"#,
1252                    r#"ORDER BY "fruit"."id" DESC"#,
1253                    r#"LIMIT $3"#,
1254                ]
1255                .join(" ")
1256                .as_str(),
1257                [25_i32.into(), 30_i32.into(), 2_u64.into()]
1258            ),])]
1259        );
1260
1261        Ok(())
1262    }
1263
1264    #[smol_potat::test]
1265    async fn first_2_after_30_before_25_desc() -> Result<(), DbErr> {
1266        use fruit::*;
1267
1268        let models = [
1269            Model {
1270                id: 27,
1271                name: "Raspberry".into(),
1272                cake_id: Some(1),
1273            },
1274            Model {
1275                id: 26,
1276                name: "Blueberry".into(),
1277                cake_id: Some(1),
1278            },
1279        ];
1280
1281        let db = MockDatabase::new(DbBackend::Postgres)
1282            .append_query_results([models.clone()])
1283            .into_connection();
1284
1285        assert_eq!(
1286            Entity::find()
1287                .cursor_by(Column::Id)
1288                .after(30)
1289                .before(25)
1290                .first(2)
1291                .desc()
1292                .all(&db)
1293                .await?,
1294            models
1295        );
1296
1297        assert_eq!(
1298            db.into_transaction_log(),
1299            [Transaction::many([Statement::from_sql_and_values(
1300                DbBackend::Postgres,
1301                [
1302                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id""#,
1303                    r#"FROM "fruit""#,
1304                    r#"WHERE "fruit"."id" < $1"#,
1305                    r#"AND "fruit"."id" > $2"#,
1306                    r#"ORDER BY "fruit"."id" DESC"#,
1307                    r#"LIMIT $3"#,
1308                ]
1309                .join(" ")
1310                .as_str(),
1311                [30_i32.into(), 25_i32.into(), 2_u64.into()]
1312            ),])]
1313        );
1314
1315        Ok(())
1316    }
1317
1318    mod test_entity {
1319        use crate as sea_orm;
1320        use crate::entity::prelude::*;
1321
1322        #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
1323        #[sea_orm(table_name = "example")]
1324        pub struct Model {
1325            #[sea_orm(primary_key)]
1326            pub id: i32,
1327            #[sea_orm(primary_key)]
1328            pub category: String,
1329        }
1330
1331        #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
1332        pub enum Relation {}
1333
1334        impl ActiveModelBehavior for ActiveModel {}
1335    }
1336
1337    mod xyz_entity {
1338        use crate as sea_orm;
1339        use crate::entity::prelude::*;
1340
1341        #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
1342        #[sea_orm(table_name = "m")]
1343        pub struct Model {
1344            #[sea_orm(primary_key)]
1345            pub x: i32,
1346            #[sea_orm(primary_key)]
1347            pub y: String,
1348            #[sea_orm(primary_key)]
1349            pub z: i64,
1350        }
1351
1352        #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
1353        pub enum Relation {}
1354
1355        impl ActiveModelBehavior for ActiveModel {}
1356    }
1357
1358    #[smol_potat::test]
1359    async fn composite_keys_1() -> Result<(), DbErr> {
1360        use test_entity::*;
1361
1362        let db = MockDatabase::new(DbBackend::Postgres)
1363            .append_query_results([[Model {
1364                id: 1,
1365                category: "CAT".into(),
1366            }]])
1367            .into_connection();
1368
1369        assert!(
1370            !Entity::find()
1371                .cursor_by((Column::Category, Column::Id))
1372                .first(3)
1373                .all(&db)
1374                .await?
1375                .is_empty()
1376        );
1377
1378        assert_eq!(
1379            db.into_transaction_log(),
1380            [Transaction::many([Statement::from_sql_and_values(
1381                DbBackend::Postgres,
1382                [
1383                    r#"SELECT "example"."id", "example"."category""#,
1384                    r#"FROM "example""#,
1385                    r#"ORDER BY "example"."category" ASC, "example"."id" ASC"#,
1386                    r#"LIMIT $1"#,
1387                ]
1388                .join(" ")
1389                .as_str(),
1390                [3_u64.into()]
1391            ),])]
1392        );
1393
1394        Ok(())
1395    }
1396
1397    #[smol_potat::test]
1398    async fn composite_keys_1_desc() -> Result<(), DbErr> {
1399        use test_entity::*;
1400
1401        let db = MockDatabase::new(DbBackend::Postgres)
1402            .append_query_results([[Model {
1403                id: 1,
1404                category: "CAT".into(),
1405            }]])
1406            .into_connection();
1407
1408        assert!(
1409            !Entity::find()
1410                .cursor_by((Column::Category, Column::Id))
1411                .last(3)
1412                .desc()
1413                .all(&db)
1414                .await?
1415                .is_empty()
1416        );
1417
1418        assert_eq!(
1419            db.into_transaction_log(),
1420            [Transaction::many([Statement::from_sql_and_values(
1421                DbBackend::Postgres,
1422                [
1423                    r#"SELECT "example"."id", "example"."category""#,
1424                    r#"FROM "example""#,
1425                    r#"ORDER BY "example"."category" ASC, "example"."id" ASC"#,
1426                    r#"LIMIT $1"#,
1427                ]
1428                .join(" ")
1429                .as_str(),
1430                [3_u64.into()]
1431            ),])]
1432        );
1433
1434        Ok(())
1435    }
1436
1437    #[smol_potat::test]
1438    async fn composite_keys_2() -> Result<(), DbErr> {
1439        use test_entity::*;
1440
1441        let db = MockDatabase::new(DbBackend::Postgres)
1442            .append_query_results([[Model {
1443                id: 1,
1444                category: "CAT".into(),
1445            }]])
1446            .into_connection();
1447
1448        assert!(
1449            !Entity::find()
1450                .cursor_by((Column::Category, Column::Id))
1451                .after(("A".to_owned(), 2))
1452                .first(3)
1453                .all(&db)
1454                .await?
1455                .is_empty()
1456        );
1457
1458        assert_eq!(
1459            db.into_transaction_log(),
1460            [Transaction::many([Statement::from_sql_and_values(
1461                DbBackend::Postgres,
1462                [
1463                    r#"SELECT "example"."id", "example"."category""#,
1464                    r#"FROM "example""#,
1465                    r#"WHERE ("example"."category" = $1 AND "example"."id" > $2)"#,
1466                    r#"OR "example"."category" > $3"#,
1467                    r#"ORDER BY "example"."category" ASC, "example"."id" ASC"#,
1468                    r#"LIMIT $4"#,
1469                ]
1470                .join(" ")
1471                .as_str(),
1472                [
1473                    "A".to_string().into(),
1474                    2i32.into(),
1475                    "A".to_string().into(),
1476                    3_u64.into(),
1477                ]
1478            )])]
1479        );
1480
1481        Ok(())
1482    }
1483
1484    #[smol_potat::test]
1485    async fn composite_keys_error() -> Result<(), DbErr> {
1486        use test_entity::*;
1487
1488        let db = MockDatabase::new(DbBackend::Postgres)
1489            .append_query_results([[Model {
1490                id: 1,
1491                category: "CAT".into(),
1492            }]])
1493            .into_connection();
1494
1495        let result = Entity::find()
1496            .cursor_by((Column::Category, Column::Id))
1497            .after("A".to_owned())
1498            .first(3)
1499            .all(&db)
1500            .await;
1501
1502        assert!(matches!(
1503            result,
1504            Err(DbErr::KeyArityMismatch {
1505                expected: 2,
1506                received: 1,
1507            })
1508        ));
1509
1510        Ok(())
1511    }
1512
1513    #[smol_potat::test]
1514    async fn composite_keys_2_desc() -> Result<(), DbErr> {
1515        use test_entity::*;
1516
1517        let db = MockDatabase::new(DbBackend::Postgres)
1518            .append_query_results([[Model {
1519                id: 1,
1520                category: "CAT".into(),
1521            }]])
1522            .into_connection();
1523
1524        assert!(
1525            !Entity::find()
1526                .cursor_by((Column::Category, Column::Id))
1527                .before(("A".to_owned(), 2))
1528                .last(3)
1529                .desc()
1530                .all(&db)
1531                .await?
1532                .is_empty()
1533        );
1534
1535        assert_eq!(
1536            db.into_transaction_log(),
1537            [Transaction::many([Statement::from_sql_and_values(
1538                DbBackend::Postgres,
1539                [
1540                    r#"SELECT "example"."id", "example"."category""#,
1541                    r#"FROM "example""#,
1542                    r#"WHERE ("example"."category" = $1 AND "example"."id" > $2)"#,
1543                    r#"OR "example"."category" > $3"#,
1544                    r#"ORDER BY "example"."category" ASC, "example"."id" ASC"#,
1545                    r#"LIMIT $4"#,
1546                ]
1547                .join(" ")
1548                .as_str(),
1549                [
1550                    "A".to_string().into(),
1551                    2i32.into(),
1552                    "A".to_string().into(),
1553                    3_u64.into(),
1554                ]
1555            )])]
1556        );
1557
1558        Ok(())
1559    }
1560
1561    #[smol_potat::test]
1562    async fn composite_keys_3() -> Result<(), DbErr> {
1563        use test_entity::*;
1564
1565        let db = MockDatabase::new(DbBackend::Postgres)
1566            .append_query_results([[Model {
1567                id: 1,
1568                category: "CAT".into(),
1569            }]])
1570            .into_connection();
1571
1572        assert!(
1573            !Entity::find()
1574                .cursor_by((Column::Category, Column::Id))
1575                .before(("A".to_owned(), 2))
1576                .last(3)
1577                .all(&db)
1578                .await?
1579                .is_empty()
1580        );
1581
1582        assert_eq!(
1583            db.into_transaction_log(),
1584            [Transaction::many([Statement::from_sql_and_values(
1585                DbBackend::Postgres,
1586                [
1587                    r#"SELECT "example"."id", "example"."category""#,
1588                    r#"FROM "example""#,
1589                    r#"WHERE ("example"."category" = $1 AND "example"."id" < $2)"#,
1590                    r#"OR "example"."category" < $3"#,
1591                    r#"ORDER BY "example"."category" DESC, "example"."id" DESC"#,
1592                    r#"LIMIT $4"#,
1593                ]
1594                .join(" ")
1595                .as_str(),
1596                [
1597                    "A".to_string().into(),
1598                    2i32.into(),
1599                    "A".to_string().into(),
1600                    3_u64.into(),
1601                ]
1602            )])]
1603        );
1604
1605        Ok(())
1606    }
1607
1608    #[smol_potat::test]
1609    async fn composite_keys_3_desc() -> Result<(), DbErr> {
1610        use test_entity::*;
1611
1612        let db = MockDatabase::new(DbBackend::Postgres)
1613            .append_query_results([[Model {
1614                id: 1,
1615                category: "CAT".into(),
1616            }]])
1617            .into_connection();
1618
1619        assert!(
1620            !Entity::find()
1621                .cursor_by((Column::Category, Column::Id))
1622                .after(("A".to_owned(), 2))
1623                .first(3)
1624                .desc()
1625                .all(&db)
1626                .await?
1627                .is_empty()
1628        );
1629
1630        assert_eq!(
1631            db.into_transaction_log(),
1632            [Transaction::many([Statement::from_sql_and_values(
1633                DbBackend::Postgres,
1634                [
1635                    r#"SELECT "example"."id", "example"."category""#,
1636                    r#"FROM "example""#,
1637                    r#"WHERE ("example"."category" = $1 AND "example"."id" < $2)"#,
1638                    r#"OR "example"."category" < $3"#,
1639                    r#"ORDER BY "example"."category" DESC, "example"."id" DESC"#,
1640                    r#"LIMIT $4"#,
1641                ]
1642                .join(" ")
1643                .as_str(),
1644                [
1645                    "A".to_string().into(),
1646                    2i32.into(),
1647                    "A".to_string().into(),
1648                    3_u64.into(),
1649                ]
1650            )])]
1651        );
1652
1653        Ok(())
1654    }
1655
1656    #[smol_potat::test]
1657    async fn composite_keys_4() -> Result<(), DbErr> {
1658        use xyz_entity::*;
1659
1660        let db = MockDatabase::new(DbBackend::Postgres)
1661            .append_query_results([[Model {
1662                x: 'x' as i32,
1663                y: "y".into(),
1664                z: 'z' as i64,
1665            }]])
1666            .into_connection();
1667
1668        assert!(
1669            !Entity::find()
1670                .cursor_by((Column::X, Column::Y, Column::Z))
1671                .first(4)
1672                .all(&db)
1673                .await?
1674                .is_empty()
1675        );
1676
1677        assert_eq!(
1678            db.into_transaction_log(),
1679            [Transaction::many([Statement::from_sql_and_values(
1680                DbBackend::Postgres,
1681                [
1682                    r#"SELECT "m"."x", "m"."y", "m"."z""#,
1683                    r#"FROM "m""#,
1684                    r#"ORDER BY "m"."x" ASC, "m"."y" ASC, "m"."z" ASC"#,
1685                    r#"LIMIT $1"#,
1686                ]
1687                .join(" ")
1688                .as_str(),
1689                [4_u64.into()]
1690            ),])]
1691        );
1692
1693        Ok(())
1694    }
1695
1696    #[smol_potat::test]
1697    async fn composite_keys_4_desc() -> Result<(), DbErr> {
1698        use xyz_entity::*;
1699
1700        let db = MockDatabase::new(DbBackend::Postgres)
1701            .append_query_results([[Model {
1702                x: 'x' as i32,
1703                y: "y".into(),
1704                z: 'z' as i64,
1705            }]])
1706            .into_connection();
1707
1708        assert!(
1709            !Entity::find()
1710                .cursor_by((Column::X, Column::Y, Column::Z))
1711                .last(4)
1712                .desc()
1713                .all(&db)
1714                .await?
1715                .is_empty()
1716        );
1717
1718        assert_eq!(
1719            db.into_transaction_log(),
1720            [Transaction::many([Statement::from_sql_and_values(
1721                DbBackend::Postgres,
1722                [
1723                    r#"SELECT "m"."x", "m"."y", "m"."z""#,
1724                    r#"FROM "m""#,
1725                    r#"ORDER BY "m"."x" ASC, "m"."y" ASC, "m"."z" ASC"#,
1726                    r#"LIMIT $1"#,
1727                ]
1728                .join(" ")
1729                .as_str(),
1730                [4_u64.into()]
1731            ),])]
1732        );
1733
1734        Ok(())
1735    }
1736
1737    #[smol_potat::test]
1738    async fn composite_keys_5() -> Result<(), DbErr> {
1739        use xyz_entity::*;
1740
1741        let db = MockDatabase::new(DbBackend::Postgres)
1742            .append_query_results([[Model {
1743                x: 'x' as i32,
1744                y: "y".into(),
1745                z: 'z' as i64,
1746            }]])
1747            .into_connection();
1748
1749        assert!(
1750            !Entity::find()
1751                .cursor_by((Column::X, Column::Y, Column::Z))
1752                .after(('x' as i32, "y".to_owned(), 'z' as i64))
1753                .first(4)
1754                .all(&db)
1755                .await?
1756                .is_empty()
1757        );
1758
1759        assert_eq!(
1760            db.into_transaction_log(),
1761            [Transaction::many([Statement::from_sql_and_values(
1762                DbBackend::Postgres,
1763                [
1764                    r#"SELECT "m"."x", "m"."y", "m"."z""#,
1765                    r#"FROM "m""#,
1766                    r#"WHERE ("m"."x" = $1 AND "m"."y" = $2 AND "m"."z" > $3)"#,
1767                    r#"OR ("m"."x" = $4 AND "m"."y" > $5)"#,
1768                    r#"OR "m"."x" > $6"#,
1769                    r#"ORDER BY "m"."x" ASC, "m"."y" ASC, "m"."z" ASC"#,
1770                    r#"LIMIT $7"#,
1771                ]
1772                .join(" ")
1773                .as_str(),
1774                [
1775                    ('x' as i32).into(),
1776                    "y".into(),
1777                    ('z' as i64).into(),
1778                    ('x' as i32).into(),
1779                    "y".into(),
1780                    ('x' as i32).into(),
1781                    4_u64.into(),
1782                ]
1783            ),])]
1784        );
1785
1786        Ok(())
1787    }
1788
1789    #[smol_potat::test]
1790    async fn composite_keys_5_desc() -> Result<(), DbErr> {
1791        use xyz_entity::*;
1792
1793        let db = MockDatabase::new(DbBackend::Postgres)
1794            .append_query_results([[Model {
1795                x: 'x' as i32,
1796                y: "y".into(),
1797                z: 'z' as i64,
1798            }]])
1799            .into_connection();
1800
1801        assert!(
1802            !Entity::find()
1803                .cursor_by((Column::X, Column::Y, Column::Z))
1804                .before(('x' as i32, "y".to_owned(), 'z' as i64))
1805                .last(4)
1806                .desc()
1807                .all(&db)
1808                .await?
1809                .is_empty()
1810        );
1811
1812        assert_eq!(
1813            db.into_transaction_log(),
1814            [Transaction::many([Statement::from_sql_and_values(
1815                DbBackend::Postgres,
1816                [
1817                    r#"SELECT "m"."x", "m"."y", "m"."z""#,
1818                    r#"FROM "m""#,
1819                    r#"WHERE ("m"."x" = $1 AND "m"."y" = $2 AND "m"."z" > $3)"#,
1820                    r#"OR ("m"."x" = $4 AND "m"."y" > $5)"#,
1821                    r#"OR "m"."x" > $6"#,
1822                    r#"ORDER BY "m"."x" ASC, "m"."y" ASC, "m"."z" ASC"#,
1823                    r#"LIMIT $7"#,
1824                ]
1825                .join(" ")
1826                .as_str(),
1827                [
1828                    ('x' as i32).into(),
1829                    "y".into(),
1830                    ('z' as i64).into(),
1831                    ('x' as i32).into(),
1832                    "y".into(),
1833                    ('x' as i32).into(),
1834                    4_u64.into(),
1835                ]
1836            ),])]
1837        );
1838
1839        Ok(())
1840    }
1841
1842    mod composite_entity {
1843        use crate as sea_orm;
1844        use crate::entity::prelude::*;
1845
1846        #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
1847        #[sea_orm(table_name = "t")]
1848        pub struct Model {
1849            #[sea_orm(primary_key)]
1850            pub col_1: String,
1851            #[sea_orm(primary_key)]
1852            pub col_2: String,
1853            #[sea_orm(primary_key)]
1854            pub col_3: String,
1855            #[sea_orm(primary_key)]
1856            pub col_4: String,
1857            #[sea_orm(primary_key)]
1858            pub col_5: String,
1859            #[sea_orm(primary_key)]
1860            pub col_6: String,
1861            #[sea_orm(primary_key)]
1862            pub col_7: String,
1863            #[sea_orm(primary_key)]
1864            pub col_8: String,
1865            #[sea_orm(primary_key)]
1866            pub col_9: String,
1867            #[sea_orm(primary_key)]
1868            pub col_10: String,
1869            #[sea_orm(primary_key)]
1870            pub col_11: String,
1871            #[sea_orm(primary_key)]
1872            pub col_12: String,
1873        }
1874
1875        #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
1876        pub enum Relation {}
1877
1878        impl ActiveModelBehavior for ActiveModel {}
1879    }
1880
1881    #[smol_potat::test]
1882    async fn cursor_by_many() -> Result<(), DbErr> {
1883        use composite_entity::*;
1884
1885        let base_sql = [
1886            r#"SELECT "t"."col_1", "t"."col_2", "t"."col_3", "t"."col_4", "t"."col_5", "t"."col_6", "t"."col_7", "t"."col_8", "t"."col_9", "t"."col_10", "t"."col_11", "t"."col_12""#,
1887            r#"FROM "t" WHERE"#,
1888        ].join(" ");
1889
1890        assert_eq!(
1891            DbBackend::Postgres.build(&
1892                Entity::find()
1893                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4))
1894                .after(("val_1", "val_2", "val_3", "val_4")).apply_limit().apply_order_by().apply_filters()?.query
1895            ).to_string(),
1896            format!("{base_sql} {}", [
1897                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#,
1898                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#,
1899                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#,
1900                r#"OR "t"."col_1" > 'val_1'"#,
1901                r#"ORDER BY "t"."col_1" ASC, "t"."col_2" ASC, "t"."col_3" ASC, "t"."col_4" ASC"#,
1902            ].join(" "))
1903        );
1904
1905        assert_eq!(
1906            DbBackend::Postgres.build(&
1907                Entity::find()
1908                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5))
1909                .after(("val_1", "val_2", "val_3", "val_4", "val_5")).apply_limit().apply_order_by().apply_filters()?
1910                .query
1911            ).to_string(),
1912            format!("{base_sql} {}", [
1913                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" > 'val_5')"#,
1914                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#,
1915                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#,
1916                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#,
1917                r#"OR "t"."col_1" > 'val_1'"#,
1918                r#"ORDER BY "t"."col_1" ASC, "t"."col_2" ASC, "t"."col_3" ASC, "t"."col_4" ASC, "t"."col_5" ASC"#,
1919            ].join(" "))
1920        );
1921
1922        assert_eq!(
1923            DbBackend::Postgres.build(&
1924                Entity::find()
1925                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6))
1926                .after(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6")).apply_limit().apply_order_by().apply_filters()?
1927                .query
1928            ).to_string(),
1929            format!("{base_sql} {}", [
1930                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" > 'val_6')"#,
1931                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" > 'val_5')"#,
1932                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#,
1933                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#,
1934                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#,
1935                r#"OR "t"."col_1" > 'val_1'"#,
1936                r#"ORDER BY "t"."col_1" ASC, "t"."col_2" ASC, "t"."col_3" ASC, "t"."col_4" ASC, "t"."col_5" ASC, "t"."col_6" ASC"#,
1937            ].join(" "))
1938        );
1939
1940        assert_eq!(
1941            DbBackend::Postgres.build(&
1942                Entity::find()
1943                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7))
1944                .before(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7")).apply_limit().apply_order_by().apply_filters()?
1945                .query
1946            ).to_string(),
1947            format!("{base_sql} {}", [
1948                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#,
1949                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#,
1950                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#,
1951                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#,
1952                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#,
1953                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#,
1954                r#"OR "t"."col_1" < 'val_1'"#,
1955                r#"ORDER BY "t"."col_1" ASC, "t"."col_2" ASC, "t"."col_3" ASC, "t"."col_4" ASC, "t"."col_5" ASC, "t"."col_6" ASC, "t"."col_7" ASC"#,
1956            ].join(" "))
1957        );
1958
1959        assert_eq!(
1960            DbBackend::Postgres.build(&
1961                Entity::find()
1962                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7, Column::Col8))
1963                .before(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7", "val_8")).apply_limit().apply_order_by().apply_filters()?
1964                .query
1965            ).to_string(),
1966            format!("{base_sql} {}", [
1967                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" < 'val_8')"#,
1968                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#,
1969                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#,
1970                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#,
1971                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#,
1972                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#,
1973                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#,
1974                r#"OR "t"."col_1" < 'val_1'"#,
1975                r#"ORDER BY "t"."col_1" ASC, "t"."col_2" ASC, "t"."col_3" ASC, "t"."col_4" ASC, "t"."col_5" ASC, "t"."col_6" ASC, "t"."col_7" ASC, "t"."col_8" ASC"#,
1976            ].join(" "))
1977        );
1978
1979        assert_eq!(
1980            DbBackend::Postgres.build(&
1981                Entity::find()
1982                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7, Column::Col8, Column::Col9))
1983                .before(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7", "val_8", "val_9")).apply_limit().apply_order_by().apply_filters()?
1984                .query
1985            ).to_string(),
1986            format!("{base_sql} {}", [
1987                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" = 'val_8' AND "t"."col_9" < 'val_9')"#,
1988                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" < 'val_8')"#,
1989                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#,
1990                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#,
1991                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#,
1992                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#,
1993                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#,
1994                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#,
1995                r#"OR "t"."col_1" < 'val_1'"#,
1996                r#"ORDER BY "t"."col_1" ASC, "t"."col_2" ASC, "t"."col_3" ASC, "t"."col_4" ASC, "t"."col_5" ASC, "t"."col_6" ASC, "t"."col_7" ASC, "t"."col_8" ASC, "t"."col_9" ASC"#,
1997            ].join(" "))
1998        );
1999
2000        Ok(())
2001    }
2002
2003    #[smol_potat::test]
2004    async fn cursor_by_many_desc() -> Result<(), DbErr> {
2005        use composite_entity::*;
2006
2007        let base_sql = [
2008            r#"SELECT "t"."col_1", "t"."col_2", "t"."col_3", "t"."col_4", "t"."col_5", "t"."col_6", "t"."col_7", "t"."col_8", "t"."col_9", "t"."col_10", "t"."col_11", "t"."col_12""#,
2009            r#"FROM "t" WHERE"#,
2010        ].join(" ");
2011
2012        assert_eq!(
2013            DbBackend::Postgres.build(&
2014                Entity::find()
2015                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4))
2016                .before(("val_1", "val_2", "val_3", "val_4")).desc().apply_limit().apply_order_by().apply_filters()?.query
2017            ).to_string(),
2018            format!("{base_sql} {}", [
2019                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#,
2020                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#,
2021                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#,
2022                r#"OR "t"."col_1" > 'val_1'"#,
2023                r#"ORDER BY "t"."col_1" DESC, "t"."col_2" DESC, "t"."col_3" DESC, "t"."col_4" DESC"#,
2024            ].join(" "))
2025        );
2026
2027        assert_eq!(
2028            DbBackend::Postgres.build(&
2029                Entity::find()
2030                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5))
2031                .before(("val_1", "val_2", "val_3", "val_4", "val_5")).desc().apply_limit().apply_order_by().apply_filters()?
2032                .query
2033            ).to_string(),
2034            format!("{base_sql} {}", [
2035                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" > 'val_5')"#,
2036                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#,
2037                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#,
2038                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#,
2039                r#"OR "t"."col_1" > 'val_1'"#,
2040                r#"ORDER BY "t"."col_1" DESC, "t"."col_2" DESC, "t"."col_3" DESC, "t"."col_4" DESC, "t"."col_5" DESC"#,
2041            ].join(" "))
2042        );
2043
2044        assert_eq!(
2045            DbBackend::Postgres.build(&
2046                Entity::find()
2047                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6))
2048                .before(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6")).desc().apply_limit().apply_order_by().apply_filters()?
2049                .query
2050            ).to_string(),
2051            format!("{base_sql} {}", [
2052                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" > 'val_6')"#,
2053                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" > 'val_5')"#,
2054                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" > 'val_4')"#,
2055                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" > 'val_3')"#,
2056                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" > 'val_2')"#,
2057                r#"OR "t"."col_1" > 'val_1'"#,
2058                r#"ORDER BY "t"."col_1" DESC, "t"."col_2" DESC, "t"."col_3" DESC, "t"."col_4" DESC, "t"."col_5" DESC, "t"."col_6" DESC"#,
2059            ].join(" "))
2060        );
2061
2062        assert_eq!(
2063            DbBackend::Postgres.build(&
2064                Entity::find()
2065                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7))
2066                .after(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7")).desc().apply_limit().apply_order_by().apply_filters()?
2067                .query
2068            ).to_string(),
2069            format!("{base_sql} {}", [
2070                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#,
2071                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#,
2072                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#,
2073                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#,
2074                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#,
2075                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#,
2076                r#"OR "t"."col_1" < 'val_1'"#,
2077                r#"ORDER BY "t"."col_1" DESC, "t"."col_2" DESC, "t"."col_3" DESC, "t"."col_4" DESC, "t"."col_5" DESC, "t"."col_6" DESC, "t"."col_7" DESC"#,
2078            ].join(" "))
2079        );
2080
2081        assert_eq!(
2082            DbBackend::Postgres.build(&
2083                Entity::find()
2084                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7, Column::Col8))
2085                .after(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7", "val_8")).desc().apply_limit().apply_order_by().apply_filters()?
2086                .query
2087            ).to_string(),
2088            format!("{base_sql} {}", [
2089                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" < 'val_8')"#,
2090                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#,
2091                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#,
2092                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#,
2093                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#,
2094                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#,
2095                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#,
2096                r#"OR "t"."col_1" < 'val_1'"#,
2097                r#"ORDER BY "t"."col_1" DESC, "t"."col_2" DESC, "t"."col_3" DESC, "t"."col_4" DESC, "t"."col_5" DESC, "t"."col_6" DESC, "t"."col_7" DESC, "t"."col_8" DESC"#,
2098            ].join(" "))
2099        );
2100
2101        assert_eq!(
2102            DbBackend::Postgres.build(&
2103                Entity::find()
2104                .cursor_by((Column::Col1, Column::Col2, Column::Col3, Column::Col4, Column::Col5, Column::Col6, Column::Col7, Column::Col8, Column::Col9))
2105                .after(("val_1", "val_2", "val_3", "val_4", "val_5", "val_6", "val_7", "val_8", "val_9")).desc().apply_limit().apply_order_by().apply_filters()?
2106                .query
2107            ).to_string(),
2108            format!("{base_sql} {}", [
2109                r#"("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" = 'val_8' AND "t"."col_9" < 'val_9')"#,
2110                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" = 'val_7' AND "t"."col_8" < 'val_8')"#,
2111                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" = 'val_6' AND "t"."col_7" < 'val_7')"#,
2112                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" = 'val_5' AND "t"."col_6" < 'val_6')"#,
2113                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" = 'val_4' AND "t"."col_5" < 'val_5')"#,
2114                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" = 'val_3' AND "t"."col_4" < 'val_4')"#,
2115                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" = 'val_2' AND "t"."col_3" < 'val_3')"#,
2116                r#"OR ("t"."col_1" = 'val_1' AND "t"."col_2" < 'val_2')"#,
2117                r#"OR "t"."col_1" < 'val_1'"#,
2118                r#"ORDER BY "t"."col_1" DESC, "t"."col_2" DESC, "t"."col_3" DESC, "t"."col_4" DESC, "t"."col_5" DESC, "t"."col_6" DESC, "t"."col_7" DESC, "t"."col_8" DESC, "t"."col_9" DESC"#,
2119            ].join(" "))
2120        );
2121
2122        Ok(())
2123    }
2124
2125    mod test_base_entity {
2126        use crate as sea_orm;
2127        use crate::entity::prelude::*;
2128
2129        #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
2130        #[sea_orm(table_name = "base")]
2131        pub struct Model {
2132            #[sea_orm(primary_key)]
2133            pub id: i32,
2134            #[sea_orm(primary_key)]
2135            pub name: String,
2136        }
2137
2138        #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
2139        pub enum Relation {
2140            #[sea_orm(has_many = "super::test_related_entity::Entity")]
2141            TestRelatedEntity,
2142        }
2143
2144        impl Related<super::test_related_entity::Entity> for Entity {
2145            fn to() -> RelationDef {
2146                Relation::TestRelatedEntity.def()
2147            }
2148        }
2149
2150        impl ActiveModelBehavior for ActiveModel {}
2151    }
2152
2153    mod test_related_entity {
2154        use super::test_base_entity;
2155        use crate as sea_orm;
2156        use crate::entity::prelude::*;
2157
2158        #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
2159        #[sea_orm(table_name = "related")]
2160        pub struct Model {
2161            #[sea_orm(primary_key)]
2162            pub id: i32,
2163            #[sea_orm(primary_key)]
2164            pub name: String,
2165            pub test_id: i32,
2166        }
2167
2168        #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
2169        pub enum Relation {
2170            #[sea_orm(
2171                belongs_to = "test_base_entity::Entity",
2172                from = "Column::TestId",
2173                to = "super::test_base_entity::Column::Id"
2174            )]
2175            TestBaseEntity,
2176        }
2177
2178        impl Related<super::test_base_entity::Entity> for Entity {
2179            fn to() -> RelationDef {
2180                Relation::TestBaseEntity.def()
2181            }
2182        }
2183
2184        impl ActiveModelBehavior for ActiveModel {}
2185    }
2186
2187    #[smol_potat::test]
2188    async fn related_composite_keys_1() -> Result<(), DbErr> {
2189        let db = MockDatabase::new(DbBackend::Postgres)
2190            .append_query_results([[(
2191                test_base_entity::Model {
2192                    id: 1,
2193                    name: "CAT".into(),
2194                },
2195                test_related_entity::Model {
2196                    id: 1,
2197                    name: "CATE".into(),
2198                    test_id: 1,
2199                },
2200            )]])
2201            .into_connection();
2202
2203        assert!(
2204            !test_base_entity::Entity::find()
2205                .find_also_related(test_related_entity::Entity)
2206                .cursor_by((test_base_entity::Column::Id, test_base_entity::Column::Name))
2207                .first(1)
2208                .all(&db)
2209                .await?
2210                .is_empty()
2211        );
2212
2213        assert_eq!(
2214            db.into_transaction_log(),
2215            [Transaction::many([Statement::from_sql_and_values(
2216                DbBackend::Postgres,
2217                [
2218                    r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
2219                    r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
2220                    r#"FROM "base""#,
2221                    r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
2222                    r#"ORDER BY "base"."id" ASC, "base"."name" ASC, "related"."id" ASC, "related"."name" ASC LIMIT $1"#,
2223                ]
2224                .join(" ")
2225                .as_str(),
2226                [1_u64.into()]
2227            ),])]
2228        );
2229
2230        Ok(())
2231    }
2232
2233    #[smol_potat::test]
2234    async fn related_composite_keys_1_desc() -> Result<(), DbErr> {
2235        let db = MockDatabase::new(DbBackend::Postgres)
2236            .append_query_results([[(
2237                test_base_entity::Model {
2238                    id: 1,
2239                    name: "CAT".into(),
2240                },
2241                test_related_entity::Model {
2242                    id: 1,
2243                    name: "CATE".into(),
2244                    test_id: 1,
2245                },
2246            )]])
2247            .into_connection();
2248
2249        assert!(
2250            !test_base_entity::Entity::find()
2251                .find_also_related(test_related_entity::Entity)
2252                .cursor_by((test_base_entity::Column::Id, test_base_entity::Column::Name))
2253                .last(1)
2254                .desc()
2255                .all(&db)
2256                .await?
2257                .is_empty()
2258        );
2259
2260        assert_eq!(
2261            db.into_transaction_log(),
2262            [Transaction::many([Statement::from_sql_and_values(
2263                DbBackend::Postgres,
2264                [
2265                    r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
2266                    r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
2267                    r#"FROM "base""#,
2268                    r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
2269                    r#"ORDER BY "base"."id" ASC, "base"."name" ASC, "related"."id" ASC, "related"."name" ASC LIMIT $1"#,
2270                ]
2271                .join(" ")
2272                .as_str(),
2273                [1_u64.into()]
2274            ),])]
2275        );
2276
2277        Ok(())
2278    }
2279
2280    #[smol_potat::test]
2281    async fn related_composite_keys_2() -> Result<(), DbErr> {
2282        let db = MockDatabase::new(DbBackend::Postgres)
2283            .append_query_results([[(
2284                test_base_entity::Model {
2285                    id: 1,
2286                    name: "CAT".into(),
2287                },
2288                test_related_entity::Model {
2289                    id: 1,
2290                    name: "CATE".into(),
2291                    test_id: 1,
2292                },
2293            )]])
2294            .into_connection();
2295
2296        assert!(
2297            !test_base_entity::Entity::find()
2298                .find_also_related(test_related_entity::Entity)
2299                .cursor_by((test_base_entity::Column::Id, test_base_entity::Column::Name))
2300                .after((1, "C".to_string()))
2301                .first(2)
2302                .all(&db)
2303                .await?
2304                .is_empty()
2305        );
2306
2307        assert_eq!(
2308            db.into_transaction_log(),
2309            [Transaction::many([Statement::from_sql_and_values(
2310                DbBackend::Postgres,
2311                [
2312                    r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
2313                    r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
2314                    r#"FROM "base""#,
2315                    r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
2316                    r#"WHERE ("base"."id" = $1 AND "base"."name" > $2) OR "base"."id" > $3"#,
2317                    r#"ORDER BY "base"."id" ASC, "base"."name" ASC, "related"."id" ASC, "related"."name" ASC LIMIT $4"#,
2318                ]
2319                .join(" ")
2320                .as_str(),
2321                [
2322                    1_i32.into(),
2323                    "C".into(),
2324                    1_i32.into(),
2325                    2_u64.into(),
2326                ]
2327            ),])]
2328        );
2329
2330        Ok(())
2331    }
2332
2333    #[smol_potat::test]
2334    async fn related_composite_keys_2_desc() -> Result<(), DbErr> {
2335        let db = MockDatabase::new(DbBackend::Postgres)
2336            .append_query_results([[(
2337                test_base_entity::Model {
2338                    id: 1,
2339                    name: "CAT".into(),
2340                },
2341                test_related_entity::Model {
2342                    id: 1,
2343                    name: "CATE".into(),
2344                    test_id: 1,
2345                },
2346            )]])
2347            .into_connection();
2348
2349        assert!(
2350            !test_base_entity::Entity::find()
2351                .find_also_related(test_related_entity::Entity)
2352                .cursor_by((test_base_entity::Column::Id, test_base_entity::Column::Name))
2353                .before((1, "C".to_string()))
2354                .last(2)
2355                .desc()
2356                .all(&db)
2357                .await?
2358                .is_empty()
2359        );
2360
2361        assert_eq!(
2362            db.into_transaction_log(),
2363            [Transaction::many([Statement::from_sql_and_values(
2364                DbBackend::Postgres,
2365                [
2366                    r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
2367                    r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
2368                    r#"FROM "base""#,
2369                    r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
2370                    r#"WHERE ("base"."id" = $1 AND "base"."name" > $2) OR "base"."id" > $3"#,
2371                    r#"ORDER BY "base"."id" ASC, "base"."name" ASC, "related"."id" ASC, "related"."name" ASC LIMIT $4"#,
2372                ]
2373                .join(" ")
2374                .as_str(),
2375                [
2376                    1_i32.into(),
2377                    "C".into(),
2378                    1_i32.into(),
2379                    2_u64.into(),
2380                ]
2381            ),])]
2382        );
2383
2384        Ok(())
2385    }
2386
2387    #[smol_potat::test]
2388    async fn related_composite_keys_3() -> Result<(), DbErr> {
2389        let db = MockDatabase::new(DbBackend::Postgres)
2390            .append_query_results([[(
2391                test_base_entity::Model {
2392                    id: 1,
2393                    name: "CAT".into(),
2394                },
2395                test_related_entity::Model {
2396                    id: 1,
2397                    name: "CATE".into(),
2398                    test_id: 1,
2399                },
2400            )]])
2401            .into_connection();
2402
2403        assert!(
2404            !test_base_entity::Entity::find()
2405                .find_also_related(test_related_entity::Entity)
2406                .cursor_by_other((
2407                    test_related_entity::Column::Id,
2408                    test_related_entity::Column::Name
2409                ))
2410                .after((1, "CAT".to_string()))
2411                .first(2)
2412                .all(&db)
2413                .await?
2414                .is_empty()
2415        );
2416
2417        assert_eq!(
2418            db.into_transaction_log(),
2419            [Transaction::many([Statement::from_sql_and_values(
2420                DbBackend::Postgres,
2421                [
2422                    r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
2423                    r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
2424                    r#"FROM "base""#,
2425                    r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
2426                    r#"WHERE ("related"."id" = $1 AND "related"."name" > $2) OR "related"."id" > $3"#,
2427                    r#"ORDER BY "related"."id" ASC, "related"."name" ASC, "base"."id" ASC, "base"."name" ASC LIMIT $4"#,
2428                ]
2429                .join(" ")
2430                .as_str(),
2431                [
2432                    1_i32.into(),
2433                    "CAT".into(),
2434                    1_i32.into(),
2435                    2_u64.into(),
2436                ]
2437            ),])]
2438        );
2439
2440        Ok(())
2441    }
2442
2443    #[smol_potat::test]
2444    async fn related_composite_keys_3_desc() -> Result<(), DbErr> {
2445        let db = MockDatabase::new(DbBackend::Postgres)
2446            .append_query_results([[(
2447                test_base_entity::Model {
2448                    id: 1,
2449                    name: "CAT".into(),
2450                },
2451                test_related_entity::Model {
2452                    id: 1,
2453                    name: "CATE".into(),
2454                    test_id: 1,
2455                },
2456            )]])
2457            .into_connection();
2458
2459        assert!(
2460            !test_base_entity::Entity::find()
2461                .find_also_related(test_related_entity::Entity)
2462                .cursor_by_other((
2463                    test_related_entity::Column::Id,
2464                    test_related_entity::Column::Name
2465                ))
2466                .before((1, "CAT".to_string()))
2467                .last(2)
2468                .desc()
2469                .all(&db)
2470                .await?
2471                .is_empty()
2472        );
2473
2474        assert_eq!(
2475            db.into_transaction_log(),
2476            [Transaction::many([Statement::from_sql_and_values(
2477                DbBackend::Postgres,
2478                [
2479                    r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
2480                    r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
2481                    r#"FROM "base""#,
2482                    r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
2483                    r#"WHERE ("related"."id" = $1 AND "related"."name" > $2) OR "related"."id" > $3"#,
2484                    r#"ORDER BY "related"."id" ASC, "related"."name" ASC, "base"."id" ASC, "base"."name" ASC LIMIT $4"#,
2485                ]
2486                .join(" ")
2487                .as_str(),
2488                [
2489                    1_i32.into(),
2490                    "CAT".into(),
2491                    1_i32.into(),
2492                    2_u64.into(),
2493                ]
2494            ),])]
2495        );
2496
2497        Ok(())
2498    }
2499}