sea_orm/entity/
relation.rs

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