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::{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
16/// A table-like source accepted by an explicit JOIN tuple.
17#[doc(hidden)]
18pub trait JoinSource<'a>: join_source_private::Sealed {
19    type JoinedTable;
20
21    fn into_join_source_sql(self) -> SQL<'a, PostgresValue<'a>>;
22}
23
24mod join_source_private {
25    pub trait Sealed {}
26}
27
28impl<'a, Table> join_source_private::Sealed for Table where Table: PostgresTable<'a> {}
29
30impl<'a, Name, Projection, Query> join_source_private::Sealed
31    for drizzle_core::Derived<'a, PostgresValue<'a>, Name, Projection, Query>
32where
33    Name: drizzle_core::Tag,
34    Projection: drizzle_core::DerivedProjection<Name>,
35    Query: ToSQL<'a, PostgresValue<'a>>,
36{
37}
38
39impl<'a, Table> JoinSource<'a> for Table
40where
41    Table: PostgresTable<'a>,
42{
43    type JoinedTable = Table;
44
45    fn into_join_source_sql(self) -> SQL<'a, PostgresValue<'a>> {
46        self.into_sql()
47    }
48}
49
50impl<'a, Name, Projection, Query> JoinSource<'a>
51    for drizzle_core::Derived<'a, PostgresValue<'a>, Name, Projection, Query>
52where
53    Name: drizzle_core::Tag,
54    Projection: drizzle_core::DerivedProjection<Name>,
55    Query: ToSQL<'a, PostgresValue<'a>>,
56{
57    type JoinedTable = Self;
58
59    fn into_join_source_sql(self) -> SQL<'a, PostgresValue<'a>> {
60        self.into_sql()
61    }
62}
63
64/// A source or legacy tuple accepted by [`crate::builder::SelectBuilder::cross_join`].
65///
66/// A bare source renders `CROSS JOIN`. The legacy `(source, predicate)`
67/// form renders the equivalent portable `INNER JOIN ... ON ...`, because
68/// PostgreSQL does not allow an `ON` clause after `CROSS JOIN`.
69#[doc(hidden)]
70pub trait CrossJoinArg<'a, FromTable>: cross_join_arg_private::Sealed {
71    type JoinedTable;
72
73    fn into_cross_join_sql(self) -> SQL<'a, PostgresValue<'a>>;
74}
75
76mod cross_join_arg_private {
77    pub trait Sealed {}
78
79    impl<'a, Source> Sealed for Source where Source: super::JoinSource<'a> {}
80
81    impl<'a, Source, Condition> Sealed for (Source, Condition)
82    where
83        Source: super::JoinSource<'a>,
84        Condition: drizzle_core::ToSQL<'a, crate::values::PostgresValue<'a>>,
85    {
86    }
87}
88
89impl<'a, Source, FromTable> CrossJoinArg<'a, FromTable> for Source
90where
91    Source: JoinSource<'a>,
92{
93    type JoinedTable = Source::JoinedTable;
94
95    fn into_cross_join_sql(self) -> SQL<'a, PostgresValue<'a>> {
96        Join::new()
97            .cross()
98            .into_sql()
99            .append(self.into_join_source_sql())
100    }
101}
102
103impl<'a, Source, Condition, FromTable> CrossJoinArg<'a, FromTable> for (Source, Condition)
104where
105    Source: JoinSource<'a>,
106    Condition: ToSQL<'a, PostgresValue<'a>>,
107{
108    type JoinedTable = Source::JoinedTable;
109
110    fn into_cross_join_sql(self) -> SQL<'a, PostgresValue<'a>> {
111        let (source, condition) = self;
112        Join::new()
113            .inner()
114            .into_sql()
115            .append(source.into_join_source_sql())
116            .push(Token::ON)
117            .append(condition.into_sql())
118    }
119}
120
121drizzle_core::impl_join_arg_trait!(
122    table_trait: PostgresTable<'a>,
123    table_info_trait: SQLTableInfo,
124    condition_trait: ToSQL<'a, PostgresValue<'a>>,
125    join_source_trait: JoinSource<'a>,
126    value_type: PostgresValue<'a>,
127);
128
129// Generate all join helper functions using the shared macro
130drizzle_core::impl_join_helpers!(
131    table_trait: PostgresTable<'a>,
132    condition_trait: ToSQL<'a, PostgresValue<'a>>,
133    sql_type: SQL<'a, PostgresValue<'a>>,
134);
135
136/// Helper function to create a SELECT DISTINCT ON statement (PostgreSQL-specific)
137pub(crate) fn select_distinct_on<'a, On, Columns>(
138    on: On,
139    columns: Columns,
140) -> SQL<'a, PostgresValue<'a>>
141where
142    On: ToSQL<'a, PostgresValue<'a>>,
143    Columns: ToSQL<'a, PostgresValue<'a>>,
144{
145    SQL::from_iter([Token::SELECT, Token::DISTINCT, Token::ON, Token::LPAREN])
146        .append(on.into_sql())
147        .push(Token::RPAREN)
148        .append(columns.into_sql())
149}
150
151//------------------------------------------------------------------------------
152// USING clause internal helper (PostgreSQL-specific)
153//------------------------------------------------------------------------------
154
155fn join_using_internal<'a, Table>(
156    table: Table,
157    join: Join,
158    columns: impl ToSQL<'a, PostgresValue<'a>>,
159) -> SQL<'a, PostgresValue<'a>>
160where
161    Table: PostgresTable<'a>,
162{
163    join.into_sql()
164        .append(table.into_sql())
165        .push(Token::USING)
166        .push(Token::LPAREN)
167        .append(columns.into_sql())
168        .push(Token::RPAREN)
169}
170
171//------------------------------------------------------------------------------
172// USING clause versions of JOIN functions (PostgreSQL-specific)
173//------------------------------------------------------------------------------
174
175/// Creates a JOIN ... USING clause, matching rows where the named columns are equal.
176pub fn join_using<'a, Table>(
177    table: Table,
178    columns: impl ToSQL<'a, PostgresValue<'a>>,
179) -> SQL<'a, PostgresValue<'a>>
180where
181    Table: PostgresTable<'a>,
182{
183    join_using_internal(table, Join::new(), columns)
184}
185
186/// Creates an INNER JOIN ... USING clause.
187pub fn inner_join_using<'a, Table>(
188    table: Table,
189    columns: impl ToSQL<'a, PostgresValue<'a>>,
190) -> SQL<'a, PostgresValue<'a>>
191where
192    Table: PostgresTable<'a>,
193{
194    join_using_internal(table, Join::new().inner(), columns)
195}
196
197/// Creates a LEFT JOIN ... USING clause.
198pub fn left_join_using<'a, Table>(
199    table: Table,
200    columns: impl ToSQL<'a, PostgresValue<'a>>,
201) -> SQL<'a, PostgresValue<'a>>
202where
203    Table: PostgresTable<'a>,
204{
205    join_using_internal(table, Join::new().left(), columns)
206}
207
208/// Creates a LEFT OUTER JOIN ... USING clause.
209pub fn left_outer_join_using<'a, Table>(
210    table: Table,
211    columns: impl ToSQL<'a, PostgresValue<'a>>,
212) -> SQL<'a, PostgresValue<'a>>
213where
214    Table: PostgresTable<'a>,
215{
216    join_using_internal(table, Join::new().left().outer(), columns)
217}
218
219/// Creates a RIGHT JOIN ... USING clause.
220pub fn right_join_using<'a, Table>(
221    table: Table,
222    columns: impl ToSQL<'a, PostgresValue<'a>>,
223) -> SQL<'a, PostgresValue<'a>>
224where
225    Table: PostgresTable<'a>,
226{
227    join_using_internal(table, Join::new().right(), columns)
228}
229
230/// Creates a RIGHT OUTER JOIN ... USING clause.
231pub fn right_outer_join_using<'a, Table>(
232    table: Table,
233    columns: impl ToSQL<'a, PostgresValue<'a>>,
234) -> SQL<'a, PostgresValue<'a>>
235where
236    Table: PostgresTable<'a>,
237{
238    join_using_internal(table, Join::new().right().outer(), columns)
239}
240
241/// Creates a FULL JOIN ... USING clause.
242pub fn full_join_using<'a, Table>(
243    table: Table,
244    columns: impl ToSQL<'a, PostgresValue<'a>>,
245) -> SQL<'a, PostgresValue<'a>>
246where
247    Table: PostgresTable<'a>,
248{
249    join_using_internal(table, Join::new().full(), columns)
250}
251
252/// Creates a FULL OUTER JOIN ... USING clause.
253pub fn full_outer_join_using<'a, Table>(
254    table: Table,
255    columns: impl ToSQL<'a, PostgresValue<'a>>,
256) -> SQL<'a, PostgresValue<'a>>
257where
258    Table: PostgresTable<'a>,
259{
260    join_using_internal(table, Join::new().full().outer(), columns)
261}
262
263// Note: NATURAL JOINs don't use USING clause as they automatically match column names
264// CROSS JOIN also doesn't use USING clause as it produces Cartesian product
265
266/// Creates an INSERT INTO statement with the specified table - `PostgreSQL` specific
267pub(crate) fn insert<'a, Table>(table: &Table) -> SQL<'a, PostgresValue<'a>>
268where
269    Table: PostgresTable<'a>,
270{
271    SQL::from_iter([Token::INSERT, Token::INTO]).append(table)
272}
273
274/// Creates the rows of an INSERT statement.
275///
276/// Rows usually set the same columns. A `None` passed to a `with_*` setter
277/// leaves that column to its default without changing the row's type, so
278/// rows can differ; then every row lists the union of the columns, with
279/// `DEFAULT` where it sets none.
280pub(crate) fn values<'a, Table, T>(
281    rows: impl IntoIterator<Item = Table::Insert<T>>,
282) -> SQL<'a, PostgresValue<'a>>
283where
284    Table: PostgresTable<'a>,
285{
286    let rows: Vec<_> = rows.into_iter().collect();
287
288    if rows.is_empty() {
289        return SQL::from(Token::VALUES);
290    }
291
292    let columns_info = rows[0].columns();
293    let columns_slice = columns_info.as_ref();
294    if rows[1..]
295        .iter()
296        .any(|row| row.columns().as_ref() != columns_slice)
297    {
298        let rows_sql = drizzle_core::helpers::insert_values_with_defaults(
299            rows.iter()
300                .map(|row| (row.columns(), row.values()))
301                .collect(),
302        );
303        if let Some(rows_sql) = rows_sql {
304            return rows_sql;
305        }
306    }
307
308    if columns_slice.is_empty() {
309        // `DEFAULT VALUES` inserts one row. A query without columns inserts
310        // one all-default row per result row.
311        // Raw text, not SELECT/FROM tokens: the renderer expands a bare
312        // `SELECT` token followed by `FROM` into a projection.
313        return if rows.len() == 1 {
314            SQL::from_iter([Token::DEFAULT, Token::VALUES])
315        } else {
316            SQL::raw("SELECT FROM").append(SQL::func(
317                "generate_series",
318                SQL::number(1)
319                    .push(Token::COMMA)
320                    .append(SQL::number(rows.len())),
321            ))
322        };
323    }
324
325    let columns_sql = SQL::columns(columns_slice);
326    let mut values_sql = SQL::with_capacity_chunks(rows.len().saturating_mul(4));
327    for (idx, row) in rows.iter().enumerate() {
328        if idx > 0 {
329            values_sql.push_mut(Token::COMMA);
330        }
331        values_sql.push_mut(Token::LPAREN);
332        values_sql.append_mut(row.values());
333        values_sql.push_mut(Token::RPAREN);
334    }
335
336    columns_sql.parens().push(Token::VALUES).append(values_sql)
337}
338
339/// Helper function to create a RETURNING clause - `PostgreSQL` specific
340pub(crate) fn returning<'a, 'b, I>(columns: I) -> SQL<'a, PostgresValue<'a>>
341where
342    I: ToSQL<'a, PostgresValue<'a>>,
343{
344    let columns = columns.into_sql();
345    let columns = if columns.chunks.is_empty() {
346        SQL::from(Token::STAR)
347    } else {
348        columns
349    };
350    SQL::from(Token::RETURNING).append(columns)
351}
352
353//------------------------------------------------------------------------------
354// FOR UPDATE/SHARE row locking (PostgreSQL-specific)
355//------------------------------------------------------------------------------
356
357/// Helper function to create a FOR UPDATE clause
358pub(crate) fn for_update<'a>() -> SQL<'a, PostgresValue<'a>> {
359    SQL::from_iter([Token::FOR, Token::UPDATE])
360}
361
362/// Helper function to create a FOR SHARE clause
363pub(crate) fn for_share<'a>() -> SQL<'a, PostgresValue<'a>> {
364    SQL::from_iter([Token::FOR, Token::SHARE])
365}
366
367/// Helper function to create a FOR NO KEY UPDATE clause
368pub(crate) fn for_no_key_update<'a>() -> SQL<'a, PostgresValue<'a>> {
369    SQL::from_iter([Token::FOR, Token::NO, Token::KEY, Token::UPDATE])
370}
371
372/// Helper function to create a FOR KEY SHARE clause
373pub(crate) fn for_key_share<'a>() -> SQL<'a, PostgresValue<'a>> {
374    SQL::from_iter([Token::FOR, Token::KEY, Token::SHARE])
375}
376
377/// Helper function to create a FOR UPDATE OF table clause.
378/// Uses unqualified table name as required by `PostgreSQL`.
379pub(crate) fn for_update_of<'a>(table_name: &str) -> SQL<'a, PostgresValue<'a>> {
380    SQL::from_iter([Token::FOR, Token::UPDATE, Token::OF])
381        .append(SQL::ident(String::from(table_name)))
382}
383
384/// Helper function to create a FOR SHARE OF table clause.
385/// Uses unqualified table name as required by `PostgreSQL`.
386pub(crate) fn for_share_of<'a>(table_name: &str) -> SQL<'a, PostgresValue<'a>> {
387    SQL::from_iter([Token::FOR, Token::SHARE, Token::OF])
388        .append(SQL::ident(String::from(table_name)))
389}
390
391/// Helper function to add NOWAIT to a FOR clause
392pub(crate) fn nowait<'a>() -> SQL<'a, PostgresValue<'a>> {
393    SQL::from(Token::NOWAIT)
394}
395
396/// Helper function to add SKIP LOCKED to a FOR clause
397pub(crate) fn skip_locked<'a>() -> SQL<'a, PostgresValue<'a>> {
398    SQL::from_iter([Token::SKIP, Token::LOCKED])
399}