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