Skip to main content

drizzle_postgres/
builder.rs

1use drizzle_core::Token;
2// Re-export common enums and traits from core
3pub use drizzle_core::builder::{BuilderInit, ExecutableState};
4pub use drizzle_core::{
5    OrderBy, SQL, ToSQL,
6    traits::{SQLSchema, SQLTable},
7};
8
9// Local imports
10use crate::{common::PostgresSchemaType, traits::PostgresTable, values::PostgresValue};
11use core::{fmt::Debug, marker::PhantomData};
12
13// Import modules - these provide specific builder types
14pub mod cte;
15pub mod delete;
16pub mod insert;
17pub mod prepared;
18pub mod refresh;
19pub mod select;
20pub mod update;
21
22// Re-export CTE types
23pub use cte::{CTEDefinition, CTEView};
24
25// Export state markers for easier use
26pub use delete::{DeleteInitial, DeleteReturningSet, DeleteWhereSet};
27pub use insert::{
28    InsertDoUpdateSet, InsertInitial, InsertOnConflictSet, InsertReturningSet, InsertValuesSet,
29    OnConflictBuilder,
30};
31pub use refresh::{
32    RefreshConcurrently, RefreshInitial, RefreshMaterializedView, RefreshWithNoData,
33    refresh_materialized_view,
34};
35pub use select::{
36    SelectForSet, SelectFromSet, SelectGroupSet, SelectInitial, SelectJoinSet, SelectLimitSet,
37    SelectOffsetSet, SelectOrderSet, SelectSetOpSet, SelectWhereSet,
38};
39pub use update::{
40    UpdateFromSet, UpdateInitial, UpdateReturningSet, UpdateSetClauseSet, UpdateWhereSet,
41};
42
43// Re-export SQLViewInfo for convenience when using refresh_materialized_view
44pub use drizzle_core::traits::SQLViewInfo;
45
46#[derive(Debug, Clone)]
47pub struct CTEInit;
48
49impl ExecutableState for CTEInit {}
50
51/// Main query builder for `PostgreSQL`
52///
53/// The `S` type parameter represents the schema type, which is used
54/// to ensure type safety when building queries.
55#[derive(Debug, Clone, Default)]
56pub struct QueryBuilder<
57    'a,
58    Schema = (),
59    State = (),
60    Table = (),
61    Marker = (),
62    Row = (),
63    Grouped = (),
64> {
65    pub sql: SQL<'a, PostgresValue<'a>>,
66    schema: PhantomData<Schema>,
67    state: PhantomData<State>,
68    table: PhantomData<Table>,
69    marker: PhantomData<Marker>,
70    row: PhantomData<Row>,
71    grouped: PhantomData<Grouped>,
72}
73
74//------------------------------------------------------------------------------
75// QueryBuilder Implementation
76//------------------------------------------------------------------------------
77
78impl<'a, Schema, State, Table, Marker, Row, Grouped> ToSQL<'a, PostgresValue<'a>>
79    for QueryBuilder<'a, Schema, State, Table, Marker, Row, Grouped>
80{
81    fn to_sql(&self) -> SQL<'a, PostgresValue<'a>> {
82        self.sql.clone()
83    }
84}
85
86impl<'a, Schema, State, Table, Marker, Row, Grouped>
87    QueryBuilder<'a, Schema, State, Table, Marker, Row, Grouped>
88where
89    State: ExecutableState,
90{
91    /// Attaches a [sqlcommenter](https://google.github.io/sqlcommenter/) comment
92    /// to the query.
93    ///
94    /// The comment is prepended to the generated SQL and wrapped in `/* ... */`.
95    /// Any `/*` or `*/` sequences in the input are sanitised so they can't
96    /// terminate the surrounding comment.
97    #[must_use]
98    pub fn comment(mut self, text: impl AsRef<str>) -> Self {
99        let fragment = drizzle_core::sql::comment::<PostgresValue<'a>>(text);
100        if fragment.chunks.is_empty() {
101            return self;
102        }
103        let existing = core::mem::replace(&mut self.sql, fragment);
104        self.sql.append_mut(existing);
105        self
106    }
107
108    /// Attaches a tag-style [sqlcommenter](https://google.github.io/sqlcommenter/)
109    /// comment to the query.
110    ///
111    /// Each `(key, value)` pair is URL-encoded, sorted alphabetically, joined
112    /// with `,`, and wrapped in `/* ... */`. Pairs with empty values are
113    /// skipped; an all-empty input is a no-op.
114    #[must_use]
115    pub fn comment_tags<I, K, V>(mut self, pairs: I) -> Self
116    where
117        I: IntoIterator<Item = (K, V)>,
118        K: AsRef<str>,
119        V: AsRef<str>,
120    {
121        let fragment = drizzle_core::sql::comment_tags::<PostgresValue<'a>, _, _, _>(pairs);
122        if fragment.chunks.is_empty() {
123            return self;
124        }
125        let existing = core::mem::replace(&mut self.sql, fragment);
126        self.sql.append_mut(existing);
127        self
128    }
129}
130
131impl<'a> QueryBuilder<'a> {
132    /// Creates a new query builder for the given schema
133    #[must_use]
134    pub const fn new<S>() -> QueryBuilder<'a, S, BuilderInit> {
135        QueryBuilder {
136            sql: SQL::empty(),
137            schema: PhantomData,
138            state: PhantomData,
139            table: PhantomData,
140            marker: PhantomData,
141            row: PhantomData,
142            grouped: PhantomData,
143        }
144    }
145}
146
147impl<'a, Schema> QueryBuilder<'a, Schema, BuilderInit> {
148    /// Begins a SELECT query with the specified columns.
149    ///
150    /// Pass individual columns, tuples of columns, or `()` to select all columns.
151    pub fn select<T>(
152        &self,
153        columns: T,
154    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
155    where
156        T: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
157    {
158        let sql = crate::helpers::select(columns);
159        select::SelectBuilder {
160            sql,
161            schema: PhantomData,
162            state: PhantomData,
163            table: PhantomData,
164            marker: PhantomData,
165            row: PhantomData,
166            grouped: PhantomData,
167        }
168    }
169
170    /// Begins a SELECT DISTINCT query with the specified columns.
171    ///
172    /// SELECT DISTINCT removes duplicate rows from the result set.
173    pub fn select_distinct<T>(
174        &self,
175        columns: T,
176    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
177    where
178        T: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
179    {
180        let sql = crate::helpers::select_distinct(columns);
181        select::SelectBuilder {
182            sql,
183            schema: PhantomData,
184            state: PhantomData,
185            table: PhantomData,
186            marker: PhantomData,
187            row: PhantomData,
188            grouped: PhantomData,
189        }
190    }
191
192    /// Begins a SELECT DISTINCT ON query (PostgreSQL-specific).
193    ///
194    /// Returns one row per distinct combination of the `on` columns.
195    /// Use with `order_by` to control which row is kept for each group.
196    pub fn select_distinct_on<On, Columns>(
197        &self,
198        on: On,
199        columns: Columns,
200    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), Columns::Marker>
201    where
202        On: ToSQL<'a, PostgresValue<'a>>,
203        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
204    {
205        let sql = crate::helpers::select_distinct_on(on, columns);
206        select::SelectBuilder {
207            sql,
208            schema: PhantomData,
209            state: PhantomData,
210            table: PhantomData,
211            marker: PhantomData,
212            row: PhantomData,
213            grouped: PhantomData,
214        }
215    }
216}
217
218impl<'a, Schema> QueryBuilder<'a, Schema, CTEInit> {
219    /// Begins a SELECT query with the specified columns after a CTE.
220    pub fn select<T>(
221        &self,
222        columns: T,
223    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
224    where
225        T: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
226    {
227        let sql = self.sql.clone().append(crate::helpers::select(columns));
228        select::SelectBuilder {
229            sql,
230            schema: PhantomData,
231            state: PhantomData,
232            table: PhantomData,
233            marker: PhantomData,
234            row: PhantomData,
235            grouped: PhantomData,
236        }
237    }
238
239    /// Begins a SELECT DISTINCT query with the specified columns after a CTE.
240    pub fn select_distinct<T>(
241        &self,
242        columns: T,
243    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
244    where
245        T: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
246    {
247        let sql = self
248            .sql
249            .clone()
250            .append(crate::helpers::select_distinct(columns));
251        select::SelectBuilder {
252            sql,
253            schema: PhantomData,
254            state: PhantomData,
255            table: PhantomData,
256            marker: PhantomData,
257            row: PhantomData,
258            grouped: PhantomData,
259        }
260    }
261
262    /// Begins a SELECT DISTINCT ON query with the specified columns after a CTE.
263    pub fn select_distinct_on<On, Columns>(
264        &self,
265        on: On,
266        columns: Columns,
267    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), Columns::Marker>
268    where
269        On: ToSQL<'a, PostgresValue<'a>>,
270        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
271    {
272        let sql = self
273            .sql
274            .clone()
275            .append(crate::helpers::select_distinct_on(on, columns));
276        select::SelectBuilder {
277            sql,
278            schema: PhantomData,
279            state: PhantomData,
280            table: PhantomData,
281            marker: PhantomData,
282            row: PhantomData,
283            grouped: PhantomData,
284        }
285    }
286
287    /// Begins an INSERT query after a CTE.
288    pub fn insert<Table>(
289        &self,
290        table: Table,
291    ) -> insert::InsertBuilder<'a, Schema, insert::InsertInitial, Table>
292    where
293        Table: PostgresTable<'a>,
294    {
295        let sql = self
296            .sql
297            .clone()
298            .append(crate::helpers::insert::<Table>(&table));
299
300        insert::InsertBuilder {
301            sql,
302            schema: PhantomData,
303            state: PhantomData,
304            table: PhantomData,
305            marker: PhantomData,
306            row: PhantomData,
307            grouped: PhantomData,
308        }
309    }
310
311    /// Begins an UPDATE query after a CTE.
312    pub fn update<Table>(
313        &self,
314        table: Table,
315    ) -> update::UpdateBuilder<'a, Schema, update::UpdateInitial, Table>
316    where
317        Table: PostgresTable<'a>,
318    {
319        let sql = self.sql.clone().append(crate::helpers::update::<
320            Table,
321            PostgresSchemaType,
322            PostgresValue<'a>,
323        >(&table));
324
325        update::UpdateBuilder {
326            sql,
327            schema: PhantomData,
328            state: PhantomData,
329            table: PhantomData,
330            marker: PhantomData,
331            row: PhantomData,
332            grouped: PhantomData,
333        }
334    }
335
336    /// Begins a DELETE query after a CTE.
337    pub fn delete<Table>(
338        &self,
339        table: Table,
340    ) -> delete::DeleteBuilder<'a, Schema, delete::DeleteInitial, Table>
341    where
342        Table: PostgresTable<'a>,
343    {
344        let sql = self.sql.clone().append(crate::helpers::delete::<
345            Table,
346            PostgresSchemaType,
347            PostgresValue<'a>,
348        >(&table));
349
350        delete::DeleteBuilder {
351            sql,
352            schema: PhantomData,
353            state: PhantomData,
354            table: PhantomData,
355            marker: PhantomData,
356            row: PhantomData,
357            grouped: PhantomData,
358        }
359    }
360
361    #[must_use]
362    pub fn with<C>(&self, cte: &C) -> Self
363    where
364        C: CTEDefinition<'a>,
365    {
366        let sql = self
367            .sql
368            .clone()
369            .push(Token::COMMA)
370            .append(cte.cte_definition());
371        QueryBuilder {
372            sql,
373            schema: PhantomData,
374            state: PhantomData,
375            table: PhantomData,
376            marker: PhantomData,
377            row: PhantomData,
378            grouped: PhantomData,
379        }
380    }
381}
382
383impl<'a, Schema> QueryBuilder<'a, Schema, BuilderInit> {
384    /// Begins an INSERT query for the specified table.
385    pub fn insert<Table>(
386        &self,
387        table: Table,
388    ) -> insert::InsertBuilder<'a, Schema, insert::InsertInitial, Table>
389    where
390        Table: PostgresTable<'a>,
391    {
392        let sql = crate::helpers::insert::<Table>(&table);
393
394        insert::InsertBuilder {
395            sql,
396            schema: PhantomData,
397            state: PhantomData,
398            table: PhantomData,
399            marker: PhantomData,
400            row: PhantomData,
401            grouped: PhantomData,
402        }
403    }
404
405    /// Begins an UPDATE query for the specified table.
406    pub fn update<Table>(
407        &self,
408        table: Table,
409    ) -> update::UpdateBuilder<'a, Schema, update::UpdateInitial, Table>
410    where
411        Table: PostgresTable<'a>,
412    {
413        let sql = crate::helpers::update::<Table, PostgresSchemaType, PostgresValue<'a>>(&table);
414
415        update::UpdateBuilder {
416            sql,
417            schema: PhantomData,
418            state: PhantomData,
419            table: PhantomData,
420            marker: PhantomData,
421            row: PhantomData,
422            grouped: PhantomData,
423        }
424    }
425
426    /// Begins a DELETE query for the specified table.
427    pub fn delete<Table>(
428        &self,
429        table: Table,
430    ) -> delete::DeleteBuilder<'a, Schema, delete::DeleteInitial, Table>
431    where
432        Table: PostgresTable<'a>,
433    {
434        let sql = crate::helpers::delete::<Table, PostgresSchemaType, PostgresValue<'a>>(&table);
435
436        delete::DeleteBuilder {
437            sql,
438            schema: PhantomData,
439            state: PhantomData,
440            table: PhantomData,
441            marker: PhantomData,
442            row: PhantomData,
443            grouped: PhantomData,
444        }
445    }
446
447    /// Starts a WITH (CTE) clause. Chain additional `.with()` calls to add more CTEs.
448    pub fn with<C>(&self, cte: &C) -> QueryBuilder<'a, Schema, CTEInit>
449    where
450        C: CTEDefinition<'a>,
451    {
452        let sql = SQL::from(Token::WITH).append(cte.cte_definition());
453        QueryBuilder {
454            sql,
455            schema: PhantomData,
456            state: PhantomData,
457            table: PhantomData,
458            marker: PhantomData,
459            row: PhantomData,
460            grouped: PhantomData,
461        }
462    }
463}
464
465// Marker trait to indicate a query builder state is executable
466#[cfg(test)]
467mod tests {
468    use super::*;
469
470    #[test]
471    fn test_query_builder_new() {
472        let qb = QueryBuilder::new::<()>();
473        let sql = qb.to_sql();
474        assert_eq!(sql.sql(), "");
475        assert_eq!(sql.params().count(), 0);
476    }
477
478    #[test]
479    fn test_builder_init_type() {
480        let _state = BuilderInit;
481    }
482}