Composite key cursor pagination (#1216)
* Composite key cursor pagination * Fix Composite key of 3
This commit is contained in:
parent
565cb5e6d0
commit
d082340848
@ -73,13 +73,26 @@ where
|
|||||||
{
|
{
|
||||||
match (&self.order_columns, values.into_value_tuple()) {
|
match (&self.order_columns, values.into_value_tuple()) {
|
||||||
(Identity::Unary(c1), ValueTuple::One(v1)) => Condition::all().add(f(c1, v1)),
|
(Identity::Unary(c1), ValueTuple::One(v1)) => Condition::all().add(f(c1, v1)),
|
||||||
(Identity::Binary(c1, c2), ValueTuple::Two(v1, v2)) => {
|
(Identity::Binary(c1, c2), ValueTuple::Two(v1, v2)) => Condition::any()
|
||||||
Condition::all().add(f(c1, v1)).add(f(c2, v2))
|
.add(
|
||||||
}
|
Condition::all()
|
||||||
(Identity::Ternary(c1, c2, c3), ValueTuple::Three(v1, v2, v3)) => Condition::all()
|
.add(Expr::tbl(SeaRc::clone(&self.table), SeaRc::clone(c1)).eq(v1.clone()))
|
||||||
.add(f(c1, v1))
|
.add(f(c2, v2)),
|
||||||
.add(f(c2, v2))
|
)
|
||||||
|
.add(f(c1, v1)),
|
||||||
|
(Identity::Ternary(c1, c2, c3), ValueTuple::Three(v1, v2, v3)) => Condition::any()
|
||||||
|
.add(
|
||||||
|
Condition::all()
|
||||||
|
.add(Expr::tbl(SeaRc::clone(&self.table), SeaRc::clone(c1)).eq(v1.clone()))
|
||||||
|
.add(Expr::tbl(SeaRc::clone(&self.table), SeaRc::clone(c2)).eq(v2.clone()))
|
||||||
.add(f(c3, v3)),
|
.add(f(c3, v3)),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
Condition::all()
|
||||||
|
.add(Expr::tbl(SeaRc::clone(&self.table), SeaRc::clone(c1)).eq(v1.clone()))
|
||||||
|
.add(f(c2, v2)),
|
||||||
|
)
|
||||||
|
.add(f(c1, v1)),
|
||||||
_ => panic!("column arity mismatch"),
|
_ => panic!("column arity mismatch"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -390,73 +403,254 @@ mod tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod test_entity {
|
||||||
|
use crate as sea_orm;
|
||||||
|
use crate::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "example")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub category: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod xyz_entity {
|
||||||
|
use crate as sea_orm;
|
||||||
|
use crate::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "m")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub x: i32,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub y: String,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub z: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
}
|
||||||
|
|
||||||
#[smol_potat::test]
|
#[smol_potat::test]
|
||||||
async fn composite_keys() -> Result<(), DbErr> {
|
async fn composite_keys_1() -> Result<(), DbErr> {
|
||||||
use cake_filling::*;
|
use test_entity::*;
|
||||||
|
|
||||||
let db = MockDatabase::new(DbBackend::Postgres)
|
let db = MockDatabase::new(DbBackend::Postgres)
|
||||||
.append_query_results(vec![vec![
|
.append_query_results(vec![vec![Model {
|
||||||
Model {
|
id: 1,
|
||||||
cake_id: 1,
|
category: "CAT".into(),
|
||||||
filling_id: 2,
|
}]])
|
||||||
},
|
|
||||||
Model {
|
|
||||||
cake_id: 1,
|
|
||||||
filling_id: 3,
|
|
||||||
},
|
|
||||||
Model {
|
|
||||||
cake_id: 2,
|
|
||||||
filling_id: 3,
|
|
||||||
},
|
|
||||||
]])
|
|
||||||
.into_connection();
|
.into_connection();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(!Entity::find()
|
||||||
Entity::find()
|
.cursor_by((Column::Category, Column::Id))
|
||||||
.cursor_by((Column::CakeId, Column::FillingId))
|
|
||||||
.after((0, 1))
|
|
||||||
.before((10, 11))
|
|
||||||
.first(3)
|
.first(3)
|
||||||
.all(&db)
|
.all(&db)
|
||||||
.await?,
|
.await?
|
||||||
vec![
|
.is_empty());
|
||||||
Model {
|
|
||||||
cake_id: 1,
|
|
||||||
filling_id: 2,
|
|
||||||
},
|
|
||||||
Model {
|
|
||||||
cake_id: 1,
|
|
||||||
filling_id: 3,
|
|
||||||
},
|
|
||||||
Model {
|
|
||||||
cake_id: 2,
|
|
||||||
filling_id: 3,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
db.into_transaction_log(),
|
db.into_transaction_log(),
|
||||||
vec![Transaction::many(vec![Statement::from_sql_and_values(
|
vec![Transaction::many(vec![Statement::from_sql_and_values(
|
||||||
DbBackend::Postgres,
|
DbBackend::Postgres,
|
||||||
[
|
[
|
||||||
r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id""#,
|
r#"SELECT "example"."id", "example"."category""#,
|
||||||
r#"FROM "cake_filling""#,
|
r#"FROM "example""#,
|
||||||
r#"WHERE "cake_filling"."cake_id" > $1"#,
|
r#"ORDER BY "example"."category" ASC, "example"."id" ASC"#,
|
||||||
r#"AND "cake_filling"."filling_id" > $2"#,
|
r#"LIMIT $1"#,
|
||||||
r#"AND "cake_filling"."cake_id" < $3"#,
|
]
|
||||||
r#"AND "cake_filling"."filling_id" < $4"#,
|
.join(" ")
|
||||||
r#"ORDER BY "cake_filling"."cake_id" ASC, "cake_filling"."filling_id" ASC"#,
|
.as_str(),
|
||||||
r#"LIMIT $5"#,
|
vec![3_u64.into()]
|
||||||
|
),])]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[smol_potat::test]
|
||||||
|
async fn composite_keys_2() -> Result<(), DbErr> {
|
||||||
|
use test_entity::*;
|
||||||
|
|
||||||
|
let db = MockDatabase::new(DbBackend::Postgres)
|
||||||
|
.append_query_results(vec![vec![Model {
|
||||||
|
id: 1,
|
||||||
|
category: "CAT".into(),
|
||||||
|
}]])
|
||||||
|
.into_connection();
|
||||||
|
|
||||||
|
assert!(!Entity::find()
|
||||||
|
.cursor_by((Column::Category, Column::Id))
|
||||||
|
.after(("A".to_owned(), 2))
|
||||||
|
.first(3)
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.into_transaction_log(),
|
||||||
|
vec![Transaction::many(vec![Statement::from_sql_and_values(
|
||||||
|
DbBackend::Postgres,
|
||||||
|
[
|
||||||
|
r#"SELECT "example"."id", "example"."category""#,
|
||||||
|
r#"FROM "example""#,
|
||||||
|
r#"WHERE ("example"."category" = $1 AND "example"."id" > $2)"#,
|
||||||
|
r#"OR "example"."category" > $3"#,
|
||||||
|
r#"ORDER BY "example"."category" ASC, "example"."id" ASC"#,
|
||||||
|
r#"LIMIT $4"#,
|
||||||
]
|
]
|
||||||
.join(" ")
|
.join(" ")
|
||||||
.as_str(),
|
.as_str(),
|
||||||
vec![
|
vec![
|
||||||
0_i32.into(),
|
"A".to_string().into(),
|
||||||
1_i32.into(),
|
2i32.into(),
|
||||||
10_i32.into(),
|
"A".to_string().into(),
|
||||||
11_i32.into(),
|
3_u64.into(),
|
||||||
3_u64.into()
|
]
|
||||||
|
)])]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[smol_potat::test]
|
||||||
|
async fn composite_keys_3() -> Result<(), DbErr> {
|
||||||
|
use test_entity::*;
|
||||||
|
|
||||||
|
let db = MockDatabase::new(DbBackend::Postgres)
|
||||||
|
.append_query_results(vec![vec![Model {
|
||||||
|
id: 1,
|
||||||
|
category: "CAT".into(),
|
||||||
|
}]])
|
||||||
|
.into_connection();
|
||||||
|
|
||||||
|
assert!(!Entity::find()
|
||||||
|
.cursor_by((Column::Category, Column::Id))
|
||||||
|
.before(("A".to_owned(), 2))
|
||||||
|
.last(3)
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.into_transaction_log(),
|
||||||
|
vec![Transaction::many(vec![Statement::from_sql_and_values(
|
||||||
|
DbBackend::Postgres,
|
||||||
|
[
|
||||||
|
r#"SELECT "example"."id", "example"."category""#,
|
||||||
|
r#"FROM "example""#,
|
||||||
|
r#"WHERE ("example"."category" = $1 AND "example"."id" < $2)"#,
|
||||||
|
r#"OR "example"."category" < $3"#,
|
||||||
|
r#"ORDER BY "example"."category" DESC, "example"."id" DESC"#,
|
||||||
|
r#"LIMIT $4"#,
|
||||||
|
]
|
||||||
|
.join(" ")
|
||||||
|
.as_str(),
|
||||||
|
vec![
|
||||||
|
"A".to_string().into(),
|
||||||
|
2i32.into(),
|
||||||
|
"A".to_string().into(),
|
||||||
|
3_u64.into(),
|
||||||
|
]
|
||||||
|
)])]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[smol_potat::test]
|
||||||
|
async fn composite_keys_4() -> Result<(), DbErr> {
|
||||||
|
use xyz_entity::*;
|
||||||
|
|
||||||
|
let db = MockDatabase::new(DbBackend::Postgres)
|
||||||
|
.append_query_results(vec![vec![Model {
|
||||||
|
x: 'x' as i32,
|
||||||
|
y: "y".into(),
|
||||||
|
z: 'z' as i64,
|
||||||
|
}]])
|
||||||
|
.into_connection();
|
||||||
|
|
||||||
|
assert!(!Entity::find()
|
||||||
|
.cursor_by((Column::X, Column::Y, Column::Z))
|
||||||
|
.first(4)
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.into_transaction_log(),
|
||||||
|
vec![Transaction::many(vec![Statement::from_sql_and_values(
|
||||||
|
DbBackend::Postgres,
|
||||||
|
[
|
||||||
|
r#"SELECT "m"."x", "m"."y", "m"."z""#,
|
||||||
|
r#"FROM "m""#,
|
||||||
|
r#"ORDER BY "m"."x" ASC, "m"."y" ASC, "m"."z" ASC"#,
|
||||||
|
r#"LIMIT $1"#,
|
||||||
|
]
|
||||||
|
.join(" ")
|
||||||
|
.as_str(),
|
||||||
|
vec![4_u64.into()]
|
||||||
|
),])]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[smol_potat::test]
|
||||||
|
async fn composite_keys_5() -> Result<(), DbErr> {
|
||||||
|
use xyz_entity::*;
|
||||||
|
|
||||||
|
let db = MockDatabase::new(DbBackend::Postgres)
|
||||||
|
.append_query_results(vec![vec![Model {
|
||||||
|
x: 'x' as i32,
|
||||||
|
y: "y".into(),
|
||||||
|
z: 'z' as i64,
|
||||||
|
}]])
|
||||||
|
.into_connection();
|
||||||
|
|
||||||
|
assert!(!Entity::find()
|
||||||
|
.cursor_by((Column::X, Column::Y, Column::Z))
|
||||||
|
.after(('x' as i32, "y".to_owned(), 'z' as i64))
|
||||||
|
.first(4)
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.into_transaction_log(),
|
||||||
|
vec![Transaction::many(vec![Statement::from_sql_and_values(
|
||||||
|
DbBackend::Postgres,
|
||||||
|
[
|
||||||
|
r#"SELECT "m"."x", "m"."y", "m"."z""#,
|
||||||
|
r#"FROM "m""#,
|
||||||
|
r#"WHERE ("m"."x" = $1 AND "m"."y" = $2 AND "m"."z" > $3)"#,
|
||||||
|
r#"OR ("m"."x" = $4 AND "m"."y" > $5)"#,
|
||||||
|
r#"OR "m"."x" > $6"#,
|
||||||
|
r#"ORDER BY "m"."x" ASC, "m"."y" ASC, "m"."z" ASC"#,
|
||||||
|
r#"LIMIT $7"#,
|
||||||
|
]
|
||||||
|
.join(" ")
|
||||||
|
.as_str(),
|
||||||
|
vec![
|
||||||
|
('x' as i32).into(),
|
||||||
|
"y".into(),
|
||||||
|
('z' as i64).into(),
|
||||||
|
('x' as i32).into(),
|
||||||
|
"y".into(),
|
||||||
|
('x' as i32).into(),
|
||||||
|
4_u64.into(),
|
||||||
]
|
]
|
||||||
),])]
|
),])]
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user