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