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