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