Skip to main content

drizzle_postgres/
helpers.rs

1#[cfg(not(feature = "std"))]
2use crate::prelude::*;
3use crate::traits::PostgresTable;
4use crate::values::PostgresValue;
5use drizzle_core::{ColumnRef, SQL, SQLTableInfo, ToSQL, Token, helpers, traits::SQLModel};
6
7// Re-export core helpers with PostgresValue type for convenience
8pub(crate) use helpers::{
9    delete, except, except_all, from, group_by_expr, having, intersect, intersect_all, limit,
10    offset, order_by, select, select_distinct, set, union, union_all, update, r#where,
11};
12
13// Re-export Join from core
14pub use drizzle_core::Join;
15
16drizzle_core::impl_join_arg_trait!(
17    table_trait: PostgresTable<'a>,
18    table_info_trait: SQLTableInfo,
19    condition_trait: ToSQL<'a, PostgresValue<'a>>,
20    value_type: PostgresValue<'a>,
21);
22
23/// Helper to convert column info to SQL for joining (column names only for INSERT)
24fn columns_info_to_sql<'a>(columns: &[ColumnRef]) -> SQL<'a, PostgresValue<'a>> {
25    let mut sql = SQL::with_capacity_chunks(columns.len().saturating_mul(2));
26    for (idx, col) in columns.iter().enumerate() {
27        if idx > 0 {
28            sql.push_mut(Token::COMMA);
29        }
30        sql.append_mut(SQL::ident(col.name));
31    }
32    sql
33}
34
35// Generate all join helper functions using the shared macro
36drizzle_core::impl_join_helpers!(
37    table_trait: PostgresTable<'a>,
38    condition_trait: ToSQL<'a, PostgresValue<'a>>,
39    sql_type: SQL<'a, PostgresValue<'a>>,
40);
41
42/// Helper function to create a SELECT DISTINCT ON statement (PostgreSQL-specific)
43pub(crate) fn select_distinct_on<'a, On, Columns>(
44    on: On,
45    columns: Columns,
46) -> SQL<'a, PostgresValue<'a>>
47where
48    On: ToSQL<'a, PostgresValue<'a>>,
49    Columns: ToSQL<'a, PostgresValue<'a>>,
50{
51    SQL::from_iter([Token::SELECT, Token::DISTINCT, Token::ON, Token::LPAREN])
52        .append(on.to_sql())
53        .push(Token::RPAREN)
54        .append(columns.to_sql())
55}
56
57//------------------------------------------------------------------------------
58// USING clause internal helper (PostgreSQL-specific)
59//------------------------------------------------------------------------------
60
61fn join_using_internal<'a, Table>(
62    table: Table,
63    join: Join,
64    columns: impl ToSQL<'a, PostgresValue<'a>>,
65) -> SQL<'a, PostgresValue<'a>>
66where
67    Table: PostgresTable<'a>,
68{
69    join.to_sql()
70        .append(table.to_sql())
71        .push(Token::USING)
72        .push(Token::LPAREN)
73        .append(columns.to_sql())
74        .push(Token::RPAREN)
75}
76
77//------------------------------------------------------------------------------
78// USING clause versions of JOIN functions (PostgreSQL-specific)
79//------------------------------------------------------------------------------
80
81/// Creates a JOIN ... USING clause, matching rows where the named columns are equal.
82pub fn join_using<'a, Table>(
83    table: Table,
84    columns: impl ToSQL<'a, PostgresValue<'a>>,
85) -> SQL<'a, PostgresValue<'a>>
86where
87    Table: PostgresTable<'a>,
88{
89    join_using_internal(table, Join::new(), columns)
90}
91
92/// Creates an INNER JOIN ... USING clause.
93pub fn inner_join_using<'a, Table>(
94    table: Table,
95    columns: impl ToSQL<'a, PostgresValue<'a>>,
96) -> SQL<'a, PostgresValue<'a>>
97where
98    Table: PostgresTable<'a>,
99{
100    join_using_internal(table, Join::new().inner(), columns)
101}
102
103/// Creates a LEFT JOIN ... USING clause.
104pub fn left_join_using<'a, Table>(
105    table: Table,
106    columns: impl ToSQL<'a, PostgresValue<'a>>,
107) -> SQL<'a, PostgresValue<'a>>
108where
109    Table: PostgresTable<'a>,
110{
111    join_using_internal(table, Join::new().left(), columns)
112}
113
114/// Creates a LEFT OUTER JOIN ... USING clause.
115pub fn left_outer_join_using<'a, Table>(
116    table: Table,
117    columns: impl ToSQL<'a, PostgresValue<'a>>,
118) -> SQL<'a, PostgresValue<'a>>
119where
120    Table: PostgresTable<'a>,
121{
122    join_using_internal(table, Join::new().left().outer(), columns)
123}
124
125/// Creates a RIGHT JOIN ... USING clause.
126pub fn right_join_using<'a, Table>(
127    table: Table,
128    columns: impl ToSQL<'a, PostgresValue<'a>>,
129) -> SQL<'a, PostgresValue<'a>>
130where
131    Table: PostgresTable<'a>,
132{
133    join_using_internal(table, Join::new().right(), columns)
134}
135
136/// Creates a RIGHT OUTER JOIN ... USING clause.
137pub fn right_outer_join_using<'a, Table>(
138    table: Table,
139    columns: impl ToSQL<'a, PostgresValue<'a>>,
140) -> SQL<'a, PostgresValue<'a>>
141where
142    Table: PostgresTable<'a>,
143{
144    join_using_internal(table, Join::new().right().outer(), columns)
145}
146
147/// Creates a FULL JOIN ... USING clause.
148pub fn full_join_using<'a, Table>(
149    table: Table,
150    columns: impl ToSQL<'a, PostgresValue<'a>>,
151) -> SQL<'a, PostgresValue<'a>>
152where
153    Table: PostgresTable<'a>,
154{
155    join_using_internal(table, Join::new().full(), columns)
156}
157
158/// Creates a FULL OUTER JOIN ... USING clause.
159pub fn full_outer_join_using<'a, Table>(
160    table: Table,
161    columns: impl ToSQL<'a, PostgresValue<'a>>,
162) -> SQL<'a, PostgresValue<'a>>
163where
164    Table: PostgresTable<'a>,
165{
166    join_using_internal(table, Join::new().full().outer(), columns)
167}
168
169// Note: NATURAL JOINs don't use USING clause as they automatically match column names
170// CROSS JOIN also doesn't use USING clause as it produces Cartesian product
171
172/// Creates an INSERT INTO statement with the specified table - `PostgreSQL` specific
173pub(crate) fn insert<'a, Table>(table: &Table) -> SQL<'a, PostgresValue<'a>>
174where
175    Table: PostgresTable<'a>,
176{
177    SQL::from_iter([Token::INSERT, Token::INTO]).append(table)
178}
179
180/// Creates a VALUES clause for INSERT statements.
181/// All rows must declare the same set of columns.
182pub(crate) fn values<'a, Table, T>(
183    rows: impl IntoIterator<Item = Table::Insert<T>>,
184) -> SQL<'a, PostgresValue<'a>>
185where
186    Table: PostgresTable<'a>,
187{
188    let rows: Vec<_> = rows.into_iter().collect();
189
190    if rows.is_empty() {
191        return SQL::from(Token::VALUES);
192    }
193
194    // Since all rows have the same PATTERN, they all have the same columns
195    // Get column info from the first row (all rows will have the same columns)
196    let columns_info = rows[0].columns();
197    let columns_slice = columns_info.as_ref();
198    // Check if this is a DEFAULT VALUES case (no columns)
199    if columns_slice.is_empty() {
200        return SQL::from_iter([Token::DEFAULT, Token::VALUES]);
201    }
202
203    let columns_sql = columns_info_to_sql(columns_slice);
204    let mut values_sql = SQL::with_capacity_chunks(rows.len().saturating_mul(4));
205    for (idx, row) in rows.iter().enumerate() {
206        if idx > 0 {
207            values_sql.push_mut(Token::COMMA);
208        }
209        values_sql.push_mut(Token::LPAREN);
210        values_sql.append_mut(row.values());
211        values_sql.push_mut(Token::RPAREN);
212    }
213
214    columns_sql.parens().push(Token::VALUES).append(values_sql)
215}
216
217/// Helper function to create a RETURNING clause - `PostgreSQL` specific
218pub(crate) fn returning<'a, 'b, I>(columns: I) -> SQL<'a, PostgresValue<'a>>
219where
220    I: ToSQL<'a, PostgresValue<'a>>,
221{
222    let columns = columns.to_sql();
223    let columns = if columns.chunks.is_empty() {
224        SQL::from(Token::STAR)
225    } else {
226        columns
227    };
228    SQL::from(Token::RETURNING).append(columns)
229}
230
231//------------------------------------------------------------------------------
232// FOR UPDATE/SHARE row locking (PostgreSQL-specific)
233//------------------------------------------------------------------------------
234
235/// Helper function to create a FOR UPDATE clause
236pub(crate) fn for_update<'a>() -> SQL<'a, PostgresValue<'a>> {
237    SQL::from_iter([Token::FOR, Token::UPDATE])
238}
239
240/// Helper function to create a FOR SHARE clause
241pub(crate) fn for_share<'a>() -> SQL<'a, PostgresValue<'a>> {
242    SQL::from_iter([Token::FOR, Token::SHARE])
243}
244
245/// Helper function to create a FOR NO KEY UPDATE clause
246pub(crate) fn for_no_key_update<'a>() -> SQL<'a, PostgresValue<'a>> {
247    SQL::from_iter([Token::FOR, Token::NO, Token::KEY, Token::UPDATE])
248}
249
250/// Helper function to create a FOR KEY SHARE clause
251pub(crate) fn for_key_share<'a>() -> SQL<'a, PostgresValue<'a>> {
252    SQL::from_iter([Token::FOR, Token::KEY, Token::SHARE])
253}
254
255/// Helper function to create a FOR UPDATE OF table clause.
256/// Uses unqualified table name as required by `PostgreSQL`.
257pub(crate) fn for_update_of<'a>(table_name: &str) -> SQL<'a, PostgresValue<'a>> {
258    SQL::from_iter([Token::FOR, Token::UPDATE, Token::OF])
259        .append(SQL::ident(String::from(table_name)))
260}
261
262/// Helper function to create a FOR SHARE OF table clause.
263/// Uses unqualified table name as required by `PostgreSQL`.
264pub(crate) fn for_share_of<'a>(table_name: &str) -> SQL<'a, PostgresValue<'a>> {
265    SQL::from_iter([Token::FOR, Token::SHARE, Token::OF])
266        .append(SQL::ident(String::from(table_name)))
267}
268
269/// Helper function to add NOWAIT to a FOR clause
270pub(crate) fn nowait<'a>() -> SQL<'a, PostgresValue<'a>> {
271    SQL::from(Token::NOWAIT)
272}
273
274/// Helper function to add SKIP LOCKED to a FOR clause
275pub(crate) fn skip_locked<'a>() -> SQL<'a, PostgresValue<'a>> {
276    SQL::from_iter([Token::SKIP, Token::LOCKED])
277}