drizzle_core/
join.rs

1//! Join types and helper macros for SQL JOIN operations
2//!
3//! This module provides shared JOIN functionality that can be used by
4//! dialect-specific implementations (SQLite, PostgreSQL, etc.)
5
6use crate::{SQL, ToSQL, traits::SQLParam};
7
8// =============================================================================
9// Join Type Enum
10// =============================================================================
11
12/// The type of JOIN operation
13#[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// =============================================================================
25// Join Builder Struct
26// =============================================================================
27
28/// Builder for constructing JOIN clauses
29///
30/// This struct uses a builder pattern with const fn methods to allow
31/// compile-time construction of JOIN specifications.
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
33pub struct Join {
34    pub natural: bool,
35    pub join_type: JoinType,
36    pub outer: bool, // only meaningful for LEFT/RIGHT/FULL
37}
38
39impl Join {
40    /// Creates a new Join with default settings (basic JOIN)
41    pub const fn new() -> Self {
42        Self {
43            natural: false,
44            join_type: JoinType::Join,
45            outer: false,
46        }
47    }
48
49    /// Makes this a NATURAL join
50    pub const fn natural(mut self) -> Self {
51        self.natural = true;
52        self
53    }
54
55    /// Makes this an INNER join
56    pub const fn inner(mut self) -> Self {
57        self.join_type = JoinType::Inner;
58        self
59    }
60
61    /// Makes this a LEFT join
62    pub const fn left(mut self) -> Self {
63        self.join_type = JoinType::Left;
64        self
65    }
66
67    /// Makes this a RIGHT join
68    pub const fn right(mut self) -> Self {
69        self.join_type = JoinType::Right;
70        self
71    }
72
73    /// Makes this a FULL join
74    pub const fn full(mut self) -> Self {
75        self.join_type = JoinType::Full;
76        self
77    }
78
79    /// Makes this a CROSS join
80    pub const fn cross(mut self) -> Self {
81        self.join_type = JoinType::Cross;
82        self
83    }
84
85    /// Makes this an OUTER join (LEFT OUTER, RIGHT OUTER, FULL OUTER)
86    pub const fn outer(mut self) -> Self {
87        self.outer = true;
88        self
89    }
90}
91
92impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for Join {
93    fn to_sql(&self) -> SQL<'a, V> {
94        // Use pre-computed static strings to avoid Vec allocation
95        let join_str = match (self.natural, self.join_type, self.outer) {
96            // NATURAL variants
97            (true, JoinType::Join, _) => "NATURAL JOIN",
98            (true, JoinType::Inner, _) => "NATURAL INNER JOIN",
99            (true, JoinType::Left, false) => "NATURAL LEFT JOIN",
100            (true, JoinType::Left, true) => "NATURAL LEFT OUTER JOIN",
101            (true, JoinType::Right, false) => "NATURAL RIGHT JOIN",
102            (true, JoinType::Right, true) => "NATURAL RIGHT OUTER JOIN",
103            (true, JoinType::Full, false) => "NATURAL FULL JOIN",
104            (true, JoinType::Full, true) => "NATURAL FULL OUTER JOIN",
105            (true, JoinType::Cross, _) => "NATURAL CROSS JOIN",
106            // Non-NATURAL variants
107            (false, JoinType::Join, _) => "JOIN",
108            (false, JoinType::Inner, _) => "INNER JOIN",
109            (false, JoinType::Left, false) => "LEFT JOIN",
110            (false, JoinType::Left, true) => "LEFT OUTER JOIN",
111            (false, JoinType::Right, false) => "RIGHT JOIN",
112            (false, JoinType::Right, true) => "RIGHT OUTER JOIN",
113            (false, JoinType::Full, false) => "FULL JOIN",
114            (false, JoinType::Full, true) => "FULL OUTER JOIN",
115            (false, JoinType::Cross, _) => "CROSS JOIN",
116        };
117        SQL::raw(join_str)
118    }
119}
120
121// =============================================================================
122// Join Helper Macro
123// =============================================================================
124
125/// Macro to generate join helper functions for a specific dialect.
126///
127/// This macro generates all the standard join helper functions (natural_join,
128/// left_join, etc.) that create SQL JOIN clauses. Each dialect invokes this
129/// macro with their specific table trait and SQL type.
130///
131/// # Usage
132/// ```ignore
133/// impl_join_helpers!(
134///     /// Trait bound for table types
135///     table_trait: SQLiteTable<'a>,
136///     /// Trait bound for condition types
137///     condition_trait: ToSQL<'a, SQLiteValue<'a>>,
138///     /// Return type for SQL
139///     sql_type: SQLiteSQL<'a>,
140/// );
141/// ```
142#[macro_export]
143macro_rules! impl_join_helpers {
144    (
145        table_trait: $TableTrait:path,
146        condition_trait: $ConditionTrait:path,
147        sql_type: $SQLType:ty $(,)?
148    ) => {
149        fn join_internal<'a, Table>(
150            table: Table,
151            join: $crate::Join,
152            condition: impl $ConditionTrait,
153        ) -> $SQLType
154        where
155            Table: $TableTrait,
156        {
157            use $crate::ToSQL;
158            join.to_sql()
159                .append(&table)
160                .push($crate::Token::ON)
161                .append(&condition)
162        }
163
164        /// Helper function to create a NATURAL JOIN clause
165        pub fn natural_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
166        where
167            Table: $TableTrait,
168        {
169            join_internal(table, $crate::Join::new().natural(), condition)
170        }
171
172        /// Helper function to create a JOIN clause
173        pub fn join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
174        where
175            Table: $TableTrait,
176        {
177            join_internal(table, $crate::Join::new(), condition)
178        }
179
180        /// Helper function to create a NATURAL LEFT JOIN clause
181        pub fn natural_left_join<'a, Table>(
182            table: Table,
183            condition: impl $ConditionTrait,
184        ) -> $SQLType
185        where
186            Table: $TableTrait,
187        {
188            join_internal(table, $crate::Join::new().natural().left(), condition)
189        }
190
191        /// Helper function to create a LEFT JOIN clause
192        pub fn left_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
193        where
194            Table: $TableTrait,
195        {
196            join_internal(table, $crate::Join::new().left(), condition)
197        }
198
199        /// Helper function to create a LEFT OUTER JOIN clause
200        pub fn left_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
201        where
202            Table: $TableTrait,
203        {
204            join_internal(table, $crate::Join::new().left().outer(), condition)
205        }
206
207        /// Helper function to create a NATURAL LEFT OUTER JOIN clause
208        pub fn natural_left_outer_join<'a, Table>(
209            table: Table,
210            condition: impl $ConditionTrait,
211        ) -> $SQLType
212        where
213            Table: $TableTrait,
214        {
215            join_internal(
216                table,
217                $crate::Join::new().natural().left().outer(),
218                condition,
219            )
220        }
221
222        /// Helper function to create a NATURAL RIGHT JOIN clause
223        pub fn natural_right_join<'a, Table>(
224            table: Table,
225            condition: impl $ConditionTrait,
226        ) -> $SQLType
227        where
228            Table: $TableTrait,
229        {
230            join_internal(table, $crate::Join::new().natural().right(), condition)
231        }
232
233        /// Helper function to create a RIGHT JOIN clause
234        pub fn right_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
235        where
236            Table: $TableTrait,
237        {
238            join_internal(table, $crate::Join::new().right(), condition)
239        }
240
241        /// Helper function to create a RIGHT OUTER JOIN clause
242        pub fn right_outer_join<'a, Table>(
243            table: Table,
244            condition: impl $ConditionTrait,
245        ) -> $SQLType
246        where
247            Table: $TableTrait,
248        {
249            join_internal(table, $crate::Join::new().right().outer(), condition)
250        }
251
252        /// Helper function to create a NATURAL RIGHT OUTER JOIN clause
253        pub fn natural_right_outer_join<'a, Table>(
254            table: Table,
255            condition: impl $ConditionTrait,
256        ) -> $SQLType
257        where
258            Table: $TableTrait,
259        {
260            join_internal(
261                table,
262                $crate::Join::new().natural().right().outer(),
263                condition,
264            )
265        }
266
267        /// Helper function to create a NATURAL FULL JOIN clause
268        pub fn natural_full_join<'a, Table>(
269            table: Table,
270            condition: impl $ConditionTrait,
271        ) -> $SQLType
272        where
273            Table: $TableTrait,
274        {
275            join_internal(table, $crate::Join::new().natural().full(), condition)
276        }
277
278        /// Helper function to create a FULL JOIN clause
279        pub fn full_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
280        where
281            Table: $TableTrait,
282        {
283            join_internal(table, $crate::Join::new().full(), condition)
284        }
285
286        /// Helper function to create a FULL OUTER JOIN clause
287        pub fn full_outer_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
288        where
289            Table: $TableTrait,
290        {
291            join_internal(table, $crate::Join::new().full().outer(), condition)
292        }
293
294        /// Helper function to create a NATURAL FULL OUTER JOIN clause
295        pub fn natural_full_outer_join<'a, Table>(
296            table: Table,
297            condition: impl $ConditionTrait,
298        ) -> $SQLType
299        where
300            Table: $TableTrait,
301        {
302            join_internal(
303                table,
304                $crate::Join::new().natural().full().outer(),
305                condition,
306            )
307        }
308
309        /// Helper function to create a NATURAL INNER JOIN clause
310        pub fn natural_inner_join<'a, Table>(
311            table: Table,
312            condition: impl $ConditionTrait,
313        ) -> $SQLType
314        where
315            Table: $TableTrait,
316        {
317            join_internal(table, $crate::Join::new().natural().inner(), condition)
318        }
319
320        /// Helper function to create an INNER JOIN clause
321        pub fn inner_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
322        where
323            Table: $TableTrait,
324        {
325            join_internal(table, $crate::Join::new().inner(), condition)
326        }
327
328        /// Helper function to create a CROSS JOIN clause
329        pub fn cross_join<'a, Table>(table: Table, condition: impl $ConditionTrait) -> $SQLType
330        where
331            Table: $TableTrait,
332        {
333            join_internal(table, $crate::Join::new().cross(), condition)
334        }
335    };
336}