Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom join on conditions #793

Merged
merged 6 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sea-orm-macros/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod field_attr {
pub has_many: Option<syn::Lit>,
pub on_update: Option<syn::Lit>,
pub on_delete: Option<syn::Lit>,
pub on_condition: Option<syn::Lit>,
pub from: Option<syn::Lit>,
pub to: Option<syn::Lit>,
pub fk_name: Option<syn::Lit>,
Expand Down
11 changes: 11 additions & 0 deletions sea-orm-macros/src/derives/relation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ impl DeriveRelation {
result = quote! { #result.on_delete(sea_orm::prelude::ForeignKeyAction::#on_delete) };
}

if attr.on_condition.is_some() {
let on_condition = attr
.on_condition
.as_ref()
.map(Self::parse_lit_string)
.ok_or_else(|| {
syn::Error::new_spanned(variant, "Missing value for 'on_condition'")
})??;
result = quote! { #result.on_condition(|_, _| sea_orm::sea_query::IntoCondition::into_condition(#on_condition)) };
}

if attr.fk_name.is_some() {
let fk_name = attr
.fk_name
Expand Down
22 changes: 15 additions & 7 deletions src/entity/link.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
join_tbl_on_condition, unpack_table_ref, EntityTrait, QuerySelect, RelationDef, Select,
};
use sea_query::{Alias, IntoIden, JoinType, SeaRc};
use sea_query::{Alias, Condition, IntoIden, JoinType, SeaRc};

/// Same as [RelationDef]
pub type LinkDef = RelationDef;
Expand All @@ -20,20 +20,28 @@ pub trait Linked {
/// Find all the Entities that are linked to the Entity
fn find_linked(&self) -> Select<Self::ToEntity> {
let mut select = Select::new();
for (i, rel) in self.link().into_iter().rev().enumerate() {
for (i, mut rel) in self.link().into_iter().rev().enumerate() {
let from_tbl = Alias::new(&format!("r{}", i)).into_iden();
let to_tbl = if i > 0 {
Alias::new(&format!("r{}", i - 1)).into_iden()
} else {
unpack_table_ref(&rel.to_tbl)
};
let table_ref = rel.from_tbl;

select.query().join_as(
JoinType::InnerJoin,
rel.from_tbl,
let mut condition = Condition::all().add(join_tbl_on_condition(
SeaRc::clone(&from_tbl),
join_tbl_on_condition(from_tbl, to_tbl, rel.from_col, rel.to_col),
);
SeaRc::clone(&to_tbl),
rel.from_col,
rel.to_col,
));
if let Some(f) = rel.on_condition.take() {
condition = condition.add(f(SeaRc::clone(&from_tbl), SeaRc::clone(&to_tbl)));
}

select
.query()
.join_as(JoinType::InnerJoin, table_ref, from_tbl, condition);
}
select
}
Expand Down
31 changes: 28 additions & 3 deletions src/entity/relation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select};
use core::marker::PhantomData;
use sea_query::{JoinType, TableRef};
use sea_query::{Condition, DynIden, JoinType, TableRef};
use std::fmt::Debug;

/// Defines the type of relationship
Expand Down Expand Up @@ -42,7 +42,7 @@ where
}

/// Defines a relationship
#[derive(Debug)]
#[allow(missing_debug_implementations)]
billy1624 marked this conversation as resolved.
Show resolved Hide resolved
pub struct RelationDef {
/// The type of relationship defined in [RelationType]
pub rel_type: RelationType,
Expand All @@ -62,12 +62,14 @@ pub struct RelationDef {
/// Defines an operation to be performed on a Foreign Key when a
/// `UPDATE` Operation is performed
pub on_update: Option<ForeignKeyAction>,
/// Custom join ON condition
pub on_condition: Option<Box<dyn Fn(DynIden, DynIden) -> Condition>>,
/// The name of foreign key constraint
pub fk_name: Option<String>,
}

/// Defines a helper to build a relation
#[derive(Debug)]
#[allow(missing_debug_implementations)]
pub struct RelationBuilder<E, R>
where
E: EntityTrait,
Expand All @@ -82,6 +84,7 @@ where
is_owner: bool,
on_delete: Option<ForeignKeyAction>,
on_update: Option<ForeignKeyAction>,
on_condition: Option<Box<dyn Fn(DynIden, DynIden) -> Condition>>,
fk_name: Option<String>,
}

Expand All @@ -97,9 +100,19 @@ impl RelationDef {
is_owner: !self.is_owner,
on_delete: self.on_delete,
on_update: self.on_update,
on_condition: self.on_condition,
fk_name: None,
}
}

/// Set custom join ON condition
billy1624 marked this conversation as resolved.
Show resolved Hide resolved
pub fn on_condition<F>(mut self, f: F) -> Self
where
F: Fn(DynIden, DynIden) -> Condition + 'static,
billy1624 marked this conversation as resolved.
Show resolved Hide resolved
{
self.on_condition = Some(Box::new(f));
self
}
}

impl<E, R> RelationBuilder<E, R>
Expand All @@ -118,6 +131,7 @@ where
is_owner,
on_delete: None,
on_update: None,
on_condition: None,
fk_name: None,
}
}
Expand All @@ -133,6 +147,7 @@ where
is_owner,
on_delete: None,
on_update: None,
on_condition: None,
fk_name: None,
}
}
Expand Down Expand Up @@ -167,6 +182,15 @@ where
self
}

/// Set custom join ON condition
billy1624 marked this conversation as resolved.
Show resolved Hide resolved
pub fn on_condition<F>(mut self, f: F) -> Self
where
F: Fn(DynIden, DynIden) -> Condition + 'static,
{
self.on_condition = Some(Box::new(f));
self
}

/// Set the name of foreign key constraint
pub fn fk_name(mut self, fk_name: &str) -> Self {
self.fk_name = Some(fk_name.to_owned());
Expand All @@ -189,6 +213,7 @@ where
is_owner: b.is_owner,
on_delete: b.on_delete,
on_update: b.on_update,
on_condition: b.on_condition,
fk_name: b.fk_name,
}
}
Expand Down
14 changes: 12 additions & 2 deletions src/query/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,13 +430,23 @@ pub trait QueryFilter: Sized {
}
}

pub(crate) fn join_condition(rel: RelationDef) -> SimpleExpr {
pub(crate) fn join_condition(mut rel: RelationDef) -> Condition {
let from_tbl = unpack_table_ref(&rel.from_tbl);
let to_tbl = unpack_table_ref(&rel.to_tbl);
let owner_keys = rel.from_col;
let foreign_keys = rel.to_col;

join_tbl_on_condition(from_tbl, to_tbl, owner_keys, foreign_keys)
let mut condition = Condition::all().add(join_tbl_on_condition(
SeaRc::clone(&from_tbl),
SeaRc::clone(&to_tbl),
owner_keys,
foreign_keys,
));
if let Some(f) = rel.on_condition.take() {
condition = condition.add(f(from_tbl, to_tbl));
}

condition
}

pub(crate) fn join_tbl_on_condition(
Expand Down
85 changes: 77 additions & 8 deletions src/query/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
Linked, QuerySelect, Related, Select, SelectA, SelectB, SelectTwo, SelectTwoMany,
};
pub use sea_query::JoinType;
use sea_query::{Alias, DynIden, Expr, IntoIden, SeaRc, SelectExpr};
use sea_query::{Alias, Condition, DynIden, Expr, IntoIden, SeaRc, SelectExpr};

impl<E> Select<E>
where
Expand Down Expand Up @@ -69,20 +69,27 @@ where
T: EntityTrait,
{
let mut slf = self;
for (i, rel) in l.link().into_iter().enumerate() {
for (i, mut rel) in l.link().into_iter().enumerate() {
let to_tbl = Alias::new(&format!("r{}", i)).into_iden();
let from_tbl = if i > 0 {
Alias::new(&format!("r{}", i - 1)).into_iden()
} else {
unpack_table_ref(&rel.from_tbl)
};
let table_ref = rel.to_tbl;

slf.query().join_as(
JoinType::LeftJoin,
rel.to_tbl,
let mut condition = Condition::all().add(join_tbl_on_condition(
SeaRc::clone(&from_tbl),
SeaRc::clone(&to_tbl),
join_tbl_on_condition(from_tbl, to_tbl, rel.from_col, rel.to_col),
);
rel.from_col,
rel.to_col,
));
if let Some(f) = rel.on_condition.take() {
condition = condition.add(f(SeaRc::clone(&from_tbl), SeaRc::clone(&to_tbl)));
}

slf.query()
.join_as(JoinType::LeftJoin, table_ref, to_tbl, condition);
}
slf = slf.apply_alias(SelectA.as_str());
let text_type = SeaRc::new(Alias::new("text")) as DynIden;
Expand Down Expand Up @@ -112,8 +119,12 @@ where
#[cfg(test)]
mod tests {
use crate::tests_cfg::{cake, cake_filling, cake_filling_price, entity_linked, filling, fruit};
use crate::{ColumnTrait, DbBackend, EntityTrait, ModelTrait, QueryFilter, QueryTrait};
use crate::{
ColumnTrait, DbBackend, EntityTrait, ModelTrait, QueryFilter, QuerySelect, QueryTrait,
RelationTrait,
};
use pretty_assertions::assert_eq;
use sea_query::JoinType;

#[test]
fn join_1() {
Expand Down Expand Up @@ -355,4 +366,62 @@ mod tests {
.join(" ")
);
}

#[test]
fn join_14() {
assert_eq!(
cake::Entity::find()
.join(JoinType::LeftJoin, cake::Relation::TropicalFruit.def())
.build(DbBackend::MySql)
.to_string(),
[
"SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
"LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE '%tropical%'",
]
.join(" ")
);
}

#[test]
fn join_15() {
let cake_model = cake::Model {
id: 18,
name: "".to_owned(),
};

assert_eq!(
cake_model
.find_linked(entity_linked::CheeseCakeToFillingVendor)
.build(DbBackend::MySql)
.to_string(),
[
r#"SELECT `vendor`.`id`, `vendor`.`name`"#,
r#"FROM `vendor`"#,
r#"INNER JOIN `filling` AS `r0` ON `r0`.`vendor_id` = `vendor`.`id`"#,
r#"INNER JOIN `cake_filling` AS `r1` ON `r1`.`filling_id` = `r0`.`id`"#,
r#"INNER JOIN `cake` AS `r2` ON `r2`.`id` = `r1`.`cake_id` AND `r2`.`name` LIKE '%cheese%'"#,
r#"WHERE `r2`.`id` = 18"#,
]
.join(" ")
);
}

#[test]
fn join_16() {
assert_eq!(
cake::Entity::find()
.find_also_linked(entity_linked::CheeseCakeToFillingVendor)
.build(DbBackend::MySql)
.to_string(),
[
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` AND `cake`.`name` LIKE '%cheese%'"#,
r#"LEFT JOIN `filling` AS `r1` ON `r0`.`filling_id` = `r1`.`id`"#,
r#"LEFT JOIN `vendor` AS `r2` ON `r1`.`vendor_id` = `r2`.`id`"#,
]
.join(" ")
);
}
}
5 changes: 5 additions & 0 deletions src/tests_cfg/cake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ pub struct Model {
pub enum Relation {
#[sea_orm(has_many = "super::fruit::Entity")]
Fruit,
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
)]
TropicalFruit,
}

impl Related<super::fruit::Entity> for Entity {
Expand Down
25 changes: 25 additions & 0 deletions src/tests_cfg/entity_linked.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::entity::prelude::*;
use sea_query::{Expr, IntoCondition};

#[derive(Debug)]
pub struct CakeToFilling;
Expand Down Expand Up @@ -32,3 +33,27 @@ impl Linked for CakeToFillingVendor {
]
}
}

#[derive(Debug)]
pub struct CheeseCakeToFillingVendor;

impl Linked for CheeseCakeToFillingVendor {
type FromEntity = super::cake::Entity;

type ToEntity = super::vendor::Entity;

fn link(&self) -> Vec<RelationDef> {
vec![
super::cake_filling::Relation::Cake
.def()
.on_condition(|left, _| {
Expr::tbl(left, super::cake::Column::Name)
.like("%cheese%")
.into_condition()
})
.rev(),
super::cake_filling::Relation::Filling.def(),
super::filling::Relation::Vendor.def(),
]
}
}
billy1624 marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 1 addition & 5 deletions tests/common/features/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,11 +298,7 @@ pub async fn create_json_vec_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(json_vec::Column::StrVec)
.string()
.not_null(),
)
.col(ColumnDef::new(json_vec::Column::StrVec).string().not_null())
.to_owned();

create_table(db, &create_table_stmt, JsonVec).await
Expand Down
Loading