Skip to main content

drizzle_postgres/
helpers.rs

1//! SQL fragment builders used by the `PostgreSQL` query builders.
2//!
3//! Most items are internal. The public ones render join fragments for
4//! hand-built SQL: `join`, `left_join`, `natural_join` and the other
5//! `*_join` functions (`JOIN table ON condition`), and the `PostgreSQL`-only
6//! `*_join_using` functions (`JOIN table USING (columns)`). [`JoinArg`] is the
7//! argument type of the select builder's `.join(...)` methods.
8
9#[cfg(not(feature = "std"))]
10use crate::prelude::*;
11use crate::traits::PostgresTable;
12use crate::values::PostgresValue;
13use drizzle_core::{SQL, SQLTableInfo, ToSQL, Token, helpers, traits::SQLModel};
14
15// Re-export core helpers with PostgresValue type for convenience
16pub(crate) use helpers::{
17    delete, except, except_all, from, group_by_expr, having, intersect, intersect_all, limit,
18    offset, order_by, select, select_distinct, set, union, union_all, update, r#where,
19};
20
21// Re-export Join from core
22pub use drizzle_core::Join;
23
24/// A table or derived table that can be joined.
25#[doc(hidden)]
26#[diagnostic::on_unimplemented(
27    message = "`{Self}` cannot follow JOIN",
28    label = "join a table, a view, or an aliased subquery"
29)]
30pub trait JoinSource<'a>: join_source_private::Sealed {
31    type JoinedTable;
32
33    fn into_join_source_sql(self) -> SQL<'a, PostgresValue<'a>>;
34}
35
36mod join_source_private {
37    pub trait Sealed {}
38}
39
40impl<'a, Table> join_source_private::Sealed for Table where Table: PostgresTable<'a> {}
41
42impl<'a, Name, Projection, Query> join_source_private::Sealed
43    for drizzle_core::Derived<'a, PostgresValue<'a>, Name, Projection, Query>
44where
45    Name: drizzle_core::Tag,
46    Projection: drizzle_core::DerivedProjection<Name>,
47    Query: ToSQL<'a, PostgresValue<'a>>,
48{
49}
50
51impl<'a, Table> JoinSource<'a> for Table
52where
53    Table: PostgresTable<'a>,
54{
55    type JoinedTable = Table;
56
57    fn into_join_source_sql(self) -> SQL<'a, PostgresValue<'a>> {
58        self.into_sql()
59    }
60}
61
62impl<'a, Name, Projection, Query> JoinSource<'a>
63    for drizzle_core::Derived<'a, PostgresValue<'a>, Name, Projection, Query>
64where
65    Name: drizzle_core::Tag,
66    Projection: drizzle_core::DerivedProjection<Name>,
67    Query: ToSQL<'a, PostgresValue<'a>>,
68{
69    type JoinedTable = Self;
70
71    fn into_join_source_sql(self) -> SQL<'a, PostgresValue<'a>> {
72        self.into_sql()
73    }
74}
75
76/// A source or legacy tuple accepted by [`crate::builder::SelectBuilder::cross_join`].
77///
78/// A bare source renders `CROSS JOIN`. The legacy `(source, predicate)`
79/// form renders the equivalent portable `INNER JOIN ... ON ...`, because
80/// PostgreSQL does not allow an `ON` clause after `CROSS JOIN`.
81#[doc(hidden)]
82pub trait CrossJoinArg<'a, FromTable>: cross_join_arg_private::Sealed {
83    type JoinedTable;
84    /// Sources read by the legacy `ON` predicate (see [`drizzle_core::scope`]).
85    type OnSources;
86
87    fn into_cross_join_sql(self) -> SQL<'a, PostgresValue<'a>>;
88}
89
90mod cross_join_arg_private {
91    pub trait Sealed {}
92
93    impl<'a, Source> Sealed for Source where Source: super::JoinSource<'a> {}
94
95    impl<'a, Source, Condition> Sealed for (Source, Condition)
96    where
97        Source: super::JoinSource<'a>,
98        Condition: drizzle_core::ToSQL<'a, crate::values::PostgresValue<'a>>,
99    {
100    }
101}
102
103impl<'a, Source, FromTable> CrossJoinArg<'a, FromTable> for Source
104where
105    Source: JoinSource<'a>,
106{
107    type JoinedTable = Source::JoinedTable;
108    type OnSources = ();
109
110    fn into_cross_join_sql(self) -> SQL<'a, PostgresValue<'a>> {
111        Join::new()
112            .cross()
113            .into_sql()
114            .append(self.into_join_source_sql())
115    }
116}
117
118impl<'a, Source, Condition, FromTable> CrossJoinArg<'a, FromTable> for (Source, Condition)
119where
120    Source: JoinSource<'a>,
121    Condition: ToSQL<'a, PostgresValue<'a>> + drizzle_core::expr::ExprSources,
122{
123    type JoinedTable = Source::JoinedTable;
124    type OnSources = Condition::Sources;
125
126    fn into_cross_join_sql(self) -> SQL<'a, PostgresValue<'a>> {
127        let (source, condition) = self;
128        Join::new()
129            .inner()
130            .into_sql()
131            .append(source.into_join_source_sql())
132            .push(Token::ON)
133            .append(condition.into_sql())
134    }
135}
136
137drizzle_core::impl_join_arg_trait!(
138    table_trait: PostgresTable<'a>,
139    table_info_trait: SQLTableInfo,
140    condition_trait: ToSQL<'a, PostgresValue<'a>>,
141    join_source_trait: JoinSource<'a>,
142    value_type: PostgresValue<'a>,
143);
144
145// Generate all join helper functions using the shared macro
146drizzle_core::impl_join_helpers!(
147    table_trait: PostgresTable<'a>,
148    condition_trait: ToSQL<'a, PostgresValue<'a>>,
149    sql_type: SQL<'a, PostgresValue<'a>>,
150);
151
152/// Renders `SELECT DISTINCT ON (on) columns`.
153pub(crate) fn select_distinct_on<'a, On, Columns>(
154    on: On,
155    columns: Columns,
156) -> SQL<'a, PostgresValue<'a>>
157where
158    On: ToSQL<'a, PostgresValue<'a>>,
159    Columns: ToSQL<'a, PostgresValue<'a>>,
160{
161    SQL::from_iter([Token::SELECT, Token::DISTINCT, Token::ON, Token::LPAREN])
162        .append(on.into_sql())
163        .push(Token::RPAREN)
164        .append(columns.into_sql())
165}
166
167//------------------------------------------------------------------------------
168// USING clause internal helper (PostgreSQL-specific)
169//------------------------------------------------------------------------------
170
171fn join_using_internal<'a, Table>(
172    table: Table,
173    join: Join,
174    columns: impl ToSQL<'a, PostgresValue<'a>>,
175) -> SQL<'a, PostgresValue<'a>>
176where
177    Table: PostgresTable<'a>,
178{
179    join.into_sql()
180        .append(table.into_sql())
181        .push(Token::USING)
182        .push(Token::LPAREN)
183        .append(columns.into_sql())
184        .push(Token::RPAREN)
185}
186
187//------------------------------------------------------------------------------
188// USING clause versions of JOIN functions (PostgreSQL-specific)
189//------------------------------------------------------------------------------
190
191/// Renders `JOIN table USING (columns)`, which joins rows whose same-named
192/// columns are equal.
193///
194/// `columns` is rendered as given. `PostgreSQL` requires bare column names in
195/// `USING`, so pass identifiers such as `SQL::ident("id")`; a table column
196/// renders qualified (`"posts"."id"`), which the server rejects.
197///
198/// # Examples
199///
200/// ```rust
201/// # extern crate self as drizzle;
202/// # mod _drizzle {
203/// #     pub mod core { pub use drizzle_core::*; }
204/// #     pub mod error { pub use drizzle_core::error::*; }
205/// #     pub mod types { pub use drizzle_types::*; }
206/// #     pub mod migrations { pub use drizzle_migrations::*; }
207/// #     pub use drizzle_types::Dialect;
208/// #     pub use drizzle_types as ddl;
209/// #     pub mod postgres {
210/// #         pub mod values { pub use drizzle_postgres::values::*; }
211/// #         pub mod traits { pub use drizzle_postgres::traits::*; }
212/// #         pub mod common { pub use drizzle_postgres::common::*; }
213/// #         pub mod attrs { pub use drizzle_postgres::attrs::*; }
214/// #         pub mod builder { pub use drizzle_postgres::builder::*; }
215/// #         pub mod helpers { pub use drizzle_postgres::helpers::*; }
216/// #         pub mod expr { pub use drizzle_postgres::expr::*; }
217/// #         pub mod types { pub use drizzle_postgres::types::*; }
218/// #         #[cfg(feature = "aws-data-api")]
219/// #         pub mod aws_data_api { pub use drizzle_postgres::aws_data_api::*; }
220/// #         pub struct Row;
221/// #         impl Row {
222/// #             pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
223/// #             pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
224/// #             pub fn len(&self) -> usize { unimplemented!() }
225/// #         }
226/// #         pub mod prelude {
227/// #             pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
228/// #             pub use drizzle_postgres::attrs::*;
229/// #             pub use drizzle_postgres::common::PostgresSchemaType;
230/// #             pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
231/// #             pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
232/// #             pub use drizzle_core::*;
233/// #         }
234/// #     }
235/// # }
236/// # pub use _drizzle::*;
237/// # pub use const_format;
238/// # fn main() {
239/// # use drizzle::postgres::prelude::*;
240/// # use drizzle::postgres::builder::QueryBuilder;
241/// # #[PostgresTable(name = "users")]
242/// # struct User {
243/// #     #[column(serial, primary)]
244/// #     id: i32,
245/// #     name: String,
246/// #     email: Option<String>,
247/// # }
248/// # #[PostgresTable(name = "posts")]
249/// # struct Post {
250/// #     #[column(serial, primary)]
251/// #     id: i32,
252/// #     #[column(references = User::id)]
253/// #     author_id: i32,
254/// #     title: String,
255/// # }
256/// # #[derive(PostgresSchema)]
257/// # struct Schema {
258/// #     user: User,
259/// #     post: Post,
260/// # }
261/// # let db = QueryBuilder::new::<Schema>();
262/// # let Schema { user, post } = Schema::new();
263/// use drizzle::core::SQL;
264/// use drizzle::postgres::values::PostgresValue;
265///
266/// let join = drizzle::postgres::helpers::join_using(post, SQL::<PostgresValue>::ident("id"));
267/// assert_eq!(join.sql(), r#"JOIN "posts" USING ("id")"#);
268/// # }
269/// ```
270pub fn join_using<'a, Table>(
271    table: Table,
272    columns: impl ToSQL<'a, PostgresValue<'a>>,
273) -> SQL<'a, PostgresValue<'a>>
274where
275    Table: PostgresTable<'a>,
276{
277    join_using_internal(table, Join::new(), columns)
278}
279
280/// Renders `INNER JOIN table USING (columns)`. See [`join_using`] for how to pass `columns`.
281pub fn inner_join_using<'a, Table>(
282    table: Table,
283    columns: impl ToSQL<'a, PostgresValue<'a>>,
284) -> SQL<'a, PostgresValue<'a>>
285where
286    Table: PostgresTable<'a>,
287{
288    join_using_internal(table, Join::new().inner(), columns)
289}
290
291/// Renders `LEFT JOIN table USING (columns)`. See [`join_using`] for how to pass `columns`.
292pub fn left_join_using<'a, Table>(
293    table: Table,
294    columns: impl ToSQL<'a, PostgresValue<'a>>,
295) -> SQL<'a, PostgresValue<'a>>
296where
297    Table: PostgresTable<'a>,
298{
299    join_using_internal(table, Join::new().left(), columns)
300}
301
302/// Renders `LEFT OUTER JOIN table USING (columns)`. See [`join_using`] for how to pass `columns`.
303pub fn left_outer_join_using<'a, Table>(
304    table: Table,
305    columns: impl ToSQL<'a, PostgresValue<'a>>,
306) -> SQL<'a, PostgresValue<'a>>
307where
308    Table: PostgresTable<'a>,
309{
310    join_using_internal(table, Join::new().left().outer(), columns)
311}
312
313/// Renders `RIGHT JOIN table USING (columns)`. See [`join_using`] for how to pass `columns`.
314pub fn right_join_using<'a, Table>(
315    table: Table,
316    columns: impl ToSQL<'a, PostgresValue<'a>>,
317) -> SQL<'a, PostgresValue<'a>>
318where
319    Table: PostgresTable<'a>,
320{
321    join_using_internal(table, Join::new().right(), columns)
322}
323
324/// Renders `RIGHT OUTER JOIN table USING (columns)`. See [`join_using`] for how to pass `columns`.
325pub fn right_outer_join_using<'a, Table>(
326    table: Table,
327    columns: impl ToSQL<'a, PostgresValue<'a>>,
328) -> SQL<'a, PostgresValue<'a>>
329where
330    Table: PostgresTable<'a>,
331{
332    join_using_internal(table, Join::new().right().outer(), columns)
333}
334
335/// Renders `FULL JOIN table USING (columns)`. See [`join_using`] for how to pass `columns`.
336pub fn full_join_using<'a, Table>(
337    table: Table,
338    columns: impl ToSQL<'a, PostgresValue<'a>>,
339) -> SQL<'a, PostgresValue<'a>>
340where
341    Table: PostgresTable<'a>,
342{
343    join_using_internal(table, Join::new().full(), columns)
344}
345
346/// Renders `FULL OUTER JOIN table USING (columns)`. See [`join_using`] for how to pass `columns`.
347pub fn full_outer_join_using<'a, Table>(
348    table: Table,
349    columns: impl ToSQL<'a, PostgresValue<'a>>,
350) -> SQL<'a, PostgresValue<'a>>
351where
352    Table: PostgresTable<'a>,
353{
354    join_using_internal(table, Join::new().full().outer(), columns)
355}
356
357// Note: NATURAL JOINs don't use USING clause as they automatically match column names
358// CROSS JOIN also doesn't use USING clause as it produces Cartesian product
359
360/// Renders `INSERT INTO table`.
361pub(crate) fn insert<'a, Table>(table: &Table) -> SQL<'a, PostgresValue<'a>>
362where
363    Table: PostgresTable<'a>,
364{
365    SQL::from_iter([Token::INSERT, Token::INTO]).append(table)
366}
367
368/// Creates the rows of an INSERT statement.
369///
370/// Rows usually set the same columns. A `None` passed to a `with_*` setter
371/// leaves that column to its default without changing the row's type, so
372/// rows can differ; then every row lists the union of the columns, with
373/// `DEFAULT` where it sets none.
374pub(crate) fn values<'a, Table, T>(
375    rows: impl IntoIterator<Item = Table::Insert<T>>,
376) -> SQL<'a, PostgresValue<'a>>
377where
378    Table: PostgresTable<'a>,
379{
380    let rows: Vec<_> = rows.into_iter().collect();
381
382    if rows.is_empty() {
383        return match <Table as drizzle_core::SQLSchema<
384            'a,
385            crate::common::PostgresSchemaType,
386            PostgresValue<'a>,
387        >>::TYPE
388        {
389            crate::common::PostgresSchemaType::Table(table) => {
390                // `OVERRIDING SYSTEM VALUE` lets the list name an identity
391                // column declared `GENERATED ALWAYS`.
392                drizzle_core::helpers::insert_no_rows(
393                    table,
394                    "OVERRIDING SYSTEM VALUE SELECT NULL WHERE 1 = 0",
395                )
396            }
397            _ => SQL::from(Token::VALUES),
398        };
399    }
400
401    let columns_info = rows[0].columns();
402    let columns_slice = columns_info.as_ref();
403    if rows[1..]
404        .iter()
405        .any(|row| row.columns().as_ref() != columns_slice)
406    {
407        let rows_sql = drizzle_core::helpers::insert_values_with_defaults(
408            rows.iter()
409                .map(|row| (row.columns(), row.values()))
410                .collect(),
411        );
412        if let Some(rows_sql) = rows_sql {
413            return rows_sql;
414        }
415    }
416
417    if columns_slice.is_empty() {
418        // `DEFAULT VALUES` inserts one row. A query without columns inserts
419        // one all-default row per result row.
420        // Raw text, not SELECT/FROM tokens: the renderer expands a bare
421        // `SELECT` token followed by `FROM` into a projection.
422        return if rows.len() == 1 {
423            SQL::from_iter([Token::DEFAULT, Token::VALUES])
424        } else {
425            SQL::raw("SELECT FROM").append(SQL::func(
426                "generate_series",
427                SQL::number(1)
428                    .push(Token::COMMA)
429                    .append(SQL::number(rows.len())),
430            ))
431        };
432    }
433
434    let columns_sql = SQL::columns(columns_slice);
435    let mut values_sql = SQL::with_capacity_chunks(rows.len().saturating_mul(4));
436    for (idx, row) in rows.iter().enumerate() {
437        if idx > 0 {
438            values_sql.push_mut(Token::COMMA);
439        }
440        values_sql.push_mut(Token::LPAREN);
441        values_sql.append_mut(row.values());
442        values_sql.push_mut(Token::RPAREN);
443    }
444
445    columns_sql.parens().push(Token::VALUES).append(values_sql)
446}
447
448/// Renders `RETURNING columns`, or `RETURNING *` for an empty list.
449pub(crate) fn returning<'a, 'b, I>(columns: I) -> SQL<'a, PostgresValue<'a>>
450where
451    I: ToSQL<'a, PostgresValue<'a>>,
452{
453    let columns = columns.into_sql();
454    let columns = if columns.chunks.is_empty() {
455        SQL::from(Token::STAR)
456    } else {
457        columns
458    };
459    SQL::from(Token::RETURNING).append(columns)
460}
461
462//------------------------------------------------------------------------------
463// FOR UPDATE/SHARE row locking (PostgreSQL-specific)
464//------------------------------------------------------------------------------
465
466/// Renders `FOR UPDATE`.
467pub(crate) fn for_update<'a>() -> SQL<'a, PostgresValue<'a>> {
468    SQL::from_iter([Token::FOR, Token::UPDATE])
469}
470
471/// Renders `FOR SHARE`.
472pub(crate) fn for_share<'a>() -> SQL<'a, PostgresValue<'a>> {
473    SQL::from_iter([Token::FOR, Token::SHARE])
474}
475
476/// Renders `FOR NO KEY UPDATE`.
477pub(crate) fn for_no_key_update<'a>() -> SQL<'a, PostgresValue<'a>> {
478    SQL::from_iter([Token::FOR, Token::NO, Token::KEY, Token::UPDATE])
479}
480
481/// Renders `FOR KEY SHARE`.
482pub(crate) fn for_key_share<'a>() -> SQL<'a, PostgresValue<'a>> {
483    SQL::from_iter([Token::FOR, Token::KEY, Token::SHARE])
484}
485
486/// Renders `FOR UPDATE OF table`, with the bare table name `PostgreSQL` requires.
487pub(crate) fn for_update_of<'a>(table_name: &str) -> SQL<'a, PostgresValue<'a>> {
488    SQL::from_iter([Token::FOR, Token::UPDATE, Token::OF])
489        .append(SQL::ident(String::from(table_name)))
490}
491
492/// Renders `FOR SHARE OF table`, with the bare table name `PostgreSQL` requires.
493pub(crate) fn for_share_of<'a>(table_name: &str) -> SQL<'a, PostgresValue<'a>> {
494    SQL::from_iter([Token::FOR, Token::SHARE, Token::OF])
495        .append(SQL::ident(String::from(table_name)))
496}
497
498/// Renders `NOWAIT`.
499pub(crate) fn nowait<'a>() -> SQL<'a, PostgresValue<'a>> {
500    SQL::from(Token::NOWAIT)
501}
502
503/// Renders `SKIP LOCKED`.
504pub(crate) fn skip_locked<'a>() -> SQL<'a, PostgresValue<'a>> {
505    SQL::from_iter([Token::SKIP, Token::LOCKED])
506}