1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::fmt::{Display, Formatter, Write};

use crate::conditional::{BuildCondition, Condition};
use crate::value::Value;
use crate::DBImpl;

/**
Definition of all available Join types
*/
#[derive(Copy, Clone, Debug)]
pub enum JoinType {
    /// Normal join operation.
    ///
    /// Equivalent to INNER JOIN
    Join,
    /// Cartesian product of the tables
    CrossJoin,
    /// Given:
    /// T1 LEFT JOIN T2 ON ..
    ///
    /// First, an inner join is performed.
    /// Then, for each row in T1 that does not satisfy the join condition with any row in T2,
    /// a joined row is added with null values in columns of T2.
    LeftJoin,
    /// Given:
    /// T1 RIGHT JOIN T2 ON ..
    ///
    /// First, an inner join is performed.
    /// Then, for each row in T2 that does not satisfy the join condition with any row in T1,
    /// a joined row is added with null values in columns of T1.
    RightJoin,
    /// Given:
    /// T1 FULL JOIN T2 ON ..
    ///
    /// First, an inner join is performed.
    /// Then, for each row in T2 that does not satisfy the join condition with any row in T1,
    /// a joined row is added with null values in columns of T1.
    /// Also, for each row in T1 that does not satisfy the join condition with any row in T2,
    /// a joined row is added with null values in columns of T2.
    FullJoin,
}

impl Display for JoinType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            JoinType::Join => write!(f, "JOIN"),
            JoinType::CrossJoin => write!(f, "CROSS JOIN"),
            JoinType::LeftJoin => write!(f, "LEFT JOIN"),
            JoinType::RightJoin => write!(f, "RIGHT JOIN"),
            JoinType::FullJoin => write!(f, "FULL JOIN"),
        }
    }
}

/**
Trait representing a join table builder.
 */
pub trait JoinTable<'post_query> {
    /**
    Method to build a join table expression.

    **Parameter**:
    - `s`: Mutable reference to String to write to.
    - `lookup`: List of values for bind parameter.
    */
    fn build(self, s: &mut String, lookup: &mut Vec<Value<'post_query>>);
}

/**
Data of a JOIN expression.
*/
#[derive(Debug, Copy, Clone)]
pub struct JoinTableData<'until_build, 'post_query> {
    /// Type of the join operation
    pub join_type: JoinType,
    /// Name of the join table
    pub table_name: &'until_build str,
    /// Alias for the join table
    pub join_alias: &'until_build str,
    /// Condition to apply the join on
    pub join_condition: &'until_build Condition<'post_query>,
}

/**
Representation of the JOIN expression

Should only be constructed via [DBImpl::join_table].
*/
#[derive(Debug, Copy, Clone)]
pub enum JoinTableImpl<'until_build, 'post_query> {
    /**
    SQLite representation of a JOIN expression.
     */
    #[cfg(feature = "sqlite")]
    SQLite(JoinTableData<'until_build, 'post_query>),
    /**
    MySQL representation of a JOIN expression.
     */
    #[cfg(feature = "mysql")]
    MySQL(JoinTableData<'until_build, 'post_query>),
    /**
    Postgres representation of a JOIN expression.
     */
    #[cfg(feature = "postgres")]
    Postgres(JoinTableData<'until_build, 'post_query>),
}

impl<'until_build, 'post_query> JoinTable<'post_query>
    for JoinTableImpl<'until_build, 'post_query>
{
    fn build(self, s: &mut String, lookup: &mut Vec<Value<'post_query>>) {
        match self {
            #[cfg(feature = "sqlite")]
            JoinTableImpl::SQLite(d) => write!(
                s,
                "{} {} AS {} ON {}",
                d.join_type,
                d.table_name,
                d.join_alias,
                d.join_condition.build(DBImpl::SQLite, lookup)
            )
            .unwrap(),
            #[cfg(feature = "mysql")]
            JoinTableImpl::MySQL(d) => write!(
                s,
                "{} {} AS {} ON {}",
                d.join_type,
                d.table_name,
                d.join_alias,
                d.join_condition.build(DBImpl::MySQL, lookup)
            )
            .unwrap(),
            #[cfg(feature = "postgres")]
            JoinTableImpl::Postgres(d) => write!(
                s,
                "{} \"{}\" AS {} ON {}",
                d.join_type,
                d.table_name,
                d.join_alias,
                d.join_condition.build(DBImpl::Postgres, lookup)
            )
            .unwrap(),
        }
    }
}