use crate::{
EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select, join_tbl_on_condition,
};
use core::marker::PhantomData;
use sea_query::{
Condition, ConditionType, DynIden, ForeignKeyCreateStatement, IntoIden, JoinType,
TableForeignKey, TableRef,
};
use std::{fmt::Debug, sync::Arc};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum RelationType {
HasOne,
HasMany,
}
pub type ForeignKeyAction = sea_query::ForeignKeyAction;
pub trait RelationTrait: Iterable + Debug + 'static {
fn def(&self) -> RelationDef;
fn name(&self) -> String {
format!("{self:?}")
}
}
pub trait Related<R>
where
R: EntityTrait,
{
fn to() -> RelationDef;
fn via() -> Option<RelationDef> {
None
}
fn find_related() -> Select<R> {
Select::<R>::new().join_join_rev(JoinType::InnerJoin, Self::to(), Self::via())
}
}
pub trait RelatedSelfVia<R>
where
R: EntityTrait,
{
fn to() -> RelationDef;
fn via() -> RelationDef;
fn find_related() -> Select<R> {
Select::<R>::new().join_join_rev(JoinType::InnerJoin, Self::to(), Some(Self::via()))
}
}
#[derive(derive_more::Debug, Clone)]
pub struct RelationDef {
pub rel_type: RelationType,
pub from_tbl: TableRef,
pub to_tbl: TableRef,
pub from_col: Identity,
pub to_col: Identity,
pub is_owner: bool,
pub skip_fk: bool,
pub on_delete: Option<ForeignKeyAction>,
pub on_update: Option<ForeignKeyAction>,
#[debug("{}", on_condition.is_some())]
pub on_condition: Option<Arc<dyn Fn(DynIden, DynIden) -> Condition>>,
pub fk_name: Option<String>,
pub condition_type: ConditionType,
}
impl From<RelationDef> for Condition {
fn from(mut rel: RelationDef) -> Condition {
let from_tbl = match rel.from_tbl.sea_orm_table_alias() {
Some(alias) => alias,
None => rel.from_tbl.sea_orm_table(),
};
let to_tbl = match rel.to_tbl.sea_orm_table_alias() {
Some(alias) => alias,
None => rel.to_tbl.sea_orm_table(),
};
let owner_keys = rel.from_col;
let foreign_keys = rel.to_col;
let mut condition = match rel.condition_type {
ConditionType::All => Condition::all(),
ConditionType::Any => Condition::any(),
};
condition = condition.add(join_tbl_on_condition(
from_tbl.clone(),
to_tbl.clone(),
owner_keys,
foreign_keys,
));
if let Some(f) = rel.on_condition.take() {
condition = condition.add(f(from_tbl.clone(), to_tbl.clone()));
}
condition
}
}
#[derive(derive_more::Debug)]
pub struct RelationBuilder<E, R>
where
E: EntityTrait,
R: EntityTrait,
{
entities: PhantomData<(E, R)>,
rel_type: RelationType,
from_tbl: TableRef,
to_tbl: TableRef,
from_col: Option<Identity>,
to_col: Option<Identity>,
is_owner: bool,
skip_fk: bool,
on_delete: Option<ForeignKeyAction>,
on_update: Option<ForeignKeyAction>,
#[debug("{}", on_condition.is_some())]
on_condition: Option<Arc<dyn Fn(DynIden, DynIden) -> Condition>>,
fk_name: Option<String>,
condition_type: ConditionType,
}
impl RelationDef {
pub fn rev(self) -> Self {
Self {
rel_type: self.rel_type,
from_tbl: self.to_tbl,
to_tbl: self.from_tbl,
from_col: self.to_col,
to_col: self.from_col,
is_owner: !self.is_owner,
skip_fk: self.skip_fk,
on_delete: self.on_delete,
on_update: self.on_update,
on_condition: self.on_condition,
fk_name: None,
condition_type: self.condition_type,
}
}
pub fn from_alias<A>(mut self, alias: A) -> Self
where
A: IntoIden,
{
self.from_tbl = self.from_tbl.alias(alias);
self
}
pub fn on_condition<F>(mut self, f: F) -> Self
where
F: Fn(DynIden, DynIden) -> Condition + 'static,
{
self.on_condition = Some(Arc::new(f));
self
}
pub fn condition_type(mut self, condition_type: ConditionType) -> Self {
self.condition_type = condition_type;
self
}
}
impl<E, R> RelationBuilder<E, R>
where
E: EntityTrait,
R: EntityTrait,
{
pub(crate) fn new(rel_type: RelationType, from: E, to: R, is_owner: bool) -> Self {
Self {
entities: PhantomData,
rel_type,
from_tbl: from.table_ref(),
to_tbl: to.table_ref(),
from_col: None,
to_col: None,
is_owner,
skip_fk: false,
on_delete: None,
on_update: None,
on_condition: None,
fk_name: None,
condition_type: ConditionType::All,
}
}
pub(crate) fn from_rel(rel_type: RelationType, rel: RelationDef, is_owner: bool) -> Self {
Self {
entities: PhantomData,
rel_type,
from_tbl: rel.from_tbl,
to_tbl: rel.to_tbl,
from_col: Some(rel.from_col),
to_col: Some(rel.to_col),
is_owner,
skip_fk: false,
on_delete: None,
on_update: None,
on_condition: None,
fk_name: None,
condition_type: ConditionType::All,
}
}
pub fn from<T>(mut self, identifier: T) -> Self
where
T: IdentityOf<E>,
{
self.from_col = Some(identifier.identity_of());
self
}
pub fn to<T>(mut self, identifier: T) -> Self
where
T: IdentityOf<R>,
{
self.to_col = Some(identifier.identity_of());
self
}
pub fn skip_fk(mut self) -> Self {
self.skip_fk = true;
self
}
pub fn on_delete(mut self, action: ForeignKeyAction) -> Self {
self.on_delete = Some(action);
self
}
pub fn on_update(mut self, action: ForeignKeyAction) -> Self {
self.on_update = Some(action);
self
}
pub fn on_condition<F>(mut self, f: F) -> Self
where
F: Fn(DynIden, DynIden) -> Condition + 'static,
{
self.on_condition = Some(Arc::new(f));
self
}
pub fn fk_name(mut self, fk_name: &str) -> Self {
self.fk_name = Some(fk_name.to_owned());
self
}
pub fn condition_type(mut self, condition_type: ConditionType) -> Self {
self.condition_type = condition_type;
self
}
}
impl<E, R> From<RelationBuilder<E, R>> for RelationDef
where
E: EntityTrait,
R: EntityTrait,
{
fn from(b: RelationBuilder<E, R>) -> Self {
RelationDef {
rel_type: b.rel_type,
from_tbl: b.from_tbl,
to_tbl: b.to_tbl,
from_col: b.from_col.expect("Reference column is not set"),
to_col: b.to_col.expect("Owner column is not set"),
is_owner: b.is_owner,
skip_fk: b.skip_fk,
on_delete: b.on_delete,
on_update: b.on_update,
on_condition: b.on_condition,
fk_name: b.fk_name,
condition_type: b.condition_type,
}
}
}
macro_rules! set_foreign_key_stmt {
( $relation: ident, $foreign_key: ident ) => {
let from_cols: Vec<String> = $relation
.from_col
.into_iter()
.map(|col| {
let col_name = col.to_string();
$foreign_key.from_col(col);
col_name
})
.collect();
for col in $relation.to_col.into_iter() {
$foreign_key.to_col(col);
}
if let Some(action) = $relation.on_delete {
$foreign_key.on_delete(action);
}
if let Some(action) = $relation.on_update {
$foreign_key.on_update(action);
}
let name = if let Some(name) = $relation.fk_name {
name
} else {
let from_tbl = &$relation.from_tbl.sea_orm_table().clone();
format!("fk-{}-{}", from_tbl.to_string(), from_cols.join("-"))
};
$foreign_key.name(&name);
};
}
impl From<RelationDef> for ForeignKeyCreateStatement {
fn from(relation: RelationDef) -> Self {
let mut foreign_key_stmt = Self::new();
set_foreign_key_stmt!(relation, foreign_key_stmt);
foreign_key_stmt
.from_tbl(relation.from_tbl)
.to_tbl(relation.to_tbl)
.take()
}
}
impl From<RelationDef> for TableForeignKey {
fn from(relation: RelationDef) -> Self {
let mut foreign_key = Self::new();
set_foreign_key_stmt!(relation, foreign_key);
foreign_key
.from_tbl(relation.from_tbl.sea_orm_table().clone())
.to_tbl(relation.to_tbl.sea_orm_table().clone())
.take()
}
}
impl std::hash::Hash for RelationDef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.rel_type.hash(state);
self.from_tbl.sea_orm_table().hash(state);
self.to_tbl.sea_orm_table().hash(state);
self.from_col.hash(state);
self.to_col.hash(state);
self.is_owner.hash(state);
}
}
impl PartialEq for RelationDef {
fn eq(&self, other: &Self) -> bool {
self.rel_type.eq(&other.rel_type)
&& self.from_tbl.eq(&other.from_tbl)
&& self.to_tbl.eq(&other.to_tbl)
&& itertools::equal(self.from_col.iter(), other.from_col.iter())
&& itertools::equal(self.to_col.iter(), other.to_col.iter())
&& self.is_owner.eq(&other.is_owner)
}
}
impl Eq for RelationDef {}
#[cfg(test)]
mod tests {
use crate::{
RelationBuilder, RelationDef,
tests_cfg::{cake, fruit},
};
#[cfg(not(feature = "sync"))]
#[test]
fn assert_relation_traits() {
fn assert_send_sync<T: Send>() {}
assert_send_sync::<RelationDef>();
assert_send_sync::<RelationBuilder<cake::Entity, fruit::Entity>>();
}
}