Add cursor pagination desc order (#2037)

* feat: add cursor pagination desc order

* feat: add cursor pagination desc order

* test: add cursor integration tests

* test: add cursor integration tests

* test: add query cursor_by tests

* test: add query cursor_by tests

* test: add query cursor_by tests

* test: add query cursor_by tests

* test: add query cursor_by tests

* test: add query cursor_by sort tests

* chore: remove unused import
This commit is contained in:
Maksim Zaborovskiy 2024-01-14 16:23:55 +01:00 committed by GitHub
parent 5f9c450027
commit c5adb9d1d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 1420 additions and 31 deletions

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ use serde_json::json;
feature = "sqlx-sqlite", feature = "sqlx-sqlite",
feature = "sqlx-postgres" feature = "sqlx-postgres"
))] ))]
async fn main() -> Result<(), DbErr> { async fn cursor_tests() -> Result<(), DbErr> {
let ctx = TestContext::new("cursor_tests").await; let ctx = TestContext::new("cursor_tests").await;
create_tables(&ctx.db).await?; create_tables(&ctx.db).await?;
create_insert_default(&ctx.db).await?; create_insert_default(&ctx.db).await?;
@ -103,6 +103,54 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
] ]
); );
// Before 5 DESC, i.e. id > 5
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5).desc();
assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 10 },
Model { id: 9 },
Model { id: 8 },
Model { id: 7 },
]
);
assert_eq!(
cursor.first(5).all(db).await?,
[
Model { id: 10 },
Model { id: 9 },
Model { id: 8 },
Model { id: 7 },
Model { id: 6 },
]
);
assert_eq!(
cursor.last(4).all(db).await?,
[
Model { id: 9 },
Model { id: 8 },
Model { id: 7 },
Model { id: 6 },
]
);
assert_eq!(
cursor.last(5).all(db).await?,
[
Model { id: 10 },
Model { id: 9 },
Model { id: 8 },
Model { id: 7 },
Model { id: 6 },
]
);
// After 5, i.e. id > 5 // After 5, i.e. id > 5
let mut cursor = Entity::find().cursor_by(Column::Id); let mut cursor = Entity::find().cursor_by(Column::Id);
@ -173,6 +221,62 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
] ]
); );
// After 5 DESC, i.e. id < 5
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();
assert_eq!(
cursor.first(3).all(db).await?,
[Model { id: 4 }, Model { id: 3 }, Model { id: 2 },]
);
assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
assert_eq!(
cursor.first(5).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
assert_eq!(
cursor.last(3).all(db).await?,
[Model { id: 3 }, Model { id: 2 }, Model { id: 1 },]
);
assert_eq!(
cursor.last(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
assert_eq!(
cursor.last(5).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
// Between 5 and 8, i.e. id > 5 AND id < 8 // Between 5 and 8, i.e. id > 5 AND id < 8
let mut cursor = Entity::find().cursor_by(Column::Id); let mut cursor = Entity::find().cursor_by(Column::Id);
@ -203,6 +307,64 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
[Model { id: 6 }, Model { id: 7 }] [Model { id: 6 }, Model { id: 7 }]
); );
// Between 8 and 5 DESC, i.e. id < 8 AND id > 5
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(8).before(5).desc();
assert_eq!(cursor.first(1).all(db).await?, [Model { id: 7 }]);
assert_eq!(
cursor.first(2).all(db).await?,
[Model { id: 7 }, Model { id: 6 }]
);
assert_eq!(
cursor.first(3).all(db).await?,
[Model { id: 7 }, Model { id: 6 }]
);
assert_eq!(cursor.last(1).all(db).await?, [Model { id: 6 }]);
assert_eq!(
cursor.last(2).all(db).await?,
[Model { id: 7 }, Model { id: 6 }]
);
assert_eq!(
cursor.last(3).all(db).await?,
[Model { id: 7 }, Model { id: 6 }]
);
// Ensure asc/desc order can be changed
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.first(2);
assert_eq!(cursor.all(db).await?, [Model { id: 1 }, Model { id: 2 },]);
assert_eq!(
cursor.asc().all(db).await?,
[Model { id: 1 }, Model { id: 2 },]
);
assert_eq!(
cursor.desc().all(db).await?,
[Model { id: 10 }, Model { id: 9 },]
);
assert_eq!(
cursor.asc().all(db).await?,
[Model { id: 1 }, Model { id: 2 },]
);
assert_eq!(
cursor.desc().all(db).await?,
[Model { id: 10 }, Model { id: 9 },]
);
// Fetch custom struct // Fetch custom struct
#[derive(FromQueryResult, Debug, PartialEq, Clone)] #[derive(FromQueryResult, Debug, PartialEq, Clone)]
@ -210,6 +372,10 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
id: i32, id: i32,
} }
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).before(8);
let mut cursor = cursor.into_model::<Row>(); let mut cursor = cursor.into_model::<Row>();
assert_eq!( assert_eq!(
@ -222,8 +388,30 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
[Row { id: 6 }, Row { id: 7 }] [Row { id: 6 }, Row { id: 7 }]
); );
// Fetch custom struct desc
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(8).before(5).desc();
let mut cursor = cursor.into_model::<Row>();
assert_eq!(
cursor.first(2).all(db).await?,
[Row { id: 7 }, Row { id: 6 }]
);
assert_eq!(
cursor.first(3).all(db).await?,
[Row { id: 7 }, Row { id: 6 }]
);
// Fetch JSON value // Fetch JSON value
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).before(8);
let mut cursor = cursor.into_json(); let mut cursor = cursor.into_json();
assert_eq!( assert_eq!(
@ -275,6 +463,54 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
] ]
); );
// Fetch JSON value desc
let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(8).before(5).desc();
let mut cursor = cursor.into_json();
assert_eq!(
cursor.first(2).all(db).await?,
[json!({ "id": 7 }), json!({ "id": 6 })]
);
assert_eq!(
cursor.first(3).all(db).await?,
[json!({ "id": 7 }), json!({ "id": 6 })]
);
let mut cursor = cursor.into_partial_model::<PartialRow>();
assert_eq!(
cursor.first(2).all(db).await?,
[
PartialRow {
id: 7,
id_shifted: 1007,
},
PartialRow {
id: 6,
id_shifted: 1006,
}
]
);
assert_eq!(
cursor.first(3).all(db).await?,
[
PartialRow {
id: 7,
id_shifted: 1007,
},
PartialRow {
id: 6,
id_shifted: 1006,
},
]
);
Ok(()) Ok(())
} }
@ -387,6 +623,17 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
[bakery(1), bakery(2), bakery(3), bakery(4),] [bakery(1), bakery(2), bakery(3), bakery(4),]
); );
assert_eq!(
bakery::Entity::find()
.cursor_by(bakery::Column::Id)
.before(5)
.first(4)
.desc()
.all(db)
.await?,
[bakery(10), bakery(9), bakery(8), bakery(7),]
);
assert_eq!( assert_eq!(
bakery::Entity::find() bakery::Entity::find()
.find_also_related(Baker) .find_also_related(Baker)
@ -411,6 +658,31 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
] ]
); );
assert_eq!(
bakery::Entity::find()
.find_also_related(Baker)
.cursor_by(bakery::Column::Id)
.after(5)
.last(20)
.desc()
.all(db)
.await?,
[
(bakery(4), Some(baker('X'))),
(bakery(4), Some(baker('N'))),
(bakery(4), Some(baker('D'))),
(bakery(3), Some(baker('W'))),
(bakery(3), Some(baker('M'))),
(bakery(3), Some(baker('C'))),
(bakery(2), Some(baker('V'))),
(bakery(2), Some(baker('L'))),
(bakery(2), Some(baker('B'))),
(bakery(1), Some(baker('U'))),
(bakery(1), Some(baker('K'))),
(bakery(1), Some(baker('A'))),
]
);
assert_eq!( assert_eq!(
bakery::Entity::find() bakery::Entity::find()
.find_also_related(Baker) .find_also_related(Baker)
@ -427,6 +699,41 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
] ]
); );
assert_eq!(
bakery::Entity::find()
.find_also_related(Baker)
.cursor_by(bakery::Column::Id)
.after(5)
.last(4)
.desc()
.all(db)
.await?,
[
(bakery(2), Some(baker('B'))),
(bakery(1), Some(baker('U'))),
(bakery(1), Some(baker('K'))),
(bakery(1), Some(baker('A'))),
]
);
// since "10" is before "2" lexicologically, it return that first
assert_eq!(
bakery::Entity::find()
.find_also_related(Baker)
.cursor_by(bakery::Column::Name)
.after("3")
.last(4)
.desc()
.all(db)
.await?,
[
(bakery(10), Some(baker('J'))),
(bakery(1), Some(baker('U'))),
(bakery(1), Some(baker('K'))),
(bakery(1), Some(baker('A'))),
]
);
// since "10" is before "2" lexicologically, it return that first // since "10" is before "2" lexicologically, it return that first
assert_eq!( assert_eq!(
bakery::Entity::find() bakery::Entity::find()
@ -444,6 +751,24 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
] ]
); );
// since "10" is before "2" lexicologically, it return that first
assert_eq!(
bakery::Entity::find()
.find_also_related(Baker)
.cursor_by(bakery::Column::Name)
.after("3")
.last(4)
.desc()
.all(db)
.await?,
[
(bakery(10), Some(baker('J'))),
(bakery(1), Some(baker('U'))),
(bakery(1), Some(baker('K'))),
(bakery(1), Some(baker('A'))),
]
);
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs { enum QueryAs {
CakeId, CakeId,
@ -473,6 +798,29 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
] ]
); );
assert_eq!(
cake::Entity::find()
.find_also_related(Baker)
.select_only()
.column_as(cake::Column::Id, QueryAs::CakeId)
.column_as(cake::Column::Name, QueryAs::CakeName)
.column_as(baker::Column::Name, QueryAs::BakerName)
.cursor_by(cake::Column::Name)
.after("e")
.last(4)
.desc()
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
vec![
cakebaker('b', 'B'),
cakebaker('b', 'A'),
cakebaker('a', 'B'),
cakebaker('a', 'A')
]
);
assert_eq!( assert_eq!(
cake::Entity::find() cake::Entity::find()
.find_also_related(Baker) .find_also_related(Baker)
@ -487,7 +835,25 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
.into_model::<CakeBakerlite>() .into_model::<CakeBakerlite>()
.all(db) .all(db)
.await?, .await?,
vec![cakebaker('a', 'A'), cakebaker('a', 'B'),] vec![cakebaker('a', 'A'), cakebaker('a', 'B')]
);
assert_eq!(
cake::Entity::find()
.find_also_related(Baker)
.select_only()
.column_as(cake::Column::Id, QueryAs::CakeId)
.column_as(cake::Column::Name, QueryAs::CakeName)
.column_as(baker::Column::Name, QueryAs::BakerName)
.cursor_by(cake::Column::Name)
.after("b")
.last(4)
.desc()
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
vec![cakebaker('a', 'B'), cakebaker('a', 'A')]
); );
assert_eq!( assert_eq!(
@ -507,6 +873,24 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
vec![cakebaker('a', 'A'), cakebaker('b', 'A'),] vec![cakebaker('a', 'A'), cakebaker('b', 'A'),]
); );
assert_eq!(
cake::Entity::find()
.find_also_related(Baker)
.select_only()
.column_as(cake::Column::Id, QueryAs::CakeId)
.column_as(cake::Column::Name, QueryAs::CakeName)
.column_as(baker::Column::Name, QueryAs::BakerName)
.cursor_by_other(baker::Column::Name)
.after("B")
.last(4)
.desc()
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
vec![cakebaker('b', 'A'), cakebaker('a', 'A'),]
);
assert_eq!( assert_eq!(
cake::Entity::find() cake::Entity::find()
.find_also_related(Baker) .find_also_related(Baker)
@ -531,5 +915,30 @@ pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), Db
] ]
); );
assert_eq!(
cake::Entity::find()
.find_also_related(Baker)
.select_only()
.column_as(cake::Column::Id, QueryAs::CakeId)
.column_as(cake::Column::Name, QueryAs::CakeName)
.column_as(baker::Column::Name, QueryAs::BakerName)
.cursor_by_other(baker::Column::Name)
.after("E")
.last(20)
.desc()
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
vec![
cakebaker('d', 'D'),
cakebaker('c', 'C'),
cakebaker('b', 'B'),
cakebaker('a', 'B'),
cakebaker('b', 'A'),
cakebaker('a', 'A'),
]
);
Ok(()) Ok(())
} }