Skip to main content

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}