SelectTwoMany
This commit is contained in:
parent
df7bb5c195
commit
a0db19758b
@ -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
|
||||
|
@ -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?;
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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`",
|
||||
|
@ -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)]
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user