diff --git a/sea-orm-macros/src/attributes.rs b/sea-orm-macros/src/attributes.rs index 6b545f68d7..f9e3bc4258 100644 --- a/sea-orm-macros/src/attributes.rs +++ b/sea-orm-macros/src/attributes.rs @@ -30,5 +30,6 @@ pub mod field_attr { pub from: Option, pub to: Option, pub fk_name: Option, + pub condition_type: Option, } } diff --git a/sea-orm-macros/src/derives/relation.rs b/sea-orm-macros/src/derives/relation.rs index 972d9e17c5..c0417664e7 100644 --- a/sea-orm-macros/src/derives/relation.rs +++ b/sea-orm-macros/src/derives/relation.rs @@ -163,6 +163,28 @@ impl DeriveRelation { result = quote! { #result.fk_name(#fk_name) }; } + if attr.condition_type.is_some() { + let condition_type = attr + .condition_type + .as_ref() + .map(|lit| { + match lit { + syn::Lit::Str(lit_str) => { + match lit_str.value().to_ascii_lowercase().as_str() { + "all" => Ok(quote!( sea_orm::sea_query::ConditionType::All )), + "any" => Ok(quote!( sea_orm::sea_query::ConditionType::Any )), + _ => Err(syn::Error::new_spanned(lit, "Condition type must be one of `all` or `any`")), + } + }, + _ => Err(syn::Error::new_spanned(lit, "attribute must be a string")), + } + }) + .ok_or_else(|| { + syn::Error::new_spanned(variant, "Missing value for 'condition_type'") + })??; + result = quote! { #result.condition_type(#condition_type) }; + } + result = quote! { #result.into() }; Result::<_, syn::Error>::Ok(result) diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 74e493055b..beb11c456a 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -1,8 +1,8 @@ use crate::{unpack_table_ref, EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select}; use core::marker::PhantomData; use sea_query::{ - Alias, Condition, DynIden, ForeignKeyCreateStatement, JoinType, SeaRc, TableForeignKey, - TableRef, + Alias, Condition, ConditionType, DynIden, ForeignKeyCreateStatement, JoinType, SeaRc, + TableForeignKey, TableRef, }; use std::fmt::Debug; @@ -68,6 +68,8 @@ pub struct RelationDef { pub on_condition: Option Condition + Send + Sync>>, /// The name of foreign key constraint pub fk_name: Option, + /// Condition type of join on expression + pub condition_type: ConditionType, } impl std::fmt::Debug for RelationDef { @@ -123,6 +125,7 @@ where on_update: Option, on_condition: Option Condition + Send + Sync>>, fk_name: Option, + condition_type: ConditionType, } impl std::fmt::Debug for RelationBuilder @@ -160,6 +163,7 @@ impl RelationDef { on_update: self.on_update, on_condition: self.on_condition, fk_name: None, + condition_type: self.condition_type, } } @@ -205,6 +209,42 @@ impl RelationDef { self.on_condition = Some(Box::new(f)); self } + + /// Set the condition type of join on expression + /// + /// # Examples + /// + /// ``` + /// use sea_orm::{entity::*, query::*, DbBackend, tests_cfg::{cake, cake_filling}}; + /// use sea_query::{Expr, IntoCondition, ConditionType}; + /// + /// assert_eq!( + /// cake::Entity::find() + /// .join( + /// JoinType::LeftJoin, + /// cake_filling::Relation::Cake + /// .def() + /// .rev() + /// .condition_type(ConditionType::Any) + /// .on_condition(|_left, right| { + /// Expr::col((right, cake_filling::Column::CakeId)) + /// .gt(10i32) + /// .into_condition() + /// }) + /// ) + /// .build(DbBackend::MySql) + /// .to_string(), + /// [ + /// "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", + /// "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` OR `cake_filling`.`cake_id` > 10", + /// ] + /// .join(" ") + /// ); + /// ``` + pub fn condition_type(mut self, condition_type: ConditionType) -> Self { + self.condition_type = condition_type; + self + } } impl RelationBuilder @@ -225,6 +265,7 @@ where on_update: None, on_condition: None, fk_name: None, + condition_type: ConditionType::All, } } @@ -241,6 +282,7 @@ where on_update: None, on_condition: None, fk_name: None, + condition_type: ConditionType::All, } } @@ -291,6 +333,12 @@ where self.fk_name = Some(fk_name.to_owned()); self } + + /// Set the condition type of join on expression + pub fn condition_type(mut self, condition_type: ConditionType) -> Self { + self.condition_type = condition_type; + self + } } impl From> for RelationDef @@ -310,6 +358,7 @@ where on_update: b.on_update, on_condition: b.on_condition, fk_name: b.fk_name, + condition_type: b.condition_type, } } } @@ -371,7 +420,7 @@ impl From for ForeignKeyCreateStatement { /// Creates a column definition for example to update a table. /// ``` -/// use sea_query::{Alias, IntoIden, MysqlQueryBuilder, TableAlterStatement, TableRef}; +/// use sea_query::{Alias, IntoIden, MysqlQueryBuilder, TableAlterStatement, TableRef, ConditionType}; /// use sea_orm::{EnumIter, Iden, Identity, PrimaryKeyTrait, RelationDef, RelationTrait, RelationType}; /// /// let relation = RelationDef { @@ -385,6 +434,7 @@ impl From for ForeignKeyCreateStatement { /// on_update: None, /// on_condition: None, /// fk_name: Some("foo-bar".to_string()), +/// condition_type: ConditionType::All, /// }; /// /// let mut alter_table = TableAlterStatement::new() diff --git a/src/query/helper.rs b/src/query/helper.rs index 2826b1171a..b0be04fe6e 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -3,8 +3,8 @@ use crate::{ PrimaryKeyToColumn, RelationDef, }; use sea_query::{ - Alias, Expr, Iden, IntoCondition, IntoIden, LockType, SeaRc, SelectExpr, SelectStatement, - SimpleExpr, TableRef, + Alias, ConditionType, Expr, Iden, IntoCondition, IntoIden, LockType, SeaRc, SelectExpr, + SelectStatement, SimpleExpr, TableRef, }; pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement}; @@ -667,7 +667,12 @@ pub(crate) fn join_condition(mut rel: RelationDef) -> Condition { let owner_keys = rel.from_col; let foreign_keys = rel.to_col; - let mut condition = Condition::all().add(join_tbl_on_condition( + let mut condition = match rel.condition_type { + ConditionType::All => Condition::all(), + ConditionType::Any => Condition::any(), + }; + + condition = condition.add(join_tbl_on_condition( SeaRc::clone(&from_tbl), SeaRc::clone(&to_tbl), owner_keys, diff --git a/src/query/join.rs b/src/query/join.rs index f7a81c9329..4256167262 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -117,7 +117,7 @@ mod tests { RelationTrait, }; use pretty_assertions::assert_eq; - use sea_query::{Alias, Expr, IntoCondition, JoinType}; + use sea_query::{Alias, ConditionType, Expr, IntoCondition, JoinType}; #[test] fn join_1() { @@ -559,4 +559,36 @@ mod tests { .join(" ") ); } + + #[test] + fn join_22() { + assert_eq!( + cake::Entity::find() + .column_as( + Expr::col((Alias::new("cake_filling_alias"), cake_filling::Column::CakeId)), + "cake_filling_cake_id" + ) + .join(JoinType::LeftJoin, cake::Relation::OrTropicalFruit.def()) + .join_as_rev( + JoinType::LeftJoin, + cake_filling::Relation::Cake + .def() + .condition_type(ConditionType::Any) + .on_condition(|left, _right| { + Expr::col((left, cake_filling::Column::CakeId)) + .gt(10) + .into_condition() + }), + Alias::new("cake_filling_alias") + ) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `cake`.`id`, `cake`.`name`, `cake_filling_alias`.`cake_id` AS `cake_filling_cake_id` FROM `cake`", + "LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` OR `fruit`.`name` LIKE '%tropical%'", + "LEFT JOIN `cake_filling` AS `cake_filling_alias` ON `cake_filling_alias`.`cake_id` = `cake`.`id` OR `cake_filling_alias`.`cake_id` > 10", + ] + .join(" ") + ); + } } diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index bb7b79528b..810bf33282 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -23,6 +23,12 @@ pub enum Relation { on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# )] TropicalFruit, + #[sea_orm( + has_many = "super::fruit::Entity", + condition_type = "any", + on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# + )] + OrTropicalFruit, } impl Related for Entity {