sea_orm/entity/
relation.rs

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