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)
}
}
#[doc(hidden)]
pub trait LateralArg<'a, V: SQLParam>: lateral_private::Arg {
type JoinedTable;
fn into_lateral_sql(self, join: Join) -> SQL<'a, V>;
}
impl<'a, V, Name, Projection, Query, Condition> LateralArg<'a, V>
for (crate::Derived<'a, V, Name, Projection, Query>, Condition)
where
V: SQLParam + 'a,
Name: crate::Tag,
Projection: crate::DerivedProjection<Name>,
Query: ToSQL<'a, V>,
Condition: crate::expr::Expr<'a, V>,
Condition::SQLType: crate::types::BooleanLike,
{
type JoinedTable = crate::Derived<'a, V, Name, Projection, Query>;
fn into_lateral_sql(self, join: Join) -> SQL<'a, V> {
let (source, condition) = self;
join.to_sql()
.append(SQL::raw(" LATERAL "))
.append(source.into_sql())
.push(crate::Token::ON)
.append(condition.into_sql())
}
}
#[doc(hidden)]
pub trait LateralSource<'a, V: SQLParam>: lateral_private::Source {
type JoinedTable;
fn into_cross_lateral_sql(self) -> SQL<'a, V>;
}
impl<'a, V, Name, Projection, Query> LateralSource<'a, V>
for crate::Derived<'a, V, Name, Projection, Query>
where
V: SQLParam + 'a,
Name: crate::Tag,
Projection: crate::DerivedProjection<Name>,
Query: ToSQL<'a, V>,
{
type JoinedTable = Self;
fn into_cross_lateral_sql(self) -> SQL<'a, V> {
Join::new()
.cross()
.to_sql()
.append(SQL::raw(" LATERAL "))
.append(self.into_sql())
}
}
mod lateral_private {
pub trait Arg {}
pub trait Source {}
impl<V, Name, Projection, Query, Condition> Arg
for (crate::Derived<'_, V, Name, Projection, Query>, Condition)
where
V: crate::SQLParam,
{
}
impl<V, Name, Projection, Query> Source for crate::Derived<'_, V, Name, Projection, Query> where
V: crate::SQLParam
{
}
}
#[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) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new().natural().to_sql().append(&table)
}
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) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new().natural().left().to_sql().append(&table)
}
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) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new()
.natural()
.left()
.outer()
.to_sql()
.append(&table)
}
pub fn natural_right_join<'a, Table>(table: Table) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new()
.natural()
.right()
.to_sql()
.append(&table)
}
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) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new()
.natural()
.right()
.outer()
.to_sql()
.append(&table)
}
pub fn natural_full_join<'a, Table>(table: Table) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new().natural().full().to_sql().append(&table)
}
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) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new()
.natural()
.full()
.outer()
.to_sql()
.append(&table)
}
pub fn natural_inner_join<'a, Table>(table: Table) -> $SQLType
where
Table: $TableTrait,
{
use $crate::ToSQL;
$crate::Join::new()
.natural()
.inner()
.to_sql()
.append(&table)
}
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().inner(), condition)
}
};
}
#[macro_export]
macro_rules! impl_join_arg_trait {
(
table_trait: $TableTrait:path,
table_info_trait: $TableInfoTrait:path,
condition_trait: $ConditionTrait:path,
join_source_trait: $JoinSourceTrait: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: $JoinSourceTrait,
C: $ConditionTrait,
{
type JoinedTable = U::JoinedTable;
fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType> {
let (source, condition) = self;
join.to_sql()
.append($crate::SQL::raw(" "))
.append(source.into_join_source_sql())
.push($crate::Token::ON)
.append(condition.into_sql())
}
}
};
}