reinhardt_query/types/
join.rs1use super::{iden::DynIden, table_ref::TableRef};
10use crate::expr::{Condition, IntoCondition};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum JoinType {
15 Join,
17 InnerJoin,
19 LeftJoin,
21 RightJoin,
23 FullOuterJoin,
25 CrossJoin,
27}
28
29impl JoinType {
30 #[must_use]
32 pub fn as_str(&self) -> &'static str {
33 match self {
34 Self::Join => "JOIN",
35 Self::InnerJoin => "INNER JOIN",
36 Self::LeftJoin => "LEFT JOIN",
37 Self::RightJoin => "RIGHT JOIN",
38 Self::FullOuterJoin => "FULL OUTER JOIN",
39 Self::CrossJoin => "CROSS JOIN",
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
48pub enum JoinOn {
49 Columns(ColumnPair),
51 Condition(Condition),
53 Using(Vec<DynIden>),
55}
56
57#[derive(Debug, Clone)]
59pub struct ColumnPair {
60 pub left: ColumnSpec,
62 pub right: ColumnSpec,
64}
65
66#[derive(Debug, Clone)]
68pub enum ColumnSpec {
69 Column(DynIden),
71 TableColumn(DynIden, DynIden),
73}
74
75impl ColumnSpec {
76 pub fn column<I: super::iden::IntoIden>(column: I) -> Self {
78 Self::Column(column.into_iden())
79 }
80
81 pub fn table_column<T: super::iden::IntoIden, C: super::iden::IntoIden>(
83 table: T,
84 column: C,
85 ) -> Self {
86 Self::TableColumn(table.into_iden(), column.into_iden())
87 }
88}
89
90#[derive(Debug, Clone)]
95pub struct JoinExpr {
96 pub join: JoinType,
98 pub table: TableRef,
100 pub on: Option<JoinOn>,
102}
103
104impl JoinExpr {
105 pub fn new(table: TableRef) -> Self {
107 Self {
108 join: JoinType::InnerJoin,
109 table,
110 on: None,
111 }
112 }
113
114 #[must_use]
116 pub fn join_type(mut self, join: JoinType) -> Self {
117 self.join = join;
118 self
119 }
120
121 #[must_use]
123 pub fn on(mut self, condition: JoinOn) -> Self {
124 self.on = Some(condition);
125 self
126 }
127
128 #[must_use]
130 pub fn on_columns(mut self, left: ColumnSpec, right: ColumnSpec) -> Self {
131 self.on = Some(JoinOn::Columns(ColumnPair { left, right }));
132 self
133 }
134
135 #[must_use]
137 pub fn on_condition<C: IntoCondition>(mut self, condition: C) -> Self {
138 self.on = Some(JoinOn::Condition(condition.into_condition()));
139 self
140 }
141
142 #[must_use]
144 pub fn using_columns<I, C>(mut self, columns: I) -> Self
145 where
146 I: IntoIterator<Item = C>,
147 C: super::iden::IntoIden,
148 {
149 let cols: Vec<DynIden> = columns.into_iter().map(|c| c.into_iden()).collect();
150 self.on = Some(JoinOn::Using(cols));
151 self
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::types::iden::IntoIden;
159 use rstest::rstest;
160
161 #[rstest]
162 #[case(JoinType::Join, "JOIN")]
163 #[case(JoinType::InnerJoin, "INNER JOIN")]
164 #[case(JoinType::LeftJoin, "LEFT JOIN")]
165 #[case(JoinType::RightJoin, "RIGHT JOIN")]
166 #[case(JoinType::FullOuterJoin, "FULL OUTER JOIN")]
167 #[case(JoinType::CrossJoin, "CROSS JOIN")]
168 fn test_join_type_as_str(#[case] join_type: JoinType, #[case] expected: &str) {
169 assert_eq!(join_type.as_str(), expected);
170 }
171
172 #[rstest]
173 fn test_column_spec_simple() {
174 let _spec = ColumnSpec::column("id");
175 }
176
177 #[rstest]
178 fn test_column_spec_qualified() {
179 let _spec = ColumnSpec::table_column("users", "id");
180 }
181
182 #[rstest]
183 fn test_join_expr_builder() {
184 use crate::types::Alias;
185
186 let table = TableRef::Table(Alias::new("posts").into_iden());
187 let join = JoinExpr::new(table)
188 .join_type(JoinType::LeftJoin)
189 .on_columns(
190 ColumnSpec::table_column("users", "id"),
191 ColumnSpec::table_column("posts", "user_id"),
192 );
193
194 assert_eq!(join.join, JoinType::LeftJoin);
195 assert!(join.on.is_some());
196 }
197}