Skip to main content

drizzle_mysql/builder/
mod.rs

1//! Typed `MySQL` query builder.
2//!
3//! Start with [`QueryBuilder::new`]. Each statement has its own builder
4//! alias with its clause order: [`SelectBuilder`], [`InsertBuilder`],
5//! [`UpdateBuilder`] and [`DeleteBuilder`].
6//!
7//! Examples in this module use the `drizzle` crate and its macros, which
8//! this crate cannot depend on, so they are shown but not compiled here.
9//! The workspace's `tests/mysql/builder.rs` compiles and checks the same
10//! kinds of query.
11
12use crate::{common::MySQLSchemaType, traits::MySQLTable, values::MySQLValue};
13use core::{fmt::Debug, marker::PhantomData};
14use drizzle_core::{SQL, ToSQL, Token};
15
16pub use drizzle_core::{BuilderInit, ExecutableState};
17
18macro_rules! mutation_builder_methods {
19    (
20        $builder:ident,
21        prepare: [$($prepare:ty),+ $(,)?],
22        order_by: [$($order:ty),+ $(,)?] => $ordered:ty,
23        limit: [$($limit:ty),+ $(,)?] => $limited:ty $(,)?
24    ) => {
25        $(
26            impl<'a, S, T, M, R> $builder<'a, S, $prepare, T, M, R> {
27                /// Renders this statement as a [`PreparedStatement`](drizzle_core::prepared::PreparedStatement)
28                /// whose placeholders are bound in `MySQL`'s positional order.
29                #[must_use]
30                pub fn prepare(
31                    &self,
32                ) -> drizzle_core::prepared::PreparedStatement<'a, crate::values::MySQLValue<'a>> {
33                    self.prepared_statement()
34                }
35            }
36        )+
37
38        $(
39            impl<'a, S, T> $builder<'a, S, $order, T> {
40                /// Adds ORDER BY, which sets the order rows are changed in.
41                ///
42                /// Mostly useful with `limit`. Only columns of the target
43                /// table may be used.
44                pub fn order_by<O, ScopeProof>(self, order: O) -> $builder<'a, S, $ordered, T>
45                where
46                    O: drizzle_core::ToSQL<'a, crate::values::MySQLValue<'a>>
47                        + drizzle_core::expr::ExprSources,
48                    O::Sources: drizzle_core::scope::SourcesIn<
49                        drizzle_core::Cons<T, drizzle_core::Nil>,
50                        ScopeProof,
51                    >,
52                {
53                    $builder::from_sql(self.sql.append(crate::helpers::order_by(order)))
54                }
55            }
56        )+
57
58        $(
59            impl<'a, S, T> $builder<'a, S, $limit, T> {
60                /// Adds LIMIT, which caps how many rows are changed.
61                ///
62                /// # Panics
63                ///
64                /// Panics when a numeric argument is negative or does not
65                /// fit in `usize`.
66                #[track_caller]
67                pub fn limit<P>(self, limit: P) -> $builder<'a, S, $limited, T>
68                where
69                    P: drizzle_core::PaginationArg<'a, crate::values::MySQLValue<'a>>,
70                {
71                    $builder::from_sql(self.sql.append(crate::helpers::limit(limit)))
72                }
73            }
74        )+
75    };
76}
77
78pub mod cte;
79/// Typed MySQL `DELETE` statements.
80pub mod delete;
81/// Typed MySQL `INSERT` statements.
82pub mod insert;
83/// Typed MySQL `SELECT` statements.
84pub mod select;
85/// Typed MySQL `UPDATE` statements.
86pub mod update;
87
88pub use cte::{CTEDefinition, CTEView};
89pub use delete::{DeleteBuilder, DeleteInitial, DeleteLimitSet, DeleteOrderSet, DeleteWhereSet};
90pub use insert::{
91    InsertBuilder, InsertColumnsSet, InsertIgnoreSet, InsertInitial, InsertOnDuplicateKeyUpdateSet,
92    InsertValuesSet,
93};
94pub use select::{
95    CompletedSelect, ForShare, ForUpdate, IntoSelectQuery, NoWait, SelectBuilder, SelectForSet,
96    SelectFromSet, SelectGroupSet, SelectHavingSet, SelectIndexHintSet, SelectInitial,
97    SelectJoinSet, SelectLimitSet, SelectOffsetSet, SelectOrderSet, SelectSetOpSet, SelectWhereSet,
98    SkipLocked, Wait,
99};
100pub use update::{
101    UpdateBuilder, UpdateInitial, UpdateLimitSet, UpdateOrderSet, UpdateSetClauseSet,
102    UpdateWhereSet,
103};
104
105/// Builder state after [`QueryBuilder::with`]: the next call starts the
106/// statement that uses the common table expressions.
107#[derive(Debug, Clone)]
108pub struct CTEInit;
109
110/// Type-safe SQL query builder for `MySQL`.
111///
112/// Start with [`QueryBuilder::new`], then call [`select`](Self::select),
113/// [`insert`](Self::insert), [`update`](Self::update),
114/// [`delete`](Self::delete) or [`with`](Self::with). Each method returns a
115/// builder in a new state that only offers the clauses that may come next,
116/// so an out-of-order query does not compile. Call [`ToSQL::to_sql`] for the
117/// SQL text and parameters, or `prepare()` for a statement with
118/// placeholders. It does not run queries; the `drizzle` crate's `MySQL`
119/// drivers do.
120///
121/// # Type parameters
122///
123/// - `Schema`: the schema the query runs against.
124/// - `State`: which clauses have been added so far.
125/// - `Table`: the table the last FROM or JOIN added, or the target table of
126///   an INSERT, UPDATE or DELETE.
127/// - `Marker`: the selected columns and the tables in scope.
128/// - `Row`: the Rust type of one result row.
129/// - `Grouped`: the GROUP BY columns.
130///
131/// # Examples
132///
133/// ```rust
134/// # let _ = r####"
135/// use drizzle::core::expr::eq;
136/// use drizzle::mysql::{builder::QueryBuilder, prelude::*};
137///
138/// #[MySQLTable(NAME = "users")]
139/// struct Users {
140///     #[column(PRIMARY, AUTO_INCREMENT)]
141///     id: u64,
142///     #[column(VARCHAR(255))]
143///     name: String,
144///     #[column(DEFAULT = true)]
145///     active: bool,
146/// }
147///
148/// #[derive(MySQLSchema)]
149/// struct Schema {
150///     users: Users,
151/// }
152///
153/// let builder = QueryBuilder::new::<Schema>();
154/// let Schema { users } = Schema::new();
155/// let query = builder
156///     .select(users.name)
157///     .from(users)
158///     .r#where(eq(users.active, true))
159///     .order_by(asc(users.name))
160///     .limit(10);
161/// assert_eq!(
162///     query.to_sql().sql(),
163///     "SELECT `users`.`name` FROM `users` WHERE `users`.`active` = ? ORDER BY `users`.`name` ASC LIMIT ?"
164/// );
165/// # "####;
166/// ```
167#[derive(Debug, Clone)]
168pub struct QueryBuilder<
169    'a,
170    Schema = (),
171    State = (),
172    Table = (),
173    Marker = (),
174    Row = (),
175    Grouped = (),
176> {
177    pub(crate) sql: SQL<'a, MySQLValue<'a>>,
178    pub(crate) schema: PhantomData<Schema>,
179    pub(crate) state: PhantomData<State>,
180    pub(crate) table: PhantomData<Table>,
181    pub(crate) marker: PhantomData<Marker>,
182    pub(crate) row: PhantomData<Row>,
183    pub(crate) grouped: PhantomData<Grouped>,
184}
185
186impl<'a, Schema, State, Table, Marker, Row, Grouped> ToSQL<'a, MySQLValue<'a>>
187    for QueryBuilder<'a, Schema, State, Table, Marker, Row, Grouped>
188where
189    State: ExecutableState,
190{
191    fn to_sql(&self) -> SQL<'a, MySQLValue<'a>> {
192        self.sql.clone()
193    }
194
195    fn into_sql(self) -> SQL<'a, MySQLValue<'a>> {
196        self.sql
197    }
198}
199
200impl<'a, Schema, State, Table, Marker, Row, Grouped>
201    QueryBuilder<'a, Schema, State, Table, Marker, Row, Grouped>
202where
203    State: ExecutableState,
204{
205    /// Prepends a [sqlcommenter](https://google.github.io/sqlcommenter/)
206    /// comment (`/*...*/`) to the query.
207    ///
208    /// `/*` and `*/` inside `text` are broken up (`/ *`, `* /`) so the text
209    /// cannot end the comment early. An empty `text` leaves the query
210    /// unchanged.
211    #[must_use]
212    pub fn comment(mut self, text: impl AsRef<str>) -> Self {
213        let fragment = drizzle_core::sql::comment::<MySQLValue<'a>>(text);
214        if !fragment.chunks.is_empty() {
215            let existing = core::mem::replace(&mut self.sql, fragment);
216            self.sql.append_mut(existing);
217        }
218        self
219    }
220
221    /// Prepends a tag-style [sqlcommenter](https://google.github.io/sqlcommenter/)
222    /// comment to the query.
223    ///
224    /// Each `(key, value)` pair is URL-encoded and written as `key='value'`.
225    /// Pairs are sorted and joined with `,`. Pairs with an empty value are
226    /// skipped; if none remain, the query is unchanged.
227    #[must_use]
228    pub fn comment_tags<I, K, V>(mut self, pairs: I) -> Self
229    where
230        I: IntoIterator<Item = (K, V)>,
231        K: AsRef<str>,
232        V: AsRef<str>,
233    {
234        let fragment = drizzle_core::sql::comment_tags::<MySQLValue<'a>, _, _, _>(pairs);
235        if !fragment.chunks.is_empty() {
236            let existing = core::mem::replace(&mut self.sql, fragment);
237            self.sql.append_mut(existing);
238        }
239        self
240    }
241}
242
243impl<'a> QueryBuilder<'a> {
244    /// Creates a query builder for the schema `S`.
245    #[must_use]
246    pub const fn new<S>() -> QueryBuilder<'a, S, BuilderInit> {
247        QueryBuilder {
248            sql: SQL::empty(),
249            schema: PhantomData,
250            state: PhantomData,
251            table: PhantomData,
252            marker: PhantomData,
253            row: PhantomData,
254            grouped: PhantomData,
255        }
256    }
257}
258
259impl<'a, Schema> QueryBuilder<'a, Schema, BuilderInit> {
260    /// Starts a `SELECT` with the given columns.
261    ///
262    /// Pass one column or expression, a tuple of them, or `()` to select
263    /// every column of the FROM table (and of joined tables). Call
264    /// [`from`](select::SelectBuilder::from) next.
265    ///
266    /// # Examples
267    ///
268    /// ```rust
269    /// # let _ = r####"
270    /// # use drizzle::core::expr::eq;
271    /// # use drizzle::mysql::{builder::QueryBuilder, prelude::*};
272    /// #
273    /// # #[MySQLTable(NAME = "users")]
274    /// # struct Users {
275    /// #     #[column(PRIMARY, AUTO_INCREMENT)]
276    /// #     id: u64,
277    /// #     #[column(VARCHAR(255))]
278    /// #     name: String,
279    /// #     #[column(DEFAULT = true)]
280    /// #     active: bool,
281    /// # }
282    /// #
283    /// # #[derive(MySQLSchema)]
284    /// # struct Schema {
285    /// #     users: Users,
286    /// # }
287    /// #
288    /// # let builder = QueryBuilder::new::<Schema>();
289    /// # let Schema { users } = Schema::new();
290    /// let query = builder.select(()).from(users);
291    /// assert_eq!(
292    ///     query.to_sql().sql(),
293    ///     "SELECT `users`.`id`, `users`.`name`, `users`.`active` FROM `users`"
294    /// );
295    /// # "####;
296    /// ```
297    pub fn select<T>(
298        &self,
299        columns: T,
300    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
301    where
302        T: ToSQL<'a, MySQLValue<'a>> + drizzle_core::IntoSelectTarget,
303    {
304        QueryBuilder::from_sql(crate::helpers::select(columns))
305    }
306
307    /// Starts a `SELECT DISTINCT`, which drops duplicate rows.
308    pub fn select_distinct<T>(
309        &self,
310        columns: T,
311    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
312    where
313        T: ToSQL<'a, MySQLValue<'a>> + drizzle_core::IntoSelectTarget,
314    {
315        QueryBuilder::from_sql(crate::helpers::select_distinct(columns))
316    }
317
318    /// Starts an `INSERT` into `table`.
319    ///
320    /// Call [`values`](insert::InsertBuilder::values),
321    /// [`select`](insert::InsertBuilder::select),
322    /// [`columns`](insert::InsertBuilder::columns) or
323    /// [`ignore`](insert::InsertBuilder::ignore) next.
324    pub fn insert<Table>(
325        &self,
326        table: Table,
327    ) -> insert::InsertBuilder<'a, Schema, insert::InsertInitial, Table>
328    where
329        Table: MySQLTable<'a>,
330    {
331        QueryBuilder::from_sql(drizzle_core::helpers::insert::<
332            Table,
333            MySQLSchemaType,
334            MySQLValue<'a>,
335        >(&table))
336    }
337
338    /// Starts an `UPDATE` of `table`. Call
339    /// [`set`](update::UpdateBuilder::set) next.
340    pub fn update<Table>(
341        &self,
342        table: Table,
343    ) -> update::UpdateBuilder<'a, Schema, update::UpdateInitial, Table>
344    where
345        Table: MySQLTable<'a>,
346    {
347        QueryBuilder::from_sql(crate::helpers::update::<
348            Table,
349            MySQLSchemaType,
350            MySQLValue<'a>,
351        >(&table))
352    }
353
354    /// Starts a `DELETE` from `table`.
355    ///
356    /// Without `where`, the statement deletes every row.
357    pub fn delete<Table>(
358        &self,
359        table: Table,
360    ) -> delete::DeleteBuilder<'a, Schema, delete::DeleteInitial, Table>
361    where
362        Table: MySQLTable<'a>,
363    {
364        QueryBuilder::from_sql(crate::helpers::delete::<
365            Table,
366            MySQLSchemaType,
367            MySQLValue<'a>,
368        >(&table))
369    }
370
371    /// Starts a WITH clause with one common table expression.
372    ///
373    /// Build the CTE with [`into_cte`](select::SelectBuilder::into_cte). Add
374    /// more with another `.with(..)`, then start a SELECT, UPDATE or DELETE.
375    /// For `INSERT ... SELECT`, put the WITH on the inner SELECT instead.
376    ///
377    /// # Examples
378    ///
379    /// ```rust
380    /// # let _ = r####"
381    /// # use drizzle::core::expr::eq;
382    /// # use drizzle::mysql::{builder::QueryBuilder, prelude::*};
383    /// #
384    /// # #[MySQLTable(NAME = "users")]
385    /// # struct Users {
386    /// #     #[column(PRIMARY, AUTO_INCREMENT)]
387    /// #     id: u64,
388    /// #     #[column(VARCHAR(255))]
389    /// #     name: String,
390    /// #     #[column(DEFAULT = true)]
391    /// #     active: bool,
392    /// # }
393    /// #
394    /// # #[derive(MySQLSchema)]
395    /// # struct Schema {
396    /// #     users: Users,
397    /// # }
398    /// #
399    /// # let builder = QueryBuilder::new::<Schema>();
400    /// # let Schema { users } = Schema::new();
401    /// struct Active;
402    /// impl drizzle::core::Tag for Active {
403    ///     const NAME: &'static str = "active";
404    /// }
405    ///
406    /// let active = builder
407    ///     .select((users.id, users.name))
408    ///     .from(users)
409    ///     .r#where(eq(users.active, true))
410    ///     .into_cte::<Active>();
411    /// let query = builder.with(&active).select(active.name).from(&active);
412    /// // WITH `active` AS (SELECT ...) SELECT `active`.`name` FROM `active`
413    /// # "####;
414    /// ```
415    pub fn with<C>(&self, cte: &C) -> QueryBuilder<'a, Schema, CTEInit>
416    where
417        C: CTEDefinition<'a>,
418    {
419        QueryBuilder::from_sql(SQL::from(Token::WITH).append(cte.cte_definition()))
420    }
421}
422
423impl<'a, Schema> QueryBuilder<'a, Schema, CTEInit> {
424    /// Starts a `SELECT` after the WITH clause.
425    pub fn select<T>(
426        &self,
427        columns: T,
428    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
429    where
430        T: ToSQL<'a, MySQLValue<'a>> + drizzle_core::IntoSelectTarget,
431    {
432        QueryBuilder::from_sql(self.sql.clone().append(crate::helpers::select(columns)))
433    }
434
435    /// Starts a `SELECT DISTINCT` after the WITH clause.
436    pub fn select_distinct<T>(
437        &self,
438        columns: T,
439    ) -> select::SelectBuilder<'a, Schema, select::SelectInitial, (), T::Marker>
440    where
441        T: ToSQL<'a, MySQLValue<'a>> + drizzle_core::IntoSelectTarget,
442    {
443        QueryBuilder::from_sql(
444            self.sql
445                .clone()
446                .append(crate::helpers::select_distinct(columns)),
447        )
448    }
449
450    /// Starts an `UPDATE` after the WITH clause.
451    pub fn update<Table>(
452        &self,
453        table: Table,
454    ) -> update::UpdateBuilder<'a, Schema, update::UpdateInitial, Table>
455    where
456        Table: MySQLTable<'a>,
457    {
458        QueryBuilder::from_sql(self.sql.clone().append(crate::helpers::update::<
459            Table,
460            MySQLSchemaType,
461            MySQLValue<'a>,
462        >(&table)))
463    }
464
465    /// Starts a `DELETE` after the WITH clause.
466    pub fn delete<Table>(
467        &self,
468        table: Table,
469    ) -> delete::DeleteBuilder<'a, Schema, delete::DeleteInitial, Table>
470    where
471        Table: MySQLTable<'a>,
472    {
473        QueryBuilder::from_sql(self.sql.clone().append(crate::helpers::delete::<
474            Table,
475            MySQLSchemaType,
476            MySQLValue<'a>,
477        >(&table)))
478    }
479
480    #[must_use]
481    /// Adds another common table expression to the WITH clause.
482    pub fn with<C>(&self, cte: &C) -> Self
483    where
484        C: CTEDefinition<'a>,
485    {
486        QueryBuilder::from_sql(
487            self.sql
488                .clone()
489                .push(Token::COMMA)
490                .append(cte.cte_definition()),
491        )
492    }
493}
494
495impl<'a, S, State, T, M, R, G> QueryBuilder<'a, S, State, T, M, R, G> {
496    pub(crate) fn from_sql(sql: SQL<'a, MySQLValue<'a>>) -> Self {
497        Self {
498            sql,
499            schema: PhantomData,
500            state: PhantomData,
501            table: PhantomData,
502            marker: PhantomData,
503            row: PhantomData,
504            grouped: PhantomData,
505        }
506    }
507
508    pub(crate) fn prepared_statement(
509        &self,
510    ) -> drizzle_core::prepared::PreparedStatement<'a, MySQLValue<'a>> {
511        drizzle_core::prepared::prepare_render(&self.sql)
512    }
513}