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}