sea_orm/entity/
relation.rs

1use crate::{
2    EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select, join_tbl_on_condition,
3};
4use core::marker::PhantomData;
5use sea_query::{
6    Condition, ConditionType, DynIden, ForeignKeyCreateStatement, IntoIden, JoinType,
7    TableForeignKey, TableRef,
8};
9use std::{fmt::Debug, sync::Arc};
10
11/// Defines the type of relationship
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13pub enum RelationType {
14    /// An Entity has one relationship
15    HasOne,
16    /// An Entity has many relationships
17    HasMany,
18}
19
20/// Action to perform on a foreign key whenever there are changes
21/// to an ActiveModel
22pub type ForeignKeyAction = sea_query::ForeignKeyAction;
23
24/// Defines the relations of an Entity
25pub trait RelationTrait: Iterable + Debug + 'static {
26    /// Creates a [`RelationDef`]
27    fn def(&self) -> RelationDef;
28
29    /// Name of the relation enum
30    fn name(&self) -> String {
31        format!("{self:?}")
32    }
33}
34
35/// Checks if Entities are related
36pub trait Related<R>
37where
38    R: EntityTrait,
39{
40    /// Check if an entity is related to another entity
41    fn to() -> RelationDef;
42
43    /// Check if an entity is related through another entity
44    fn via() -> Option<RelationDef> {
45        None
46    }
47
48    /// Find related Entities
49    fn find_related() -> Select<R> {
50        Select::<R>::new().join_join_rev(JoinType::InnerJoin, Self::to(), Self::via())
51    }
52}
53
54/// Defines a relationship
55#[derive(derive_more::Debug, Clone)]
56pub struct RelationDef {
57    /// The type of relationship defined in [RelationType]
58    pub rel_type: RelationType,
59    /// Reference from another Entity
60    pub from_tbl: TableRef,
61    /// Reference to another Entity
62    pub to_tbl: TableRef,
63    /// Reference to from a Column
64    pub from_col: Identity,
65    /// Reference to another column
66    pub to_col: Identity,
67    /// Defines the owner of the Relation
68    pub is_owner: bool,
69    /// Specifies if the foreign key should be skipped
70    pub skip_fk: bool,
71    /// Defines an operation to be performed on a Foreign Key when a
72    /// `DELETE` Operation is performed
73    pub on_delete: Option<ForeignKeyAction>,
74    /// Defines an operation to be performed on a Foreign Key when a
75    /// `UPDATE` Operation is performed
76    pub on_update: Option<ForeignKeyAction>,
77    /// Custom join ON condition
78    #[debug("{}", on_condition.is_some())]
79    pub on_condition: Option<Arc<dyn Fn(DynIden, DynIden) -> Condition + Send + Sync>>,
80    /// The name of foreign key constraint
81    pub fk_name: Option<String>,
82    /// Condition type of join on expression
83    pub condition_type: ConditionType,
84}
85
86/// Idiomatically generate the join condition.
87///
88/// This allows using [RelationDef] directly where [`sea_query`] expects an [`IntoCondition`].
89///
90/// ## Examples
91///
92/// ```
93/// use sea_orm::tests_cfg::{cake, fruit};
94/// use sea_orm::{entity::*, sea_query::*};
95///
96/// let query = Query::select()
97///     .from(fruit::Entity)
98///     .inner_join(cake::Entity, fruit::Relation::Cake.def())
99///     .to_owned();
100///
101/// assert_eq!(
102///     query.to_string(MysqlQueryBuilder),
103///     r#"SELECT  FROM `fruit` INNER JOIN `cake` ON `fruit`.`cake_id` = `cake`.`id`"#
104/// );
105/// assert_eq!(
106///     query.to_string(PostgresQueryBuilder),
107///     r#"SELECT  FROM "fruit" INNER JOIN "cake" ON "fruit"."cake_id" = "cake"."id""#
108/// );
109/// assert_eq!(
110///     query.to_string(SqliteQueryBuilder),
111///     r#"SELECT  FROM "fruit" INNER JOIN "cake" ON "fruit"."cake_id" = "cake"."id""#
112/// );
113/// ```
114impl From<RelationDef> for Condition {
115    fn from(mut rel: RelationDef) -> Condition {
116        // Use table alias (if any) to construct the join condition
117        let from_tbl = match rel.from_tbl.sea_orm_table_alias() {
118            Some(alias) => alias,
119            None => rel.from_tbl.sea_orm_table(),
120        };
121        let to_tbl = match rel.to_tbl.sea_orm_table_alias() {
122            Some(alias) => alias,
123            None => rel.to_tbl.sea_orm_table(),
124        };
125        let owner_keys = rel.from_col;
126        let foreign_keys = rel.to_col;
127
128        let mut condition = match rel.condition_type {
129            ConditionType::All => Condition::all(),
130            ConditionType::Any => Condition::any(),
131        };
132
133        condition = condition.add(join_tbl_on_condition(
134            from_tbl.clone(),
135            to_tbl.clone(),
136            owner_keys,
137            foreign_keys,
138        ));
139        if let Some(f) = rel.on_condition.take() {
140            condition = condition.add(f(from_tbl.clone(), to_tbl.clone()));
141        }
142
143        condition
144    }
145}
146
147/// Defines a helper to build a relation
148#[derive(derive_more::Debug)]
149pub struct RelationBuilder<E, R>
150where
151    E: EntityTrait,
152    R: EntityTrait,
153{
154    entities: PhantomData<(E, R)>,
155    rel_type: RelationType,
156    from_tbl: TableRef,
157    to_tbl: TableRef,
158    from_col: Option<Identity>,
159    to_col: Option<Identity>,
160    is_owner: bool,
161    skip_fk: bool,
162    on_delete: Option<ForeignKeyAction>,
163    on_update: Option<ForeignKeyAction>,
164    #[debug("{}", on_condition.is_some())]
165    on_condition: Option<Arc<dyn Fn(DynIden, DynIden) -> Condition + Send + Sync>>,
166    fk_name: Option<String>,
167    condition_type: ConditionType,
168}
169
170impl RelationDef {
171    /// Reverse this relation (swap from and to)
172    pub fn rev(self) -> Self {
173        Self {
174            rel_type: self.rel_type,
175            from_tbl: self.to_tbl,
176            to_tbl: self.from_tbl,
177            from_col: self.to_col,
178            to_col: self.from_col,
179            is_owner: !self.is_owner,
180            skip_fk: self.skip_fk,
181            on_delete: self.on_delete,
182            on_update: self.on_update,
183            on_condition: self.on_condition,
184            fk_name: None,
185            condition_type: self.condition_type,
186        }
187    }
188
189    /// Express the relation from a table alias.
190    ///
191    /// This is a shorter and more discoverable equivalent to modifying `from_tbl` field by hand.
192    ///
193    /// # Examples
194    ///
195    /// Here's a short synthetic example.
196    /// In real life you'd use aliases when the table name comes up twice and you need to disambiguate,
197    /// e.g. https://github.com/SeaQL/sea-orm/discussions/2133
198    ///
199    /// ```
200    /// use sea_orm::{
201    ///     DbBackend,
202    ///     entity::*,
203    ///     query::*,
204    ///     tests_cfg::{cake, cake_filling},
205    /// };
206    /// use sea_query::Alias;
207    ///
208    /// let cf = "cf";
209    ///
210    /// assert_eq!(
211    ///     cake::Entity::find()
212    ///         .join_as(
213    ///             JoinType::LeftJoin,
214    ///             cake_filling::Relation::Cake.def().rev(),
215    ///             cf.clone()
216    ///         )
217    ///         .join(
218    ///             JoinType::LeftJoin,
219    ///             cake_filling::Relation::Filling.def().from_alias(cf)
220    ///         )
221    ///         .build(DbBackend::MySql)
222    ///         .to_string(),
223    ///     [
224    ///         "SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
225    ///         "LEFT JOIN `cake_filling` AS `cf` ON `cake`.`id` = `cf`.`cake_id`",
226    ///         "LEFT JOIN `filling` ON `cf`.`filling_id` = `filling`.`id`",
227    ///     ]
228    ///     .join(" ")
229    /// );
230    /// ```
231    pub fn from_alias<A>(mut self, alias: A) -> Self
232    where
233        A: IntoIden,
234    {
235        self.from_tbl = self.from_tbl.alias(alias);
236        self
237    }
238
239    /// Set custom join ON condition.
240    ///
241    /// This method takes a closure with two parameters
242    /// denoting the left-hand side and right-hand side table in the join expression.
243    ///
244    /// This replaces the current condition if it is already set.
245    ///
246    /// # Examples
247    ///
248    /// ```
249    /// use sea_orm::{entity::*, query::*, DbBackend, tests_cfg::{cake, cake_filling}};
250    /// use sea_query::{Expr, ExprTrait, IntoCondition};
251    ///
252    /// assert_eq!(
253    ///     cake::Entity::find()
254    ///         .join(
255    ///             JoinType::LeftJoin,
256    ///             cake_filling::Relation::Cake
257    ///                 .def()
258    ///                 .rev()
259    ///                 .on_condition(|_left, right| {
260    ///                     Expr::col((right, cake_filling::Column::CakeId))
261    ///                         .gt(10i32)
262    ///                         .into_condition()
263    ///                 })
264    ///         )
265    ///         .build(DbBackend::MySql)
266    ///         .to_string(),
267    ///     [
268    ///         "SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
269    ///         "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` AND `cake_filling`.`cake_id` > 10",
270    ///     ]
271    ///     .join(" ")
272    /// );
273    /// ```
274    pub fn on_condition<F>(mut self, f: F) -> Self
275    where
276        F: Fn(DynIden, DynIden) -> Condition + 'static + Send + Sync,
277    {
278        self.on_condition = Some(Arc::new(f));
279        self
280    }
281
282    /// Set the condition type of join on expression
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use sea_orm::{entity::*, query::*, DbBackend, tests_cfg::{cake, cake_filling}};
288    /// use sea_query::{Expr, ExprTrait, IntoCondition, ConditionType};
289    ///
290    /// assert_eq!(
291    ///     cake::Entity::find()
292    ///         .join(
293    ///             JoinType::LeftJoin,
294    ///             cake_filling::Relation::Cake
295    ///                 .def()
296    ///                 .rev()
297    ///                 .condition_type(ConditionType::Any)
298    ///                 .on_condition(|_left, right| {
299    ///                     Expr::col((right, cake_filling::Column::CakeId))
300    ///                         .gt(10i32)
301    ///                         .into_condition()
302    ///                 })
303    ///         )
304    ///         .build(DbBackend::MySql)
305    ///         .to_string(),
306    ///     [
307    ///         "SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
308    ///         "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` OR `cake_filling`.`cake_id` > 10",
309    ///     ]
310    ///     .join(" ")
311    /// );
312    /// ```
313    pub fn condition_type(mut self, condition_type: ConditionType) -> Self {
314        self.condition_type = condition_type;
315        self
316    }
317}
318
319impl<E, R> RelationBuilder<E, R>
320where
321    E: EntityTrait,
322    R: EntityTrait,
323{
324    pub(crate) fn new(rel_type: RelationType, from: E, to: R, is_owner: bool) -> Self {
325        Self {
326            entities: PhantomData,
327            rel_type,
328            from_tbl: from.table_ref(),
329            to_tbl: to.table_ref(),
330            from_col: None,
331            to_col: None,
332            is_owner,
333            skip_fk: false,
334            on_delete: None,
335            on_update: None,
336            on_condition: None,
337            fk_name: None,
338            condition_type: ConditionType::All,
339        }
340    }
341
342    pub(crate) fn from_rel(rel_type: RelationType, rel: RelationDef, is_owner: bool) -> Self {
343        Self {
344            entities: PhantomData,
345            rel_type,
346            from_tbl: rel.from_tbl,
347            to_tbl: rel.to_tbl,
348            from_col: Some(rel.from_col),
349            to_col: Some(rel.to_col),
350            is_owner,
351            skip_fk: false,
352            on_delete: None,
353            on_update: None,
354            on_condition: None,
355            fk_name: None,
356            condition_type: ConditionType::All,
357        }
358    }
359
360    /// Build a relationship from an Entity
361    pub fn from<T>(mut self, identifier: T) -> Self
362    where
363        T: IdentityOf<E>,
364    {
365        self.from_col = Some(identifier.identity_of());
366        self
367    }
368
369    /// Build a relationship to an Entity
370    pub fn to<T>(mut self, identifier: T) -> Self
371    where
372        T: IdentityOf<R>,
373    {
374        self.to_col = Some(identifier.identity_of());
375        self
376    }
377
378    /// Force the foreign key to not be created
379    pub fn skip_fk(mut self) -> Self {
380        self.skip_fk = true;
381        self
382    }
383
384    /// An operation to perform on a foreign key when a delete operation occurs
385    pub fn on_delete(mut self, action: ForeignKeyAction) -> Self {
386        self.on_delete = Some(action);
387        self
388    }
389
390    /// An operation to perform on a foreign key when an update operation occurs
391    pub fn on_update(mut self, action: ForeignKeyAction) -> Self {
392        self.on_update = Some(action);
393        self
394    }
395
396    /// Set custom join ON condition.
397    ///
398    /// This method takes a closure with parameters
399    /// denoting the left-hand side and right-hand side table in the join expression.
400    pub fn on_condition<F>(mut self, f: F) -> Self
401    where
402        F: Fn(DynIden, DynIden) -> Condition + 'static + Send + Sync,
403    {
404        self.on_condition = Some(Arc::new(f));
405        self
406    }
407
408    /// Set the name of foreign key constraint
409    pub fn fk_name(mut self, fk_name: &str) -> Self {
410        self.fk_name = Some(fk_name.to_owned());
411        self
412    }
413
414    /// Set the condition type of join on expression
415    pub fn condition_type(mut self, condition_type: ConditionType) -> Self {
416        self.condition_type = condition_type;
417        self
418    }
419}
420
421impl<E, R> From<RelationBuilder<E, R>> for RelationDef
422where
423    E: EntityTrait,
424    R: EntityTrait,
425{
426    fn from(b: RelationBuilder<E, R>) -> Self {
427        RelationDef {
428            rel_type: b.rel_type,
429            from_tbl: b.from_tbl,
430            to_tbl: b.to_tbl,
431            from_col: b.from_col.expect("Reference column is not set"),
432            to_col: b.to_col.expect("Owner column is not set"),
433            is_owner: b.is_owner,
434            skip_fk: b.skip_fk,
435            on_delete: b.on_delete,
436            on_update: b.on_update,
437            on_condition: b.on_condition,
438            fk_name: b.fk_name,
439            condition_type: b.condition_type,
440        }
441    }
442}
443
444macro_rules! set_foreign_key_stmt {
445    ( $relation: ident, $foreign_key: ident ) => {
446        let from_cols: Vec<String> = $relation
447            .from_col
448            .into_iter()
449            .map(|col| {
450                let col_name = col.to_string();
451                $foreign_key.from_col(col);
452                col_name
453            })
454            .collect();
455        for col in $relation.to_col.into_iter() {
456            $foreign_key.to_col(col);
457        }
458        if let Some(action) = $relation.on_delete {
459            $foreign_key.on_delete(action);
460        }
461        if let Some(action) = $relation.on_update {
462            $foreign_key.on_update(action);
463        }
464        let name = if let Some(name) = $relation.fk_name {
465            name
466        } else {
467            let from_tbl = &$relation.from_tbl.sea_orm_table().clone();
468            format!("fk-{}-{}", from_tbl.to_string(), from_cols.join("-"))
469        };
470        $foreign_key.name(&name);
471    };
472}
473
474impl From<RelationDef> for ForeignKeyCreateStatement {
475    fn from(relation: RelationDef) -> Self {
476        let mut foreign_key_stmt = Self::new();
477        set_foreign_key_stmt!(relation, foreign_key_stmt);
478        foreign_key_stmt
479            .from_tbl(relation.from_tbl)
480            .to_tbl(relation.to_tbl)
481            .take()
482    }
483}
484
485/// Creates a column definition for example to update a table.
486/// ```
487/// use sea_query::{Alias, IntoIden, MysqlQueryBuilder, TableAlterStatement, IntoTableRef, ConditionType};
488/// use sea_orm::{EnumIter, Iden, Identity, PrimaryKeyTrait, RelationDef, RelationTrait, RelationType};
489///
490/// let relation = RelationDef {
491///     rel_type: RelationType::HasOne,
492///     from_tbl: "foo".into_table_ref(),
493///     to_tbl: "bar".into_table_ref(),
494///     from_col: Identity::Unary("bar_id".into_iden()),
495///     to_col: Identity::Unary("bar_id".into_iden()),
496///     is_owner: false,
497///     on_delete: None,
498///     on_update: None,
499///     on_condition: None,
500///     fk_name: Some("foo-bar".to_string()),
501///     skip_fk: false,
502///     condition_type: ConditionType::All,
503/// };
504///
505/// let mut alter_table = TableAlterStatement::new()
506///     .table("foo")
507///     .add_foreign_key(&mut relation.into()).take();
508/// assert_eq!(
509///     alter_table.to_string(MysqlQueryBuilder::default()),
510///     "ALTER TABLE `foo` ADD CONSTRAINT `foo-bar` FOREIGN KEY (`bar_id`) REFERENCES `bar` (`bar_id`)"
511/// );
512/// ```
513impl From<RelationDef> for TableForeignKey {
514    fn from(relation: RelationDef) -> Self {
515        let mut foreign_key = Self::new();
516        set_foreign_key_stmt!(relation, foreign_key);
517        foreign_key
518            .from_tbl(relation.from_tbl.sea_orm_table().clone())
519            .to_tbl(relation.to_tbl.sea_orm_table().clone())
520            .take()
521    }
522}
523
524impl std::hash::Hash for RelationDef {
525    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
526        self.rel_type.hash(state);
527        self.from_tbl.sea_orm_table().hash(state);
528        self.to_tbl.sea_orm_table().hash(state);
529        self.from_col.hash(state);
530        self.to_col.hash(state);
531        self.is_owner.hash(state);
532    }
533}
534
535impl PartialEq for RelationDef {
536    fn eq(&self, other: &Self) -> bool {
537        self.rel_type.eq(&other.rel_type)
538            && self.from_tbl.eq(&other.from_tbl)
539            && self.to_tbl.eq(&other.to_tbl)
540            && itertools::equal(self.from_col.iter(), other.from_col.iter())
541            && itertools::equal(self.to_col.iter(), other.to_col.iter())
542            && self.is_owner.eq(&other.is_owner)
543    }
544}
545
546impl Eq for RelationDef {}
547
548#[cfg(test)]
549mod tests {
550    use crate::{
551        RelationBuilder, RelationDef,
552        tests_cfg::{cake, fruit},
553    };
554
555    #[test]
556    fn assert_relation_traits() {
557        fn assert_send_sync<T: Send + Sync>() {}
558
559        assert_send_sync::<RelationDef>();
560        assert_send_sync::<RelationBuilder<cake::Entity, fruit::Entity>>();
561    }
562}