use crate::{SQL, ToSQL, traits::SQLParam};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum JoinType {
#[default]
Join,
Inner,
Left,
Right,
Full,
Cross,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct Join {
pub natural: bool,
pub join_type: JoinType,
pub outer: bool, }
impl Join {
#[must_use]
pub const fn new() -> Self {
Self {
natural: false,
join_type: JoinType::Join,
outer: false,
}
}
#[must_use]
pub const fn natural(mut self) -> Self {
self.natural = true;
self
}
#[must_use]
pub const fn inner(mut self) -> Self {
self.join_type = JoinType::Inner;
self
}
#[must_use]
pub const fn left(mut self) -> Self {
self.join_type = JoinType::Left;
self
}
#[must_use]
pub const fn right(mut self) -> Self {
self.join_type = JoinType::Right;
self
}
#[must_use]
pub const fn full(mut self) -> Self {
self.join_type = JoinType::Full;
self
}
#[must_use]
pub const fn cross(mut self) -> Self {
self.join_type = JoinType::Cross;
self
}
#[must_use]
pub const fn outer(mut self) -> Self {
self.outer = true;
self
}
}
impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for Join {
fn to_sql(&self) -> SQL<'a, V> {
let join_str = match (self.natural, self.join_type, self.outer) {
(true, JoinType::Join, _) => "NATURAL JOIN",
(true, JoinType::Inner, _) => "NATURAL INNER JOIN",
(true, JoinType::Left, false) => "NATURAL LEFT JOIN",
(true, JoinType::Left, true) => "NATURAL LEFT OUTER JOIN",
(true, JoinType::Right, false) => "NATURAL RIGHT JOIN",
(true, JoinType::Right, true) => "NATURAL RIGHT OUTER JOIN",
(true, JoinType::Full, false) => "NATURAL FULL JOIN",
(true, JoinType::Full, true) => "NATURAL FULL OUTER JOIN",
(true, JoinType::Cross, _) => "NATURAL CROSS JOIN",
(false, JoinType::Join, _) => "JOIN",
(false, JoinType::Inner, _) => "INNER JOIN",
(false, JoinType::Left, false) => "LEFT JOIN",
(false, JoinType::Left, true) => "LEFT OUTER JOIN",
(false, JoinType::Right, false) => "RIGHT JOIN",
(false, JoinType::Right, true) => "RIGHT OUTER JOIN",
(false, JoinType::Full, false) => "FULL JOIN",
(false, JoinType::Full, true) => "FULL OUTER JOIN",
(false, JoinType::Cross, _) => "CROSS JOIN",
};
SQL::raw(join_str)
}
}
#[macro_export]
macro_rules! impl_join_helpers {
(
table_trait: $TableTrait:path,
condition_trait: $ConditionTrait:path,
sql_type: $SQLType:ty $(,)?
) => {
fn join_internal<'a, Table>(
table: Table,
join: $crate::Join,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
join.to_sql()
.append(&table)
.push($crate::Token::ON)
.append(&condition)
}
pub fn natural_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().natural(), condition)
}
pub fn join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new(), condition)
}
pub fn natural_left_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().natural().left(), condition)
}
pub fn left_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().left(), condition)
}
pub fn left_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().left().outer(), condition)
}
pub fn natural_left_outer_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(
table,
$crate::Join::new().natural().left().outer(),
condition,
)
}
pub fn natural_right_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().natural().right(), condition)
}
pub fn right_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().right(), condition)
}
pub fn right_outer_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().right().outer(), condition)
}
pub fn natural_right_outer_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(
table,
$crate::Join::new().natural().right().outer(),
condition,
)
}
pub fn natural_full_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().natural().full(), condition)
}
pub fn full_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().full(), condition)
}
pub fn full_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().full().outer(), condition)
}
pub fn natural_full_outer_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(
table,
$crate::Join::new().natural().full().outer(),
condition,
)
}
pub fn natural_inner_join<'a, Table>(
table: Table,
condition: impl $ConditionTrait,
) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().natural().inner(), condition)
}
pub fn inner_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().inner(), condition)
}
pub fn cross_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
where
Table: $TableTrait,
{
join_internal(table, $crate::Join::new().cross(), condition)
}
};
}
#[macro_export]
macro_rules! impl_join_arg_trait {
(
table_trait: $TableTrait:path,
table_info_trait: $TableInfoTrait:path,
condition_trait: $ConditionTrait:path,
value_type: $ValueType:ty $(,)?
) => {
pub trait JoinArg<'a, FromTable> {
type JoinedTable;
fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType>;
}
impl<'a, U, T> JoinArg<'a, T> for U
where
U: $TableTrait + $crate::Joinable<T>,
T: $TableInfoTrait + ::core::default::Default,
{
type JoinedTable = U;
fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType> {
use $crate::ToSQL;
let from = T::default();
let cols = <U as $crate::Joinable<T>>::fk_columns();
let join_name = self.name();
let from_name = from.name();
let mut condition = $crate::SQL::with_capacity_chunks(cols.len() * 7);
for (idx, (self_col, target_col)) in cols.iter().enumerate() {
if idx > 0 {
condition.push_mut($crate::Token::AND);
}
condition.append_mut(
$crate::SQL::ident(join_name)
.push($crate::Token::DOT)
.append($crate::SQL::ident(*self_col)),
);
condition.push_mut($crate::Token::EQ);
condition.append_mut(
$crate::SQL::ident(from_name)
.push($crate::Token::DOT)
.append($crate::SQL::ident(*target_col)),
);
}
join.to_sql()
.append(&self)
.push($crate::Token::ON)
.append(&condition)
}
}
impl<'a, U, C, T> JoinArg<'a, T> for (U, C)
where
U: $TableTrait,
C: $ConditionTrait,
{
type JoinedTable = U;
fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType> {
let (table, condition) = self;
join_internal(table, join, condition)
}
}
};
}