1use crate::expr::{Dialect, Expr, adjust_placeholder_indices};
4use crate::subquery::SelectQuery;
5use sqlmodel_core::Value;
6
7#[derive(Debug, Clone)]
9pub struct Join {
10 pub join_type: JoinType,
12 pub table: String,
14 pub alias: Option<String>,
16 pub on: Expr,
18 pub lateral: bool,
22 pub is_subquery: bool,
24 pub subquery_params: Vec<Value>,
26 pub subquery: Option<Box<SelectQuery>>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum JoinType {
33 Inner,
34 Left,
35 Right,
36 Full,
37 Cross,
38}
39
40impl JoinType {
41 pub const fn as_str(&self) -> &'static str {
43 match self {
44 JoinType::Inner => "INNER JOIN",
45 JoinType::Left => "LEFT JOIN",
46 JoinType::Right => "RIGHT JOIN",
47 JoinType::Full => "FULL JOIN",
48 JoinType::Cross => "CROSS JOIN",
49 }
50 }
51}
52
53impl Join {
54 pub fn inner(table: impl Into<String>, on: Expr) -> Self {
56 Self {
57 join_type: JoinType::Inner,
58 table: table.into(),
59 alias: None,
60 on,
61 lateral: false,
62 is_subquery: false,
63 subquery_params: Vec::new(),
64 subquery: None,
65 }
66 }
67
68 pub fn left(table: impl Into<String>, on: Expr) -> Self {
70 Self {
71 join_type: JoinType::Left,
72 table: table.into(),
73 alias: None,
74 on,
75 lateral: false,
76 is_subquery: false,
77 subquery_params: Vec::new(),
78 subquery: None,
79 }
80 }
81
82 pub fn right(table: impl Into<String>, on: Expr) -> Self {
84 Self {
85 join_type: JoinType::Right,
86 table: table.into(),
87 alias: None,
88 on,
89 lateral: false,
90 is_subquery: false,
91 subquery_params: Vec::new(),
92 subquery: None,
93 }
94 }
95
96 pub fn full(table: impl Into<String>, on: Expr) -> Self {
98 Self {
99 join_type: JoinType::Full,
100 table: table.into(),
101 alias: None,
102 on,
103 lateral: false,
104 is_subquery: false,
105 subquery_params: Vec::new(),
106 subquery: None,
107 }
108 }
109
110 pub fn cross(table: impl Into<String>) -> Self {
112 Self {
113 join_type: JoinType::Cross,
114 table: table.into(),
115 alias: None,
116 on: Expr::raw("TRUE"), lateral: false,
118 is_subquery: false,
119 subquery_params: Vec::new(),
120 subquery: None,
121 }
122 }
123
124 pub fn lateral(
137 join_type: JoinType,
138 subquery_sql: impl Into<String>,
139 alias: impl Into<String>,
140 on: Expr,
141 params: Vec<Value>,
142 ) -> Self {
143 Self {
144 join_type,
145 table: subquery_sql.into(),
146 alias: Some(alias.into()),
147 on,
148 lateral: true,
149 is_subquery: true,
150 subquery_params: params,
151 subquery: None,
152 }
153 }
154
155 pub fn lateral_query(
157 join_type: JoinType,
158 subquery: SelectQuery,
159 alias: impl Into<String>,
160 on: Expr,
161 ) -> Self {
162 Self {
163 join_type,
164 table: String::new(),
165 alias: Some(alias.into()),
166 on,
167 lateral: true,
168 is_subquery: true,
169 subquery_params: Vec::new(),
170 subquery: Some(Box::new(subquery)),
171 }
172 }
173
174 pub fn left_lateral(
178 subquery_sql: impl Into<String>,
179 alias: impl Into<String>,
180 on: Expr,
181 params: Vec<Value>,
182 ) -> Self {
183 Self::lateral(JoinType::Left, subquery_sql, alias, on, params)
184 }
185
186 pub fn inner_lateral(
188 subquery_sql: impl Into<String>,
189 alias: impl Into<String>,
190 on: Expr,
191 params: Vec<Value>,
192 ) -> Self {
193 Self::lateral(JoinType::Inner, subquery_sql, alias, on, params)
194 }
195
196 pub fn cross_lateral(
198 subquery_sql: impl Into<String>,
199 alias: impl Into<String>,
200 params: Vec<Value>,
201 ) -> Self {
202 Self {
203 join_type: JoinType::Cross,
204 table: subquery_sql.into(),
205 alias: Some(alias.into()),
206 on: Expr::raw("TRUE"),
207 lateral: true,
208 is_subquery: true,
209 subquery_params: params,
210 subquery: None,
211 }
212 }
213
214 pub fn alias(mut self, alias: impl Into<String>) -> Self {
216 self.alias = Some(alias.into());
217 self
218 }
219
220 pub fn set_lateral(mut self) -> Self {
222 self.lateral = true;
223 self
224 }
225
226 pub fn to_sql(&self) -> (String, Vec<Value>) {
231 let mut params = Vec::new();
232 let sql = self.build_sql(Dialect::default(), &mut params, 0);
233 (sql, params)
234 }
235
236 pub fn to_sql_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
238 let mut params = Vec::new();
239 let sql = self.build_sql(dialect, &mut params, 0);
240 (sql, params)
241 }
242
243 pub fn build(&self, params: &mut Vec<Value>, offset: usize) -> String {
245 self.build_sql(Dialect::default(), params, offset)
246 }
247
248 pub fn build_with_dialect(
250 &self,
251 dialect: Dialect,
252 params: &mut Vec<Value>,
253 offset: usize,
254 ) -> String {
255 self.build_sql(dialect, params, offset)
256 }
257
258 fn build_sql(&self, dialect: Dialect, params: &mut Vec<Value>, offset: usize) -> String {
259 let lateral_keyword = if self.lateral { " LATERAL" } else { "" };
260
261 let (table_ref, subquery_params) = if let Some(subquery) = &self.subquery {
262 let start_idx = offset + params.len();
263 let (subquery_sql, subquery_params) = subquery.build_with_dialect(dialect);
264 let adjusted_subquery = if subquery_params.is_empty() {
265 subquery_sql
266 } else {
267 adjust_placeholder_indices(&subquery_sql, start_idx, dialect)
268 };
269 (format!("({})", adjusted_subquery), subquery_params)
270 } else if self.is_subquery {
271 let start_idx = offset + params.len();
272 let adjusted_subquery = if self.subquery_params.is_empty() {
273 self.table.clone()
274 } else {
275 adjust_placeholder_indices(&self.table, start_idx, dialect)
276 };
277 (
278 format!("({})", adjusted_subquery),
279 self.subquery_params.clone(),
280 )
281 } else {
282 (self.table.clone(), Vec::new())
283 };
284
285 let mut sql = format!(
286 " {}{}{}",
287 self.join_type.as_str(),
288 lateral_keyword,
289 if table_ref.is_empty() {
290 String::new()
291 } else {
292 format!(" {}", table_ref)
293 }
294 );
295
296 params.extend(subquery_params);
298
299 if let Some(alias) = &self.alias {
300 sql.push_str(" AS ");
301 sql.push_str(alias);
302 }
303
304 if self.join_type != JoinType::Cross {
305 let on_sql = self.on.build_with_dialect(dialect, params, offset);
306 sql.push_str(" ON ");
307 sql.push_str(&on_sql);
308 }
309
310 sql
311 }
312}