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}