1use crate::{SQL, ToSQL, traits::SQLParam};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
14pub enum JoinType {
15 #[default]
16 Join,
17 Inner,
18 Left,
19 Right,
20 Full,
21 Cross,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
33pub struct Join {
34 pub natural: bool,
35 pub join_type: JoinType,
36 pub outer: bool, }
38
39impl Join {
40 #[must_use]
42 pub const fn new() -> Self {
43 Self {
44 natural: false,
45 join_type: JoinType::Join,
46 outer: false,
47 }
48 }
49
50 #[must_use]
52 pub const fn natural(mut self) -> Self {
53 self.natural = true;
54 self
55 }
56
57 #[must_use]
59 pub const fn inner(mut self) -> Self {
60 self.join_type = JoinType::Inner;
61 self
62 }
63
64 #[must_use]
66 pub const fn left(mut self) -> Self {
67 self.join_type = JoinType::Left;
68 self
69 }
70
71 #[must_use]
73 pub const fn right(mut self) -> Self {
74 self.join_type = JoinType::Right;
75 self
76 }
77
78 #[must_use]
80 pub const fn full(mut self) -> Self {
81 self.join_type = JoinType::Full;
82 self
83 }
84
85 #[must_use]
87 pub const fn cross(mut self) -> Self {
88 self.join_type = JoinType::Cross;
89 self
90 }
91
92 #[must_use]
94 pub const fn outer(mut self) -> Self {
95 self.outer = true;
96 self
97 }
98}
99
100impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for Join {
101 fn to_sql(&self) -> SQL<'a, V> {
102 let join_str = match (self.natural, self.join_type, self.outer) {
104 (true, JoinType::Join, _) => "NATURAL JOIN",
106 (true, JoinType::Inner, _) => "NATURAL INNER JOIN",
107 (true, JoinType::Left, false) => "NATURAL LEFT JOIN",
108 (true, JoinType::Left, true) => "NATURAL LEFT OUTER JOIN",
109 (true, JoinType::Right, false) => "NATURAL RIGHT JOIN",
110 (true, JoinType::Right, true) => "NATURAL RIGHT OUTER JOIN",
111 (true, JoinType::Full, false) => "NATURAL FULL JOIN",
112 (true, JoinType::Full, true) => "NATURAL FULL OUTER JOIN",
113 (true, JoinType::Cross, _) => "NATURAL CROSS JOIN",
114 (false, JoinType::Join, _) => "JOIN",
116 (false, JoinType::Inner, _) => "INNER JOIN",
117 (false, JoinType::Left, false) => "LEFT JOIN",
118 (false, JoinType::Left, true) => "LEFT OUTER JOIN",
119 (false, JoinType::Right, false) => "RIGHT JOIN",
120 (false, JoinType::Right, true) => "RIGHT OUTER JOIN",
121 (false, JoinType::Full, false) => "FULL JOIN",
122 (false, JoinType::Full, true) => "FULL OUTER JOIN",
123 (false, JoinType::Cross, _) => "CROSS JOIN",
124 };
125 SQL::raw(join_str)
126 }
127}
128
129#[doc(hidden)]
131pub trait LateralArg<'a, V: SQLParam>: lateral_private::Arg {
132 type JoinedTable;
133
134 fn into_lateral_sql(self, join: Join) -> SQL<'a, V>;
135}
136
137impl<'a, V, Name, Projection, Query, Condition> LateralArg<'a, V>
138 for (crate::Derived<'a, V, Name, Projection, Query>, Condition)
139where
140 V: SQLParam + 'a,
141 Name: crate::Tag,
142 Projection: crate::DerivedProjection<Name>,
143 Query: ToSQL<'a, V>,
144 Condition: crate::expr::Expr<'a, V>,
145 Condition::SQLType: crate::types::BooleanLike,
146{
147 type JoinedTable = crate::Derived<'a, V, Name, Projection, Query>;
148
149 fn into_lateral_sql(self, join: Join) -> SQL<'a, V> {
150 let (source, condition) = self;
151 join.to_sql()
152 .append(SQL::raw(" LATERAL "))
153 .append(source.into_sql())
154 .push(crate::Token::ON)
155 .append(condition.into_sql())
156 }
157}
158
159#[doc(hidden)]
161pub trait LateralSource<'a, V: SQLParam>: lateral_private::Source {
162 type JoinedTable;
163
164 fn into_cross_lateral_sql(self) -> SQL<'a, V>;
165}
166
167impl<'a, V, Name, Projection, Query> LateralSource<'a, V>
168 for crate::Derived<'a, V, Name, Projection, Query>
169where
170 V: SQLParam + 'a,
171 Name: crate::Tag,
172 Projection: crate::DerivedProjection<Name>,
173 Query: ToSQL<'a, V>,
174{
175 type JoinedTable = Self;
176
177 fn into_cross_lateral_sql(self) -> SQL<'a, V> {
178 Join::new()
179 .cross()
180 .to_sql()
181 .append(SQL::raw(" LATERAL "))
182 .append(self.into_sql())
183 }
184}
185
186mod lateral_private {
187 pub trait Arg {}
188 pub trait Source {}
189
190 impl<V, Name, Projection, Query, Condition> Arg
191 for (crate::Derived<'_, V, Name, Projection, Query>, Condition)
192 where
193 V: crate::SQLParam,
194 {
195 }
196
197 impl<V, Name, Projection, Query> Source for crate::Derived<'_, V, Name, Projection, Query> where
198 V: crate::SQLParam
199 {
200 }
201}
202
203#[macro_export]
227macro_rules! impl_join_helpers {
228 (
229 table_trait: $TableTrait:path,
230 condition_trait: $ConditionTrait:path,
231 sql_type: $SQLType:ty $(,)?
232 ) => {
233 fn join_internal<'a, Table>(
234 table: Table,
235 join: $crate::Join,
236 condition: impl $ConditionTrait,
237 ) -> $SQLType
238 where
239 Table: $TableTrait,
240 {
241 use $crate::ToSQL;
242 join.to_sql()
243 .append(&table)
244 .push($crate::Token::ON)
245 .append(&condition)
246 }
247
248 pub fn natural_join<'a, Table>(table: Table) -> $SQLType
253 where
254 Table: $TableTrait,
255 {
256 use $crate::ToSQL;
257 $crate::Join::new().natural().to_sql().append(&table)
258 }
259
260 pub fn join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
262 where
263 Table: $TableTrait,
264 {
265 join_internal(table, $crate::Join::new(), condition)
266 }
267
268 pub fn natural_left_join<'a, Table>(table: Table) -> $SQLType
273 where
274 Table: $TableTrait,
275 {
276 use $crate::ToSQL;
277 $crate::Join::new().natural().left().to_sql().append(&table)
278 }
279
280 pub fn left_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
282 where
283 Table: $TableTrait,
284 {
285 join_internal(table, $crate::Join::new().left(), condition)
286 }
287
288 pub fn left_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
290 where
291 Table: $TableTrait,
292 {
293 join_internal(table, $crate::Join::new().left().outer(), condition)
294 }
295
296 pub fn natural_left_outer_join<'a, Table>(table: Table) -> $SQLType
301 where
302 Table: $TableTrait,
303 {
304 use $crate::ToSQL;
305 $crate::Join::new()
306 .natural()
307 .left()
308 .outer()
309 .to_sql()
310 .append(&table)
311 }
312
313 pub fn natural_right_join<'a, Table>(table: Table) -> $SQLType
318 where
319 Table: $TableTrait,
320 {
321 use $crate::ToSQL;
322 $crate::Join::new()
323 .natural()
324 .right()
325 .to_sql()
326 .append(&table)
327 }
328
329 pub fn right_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
331 where
332 Table: $TableTrait,
333 {
334 join_internal(table, $crate::Join::new().right(), condition)
335 }
336
337 pub fn right_outer_join<'a, Table>(
339 table: Table,
340 condition: impl $ConditionTrait,
341 ) -> $SQLType
342 where
343 Table: $TableTrait,
344 {
345 join_internal(table, $crate::Join::new().right().outer(), condition)
346 }
347
348 pub fn natural_right_outer_join<'a, Table>(table: Table) -> $SQLType
353 where
354 Table: $TableTrait,
355 {
356 use $crate::ToSQL;
357 $crate::Join::new()
358 .natural()
359 .right()
360 .outer()
361 .to_sql()
362 .append(&table)
363 }
364
365 pub fn natural_full_join<'a, Table>(table: Table) -> $SQLType
370 where
371 Table: $TableTrait,
372 {
373 use $crate::ToSQL;
374 $crate::Join::new().natural().full().to_sql().append(&table)
375 }
376
377 pub fn full_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
379 where
380 Table: $TableTrait,
381 {
382 join_internal(table, $crate::Join::new().full(), condition)
383 }
384
385 pub fn full_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
387 where
388 Table: $TableTrait,
389 {
390 join_internal(table, $crate::Join::new().full().outer(), condition)
391 }
392
393 pub fn natural_full_outer_join<'a, Table>(table: Table) -> $SQLType
398 where
399 Table: $TableTrait,
400 {
401 use $crate::ToSQL;
402 $crate::Join::new()
403 .natural()
404 .full()
405 .outer()
406 .to_sql()
407 .append(&table)
408 }
409
410 pub fn natural_inner_join<'a, Table>(table: Table) -> $SQLType
415 where
416 Table: $TableTrait,
417 {
418 use $crate::ToSQL;
419 $crate::Join::new()
420 .natural()
421 .inner()
422 .to_sql()
423 .append(&table)
424 }
425
426 pub fn inner_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
428 where
429 Table: $TableTrait,
430 {
431 join_internal(table, $crate::Join::new().inner(), condition)
432 }
433
434 pub fn cross_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
440 where
441 Table: $TableTrait,
442 {
443 join_internal(table, $crate::Join::new().inner(), condition)
444 }
445 };
446}
447
448#[macro_export]
454macro_rules! impl_join_arg_trait {
455 (
456 table_trait: $TableTrait:path,
457 table_info_trait: $TableInfoTrait:path,
458 condition_trait: $ConditionTrait:path,
459 join_source_trait: $JoinSourceTrait:path,
460 value_type: $ValueType:ty $(,)?
461 ) => {
462 pub trait JoinArg<'a, FromTable> {
464 type JoinedTable;
466
467 fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType>;
469 }
470
471 impl<'a, U, T> JoinArg<'a, T> for U
473 where
474 U: $TableTrait + $crate::Joinable<T>,
475 T: $TableInfoTrait + ::core::default::Default,
476 {
477 type JoinedTable = U;
478
479 fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType> {
480 use $crate::ToSQL;
481
482 let from = T::default();
483 let cols = <U as $crate::Joinable<T>>::fk_columns();
484 let join_name = self.name();
485 let from_name = from.name();
486
487 let mut condition = $crate::SQL::with_capacity_chunks(cols.len() * 7);
488 for (idx, (self_col, target_col)) in cols.iter().enumerate() {
489 if idx > 0 {
490 condition.push_mut($crate::Token::AND);
491 }
492 condition.append_mut(
493 $crate::SQL::ident(join_name)
494 .push($crate::Token::DOT)
495 .append($crate::SQL::ident(*self_col)),
496 );
497 condition.push_mut($crate::Token::EQ);
498 condition.append_mut(
499 $crate::SQL::ident(from_name)
500 .push($crate::Token::DOT)
501 .append($crate::SQL::ident(*target_col)),
502 );
503 }
504
505 join.to_sql()
506 .append(&self)
507 .push($crate::Token::ON)
508 .append(&condition)
509 }
510 }
511
512 impl<'a, U, C, T> JoinArg<'a, T> for (U, C)
514 where
515 U: $JoinSourceTrait,
516 C: $ConditionTrait,
517 {
518 type JoinedTable = U::JoinedTable;
519
520 fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType> {
521 let (source, condition) = self;
522 join.to_sql()
523 .append($crate::SQL::raw(" "))
524 .append(source.into_join_source_sql())
525 .push($crate::Token::ON)
526 .append(condition.into_sql())
527 }
528 }
529 };
530}