1use crate::{SQL, ToSQL, traits::SQLParam};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
15pub enum JoinType {
16 #[default]
18 Join,
19 Inner,
21 Left,
23 Right,
25 Full,
27 Cross,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
60pub struct Join {
61 pub natural: bool,
63 pub join_type: JoinType,
65 pub outer: bool,
67}
68
69impl Join {
70 #[must_use]
72 pub const fn new() -> Self {
73 Self {
74 natural: false,
75 join_type: JoinType::Join,
76 outer: false,
77 }
78 }
79
80 #[must_use]
82 pub const fn natural(mut self) -> Self {
83 self.natural = true;
84 self
85 }
86
87 #[must_use]
89 pub const fn inner(mut self) -> Self {
90 self.join_type = JoinType::Inner;
91 self
92 }
93
94 #[must_use]
96 pub const fn left(mut self) -> Self {
97 self.join_type = JoinType::Left;
98 self
99 }
100
101 #[must_use]
103 pub const fn right(mut self) -> Self {
104 self.join_type = JoinType::Right;
105 self
106 }
107
108 #[must_use]
110 pub const fn full(mut self) -> Self {
111 self.join_type = JoinType::Full;
112 self
113 }
114
115 #[must_use]
117 pub const fn cross(mut self) -> Self {
118 self.join_type = JoinType::Cross;
119 self
120 }
121
122 #[must_use]
125 pub const fn outer(mut self) -> Self {
126 self.outer = true;
127 self
128 }
129}
130
131impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for Join {
132 fn to_sql(&self) -> SQL<'a, V> {
133 let join_str = match (self.natural, self.join_type, self.outer) {
135 (true, JoinType::Join, _) => "NATURAL JOIN",
137 (true, JoinType::Inner, _) => "NATURAL INNER JOIN",
138 (true, JoinType::Left, false) => "NATURAL LEFT JOIN",
139 (true, JoinType::Left, true) => "NATURAL LEFT OUTER JOIN",
140 (true, JoinType::Right, false) => "NATURAL RIGHT JOIN",
141 (true, JoinType::Right, true) => "NATURAL RIGHT OUTER JOIN",
142 (true, JoinType::Full, false) => "NATURAL FULL JOIN",
143 (true, JoinType::Full, true) => "NATURAL FULL OUTER JOIN",
144 (true, JoinType::Cross, _) => "NATURAL CROSS JOIN",
145 (false, JoinType::Join, _) => "JOIN",
147 (false, JoinType::Inner, _) => "INNER JOIN",
148 (false, JoinType::Left, false) => "LEFT JOIN",
149 (false, JoinType::Left, true) => "LEFT OUTER JOIN",
150 (false, JoinType::Right, false) => "RIGHT JOIN",
151 (false, JoinType::Right, true) => "RIGHT OUTER JOIN",
152 (false, JoinType::Full, false) => "FULL JOIN",
153 (false, JoinType::Full, true) => "FULL OUTER JOIN",
154 (false, JoinType::Cross, _) => "CROSS JOIN",
155 };
156 SQL::raw(join_str)
157 }
158}
159
160#[doc(hidden)]
162pub trait LateralArg<'a, V: SQLParam>: lateral_private::Arg {
163 type JoinedTable;
165 type OnSources;
167
168 fn into_lateral_sql(self, join: Join) -> SQL<'a, V>;
170}
171
172impl<'a, V, Name, Projection, Query, Condition> LateralArg<'a, V>
173 for (crate::Derived<'a, V, Name, Projection, Query>, Condition)
174where
175 V: SQLParam + 'a,
176 Name: crate::Tag,
177 Projection: crate::DerivedProjection<Name>,
178 Query: ToSQL<'a, V>,
179 Condition: crate::expr::Expr<'a, V>,
180 Condition::SQLType: crate::types::BooleanLike,
181{
182 type JoinedTable = crate::Derived<'a, V, Name, Projection, Query>;
183 type OnSources = Condition::Sources;
184
185 fn into_lateral_sql(self, join: Join) -> SQL<'a, V> {
186 let (source, condition) = self;
187 join.to_sql()
188 .append(SQL::raw(" LATERAL "))
189 .append(source.into_sql())
190 .push(crate::Token::ON)
191 .append(condition.into_sql())
192 }
193}
194
195#[doc(hidden)]
197pub trait LateralSource<'a, V: SQLParam>: lateral_private::Source {
198 type JoinedTable;
200
201 fn into_cross_lateral_sql(self) -> SQL<'a, V>;
203}
204
205impl<'a, V, Name, Projection, Query> LateralSource<'a, V>
206 for crate::Derived<'a, V, Name, Projection, Query>
207where
208 V: SQLParam + 'a,
209 Name: crate::Tag,
210 Projection: crate::DerivedProjection<Name>,
211 Query: ToSQL<'a, V>,
212{
213 type JoinedTable = Self;
214
215 fn into_cross_lateral_sql(self) -> SQL<'a, V> {
216 Join::new()
217 .cross()
218 .to_sql()
219 .append(SQL::raw(" LATERAL "))
220 .append(self.into_sql())
221 }
222}
223
224mod lateral_private {
225 pub trait Arg {}
226 pub trait Source {}
227
228 impl<V, Name, Projection, Query, Condition> Arg
229 for (crate::Derived<'_, V, Name, Projection, Query>, Condition)
230 where
231 V: crate::SQLParam,
232 {
233 }
234
235 impl<V, Name, Projection, Query> Source for crate::Derived<'_, V, Name, Projection, Query> where
236 V: crate::SQLParam
237 {
238 }
239}
240
241#[macro_export]
285macro_rules! impl_join_helpers {
286 (
287 table_trait: $TableTrait:path,
288 condition_trait: $ConditionTrait:path,
289 sql_type: $SQLType:ty $(,)?
290 ) => {
291 fn join_internal<'a, Table>(
292 table: Table,
293 join: $crate::Join,
294 condition: impl $ConditionTrait,
295 ) -> $SQLType
296 where
297 Table: $TableTrait,
298 {
299 use $crate::ToSQL;
300 join.to_sql()
301 .append(&table)
302 .push($crate::Token::ON)
303 .append(&condition)
304 }
305
306 pub fn natural_join<'a, Table>(table: Table) -> $SQLType
311 where
312 Table: $TableTrait,
313 {
314 use $crate::ToSQL;
315 $crate::Join::new().natural().to_sql().append(&table)
316 }
317
318 pub fn join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
320 where
321 Table: $TableTrait,
322 {
323 join_internal(table, $crate::Join::new(), condition)
324 }
325
326 pub fn natural_left_join<'a, Table>(table: Table) -> $SQLType
331 where
332 Table: $TableTrait,
333 {
334 use $crate::ToSQL;
335 $crate::Join::new().natural().left().to_sql().append(&table)
336 }
337
338 pub fn left_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
340 where
341 Table: $TableTrait,
342 {
343 join_internal(table, $crate::Join::new().left(), condition)
344 }
345
346 pub fn left_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
348 where
349 Table: $TableTrait,
350 {
351 join_internal(table, $crate::Join::new().left().outer(), condition)
352 }
353
354 pub fn natural_left_outer_join<'a, Table>(table: Table) -> $SQLType
359 where
360 Table: $TableTrait,
361 {
362 use $crate::ToSQL;
363 $crate::Join::new()
364 .natural()
365 .left()
366 .outer()
367 .to_sql()
368 .append(&table)
369 }
370
371 pub fn natural_right_join<'a, Table>(table: Table) -> $SQLType
376 where
377 Table: $TableTrait,
378 {
379 use $crate::ToSQL;
380 $crate::Join::new()
381 .natural()
382 .right()
383 .to_sql()
384 .append(&table)
385 }
386
387 pub fn right_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
389 where
390 Table: $TableTrait,
391 {
392 join_internal(table, $crate::Join::new().right(), condition)
393 }
394
395 pub fn right_outer_join<'a, Table>(
397 table: Table,
398 condition: impl $ConditionTrait,
399 ) -> $SQLType
400 where
401 Table: $TableTrait,
402 {
403 join_internal(table, $crate::Join::new().right().outer(), condition)
404 }
405
406 pub fn natural_right_outer_join<'a, Table>(table: Table) -> $SQLType
411 where
412 Table: $TableTrait,
413 {
414 use $crate::ToSQL;
415 $crate::Join::new()
416 .natural()
417 .right()
418 .outer()
419 .to_sql()
420 .append(&table)
421 }
422
423 pub fn natural_full_join<'a, Table>(table: Table) -> $SQLType
428 where
429 Table: $TableTrait,
430 {
431 use $crate::ToSQL;
432 $crate::Join::new().natural().full().to_sql().append(&table)
433 }
434
435 pub fn full_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
437 where
438 Table: $TableTrait,
439 {
440 join_internal(table, $crate::Join::new().full(), condition)
441 }
442
443 pub fn full_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
445 where
446 Table: $TableTrait,
447 {
448 join_internal(table, $crate::Join::new().full().outer(), condition)
449 }
450
451 pub fn natural_full_outer_join<'a, Table>(table: Table) -> $SQLType
456 where
457 Table: $TableTrait,
458 {
459 use $crate::ToSQL;
460 $crate::Join::new()
461 .natural()
462 .full()
463 .outer()
464 .to_sql()
465 .append(&table)
466 }
467
468 pub fn natural_inner_join<'a, Table>(table: Table) -> $SQLType
473 where
474 Table: $TableTrait,
475 {
476 use $crate::ToSQL;
477 $crate::Join::new()
478 .natural()
479 .inner()
480 .to_sql()
481 .append(&table)
482 }
483
484 pub fn inner_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
486 where
487 Table: $TableTrait,
488 {
489 join_internal(table, $crate::Join::new().inner(), condition)
490 }
491
492 pub fn cross_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
498 where
499 Table: $TableTrait,
500 {
501 join_internal(table, $crate::Join::new().inner(), condition)
502 }
503 };
504}
505
506#[macro_export]
514macro_rules! impl_join_arg_trait {
515 (
516 table_trait: $TableTrait:path,
517 table_info_trait: $TableInfoTrait:path,
518 condition_trait: $ConditionTrait:path,
519 join_source_trait: $JoinSourceTrait:path,
520 value_type: $ValueType:ty $(,)?
521 ) => {
522 pub trait JoinArg<'a, FromTable> {
524 type JoinedTable;
526
527 type OnSources;
529
530 fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType>;
532 }
533
534 impl<'a, U, T> JoinArg<'a, T> for U
537 where
538 U: $TableTrait + $crate::Joinable<T>,
539 T: $TableInfoTrait + ::core::default::Default,
540 {
541 type JoinedTable = U;
542 type OnSources = ();
544
545 fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType> {
546 use $crate::ToSQL;
547
548 let from = T::default();
549 let cols = <U as $crate::Joinable<T>>::fk_columns();
550 let join_name = self.name();
551 let from_name = from.name();
552
553 let mut condition = $crate::SQL::with_capacity_chunks(cols.len() * 7);
554 for (idx, (self_col, target_col)) in cols.iter().enumerate() {
555 if idx > 0 {
556 condition.push_mut($crate::Token::AND);
557 }
558 condition.append_mut(
559 $crate::SQL::ident(join_name)
560 .push($crate::Token::DOT)
561 .append($crate::SQL::ident(*self_col)),
562 );
563 condition.push_mut($crate::Token::EQ);
564 condition.append_mut(
565 $crate::SQL::ident(from_name)
566 .push($crate::Token::DOT)
567 .append($crate::SQL::ident(*target_col)),
568 );
569 }
570
571 join.to_sql()
572 .append(&self)
573 .push($crate::Token::ON)
574 .append(&condition)
575 }
576 }
577
578 impl<'a, U, C, T> JoinArg<'a, T> for (U, C)
580 where
581 U: $JoinSourceTrait,
582 C: $ConditionTrait + $crate::expr::ExprSources,
583 {
584 type JoinedTable = U::JoinedTable;
585 type OnSources = C::Sources;
586
587 fn into_join_sql(self, join: $crate::Join) -> $crate::SQL<'a, $ValueType> {
588 let (source, condition) = self;
589 join.to_sql()
590 .append($crate::SQL::raw(" "))
591 .append(source.into_join_source_sql())
592 .push($crate::Token::ON)
593 .append(condition.into_sql())
594 }
595 }
596 };
597}