SelectTwo cursor by, cursorTrait revamp (#1826)

* WIP

* WIP

* test cases for cursor with SelectTwo

* fmt, fix test

* WIP, missing test for also_linked cursor_by

* completed many_to_many relationship

* test fixup

* fmt

* extra order_by to fix test

* WIP

* fixed cursor_by to do ordering implicitly base on both cursor column and primary key from other table

* change from map to for loop to eagerly do order_by

* fix tests
This commit is contained in:
Ivan Yiu 2023-09-23 00:03:04 +08:00 committed by GitHub
parent 9d033d01a8
commit 27ca745208
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 813 additions and 35 deletions

View File

@ -1,12 +1,14 @@
use crate::{ use crate::{
ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Identity, IntoIdentity, ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Identity, IdentityOf, IntoIdentity,
PartialModelTrait, QueryOrder, QuerySelect, Select, SelectModel, SelectorTrait, PartialModelTrait, PrimaryKeyToColumn, QueryOrder, QuerySelect, Select, SelectModel, SelectTwo,
SelectTwoModel, SelectorTrait,
}; };
use sea_query::{ use sea_query::{
Condition, DynIden, Expr, IntoValueTuple, Order, SeaRc, SelectStatement, SimpleExpr, Value, Condition, DynIden, Expr, IntoValueTuple, Order, SeaRc, SelectStatement, SimpleExpr, Value,
ValueTuple, ValueTuple,
}; };
use std::marker::PhantomData; use std::marker::PhantomData;
use strum::IntoEnumIterator as Iterable;
#[cfg(feature = "with-json")] #[cfg(feature = "with-json")]
use crate::JsonValue; use crate::JsonValue;
@ -17,11 +19,12 @@ pub struct Cursor<S>
where where
S: SelectorTrait, S: SelectorTrait,
{ {
pub(crate) query: SelectStatement, query: SelectStatement,
pub(crate) table: DynIden, table: DynIden,
pub(crate) order_columns: Identity, order_columns: Identity,
pub(crate) last: bool, secondary_order_by: Vec<(DynIden, Identity)>,
pub(crate) phantom: PhantomData<S>, last: bool,
phantom: PhantomData<S>,
} }
impl<S> Cursor<S> impl<S> Cursor<S>
@ -29,7 +32,12 @@ where
S: SelectorTrait, S: SelectorTrait,
{ {
/// Initialize a cursor /// Initialize a cursor
pub fn new<C>(query: SelectStatement, table: DynIden, order_columns: C) -> Self pub fn new<C>(
query: SelectStatement,
table: DynIden,
order_columns: C,
secondary_order_by: Vec<(DynIden, Identity)>,
) -> Self
where where
C: IntoIdentity, C: IntoIdentity,
{ {
@ -39,6 +47,7 @@ where
order_columns: order_columns.into_identity(), order_columns: order_columns.into_identity(),
last: false, last: false,
phantom: PhantomData, phantom: PhantomData,
secondary_order_by,
} }
} }
@ -158,10 +167,7 @@ where
/// Limit result set to only first N rows in ascending order of the order by column /// Limit result set to only first N rows in ascending order of the order by column
pub fn first(&mut self, num_rows: u64) -> &mut Self { pub fn first(&mut self, num_rows: u64) -> &mut Self {
self.query.limit(num_rows).clear_order_by(); self.query.limit(num_rows).clear_order_by();
let table = SeaRc::clone(&self.table); self.apply_order_by(self.table.clone(), Order::Asc);
self.apply_order_by(|query, col| {
query.order_by((SeaRc::clone(&table), SeaRc::clone(col)), Order::Asc);
});
self.last = false; self.last = false;
self self
} }
@ -169,38 +175,41 @@ where
/// Limit result set to only last N rows in ascending order of the order by column /// Limit result set to only last N rows in ascending order of the order by column
pub fn last(&mut self, num_rows: u64) -> &mut Self { pub fn last(&mut self, num_rows: u64) -> &mut Self {
self.query.limit(num_rows).clear_order_by(); self.query.limit(num_rows).clear_order_by();
let table = SeaRc::clone(&self.table); self.apply_order_by(self.table.clone(), Order::Desc);
self.apply_order_by(|query, col| {
query.order_by((SeaRc::clone(&table), SeaRc::clone(col)), Order::Desc);
});
self.last = true; self.last = true;
self self
} }
fn apply_order_by<F>(&mut self, f: F) fn apply_order_by(&mut self, table: DynIden, ord: Order) {
where
F: Fn(&mut SelectStatement, &DynIden),
{
let query = &mut self.query; let query = &mut self.query;
let order = |query: &mut SelectStatement, col| {
query.order_by((SeaRc::clone(&table), SeaRc::clone(col)), ord.clone());
};
match &self.order_columns { match &self.order_columns {
Identity::Unary(c1) => { Identity::Unary(c1) => {
f(query, c1); order(query, c1);
} }
Identity::Binary(c1, c2) => { Identity::Binary(c1, c2) => {
f(query, c1); order(query, c1);
f(query, c2); order(query, c2);
} }
Identity::Ternary(c1, c2, c3) => { Identity::Ternary(c1, c2, c3) => {
f(query, c1); order(query, c1);
f(query, c2); order(query, c2);
f(query, c3); order(query, c3);
} }
Identity::Many(vec) => { Identity::Many(vec) => {
for col in vec.iter() { for col in vec.iter() {
f(query, col); order(query, col);
} }
} }
} }
for (tbl, col) in self.secondary_order_by.clone() {
if let Identity::Unary(c1) = col {
query.order_by((tbl, c1), ord.clone());
};
}
} }
/// Fetch the paginated result /// Fetch the paginated result
@ -231,6 +240,7 @@ where
order_columns: self.order_columns, order_columns: self.order_columns,
last: self.last, last: self.last,
phantom: PhantomData, phantom: PhantomData,
secondary_order_by: self.secondary_order_by,
} }
} }
@ -251,8 +261,15 @@ where
order_columns: self.order_columns, order_columns: self.order_columns,
last: self.last, last: self.last,
phantom: PhantomData, phantom: PhantomData,
secondary_order_by: self.secondary_order_by,
} }
} }
/// Set the cursor ordering for another table when dealing with SelectTwo
pub fn set_secondary_order_by(&mut self, tbl_col: Vec<(DynIden, Identity)>) -> &mut Self {
self.secondary_order_by = tbl_col;
self
}
} }
impl<S> QuerySelect for Cursor<S> impl<S> QuerySelect for Cursor<S>
@ -281,11 +298,6 @@ where
pub trait CursorTrait { pub trait CursorTrait {
/// Select operation /// Select operation
type Selector: SelectorTrait + Send + Sync; type Selector: SelectorTrait + Send + Sync;
/// Convert current type into a cursor
fn cursor_by<C>(self, order_columns: C) -> Cursor<Self::Selector>
where
C: IntoIdentity;
} }
impl<E, M> CursorTrait for Select<E> impl<E, M> CursorTrait for Select<E>
@ -294,12 +306,79 @@ where
M: FromQueryResult + Sized + Send + Sync, M: FromQueryResult + Sized + Send + Sync,
{ {
type Selector = SelectModel<M>; type Selector = SelectModel<M>;
}
fn cursor_by<C>(self, order_columns: C) -> Cursor<Self::Selector> impl<E, M> Select<E>
where
E: EntityTrait<Model = M>,
M: FromQueryResult + Sized + Send + Sync,
{
/// Convert into a cursor
pub fn cursor_by<C>(self, order_columns: C) -> Cursor<SelectModel<M>>
where where
C: IntoIdentity, C: IntoIdentity,
{ {
Cursor::new(self.query, SeaRc::new(E::default()), order_columns) Cursor::new(self.query, SeaRc::new(E::default()), order_columns, vec![])
}
}
impl<E, F, M, N> CursorTrait for SelectTwo<E, F>
where
E: EntityTrait<Model = M>,
F: EntityTrait<Model = N>,
M: FromQueryResult + Sized + Send + Sync,
N: FromQueryResult + Sized + Send + Sync,
{
type Selector = SelectTwoModel<M, N>;
}
impl<E, F, M, N> SelectTwo<E, F>
where
E: EntityTrait<Model = M>,
F: EntityTrait<Model = N>,
M: FromQueryResult + Sized + Send + Sync,
N: FromQueryResult + Sized + Send + Sync,
{
/// Convert into a cursor using column of first entity
pub fn cursor_by<C>(self, order_columns: C) -> Cursor<SelectTwoModel<M, N>>
where
C: IdentityOf<E>,
{
let primary_keys: Vec<(DynIden, Identity)> = <F::PrimaryKey as Iterable>::iter()
.map(|pk| {
(
SeaRc::new(F::default()),
Identity::Unary(SeaRc::new(pk.into_column())),
)
})
.collect();
Cursor::new(
self.query,
SeaRc::new(E::default()),
order_columns.identity_of(),
primary_keys,
)
}
/// Convert into a cursor using column of second entity
pub fn cursor_by_other<C>(self, order_columns: C) -> Cursor<SelectTwoModel<M, N>>
where
C: IdentityOf<F>,
{
let primary_keys: Vec<(DynIden, Identity)> = <E::PrimaryKey as Iterable>::iter()
.map(|pk| {
(
SeaRc::new(E::default()),
Identity::Unary(SeaRc::new(pk.into_column())),
)
})
.collect();
Cursor::new(
self.query,
SeaRc::new(F::default()),
order_columns.identity_of(),
primary_keys,
)
} }
} }
@ -363,6 +442,231 @@ mod tests {
Ok(()) Ok(())
} }
#[smol_potat::test]
async fn first_2_before_10_also_related_select() -> Result<(), DbErr> {
let models = [
(
cake::Model {
id: 1,
name: "Blueberry Cheese Cake".into(),
},
Some(fruit::Model {
id: 9,
name: "Blueberry".into(),
cake_id: Some(1),
}),
),
(
cake::Model {
id: 2,
name: "Rasberry Cheese Cake".into(),
},
Some(fruit::Model {
id: 10,
name: "Rasberry".into(),
cake_id: Some(1),
}),
),
];
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results([models.clone()])
.into_connection();
assert_eq!(
cake::Entity::find()
.find_also_related(Fruit)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?,
models
);
assert_eq!(
db.into_transaction_log(),
[Transaction::many([Statement::from_sql_and_values(
DbBackend::Postgres,
[
r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
r#""fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id""#,
r#"FROM "cake""#,
r#"LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
r#"WHERE "cake"."id" < $1"#,
r#"ORDER BY "cake"."id" ASC, "fruit"."id" ASC LIMIT $2"#,
]
.join(" ")
.as_str(),
[10_i32.into(), 2_u64.into()]
),])]
);
Ok(())
}
#[smol_potat::test]
async fn first_2_before_10_also_related_select_cursor_other() -> Result<(), DbErr> {
let models = [(
cake::Model {
id: 1,
name: "Blueberry Cheese Cake".into(),
},
Some(fruit::Model {
id: 9,
name: "Blueberry".into(),
cake_id: Some(1),
}),
)];
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results([models.clone()])
.into_connection();
assert_eq!(
cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?,
models
);
assert_eq!(
db.into_transaction_log(),
[Transaction::many([Statement::from_sql_and_values(
DbBackend::Postgres,
[
r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
r#""fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id""#,
r#"FROM "cake""#,
r#"LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
r#"WHERE "fruit"."id" < $1"#,
r#"ORDER BY "fruit"."id" ASC, "cake"."id" ASC LIMIT $2"#,
]
.join(" ")
.as_str(),
[10_i32.into(), 2_u64.into()]
),])]
);
Ok(())
}
#[smol_potat::test]
async fn first_2_before_10_also_linked_select() -> Result<(), DbErr> {
let models = [
(
cake::Model {
id: 1,
name: "Blueberry Cheese Cake".into(),
},
Some(vendor::Model {
id: 9,
name: "Blueberry".into(),
}),
),
(
cake::Model {
id: 2,
name: "Rasberry Cheese Cake".into(),
},
Some(vendor::Model {
id: 10,
name: "Rasberry".into(),
}),
),
];
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results([models.clone()])
.into_connection();
assert_eq!(
cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?,
models
);
assert_eq!(
db.into_transaction_log(),
[Transaction::many([Statement::from_sql_and_values(
DbBackend::Postgres,
[
r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
r#""r2"."id" AS "B_id", "r2"."name" AS "B_name""#,
r#"FROM "cake""#,
r#"LEFT JOIN "cake_filling" AS "r0" ON "cake"."id" = "r0"."cake_id""#,
r#"LEFT JOIN "filling" AS "r1" ON "r0"."filling_id" = "r1"."id""#,
r#"LEFT JOIN "vendor" AS "r2" ON "r1"."vendor_id" = "r2"."id""#,
r#"WHERE "cake"."id" < $1 ORDER BY "cake"."id" ASC, "vendor"."id" ASC LIMIT $2"#,
]
.join(" ")
.as_str(),
[10_i32.into(), 2_u64.into()]
),])]
);
Ok(())
}
#[smol_potat::test]
async fn first_2_before_10_also_linked_select_cursor_other() -> Result<(), DbErr> {
let models = [(
cake::Model {
id: 1,
name: "Blueberry Cheese Cake".into(),
},
Some(vendor::Model {
id: 9,
name: "Blueberry".into(),
}),
)];
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results([models.clone()])
.into_connection();
assert_eq!(
cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by_other(vendor::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?,
models
);
assert_eq!(
db.into_transaction_log(),
[Transaction::many([Statement::from_sql_and_values(
DbBackend::Postgres,
[
r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name","#,
r#""r2"."id" AS "B_id", "r2"."name" AS "B_name""#,
r#"FROM "cake""#,
r#"LEFT JOIN "cake_filling" AS "r0" ON "cake"."id" = "r0"."cake_id""#,
r#"LEFT JOIN "filling" AS "r1" ON "r0"."filling_id" = "r1"."id""#,
r#"LEFT JOIN "vendor" AS "r2" ON "r1"."vendor_id" = "r2"."id""#,
r#"WHERE "vendor"."id" < $1 ORDER BY "vendor"."id" ASC, "cake"."id" ASC LIMIT $2"#,
]
.join(" ")
.as_str(),
[10_i32.into(), 2_u64.into()]
),])]
);
Ok(())
}
#[smol_potat::test] #[smol_potat::test]
async fn last_2_after_10() -> Result<(), DbErr> { async fn last_2_after_10() -> Result<(), DbErr> {
use fruit::*; use fruit::*;
@ -895,4 +1199,216 @@ mod tests {
Ok(()) Ok(())
} }
mod test_base_entity {
use super::test_related_entity;
use crate as sea_orm;
use crate::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "base")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(primary_key)]
pub name: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::test_related_entity::Entity")]
TestRelatedEntity,
}
impl Related<super::test_related_entity::Entity> for Entity {
fn to() -> RelationDef {
Relation::TestRelatedEntity.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
}
mod test_related_entity {
use super::test_base_entity;
use crate as sea_orm;
use crate::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "related")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(primary_key)]
pub name: String,
pub test_id: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "test_base_entity::Entity",
from = "Column::TestId",
to = "super::test_base_entity::Column::Id"
)]
TestBaseEntity,
}
impl Related<super::test_base_entity::Entity> for Entity {
fn to() -> RelationDef {
Relation::TestBaseEntity.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
}
#[smol_potat::test]
async fn related_composite_keys_1() -> Result<(), DbErr> {
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results([[(
test_base_entity::Model {
id: 1,
name: "CAT".into(),
},
test_related_entity::Model {
id: 1,
name: "CATE".into(),
test_id: 1,
},
)]])
.into_connection();
assert!(!test_base_entity::Entity::find()
.find_also_related(test_related_entity::Entity)
.cursor_by((test_base_entity::Column::Id, test_base_entity::Column::Name))
.first(1)
.all(&db)
.await?
.is_empty());
assert_eq!(
db.into_transaction_log(),
[Transaction::many([Statement::from_sql_and_values(
DbBackend::Postgres,
[
r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
r#"FROM "base""#,
r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
r#"ORDER BY "base"."id" ASC, "base"."name" ASC, "related"."id" ASC, "related"."name" ASC LIMIT $1"#,
]
.join(" ")
.as_str(),
[1_u64.into()]
),])]
);
Ok(())
}
#[smol_potat::test]
async fn related_composite_keys_2() -> Result<(), DbErr> {
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results([[(
test_base_entity::Model {
id: 1,
name: "CAT".into(),
},
test_related_entity::Model {
id: 1,
name: "CATE".into(),
test_id: 1,
},
)]])
.into_connection();
assert!(!test_base_entity::Entity::find()
.find_also_related(test_related_entity::Entity)
.cursor_by((test_base_entity::Column::Id, test_base_entity::Column::Name))
.after((1, "C".to_string()))
.first(2)
.all(&db)
.await?
.is_empty());
assert_eq!(
db.into_transaction_log(),
[Transaction::many([Statement::from_sql_and_values(
DbBackend::Postgres,
[
r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
r#"FROM "base""#,
r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
r#"WHERE ("base"."id" = $1 AND "base"."name" > $2) OR "base"."id" > $3"#,
r#"ORDER BY "base"."id" ASC, "base"."name" ASC, "related"."id" ASC, "related"."name" ASC LIMIT $4"#,
]
.join(" ")
.as_str(),
[
1_i32.into(),
"C".into(),
1_i32.into(),
2_u64.into(),
]
),])]
);
Ok(())
}
#[smol_potat::test]
async fn related_composite_keys_3() -> Result<(), DbErr> {
let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results([[(
test_base_entity::Model {
id: 1,
name: "CAT".into(),
},
test_related_entity::Model {
id: 1,
name: "CATE".into(),
test_id: 1,
},
)]])
.into_connection();
assert!(!test_base_entity::Entity::find()
.find_also_related(test_related_entity::Entity)
.cursor_by_other((
test_related_entity::Column::Id,
test_related_entity::Column::Name
))
.after((1, "CAT".to_string()))
.first(2)
.all(&db)
.await?
.is_empty());
assert_eq!(
db.into_transaction_log(),
[Transaction::many([Statement::from_sql_and_values(
DbBackend::Postgres,
[
r#"SELECT "base"."id" AS "A_id", "base"."name" AS "A_name","#,
r#""related"."id" AS "B_id", "related"."name" AS "B_name", "related"."test_id" AS "B_test_id""#,
r#"FROM "base""#,
r#"LEFT JOIN "related" ON "base"."id" = "related"."test_id""#,
r#"WHERE ("related"."id" = $1 AND "related"."name" > $2) OR "related"."id" > $3"#,
r#"ORDER BY "related"."id" ASC, "related"."name" ASC, "base"."id" ASC, "base"."name" ASC LIMIT $4"#,
]
.join(" ")
.as_str(),
[
1_i32.into(),
"CAT".into(),
1_i32.into(),
2_u64.into(),
]
),])]
);
Ok(())
}
} }

View File

@ -2,7 +2,9 @@ pub mod common;
pub use common::{features::*, setup::*, TestContext}; pub use common::{features::*, setup::*, TestContext};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use sea_orm::{entity::prelude::*, DerivePartialModel, FromQueryResult}; use sea_orm::{
entity::prelude::*, DerivePartialModel, FromQueryResult, QueryOrder, QuerySelect, Set,
};
use serde_json::json; use serde_json::json;
#[sea_orm_macros::test] #[sea_orm_macros::test]
@ -16,6 +18,9 @@ async fn main() -> Result<(), DbErr> {
create_tables(&ctx.db).await?; create_tables(&ctx.db).await?;
create_insert_default(&ctx.db).await?; create_insert_default(&ctx.db).await?;
cursor_pagination(&ctx.db).await?; cursor_pagination(&ctx.db).await?;
schema::create_tables(&ctx.db).await?;
create_baker_cake(&ctx.db).await?;
cursor_related_pagination(&ctx.db).await?;
ctx.delete().await; ctx.delete().await;
Ok(()) Ok(())
@ -274,3 +279,260 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
Ok(()) Ok(())
} }
use common::bakery_chain::{
baker, bakery, cake, cakes_bakers, schema, Baker, Bakery, Cake, CakesBakers,
};
fn bakery(i: i32) -> bakery::Model {
bakery::Model {
name: i.to_string(),
profit_margin: 10.4,
id: i,
}
}
fn baker(c: char) -> baker::Model {
baker::Model {
name: c.clone().to_string(),
contact_details: serde_json::json!({
"mobile": "+61424000000",
}),
bakery_id: Some((c as i32 - 65) % 10 + 1),
id: c as i32 - 64,
}
}
#[derive(Debug, FromQueryResult, PartialEq)]
pub struct CakeBakerlite {
pub cake_name: String,
pub cake_id: i32,
pub baker_name: String,
}
fn cakebaker(cake: char, baker: char) -> CakeBakerlite {
CakeBakerlite {
cake_name: cake.to_string(),
cake_id: cake as i32 - 96,
baker_name: baker.to_string(),
}
}
pub async fn create_baker_cake(db: &DatabaseConnection) -> Result<(), DbErr> {
use sea_orm::IntoActiveModel;
let mut bakeries: Vec<bakery::ActiveModel> = vec![];
// bakeries named from 1 to 10
for i in 1..=10 {
bakeries.push(bakery::ActiveModel {
name: Set(i.to_string()),
profit_margin: Set(10.4),
..Default::default()
});
}
let _ = Bakery::insert_many(bakeries).exec(db).await?;
let mut bakers: Vec<baker::ActiveModel> = vec![];
let mut cakes: Vec<cake::ActiveModel> = vec![];
let mut cakes_bakers: Vec<cakes_bakers::ActiveModel> = vec![];
// baker and cakes named from "A" to "Z" and from "a" to "z"
for c in 'A'..='Z' {
bakers.push(baker::ActiveModel {
name: Set(c.clone().to_string()),
contact_details: Set(serde_json::json!({
"mobile": "+61424000000",
})),
bakery_id: Set(Some((c as i32 - 65) % 10 + 1)),
..Default::default()
});
cakes.push(cake::ActiveModel {
name: Set(c.to_ascii_lowercase().to_string()),
price: Set(rust_decimal_macros::dec!(10.25)),
gluten_free: Set(false),
serial: Set(Uuid::new_v4()),
bakery_id: Set(Some((c as i32 - 65) % 10 + 1)),
..Default::default()
});
cakes_bakers.push(cakes_bakers::ActiveModel {
cake_id: Set(c as i32 - 64),
baker_id: Set(c as i32 - 64),
})
}
cakes_bakers.append(
vec![
cakes_bakers::ActiveModel {
cake_id: Set(2),
baker_id: Set(1),
},
cakes_bakers::ActiveModel {
cake_id: Set(1),
baker_id: Set(2),
},
]
.as_mut(),
);
Baker::insert_many(bakers).exec(db).await?;
Cake::insert_many(cakes).exec(db).await?;
CakesBakers::insert_many(cakes_bakers).exec(db).await?;
Ok(())
}
pub async fn cursor_related_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
use common::bakery_chain::*;
assert_eq!(
bakery::Entity::find()
.cursor_by(bakery::Column::Id)
.before(5)
.first(4)
.all(db)
.await?,
[bakery(1), bakery(2), bakery(3), bakery(4),]
);
assert_eq!(
bakery::Entity::find()
.find_also_related(Baker)
.cursor_by(bakery::Column::Id)
.before(5)
.first(20)
.all(db)
.await?,
[
(bakery(1), Some(baker('A'))),
(bakery(1), Some(baker('K'))),
(bakery(1), Some(baker('U'))),
(bakery(2), Some(baker('B'))),
(bakery(2), Some(baker('L'))),
(bakery(2), Some(baker('V'))),
(bakery(3), Some(baker('C'))),
(bakery(3), Some(baker('M'))),
(bakery(3), Some(baker('W'))),
(bakery(4), Some(baker('D'))),
(bakery(4), Some(baker('N'))),
(bakery(4), Some(baker('X'))),
]
);
assert_eq!(
bakery::Entity::find()
.find_also_related(Baker)
.cursor_by(bakery::Column::Id)
.before(5)
.first(4)
.all(db)
.await?,
[
(bakery(1), Some(baker('A'))),
(bakery(1), Some(baker('K'))),
(bakery(1), Some(baker('U'))),
(bakery(2), Some(baker('B'))),
]
);
// since "10" is before "2" lexicologically, it return that first
assert_eq!(
bakery::Entity::find()
.find_also_related(Baker)
.cursor_by(bakery::Column::Name)
.before("3")
.first(4)
.all(db)
.await?,
[
(bakery(1), Some(baker('A'))),
(bakery(1), Some(baker('K'))),
(bakery(1), Some(baker('U'))),
(bakery(10), Some(baker('J'))),
]
);
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
CakeId,
CakeName,
BakerName,
}
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)
.before("e")
.first(4)
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
vec![
cakebaker('a', 'A'),
cakebaker('a', 'B'),
cakebaker('b', 'A'),
cakebaker('b', '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)
.before("b")
.first(4)
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
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_other(baker::Column::Name)
.before("B")
.first(4)
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
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)
.before("E")
.first(20)
.clone()
.into_model::<CakeBakerlite>()
.all(db)
.await?,
vec![
cakebaker('a', 'A'),
cakebaker('b', 'A'),
cakebaker('a', 'B'),
cakebaker('b', 'B'),
cakebaker('c', 'C'),
cakebaker('d', 'D'),
]
);
Ok(())
}