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