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