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}