Skip to content

Commit 8cfc462

Browse files
authored
Add support for MySQL's INSERT INTO ... SET syntax (apache#1641)
1 parent 4c6af0a commit 8cfc462

File tree

7 files changed

+43
-20
lines changed

7 files changed

+43
-20
lines changed

src/ast/dml.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use sqlparser_derive::{Visit, VisitMut};
3232
pub use super::ddl::{ColumnDef, TableConstraint};
3333

3434
use super::{
35-
display_comma_separated, display_separated, ClusteredBy, CommentDef, Expr, FileFormat,
36-
FromTable, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident,
35+
display_comma_separated, display_separated, Assignment, ClusteredBy, CommentDef, Expr,
36+
FileFormat, FromTable, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident,
3737
InsertAliases, MysqlInsertPriority, ObjectName, OnCommit, OnInsert, OneOrManyWithParens,
3838
OrderByExpr, Query, RowAccessPolicy, SelectItem, SqlOption, SqliteOnConflict, TableEngine,
3939
TableWithJoins, Tag, WrappedCollection,
@@ -480,6 +480,9 @@ pub struct Insert {
480480
pub overwrite: bool,
481481
/// A SQL query that specifies what to insert
482482
pub source: Option<Box<Query>>,
483+
/// MySQL `INSERT INTO ... SET`
484+
/// See: <https://dev.mysql.com/doc/refman/8.4/en/insert.html>
485+
pub assignments: Vec<Assignment>,
483486
/// partitioned insert (Hive)
484487
pub partitioned: Option<Vec<Expr>>,
485488
/// Columns defined after PARTITION
@@ -545,9 +548,10 @@ impl Display for Insert {
545548

546549
if let Some(source) = &self.source {
547550
write!(f, "{source}")?;
548-
}
549-
550-
if self.source.is_none() && self.columns.is_empty() {
551+
} else if !self.assignments.is_empty() {
552+
write!(f, "SET ")?;
553+
write!(f, "{}", display_comma_separated(&self.assignments))?;
554+
} else if self.source.is_none() && self.columns.is_empty() {
551555
write!(f, "DEFAULT VALUES")?;
552556
}
553557

src/ast/spans.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1153,13 +1153,15 @@ impl Spanned for Insert {
11531153
replace_into: _, // bool
11541154
priority: _, // todo, mysql specific
11551155
insert_alias: _, // todo, mysql specific
1156+
assignments,
11561157
} = self;
11571158

11581159
union_spans(
11591160
core::iter::once(table_name.span())
11601161
.chain(table_alias.as_ref().map(|i| i.span))
11611162
.chain(columns.iter().map(|i| i.span))
11621163
.chain(source.as_ref().map(|q| q.span()))
1164+
.chain(assignments.iter().map(|i| i.span()))
11631165
.chain(partitioned.iter().flat_map(|i| i.iter().map(|k| k.span())))
11641166
.chain(after_columns.iter().map(|i| i.span))
11651167
.chain(on.as_ref().map(|i| i.span()))

src/dialect/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,13 @@ pub trait Dialect: Debug + Any {
775775
fn supports_table_sample_before_alias(&self) -> bool {
776776
false
777777
}
778+
779+
/// Returns true if this dialect supports the `INSERT INTO ... SET col1 = 1, ...` syntax.
780+
///
781+
/// MySQL: <https://dev.mysql.com/doc/refman/8.4/en/insert.html>
782+
fn supports_insert_set(&self) -> bool {
783+
false
784+
}
778785
}
779786

780787
/// This represents the operators for which precedence must be defined

src/dialect/mysql.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,15 @@ impl Dialect for MySqlDialect {
9898
true
9999
}
100100

101-
/// see <https://dev.mysql.com/doc/refman/8.4/en/create-table-select.html>
101+
/// See: <https://dev.mysql.com/doc/refman/8.4/en/create-table-select.html>
102102
fn supports_create_table_select(&self) -> bool {
103103
true
104104
}
105+
106+
/// See: <https://dev.mysql.com/doc/refman/8.4/en/insert.html>
107+
fn supports_insert_set(&self) -> bool {
108+
true
109+
}
105110
}
106111

107112
/// `LOCK TABLES`

src/parser/mod.rs

+10-14
Original file line numberDiff line numberDiff line change
@@ -11899,9 +11899,9 @@ impl<'a> Parser<'a> {
1189911899

1190011900
let is_mysql = dialect_of!(self is MySqlDialect);
1190111901

11902-
let (columns, partitioned, after_columns, source) =
11902+
let (columns, partitioned, after_columns, source, assignments) =
1190311903
if self.parse_keywords(&[Keyword::DEFAULT, Keyword::VALUES]) {
11904-
(vec![], None, vec![], None)
11904+
(vec![], None, vec![], None, vec![])
1190511905
} else {
1190611906
let (columns, partitioned, after_columns) = if !self.peek_subquery_start() {
1190711907
let columns = self.parse_parenthesized_column_list(Optional, is_mysql)?;
@@ -11918,9 +11918,14 @@ impl<'a> Parser<'a> {
1191811918
Default::default()
1191911919
};
1192011920

11921-
let source = Some(self.parse_query()?);
11921+
let (source, assignments) =
11922+
if self.dialect.supports_insert_set() && self.parse_keyword(Keyword::SET) {
11923+
(None, self.parse_comma_separated(Parser::parse_assignment)?)
11924+
} else {
11925+
(Some(self.parse_query()?), vec![])
11926+
};
1192211927

11923-
(columns, partitioned, after_columns, source)
11928+
(columns, partitioned, after_columns, source, assignments)
1192411929
};
1192511930

1192611931
let insert_alias = if dialect_of!(self is MySqlDialect | GenericDialect)
@@ -12000,6 +12005,7 @@ impl<'a> Parser<'a> {
1200012005
columns,
1200112006
after_columns,
1200212007
source,
12008+
assignments,
1200312009
table,
1200412010
on,
1200512011
returning,
@@ -14228,16 +14234,6 @@ mod tests {
1422814234
assert!(Parser::parse_sql(&GenericDialect {}, sql).is_err());
1422914235
}
1423014236

14231-
#[test]
14232-
fn test_replace_into_set() {
14233-
// NOTE: This is actually valid MySQL syntax, REPLACE and INSERT,
14234-
// but the parser does not yet support it.
14235-
// https://dev.mysql.com/doc/refman/8.3/en/insert.html
14236-
let sql = "REPLACE INTO t SET a='1'";
14237-
14238-
assert!(Parser::parse_sql(&MySqlDialect {}, sql).is_err());
14239-
}
14240-
1424114237
#[test]
1424214238
fn test_replace_into_set_placeholder() {
1424314239
let sql = "REPLACE INTO t SET ?";

tests/sqlparser_common.rs

+6
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ fn parse_insert_values() {
119119
verified_stmt("INSERT INTO customer WITH foo AS (SELECT 1) SELECT * FROM foo UNION VALUES (1)");
120120
}
121121

122+
#[test]
123+
fn parse_insert_set() {
124+
let dialects = all_dialects_where(|d| d.supports_insert_set());
125+
dialects.verified_stmt("INSERT INTO tbl1 SET col1 = 1, col2 = 'abc', col3 = current_date()");
126+
}
127+
122128
#[test]
123129
fn parse_replace_into() {
124130
let dialect = PostgreSqlDialect {};

tests/sqlparser_postgres.rs

+3
Original file line numberDiff line numberDiff line change
@@ -4423,6 +4423,7 @@ fn test_simple_postgres_insert_with_alias() {
44234423
settings: None,
44244424
format_clause: None,
44254425
})),
4426+
assignments: vec![],
44264427
partitioned: None,
44274428
after_columns: vec![],
44284429
table: false,
@@ -4493,6 +4494,7 @@ fn test_simple_postgres_insert_with_alias() {
44934494
settings: None,
44944495
format_clause: None,
44954496
})),
4497+
assignments: vec![],
44964498
partitioned: None,
44974499
after_columns: vec![],
44984500
table: false,
@@ -4559,6 +4561,7 @@ fn test_simple_insert_with_quoted_alias() {
45594561
settings: None,
45604562
format_clause: None,
45614563
})),
4564+
assignments: vec![],
45624565
partitioned: None,
45634566
after_columns: vec![],
45644567
table: false,

0 commit comments

Comments
 (0)