drizzle_postgres/
helpers.rs

1use crate::{
2    PostgresSQL, ToPostgresSQL, common::Join, traits::PostgresTable, values::PostgresValue,
3};
4use drizzle_core::{
5    SQL, ToSQL, Token, helpers,
6    traits::{SQLColumnInfo, SQLModel},
7};
8
9// Re-export core helpers with PostgresValue type for convenience
10pub(crate) use helpers::{
11    delete, from, group_by, having, limit, offset, order_by, select, set, update, r#where,
12};
13
14/// Helper to convert column info to SQL for joining (column names only for INSERT)
15fn columns_info_to_sql<'a>(columns: &[&'static dyn SQLColumnInfo]) -> PostgresSQL<'a> {
16    // For INSERT statements, we need just column names, not fully qualified names
17    let joined_names = columns
18        .iter()
19        .map(|col| col.name())
20        .collect::<Vec<_>>()
21        .join(", ");
22    SQL::raw(joined_names)
23}
24
25fn join_internal<'a, Table>(
26    table: Table,
27    join: Join,
28    condition: impl ToPostgresSQL<'a>,
29) -> PostgresSQL<'a>
30where
31    Table: PostgresTable<'a>,
32{
33    join.to_sql()
34        .append(table.to_sql())
35        .push(Token::ON)
36        .append(condition.to_sql())
37}
38
39fn join_using_internal<'a, Table>(
40    table: Table,
41    join: Join,
42    columns: impl ToPostgresSQL<'a>,
43) -> PostgresSQL<'a>
44where
45    Table: PostgresTable<'a>,
46{
47    join.to_sql()
48        .append(table.to_sql())
49        .push(Token::USING)
50        .push(Token::LPAREN)
51        .append(columns.to_sql())
52        .push(Token::RPAREN)
53}
54
55/// Helper function to create a JOIN clause using table generic
56pub fn natural_join<'a, Table>(table: Table, condition: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
57where
58    Table: PostgresTable<'a>,
59{
60    join_internal(table, Join::default().natural(), condition)
61}
62
63/// Helper function to create a JOIN clause using table generic
64pub fn join<'a, Table>(table: Table, condition: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
65where
66    Table: PostgresTable<'a>,
67{
68    join_internal(table, Join::default(), condition)
69}
70
71pub fn natural_left_join<'a, Table>(
72    table: Table,
73    condition: impl ToPostgresSQL<'a>,
74) -> PostgresSQL<'a>
75where
76    Table: PostgresTable<'a>,
77{
78    join_internal(table, Join::new().natural().left(), condition)
79}
80
81/// Helper function to create a LEFT JOIN clause using table generic
82pub fn left_join<'a, Table>(table: Table, condition: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
83where
84    Table: PostgresTable<'a>,
85{
86    join_internal(table, Join::new().left(), condition)
87}
88
89pub fn left_outer_join<'a, Table>(
90    table: Table,
91    condition: impl ToPostgresSQL<'a>,
92) -> PostgresSQL<'a>
93where
94    Table: PostgresTable<'a>,
95{
96    join_internal(table, Join::new().left().outer(), condition)
97}
98
99pub fn natural_left_outer_join<'a, Table>(
100    table: Table,
101    condition: impl ToPostgresSQL<'a>,
102) -> PostgresSQL<'a>
103where
104    Table: PostgresTable<'a>,
105{
106    join_internal(table, Join::new().natural().left().outer(), condition)
107}
108
109pub fn natural_right_join<'a, Table>(
110    table: Table,
111    condition: impl ToPostgresSQL<'a>,
112) -> PostgresSQL<'a>
113where
114    Table: PostgresTable<'a>,
115{
116    join_internal(table, Join::new().natural().right(), condition)
117}
118
119/// Helper function to create a RIGHT JOIN clause using table generic
120pub fn right_join<'a, Table>(table: Table, condition: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
121where
122    Table: PostgresTable<'a>,
123{
124    join_internal(table, Join::new().right(), condition)
125}
126
127pub fn right_outer_join<'a, Table>(
128    table: Table,
129    condition: impl ToPostgresSQL<'a>,
130) -> PostgresSQL<'a>
131where
132    Table: PostgresTable<'a>,
133{
134    join_internal(table, Join::new().right().outer(), condition)
135}
136
137pub fn natural_right_outer_join<'a, Table>(
138    table: Table,
139    condition: impl ToPostgresSQL<'a>,
140) -> PostgresSQL<'a>
141where
142    Table: PostgresTable<'a>,
143{
144    join_internal(table, Join::new().natural().right().outer(), condition)
145}
146
147pub fn natural_full_join<'a, Table>(
148    table: Table,
149    condition: impl ToPostgresSQL<'a>,
150) -> PostgresSQL<'a>
151where
152    Table: PostgresTable<'a>,
153{
154    join_internal(table, Join::new().natural().full(), condition)
155}
156
157/// Helper function to create a FULL JOIN clause using table generic
158pub fn full_join<'a, Table>(table: Table, condition: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
159where
160    Table: PostgresTable<'a>,
161{
162    join_internal(table, Join::new().full(), condition)
163}
164
165pub fn full_outer_join<'a, Table>(
166    table: Table,
167    condition: impl ToPostgresSQL<'a>,
168) -> PostgresSQL<'a>
169where
170    Table: PostgresTable<'a>,
171{
172    join_internal(table, Join::new().full().outer(), condition)
173}
174
175pub fn natural_full_outer_join<'a, Table>(
176    table: Table,
177    condition: impl ToPostgresSQL<'a>,
178) -> PostgresSQL<'a>
179where
180    Table: PostgresTable<'a>,
181{
182    join_internal(table, Join::new().natural().full().outer(), condition)
183}
184
185pub fn natural_inner_join<'a, Table>(
186    table: Table,
187    condition: impl ToPostgresSQL<'a>,
188) -> PostgresSQL<'a>
189where
190    Table: PostgresTable<'a>,
191{
192    join_internal(table, Join::new().natural().inner(), condition)
193}
194
195/// Helper function to create an INNER JOIN clause using table generic
196pub fn inner_join<'a, Table>(table: Table, condition: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
197where
198    Table: PostgresTable<'a>,
199{
200    join_internal(table, Join::new().inner(), condition)
201}
202
203/// Helper function to create a CROSS JOIN clause using table generic
204pub fn cross_join<'a, Table>(table: Table, condition: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
205where
206    Table: PostgresTable<'a>,
207{
208    join_internal(table, Join::new().cross(), condition)
209}
210
211//------------------------------------------------------------------------------
212// USING clause versions of JOIN functions (PostgreSQL-specific)
213//------------------------------------------------------------------------------
214
215pub fn join_using<'a, Table>(table: Table, columns: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
216where
217    Table: PostgresTable<'a>,
218{
219    join_using_internal(table, Join::new(), columns)
220}
221
222pub fn inner_join_using<'a, Table>(table: Table, columns: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
223where
224    Table: PostgresTable<'a>,
225{
226    join_using_internal(table, Join::new().inner(), columns)
227}
228
229pub fn left_join_using<'a, Table>(table: Table, columns: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
230where
231    Table: PostgresTable<'a>,
232{
233    join_using_internal(table, Join::new().left(), columns)
234}
235
236pub fn left_outer_join_using<'a, Table>(
237    table: Table,
238    columns: impl ToPostgresSQL<'a>,
239) -> PostgresSQL<'a>
240where
241    Table: PostgresTable<'a>,
242{
243    join_using_internal(table, Join::new().left().outer(), columns)
244}
245
246pub fn right_join_using<'a, Table>(table: Table, columns: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
247where
248    Table: PostgresTable<'a>,
249{
250    join_using_internal(table, Join::new().right(), columns)
251}
252
253pub fn right_outer_join_using<'a, Table>(
254    table: Table,
255    columns: impl ToPostgresSQL<'a>,
256) -> PostgresSQL<'a>
257where
258    Table: PostgresTable<'a>,
259{
260    join_using_internal(table, Join::new().right().outer(), columns)
261}
262
263pub fn full_join_using<'a, Table>(table: Table, columns: impl ToPostgresSQL<'a>) -> PostgresSQL<'a>
264where
265    Table: PostgresTable<'a>,
266{
267    join_using_internal(table, Join::new().full(), columns)
268}
269
270pub fn full_outer_join_using<'a, Table>(
271    table: Table,
272    columns: impl ToPostgresSQL<'a>,
273) -> PostgresSQL<'a>
274where
275    Table: PostgresTable<'a>,
276{
277    join_using_internal(table, Join::new().full().outer(), columns)
278}
279
280// Note: NATURAL JOINs don't use USING clause as they automatically match column names
281// CROSS JOIN also doesn't use USING clause as it produces Cartesian product
282
283/// Creates an INSERT INTO statement with the specified table - PostgreSQL specific
284pub(crate) fn insert<'a, Table>(table: Table) -> SQL<'a, PostgresValue<'a>>
285where
286    Table: PostgresTable<'a>,
287{
288    SQL::from_iter([Token::INSERT, Token::INTO]).append(&table)
289}
290
291/// Helper function to create VALUES clause for INSERT with pattern validation
292/// All rows must have the same column pattern (enforced by type parameter)
293pub(crate) fn values<'a, Table, T>(
294    rows: impl IntoIterator<Item = Table::Insert<T>>,
295) -> SQL<'a, PostgresValue<'a>>
296where
297    Table: PostgresTable<'a>,
298{
299    let rows: Vec<_> = rows.into_iter().collect();
300
301    if rows.is_empty() {
302        return SQL::from(Token::VALUES);
303    }
304
305    // Since all rows have the same PATTERN, they all have the same columns
306    // Get column info from the first row (all rows will have the same columns)
307    let columns_info = rows[0].columns();
308
309    // Check if this is a DEFAULT VALUES case (no columns)
310    if columns_info.is_empty() {
311        return SQL::from_iter([Token::DEFAULT, Token::VALUES]);
312    }
313
314    let columns_sql = columns_info_to_sql(&columns_info);
315    let value_clauses: Vec<_> = rows.iter().map(|row| row.values().parens()).collect();
316
317    columns_sql
318        .parens()
319        .push(Token::VALUES)
320        .append(SQL::join(value_clauses, Token::COMMA))
321}
322
323/// Helper function to create a RETURNING clause - PostgreSQL specific
324pub(crate) fn returning<'a, 'b, I>(columns: I) -> PostgresSQL<'a>
325where
326    I: ToPostgresSQL<'a>,
327{
328    SQL::from(Token::RETURNING).append(columns.to_sql())
329}
330
331/// Helper function to create an UPSERT (ON CONFLICT) clause - PostgreSQL specific
332pub(crate) fn on_conflict<'a, T>(
333    conflict_target: Option<PostgresSQL<'a>>,
334    action: impl ToPostgresSQL<'a>,
335) -> PostgresSQL<'a> {
336    let mut sql = SQL::from_iter([Token::ON, Token::CONFLICT]);
337
338    if let Some(target) = conflict_target {
339        sql = sql.push(Token::LPAREN).append(target).push(Token::RPAREN);
340    }
341
342    sql.append(action.to_sql())
343}