Skip to main content

keelson_mysql/statement/
select.rs

1use keelson_core::clause::{
2    Combines, GroupBy, HasCombines, HasGroupBy, HasHaving, HasJoins, HasLimit, HasLocks, HasOffset,
3    HasOrderBy, HasSelectList, HasTableRef, HasWhere, HasWindows, HasWith, Having, Join, Limit,
4    Locks, Offset, OrderBy, SelectList, TableRef, Where, Windows, With,
5};
6use keelson_core::expr::{Expr, IntoExpr, IntoExprList};
7use keelson_core::{Dialect, Expression, Mod, Query, QueryExtensions, QueryType, SqlWriter};
8
9use super::{HasExtraTables, write_hints_and_modifiers, write_table_list};
10use crate::Mysql;
11use crate::extras::{HasHints, HasModifiers, Hints, Modifiers};
12
13/// A MySQL `SELECT`.
14///
15/// The field order is the clause order of
16/// <https://dev.mysql.com/doc/refman/8.4/en/select.html>:
17///
18/// ```text
19/// [WITH [RECURSIVE] with_query [, ...]]
20/// SELECT [hint_comment]
21///     [ALL | DISTINCT | DISTINCTROW] [HIGH_PRIORITY] [STRAIGHT_JOIN]
22///     [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
23///     [SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
24///     select_expr [, select_expr] ...
25///     [FROM table_references]
26///     [WHERE where_condition]
27///     [GROUP BY {col_name | expr | position}, ... [WITH ROLLUP]]
28///     [HAVING where_condition]
29///     [WINDOW window_name AS (window_spec) [, ...]]
30///     [ORDER BY {col_name | expr | position} [ASC | DESC], ...]
31///     [LIMIT {[offset,] row_count | row_count OFFSET offset}]
32///     [FOR {UPDATE | SHARE} [OF tbl_name [, ...]] [NOWAIT | SKIP LOCKED]
33///       | LOCK IN SHARE MODE]
34/// ```
35///
36/// Two differences from PostgreSQL are worth stating.
37///
38/// **The locking clause has two shapes, and they are alternatives.** `FOR UPDATE`
39/// and `FOR SHARE` are [`Locks`]; `LOCK IN SHARE MODE` is a production of its own
40/// with no `OF` list and no wait option, so it is a flag rather than a
41/// [`Lock`](keelson_core::clause::Lock) strength. Setting both is a caller error
42/// the server refuses.
43///
44/// **`LIMIT` gates `OFFSET`.** MySQL's grammar spells the pair as one clause, so
45/// `OFFSET` alone does not parse. Nothing here prevents it — the server is what
46/// says no.
47#[derive(Debug, Clone, Default)]
48pub struct SelectQuery {
49    /// `WITH …`.
50    pub with: With,
51    /// `/*+ … */`.
52    pub hints: Hints,
53    /// `DISTINCT`, `HIGH_PRIORITY`, `STRAIGHT_JOIN`, …
54    pub modifiers: Modifiers,
55    /// The projection. Empty renders `*`.
56    pub select_list: SelectList,
57    /// The first `FROM` item, with its joins.
58    pub from: TableRef,
59    /// Further comma-separated `FROM` items.
60    pub extra_from: Vec<TableRef>,
61    /// `WHERE …`.
62    pub where_: Where,
63    /// `GROUP BY … [WITH ROLLUP]`.
64    pub group_by: GroupBy,
65    /// `HAVING …`.
66    pub having: Having,
67    /// `WINDOW …`.
68    pub windows: Windows,
69    /// `ORDER BY …` — this query's own.
70    pub order_by: OrderBy,
71    /// `LIMIT …` — this query's own.
72    pub limit: Limit,
73    /// `OFFSET …` — this query's own. Needs a `LIMIT` to be legal.
74    pub offset: Offset,
75    /// `FOR UPDATE …` / `FOR SHARE …`.
76    pub locks: Locks,
77    /// `LOCK IN SHARE MODE`, the pre-8.0 spelling of `FOR SHARE`.
78    pub lock_in_share_mode: bool,
79    /// The set operations, and the trailing clauses that belong to their result.
80    pub combines: Combines,
81}
82
83impl SelectQuery {
84    /// An empty `SELECT`, which renders `SELECT *`.
85    pub fn new() -> SelectQuery {
86        SelectQuery::default()
87    }
88
89    /// Apply more mods to an existing query.
90    pub fn apply(&mut self, mods: impl Mod<SelectQuery>) {
91        mods.apply(self);
92    }
93
94    /// Whether this query carries a clause a set operation would silently steal —
95    /// the condition [`Combines::parenthesises_leading_query`] asks about.
96    fn has_tail_clauses(&self) -> bool {
97        !self.order_by.is_empty()
98            || !self.limit.is_empty()
99            || !self.offset.is_empty()
100            || !self.locks.is_empty()
101            || self.lock_in_share_mode
102    }
103}
104
105impl Expression for SelectQuery {
106    fn write_sql(&self, w: &mut SqlWriter<'_>) {
107        w.write_if(!self.with.is_empty(), "", &self.with, " ");
108
109        let parens = self
110            .combines
111            .parenthesises_leading_query(self.has_tail_clauses());
112        if parens {
113            w.push_str("(");
114        }
115
116        w.push_str("SELECT ");
117        write_hints_and_modifiers(w, &self.hints, &self.modifiers);
118        // The one clause whose absent rendering is not empty: `*`.
119        w.write_expr(&self.select_list);
120
121        write_table_list(
122            w,
123            " FROM ",
124            &self.from,
125            &self.extra_from,
126            "the FROM item its joins attach to",
127        );
128
129        w.write_if(!self.where_.is_empty(), " ", &self.where_, "");
130        w.write_if(!self.group_by.is_empty(), " ", &self.group_by, "");
131        w.write_if(!self.having.is_empty(), " ", &self.having, "");
132        w.write_if(!self.windows.is_empty(), " ", &self.windows, "");
133        w.write_if(!self.order_by.is_empty(), " ", &self.order_by, "");
134        w.write_if(!self.limit.is_empty(), " ", &self.limit, "");
135        w.write_if(!self.offset.is_empty(), " ", &self.offset, "");
136        w.write_if(!self.locks.is_empty(), " ", &self.locks, "");
137        if self.lock_in_share_mode {
138            w.push_str(" LOCK IN SHARE MODE");
139        }
140
141        if parens {
142            w.push_str(")");
143        }
144
145        w.write_if(!self.combines.is_empty(), " ", &self.combines, "");
146    }
147}
148
149impl Query for SelectQuery {
150    fn query_type(&self) -> QueryType {
151        QueryType::Select
152    }
153
154    fn dialect(&self) -> &dyn Dialect {
155        &Mysql
156    }
157}
158
159impl<H, L, M> QueryExtensions<H, L, M> for SelectQuery {}
160
161impl IntoExpr for SelectQuery {
162    fn into_expr(self) -> Expr {
163        crate::query(self)
164    }
165}
166
167impl IntoExprList for SelectQuery {
168    fn into_expr_list(self) -> Vec<Expr> {
169        vec![self.into_expr()]
170    }
171}
172
173impl HasWith for SelectQuery {
174    fn with_mut(&mut self) -> &mut With {
175        &mut self.with
176    }
177}
178
179impl HasHints for SelectQuery {
180    fn hints_mut(&mut self) -> &mut Hints {
181        &mut self.hints
182    }
183}
184
185impl HasModifiers for SelectQuery {
186    fn modifiers_mut(&mut self) -> &mut Modifiers {
187        &mut self.modifiers
188    }
189}
190
191impl HasSelectList for SelectQuery {
192    fn select_list_mut(&mut self) -> &mut SelectList {
193        &mut self.select_list
194    }
195}
196
197impl HasTableRef for SelectQuery {
198    fn table_ref_mut(&mut self) -> &mut TableRef {
199        &mut self.from
200    }
201}
202
203impl HasExtraTables for SelectQuery {
204    fn extra_tables_mut(&mut self) -> &mut Vec<TableRef> {
205        &mut self.extra_from
206    }
207}
208
209impl HasJoins for SelectQuery {
210    fn joins_mut(&mut self) -> &mut Vec<Join> {
211        &mut self.from.joins
212    }
213}
214
215impl HasWhere for SelectQuery {
216    fn where_mut(&mut self) -> &mut Where {
217        &mut self.where_
218    }
219}
220
221impl HasGroupBy for SelectQuery {
222    fn group_by_mut(&mut self) -> &mut GroupBy {
223        &mut self.group_by
224    }
225}
226
227impl HasHaving for SelectQuery {
228    fn having_mut(&mut self) -> &mut Having {
229        &mut self.having
230    }
231}
232
233impl HasWindows for SelectQuery {
234    fn windows_mut(&mut self) -> &mut Windows {
235        &mut self.windows
236    }
237}
238
239impl HasOrderBy for SelectQuery {
240    fn order_by_mut(&mut self) -> &mut OrderBy {
241        &mut self.order_by
242    }
243}
244
245impl HasLimit for SelectQuery {
246    fn limit_mut(&mut self) -> &mut Limit {
247        &mut self.limit
248    }
249}
250
251impl HasOffset for SelectQuery {
252    fn offset_mut(&mut self) -> &mut Offset {
253        &mut self.offset
254    }
255}
256
257impl HasLocks for SelectQuery {
258    fn locks_mut(&mut self) -> &mut Locks {
259        &mut self.locks
260    }
261}
262
263impl HasCombines for SelectQuery {
264    fn combines_mut(&mut self) -> &mut Combines {
265        &mut self.combines
266    }
267}