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// Const constructors for all marker types
63impl SelectFromSet {
64    #[inline]
65    pub const fn new() -> Self {
66        Self
67    }
68}
69impl SelectJoinSet {
70    #[inline]
71    pub const fn new() -> Self {
72        Self
73    }
74}
75impl SelectWhereSet {
76    #[inline]
77    pub const fn new() -> Self {
78        Self
79    }
80}
81impl SelectGroupSet {
82    #[inline]
83    pub const fn new() -> Self {
84        Self
85    }
86}
87impl SelectOrderSet {
88    #[inline]
89    pub const fn new() -> Self {
90        Self
91    }
92}
93impl SelectLimitSet {
94    #[inline]
95    pub const fn new() -> Self {
96        Self
97    }
98}
99impl SelectOffsetSet {
100    #[inline]
101    pub const fn new() -> Self {
102        Self
103    }
104}
105impl SelectSetOpSet {
106    #[inline]
107    pub const fn new() -> Self {
108        Self
109    }
110}
111
112#[doc(hidden)]
113macro_rules! join_impl {
114    () => {
115        join_impl!(natural);
116        join_impl!(natural_left);
117        join_impl!(left);
118        join_impl!(left_outer);
119        join_impl!(natural_left_outer);
120        join_impl!(natural_right);
121        join_impl!(right);
122        join_impl!(right_outer);
123        join_impl!(natural_right_outer);
124        join_impl!(natural_full);
125        join_impl!(full);
126        join_impl!(full_outer);
127        join_impl!(natural_full_outer);
128        join_impl!(inner);
129        join_impl!(cross);
130
131        // USING variants only for non-natural, non-cross joins
132        join_using_impl!(left);
133        join_using_impl!(left_outer);
134        join_using_impl!(right);
135        join_using_impl!(right_outer);
136        join_using_impl!(full);
137        join_using_impl!(full_outer);
138        join_using_impl!(inner);
139        join_using_impl!(); // Plain JOIN
140    };
141    ($type:ident) => {
142        paste! {
143            /// JOIN with ON clause
144            pub fn [<$type _join>]<U:  PostgresTable<'a>>(
145                self,
146                table: U,
147                condition: impl ToSQL<'a, PostgresValue<'a>>,
148            ) -> SelectBuilder<'a, S, SelectJoinSet, T> {
149                SelectBuilder {
150                    sql: self.sql.append(helpers::[<$type _join>](table, condition)),
151                    schema: PhantomData,
152                    state: PhantomData,
153                    table: PhantomData,
154                }
155            }
156        }
157    };
158}
159
160macro_rules! join_using_impl {
161    () => {
162        /// JOIN with USING clause (PostgreSQL-specific)
163        pub fn join_using<U: PostgresTable<'a>>(
164            self,
165            table: U,
166            columns: impl ToSQL<'a, PostgresValue<'a>>,
167        ) -> SelectBuilder<'a, S, SelectJoinSet, T> {
168            SelectBuilder {
169                sql: self.sql.append(helpers::join_using(table, columns)),
170                schema: PhantomData,
171                state: PhantomData,
172                table: PhantomData,
173            }
174        }
175    };
176    ($type:ident) => {
177        paste! {
178            /// JOIN with USING clause (PostgreSQL-specific)
179            pub fn [<$type _join_using>]<U:  PostgresTable<'a>>(
180                self,
181                table: U,
182                columns: impl ToSQL<'a, PostgresValue<'a>>,
183            ) -> SelectBuilder<'a, S, SelectJoinSet, T> {
184                SelectBuilder {
185                    sql: self.sql.append(helpers::[<$type _join_using>](table, columns)),
186                    schema: PhantomData,
187                    state: PhantomData,
188                    table: PhantomData,
189                }
190            }
191        }
192    };
193}
194
195// Mark states that can execute queries as implementing the ExecutableState trait
196impl ExecutableState for SelectFromSet {}
197impl ExecutableState for SelectWhereSet {}
198impl ExecutableState for SelectLimitSet {}
199impl ExecutableState for SelectOffsetSet {}
200impl ExecutableState for SelectOrderSet {}
201impl ExecutableState for SelectGroupSet {}
202impl ExecutableState for SelectJoinSet {}
203impl ExecutableState for SelectSetOpSet {}
204
205//------------------------------------------------------------------------------
206// SelectBuilder Definition
207//------------------------------------------------------------------------------
208
209/// Builds a SELECT query specifically for PostgreSQL
210pub type SelectBuilder<'a, Schema, State, Table = ()> =
211    super::QueryBuilder<'a, Schema, State, Table>;
212
213//------------------------------------------------------------------------------
214// Initial State Implementation
215//------------------------------------------------------------------------------
216
217impl<'a, S> SelectBuilder<'a, S, SelectInitial> {
218    /// Specifies the table to select FROM and transitions state
219    #[inline]
220    pub fn from<T>(self, query: T) -> SelectBuilder<'a, S, SelectFromSet, T>
221    where
222        T: ToSQL<'a, PostgresValue<'a>>,
223    {
224        SelectBuilder {
225            sql: self.sql.append(helpers::from(query)),
226            schema: PhantomData,
227            state: PhantomData,
228            table: PhantomData,
229        }
230    }
231}
232
233//------------------------------------------------------------------------------
234// Post-FROM State Implementation
235//------------------------------------------------------------------------------
236
237impl<'a, S, T> SelectBuilder<'a, S, SelectFromSet, T> {
238    /// Adds a JOIN clause to the query
239    #[inline]
240    pub fn join<U: PostgresTable<'a>>(
241        self,
242        table: U,
243        condition: impl ToSQL<'a, PostgresValue<'a>>,
244    ) -> SelectBuilder<'a, S, SelectJoinSet, T> {
245        SelectBuilder {
246            sql: self.sql.append(helpers::join(table, condition)),
247            schema: PhantomData,
248            state: PhantomData,
249            table: PhantomData,
250        }
251    }
252
253    join_impl!();
254
255    #[inline]
256    pub fn r#where(
257        self,
258        condition: impl ToSQL<'a, PostgresValue<'a>>,
259    ) -> SelectBuilder<'a, S, SelectWhereSet, T> {
260        SelectBuilder {
261            sql: self.sql.append(helpers::r#where(condition)),
262            schema: PhantomData,
263            state: PhantomData,
264            table: PhantomData,
265        }
266    }
267
268    /// Adds a GROUP BY clause to the query
269    pub fn group_by(
270        self,
271        expressions: impl IntoIterator<Item = impl ToSQL<'a, PostgresValue<'a>>>,
272    ) -> SelectBuilder<'a, S, SelectGroupSet, T> {
273        SelectBuilder {
274            sql: self.sql.append(helpers::group_by(expressions)),
275            schema: PhantomData,
276            state: PhantomData,
277            table: PhantomData,
278        }
279    }
280
281    /// Limits the number of rows returned
282    #[inline]
283    pub fn limit(self, limit: usize) -> SelectBuilder<'a, S, SelectLimitSet, T> {
284        SelectBuilder {
285            sql: self.sql.append(helpers::limit(limit)),
286            schema: PhantomData,
287            state: PhantomData,
288            table: PhantomData,
289        }
290    }
291
292    /// Sets the offset for the query results
293    #[inline]
294    pub fn offset(self, offset: usize) -> SelectBuilder<'a, S, SelectOffsetSet, T> {
295        SelectBuilder {
296            sql: self.sql.append(helpers::offset(offset)),
297            schema: PhantomData,
298            state: PhantomData,
299            table: PhantomData,
300        }
301    }
302
303    /// Sorts the query results
304    #[inline]
305    pub fn order_by<TOrderBy>(
306        self,
307        expressions: TOrderBy,
308    ) -> SelectBuilder<'a, S, SelectOrderSet, T>
309    where
310        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
311    {
312        SelectBuilder {
313            sql: self.sql.append(helpers::order_by(expressions)),
314            schema: PhantomData,
315            state: PhantomData,
316            table: PhantomData,
317        }
318    }
319
320    /// Converts this SELECT query into a CTE (Common Table Expression) with the given name.
321    #[inline]
322    pub fn as_cte(
323        self,
324        name: &'static str,
325    ) -> super::CTEView<'a, <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased, Self>
326    where
327        T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
328    {
329        super::CTEView::new(
330            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias(name),
331            name,
332            self,
333        )
334    }
335}
336
337//------------------------------------------------------------------------------
338// Post-JOIN State Implementation
339//------------------------------------------------------------------------------
340
341impl<'a, S, T> SelectBuilder<'a, S, SelectJoinSet, T> {
342    /// Adds a WHERE condition after a JOIN
343    #[inline]
344    pub fn r#where(
345        self,
346        condition: impl ToSQL<'a, PostgresValue<'a>>,
347    ) -> SelectBuilder<'a, S, SelectWhereSet, T> {
348        SelectBuilder {
349            sql: self.sql.append(crate::helpers::r#where(condition)),
350            schema: PhantomData,
351            state: PhantomData,
352            table: PhantomData,
353        }
354    }
355    /// Sorts the query results
356    #[inline]
357    pub fn order_by<TOrderBy>(
358        self,
359        expressions: TOrderBy,
360    ) -> SelectBuilder<'a, S, SelectOrderSet, T>
361    where
362        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
363    {
364        SelectBuilder {
365            sql: self.sql.append(helpers::order_by(expressions)),
366            schema: PhantomData,
367            state: PhantomData,
368            table: PhantomData,
369        }
370    }
371    /// Adds a JOIN clause to the query
372    #[inline]
373    pub fn join<U: PostgresTable<'a>>(
374        self,
375        table: U,
376        condition: impl ToSQL<'a, PostgresValue<'a>>,
377    ) -> SelectBuilder<'a, S, SelectJoinSet, T> {
378        SelectBuilder {
379            sql: self.sql.append(helpers::join(table, condition)),
380            schema: PhantomData,
381            state: PhantomData,
382            table: PhantomData,
383        }
384    }
385    join_impl!();
386}
387
388impl<'a, S, T> SelectBuilder<'a, S, SelectJoinSet, T>
389where
390    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
391{
392    /// Converts this SELECT query into a CTE (Common Table Expression) with the given name.
393    #[inline]
394    pub fn as_cte(
395        self,
396        name: &'static str,
397    ) -> super::CTEView<'a, <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased, Self>
398    {
399        super::CTEView::new(
400            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias(name),
401            name,
402            self,
403        )
404    }
405}
406
407//------------------------------------------------------------------------------
408// Post-WHERE State Implementation
409//------------------------------------------------------------------------------
410
411impl<'a, S, T> SelectBuilder<'a, S, SelectWhereSet, T> {
412    /// Adds a GROUP BY clause after a WHERE
413    pub fn group_by(
414        self,
415        expressions: impl IntoIterator<Item = impl ToSQL<'a, PostgresValue<'a>>>,
416    ) -> SelectBuilder<'a, S, SelectGroupSet, T> {
417        SelectBuilder {
418            sql: self.sql.append(helpers::group_by(expressions)),
419            schema: PhantomData,
420            state: PhantomData,
421            table: PhantomData,
422        }
423    }
424
425    /// Adds an ORDER BY clause after a WHERE
426    pub fn order_by<TOrderBy>(
427        self,
428        expressions: TOrderBy,
429    ) -> SelectBuilder<'a, S, SelectOrderSet, T>
430    where
431        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
432    {
433        SelectBuilder {
434            sql: self.sql.append(helpers::order_by(expressions)),
435            schema: PhantomData,
436            state: PhantomData,
437            table: PhantomData,
438        }
439    }
440
441    /// Adds a LIMIT clause after a WHERE
442    pub fn limit(self, limit: usize) -> SelectBuilder<'a, S, SelectLimitSet, T> {
443        SelectBuilder {
444            sql: self.sql.append(helpers::limit(limit)),
445            schema: PhantomData,
446            state: PhantomData,
447            table: PhantomData,
448        }
449    }
450}
451
452impl<'a, S, T> SelectBuilder<'a, S, SelectWhereSet, T>
453where
454    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
455{
456    /// Converts this SELECT query into a CTE (Common Table Expression) with the given name.
457    #[inline]
458    pub fn as_cte(
459        self,
460        name: &'static str,
461    ) -> super::CTEView<'a, <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased, Self>
462    {
463        super::CTEView::new(
464            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias(name),
465            name,
466            self,
467        )
468    }
469}
470
471//------------------------------------------------------------------------------
472// Post-GROUP BY State Implementation
473//------------------------------------------------------------------------------
474
475impl<'a, S, T> SelectBuilder<'a, S, SelectGroupSet, T> {
476    /// Adds a HAVING clause after GROUP BY
477    pub fn having(
478        self,
479        condition: impl ToSQL<'a, PostgresValue<'a>>,
480    ) -> SelectBuilder<'a, S, SelectGroupSet, T> {
481        SelectBuilder {
482            sql: self.sql.append(helpers::having(condition)),
483            schema: PhantomData,
484            state: PhantomData,
485            table: PhantomData,
486        }
487    }
488
489    /// Adds an ORDER BY clause after GROUP BY
490    pub fn order_by<TOrderBy>(
491        self,
492        expressions: TOrderBy,
493    ) -> SelectBuilder<'a, S, SelectOrderSet, T>
494    where
495        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
496    {
497        SelectBuilder {
498            sql: self.sql.append(helpers::order_by(expressions)),
499            schema: PhantomData,
500            state: PhantomData,
501            table: PhantomData,
502        }
503    }
504}
505
506impl<'a, S, T> SelectBuilder<'a, S, SelectGroupSet, T>
507where
508    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
509{
510    /// Converts this SELECT query into a CTE (Common Table Expression) with the given name.
511    #[inline]
512    pub fn as_cte(
513        self,
514        name: &'static str,
515    ) -> super::CTEView<'a, <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased, Self>
516    {
517        super::CTEView::new(
518            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias(name),
519            name,
520            self,
521        )
522    }
523}
524
525//------------------------------------------------------------------------------
526// Post-ORDER BY State Implementation
527//------------------------------------------------------------------------------
528
529impl<'a, S, T> SelectBuilder<'a, S, SelectOrderSet, T> {
530    /// Adds a LIMIT clause after ORDER BY
531    pub fn limit(self, limit: usize) -> SelectBuilder<'a, S, SelectLimitSet, T> {
532        SelectBuilder {
533            sql: self.sql.append(helpers::limit(limit)),
534            schema: PhantomData,
535            state: PhantomData,
536            table: PhantomData,
537        }
538    }
539}
540
541impl<'a, S, T> SelectBuilder<'a, S, SelectOrderSet, T>
542where
543    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
544{
545    /// Converts this SELECT query into a CTE (Common Table Expression) with the given name.
546    #[inline]
547    pub fn as_cte(
548        self,
549        name: &'static str,
550    ) -> super::CTEView<'a, <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased, Self>
551    {
552        super::CTEView::new(
553            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias(name),
554            name,
555            self,
556        )
557    }
558}
559
560//------------------------------------------------------------------------------
561// Post-LIMIT State Implementation
562//------------------------------------------------------------------------------
563
564impl<'a, S, T> SelectBuilder<'a, S, SelectLimitSet, T> {
565    /// Adds an OFFSET clause after LIMIT
566    pub fn offset(self, offset: usize) -> SelectBuilder<'a, S, SelectOffsetSet, T> {
567        SelectBuilder {
568            sql: self.sql.append(helpers::offset(offset)),
569            schema: PhantomData,
570            state: PhantomData,
571            table: PhantomData,
572        }
573    }
574}
575
576impl<'a, S, T> SelectBuilder<'a, S, SelectLimitSet, T>
577where
578    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
579{
580    /// Converts this SELECT query into a CTE (Common Table Expression) with the given name.
581    #[inline]
582    pub fn as_cte(
583        self,
584        name: &'static str,
585    ) -> super::CTEView<'a, <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased, Self>
586    {
587        super::CTEView::new(
588            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias(name),
589            name,
590            self,
591        )
592    }
593}
594
595impl<'a, S, T> SelectBuilder<'a, S, SelectOffsetSet, T>
596where
597    T: SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>,
598{
599    /// Converts this SELECT query into a CTE (Common Table Expression) with the given name.
600    #[inline]
601    pub fn as_cte(
602        self,
603        name: &'static str,
604    ) -> super::CTEView<'a, <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::Aliased, Self>
605    {
606        super::CTEView::new(
607            <T as SQLTable<'a, PostgresSchemaType, PostgresValue<'a>>>::alias(name),
608            name,
609            self,
610        )
611    }
612}
613
614//------------------------------------------------------------------------------
615// Set operation support (UNION / INTERSECT / EXCEPT)
616//------------------------------------------------------------------------------
617
618impl<'a, S, State, T> SelectBuilder<'a, S, State, T>
619where
620    State: ExecutableState,
621{
622    /// Combines this query with another using UNION.
623    pub fn union(
624        self,
625        other: impl ToSQL<'a, PostgresValue<'a>>,
626    ) -> SelectBuilder<'a, S, SelectSetOpSet, T> {
627        SelectBuilder {
628            sql: helpers::union(self.sql, other),
629            schema: PhantomData,
630            state: PhantomData,
631            table: PhantomData,
632        }
633    }
634
635    /// Combines this query with another using UNION ALL.
636    pub fn union_all(
637        self,
638        other: impl ToSQL<'a, PostgresValue<'a>>,
639    ) -> SelectBuilder<'a, S, SelectSetOpSet, T> {
640        SelectBuilder {
641            sql: helpers::union_all(self.sql, other),
642            schema: PhantomData,
643            state: PhantomData,
644            table: PhantomData,
645        }
646    }
647
648    /// Combines this query with another using INTERSECT.
649    pub fn intersect(
650        self,
651        other: impl ToSQL<'a, PostgresValue<'a>>,
652    ) -> SelectBuilder<'a, S, SelectSetOpSet, T> {
653        SelectBuilder {
654            sql: helpers::intersect(self.sql, other),
655            schema: PhantomData,
656            state: PhantomData,
657            table: PhantomData,
658        }
659    }
660
661    /// Combines this query with another using INTERSECT ALL.
662    pub fn intersect_all(
663        self,
664        other: impl ToSQL<'a, PostgresValue<'a>>,
665    ) -> SelectBuilder<'a, S, SelectSetOpSet, T> {
666        SelectBuilder {
667            sql: helpers::intersect_all(self.sql, other),
668            schema: PhantomData,
669            state: PhantomData,
670            table: PhantomData,
671        }
672    }
673
674    /// Combines this query with another using EXCEPT.
675    pub fn except(
676        self,
677        other: impl ToSQL<'a, PostgresValue<'a>>,
678    ) -> SelectBuilder<'a, S, SelectSetOpSet, T> {
679        SelectBuilder {
680            sql: helpers::except(self.sql, other),
681            schema: PhantomData,
682            state: PhantomData,
683            table: PhantomData,
684        }
685    }
686
687    /// Combines this query with another using EXCEPT ALL.
688    pub fn except_all(
689        self,
690        other: impl ToSQL<'a, PostgresValue<'a>>,
691    ) -> SelectBuilder<'a, S, SelectSetOpSet, T> {
692        SelectBuilder {
693            sql: helpers::except_all(self.sql, other),
694            schema: PhantomData,
695            state: PhantomData,
696            table: PhantomData,
697        }
698    }
699}
700
701impl<'a, S, T> SelectBuilder<'a, S, SelectSetOpSet, T> {
702    /// Sorts the results of a set operation.
703    pub fn order_by<TOrderBy>(
704        self,
705        expressions: TOrderBy,
706    ) -> SelectBuilder<'a, S, SelectOrderSet, T>
707    where
708        TOrderBy: ToSQL<'a, PostgresValue<'a>>,
709    {
710        SelectBuilder {
711            sql: self.sql.append(helpers::order_by(expressions)),
712            schema: PhantomData,
713            state: PhantomData,
714            table: PhantomData,
715        }
716    }
717
718    /// Limits the results of a set operation.
719    pub fn limit(self, limit: usize) -> SelectBuilder<'a, S, SelectLimitSet, T> {
720        SelectBuilder {
721            sql: self.sql.append(helpers::limit(limit)),
722            schema: PhantomData,
723            state: PhantomData,
724            table: PhantomData,
725        }
726    }
727
728    /// Offsets the results of a set operation.
729    pub fn offset(self, offset: usize) -> SelectBuilder<'a, S, SelectOffsetSet, T> {
730        SelectBuilder {
731            sql: self.sql.append(helpers::offset(offset)),
732            schema: PhantomData,
733            state: PhantomData,
734            table: PhantomData,
735        }
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::*;
742    use drizzle_core::{SQL, ToSQL};
743
744    #[test]
745    fn test_select_builder_creation() {
746        let builder = SelectBuilder::<(), SelectInitial> {
747            sql: SQL::raw("SELECT *"),
748            schema: PhantomData,
749            state: PhantomData,
750            table: PhantomData,
751        };
752
753        assert_eq!(builder.to_sql().sql(), "SELECT *");
754    }
755}