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::fmt::Debug;
6use core::marker::PhantomData;
7use drizzle_core::ToSQL;
8use drizzle_core::traits::SQLTable;
9use paste::paste;
10
11// Import the ExecutableState trait
12use super::ExecutableState;
13
14//------------------------------------------------------------------------------
15// Type State Markers
16//------------------------------------------------------------------------------
17
18/// Marker for the initial state of `SelectBuilder`.
19#[derive(Debug, Clone, Copy, Default)]
20pub struct SelectInitial;
21
22/// Marker for the state after FROM clause
23#[derive(Debug, Clone, Copy, Default)]
24pub struct SelectFromSet;
25
26/// Marker for the state after JOIN clause
27#[derive(Debug, Clone, Copy, Default)]
28pub struct SelectJoinSet;
29
30/// Marker for the state after WHERE clause
31#[derive(Debug, Clone, Copy, Default)]
32pub struct SelectWhereSet;
33
34/// Marker for the state after GROUP BY clause
35#[derive(Debug, Clone, Copy, Default)]
36pub struct SelectGroupSet;
37
38/// Marker for the state after ORDER BY clause
39#[derive(Debug, Clone, Copy, Default)]
40pub struct SelectOrderSet;
41
42/// Marker for the state after LIMIT clause
43#[derive(Debug, Clone, Copy, Default)]
44pub struct SelectLimitSet;
45
46/// Marker for the state after OFFSET clause
47#[derive(Debug, Clone, Copy, Default)]
48pub struct SelectOffsetSet;
49
50/// Marker for the state after set operations (UNION/INTERSECT/EXCEPT)
51#[derive(Debug, Clone, Copy, Default)]
52pub struct SelectSetOpSet;
53
54/// Marker for the state after FOR UPDATE/SHARE clause
55#[derive(Debug, Clone, Copy, Default)]
56pub struct SelectForSet;
57
58//------------------------------------------------------------------------------
59// Join macros (generates all join variants)
60//------------------------------------------------------------------------------
61
62#[doc(hidden)]
63macro_rules! join_impl {
64    () => {
65        join_impl!(natural, Join::new().natural(), drizzle_core::AfterJoin);
66        join_impl!(natural_left, Join::new().natural().left(), drizzle_core::AfterLeftJoin);
67        join_impl!(left, Join::new().left(), drizzle_core::AfterLeftJoin);
68        join_impl!(left_outer, Join::new().left().outer(), drizzle_core::AfterLeftJoin);
69        join_impl!(natural_left_outer, Join::new().natural().left().outer(), drizzle_core::AfterLeftJoin);
70        join_impl!(natural_right, Join::new().natural().right(), drizzle_core::AfterRightJoin);
71        join_impl!(right, Join::new().right(), drizzle_core::AfterRightJoin);
72        join_impl!(right_outer, Join::new().right().outer(), drizzle_core::AfterRightJoin);
73        join_impl!(natural_right_outer, Join::new().natural().right().outer(), drizzle_core::AfterRightJoin);
74        join_impl!(natural_full, Join::new().natural().full(), drizzle_core::AfterFullJoin);
75        join_impl!(full, Join::new().full(), drizzle_core::AfterFullJoin);
76        join_impl!(full_outer, Join::new().full().outer(), drizzle_core::AfterFullJoin);
77        join_impl!(natural_full_outer, Join::new().natural().full().outer(), drizzle_core::AfterFullJoin);
78        join_impl!(inner, Join::new().inner(), drizzle_core::AfterJoin);
79        join_impl!(cross, Join::new().cross(), drizzle_core::AfterJoin);
80
81        // USING variants only for non-natural, non-cross joins
82        join_using_impl!(left, drizzle_core::AfterLeftJoin);
83        join_using_impl!(left_outer, drizzle_core::AfterLeftJoin);
84        join_using_impl!(right, drizzle_core::AfterRightJoin);
85        join_using_impl!(right_outer, drizzle_core::AfterRightJoin);
86        join_using_impl!(full, drizzle_core::AfterFullJoin);
87        join_using_impl!(full_outer, drizzle_core::AfterFullJoin);
88        join_using_impl!(inner, drizzle_core::AfterJoin);
89        join_using_impl!(); // Plain JOIN
90    };
91    ($type:ident, $join_expr:expr, $join_trait:path) => {
92        paste! {
93            /// JOIN with ON clause
94            pub fn [<$type _join>]<J: crate::helpers::JoinArg<'a, T>>(
95                self,
96                arg: J,
97            ) -> SelectBuilder<'a, S, SelectJoinSet, J::JoinedTable, <M as drizzle_core::ScopePush<J::JoinedTable>>::Out, <M as $join_trait<R, J::JoinedTable>>::NewRow, G>
98            where
99                M: $join_trait<R, J::JoinedTable> + drizzle_core::ScopePush<J::JoinedTable>,
100            {
101                use drizzle_core::Join;
102                SelectBuilder {
103                    sql: self.sql.append(arg.into_join_sql($join_expr)),
104                    schema: PhantomData,
105                    state: PhantomData,
106                    table: PhantomData,
107                    marker: PhantomData,
108                    row: PhantomData,
109                    grouped: PhantomData,
110                }
111            }
112        }
113    };
114}
115
116macro_rules! join_using_impl {
117    () => {
118        /// JOIN with USING clause (PostgreSQL-specific)
119        pub fn join_using<U: PostgresTable<'a>>(
120            self,
121            table: U,
122            columns: impl ToSQL<'a, PostgresValue<'a>>,
123        ) -> SelectBuilder<
124            'a,
125            S,
126            SelectJoinSet,
127            U,
128            <M as drizzle_core::ScopePush<U>>::Out,
129            <M as drizzle_core::AfterJoin<R, U>>::NewRow,
130            G,
131        >
132        where
133            M: drizzle_core::AfterJoin<R, U> + drizzle_core::ScopePush<U>,
134        {
135            SelectBuilder {
136                sql: self.sql.append(helpers::join_using(table, columns)),
137                schema: PhantomData,
138                state: PhantomData,
139                table: PhantomData,
140                marker: PhantomData,
141                row: PhantomData,
142                grouped: PhantomData,
143            }
144        }
145    };
146    ($type:ident, $join_trait:path) => {
147        paste! {
148            /// JOIN with USING clause (PostgreSQL-specific)
149            pub fn [<$type _join_using>]<U: PostgresTable<'a>>(
150                self,
151                table: U,
152                columns: impl ToSQL<'a, PostgresValue<'a>>,
153            ) -> SelectBuilder<
154                'a,
155                S,
156                SelectJoinSet,
157                U,
158                <M as drizzle_core::ScopePush<U>>::Out,
159                <M as $join_trait<R, U>>::NewRow,
160                G,
161            >
162            where
163                M: $join_trait<R, U> + drizzle_core::ScopePush<U>,
164            {
165                SelectBuilder {
166                    sql: self.sql.append(helpers::[<$type _join_using>](table, columns)),
167                    schema: PhantomData,
168                    state: PhantomData,
169                    table: PhantomData,
170                    marker: PhantomData,
171                    row: PhantomData,
172                    grouped: PhantomData,
173                }
174            }
175        }
176    };
177}
178
179//------------------------------------------------------------------------------
180// Capability trait impls for each state
181//------------------------------------------------------------------------------
182
183// ExecutableState
184impl ExecutableState for SelectFromSet {}
185impl ExecutableState for SelectWhereSet {}
186impl ExecutableState for SelectLimitSet {}
187impl ExecutableState for SelectOffsetSet {}
188impl ExecutableState for SelectOrderSet {}
189impl ExecutableState for SelectGroupSet {}
190impl ExecutableState for SelectJoinSet {}
191impl ExecutableState for SelectSetOpSet {}
192impl ExecutableState for SelectForSet {}
193
194// WhereAllowed
195impl drizzle_core::WhereAllowed for SelectFromSet {}
196impl drizzle_core::WhereAllowed for SelectJoinSet {}
197
198// GroupByAllowed
199impl drizzle_core::GroupByAllowed for SelectFromSet {}
200impl drizzle_core::GroupByAllowed for SelectJoinSet {}
201impl drizzle_core::GroupByAllowed for SelectWhereSet {}
202
203// OrderByAllowed
204impl drizzle_core::OrderByAllowed for SelectFromSet {}
205impl drizzle_core::OrderByAllowed for SelectJoinSet {}
206impl drizzle_core::OrderByAllowed for SelectWhereSet {}
207impl drizzle_core::OrderByAllowed for SelectGroupSet {}
208impl drizzle_core::OrderByAllowed for SelectSetOpSet {}
209
210// LimitAllowed
211impl drizzle_core::LimitAllowed for SelectFromSet {}
212impl drizzle_core::LimitAllowed for SelectJoinSet {}
213impl drizzle_core::LimitAllowed for SelectWhereSet {}
214impl drizzle_core::LimitAllowed for SelectGroupSet {}
215impl drizzle_core::LimitAllowed for SelectOrderSet {}
216impl drizzle_core::LimitAllowed for SelectSetOpSet {}
217
218// OffsetAllowed
219impl drizzle_core::OffsetAllowed for SelectFromSet {}
220impl drizzle_core::OffsetAllowed for SelectLimitSet {}
221impl drizzle_core::OffsetAllowed for SelectSetOpSet {}
222
223// JoinAllowed
224impl drizzle_core::JoinAllowed for SelectFromSet {}
225impl drizzle_core::JoinAllowed for SelectJoinSet {}
226
227// HavingAllowed
228impl drizzle_core::HavingAllowed for SelectGroupSet {}
229
230// GroupByApplied (for aggregate/scalar mixing enforcement)
231impl drizzle_core::GroupByApplied for SelectGroupSet {}
232impl drizzle_core::GroupByApplied for SelectOrderSet {}
233impl drizzle_core::GroupByApplied for SelectLimitSet {}
234impl drizzle_core::GroupByApplied for SelectOffsetSet {}
235impl drizzle_core::GroupByApplied for SelectSetOpSet {}
236
237#[doc(hidden)]
238pub trait AsCteState {}
239
240impl AsCteState for SelectFromSet {}
241impl AsCteState for SelectJoinSet {}
242impl AsCteState for SelectWhereSet {}
243impl AsCteState for SelectGroupSet {}
244impl AsCteState for SelectOrderSet {}
245impl AsCteState for SelectLimitSet {}
246impl AsCteState for SelectOffsetSet {}
247
248//------------------------------------------------------------------------------
249// SelectBuilder Definition
250//------------------------------------------------------------------------------
251
252/// Builds a SELECT query specifically for `PostgreSQL`
253pub type SelectBuilder<'a, Schema, State, Table = (), Marker = (), Row = (), Grouped = ()> =
254    super::QueryBuilder<'a, Schema, State, Table, Marker, Row, Grouped>;
255
256//------------------------------------------------------------------------------
257// Initial State: .from()
258//------------------------------------------------------------------------------
259
260impl<'a, S, M> SelectBuilder<'a, S, SelectInitial, (), M> {
261    /// Specifies the table to select FROM and transitions state.
262    #[inline]
263    #[allow(clippy::type_complexity)]
264    pub fn from<T>(
265        self,
266        query: T,
267    ) -> SelectBuilder<
268        'a,
269        S,
270        SelectFromSet,
271        T,
272        drizzle_core::Scoped<M, drizzle_core::Cons<T, drizzle_core::Nil>>,
273        <M as drizzle_core::ResolveRow<T>>::Row,
274    >
275    where
276        T: ToSQL<'a, PostgresValue<'a>>,
277        M: drizzle_core::ResolveRow<T>,
278    {
279        SelectBuilder {
280            sql: self.sql.append(helpers::from(query)),
281            schema: PhantomData,
282            state: PhantomData,
283            table: PhantomData,
284            marker: PhantomData,
285            row: PhantomData,
286            grouped: PhantomData,
287        }
288    }
289}
290
291//------------------------------------------------------------------------------
292// Capability-gated methods (generic over State)
293//------------------------------------------------------------------------------
294
295// JOIN (available from SelectFromSet and SelectJoinSet)
296impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
297where
298    State: drizzle_core::JoinAllowed,
299{
300    /// Adds an INNER JOIN clause to the query.
301    #[inline]
302    #[allow(clippy::type_complexity)]
303    pub fn join<J: crate::helpers::JoinArg<'a, T>>(
304        self,
305        arg: J,
306    ) -> SelectBuilder<
307        'a,
308        S,
309        SelectJoinSet,
310        J::JoinedTable,
311        <M as drizzle_core::ScopePush<J::JoinedTable>>::Out,
312        <M as drizzle_core::AfterJoin<R, J::JoinedTable>>::NewRow,
313        G,
314    >
315    where
316        M: drizzle_core::AfterJoin<R, J::JoinedTable> + drizzle_core::ScopePush<J::JoinedTable>,
317    {
318        use drizzle_core::Join;
319        SelectBuilder {
320            sql: self.sql.append(arg.into_join_sql(Join::new())),
321            schema: PhantomData,
322            state: PhantomData,
323            table: PhantomData,
324            marker: PhantomData,
325            row: PhantomData,
326            grouped: PhantomData,
327        }
328    }
329
330    join_impl!();
331}
332
333// WHERE (available from SelectFromSet and SelectJoinSet)
334impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
335where
336    State: drizzle_core::WhereAllowed,
337{
338    /// Adds a WHERE clause to filter query results.
339    #[inline]
340    pub fn r#where<E>(self, condition: E) -> SelectBuilder<'a, S, SelectWhereSet, T, M, R, G>
341    where
342        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
343        E::SQLType: drizzle_core::types::BooleanLike,
344    {
345        SelectBuilder {
346            sql: self.sql.append(helpers::r#where(condition)),
347            schema: PhantomData,
348            state: PhantomData,
349            table: PhantomData,
350            marker: PhantomData,
351            row: PhantomData,
352            grouped: PhantomData,
353        }
354    }
355}
356
357// GROUP BY (available from SelectFromSet, SelectJoinSet, SelectWhereSet)
358impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
359where
360    State: drizzle_core::GroupByAllowed,
361{
362    /// Adds a GROUP BY clause to the query.
363    pub fn group_by<Gr>(
364        self,
365        columns: Gr,
366    ) -> SelectBuilder<'a, S, SelectGroupSet, T, M, R, Gr::Columns>
367    where
368        Gr: drizzle_core::IntoGroupBy<'a, PostgresValue<'a>>,
369    {
370        SelectBuilder {
371            sql: self.sql.append(helpers::group_by_expr(columns)),
372            schema: PhantomData,
373            state: PhantomData,
374            table: PhantomData,
375            marker: PhantomData,
376            row: PhantomData,
377            grouped: PhantomData,
378        }
379    }
380}
381
382// HAVING (available only from SelectGroupSet)
383impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
384where
385    State: drizzle_core::HavingAllowed,
386{
387    /// Adds a HAVING clause after GROUP BY.
388    pub fn having<E>(self, condition: E) -> SelectBuilder<'a, S, SelectGroupSet, T, M, R, G>
389    where
390        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
391        E::SQLType: drizzle_core::types::BooleanLike,
392    {
393        SelectBuilder {
394            sql: self.sql.append(helpers::having(condition)),
395            schema: PhantomData,
396            state: PhantomData,
397            table: PhantomData,
398            marker: PhantomData,
399            row: PhantomData,
400            grouped: PhantomData,
401        }
402    }
403}
404
405// ORDER BY (available from many states)
406impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
407where
408    State: drizzle_core::OrderByAllowed,
409{
410    /// Sorts the query results.
411    #[inline]
412    pub fn order_by<TOrderBy>(
413        self,
414        expressions: TOrderBy,
415    ) -> SelectBuilder<'a, S, SelectOrderSet, T, M, R, G>
416    where
417        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
418    {
419        SelectBuilder {
420            sql: self.sql.append(helpers::order_by(expressions)),
421            schema: PhantomData,
422            state: PhantomData,
423            table: PhantomData,
424            marker: PhantomData,
425            row: PhantomData,
426            grouped: PhantomData,
427        }
428    }
429}
430
431// LIMIT (available from many states)
432impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
433where
434    State: drizzle_core::LimitAllowed,
435{
436    /// Limits the number of rows returned.
437    ///
438    /// # Panics
439    ///
440    /// Panics when a signed numeric argument is negative or a numeric value
441    /// does not fit in `usize`.
442    #[inline]
443    #[must_use]
444    #[track_caller]
445    pub fn limit<P>(self, limit: P) -> SelectBuilder<'a, S, SelectLimitSet, T, M, R, G>
446    where
447        P: drizzle_core::PaginationArg<'a, PostgresValue<'a>>,
448    {
449        SelectBuilder {
450            sql: self.sql.append(helpers::limit(limit)),
451            schema: PhantomData,
452            state: PhantomData,
453            table: PhantomData,
454            marker: PhantomData,
455            row: PhantomData,
456            grouped: PhantomData,
457        }
458    }
459}
460
461// OFFSET (available from SelectFromSet, SelectLimitSet, SelectSetOpSet)
462impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
463where
464    State: drizzle_core::OffsetAllowed,
465{
466    /// Sets the offset for the query results.
467    ///
468    /// # Panics
469    ///
470    /// Panics when a signed numeric argument is negative or a numeric value
471    /// does not fit in `usize`.
472    #[inline]
473    #[must_use]
474    #[track_caller]
475    pub fn offset<P>(self, offset: P) -> SelectBuilder<'a, S, SelectOffsetSet, T, M, R, G>
476    where
477        P: drizzle_core::PaginationArg<'a, PostgresValue<'a>>,
478    {
479        SelectBuilder {
480            sql: self.sql.append(helpers::offset(offset)),
481            schema: PhantomData,
482            state: PhantomData,
483            table: PhantomData,
484            marker: PhantomData,
485            row: PhantomData,
486            grouped: PhantomData,
487        }
488    }
489}
490
491//------------------------------------------------------------------------------
492// CTE support
493//------------------------------------------------------------------------------
494
495impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
496where
497    State: AsCteState,
498    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
499{
500    /// Converts this SELECT query into a typed CTE using alias tag name.
501    #[inline]
502    #[must_use]
503    pub fn into_cte<Tag: drizzle_core::Tag + 'static>(
504        self,
505    ) -> super::CTEView<
506        'a,
507        <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased<Tag>,
508        Self,
509    > {
510        let name = Tag::NAME;
511        super::CTEView::new(
512            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias::<Tag>(),
513            name,
514            self,
515        )
516    }
517}
518
519//------------------------------------------------------------------------------
520// Set operation support (UNION / INTERSECT / EXCEPT)
521//------------------------------------------------------------------------------
522
523impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
524where
525    State: ExecutableState,
526{
527    /// Combines this query with another using UNION.
528    pub fn union(
529        self,
530        other: impl IntoSelect<'a, S, M, R>,
531    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
532        SelectBuilder {
533            sql: helpers::union(self.sql, other.into_select()),
534            schema: PhantomData,
535            state: PhantomData,
536            table: PhantomData,
537            marker: PhantomData,
538            row: PhantomData,
539            grouped: PhantomData,
540        }
541    }
542
543    /// Combines this query with another using UNION ALL.
544    pub fn union_all(
545        self,
546        other: impl IntoSelect<'a, S, M, R>,
547    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
548        SelectBuilder {
549            sql: helpers::union_all(self.sql, other.into_select()),
550            schema: PhantomData,
551            state: PhantomData,
552            table: PhantomData,
553            marker: PhantomData,
554            row: PhantomData,
555            grouped: PhantomData,
556        }
557    }
558
559    /// Combines this query with another using INTERSECT.
560    pub fn intersect(
561        self,
562        other: impl IntoSelect<'a, S, M, R>,
563    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
564        SelectBuilder {
565            sql: helpers::intersect(self.sql, other.into_select()),
566            schema: PhantomData,
567            state: PhantomData,
568            table: PhantomData,
569            marker: PhantomData,
570            row: PhantomData,
571            grouped: PhantomData,
572        }
573    }
574
575    /// Combines this query with another using INTERSECT ALL.
576    pub fn intersect_all(
577        self,
578        other: impl IntoSelect<'a, S, M, R>,
579    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
580        SelectBuilder {
581            sql: helpers::intersect_all(self.sql, other.into_select()),
582            schema: PhantomData,
583            state: PhantomData,
584            table: PhantomData,
585            marker: PhantomData,
586            row: PhantomData,
587            grouped: PhantomData,
588        }
589    }
590
591    /// Combines this query with another using EXCEPT.
592    pub fn except(
593        self,
594        other: impl IntoSelect<'a, S, M, R>,
595    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
596        SelectBuilder {
597            sql: helpers::except(self.sql, other.into_select()),
598            schema: PhantomData,
599            state: PhantomData,
600            table: PhantomData,
601            marker: PhantomData,
602            row: PhantomData,
603            grouped: PhantomData,
604        }
605    }
606
607    /// Combines this query with another using EXCEPT ALL.
608    pub fn except_all(
609        self,
610        other: impl IntoSelect<'a, S, M, R>,
611    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
612        SelectBuilder {
613            sql: helpers::except_all(self.sql, other.into_select()),
614            schema: PhantomData,
615            state: PhantomData,
616            table: PhantomData,
617            marker: PhantomData,
618            row: PhantomData,
619            grouped: PhantomData,
620        }
621    }
622}
623
624//------------------------------------------------------------------------------
625// Expr impl for subquery usage
626//------------------------------------------------------------------------------
627
628impl<'a, S, State, T, M, R, G> drizzle_core::expr::Expr<'a, PostgresValue<'a>>
629    for SelectBuilder<'a, S, State, T, M, R, G>
630where
631    State: ExecutableState,
632    M: drizzle_core::expr::SubqueryType<'a, PostgresValue<'a>>,
633{
634    type SQLType = <M as drizzle_core::expr::SubqueryType<'a, PostgresValue<'a>>>::SQLType;
635    type Nullable = drizzle_core::expr::Null;
636    type Aggregate = drizzle_core::expr::Scalar;
637}
638
639//------------------------------------------------------------------------------
640// IntoSelect conversion trait
641//------------------------------------------------------------------------------
642
643/// Conversion trait for types that can become a `SelectBuilder`.
644/// Used by set operations to accept both raw `SelectBuilder` and `DrizzleBuilder`.
645pub trait IntoSelect<'a, S, M, R> {
646    type State: ExecutableState;
647    type Table;
648    fn into_select(self) -> SelectBuilder<'a, S, Self::State, Self::Table, M, R>;
649}
650
651impl<'a, S, State: ExecutableState, T, M, R, G> IntoSelect<'a, S, M, R>
652    for SelectBuilder<'a, S, State, T, M, R, G>
653{
654    type State = State;
655    type Table = T;
656    fn into_select(self) -> SelectBuilder<'a, S, State, T, M, R> {
657        SelectBuilder {
658            sql: self.sql,
659            schema: PhantomData,
660            state: PhantomData,
661            table: PhantomData,
662            marker: PhantomData,
663            row: PhantomData,
664            grouped: PhantomData,
665        }
666    }
667}
668
669//------------------------------------------------------------------------------
670// FOR UPDATE/SHARE Row Locking (PostgreSQL-specific)
671//------------------------------------------------------------------------------
672
673/// Trait for states that can have FOR UPDATE/SHARE clauses applied.
674pub trait ForLockableState {}
675
676impl ForLockableState for SelectFromSet {}
677impl ForLockableState for SelectWhereSet {}
678impl ForLockableState for SelectOrderSet {}
679impl ForLockableState for SelectLimitSet {}
680impl ForLockableState for SelectOffsetSet {}
681impl ForLockableState for SelectJoinSet {}
682impl ForLockableState for SelectGroupSet {}
683
684impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
685where
686    State: ForLockableState,
687{
688    /// Adds FOR UPDATE clause to lock selected rows for update.
689    #[must_use]
690    pub fn for_update(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
691        SelectBuilder {
692            sql: self.sql.append(helpers::for_update()),
693            schema: PhantomData,
694            state: PhantomData,
695            table: PhantomData,
696            marker: PhantomData,
697            row: PhantomData,
698            grouped: PhantomData,
699        }
700    }
701
702    /// Adds FOR SHARE clause to lock selected rows for shared access.
703    #[must_use]
704    pub fn for_share(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
705        SelectBuilder {
706            sql: self.sql.append(helpers::for_share()),
707            schema: PhantomData,
708            state: PhantomData,
709            table: PhantomData,
710            marker: PhantomData,
711            row: PhantomData,
712            grouped: PhantomData,
713        }
714    }
715
716    /// Adds FOR NO KEY UPDATE clause.
717    #[must_use]
718    pub fn for_no_key_update(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
719        SelectBuilder {
720            sql: self.sql.append(helpers::for_no_key_update()),
721            schema: PhantomData,
722            state: PhantomData,
723            table: PhantomData,
724            marker: PhantomData,
725            row: PhantomData,
726            grouped: PhantomData,
727        }
728    }
729
730    /// Adds FOR KEY SHARE clause.
731    #[must_use]
732    pub fn for_key_share(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
733        SelectBuilder {
734            sql: self.sql.append(helpers::for_key_share()),
735            schema: PhantomData,
736            state: PhantomData,
737            table: PhantomData,
738            marker: PhantomData,
739            row: PhantomData,
740            grouped: PhantomData,
741        }
742    }
743
744    /// Adds FOR UPDATE OF table clause.
745    pub fn for_update_of<U: PostgresTable<'a>>(
746        self,
747        table: U,
748    ) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
749        SelectBuilder {
750            sql: self.sql.append(helpers::for_update_of(table.name())),
751            schema: PhantomData,
752            state: PhantomData,
753            table: PhantomData,
754            marker: PhantomData,
755            row: PhantomData,
756            grouped: PhantomData,
757        }
758    }
759
760    /// Adds FOR SHARE OF table clause.
761    pub fn for_share_of<U: PostgresTable<'a>>(
762        self,
763        table: U,
764    ) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
765        SelectBuilder {
766            sql: self.sql.append(helpers::for_share_of(table.name())),
767            schema: PhantomData,
768            state: PhantomData,
769            table: PhantomData,
770            marker: PhantomData,
771            row: PhantomData,
772            grouped: PhantomData,
773        }
774    }
775}
776
777//------------------------------------------------------------------------------
778// Post-FOR State Implementation (NOWAIT / SKIP LOCKED)
779//------------------------------------------------------------------------------
780
781impl<S, T, M, R, G> SelectBuilder<'_, S, SelectForSet, T, M, R, G> {
782    /// Adds NOWAIT option to fail immediately if rows are locked.
783    #[must_use]
784    pub fn nowait(self) -> Self {
785        SelectBuilder {
786            sql: self.sql.append(helpers::nowait()),
787            schema: PhantomData,
788            state: PhantomData,
789            table: PhantomData,
790            marker: PhantomData,
791            row: PhantomData,
792            grouped: PhantomData,
793        }
794    }
795
796    /// Adds SKIP LOCKED option to skip over locked rows.
797    #[must_use]
798    pub fn skip_locked(self) -> Self {
799        SelectBuilder {
800            sql: self.sql.append(helpers::skip_locked()),
801            schema: PhantomData,
802            state: PhantomData,
803            table: PhantomData,
804            marker: PhantomData,
805            row: PhantomData,
806            grouped: PhantomData,
807        }
808    }
809}
810
811#[cfg(test)]
812mod tests {
813    use super::*;
814    use drizzle_core::{SQL, ToSQL};
815
816    #[test]
817    fn test_select_builder_creation() {
818        let builder = SelectBuilder::<(), SelectInitial> {
819            sql: SQL::raw("SELECT *"),
820            schema: PhantomData,
821            state: PhantomData,
822            table: PhantomData,
823            marker: PhantomData,
824            row: PhantomData,
825            grouped: PhantomData,
826        };
827
828        assert_eq!(builder.to_sql().sql(), "SELECT *");
829    }
830}