drizzle_postgres/
helpers.rs

1use crate::{PostgresSQL, traits::PostgresTable, values::PostgresValue};
2use drizzle_core::{
3    Join, SQL, ToSQL, Token, helpers,
4    traits::{SQLColumnInfo, SQLModel},
5};
6
7// Re-export core helpers with PostgresValue type for convenience
8pub(crate) use helpers::{
9    delete, except, except_all, from, group_by, having, intersect, intersect_all, limit, offset,
10    order_by, select, select_distinct, set, union, union_all, update, r#where,
11};
12
13/// Helper to convert column info to SQL for joining (column names only for INSERT)
14fn columns_info_to_sql<'a>(columns: &[&'static dyn SQLColumnInfo]) -> PostgresSQL<'a> {
15    // For INSERT statements, use quoted column names only (no table qualifiers)
16    SQL::join(
17        columns.iter().map(|col| SQL::ident(col.name())),
18        Token::COMMA,
19    )
20}
21
22// Generate all join helper functions using the shared macro
23drizzle_core::impl_join_helpers!(
24    table_trait: PostgresTable<'a>,
25    condition_trait: ToSQL<'a, PostgresValue<'a>>,
26    sql_type: PostgresSQL<'a>,
27);
28
29/// Helper function to create a SELECT DISTINCT ON statement (PostgreSQL-specific)
30pub(crate) fn select_distinct_on<'a, On, Columns>(
31    on: On,
32    columns: Columns,
33) -> SQL<'a, PostgresValue<'a>>
34where
35    On: ToSQL<'a, PostgresValue<'a>>,
36    Columns: ToSQL<'a, PostgresValue<'a>>,
37{
38    SQL::from_iter([Token::SELECT, Token::DISTINCT, Token::ON, Token::LPAREN])
39        .append(on.to_sql())
40        .push(Token::RPAREN)
41        .append(columns.to_sql())
42}
43
44//------------------------------------------------------------------------------
45// USING clause internal helper (PostgreSQL-specific)
46//------------------------------------------------------------------------------
47
48fn join_using_internal<'a, Table>(
49    table: Table,
50    join: Join,
51    columns: impl ToSQL<'a, PostgresValue<'a>>,
52) -> PostgresSQL<'a>
53where
54    Table: PostgresTable<'a>,
55{
56    join.to_sql()
57        .append(table.to_sql())
58        .push(Token::USING)
59        .push(Token::LPAREN)
60        .append(columns.to_sql())
61        .push(Token::RPAREN)
62}
63
64//------------------------------------------------------------------------------
65// USING clause versions of JOIN functions (PostgreSQL-specific)
66//------------------------------------------------------------------------------
67
68pub fn join_using<'a, Table>(
69    table: Table,
70    columns: impl ToSQL<'a, PostgresValue<'a>>,
71) -> PostgresSQL<'a>
72where
73    Table: PostgresTable<'a>,
74{
75    join_using_internal(table, Join::new(), columns)
76}
77
78pub fn inner_join_using<'a, Table>(
79    table: Table,
80    columns: impl ToSQL<'a, PostgresValue<'a>>,
81) -> PostgresSQL<'a>
82where
83    Table: PostgresTable<'a>,
84{
85    join_using_internal(table, Join::new().inner(), columns)
86}
87
88pub fn left_join_using<'a, Table>(
89    table: Table,
90    columns: impl ToSQL<'a, PostgresValue<'a>>,
91) -> PostgresSQL<'a>
92where
93    Table: PostgresTable<'a>,
94{
95    join_using_internal(table, Join::new().left(), columns)
96}
97
98pub fn left_outer_join_using<'a, Table>(
99    table: Table,
100    columns: impl ToSQL<'a, PostgresValue<'a>>,
101) -> PostgresSQL<'a>
102where
103    Table: PostgresTable<'a>,
104{
105    join_using_internal(table, Join::new().left().outer(), columns)
106}
107
108pub fn right_join_using<'a, Table>(
109    table: Table,
110    columns: impl ToSQL<'a, PostgresValue<'a>>,
111) -> PostgresSQL<'a>
112where
113    Table: PostgresTable<'a>,
114{
115    join_using_internal(table, Join::new().right(), columns)
116}
117
118pub fn right_outer_join_using<'a, Table>(
119    table: Table,
120    columns: impl ToSQL<'a, PostgresValue<'a>>,
121) -> PostgresSQL<'a>
122where
123    Table: PostgresTable<'a>,
124{
125    join_using_internal(table, Join::new().right().outer(), columns)
126}
127
128pub fn full_join_using<'a, Table>(
129    table: Table,
130    columns: impl ToSQL<'a, PostgresValue<'a>>,
131) -> PostgresSQL<'a>
132where
133    Table: PostgresTable<'a>,
134{
135    join_using_internal(table, Join::new().full(), columns)
136}
137
138pub fn full_outer_join_using<'a, Table>(
139    table: Table,
140    columns: impl ToSQL<'a, PostgresValue<'a>>,
141) -> PostgresSQL<'a>
142where
143    Table: PostgresTable<'a>,
144{
145    join_using_internal(table, Join::new().full().outer(), columns)
146}
147
148// Note: NATURAL JOINs don't use USING clause as they automatically match column names
149// CROSS JOIN also doesn't use USING clause as it produces Cartesian product
150
151/// Creates an INSERT INTO statement with the specified table - PostgreSQL specific
152pub(crate) fn insert<'a, Table>(table: Table) -> SQL<'a, PostgresValue<'a>>
153where
154    Table: PostgresTable<'a>,
155{
156    SQL::from_iter([Token::INSERT, Token::INTO]).append(&table)
157}
158
159/// Helper function to create VALUES clause for INSERT with pattern validation
160/// All rows must have the same column pattern (enforced by type parameter)
161pub(crate) fn values<'a, Table, T>(
162    rows: impl IntoIterator<Item = Table::Insert<T>>,
163) -> SQL<'a, PostgresValue<'a>>
164where
165    Table: PostgresTable<'a>,
166{
167    let rows: Vec<_> = rows.into_iter().collect();
168
169    if rows.is_empty() {
170        return SQL::from(Token::VALUES);
171    }
172
173    // Since all rows have the same PATTERN, they all have the same columns
174    // Get column info from the first row (all rows will have the same columns)
175    let columns_info = rows[0].columns();
176    let columns_slice = columns_info.as_ref();
177    // Check if this is a DEFAULT VALUES case (no columns)
178    if columns_slice.is_empty() {
179        return SQL::from_iter([Token::DEFAULT, Token::VALUES]);
180    }
181
182    let columns_sql = columns_info_to_sql(columns_slice);
183    let value_clauses: Vec<_> = rows.iter().map(|row| row.values().parens()).collect();
184
185    columns_sql
186        .parens()
187        .push(Token::VALUES)
188        .append(SQL::join(value_clauses, Token::COMMA))
189}
190
191/// Helper function to create a RETURNING clause - PostgreSQL specific
192pub(crate) fn returning<'a, 'b, I>(columns: I) -> PostgresSQL<'a>
193where
194    I: ToSQL<'a, PostgresValue<'a>>,
195{
196    SQL::from(Token::RETURNING).append(columns.to_sql())
197}
198
199/// Helper function to create an UPSERT (ON CONFLICT) clause - PostgreSQL specific
200#[allow(dead_code)]
201pub(crate) fn on_conflict<'a>(
202    conflict_target: Option<PostgresSQL<'a>>,
203    action: impl ToSQL<'a, PostgresValue<'a>>,
204) -> PostgresSQL<'a> {
205    let mut sql = SQL::from_iter([Token::ON, Token::CONFLICT]);
206
207    if let Some(target) = conflict_target {
208        sql = sql.push(Token::LPAREN).append(target).push(Token::RPAREN);
209    }
210
211    sql.append(action.to_sql())
212}