keelson_mysql/statement/mod.rs
1//! The statement types, each shaped by MySQL's own grammar: the five DML ones,
2//! and the `VALUES` and `TABLE` statements of MySQL 8.0.19+.
3//!
4//! Every one composes the shared clause structs as named fields, in the order
5//! *13.2 Data Manipulation Statements* lists them, and implements the `Has*`
6//! traits for the clauses it actually has. *Not* implementing one is how "this
7//! statement has no such clause" is said: `select::having(..)` will not compile
8//! against an `UpdateQuery`.
9//!
10//! # What MySQL does not have, and therefore is not here
11//!
12//! * **No `RETURNING`** on any of them. There is no
13//! [`Returning`](keelson_core::clause::Returning) field anywhere in this crate
14//! and no `returning` mod to apply to one.
15//! * **No `FETCH`**, so no [`HasFetch`](keelson_core::clause::HasFetch).
16//! * **No `WITH` on `INSERT` or `REPLACE`.** MySQL permits a `WITH` clause "at the
17//! beginning of `SELECT`, `UPDATE`, and `DELETE` statements" and, for
18//! `INSERT … SELECT`, only *immediately preceding the `SELECT`*
19//! (*15.2.20 WITH*). So `WITH c AS (…) INSERT …` is not MySQL; the CTE goes
20//! inside the sub-query handed to [`insert::query`](crate::insert::query).
21//! * **No `OFFSET` on `UPDATE` or `DELETE`** — their `LIMIT` takes a row count and
22//! nothing else.
23//!
24//! # Which table a mod means
25//!
26//! | statement | [`HasTableRef`](keelson_core::clause::HasTableRef) | [`HasTargetTable`] | [`HasExtraTables`] | [`HasDeleteTables`] |
27//! |---|---|---|---|---|
28//! | `SELECT` | first `FROM` item | — | further `FROM` items | — |
29//! | `INSERT`/`REPLACE` | `INTO` target | — | — | — |
30//! | `UPDATE` | — | the updated `table_references` | further ones | — |
31//! | `DELETE` | first `USING` item | — | further `USING` items | the `FROM` list |
32//!
33//! `UPDATE` is the row that differs from PostgreSQL: MySQL has no `UPDATE … FROM`,
34//! so there is only one table list and it *is* the target — joins and all. That is
35//! why `UpdateQuery` implements [`HasTargetTable`] and not `HasTableRef`, and why
36//! `update::inner_join` lands on the updated table rather than on a separate
37//! from-item.
38
39mod delete;
40mod insert;
41mod replace;
42mod select;
43mod table;
44mod update;
45mod values;
46
47pub use delete::DeleteQuery;
48pub use insert::InsertQuery;
49pub use replace::ReplaceQuery;
50pub use select::SelectQuery;
51pub use table::TableQuery;
52pub use update::UpdateQuery;
53pub use values::ValuesQuery;
54
55use std::borrow::Cow;
56
57use keelson_core::clause::TableRef;
58
59/// A statement whose table list is the thing being modified: the
60/// `table_references` an `UPDATE` writes to.
61///
62/// `SELECT`, `INSERT` and `REPLACE` have one table each and use
63/// [`HasTableRef`](keelson_core::clause::HasTableRef) for it, which is why
64/// `update::table(..)` cannot be applied to them.
65pub trait HasTargetTable {
66 /// The target table to modify.
67 fn target_table_mut(&mut self) -> &mut TableRef;
68}
69
70/// A statement whose table list may hold more than one entry.
71///
72/// MySQL's `table_references` is comma-separated, and a comma there means the same
73/// thing as `CROSS JOIN`. The first entry lives in `HasTableRef` or
74/// [`HasTargetTable`]; the rest are appended here.
75pub trait HasExtraTables {
76 /// The additional table references to modify.
77 fn extra_tables_mut(&mut self) -> &mut Vec<TableRef>;
78}
79
80/// A `DELETE`'s `FROM` list — the tables rows are actually removed from.
81///
82/// Separate from every other table trait because `DELETE` is the one statement
83/// where the tables being modified and the tables being *read* are two different
84/// lists:
85///
86/// ```text
87/// DELETE FROM t1, t2 USING t1 INNER JOIN t2 ON … WHERE …
88/// ```
89///
90/// The partition list comes with it, because `DELETE` is also the one statement
91/// that writes `PARTITION` *after* the alias (*15.2.2*):
92/// `DELETE FROM tbl [[AS] alias] [PARTITION (…)]`. Everywhere else it precedes
93/// the alias, which is where [`TableRef`] puts it — so the chain's partitions are
94/// moved out of the table reference and into this slot.
95pub trait HasDeleteTables {
96 /// The tables to delete from.
97 fn delete_tables_mut(&mut self) -> &mut Vec<TableRef>;
98
99 /// The partitions to restrict the delete to.
100 fn delete_partitions_mut(&mut self) -> &mut Vec<Cow<'static, str>>;
101}
102
103/// Write a keyword and its comma-separated table list, skipping absent entries.
104///
105/// An entry with no table renders nothing, so it must not contribute a comma
106/// either; and if the leading item is absent the whole clause goes, because
107/// `FROM , \`x\`` is not a repair of anything.
108///
109/// Two things must not go with it: joins and the extra items. Joins hang off
110/// the leading item, so with no item they have nowhere to attach; extra items
111/// are second and later entries of a list the leading item opens, so with no
112/// item there is no list to be in. Dropping either one the caller asked for
113/// would build *valid* SQL that silently means something else, which no
114/// grammar or engine can catch after the fact. That is recorded as
115/// [`Error::Incomplete`](keelson_core::Error::Incomplete) with `missing`
116/// naming the absent item. (`UPDATE` reaches neither guard: its absent target
117/// is already an `Incomplete` before this writer runs, so its `table_also`
118/// entries always have their leading `table_references` entry.)
119fn write_table_list(
120 w: &mut keelson_core::SqlWriter<'_>,
121 keyword: &str,
122 first: &TableRef,
123 rest: &[TableRef],
124 missing: &'static str,
125) {
126 if first.is_empty() {
127 if !first.joins.is_empty() || rest.iter().any(|t| !t.is_empty()) {
128 w.record_error(keelson_core::Error::Incomplete(missing));
129 }
130 return;
131 }
132 let items = std::iter::once(first)
133 .chain(rest.iter())
134 .filter(|t| !t.is_empty());
135 w.write_iter(items, keyword, ", ", "");
136}
137
138/// Write a statement's optimizer hints and modifiers, each followed by a space.
139///
140/// *10.9.2* puts the hint comment immediately after the statement's first
141/// keyword, before the modifiers: `SELECT /*+ … */ DISTINCT …`.
142fn write_hints_and_modifiers(
143 w: &mut keelson_core::SqlWriter<'_>,
144 hints: &crate::extras::Hints,
145 modifiers: &crate::extras::Modifiers,
146) {
147 w.write_if(!hints.is_empty(), "", hints, " ");
148 w.write_if(!modifiers.is_empty(), "", modifiers, " ");
149}