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