Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom join on conditions #793

Merged
merged 6 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sea-orm-macros/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod field_attr {
pub has_many: Option<syn::Lit>,
pub on_update: Option<syn::Lit>,
pub on_delete: Option<syn::Lit>,
pub on_condition: Option<syn::Lit>,
pub from: Option<syn::Lit>,
pub to: Option<syn::Lit>,
pub fk_name: Option<syn::Lit>,
Expand Down
11 changes: 11 additions & 0 deletions sea-orm-macros/src/derives/relation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ impl DeriveRelation {
result = quote! { #result.on_delete(sea_orm::prelude::ForeignKeyAction::#on_delete) };
}

if attr.on_condition.is_some() {
let on_condition = attr
.on_condition
.as_ref()
.map(Self::parse_lit_string)
.ok_or_else(|| {
syn::Error::new_spanned(variant, "Missing value for 'on_condition'")
})??;
result = quote! { #result.on_condition(|_, _| sea_orm::sea_query::IntoCondition::into_condition(#on_condition)) };
}

if attr.fk_name.is_some() {
let fk_name = attr
.fk_name
Expand Down
22 changes: 15 additions & 7 deletions src/entity/link.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
join_tbl_on_condition, unpack_table_ref, EntityTrait, QuerySelect, RelationDef, Select,
};
use sea_query::{Alias, IntoIden, JoinType, SeaRc};
use sea_query::{Alias, Condition, IntoIden, JoinType, SeaRc};

/// Same as [RelationDef]
pub type LinkDef = RelationDef;
Expand All @@ -20,20 +20,28 @@ pub trait Linked {
/// Find all the Entities that are linked to the Entity
fn find_linked(&self) -> Select<Self::ToEntity> {
let mut select = Select::new();
for (i, rel) in self.link().into_iter().rev().enumerate() {
for (i, mut rel) in self.link().into_iter().rev().enumerate() {
let from_tbl = Alias::new(&format!("r{}", i)).into_iden();
let to_tbl = if i > 0 {
Alias::new(&format!("r{}", i - 1)).into_iden()
} else {
unpack_table_ref(&rel.to_tbl)
};
let table_ref = rel.from_tbl;

select.query().join_as(
JoinType::InnerJoin,
rel.from_tbl,
let mut condition = Condition::all().add(join_tbl_on_condition(
SeaRc::clone(&from_tbl),
join_tbl_on_condition(from_tbl, to_tbl, rel.from_col, rel.to_col),
);
SeaRc::clone(&to_tbl),
rel.from_col,
rel.to_col,
));
if let Some(f) = rel.on_condition.take() {
condition = condition.add(f(SeaRc::clone(&from_tbl), SeaRc::clone(&to_tbl)));
}

select
.query()
.join_as(JoinType::InnerJoin, table_ref, from_tbl, condition);
}
select
}
Expand Down
116 changes: 113 additions & 3 deletions src/entity/relation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{EntityTrait, Identity, IdentityOf, Iterable, QuerySelect, Select};
use core::marker::PhantomData;
use sea_query::{JoinType, TableRef};
use sea_query::{Alias, Condition, DynIden, JoinType, SeaRc, TableRef};
use std::fmt::Debug;

/// Defines the type of relationship
Expand Down Expand Up @@ -42,7 +42,6 @@ where
}

/// Defines a relationship
#[derive(Debug)]
pub struct RelationDef {
/// The type of relationship defined in [RelationType]
pub rel_type: RelationType,
Expand All @@ -62,12 +61,49 @@ pub struct RelationDef {
/// Defines an operation to be performed on a Foreign Key when a
/// `UPDATE` Operation is performed
pub on_update: Option<ForeignKeyAction>,
/// Custom join ON condition
pub on_condition: Option<Box<dyn Fn(DynIden, DynIden) -> Condition>>,
/// The name of foreign key constraint
pub fk_name: Option<String>,
}

impl std::fmt::Debug for RelationDef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut d = f.debug_struct("RelationDef");
d.field("rel_type", &self.rel_type)
.field("from_tbl", &self.from_tbl)
.field("to_tbl", &self.to_tbl)
.field("from_col", &self.from_col)
.field("to_col", &self.to_col)
.field("is_owner", &self.is_owner)
.field("on_delete", &self.on_delete)
.field("on_update", &self.on_update);
debug_on_condition(&mut d, &self.on_condition);
d.field("fk_name", &self.fk_name).finish()
}
}

fn debug_on_condition(
d: &mut core::fmt::DebugStruct<'_, '_>,
on_condition: &Option<Box<dyn Fn(DynIden, DynIden) -> Condition>>,
) {
match on_condition {
Some(func) => {
d.field(
"on_condition",
&func(
SeaRc::new(Alias::new("left")),
SeaRc::new(Alias::new("right")),
),
);
}
None => {
d.field("on_condition", &Option::<Condition>::None);
}
}
}

/// Defines a helper to build a relation
#[derive(Debug)]
pub struct RelationBuilder<E, R>
where
E: EntityTrait,
Expand All @@ -82,9 +118,31 @@ where
is_owner: bool,
on_delete: Option<ForeignKeyAction>,
on_update: Option<ForeignKeyAction>,
on_condition: Option<Box<dyn Fn(DynIden, DynIden) -> Condition>>,
fk_name: Option<String>,
}

impl<E, R> std::fmt::Debug for RelationBuilder<E, R>
where
E: EntityTrait,
R: EntityTrait,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut d = f.debug_struct("RelationBuilder");
d.field("entities", &self.entities)
.field("rel_type", &self.rel_type)
.field("from_tbl", &self.from_tbl)
.field("to_tbl", &self.to_tbl)
.field("from_col", &self.from_col)
.field("to_col", &self.to_col)
.field("is_owner", &self.is_owner)
.field("on_delete", &self.on_delete)
.field("on_update", &self.on_update);
debug_on_condition(&mut d, &self.on_condition);
d.field("fk_name", &self.fk_name).finish()
}
}

impl RelationDef {
/// Reverse this relation (swap from and to)
pub fn rev(self) -> Self {
Expand All @@ -97,9 +155,46 @@ impl RelationDef {
is_owner: !self.is_owner,
on_delete: self.on_delete,
on_update: self.on_update,
on_condition: self.on_condition,
fk_name: None,
}
}

/// Set custom join ON condition.
///
/// This method takes a closure with parameters
/// denoting the left-hand side and right-hand side table in the join expression.
///
/// # Examples
///
/// assert_eq!(
/// cake::Entity::find()
/// .join(
/// JoinType::LeftJoin,
/// cake_filling::Relation::Cake
/// .def()
/// .rev()
/// .on_condition(|_left, right| {
/// Expr::tbl(right, cake_filling::Column::CakeId)
/// .gt(10)
/// .into_condition()
/// })
/// )
/// .build(DbBackend::MySql)
/// .to_string(),
/// [
/// "SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
/// "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` AND `cake_filling`.`cake_id` > 10",
/// ]
/// .join(" ")
/// );
pub fn on_condition<F>(mut self, f: F) -> Self
where
F: Fn(DynIden, DynIden) -> Condition + 'static,
billy1624 marked this conversation as resolved.
Show resolved Hide resolved
{
self.on_condition = Some(Box::new(f));
self
}
}

impl<E, R> RelationBuilder<E, R>
Expand All @@ -118,6 +213,7 @@ where
is_owner,
on_delete: None,
on_update: None,
on_condition: None,
fk_name: None,
}
}
Expand All @@ -133,6 +229,7 @@ where
is_owner,
on_delete: None,
on_update: None,
on_condition: None,
fk_name: None,
}
}
Expand Down Expand Up @@ -167,6 +264,18 @@ where
self
}

/// Set custom join ON condition.
///
/// This method takes a closure with parameters
/// denoting the left-hand side and right-hand side table in the join expression.
pub fn on_condition<F>(mut self, f: F) -> Self
where
F: Fn(DynIden, DynIden) -> Condition + 'static,
{
self.on_condition = Some(Box::new(f));
self
}

/// Set the name of foreign key constraint
pub fn fk_name(mut self, fk_name: &str) -> Self {
self.fk_name = Some(fk_name.to_owned());
Expand All @@ -189,6 +298,7 @@ where
is_owner: b.is_owner,
on_delete: b.on_delete,
on_update: b.on_update,
on_condition: b.on_condition,
fk_name: b.fk_name,
}
}
Expand Down
14 changes: 12 additions & 2 deletions src/query/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,13 +430,23 @@ pub trait QueryFilter: Sized {
}
}

pub(crate) fn join_condition(rel: RelationDef) -> SimpleExpr {
pub(crate) fn join_condition(mut rel: RelationDef) -> Condition {
let from_tbl = unpack_table_ref(&rel.from_tbl);
let to_tbl = unpack_table_ref(&rel.to_tbl);
let owner_keys = rel.from_col;
let foreign_keys = rel.to_col;

join_tbl_on_condition(from_tbl, to_tbl, owner_keys, foreign_keys)
let mut condition = Condition::all().add(join_tbl_on_condition(
SeaRc::clone(&from_tbl),
SeaRc::clone(&to_tbl),
owner_keys,
foreign_keys,
));
if let Some(f) = rel.on_condition.take() {
condition = condition.add(f(from_tbl, to_tbl));
}

condition
}

pub(crate) fn join_tbl_on_condition(
Expand Down
Loading