SelectTwoMany

This commit is contained in:
Chris Tsang 2021-06-27 03:39:09 +08:00
parent df7bb5c195
commit a0db19758b
7 changed files with 214 additions and 45 deletions

View File

@ -32,15 +32,27 @@ Model { id: 2, name: "Rasberry", cake_id: Some(1) }
Model { id: 3, name: "Strawberry", cake_id: Some(2) }
Model { id: 4, name: "Apple", cake_id: None }
Model { id: 5, name: "Banana", cake_id: None }
Model { id: 6, name: "Cherry", cake_id: None }
Model { id: 7, name: "Lemon", cake_id: None }
Model { id: 8, name: "Orange", cake_id: None }
Model { id: 9, name: "Pineapple", cake_id: None }
===== =====
find cakes and fruits: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id` FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`
(Model { id: 1, name: "New York Cheese" }, Model { id: 2, name: "Rasberry", cake_id: Some(1) })
(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 1, name: "Blueberry", cake_id: Some(1) }))
(Model { id: 1, name: "New York Cheese" }, Model { id: 1, name: "Blueberry", cake_id: Some(1) })
(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 2, name: "Rasberry", cake_id: Some(1) }))
(Model { id: 2, name: "Chocolate Forest" }, Model { id: 3, name: "Strawberry", cake_id: Some(2) })
(Model { id: 2, name: "Chocolate Forest" }, Some(Model { id: 3, name: "Strawberry", cake_id: Some(2) }))
===== =====
@ -48,7 +60,7 @@ find one by primary key: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `ca
Model { id: 1, name: "New York Cheese" }
find one by like: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%chocolate%' LIMIT 1
find one by name: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%chocolate%' LIMIT 1
Some(Model { id: 2, name: "Chocolate Forest" })
@ -68,15 +80,11 @@ SelectResult { name: "Chocolate Forest", num_of_fruits: 1 }
===== =====
find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`
find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` ORDER BY `cake`.`id` ASC
(Model { id: 1, name: "New York Cheese" }, Model { id: 1, name: "Vanilla" })
(Model { id: 1, name: "New York Cheese" }, [Model { id: 1, name: "Vanilla" }, Model { id: 2, name: "Lemon" }])
(Model { id: 1, name: "New York Cheese" }, Model { id: 2, name: "Lemon" })
(Model { id: 2, name: "Chocolate Forest" }, Model { id: 2, name: "Lemon" })
(Model { id: 2, name: "Chocolate Forest" }, Model { id: 3, name: "Mango" })
(Model { id: 2, name: "Chocolate Forest" }, [Model { id: 2, name: "Lemon" }, Model { id: 3, name: "Mango" }])
find fillings for cheese cake: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 1 LIMIT 1
SELECT `filling`.`id`, `filling`.`name` FROM `filling` INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id` INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id` WHERE `cake`.`id` = 1

View File

@ -66,7 +66,10 @@ async fn find_all(db: &DbConn) -> Result<(), QueryErr> {
async fn find_together(db: &DbConn) -> Result<(), QueryErr> {
print!("find cakes and fruits: ");
let both = Cake::find().left_join_and_select_also(Fruit).all(db).await?;
let both = Cake::find()
.left_join_and_select_also(Fruit)
.all(db)
.await?;
println!();
for bb in both.iter() {
@ -141,7 +144,10 @@ async fn count_fruits_by_cake(db: &DbConn) -> Result<(), QueryErr> {
async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> {
print!("find cakes and fillings: ");
let both = Cake::find().left_join_and_select_also(Filling).all(db).await?;
let both = Cake::find()
.left_join_and_select_with(Filling)
.all(db)
.await?;
println!();
for bb in both.iter() {
@ -211,7 +217,7 @@ async fn find_together_json(db: &DbConn) -> Result<(), QueryErr> {
print!("find cakes and fruits: ");
let cakes_fruits = Cake::find()
.left_join_and_select_also(Fruit)
.left_join_and_select_with(Fruit)
.into_json()
.all(db)
.await?;

View File

@ -15,7 +15,7 @@ pub trait FromQueryResult {
where
Self: Sized;
fn from_query_result_opt(res: &QueryResult, pre: &str) -> Result<Option<Self>, TypeErr>
fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, TypeErr>
where
Self: Sized,
{

View File

@ -1,6 +1,7 @@
use crate::{
query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, JsonValue,
ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo, TypeErr,
ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo,
SelectTwoMany, TypeErr,
};
use sea_query::SelectStatement;
use std::marker::PhantomData;
@ -57,7 +58,7 @@ where
fn from_raw_query_result(res: QueryResult) -> Result<Self::Item, TypeErr> {
Ok((
M::from_query_result(&res, combine::SELECT_A)?,
N::from_query_result_opt(&res, combine::SELECT_B)?,
N::from_query_result_optional(&res, combine::SELECT_B)?,
))
}
}
@ -132,11 +133,54 @@ where
self.into_model::<E::Model, F::Model>().one(db).await
}
pub async fn all(self, db: &DatabaseConnection) -> Result<Vec<(E::Model, Option<F::Model>)>, QueryErr> {
pub async fn all(
self,
db: &DatabaseConnection,
) -> Result<Vec<(E::Model, Option<F::Model>)>, QueryErr> {
self.into_model::<E::Model, F::Model>().all(db).await
}
}
impl<E, F> SelectTwoMany<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
fn into_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
where
M: FromQueryResult,
N: FromQueryResult,
{
Selector {
query: self.query,
selector: SelectTwoModel { model: PhantomData },
}
}
#[cfg(feature = "with-json")]
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
Selector {
query: self.query,
selector: SelectTwoModel { model: PhantomData },
}
}
pub async fn one(
self,
db: &DatabaseConnection,
) -> Result<Option<(E::Model, Option<F::Model>)>, QueryErr> {
self.into_model::<E::Model, F::Model>().one(db).await
}
pub async fn all(
self,
db: &DatabaseConnection,
) -> Result<Vec<(E::Model, Vec<F::Model>)>, QueryErr> {
let rows = self.into_model::<E::Model, F::Model>().all(db).await?;
Ok(consolidate_query_result::<E, F>(rows))
}
}
impl<S> Selector<S>
where
S: SelectorTrait,
@ -172,11 +216,14 @@ where
}
}
fn parse_query_result<L, R>(rows: Vec<(L::Model, Option<R>)>) -> Vec<(L::Model, Vec<R>)>
fn consolidate_query_result<L, R>(
rows: Vec<(L::Model, Option<R::Model>)>,
) -> Vec<(L::Model, Vec<R::Model>)>
where
L: EntityTrait,
R: EntityTrait,
{
let mut acc: Vec<(L::Model, Vec<R>)> = Vec::new();
let mut acc: Vec<(L::Model, Vec<R::Model>)> = Vec::new();
for (l, r) in rows {
if let Some((last_l, last_r)) = acc.last_mut() {
let mut same_l = true;
@ -203,4 +250,4 @@ where
}
}
acc
}
}

View File

@ -1,4 +1,4 @@
use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo};
use crate::{EntityTrait, IntoSimpleExpr, Iterable, QueryTrait, Select, SelectTwo, SelectTwoMany};
use core::marker::PhantomData;
pub use sea_query::JoinType;
use sea_query::{Alias, ColumnRef, Iden, Order, SeaRc, SelectExpr, SelectStatement, SimpleExpr};
@ -40,9 +40,36 @@ where
self = self.apply_alias(SELECT_A);
SelectTwo::new(self.into_query())
}
pub fn select_with<F>(mut self, _: F) -> SelectTwoMany<E, F>
where
F: EntityTrait,
{
self = self.apply_alias(SELECT_A);
SelectTwoMany::new(self.into_query())
}
}
impl<E, F> SelectTwo<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
pub(crate) fn new(query: SelectStatement) -> Self {
Self {
query,
entity: PhantomData,
}
.prepare_select()
}
fn prepare_select(mut self) -> Self {
prepare_select_two::<F, Self>(&mut self);
self
}
}
impl<E, F> SelectTwoMany<E, F>
where
E: EntityTrait,
F: EntityTrait,
@ -57,13 +84,7 @@ where
}
fn prepare_select(mut self) -> Self {
for col in <F::Column as Iterable>::iter() {
let alias = format!("{}{}", SELECT_B, col.to_string().as_str());
self.query.expr(SelectExpr {
expr: col.into_simple_expr(),
alias: Some(SeaRc::new(Alias::new(&alias))),
});
}
prepare_select_two::<F, Self>(&mut self);
self
}
@ -75,6 +96,20 @@ where
}
}
fn prepare_select_two<F, S>(selector: &mut S)
where
F: EntityTrait,
S: QueryTrait<QueryStatement = SelectStatement>,
{
for col in <F::Column as Iterable>::iter() {
let alias = format!("{}{}", SELECT_B, col.to_string().as_str());
selector.query().expr(SelectExpr {
expr: col.into_simple_expr(),
alias: Some(SeaRc::new(Alias::new(&alias))),
});
}
}
#[cfg(test)]
mod tests {
use crate::tests_cfg::{cake, fruit};
@ -101,6 +136,22 @@ mod tests {
.select_also(fruit::Entity)
.build(MysqlQueryBuilder)
.to_string(),
[
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,",
"`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`",
"FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`",
].join(" ")
);
}
#[test]
fn select_with_1() {
assert_eq!(
cake::Entity::find()
.left_join(fruit::Entity)
.select_with(fruit::Entity)
.build(MysqlQueryBuilder)
.to_string(),
[
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,",
"`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`",
@ -120,6 +171,25 @@ mod tests {
.filter(fruit::Column::Id.eq(2))
.build(MysqlQueryBuilder)
.to_string(),
[
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,",
"`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`",
"FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`",
"WHERE `cake`.`id` = 1 AND `fruit`.`id` = 2",
].join(" ")
);
}
#[test]
fn select_with_2() {
assert_eq!(
cake::Entity::find()
.left_join(fruit::Entity)
.select_with(fruit::Entity)
.filter(cake::Column::Id.eq(1))
.filter(fruit::Column::Id.eq(2))
.build(MysqlQueryBuilder)
.to_string(),
[
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,",
"`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`",

View File

@ -1,4 +1,4 @@
use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo};
use crate::{EntityTrait, QuerySelect, Related, Select, SelectTwo, SelectTwoMany};
pub use sea_query::JoinType;
impl<E> Select<E>
@ -48,6 +48,15 @@ where
{
self.left_join(r).select_also(r)
}
/// Left Join with a Related Entity and select the related Entity as a `Vec`
pub fn left_join_and_select_with<R>(self, r: R) -> SelectTwoMany<E, R>
where
R: EntityTrait,
E: Related<R>,
{
self.left_join(r).select_with(r)
}
}
#[cfg(test)]

View File

@ -23,6 +23,16 @@ where
pub(crate) entity: PhantomData<(E, F)>,
}
#[derive(Clone, Debug)]
pub struct SelectTwoMany<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
pub(crate) query: SelectStatement,
pub(crate) entity: PhantomData<(E, F)>,
}
pub trait IntoSimpleExpr {
fn into_simple_expr(self) -> SimpleExpr;
}
@ -51,6 +61,18 @@ macro_rules! impl_trait {
&mut self.query
}
}
impl<E, F> $trait for SelectTwoMany<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
type QueryStatement = SelectStatement;
fn query(&mut self) -> &mut SelectStatement {
&mut self.query
}
}
};
}
@ -118,19 +140,26 @@ where
}
}
impl<E, F> QueryTrait for SelectTwo<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
type QueryStatement = SelectStatement;
fn query(&mut self) -> &mut SelectStatement {
&mut self.query
}
fn as_query(&self) -> &SelectStatement {
&self.query
}
fn into_query(self) -> SelectStatement {
self.query
}
macro_rules! select_two {
( $selector: ident ) => {
impl<E, F> QueryTrait for $selector<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
type QueryStatement = SelectStatement;
fn query(&mut self) -> &mut SelectStatement {
&mut self.query
}
fn as_query(&self) -> &SelectStatement {
&self.query
}
fn into_query(self) -> SelectStatement {
self.query
}
}
};
}
select_two!(SelectTwo);
select_two!(SelectTwoMany);