drizzle_postgres/
builder.rs

1use drizzle_core::Token;
2// Re-export common enums and traits from core
3pub use drizzle_core::{
4    OrderBy, SQL, ToSQL,
5    traits::{SQLSchema, SQLTable},
6};
7
8// Local imports
9use crate::{
10    ToPostgresSQL, common::PostgresSchemaType, traits::PostgresTable, values::PostgresValue,
11};
12use std::{fmt::Debug, marker::PhantomData};
13
14// Import modules - these provide specific builder types
15pub mod cte;
16pub mod delete;
17pub mod insert;
18pub mod prepared;
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    Conflict, InsertInitial, InsertOnConflictSet, InsertReturningSet, InsertValuesSet,
29};
30pub use select::{
31    SelectFromSet, SelectGroupSet, SelectInitial, SelectJoinSet, SelectLimitSet, SelectOffsetSet,
32    SelectOrderSet, SelectWhereSet,
33};
34pub use update::{UpdateInitial, UpdateReturningSet, UpdateSetClauseSet, UpdateWhereSet};
35
36//------------------------------------------------------------------------------
37// Common SQL Components
38//------------------------------------------------------------------------------
39
40/// Represents an ORDER BY clause in a query
41#[derive(Debug, Clone)]
42pub struct OrderByClause<'a> {
43    /// The expression to order by
44    pub expr: SQL<'a, PostgresValue<'a>>,
45    /// The direction to sort (ASC or DESC)
46    pub direction: OrderBy,
47}
48
49impl<'a> OrderByClause<'a> {
50    /// Creates a new ORDER BY clause
51    pub const fn new(expr: SQL<'a, PostgresValue<'a>>, direction: OrderBy) -> Self {
52        Self { expr, direction }
53    }
54}
55
56pub trait BuilderState {}
57
58#[derive(Debug, Clone)]
59pub struct BuilderInit;
60
61#[derive(Debug, Clone)]
62pub struct CTEInit;
63
64impl BuilderState for BuilderInit {}
65impl ExecutableState for BuilderInit {}
66
67impl ExecutableState for CTEInit {}
68
69/// Main query builder for PostgreSQL
70///
71/// The `S` type parameter represents the schema type, which is used
72/// to ensure type safety when building queries.
73#[derive(Debug, Clone, Default)]
74pub struct QueryBuilder<'a, Schema = (), State = (), Table = ()> {
75    pub sql: SQL<'a, PostgresValue<'a>>,
76    schema: PhantomData<Schema>,
77    state: PhantomData<State>,
78    table: PhantomData<Table>,
79}
80
81//------------------------------------------------------------------------------
82// QueryBuilder Implementation
83//------------------------------------------------------------------------------
84
85impl<'a, Schema, State, Table> ToSQL<'a, PostgresValue<'a>>
86    for QueryBuilder<'a, Schema, State, Table>
87{
88    fn to_sql(&self) -> SQL<'a, PostgresValue<'a>> {
89        self.sql.clone()
90    }
91}
92
93impl<'a> QueryBuilder<'a> {
94    /// Creates a new query builder for the given schema
95    pub const fn new<S>() -> QueryBuilder<'a, S, BuilderInit> {
96        QueryBuilder {
97            sql: SQL::empty(),
98            schema: PhantomData,
99            state: PhantomData,
100            table: PhantomData,
101        }
102    }
103}
104
105impl<'a, Schema, State> QueryBuilder<'a, Schema, State>
106where
107    State: BuilderState,
108{
109    pub fn select<T>(&self, columns: T) -> select::SelectBuilder<'a, Schema, select::SelectInitial>
110    where
111        T: ToPostgresSQL<'a>,
112    {
113        let sql = crate::helpers::select(columns);
114        select::SelectBuilder {
115            sql,
116            schema: PhantomData,
117            state: PhantomData,
118            table: PhantomData,
119        }
120    }
121}
122
123impl<'a, Schema> QueryBuilder<'a, Schema, CTEInit> {
124    pub fn select<T>(&self, columns: T) -> select::SelectBuilder<'a, Schema, select::SelectInitial>
125    where
126        T: ToPostgresSQL<'a>,
127    {
128        let sql = self.sql.clone().append(crate::helpers::select(columns));
129        select::SelectBuilder {
130            sql,
131            schema: PhantomData,
132            state: PhantomData,
133            table: PhantomData,
134        }
135    }
136
137    pub fn with<C>(&self, cte: C) -> QueryBuilder<'a, Schema, CTEInit>
138    where
139        C: CTEDefinition<'a>,
140    {
141        let sql = self
142            .sql
143            .clone()
144            .push(Token::COMMA)
145            .append(cte.cte_definition());
146        QueryBuilder {
147            sql,
148            schema: PhantomData,
149            state: PhantomData,
150            table: PhantomData,
151        }
152    }
153}
154
155impl<'a, Schema, State> QueryBuilder<'a, Schema, State>
156where
157    State: BuilderState,
158{
159    pub fn insert<Table>(
160        &self,
161        table: Table,
162    ) -> insert::InsertBuilder<'a, Schema, insert::InsertInitial, Table>
163    where
164        Table: PostgresTable<'a>,
165    {
166        let sql = crate::helpers::insert(table);
167
168        insert::InsertBuilder {
169            sql,
170            schema: PhantomData,
171            state: PhantomData,
172            table: PhantomData,
173        }
174    }
175
176    pub fn update<Table>(
177        &self,
178        table: Table,
179    ) -> update::UpdateBuilder<'a, Schema, update::UpdateInitial, Table>
180    where
181        Table: PostgresTable<'a>,
182    {
183        let sql = crate::helpers::update::<'a, Table, PostgresSchemaType, PostgresValue<'a>>(table);
184
185        update::UpdateBuilder {
186            sql,
187            schema: PhantomData,
188            state: PhantomData,
189            table: PhantomData,
190        }
191    }
192
193    pub fn delete<Table>(
194        &self,
195        table: Table,
196    ) -> delete::DeleteBuilder<'a, Schema, delete::DeleteInitial, Table>
197    where
198        Table: PostgresTable<'a>,
199    {
200        let sql = crate::helpers::delete::<'a, Table, PostgresSchemaType, PostgresValue<'a>>(table);
201
202        delete::DeleteBuilder {
203            sql,
204            schema: PhantomData,
205            state: PhantomData,
206            table: PhantomData,
207        }
208    }
209
210    pub fn with<C>(&self, cte: C) -> QueryBuilder<'a, Schema, CTEInit>
211    where
212        C: CTEDefinition<'a>,
213    {
214        let sql = SQL::from(Token::WITH).append(cte.cte_definition());
215        QueryBuilder {
216            sql,
217            schema: PhantomData,
218            state: PhantomData,
219            table: PhantomData,
220        }
221    }
222}
223
224// Marker trait to indicate a query builder state is executable
225pub trait ExecutableState {}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_query_builder_new() {
233        let qb = QueryBuilder::new::<()>();
234        let sql = qb.to_sql();
235        assert_eq!(sql.sql(), "");
236        assert_eq!(sql.params().len(), 0);
237    }
238
239    #[test]
240    fn test_builder_state_trait() {
241        // Test that different states implement BuilderState
242        fn assert_builder_state<T: BuilderState>() {}
243
244        assert_builder_state::<BuilderInit>();
245    }
246}