Skip to content

Commit be460b2

Browse files
xitepiffyio
andauthored
[MySQL, Oracle] Parse optimizer hints (#2162)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent 2d47fec commit be460b2

19 files changed

+336
-10
lines changed

src/ast/dml.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use crate::{
3232
use super::{
3333
display_comma_separated, helpers::attached_token::AttachedToken, query::InputFormatClause,
3434
Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert,
35-
OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict, TableFactor,
36-
TableObject, TableWithJoins, UpdateTableFromKind, Values,
35+
OptimizerHint, OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict,
36+
TableFactor, TableObject, TableWithJoins, UpdateTableFromKind, Values,
3737
};
3838

3939
/// INSERT statement.
@@ -43,6 +43,11 @@ use super::{
4343
pub struct Insert {
4444
/// Token for the `INSERT` keyword (or its substitutes)
4545
pub insert_token: AttachedToken,
46+
/// A query optimizer hint
47+
///
48+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
49+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
50+
pub optimizer_hint: Option<OptimizerHint>,
4651
/// Only for Sqlite
4752
pub or: Option<SqliteOnConflict>,
4853
/// Only for mysql
@@ -102,7 +107,11 @@ impl Display for Insert {
102107
};
103108

104109
if let Some(on_conflict) = self.or {
105-
write!(f, "INSERT {on_conflict} INTO {table_name} ")?;
110+
f.write_str("INSERT")?;
111+
if let Some(hint) = self.optimizer_hint.as_ref() {
112+
write!(f, " {hint}")?;
113+
}
114+
write!(f, " {on_conflict} INTO {table_name} ")?;
106115
} else {
107116
write!(
108117
f,
@@ -111,8 +120,11 @@ impl Display for Insert {
111120
"REPLACE"
112121
} else {
113122
"INSERT"
114-
},
123+
}
115124
)?;
125+
if let Some(hint) = self.optimizer_hint.as_ref() {
126+
write!(f, " {hint}")?;
127+
}
116128
if let Some(priority) = self.priority {
117129
write!(f, " {priority}",)?;
118130
}
@@ -188,6 +200,11 @@ impl Display for Insert {
188200
pub struct Delete {
189201
/// Token for the `DELETE` keyword
190202
pub delete_token: AttachedToken,
203+
/// A query optimizer hint
204+
///
205+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
206+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
207+
pub optimizer_hint: Option<OptimizerHint>,
191208
/// Multi tables delete are supported in mysql
192209
pub tables: Vec<ObjectName>,
193210
/// FROM
@@ -207,6 +224,10 @@ pub struct Delete {
207224
impl Display for Delete {
208225
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209226
f.write_str("DELETE")?;
227+
if let Some(hint) = self.optimizer_hint.as_ref() {
228+
f.write_str(" ")?;
229+
hint.fmt(f)?;
230+
}
210231
if !self.tables.is_empty() {
211232
indented_list(f, &self.tables)?;
212233
}
@@ -257,6 +278,11 @@ impl Display for Delete {
257278
pub struct Update {
258279
/// Token for the `UPDATE` keyword
259280
pub update_token: AttachedToken,
281+
/// A query optimizer hint
282+
///
283+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
284+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
285+
pub optimizer_hint: Option<OptimizerHint>,
260286
/// TABLE
261287
pub table: TableWithJoins,
262288
/// Column assignments
@@ -276,6 +302,10 @@ pub struct Update {
276302
impl Display for Update {
277303
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
278304
f.write_str("UPDATE ")?;
305+
if let Some(hint) = self.optimizer_hint.as_ref() {
306+
hint.fmt(f)?;
307+
f.write_str(" ")?;
308+
}
279309
if let Some(or) = &self.or {
280310
or.fmt(f)?;
281311
f.write_str(" ")?;
@@ -322,6 +352,10 @@ impl Display for Update {
322352
pub struct Merge {
323353
/// The `MERGE` token that starts the statement.
324354
pub merge_token: AttachedToken,
355+
/// A query optimizer hint
356+
///
357+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
358+
pub optimizer_hint: Option<OptimizerHint>,
325359
/// optional INTO keyword
326360
pub into: bool,
327361
/// Specifies the table to merge
@@ -338,12 +372,18 @@ pub struct Merge {
338372

339373
impl Display for Merge {
340374
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375+
f.write_str("MERGE")?;
376+
if let Some(hint) = self.optimizer_hint.as_ref() {
377+
write!(f, " {hint}")?;
378+
}
379+
if self.into {
380+
write!(f, " INTO")?;
381+
}
341382
write!(
342383
f,
343-
"MERGE{int} {table} USING {source} ",
344-
int = if self.into { " INTO" } else { "" },
384+
" {table} USING {source} ",
345385
table = self.table,
346-
source = self.source,
386+
source = self.source
347387
)?;
348388
write!(f, "ON {on} ", on = self.on)?;
349389
write!(f, "{}", display_separated(&self.clauses, " "))?;

src/ast/mod.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11688,6 +11688,57 @@ pub struct ResetStatement {
1168811688
pub reset: Reset,
1168911689
}
1169011690

11691+
/// Query optimizer hints are optionally supported comments after the
11692+
/// `SELECT`, `INSERT`, `UPDATE`, `REPLACE`, `MERGE`, and `DELETE` keywords in
11693+
/// the corresponding statements.
11694+
///
11695+
/// See [Select::optimizer_hint]
11696+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
11697+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11698+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
11699+
pub struct OptimizerHint {
11700+
/// the raw test of the optimizer hint without its markers
11701+
pub text: String,
11702+
/// the style of the comment which `text` was extracted from,
11703+
/// e.g. `/*+...*/` or `--+...`
11704+
///
11705+
/// Not all dialects support all styles, though.
11706+
pub style: OptimizerHintStyle,
11707+
}
11708+
11709+
/// The commentary style of an [optimizer hint](OptimizerHint)
11710+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
11711+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11712+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
11713+
pub enum OptimizerHintStyle {
11714+
/// A hint corresponding to a single line comment,
11715+
/// e.g. `--+ LEADING(v.e v.d t)`
11716+
SingleLine {
11717+
/// the comment prefix, e.g. `--`
11718+
prefix: String,
11719+
},
11720+
/// A hint corresponding to a multi line comment,
11721+
/// e.g. `/*+ LEADING(v.e v.d t) */`
11722+
MultiLine,
11723+
}
11724+
11725+
impl fmt::Display for OptimizerHint {
11726+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11727+
match &self.style {
11728+
OptimizerHintStyle::SingleLine { prefix } => {
11729+
f.write_str(prefix)?;
11730+
f.write_str("+")?;
11731+
f.write_str(&self.text)
11732+
}
11733+
OptimizerHintStyle::MultiLine => {
11734+
f.write_str("/*+")?;
11735+
f.write_str(&self.text)?;
11736+
f.write_str("*/")
11737+
}
11738+
}
11739+
}
11740+
}
11741+
1169111742
impl fmt::Display for ResetStatement {
1169211743
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1169311744
match &self.reset {

src/ast/query.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ pub enum SelectFlavor {
343343
pub struct Select {
344344
/// Token for the `SELECT` keyword
345345
pub select_token: AttachedToken,
346+
/// A query optimizer hint
347+
///
348+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
349+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
350+
pub optimizer_hint: Option<OptimizerHint>,
346351
/// `SELECT [DISTINCT] ...`
347352
pub distinct: Option<Distinct>,
348353
/// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]`
@@ -410,6 +415,11 @@ impl fmt::Display for Select {
410415
}
411416
}
412417

418+
if let Some(hint) = self.optimizer_hint.as_ref() {
419+
f.write_str(" ")?;
420+
hint.fmt(f)?;
421+
}
422+
413423
if let Some(value_table_mode) = self.value_table_mode {
414424
f.write_str(" ")?;
415425
value_table_mode.fmt(f)?;

src/ast/spans.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,7 @@ impl Spanned for Delete {
894894
fn span(&self) -> Span {
895895
let Delete {
896896
delete_token,
897+
optimizer_hint: _,
897898
tables,
898899
from,
899900
using,
@@ -927,6 +928,7 @@ impl Spanned for Update {
927928
fn span(&self) -> Span {
928929
let Update {
929930
update_token,
931+
optimizer_hint: _,
930932
table,
931933
assignments,
932934
from,
@@ -1290,6 +1292,7 @@ impl Spanned for Insert {
12901292
fn span(&self) -> Span {
12911293
let Insert {
12921294
insert_token,
1295+
optimizer_hint: _,
12931296
or: _, // enum, sqlite specific
12941297
ignore: _, // bool
12951298
into: _, // bool
@@ -2233,6 +2236,7 @@ impl Spanned for Select {
22332236
fn span(&self) -> Span {
22342237
let Select {
22352238
select_token,
2239+
optimizer_hint: _,
22362240
distinct: _, // todo
22372241
top: _, // todo, mysql specific
22382242
projection,
@@ -2819,6 +2823,7 @@ WHERE id = 1
28192823
// ~ individual tokens within the statement
28202824
let Statement::Merge(Merge {
28212825
merge_token,
2826+
optimizer_hint: _,
28222827
into: _,
28232828
table: _,
28242829
source: _,

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,8 @@ impl Dialect for GenericDialect {
271271
fn supports_select_format(&self) -> bool {
272272
true
273273
}
274+
275+
fn supports_comment_optimizer_hint(&self) -> bool {
276+
true
277+
}
274278
}

src/dialect/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,16 @@ pub trait Dialect: Debug + Any {
13221322
false
13231323
}
13241324

1325+
/// Returns `true` if the dialect supports query optimizer hints in the
1326+
/// format of single and multi line comments immediately following a
1327+
/// `SELECT`, `INSERT`, `REPLACE`, `DELETE`, or `MERGE` keyword.
1328+
///
1329+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
1330+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Comments.html#SQLRF-GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
1331+
fn supports_comment_optimizer_hint(&self) -> bool {
1332+
false
1333+
}
1334+
13251335
/// Returns true if the dialect considers the `&&` operator as a boolean AND operator.
13261336
fn supports_double_ampersand_operator(&self) -> bool {
13271337
false

src/dialect/mysql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ impl Dialect for MySqlDialect {
182182
fn supports_binary_kw_as_cast(&self) -> bool {
183183
true
184184
}
185+
186+
fn supports_comment_optimizer_hint(&self) -> bool {
187+
true
188+
}
185189
}
186190

187191
/// `LOCK TABLES`

src/dialect/oracle.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,8 @@ impl Dialect for OracleDialect {
9999
fn supports_quote_delimited_string(&self) -> bool {
100100
true
101101
}
102+
103+
fn supports_comment_optimizer_hint(&self) -> bool {
104+
true
105+
}
102106
}

src/parser/merge.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ impl Parser<'_> {
4343

4444
/// Parse a `MERGE` statement
4545
pub fn parse_merge(&mut self, merge_token: TokenWithSpan) -> Result<Merge, ParserError> {
46+
let optimizer_hint = self.maybe_parse_optimizer_hint()?;
4647
let into = self.parse_keyword(Keyword::INTO);
4748

4849
let table = self.parse_table_factor()?;
@@ -59,6 +60,7 @@ impl Parser<'_> {
5960

6061
Ok(Merge {
6162
merge_token: merge_token.into(),
63+
optimizer_hint,
6264
into,
6365
table,
6466
source,

0 commit comments

Comments
 (0)