Skip to content

Commit 1f0eae6

Browse files
committed
[MySQL, Oracle] Parse optimizer hints for INSERTs
1 parent 6dd814a commit 1f0eae6

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
@@ -1290,6 +1290,7 @@ impl Spanned for Insert {
12901290
fn span(&self) -> Span {
12911291
let Insert {
12921292
insert_token,
1293+
optimizer_hint: _,
12931294
or: _, // enum, sqlite specific
12941295
ignore: _, // bool
12951296
into: _, // bool

src/parser/mod.rs

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

1680216802
/// Parse an INSERT statement
1680316803
pub fn parse_insert(&mut self, insert_token: TokenWithSpan) -> Result<Statement, ParserError> {
16804+
let optimizer_hint = self.parse_optional_optimizer_hint()?;
1680416805
let or = self.parse_conflict_clause();
1680516806
let priority = if !dialect_of!(self is MySqlDialect | GenericDialect) {
1680616807
None
@@ -16970,6 +16971,7 @@ impl<'a> Parser<'a> {
1697016971

1697116972
Ok(Insert {
1697216973
insert_token: insert_token.into(),
16974+
optimizer_hint,
1697316975
or,
1697416976
table: table_object,
1697516977
table_alias,

tests/sqlparser_mysql.rs

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

43904390
#[test]
4391-
fn test_select_optimizer_hints() {
4392-
mysql_and_generic().verified_stmt(
4391+
fn test_optimizer_hints() {
4392+
let mysql_dialect = mysql_and_generic();
4393+
4394+
// ~ selects
4395+
mysql_dialect.verified_stmt(
43934396
"\
43944397
SELECT /*+ SET_VAR(optimizer_switch = 'mrr_cost_based=off') \
43954398
SET_VAR(max_heap_table_size = 1G) */ 1",
43964399
);
43974400

4398-
mysql_and_generic().verified_stmt(
4401+
mysql_dialect.verified_stmt(
43994402
"\
44004403
SELECT /*+ SET_VAR(target_partitions=1) */ * FROM \
44014404
(SELECT /*+ SET_VAR(target_partitions=8) */ * FROM t1 LIMIT 1) AS dt",
44024405
);
4406+
4407+
// ~ inserts / replace
4408+
mysql_dialect.verified_stmt("\
4409+
INSERT /*+ RESOURCE_GROUP(Batch) */ \
4410+
INTO t2 VALUES (2)");
4411+
4412+
mysql_dialect.verified_stmt("\
4413+
REPLACE /*+ foobar */ INTO test \
4414+
VALUES (1, 'Old', '2014-08-20 18:47:00')");
44034415
}

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
@@ -5387,6 +5387,7 @@ fn test_simple_postgres_insert_with_alias() {
53875387
statement,
53885388
Statement::Insert(Insert {
53895389
insert_token: AttachedToken::empty(),
5390+
optimizer_hint: None,
53905391
or: None,
53915392
ignore: false,
53925393
into: true,
@@ -5458,6 +5459,7 @@ fn test_simple_postgres_insert_with_alias() {
54585459
statement,
54595460
Statement::Insert(Insert {
54605461
insert_token: AttachedToken::empty(),
5462+
optimizer_hint: None,
54615463
or: None,
54625464
ignore: false,
54635465
into: true,
@@ -5531,6 +5533,7 @@ fn test_simple_insert_with_quoted_alias() {
55315533
statement,
55325534
Statement::Insert(Insert {
55335535
insert_token: AttachedToken::empty(),
5536+
optimizer_hint: None,
55345537
or: None,
55355538
ignore: false,
55365539
into: true,

0 commit comments

Comments
 (0)