Skip to main content

keelson_mysql/statement/
update.rs

1use keelson_core::clause::{
2    HasJoins, HasLimit, HasOrderBy, HasSet, HasWhere, HasWith, Join, Limit, OrderBy, Set, TableRef,
3    Where, With,
4};
5use keelson_core::expr::{Expr, IntoExpr, IntoExprList};
6use keelson_core::{Dialect, Error, Expression, Mod, Query, QueryExtensions, QueryType, SqlWriter};
7
8use super::{HasExtraTables, HasTargetTable, write_hints_and_modifiers, write_table_list};
9use crate::Mysql;
10use crate::extras::{HasHints, HasModifiers, Hints, Modifiers};
11
12/// A MySQL `UPDATE`.
13///
14/// From <https://dev.mysql.com/doc/refman/8.4/en/update.html>, with the
15/// single-table and multiple-table forms folded together:
16///
17/// ```text
18/// [WITH [RECURSIVE] with_query [, ...]]
19/// UPDATE [hint_comment] [LOW_PRIORITY] [IGNORE] table_references
20///     SET assignment_list
21///     [WHERE where_condition]
22///     [ORDER BY ...]
23///     [LIMIT row_count]
24/// ```
25///
26/// **There is no `UPDATE … FROM`.** MySQL's target *is* a `table_references`, so a
27/// join or a comma joins tables that are all updatable, and `SET` may assign to any
28/// of them. That is the one structural difference from PostgreSQL, and it is why
29/// this type implements [`HasTargetTable`] rather than
30/// [`HasTableRef`](keelson_core::clause::HasTableRef) — `update::inner_join(..)`
31/// lands on the updated table list, because there is nowhere else for it to go.
32///
33/// `ORDER BY` and `LIMIT` belong to the single-table form only; MySQL rejects them
34/// once more than one table is named. Nothing here prevents it — the shape is
35/// legal and the server is what says no.
36#[derive(Debug, Clone, Default)]
37pub struct UpdateQuery {
38    /// `WITH …`.
39    pub with: With,
40    /// `/*+ … */`.
41    pub hints: Hints,
42    /// `LOW_PRIORITY` and `IGNORE`.
43    pub modifiers: Modifiers,
44    /// The first table reference, with its joins.
45    pub table: TableRef,
46    /// Further comma-separated table references.
47    pub extra_tables: Vec<TableRef>,
48    /// `SET …`.
49    pub set: Set,
50    /// `WHERE …`.
51    pub where_: Where,
52    /// `ORDER BY …`. Single-table form only.
53    pub order_by: OrderBy,
54    /// `LIMIT row_count`. Single-table form only, and there is no `OFFSET`.
55    pub limit: Limit,
56}
57
58impl UpdateQuery {
59    /// An `UPDATE` with nothing set yet.
60    pub fn new() -> UpdateQuery {
61        UpdateQuery::default()
62    }
63
64    /// Apply more mods to an existing query.
65    pub fn apply(&mut self, mods: impl Mod<UpdateQuery>) {
66        mods.apply(self);
67    }
68}
69
70impl Expression for UpdateQuery {
71    fn write_sql(&self, w: &mut SqlWriter<'_>) {
72        w.write_if(!self.with.is_empty(), "", &self.with, " ");
73
74        if self.table.is_empty() {
75            w.record_error(Error::Incomplete("the table of an UPDATE"));
76            return;
77        }
78        if self.set.is_empty() {
79            w.record_error(Error::Incomplete("the assignments of an UPDATE"));
80            return;
81        }
82
83        w.push_str("UPDATE ");
84        write_hints_and_modifiers(w, &self.hints, &self.modifiers);
85        // The guard inside cannot fire here: an empty target was already an
86        // `Incomplete` above, and MySQL's joins legitimately live on it.
87        write_table_list(
88            w,
89            "",
90            &self.table,
91            &self.extra_tables,
92            "the table of an UPDATE",
93        );
94
95        // `Set` writes no keyword of its own — `ON DUPLICATE KEY UPDATE` takes the
96        // same list bare — so the `SET` belongs here.
97        w.push_str(" SET ");
98        w.write_expr(&self.set);
99
100        w.write_if(!self.where_.is_empty(), " ", &self.where_, "");
101        w.write_if(!self.order_by.is_empty(), " ", &self.order_by, "");
102        w.write_if(!self.limit.is_empty(), " ", &self.limit, "");
103    }
104}
105
106impl Query for UpdateQuery {
107    fn query_type(&self) -> QueryType {
108        QueryType::Update
109    }
110
111    fn dialect(&self) -> &dyn Dialect {
112        &Mysql
113    }
114}
115
116impl<H, L, M> QueryExtensions<H, L, M> for UpdateQuery {}
117
118impl IntoExpr for UpdateQuery {
119    fn into_expr(self) -> Expr {
120        crate::query(self)
121    }
122}
123
124impl IntoExprList for UpdateQuery {
125    fn into_expr_list(self) -> Vec<Expr> {
126        vec![self.into_expr()]
127    }
128}
129
130impl HasWith for UpdateQuery {
131    fn with_mut(&mut self) -> &mut With {
132        &mut self.with
133    }
134}
135
136impl HasHints for UpdateQuery {
137    fn hints_mut(&mut self) -> &mut Hints {
138        &mut self.hints
139    }
140}
141
142impl HasModifiers for UpdateQuery {
143    fn modifiers_mut(&mut self) -> &mut Modifiers {
144        &mut self.modifiers
145    }
146}
147
148impl HasTargetTable for UpdateQuery {
149    fn target_table_mut(&mut self) -> &mut TableRef {
150        &mut self.table
151    }
152}
153
154impl HasExtraTables for UpdateQuery {
155    fn extra_tables_mut(&mut self) -> &mut Vec<TableRef> {
156        &mut self.extra_tables
157    }
158}
159
160impl HasJoins for UpdateQuery {
161    fn joins_mut(&mut self) -> &mut Vec<Join> {
162        &mut self.table.joins
163    }
164}
165
166impl HasSet for UpdateQuery {
167    fn set_mut(&mut self) -> &mut Set {
168        &mut self.set
169    }
170}
171
172impl HasWhere for UpdateQuery {
173    fn where_mut(&mut self) -> &mut Where {
174        &mut self.where_
175    }
176}
177
178impl HasOrderBy for UpdateQuery {
179    fn order_by_mut(&mut self) -> &mut OrderBy {
180        &mut self.order_by
181    }
182}
183
184impl HasLimit for UpdateQuery {
185    fn limit_mut(&mut self) -> &mut Limit {
186        &mut self.limit
187    }
188}