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    pub fn group_by<Gr>(
275        self,
276        columns: Gr,
277    ) -> SelectBuilder<'a, S, SelectGroupSet, T, M, R, Gr::Columns>
278    where
279        Gr: drizzle_core::IntoGroupBy<'a, PostgresValue<'a>>,
280    {
281        SelectBuilder {
282            sql: self.sql.append(helpers::group_by_expr(columns)),
283            schema: PhantomData,
284            state: PhantomData,
285            table: PhantomData,
286            marker: PhantomData,
287            row: PhantomData,
288            grouped: PhantomData,
289        }
290    }
291}
292
293// HAVING (available only from SelectGroupSet)
294impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
295where
296    State: drizzle_core::HavingAllowed,
297{
298    /// Adds a HAVING clause after GROUP BY.
299    pub fn having<E>(self, condition: E) -> SelectBuilder<'a, S, SelectGroupSet, T, M, R, G>
300    where
301        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
302        E::SQLType: drizzle_core::types::BooleanLike,
303    {
304        SelectBuilder {
305            sql: self.sql.append(helpers::having(condition)),
306            schema: PhantomData,
307            state: PhantomData,
308            table: PhantomData,
309            marker: PhantomData,
310            row: PhantomData,
311            grouped: PhantomData,
312        }
313    }
314}
315
316// ORDER BY (available from many states)
317impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
318where
319    State: drizzle_core::OrderByAllowed,
320{
321    /// Sorts the query results.
322    #[inline]
323    pub fn order_by<TOrderBy>(
324        self,
325        expressions: TOrderBy,
326    ) -> SelectBuilder<'a, S, SelectOrderSet, T, M, R, G>
327    where
328        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
329    {
330        SelectBuilder {
331            sql: self.sql.append(helpers::order_by(expressions)),
332            schema: PhantomData,
333            state: PhantomData,
334            table: PhantomData,
335            marker: PhantomData,
336            row: PhantomData,
337            grouped: PhantomData,
338        }
339    }
340}
341
342// LIMIT (available from many states)
343impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
344where
345    State: drizzle_core::LimitAllowed,
346{
347    /// Limits the number of rows returned.
348    ///
349    /// # Panics
350    ///
351    /// Panics when a signed numeric argument is negative or a numeric value
352    /// does not fit in `usize`.
353    #[inline]
354    #[must_use]
355    #[track_caller]
356    pub fn limit<P>(self, limit: P) -> SelectBuilder<'a, S, SelectLimitSet, T, M, R, G>
357    where
358        P: drizzle_core::PaginationArg<'a, PostgresValue<'a>>,
359    {
360        SelectBuilder {
361            sql: self.sql.append(helpers::limit(limit)),
362            schema: PhantomData,
363            state: PhantomData,
364            table: PhantomData,
365            marker: PhantomData,
366            row: PhantomData,
367            grouped: PhantomData,
368        }
369    }
370}
371
372// OFFSET (available from SelectFromSet, SelectLimitSet, SelectSetOpSet)
373impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
374where
375    State: drizzle_core::OffsetAllowed,
376{
377    /// Sets the offset for the query results.
378    ///
379    /// # Panics
380    ///
381    /// Panics when a signed numeric argument is negative or a numeric value
382    /// does not fit in `usize`.
383    #[inline]
384    #[must_use]
385    #[track_caller]
386    pub fn offset<P>(self, offset: P) -> SelectBuilder<'a, S, SelectOffsetSet, T, M, R, G>
387    where
388        P: drizzle_core::PaginationArg<'a, PostgresValue<'a>>,
389    {
390        SelectBuilder {
391            sql: self.sql.append(helpers::offset(offset)),
392            schema: PhantomData,
393            state: PhantomData,
394            table: PhantomData,
395            marker: PhantomData,
396            row: PhantomData,
397            grouped: PhantomData,
398        }
399    }
400}
401
402//------------------------------------------------------------------------------
403// CTE support
404//------------------------------------------------------------------------------
405
406impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
407where
408    State: AsCteState,
409    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
410{
411    /// Converts this SELECT query into a typed CTE using alias tag name.
412    #[inline]
413    #[must_use]
414    pub fn into_cte<Tag: drizzle_core::Tag + 'static>(
415        self,
416    ) -> super::CTEView<
417        'a,
418        <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased<Tag>,
419        Self,
420    > {
421        let name = Tag::NAME;
422        super::CTEView::new(
423            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias::<Tag>(),
424            name,
425            self,
426        )
427    }
428}
429
430//------------------------------------------------------------------------------
431// Set operation support (UNION / INTERSECT / EXCEPT)
432//------------------------------------------------------------------------------
433
434impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
435where
436    State: ExecutableState,
437{
438    /// Combines this query with another using UNION.
439    pub fn union(
440        self,
441        other: impl IntoSelect<'a, S, M, R>,
442    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
443        SelectBuilder {
444            sql: helpers::union(self.sql, other.into_select()),
445            schema: PhantomData,
446            state: PhantomData,
447            table: PhantomData,
448            marker: PhantomData,
449            row: PhantomData,
450            grouped: PhantomData,
451        }
452    }
453
454    /// Combines this query with another using UNION ALL.
455    pub fn union_all(
456        self,
457        other: impl IntoSelect<'a, S, M, R>,
458    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
459        SelectBuilder {
460            sql: helpers::union_all(self.sql, other.into_select()),
461            schema: PhantomData,
462            state: PhantomData,
463            table: PhantomData,
464            marker: PhantomData,
465            row: PhantomData,
466            grouped: PhantomData,
467        }
468    }
469
470    /// Combines this query with another using INTERSECT.
471    pub fn intersect(
472        self,
473        other: impl IntoSelect<'a, S, M, R>,
474    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
475        SelectBuilder {
476            sql: helpers::intersect(self.sql, other.into_select()),
477            schema: PhantomData,
478            state: PhantomData,
479            table: PhantomData,
480            marker: PhantomData,
481            row: PhantomData,
482            grouped: PhantomData,
483        }
484    }
485
486    /// Combines this query with another using INTERSECT ALL.
487    pub fn intersect_all(
488        self,
489        other: impl IntoSelect<'a, S, M, R>,
490    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
491        SelectBuilder {
492            sql: helpers::intersect_all(self.sql, other.into_select()),
493            schema: PhantomData,
494            state: PhantomData,
495            table: PhantomData,
496            marker: PhantomData,
497            row: PhantomData,
498            grouped: PhantomData,
499        }
500    }
501
502    /// Combines this query with another using EXCEPT.
503    pub fn except(
504        self,
505        other: impl IntoSelect<'a, S, M, R>,
506    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
507        SelectBuilder {
508            sql: helpers::except(self.sql, other.into_select()),
509            schema: PhantomData,
510            state: PhantomData,
511            table: PhantomData,
512            marker: PhantomData,
513            row: PhantomData,
514            grouped: PhantomData,
515        }
516    }
517
518    /// Combines this query with another using EXCEPT ALL.
519    pub fn except_all(
520        self,
521        other: impl IntoSelect<'a, S, M, R>,
522    ) -> SelectBuilder<'a, S, SelectSetOpSet, T, M, R, G> {
523        SelectBuilder {
524            sql: helpers::except_all(self.sql, other.into_select()),
525            schema: PhantomData,
526            state: PhantomData,
527            table: PhantomData,
528            marker: PhantomData,
529            row: PhantomData,
530            grouped: PhantomData,
531        }
532    }
533}
534
535//------------------------------------------------------------------------------
536// Expr impl for subquery usage
537//------------------------------------------------------------------------------
538
539impl<'a, S, State, T, M, R, G> drizzle_core::expr::Expr<'a, PostgresValue<'a>>
540    for SelectBuilder<'a, S, State, T, M, R, G>
541where
542    State: ExecutableState,
543    M: drizzle_core::expr::SubqueryType<'a, PostgresValue<'a>>,
544{
545    type SQLType = <M as drizzle_core::expr::SubqueryType<'a, PostgresValue<'a>>>::SQLType;
546    type Nullable = drizzle_core::expr::Null;
547    type Aggregate = drizzle_core::expr::Scalar;
548}
549
550//------------------------------------------------------------------------------
551// IntoSelect conversion trait
552//------------------------------------------------------------------------------
553
554/// Conversion trait for types that can become a `SelectBuilder`.
555/// Used by set operations to accept both raw `SelectBuilder` and `DrizzleBuilder`.
556pub trait IntoSelect<'a, S, M, R> {
557    type State: ExecutableState;
558    type Table;
559    fn into_select(self) -> SelectBuilder<'a, S, Self::State, Self::Table, M, R>;
560}
561
562impl<'a, S, State: ExecutableState, T, M, R, G> IntoSelect<'a, S, M, R>
563    for SelectBuilder<'a, S, State, T, M, R, G>
564{
565    type State = State;
566    type Table = T;
567    fn into_select(self) -> SelectBuilder<'a, S, State, T, M, R> {
568        SelectBuilder {
569            sql: self.sql,
570            schema: PhantomData,
571            state: PhantomData,
572            table: PhantomData,
573            marker: PhantomData,
574            row: PhantomData,
575            grouped: PhantomData,
576        }
577    }
578}
579
580//------------------------------------------------------------------------------
581// FOR UPDATE/SHARE Row Locking (PostgreSQL-specific)
582//------------------------------------------------------------------------------
583
584/// Trait for states that can have FOR UPDATE/SHARE clauses applied.
585pub trait ForLockableState {}
586
587impl ForLockableState for SelectFromSet {}
588impl ForLockableState for SelectWhereSet {}
589impl ForLockableState for SelectOrderSet {}
590impl ForLockableState for SelectLimitSet {}
591impl ForLockableState for SelectOffsetSet {}
592impl ForLockableState for SelectJoinSet {}
593impl ForLockableState for SelectGroupSet {}
594
595impl<'a, S, State, T, M, R, G> SelectBuilder<'a, S, State, T, M, R, G>
596where
597    State: ForLockableState,
598{
599    /// Adds FOR UPDATE clause to lock selected rows for update.
600    #[must_use]
601    pub fn for_update(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
602        SelectBuilder {
603            sql: self.sql.append(helpers::for_update()),
604            schema: PhantomData,
605            state: PhantomData,
606            table: PhantomData,
607            marker: PhantomData,
608            row: PhantomData,
609            grouped: PhantomData,
610        }
611    }
612
613    /// Adds FOR SHARE clause to lock selected rows for shared access.
614    #[must_use]
615    pub fn for_share(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
616        SelectBuilder {
617            sql: self.sql.append(helpers::for_share()),
618            schema: PhantomData,
619            state: PhantomData,
620            table: PhantomData,
621            marker: PhantomData,
622            row: PhantomData,
623            grouped: PhantomData,
624        }
625    }
626
627    /// Adds FOR NO KEY UPDATE clause.
628    #[must_use]
629    pub fn for_no_key_update(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
630        SelectBuilder {
631            sql: self.sql.append(helpers::for_no_key_update()),
632            schema: PhantomData,
633            state: PhantomData,
634            table: PhantomData,
635            marker: PhantomData,
636            row: PhantomData,
637            grouped: PhantomData,
638        }
639    }
640
641    /// Adds FOR KEY SHARE clause.
642    #[must_use]
643    pub fn for_key_share(self) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
644        SelectBuilder {
645            sql: self.sql.append(helpers::for_key_share()),
646            schema: PhantomData,
647            state: PhantomData,
648            table: PhantomData,
649            marker: PhantomData,
650            row: PhantomData,
651            grouped: PhantomData,
652        }
653    }
654
655    /// Adds FOR UPDATE OF table clause.
656    pub fn for_update_of<U: PostgresTable<'a>>(
657        self,
658        table: U,
659    ) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
660        SelectBuilder {
661            sql: self.sql.append(helpers::for_update_of(table.name())),
662            schema: PhantomData,
663            state: PhantomData,
664            table: PhantomData,
665            marker: PhantomData,
666            row: PhantomData,
667            grouped: PhantomData,
668        }
669    }
670
671    /// Adds FOR SHARE OF table clause.
672    pub fn for_share_of<U: PostgresTable<'a>>(
673        self,
674        table: U,
675    ) -> SelectBuilder<'a, S, SelectForSet, T, M, R, G> {
676        SelectBuilder {
677            sql: self.sql.append(helpers::for_share_of(table.name())),
678            schema: PhantomData,
679            state: PhantomData,
680            table: PhantomData,
681            marker: PhantomData,
682            row: PhantomData,
683            grouped: PhantomData,
684        }
685    }
686}
687
688//------------------------------------------------------------------------------
689// Post-FOR State Implementation (NOWAIT / SKIP LOCKED)
690//------------------------------------------------------------------------------
691
692impl<S, T, M, R, G> SelectBuilder<'_, S, SelectForSet, T, M, R, G> {
693    /// Adds NOWAIT option to fail immediately if rows are locked.
694    #[must_use]
695    pub fn nowait(self) -> Self {
696        SelectBuilder {
697            sql: self.sql.append(helpers::nowait()),
698            schema: PhantomData,
699            state: PhantomData,
700            table: PhantomData,
701            marker: PhantomData,
702            row: PhantomData,
703            grouped: PhantomData,
704        }
705    }
706
707    /// Adds SKIP LOCKED option to skip over locked rows.
708    #[must_use]
709    pub fn skip_locked(self) -> Self {
710        SelectBuilder {
711            sql: self.sql.append(helpers::skip_locked()),
712            schema: PhantomData,
713            state: PhantomData,
714            table: PhantomData,
715            marker: PhantomData,
716            row: PhantomData,
717            grouped: PhantomData,
718        }
719    }
720}
721
722#[cfg(test)]
723mod tests {
724    use super::*;
725    use drizzle_core::{SQL, ToSQL};
726
727    #[test]
728    fn test_select_builder_creation() {
729        let builder = SelectBuilder::<(), SelectInitial> {
730            sql: SQL::raw("SELECT *"),
731            schema: PhantomData,
732            state: PhantomData,
733            table: PhantomData,
734            marker: PhantomData,
735            row: PhantomData,
736            grouped: PhantomData,
737        };
738
739        assert_eq!(builder.to_sql().sql(), "SELECT *");
740    }
741}