Select Both

This commit is contained in:
Chris Tsang 2021-05-15 15:00:07 +08:00
parent 9dc7c818d7
commit abc11753a8
11 changed files with 262 additions and 76 deletions

View File

@ -104,7 +104,7 @@ impl ModelTrait for Model {
}
}
fn from_query_result(row: QueryResult, pre: &str) -> Result<Self, TypeErr> {
fn from_query_result(row: &QueryResult, pre: &str) -> Result<Self, TypeErr> {
Ok(Self {
id: row.try_get(pre, Column::Id.as_str())?,
name: row.try_get(pre, Column::Name.as_str())?,

View File

@ -90,7 +90,7 @@ impl ModelTrait for Model {
}
}
fn from_query_result(row: QueryResult, pre: &str) -> Result<Self, TypeErr> {
fn from_query_result(row: &QueryResult, pre: &str) -> Result<Self, TypeErr> {
Ok(Self {
id: row.try_get(pre, Column::Id.as_str())?,
name: row.try_get(pre, Column::Name.as_str())?,

View File

@ -14,13 +14,22 @@ async fn main() {
.await
.unwrap();
println!("{:?}", db);
println!();
println!("{:?}\n", db);
println!("===== =====\n");
find_all(&db).await.unwrap();
println!("===== =====\n");
find_together(&db).await.unwrap();
println!("===== =====\n");
find_one(&db).await.unwrap();
println!("===== =====\n");
count_fruits_by_cake(&db).await.unwrap();
}
@ -31,8 +40,7 @@ async fn find_all(db: &Database) -> Result<(), QueryErr> {
println!();
for cc in cakes.iter() {
println!("{:?}", cc);
println!();
println!("{:?}\n", cc);
}
print!("find all fruits: ");
@ -41,8 +49,20 @@ async fn find_all(db: &Database) -> Result<(), QueryErr> {
println!();
for ff in fruits.iter() {
println!("{:?}", ff);
println!();
println!("{:?}\n", ff);
}
Ok(())
}
async fn find_together(db: &Database) -> Result<(), QueryErr> {
print!("find cakes and fruits: ");
let both = cake::Entity::find().left_join_and_select(fruit::Entity).all(db).await?;
println!();
for bb in both.iter() {
println!("{:?}\n", bb);
}
Ok(())
@ -74,8 +94,7 @@ async fn find_one(db: &Database) -> Result<(), QueryErr> {
println!();
for ff in fruits.iter() {
println!("{:?}", ff);
println!();
println!("{:?}\n", ff);
}
Ok(())
@ -93,7 +112,7 @@ async fn count_fruits_by_cake(db: &Database) -> Result<(), QueryErr> {
use sea_orm::{FromQueryResult, QueryResult, TypeErr};
impl FromQueryResult for SelectResult {
fn from_query_result(row: QueryResult, pre: &str) -> Result<Self, TypeErr> {
fn from_query_result(row: &QueryResult, pre: &str) -> Result<Self, TypeErr> {
Ok(Self {
name: row.try_get(pre, "name")?,
num_of_fruits: row.try_get(pre, "num_of_fruits")?,
@ -115,8 +134,7 @@ async fn count_fruits_by_cake(db: &Database) -> Result<(), QueryErr> {
println!();
for rr in results.iter() {
println!("{:?}", rr);
println!();
println!("{:?}\n", rr);
}
Ok(())

View File

@ -1,4 +1,5 @@
use crate::{Connection, Database, EntityTrait, FromQueryResult, QueryErr, Select, Statement};
use crate::query::combine;
use crate::{Connection, Database, EntityTrait, FromQueryResult, QueryErr, Select, SelectTwo, Statement};
use sea_query::{QueryBuilder, SelectStatement};
use std::marker::PhantomData;
@ -21,7 +22,7 @@ where
model: PhantomData<(M, N)>,
}
impl<E: 'static> Select<E>
impl<E> Select<E>
where
E: EntityTrait,
{
@ -44,6 +45,31 @@ where
}
}
impl<E, F> SelectTwo<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
fn into_model<M, N>(self) -> SelectTwoModel<M, N>
where
M: FromQueryResult,
N: FromQueryResult,
{
SelectTwoModel {
query: self.query,
model: PhantomData,
}
}
pub async fn one(self, db: &Database) -> Result<(E::Model, F::Model), QueryErr> {
self.into_model::<E::Model, F::Model>().one(db).await
}
pub async fn all(self, db: &Database) -> Result<Vec<(E::Model, F::Model)>, QueryErr> {
self.into_model::<E::Model, F::Model>().all(db).await
}
}
impl<M> SelectModel<M>
where
M: FromQueryResult,
@ -59,7 +85,7 @@ where
let builder = db.get_query_builder_backend();
self.query.limit(1);
let row = db.get_connection().query_one(self.build(builder)).await?;
Ok(M::from_query_result(row, "")?)
Ok(M::from_query_result(&row, "")?)
}
pub async fn all(self, db: &Database) -> Result<Vec<M>, QueryErr> {
@ -67,7 +93,37 @@ where
let rows = db.get_connection().query_all(self.build(builder)).await?;
let mut models = Vec::new();
for row in rows.into_iter() {
models.push(M::from_query_result(row, "")?);
models.push(M::from_query_result(&row, "")?);
}
Ok(models)
}
}
impl<M, N> SelectTwoModel<M, N>
where
M: FromQueryResult,
N: FromQueryResult,
{
pub fn build<B>(&self, builder: B) -> Statement
where
B: QueryBuilder,
{
self.query.build(builder).into()
}
pub async fn one(mut self, db: &Database) -> Result<(M, N), QueryErr> {
let builder = db.get_query_builder_backend();
self.query.limit(1);
let row = db.get_connection().query_one(self.build(builder)).await?;
Ok((M::from_query_result(&row, combine::SELECT_A)?, N::from_query_result(&row, combine::SELECT_B)?))
}
pub async fn all(self, db: &Database) -> Result<Vec<(M, N)>, QueryErr> {
let builder = db.get_query_builder_backend();
let rows = db.get_connection().query_all(self.build(builder)).await?;
let mut models = Vec::new();
for row in rows.into_iter() {
models.push((M::from_query_result(&row, combine::SELECT_A)?, N::from_query_result(&row, combine::SELECT_B)?));
}
Ok(models)
}

View File

@ -9,13 +9,13 @@ pub trait ModelTrait: Clone + Debug + Default {
fn set(&mut self, c: Self::Column, v: Value);
fn from_query_result(row: QueryResult, pre: &str) -> Result<Self, TypeErr>
fn from_query_result(row: &QueryResult, pre: &str) -> Result<Self, TypeErr>
where
Self: Sized;
}
pub trait FromQueryResult {
fn from_query_result(row: QueryResult, pre: &str) -> Result<Self, TypeErr>
fn from_query_result(row: &QueryResult, pre: &str) -> Result<Self, TypeErr>
where
Self: Sized;
}
@ -24,7 +24,7 @@ impl<M> FromQueryResult for M
where
M: ModelTrait + Sized,
{
fn from_query_result(row: QueryResult, pre: &str) -> Result<M, TypeErr> {
fn from_query_result(row: &QueryResult, pre: &str) -> Result<M, TypeErr> {
<Self as ModelTrait>::from_query_result(row, pre)
}
}

123
src/query/combine.rs Normal file
View File

@ -0,0 +1,123 @@
use crate::{EntityTrait, IntoSimpleExpr, Iterable, Select, SelectTwo};
use core::marker::PhantomData;
pub use sea_query::JoinType;
use sea_query::{Alias, ColumnRef, Iden, SelectExpr, SelectStatement, SimpleExpr};
use std::rc::Rc;
pub const SELECT_A: &str = "A_";
pub const SELECT_B: &str = "B_";
impl<E> Select<E>
where
E: EntityTrait,
{
fn apply_alias(mut self, pre: &str) -> Self {
self.query().exprs_mut_for_each(|sel| {
match &sel.alias {
Some(alias) => {
let alias = format!("{}{}", pre, alias.to_string().as_str());
sel.alias = Some(Rc::new(Alias::new(&alias)));
}
None => {
let col = match &sel.expr {
SimpleExpr::Column(col_ref) => match &col_ref {
ColumnRef::Column(col) => col,
ColumnRef::TableColumn(_, col) => col,
},
_ => panic!("cannot apply alias for expr other than Column"),
};
let alias = format!("{}{}", pre, col.to_string().as_str());
sel.alias = Some(Rc::new(Alias::new(&alias)));
}
};
});
self
}
pub fn select_also<F>(mut self, _: F) -> SelectTwo<E, F>
where
F: EntityTrait,
{
self = self.apply_alias(SELECT_A);
SelectTwo::new(self.into_query())
}
}
impl<E, F> SelectTwo<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
pub(crate) fn new(query: SelectStatement) -> Self {
let myself = Self {
query,
entity: PhantomData,
};
myself.prepare_select()
}
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(Rc::new(Alias::new(&alias))),
});
}
self
}
}
#[cfg(test)]
mod tests {
use crate::tests_cfg::{cake, fruit};
use crate::{EntityTrait, ColumnTrait, QueryHelper};
use sea_query::MysqlQueryBuilder;
#[test]
fn alias_1() {
assert_eq!(
cake::Entity::find()
.column_as(cake::Column::Id, "B")
.apply_alias("A_")
.build(MysqlQueryBuilder)
.to_string(),
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `cake`.`id` AS `A_B` FROM `cake`",
);
}
#[test]
fn select_also_1() {
assert_eq!(
cake::Entity::find()
.left_join(fruit::Entity)
.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_also_2() {
assert_eq!(
cake::Entity::find()
.left_join(fruit::Entity)
.select_also(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`",
"FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`",
"WHERE `cake`.`id` = 1 AND `fruit`.`id` = 2",
].join(" ")
);
}
}

View File

@ -1,6 +1,6 @@
use crate::{
ColumnTrait, EntityTrait, Identity, Iterable, ModelTrait, PrimaryKeyOfModel, QueryHelper,
Related, RelationDef, Select,
Related, RelationDef, Select, SelectTwo
};
pub use sea_query::JoinType;
@ -91,6 +91,15 @@ where
{
self.join_rev(JoinType::InnerJoin, R::to())
}
/// Left Join with a Related Entity and select both Entity.
pub fn left_join_and_select<R>(self, r: R) -> SelectTwo<E, R>
where
R: EntityTrait,
E: Related<R>,
{
self.left_join(r).select_also(r)
}
}
#[cfg(test)]
@ -182,16 +191,4 @@ mod tests {
.join(" ")
);
}
#[test]
fn alias_1() {
assert_eq!(
cake::Entity::find()
.column_as(cake::Column::Id, "B")
.apply_alias("A_")
.build(MysqlQueryBuilder)
.to_string(),
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `cake`.`id` AS `A_B` FROM `cake`",
);
}
}

View File

@ -1,8 +1,10 @@
pub(crate) mod combine;
mod helper;
mod join;
mod result;
mod select;
pub use combine::*;
pub use helper::*;
pub use join::*;
pub use result::*;

View File

@ -2,9 +2,7 @@ use crate::{ColumnTrait, EntityTrait, Iterable, QueryHelper, Statement};
use core::fmt::Debug;
use core::marker::PhantomData;
pub use sea_query::JoinType;
use sea_query::{
Alias, ColumnRef, Iden, IntoColumnRef, IntoIden, QueryBuilder, SelectStatement, SimpleExpr,
};
use sea_query::{Iden, IntoColumnRef, IntoIden, QueryBuilder, SelectStatement, SimpleExpr};
use std::rc::Rc;
#[derive(Clone, Debug)]
@ -91,30 +89,12 @@ where
self.query.from(E::default().into_iden());
self
}
}
pub(crate) fn apply_alias(mut self, pre: &str) -> Self {
self.query().exprs_mut_for_each(|sel| {
match &sel.alias {
Some(alias) => {
let alias = format!("{}{}", pre, alias.to_string().as_str());
sel.alias = Some(Rc::new(Alias::new(&alias)));
}
None => {
let col = match &sel.expr {
SimpleExpr::Column(col_ref) => match &col_ref {
ColumnRef::Column(col) => col,
ColumnRef::TableColumn(_, col) => col,
},
_ => panic!("cannot apply alias for expr other than Column"),
};
let alias = format!("{}{}", pre, col.to_string().as_str());
sel.alias = Some(Rc::new(Alias::new(&alias)));
}
};
});
self
}
impl<E> Select<E>
where
E: EntityTrait,
{
/// Get a mutable ref to the query builder
pub fn query(&mut self) -> &mut SelectStatement {
&mut self.query
@ -139,21 +119,31 @@ where
}
}
#[cfg(test)]
mod tests {
use crate::tests_cfg::cake;
use crate::{EntityTrait, QueryHelper};
use sea_query::MysqlQueryBuilder;
impl<E, F> SelectTwo<E, F>
where
E: EntityTrait,
F: EntityTrait,
{
/// Get a mutable ref to the query builder
pub fn query(&mut self) -> &mut SelectStatement {
&mut self.query
}
#[test]
fn alias_1() {
assert_eq!(
cake::Entity::find()
.column_as(cake::Column::Id, "B")
.apply_alias("A_")
.build(MysqlQueryBuilder)
.to_string(),
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `cake`.`id` AS `A_B` FROM `cake`",
);
/// Get an immutable ref to the query builder
pub fn as_query(&self) -> &SelectStatement {
&self.query
}
/// Take ownership of the query builder
pub fn into_query(self) -> SelectStatement {
self.query
}
/// Build the query as [`Statement`]
pub fn build<B>(&self, builder: B) -> Statement
where
B: QueryBuilder,
{
self.as_query().build(builder).into()
}
}

View File

@ -104,7 +104,7 @@ impl ModelTrait for Model {
}
}
fn from_query_result(row: QueryResult, pre: &str) -> Result<Self, TypeErr> {
fn from_query_result(row: &QueryResult, pre: &str) -> Result<Self, TypeErr> {
Ok(Self {
id: row.try_get(pre, Column::Id.as_str())?,
name: row.try_get(pre, Column::Name.as_str())?,

View File

@ -90,7 +90,7 @@ impl ModelTrait for Model {
}
}
fn from_query_result(row: QueryResult, pre: &str) -> Result<Self, TypeErr> {
fn from_query_result(row: &QueryResult, pre: &str) -> Result<Self, TypeErr> {
Ok(Self {
id: row.try_get(pre, Column::Id.as_str())?,
name: row.try_get(pre, Column::Name.as_str())?,