Skip to content

Commit 61727e4

Browse files
committed
[MySQL, Oracle] Parse optimizer hints for INSERTs
1 parent a8332a7 commit 61727e4

6 files changed

Lines changed: 45 additions & 13 deletions

File tree

src/ast/dml.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,12 @@ use serde::{Deserialize, Serialize};
2525
use sqlparser_derive::{Visit, VisitMut};
2626

2727
use crate::{
28-
ast::display_separated,
29-
display_utils::{indented_list, Indent, SpaceOrNewline},
28+
ast::{display_separated},
29+
display_utils::{Indent, SpaceOrNewline, indented_list},
3030
};
3131

3232
use super::{
33-
display_comma_separated, helpers::attached_token::AttachedToken, query::InputFormatClause,
34-
Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert,
35-
OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict, TableFactor,
36-
TableObject, TableWithJoins, UpdateTableFromKind, Values,
33+
Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert, OptimizerHint, OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict, TableFactor, TableObject, TableWithJoins, UpdateTableFromKind, Values, display_comma_separated, helpers::attached_token::AttachedToken, query::InputFormatClause
3734
};
3835

3936
/// INSERT statement.
@@ -43,6 +40,11 @@ use super::{
4340
pub struct Insert {
4441
/// Token for the `INSERT` keyword (or its substitutes)
4542
pub insert_token: AttachedToken,
43+
/// A query optimizer hint
44+
///
45+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
46+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
47+
pub optimizer_hint: Option<OptimizerHint>,
4648
/// Only for Sqlite
4749
pub or: Option<SqliteOnConflict>,
4850
/// Only for mysql
@@ -102,7 +104,11 @@ impl Display for Insert {
102104
};
103105

104106
if let Some(on_conflict) = self.or {
105-
write!(f, "INSERT {on_conflict} INTO {table_name} ")?;
107+
f.write_str("INSERT")?;
108+
if let Some(hint) = self.optimizer_hint.as_ref() {
109+
write!(f, " {hint}")?;
110+
}
111+
write!(f, " {on_conflict} INTO {table_name} ")?;
106112
} else {
107113
write!(
108114
f,
@@ -111,8 +117,10 @@ impl Display for Insert {
111117
"REPLACE"
112118
} else {
113119
"INSERT"
114-
},
115-
)?;
120+
})?;
121+
if let Some(hint) = self.optimizer_hint.as_ref() {
122+
write!(f, " {hint}")?;
123+
}
116124
if let Some(priority) = self.priority {
117125
write!(f, " {priority}",)?;
118126
}

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,7 @@ impl Spanned for Insert {
12881288
fn span(&self) -> Span {
12891289
let Insert {
12901290
insert_token,
1291+
optimizer_hint: _,
12911292
or: _, // enum, sqlite specific
12921293
ignore: _, // bool
12931294
into: _, // bool

src/parser/mod.rs

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

1673916739
/// Parse an INSERT statement
1674016740
pub fn parse_insert(&mut self, insert_token: TokenWithSpan) -> Result<Statement, ParserError> {
16741+
let optimizer_hint = self.parse_optional_optimizer_hint()?;
1674116742
let or = self.parse_conflict_clause();
1674216743
let priority = if !dialect_of!(self is MySqlDialect | GenericDialect) {
1674316744
None
@@ -16907,6 +16908,7 @@ impl<'a> Parser<'a> {
1690716908

1690816909
Ok(Statement::Insert(Insert {
1690916910
insert_token: insert_token.into(),
16911+
optimizer_hint,
1691016912
or,
1691116913
table: table_object,
1691216914
table_alias,

tests/sqlparser_mysql.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4361,16 +4361,28 @@ fn test_create_index_options() {
43614361
}
43624362

43634363
#[test]
4364-
fn test_select_optimizer_hints() {
4365-
mysql_and_generic().verified_stmt(
4364+
fn test_optimizer_hints() {
4365+
let mysql_dialect = mysql_and_generic();
4366+
4367+
// ~ selects
4368+
mysql_dialect.verified_stmt(
43664369
"\
43674370
SELECT /*+ SET_VAR(optimizer_switch = 'mrr_cost_based=off') \
43684371
SET_VAR(max_heap_table_size = 1G) */ 1",
43694372
);
43704373

4371-
mysql_and_generic().verified_stmt(
4374+
mysql_dialect.verified_stmt(
43724375
"\
43734376
SELECT /*+ SET_VAR(target_partitions=1) */ * FROM \
43744377
(SELECT /*+ SET_VAR(target_partitions=8) */ * FROM t1 LIMIT 1) AS dt",
43754378
);
4379+
4380+
// ~ inserts / replace
4381+
mysql_dialect.verified_stmt("\
4382+
INSERT /*+ RESOURCE_GROUP(Batch) */ \
4383+
INTO t2 VALUES (2)");
4384+
4385+
mysql_dialect.verified_stmt("\
4386+
REPLACE /*+ foobar */ INTO test \
4387+
VALUES (1, 'Old', '2014-08-20 18:47:00')");
43764388
}

tests/sqlparser_oracle.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,10 @@ fn parse_national_quote_delimited_string_but_is_a_word() {
335335
}
336336

337337
#[test]
338-
fn parse_optimizer_hints() {
338+
fn test_optimizer_hints() {
339339
let oracle_dialect = oracle();
340340

341+
// ~ selects
341342
let select = oracle_dialect.verified_only_select_with_canonical(
342343
"SELECT /*+one two three*/ /*+not a hint!*/ 1 FROM dual",
343344
"SELECT /*+one two three*/ 1 FROM dual",
@@ -367,4 +368,9 @@ fn parse_optimizer_hints() {
367368
.map(|hint| hint.text.as_str()),
368369
Some(" one two three /* asdf */\n")
369370
);
371+
372+
// ~ inserts
373+
oracle_dialect.verified_stmt(
374+
"INSERT /*+ append */ INTO t1 SELECT * FROM all_objects");
375+
370376
}

tests/sqlparser_postgres.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5317,6 +5317,7 @@ fn test_simple_postgres_insert_with_alias() {
53175317
statement,
53185318
Statement::Insert(Insert {
53195319
insert_token: AttachedToken::empty(),
5320+
optimizer_hint: None,
53205321
or: None,
53215322
ignore: false,
53225323
into: true,
@@ -5388,6 +5389,7 @@ fn test_simple_postgres_insert_with_alias() {
53885389
statement,
53895390
Statement::Insert(Insert {
53905391
insert_token: AttachedToken::empty(),
5392+
optimizer_hint: None,
53915393
or: None,
53925394
ignore: false,
53935395
into: true,
@@ -5461,6 +5463,7 @@ fn test_simple_insert_with_quoted_alias() {
54615463
statement,
54625464
Statement::Insert(Insert {
54635465
insert_token: AttachedToken::empty(),
5466+
optimizer_hint: None,
54645467
or: None,
54655468
ignore: false,
54665469
into: true,

0 commit comments

Comments
 (0)