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