Skip to main content

drizzle_postgres/builder/
select.rs

1use crate::common::PostgresSchemaType;
2use crate::helpers;
3use crate::traits::PostgresTable;
4use crate::values::PostgresValue;
5use core::marker::PhantomData;
6use drizzle_core::ToSQL;
7use drizzle_core::traits::SQLTable;
8use paste::paste;
9
10// Import the ExecutableState trait
11use super::ExecutableState;
12
13//------------------------------------------------------------------------------
14// Type State Markers
15//------------------------------------------------------------------------------
16
17pub use drizzle_core::builder::{
18    AsCteState, SelectFromSet, SelectGroupSet, SelectInitial, SelectJoinSet, SelectLimitSet,
19    SelectOffsetSet, SelectOrderSet, SelectSetOpSet, SelectWhereSet,
20};
21
22/// States that accept a plain `ORDER BY`.
23///
24/// `SelectSetOpSet` is excluded on purpose: a compound query orders by its
25/// output columns, which the dedicated `order_by` on that state renders.
26pub trait SelectOrderAllowed {}
27impl SelectOrderAllowed for SelectFromSet {}
28impl SelectOrderAllowed for SelectJoinSet {}
29impl SelectOrderAllowed for SelectWhereSet {}
30impl SelectOrderAllowed for SelectGroupSet {}
31
32#[doc(hidden)]
33pub trait SelectWhereAllowed: drizzle_core::WhereAllowed {}
34
35impl SelectWhereAllowed for SelectFromSet {}
36impl SelectWhereAllowed for SelectJoinSet {}
37
38/// Marker for the state after FOR UPDATE/SHARE clause
39#[derive(Debug, Clone, Copy, Default)]
40pub struct SelectForSet;
41
42//------------------------------------------------------------------------------
43// Join macros (generates all join variants)
44//------------------------------------------------------------------------------
45
46#[doc(hidden)]
47macro_rules! join_impl {
48    () => {
49        join_impl!(@natural natural, Join::new().natural(), drizzle_core::AfterJoin);
50        join_impl!(@natural natural_left, Join::new().natural().left(), drizzle_core::AfterLeftJoin);
51        join_impl!(left, Join::new().left(), drizzle_core::AfterLeftJoin);
52        join_impl!(left_outer, Join::new().left().outer(), drizzle_core::AfterLeftJoin);
53        join_impl!(@natural natural_left_outer, Join::new().natural().left().outer(), drizzle_core::AfterLeftJoin);
54        join_impl!(@natural natural_right, Join::new().natural().right(), drizzle_core::AfterRightJoin);
55        join_impl!(right, Join::new().right(), drizzle_core::AfterRightJoin);
56        join_impl!(right_outer, Join::new().right().outer(), drizzle_core::AfterRightJoin);
57        join_impl!(@natural natural_right_outer, Join::new().natural().right().outer(), drizzle_core::AfterRightJoin);
58        join_impl!(@natural natural_full, Join::new().natural().full(), drizzle_core::AfterFullJoin);
59        join_impl!(full, Join::new().full(), drizzle_core::AfterFullJoin);
60        join_impl!(full_outer, Join::new().full().outer(), drizzle_core::AfterFullJoin);
61        join_impl!(@natural natural_full_outer, Join::new().natural().full().outer(), drizzle_core::AfterFullJoin);
62        join_impl!(inner, Join::new().inner(), drizzle_core::AfterJoin);
63        // USING variants only for non-natural, non-cross joins
64        join_using_impl!(left, drizzle_core::AfterLeftJoin);
65        join_using_impl!(left_outer, drizzle_core::AfterLeftJoin);
66        join_using_impl!(right, drizzle_core::AfterRightJoin);
67        join_using_impl!(right_outer, drizzle_core::AfterRightJoin);
68        join_using_impl!(full, drizzle_core::AfterFullJoin);
69        join_using_impl!(full_outer, drizzle_core::AfterFullJoin);
70        join_using_impl!(inner, drizzle_core::AfterJoin);
71        join_using_impl!(); // Plain JOIN
72    };
73    (@natural $type:ident, $join_expr:expr, $join_trait:path) => {
74        paste! {
75            /// Adds a NATURAL join. The database matches the columns both
76            /// sides share by name, so it takes a source and no ON condition.
77            #[allow(clippy::type_complexity)]
78            pub fn [<$type _join>]<J: crate::helpers::JoinSource<'a>>(
79                self,
80                source: J,
81            ) -> SelectBuilder<'a, S, SelectJoinSet, J::JoinedTable, <M as drizzle_core::ScopePush<J::JoinedTable>>::Out, <M as $join_trait<R, J::JoinedTable>>::NewRow, G>
82            where
83                M: $join_trait<R, J::JoinedTable> + drizzle_core::ScopePush<J::JoinedTable>,
84            {
85                use drizzle_core::{Join, ToSQL};
86                SelectBuilder {
87                    sql: self
88                        .sql
89                        .append($join_expr.to_sql())
90                        .append(drizzle_core::SQL::raw(" "))
91                        .append(source.into_join_source_sql()),
92                    schema: PhantomData,
93                    state: PhantomData,
94                    table: PhantomData,
95                    marker: PhantomData,
96                    row: PhantomData,
97                    grouped: PhantomData,
98                }
99            }
100        }
101    };
102    ($type:ident, $join_expr:expr, $join_trait:path) => {
103        paste! {
104            /// JOIN with ON clause
105            pub fn [<$type _join>]<J: crate::helpers::JoinArg<'a, T>>(
106                self,
107                arg: J,
108            ) -> SelectBuilder<'a, S, SelectJoinSet, J::JoinedTable, <M as drizzle_core::ScopePush<J::JoinedTable>>::Out, <M as $join_trait<R, J::JoinedTable>>::NewRow, G>
109            where
110                M: $join_trait<R, J::JoinedTable> + drizzle_core::ScopePush<J::JoinedTable>,
111            {
112                use drizzle_core::Join;
113                SelectBuilder {
114                    sql: self.sql.append(arg.into_join_sql($join_expr)),
115                    schema: PhantomData,
116                    state: PhantomData,
117                    table: PhantomData,
118                    marker: PhantomData,
119                    row: PhantomData,
120                    grouped: PhantomData,
121                }
122            }
123        }
124    };
125}
126
127macro_rules! join_using_impl {
128    () => {
129        /// JOIN with USING clause (PostgreSQL-specific)
130        pub fn join_using<U: PostgresTable<'a>>(
131            self,
132            table: U,
133            columns: impl ToSQL<'a, PostgresValue<'a>>,
134        ) -> SelectBuilder<
135            'a,
136            S,
137            SelectJoinSet,
138            U,
139            <M as drizzle_core::ScopePush<U>>::Out,
140            <M as drizzle_core::AfterJoin<R, U>>::NewRow,
141            G,
142        >
143        where
144            M: drizzle_core::AfterJoin<R, U> + drizzle_core::ScopePush<U>,
145        {
146            SelectBuilder {
147                sql: self.sql.append(helpers::join_using(table, columns)),
148                schema: PhantomData,
149                state: PhantomData,
150                table: PhantomData,
151                marker: PhantomData,
152                row: PhantomData,
153                grouped: PhantomData,
154            }
155        }
156    };
157    ($type:ident, $join_trait:path) => {
158        paste! {
159            /// JOIN with USING clause (PostgreSQL-specific)
160            pub fn [<$type _join_using>]<U: PostgresTable<'a>>(
161                self,
162                table: U,
163                columns: impl ToSQL<'a, PostgresValue<'a>>,
164            ) -> SelectBuilder<
165                'a,
166                S,
167                SelectJoinSet,
168                U,
169                <M as drizzle_core::ScopePush<U>>::Out,
170                <M as $join_trait<R, U>>::NewRow,
171                G,
172            >
173            where
174                M: $join_trait<R, U> + drizzle_core::ScopePush<U>,
175            {
176                SelectBuilder {
177                    sql: self.sql.append(helpers::[<$type _join_using>](table, columns)),
178                    schema: PhantomData,
179                    state: PhantomData,
180                    table: PhantomData,
181                    marker: PhantomData,
182                    row: PhantomData,
183                    grouped: PhantomData,
184                }
185            }
186        }
187    };
188}
189
190//------------------------------------------------------------------------------
191// Capability trait impls for each state
192//------------------------------------------------------------------------------
193
194impl ExecutableState for SelectForSet {}
195
196//------------------------------------------------------------------------------
197// SelectBuilder Definition
198//------------------------------------------------------------------------------
199
200/// Builds a SELECT query specifically for `PostgreSQL`
201pub type SelectBuilder<'a, Schema, State, Table = (), Marker = (), Row = (), Grouped = ()> =
202    super::QueryBuilder<'a, Schema, State, Table, Marker, Row, Grouped>;
203
204//------------------------------------------------------------------------------
205// Initial State: .from()
206//------------------------------------------------------------------------------
207
208impl<'a, S, M> SelectBuilder<'a, S, SelectInitial, (), M> {
209    /// Specifies the table to select FROM and transitions state.
210    #[inline]
211    #[allow(clippy::type_complexity)]
212    pub fn from<T>(
213        self,
214        query: T,
215    ) -> SelectBuilder<
216        'a,
217        S,
218        SelectFromSet,
219        T,
220        drizzle_core::Scoped<M, drizzle_core::Cons<T, drizzle_core::Nil>>,
221        <M as drizzle_core::ResolveRow<T>>::Row,
222    >
223    where
224        T: ToSQL<'a, PostgresValue<'a>>,
225        M: drizzle_core::ResolveRow<T>,
226    {
227        SelectBuilder {
228            sql: self.sql.append(helpers::from(query)),
229            schema: PhantomData,
230            state: PhantomData,
231            table: PhantomData,
232            marker: PhantomData,
233            row: PhantomData,
234            grouped: PhantomData,
235        }
236    }
237}
238
239//------------------------------------------------------------------------------
240// Capability-gated methods (generic over State)
241//------------------------------------------------------------------------------
242
243// JOIN (available from SelectFromSet and SelectJoinSet)
244impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
245where
246    State: drizzle_core::JoinAllowed,
247{
248    /// Adds an INNER JOIN clause to the query.
249    #[inline]
250    #[allow(clippy::type_complexity)]
251    pub fn join<J: crate::helpers::JoinArg<'a, T>>(
252        self,
253        arg: J,
254    ) -> SelectBuilder<
255        'a,
256        S,
257        SelectJoinSet,
258        J::JoinedTable,
259        <M as drizzle_core::ScopePush<J::JoinedTable>>::Out,
260        <M as drizzle_core::AfterJoin<R, J::JoinedTable>>::NewRow,
261        G,
262    >
263    where
264        M: drizzle_core::AfterJoin<R, J::JoinedTable> + drizzle_core::ScopePush<J::JoinedTable>,
265    {
266        use drizzle_core::Join;
267        SelectBuilder {
268            sql: self.sql.append(arg.into_join_sql(Join::new())),
269            schema: PhantomData,
270            state: PhantomData,
271            table: PhantomData,
272            marker: PhantomData,
273            row: PhantomData,
274            grouped: PhantomData,
275        }
276    }
277
278    join_impl!();
279
280    /// Adds a cross join.
281    ///
282    /// A bare source renders `CROSS JOIN`. For backwards compatibility,
283    /// `(source, predicate)` renders the equivalent `INNER JOIN ... ON ...`.
284    #[inline]
285    #[allow(clippy::type_complexity)]
286    pub fn cross_join<Arg: crate::helpers::CrossJoinArg<'a, T>>(
287        self,
288        arg: Arg,
289    ) -> SelectBuilder<
290        'a,
291        S,
292        SelectJoinSet,
293        Arg::JoinedTable,
294        <M as drizzle_core::ScopePush<Arg::JoinedTable>>::Out,
295        <M as drizzle_core::AfterJoin<R, Arg::JoinedTable>>::NewRow,
296        G,
297    >
298    where
299        M: drizzle_core::AfterJoin<R, Arg::JoinedTable> + drizzle_core::ScopePush<Arg::JoinedTable>,
300    {
301        SelectBuilder {
302            sql: self.sql.append(arg.into_cross_join_sql()),
303            schema: PhantomData,
304            state: PhantomData,
305            table: PhantomData,
306            marker: PhantomData,
307            row: PhantomData,
308            grouped: PhantomData,
309        }
310    }
311
312    /// Adds an INNER JOIN LATERAL clause.
313    #[inline]
314    #[allow(clippy::type_complexity)]
315    pub fn inner_join_lateral<J>(
316        self,
317        arg: J,
318    ) -> SelectBuilder<
319        'a,
320        S,
321        SelectJoinSet,
322        J::JoinedTable,
323        <M as drizzle_core::ScopePush<J::JoinedTable>>::Out,
324        <M as drizzle_core::AfterJoin<R, J::JoinedTable>>::NewRow,
325        G,
326    >
327    where
328        J: drizzle_core::LateralArg<'a, PostgresValue<'a>>,
329        M: drizzle_core::AfterJoin<R, J::JoinedTable> + drizzle_core::ScopePush<J::JoinedTable>,
330    {
331        use drizzle_core::Join;
332        SelectBuilder {
333            sql: self.sql.append(arg.into_lateral_sql(Join::new().inner())),
334            schema: PhantomData,
335            state: PhantomData,
336            table: PhantomData,
337            marker: PhantomData,
338            row: PhantomData,
339            grouped: PhantomData,
340        }
341    }
342
343    /// Adds a LEFT JOIN LATERAL clause.
344    #[inline]
345    #[allow(clippy::type_complexity)]
346    pub fn left_join_lateral<J, SelectionProof>(
347        self,
348        arg: J,
349    ) -> SelectBuilder<
350        'a,
351        S,
352        SelectJoinSet,
353        J::JoinedTable,
354        <M as drizzle_core::ScopePush<J::JoinedTable>>::Out,
355        <M as drizzle_core::AfterLeftJoin<R, J::JoinedTable>>::NewRow,
356        G,
357    >
358    where
359        J: drizzle_core::LateralArg<'a, PostgresValue<'a>>,
360        M: drizzle_core::AfterLeftJoin<R, J::JoinedTable>
361            + drizzle_core::ScopePush<J::JoinedTable>
362            + drizzle_core::LeftLateralSelection<SelectionProof>,
363    {
364        use drizzle_core::Join;
365        SelectBuilder {
366            sql: self.sql.append(arg.into_lateral_sql(Join::new().left())),
367            schema: PhantomData,
368            state: PhantomData,
369            table: PhantomData,
370            marker: PhantomData,
371            row: PhantomData,
372            grouped: PhantomData,
373        }
374    }
375
376    /// Adds a CROSS JOIN LATERAL clause without an ON condition.
377    #[inline]
378    #[allow(clippy::type_complexity)]
379    pub fn cross_join_lateral<Source>(
380        self,
381        source: Source,
382    ) -> SelectBuilder<
383        'a,
384        S,
385        SelectJoinSet,
386        Source::JoinedTable,
387        <M as drizzle_core::ScopePush<Source::JoinedTable>>::Out,
388        <M as drizzle_core::AfterJoin<R, Source::JoinedTable>>::NewRow,
389        G,
390    >
391    where
392        Source: drizzle_core::LateralSource<'a, PostgresValue<'a>>,
393        M: drizzle_core::AfterJoin<R, Source::JoinedTable>
394            + drizzle_core::ScopePush<Source::JoinedTable>,
395    {
396        SelectBuilder {
397            sql: self.sql.append(source.into_cross_lateral_sql()),
398            schema: PhantomData,
399            state: PhantomData,
400            table: PhantomData,
401            marker: PhantomData,
402            row: PhantomData,
403            grouped: PhantomData,
404        }
405    }
406}
407
408// WHERE (available from SelectFromSet and SelectJoinSet)
409impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
410where
411    State: SelectWhereAllowed,
412{
413    /// Adds a WHERE clause to filter query results.
414    #[inline]
415    pub fn r#where<E>(self, condition: E) -> SelectBuilder<'a, S, SelectWhereSet, T, M, R, G>
416    where
417        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
418        E::SQLType: drizzle_core::types::BooleanLike,
419    {
420        SelectBuilder {
421            sql: self.sql.append(helpers::r#where(condition)),
422            schema: PhantomData,
423            state: PhantomData,
424            table: PhantomData,
425            marker: PhantomData,
426            row: PhantomData,
427            grouped: PhantomData,
428        }
429    }
430}
431
432// GROUP BY (available from SelectFromSet, SelectJoinSet, SelectWhereSet)
433impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
434where
435    State: drizzle_core::GroupByAllowed,
436{
437    /// Adds a GROUP BY clause to the query.
438    ///
439    /// Non-aggregate columns in SELECT must appear in the GROUP BY list, with
440    /// one exception: grouping by a table's single-column primary key
441    /// functionally determines the whole row (SQL:1999, which `PostgreSQL`
442    /// implements natively), so any scalar column of that table may be
443    /// selected without being listed.
444    pub fn group_by<Gr>(
445        self,
446        columns: Gr,
447    ) -> SelectBuilder<'a, S, SelectGroupSet, T, M, R, Gr::Columns>
448    where
449        Gr: drizzle_core::IntoGroupBy<'a, PostgresValue<'a>>,
450    {
451        SelectBuilder {
452            sql: self.sql.append(helpers::group_by_expr(columns)),
453            schema: PhantomData,
454            state: PhantomData,
455            table: PhantomData,
456            marker: PhantomData,
457            row: PhantomData,
458            grouped: PhantomData,
459        }
460    }
461}
462
463// HAVING (available only from SelectGroupSet)
464impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
465where
466    State: drizzle_core::HavingAllowed,
467{
468    /// Adds a HAVING clause after GROUP BY.
469    pub fn having<E>(self, condition: E) -> SelectBuilder<'a, S, SelectGroupSet, T, M, R, G>
470    where
471        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
472        E::SQLType: drizzle_core::types::BooleanLike,
473    {
474        SelectBuilder {
475            sql: self.sql.append(helpers::having(condition)),
476            schema: PhantomData,
477            state: PhantomData,
478            table: PhantomData,
479            marker: PhantomData,
480            row: PhantomData,
481            grouped: PhantomData,
482        }
483    }
484}
485
486// ORDER BY (available from many states)
487impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
488where
489    State: SelectOrderAllowed,
490{
491    /// Sorts the query results.
492    #[inline]
493    pub fn order_by<TOrderBy>(
494        self,
495        expressions: TOrderBy,
496    ) -> SelectBuilder<'a, S, SelectOrderSet, T, M, R, G>
497    where
498        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
499    {
500        SelectBuilder {
501            sql: self.sql.append(helpers::order_by(expressions)),
502            schema: PhantomData,
503            state: PhantomData,
504            table: PhantomData,
505            marker: PhantomData,
506            row: PhantomData,
507            grouped: PhantomData,
508        }
509    }
510}
511
512// ORDER BY on a compound query: the combined rows carry no table scope, so the
513// ordering terms are rendered as output column names.
514impl<'a, S, T, M, R, G> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
515    /// Sorts a compound (`UNION` / `INTERSECT` / `EXCEPT`) result by its
516    /// output columns. Column references are rendered unqualified, which is
517    /// the only spelling PostgreSQL and turso accept here.
518    #[inline]
519    pub fn order_by<TOrderBy>(
520        self,
521        expressions: TOrderBy,
522    ) -> SelectBuilder<'a, S, SelectOrderSet, T, M, R, G>
523    where
524        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
525    {
526        SelectBuilder {
527            sql: self
528                .sql
529                .append(drizzle_core::helpers::set_order_by(expressions)),
530            schema: PhantomData,
531            state: PhantomData,
532            table: PhantomData,
533            marker: PhantomData,
534            row: PhantomData,
535            grouped: PhantomData,
536        }
537    }
538}
539
540// LIMIT (available from many states)
541impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
542where
543    State: drizzle_core::LimitAllowed,
544{
545    /// Limits the number of rows returned.
546    ///
547    /// # Panics
548    ///
549    /// Panics when a signed numeric argument is negative or a numeric value
550    /// does not fit in `usize`.
551    #[inline]
552    #[must_use]
553    #[track_caller]
554    pub fn limit<P>(self, limit: P) -> SelectBuilder<'a, S, SelectLimitSet, T, M, R, G>
555    where
556        P: drizzle_core::PaginationArg<'a, PostgresValue<'a>>,
557    {
558        SelectBuilder {
559            sql: self.sql.append(helpers::limit(limit)),
560            schema: PhantomData,
561            state: PhantomData,
562            table: PhantomData,
563            marker: PhantomData,
564            row: PhantomData,
565            grouped: PhantomData,
566        }
567    }
568}
569
570// OFFSET (available from SelectFromSet, SelectLimitSet, SelectSetOpSet)
571impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
572where
573    State: drizzle_core::OffsetAllowed,
574{
575    /// Sets the offset for the query results.
576    ///
577    /// # Panics
578    ///
579    /// Panics when a signed numeric argument is negative or a numeric value
580    /// does not fit in `usize`.
581    #[inline]
582    #[must_use]
583    #[track_caller]
584    pub fn offset<P>(self, offset: P) -> SelectBuilder<'a, S, SelectOffsetSet, T, M, R, G>
585    where
586        P: drizzle_core::PaginationArg<'a, PostgresValue<'a>>,
587    {
588        SelectBuilder {
589            sql: self.sql.append(helpers::offset(offset)),
590            schema: PhantomData,
591            state: PhantomData,
592            table: PhantomData,
593            marker: PhantomData,
594            row: PhantomData,
595            grouped: PhantomData,
596        }
597    }
598}
599
600//------------------------------------------------------------------------------
601// CTE support
602//------------------------------------------------------------------------------
603
604impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
605where
606    State: ExecutableState,
607{
608    /// Names this completed projection for use as a derived table.
609    ///
610    /// # Panics
611    ///
612    /// Panics when the projection contains duplicate output names. Name a
613    /// computed expression with [`drizzle_core::expr::NamedExt::named`] to
614    /// make each output unique.
615    #[inline]
616    #[must_use]
617    pub fn alias<Tag, ScopeProof, AggProof>(
618        self,
619        _tag: Tag,
620    ) -> drizzle_core::Derived<
621        'a,
622        PostgresValue<'a>,
623        Tag,
624        <M as drizzle_core::DerivedSelection<
625            'a,
626            PostgresValue<'a>,
627            PostgresSchemaType,
628            T,
629        >>::Projection,
630        Self,
631    >
632    where
633        Tag: drizzle_core::Tag,
634        M: drizzle_core::DerivedSelection<'a, PostgresValue<'a>, PostgresSchemaType, T>
635            + drizzle_core::row::MarkerScopeValidFor<ScopeProof>
636            + drizzle_core::row::MarkerAggValidFor<G, AggProof>,
637        <M as drizzle_core::DerivedSelection<
638            'a,
639            PostgresValue<'a>,
640            PostgresSchemaType,
641            T,
642        >>::Projection: drizzle_core::DerivedProjection<Tag>,
643{
644        // SAFETY: The executable-state, scope, aggregate, and projection
645        // bounds above prove that this query matches the derived projection.
646        unsafe { drizzle_core::Derived::new_unchecked(self) }
647    }
648}
649
650impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
651where
652    State: AsCteState,
653    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
654{
655    /// Converts this SELECT query into a typed CTE using alias tag name.
656    #[inline]
657    #[must_use]
658    pub fn into_cte<Tag: drizzle_core::Tag + 'static>(
659        self,
660    ) -> super::CTEView<
661        'a,
662        <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased<Tag>,
663        Self,
664    > {
665        let name = Tag::NAME;
666        super::CTEView::new(
667            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias::<Tag>(),
668            name,
669            self,
670        )
671    }
672}
673
674//------------------------------------------------------------------------------
675// Set operation support (UNION / INTERSECT / EXCEPT)
676//------------------------------------------------------------------------------
677
678impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
679where
680    State: ExecutableState,
681{
682    /// Combines this query with another using UNION.
683    pub fn union(
684        self,
685        other: impl IntoSelect<'a, S, M, R>,
686    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
687        SelectBuilder {
688            sql: helpers::union(self.sql, other.into_select()),
689            schema: PhantomData,
690            state: PhantomData,
691            table: PhantomData,
692            marker: PhantomData,
693            row: PhantomData,
694            grouped: PhantomData,
695        }
696    }
697
698    /// Combines this query with another using UNION ALL.
699    pub fn union_all(
700        self,
701        other: impl IntoSelect<'a, S, M, R>,
702    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
703        SelectBuilder {
704            sql: helpers::union_all(self.sql, other.into_select()),
705            schema: PhantomData,
706            state: PhantomData,
707            table: PhantomData,
708            marker: PhantomData,
709            row: PhantomData,
710            grouped: PhantomData,
711        }
712    }
713
714    /// Combines this query with another using INTERSECT.
715    pub fn intersect(
716        self,
717        other: impl IntoSelect<'a, S, M, R>,
718    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
719        SelectBuilder {
720            sql: helpers::intersect(self.sql, other.into_select()),
721            schema: PhantomData,
722            state: PhantomData,
723            table: PhantomData,
724            marker: PhantomData,
725            row: PhantomData,
726            grouped: PhantomData,
727        }
728    }
729
730    /// Combines this query with another using INTERSECT ALL.
731    pub fn intersect_all(
732        self,
733        other: impl IntoSelect<'a, S, M, R>,
734    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
735        SelectBuilder {
736            sql: helpers::intersect_all(self.sql, other.into_select()),
737            schema: PhantomData,
738            state: PhantomData,
739            table: PhantomData,
740            marker: PhantomData,
741            row: PhantomData,
742            grouped: PhantomData,
743        }
744    }
745
746    /// Combines this query with another using EXCEPT.
747    pub fn except(
748        self,
749        other: impl IntoSelect<'a, S, M, R>,
750    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
751        SelectBuilder {
752            sql: helpers::except(self.sql, other.into_select()),
753            schema: PhantomData,
754            state: PhantomData,
755            table: PhantomData,
756            marker: PhantomData,
757            row: PhantomData,
758            grouped: PhantomData,
759        }
760    }
761
762    /// Combines this query with another using EXCEPT ALL.
763    pub fn except_all(
764        self,
765        other: impl IntoSelect<'a, S, M, R>,
766    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
767        SelectBuilder {
768            sql: helpers::except_all(self.sql, other.into_select()),
769            schema: PhantomData,
770            state: PhantomData,
771            table: PhantomData,
772            marker: PhantomData,
773            row: PhantomData,
774            grouped: PhantomData,
775        }
776    }
777}
778
779//------------------------------------------------------------------------------
780// Expr impl for subquery usage
781//------------------------------------------------------------------------------
782
783impl<'a, S, State, T, M, R, G> drizzle_core::expr::Expr<'a, PostgresValue<'a>>
784    for SelectBuilder<'a, S, State, T, M, R, G>
785where
786    State: ExecutableState,
787    M: drizzle_core::expr::SubqueryType<'a, PostgresValue<'a>>,
788{
789    type SQLType = <M as drizzle_core::expr::SubqueryType<'a, PostgresValue<'a>>>::SQLType;
790    type Nullable = drizzle_core::expr::Null;
791    type Aggregate = drizzle_core::expr::Scalar;
792}
793
794//------------------------------------------------------------------------------
795// IntoSelect conversion trait
796//------------------------------------------------------------------------------
797
798/// Conversion trait for types that can become a `SelectBuilder`.
799/// Used by set operations to accept both raw `SelectBuilder` and `DrizzleBuilder`.
800pub trait IntoSelect<'a, S, M, R> {
801    type State: ExecutableState;
802    type Table;
803    fn into_select(self) -> SelectBuilder<'a, S, Self::State, Self::Table, M, R>;
804}
805
806impl<'a, S, State: ExecutableState, T, M, R, G> IntoSelect<'a, S, M, R>
807    for SelectBuilder<'a, S, State, T, M, R, G>
808{
809    type State = State;
810    type Table = T;
811    fn into_select(self) -> SelectBuilder<'a, S, State, T, M, R> {
812        SelectBuilder {
813            sql: self.sql,
814            schema: PhantomData,
815            state: PhantomData,
816            table: PhantomData,
817            marker: PhantomData,
818            row: PhantomData,
819            grouped: PhantomData,
820        }
821    }
822}
823
824mod insert_select_private {
825    use super::{
826        SelectForSet, SelectFromSet, SelectGroupSet, SelectJoinSet, SelectLimitSet,
827        SelectOffsetSet, SelectOrderSet, SelectSetOpSet, SelectWhereSet,
828    };
829
830    pub trait Sealed {}
831    pub trait Completed: super::ExecutableState {}
832
833    impl Completed for SelectFromSet {}
834    impl Completed for SelectJoinSet {}
835    impl Completed for SelectWhereSet {}
836    impl Completed for SelectGroupSet {}
837    impl Completed for SelectOrderSet {}
838    impl Completed for SelectLimitSet {}
839    impl Completed for SelectOffsetSet {}
840    impl Completed for SelectSetOpSet {}
841    impl Completed for SelectForSet {}
842}
843
844/// A completed SELECT that can supply rows to an INSERT.
845#[doc(hidden)]
846pub trait CompletedSelect<'a, S, R>: insert_select_private::Sealed {
847    type Marker;
848    type Grouped;
849
850    fn into_select_sql(self) -> drizzle_core::SQL<'a, PostgresValue<'a>>;
851}
852
853/// Converts a completed SELECT or attached SELECT wrapper into its checked source.
854#[doc(hidden)]
855pub trait IntoSelectQuery<'a, S, R> {
856    type Marker;
857    type Grouped;
858    type Select: CompletedSelect<'a, S, R, Marker = Self::Marker, Grouped = Self::Grouped>;
859
860    fn into_select_query(self) -> Self::Select;
861}
862
863impl<'a, S, State, T, M, R, G> insert_select_private::Sealed
864    for SelectBuilder<'a, S, State, T, M, R, G>
865where
866    State: insert_select_private::Completed,
867{
868}
869
870impl<'a, S, State, T, M, R, G> CompletedSelect<'a, S, R> for SelectBuilder<'a, S, State, T, M, R, G>
871where
872    State: insert_select_private::Completed,
873{
874    type Marker = M;
875    type Grouped = G;
876
877    fn into_select_sql(self) -> drizzle_core::SQL<'a, PostgresValue<'a>> {
878        self.sql
879    }
880}
881
882impl<'a, S, State, T, M, R, G> IntoSelectQuery<'a, S, R> for SelectBuilder<'a, S, State, T, M, R, G>
883where
884    State: insert_select_private::Completed,
885{
886    type Marker = M;
887    type Grouped = G;
888    type Select = Self;
889
890    fn into_select_query(self) -> Self::Select {
891        self
892    }
893}
894
895//------------------------------------------------------------------------------
896// FOR UPDATE/SHARE Row Locking (PostgreSQL-specific)
897//------------------------------------------------------------------------------
898
899/// Trait for states that can have FOR UPDATE/SHARE clauses applied.
900pub trait ForLockableState {}
901
902impl ForLockableState for SelectFromSet {}
903impl ForLockableState for SelectWhereSet {}
904impl ForLockableState for SelectOrderSet {}
905impl ForLockableState for SelectLimitSet {}
906impl ForLockableState for SelectOffsetSet {}
907impl ForLockableState for SelectJoinSet {}
908impl ForLockableState for SelectGroupSet {}
909
910impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
911where
912    State: ForLockableState,
913{
914    /// Adds FOR UPDATE clause to lock selected rows for update.
915    #[must_use]
916    pub fn for_update(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
917        SelectBuilder {
918            sql: self.sql.append(helpers::for_update()),
919            schema: PhantomData,
920            state: PhantomData,
921            table: PhantomData,
922            marker: PhantomData,
923            row: PhantomData,
924            grouped: PhantomData,
925        }
926    }
927
928    /// Adds FOR SHARE clause to lock selected rows for shared access.
929    #[must_use]
930    pub fn for_share(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
931        SelectBuilder {
932            sql: self.sql.append(helpers::for_share()),
933            schema: PhantomData,
934            state: PhantomData,
935            table: PhantomData,
936            marker: PhantomData,
937            row: PhantomData,
938            grouped: PhantomData,
939        }
940    }
941
942    /// Adds FOR NO KEY UPDATE clause.
943    #[must_use]
944    pub fn for_no_key_update(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
945        SelectBuilder {
946            sql: self.sql.append(helpers::for_no_key_update()),
947            schema: PhantomData,
948            state: PhantomData,
949            table: PhantomData,
950            marker: PhantomData,
951            row: PhantomData,
952            grouped: PhantomData,
953        }
954    }
955
956    /// Adds FOR KEY SHARE clause.
957    #[must_use]
958    pub fn for_key_share(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
959        SelectBuilder {
960            sql: self.sql.append(helpers::for_key_share()),
961            schema: PhantomData,
962            state: PhantomData,
963            table: PhantomData,
964            marker: PhantomData,
965            row: PhantomData,
966            grouped: PhantomData,
967        }
968    }
969
970    /// Adds FOR UPDATE OF table clause.
971    pub fn for_update_of<U: PostgresTable<'a>>(
972        self,
973        table: U,
974    ) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
975        SelectBuilder {
976            sql: self.sql.append(helpers::for_update_of(table.name())),
977            schema: PhantomData,
978            state: PhantomData,
979            table: PhantomData,
980            marker: PhantomData,
981            row: PhantomData,
982            grouped: PhantomData,
983        }
984    }
985
986    /// Adds FOR SHARE OF table clause.
987    pub fn for_share_of<U: PostgresTable<'a>>(
988        self,
989        table: U,
990    ) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
991        SelectBuilder {
992            sql: self.sql.append(helpers::for_share_of(table.name())),
993            schema: PhantomData,
994            state: PhantomData,
995            table: PhantomData,
996            marker: PhantomData,
997            row: PhantomData,
998            grouped: PhantomData,
999        }
1000    }
1001}
1002
1003//------------------------------------------------------------------------------
1004// Post-FOR State Implementation (NOWAIT / SKIP LOCKED)
1005//------------------------------------------------------------------------------
1006
1007impl<S, T, M, R, G> SelectBuilder<'_, S, SelectForSet, T, M, R, G> {
1008    /// Adds NOWAIT option to fail immediately if rows are locked.
1009    #[must_use]
1010    pub fn nowait(self) -> Self {
1011        SelectBuilder {
1012            sql: self.sql.append(helpers::nowait()),
1013            schema: PhantomData,
1014            state: PhantomData,
1015            table: PhantomData,
1016            marker: PhantomData,
1017            row: PhantomData,
1018            grouped: PhantomData,
1019        }
1020    }
1021
1022    /// Adds SKIP LOCKED option to skip over locked rows.
1023    #[must_use]
1024    pub fn skip_locked(self) -> Self {
1025        SelectBuilder {
1026            sql: self.sql.append(helpers::skip_locked()),
1027            schema: PhantomData,
1028            state: PhantomData,
1029            table: PhantomData,
1030            marker: PhantomData,
1031            row: PhantomData,
1032            grouped: PhantomData,
1033        }
1034    }
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039    use super::*;
1040    use drizzle_core::{SQL, ToSQL};
1041
1042    #[test]
1043    fn test_select_builder_creation() {
1044        let builder = SelectBuilder::<(), SelectInitial> {
1045            sql: SQL::raw("SELECT *"),
1046            schema: PhantomData,
1047            state: PhantomData,
1048            table: PhantomData,
1049            marker: PhantomData,
1050            row: PhantomData,
1051            grouped: PhantomData,
1052        };
1053
1054        assert_eq!(builder.to_sql().sql(), "SELECT *");
1055    }
1056}