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