mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
Support Mysql REPLACE
statement and PRIORITY
clause of INSERT
(#1072)
This commit is contained in:
parent
7ea47c71fb
commit
c62ecb1100
5 changed files with 211 additions and 2 deletions
|
@ -1443,6 +1443,10 @@ pub enum Statement {
|
||||||
on: Option<OnInsert>,
|
on: Option<OnInsert>,
|
||||||
/// RETURNING
|
/// RETURNING
|
||||||
returning: Option<Vec<SelectItem>>,
|
returning: Option<Vec<SelectItem>>,
|
||||||
|
/// Only for mysql
|
||||||
|
replace_into: bool,
|
||||||
|
/// Only for mysql
|
||||||
|
priority: Option<MysqlInsertPriority>,
|
||||||
},
|
},
|
||||||
// TODO: Support ROW FORMAT
|
// TODO: Support ROW FORMAT
|
||||||
Directory {
|
Directory {
|
||||||
|
@ -2404,18 +2408,29 @@ impl fmt::Display for Statement {
|
||||||
table,
|
table,
|
||||||
on,
|
on,
|
||||||
returning,
|
returning,
|
||||||
|
replace_into,
|
||||||
|
priority,
|
||||||
} => {
|
} => {
|
||||||
if let Some(action) = or {
|
if let Some(action) = or {
|
||||||
write!(f, "INSERT OR {action} INTO {table_name} ")?;
|
write!(f, "INSERT OR {action} INTO {table_name} ")?;
|
||||||
} else {
|
} else {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"INSERT{ignore}{over}{int}{tbl} {table_name} ",
|
"{start}",
|
||||||
|
start = if *replace_into { "REPLACE" } else { "INSERT" },
|
||||||
|
)?;
|
||||||
|
if let Some(priority) = priority {
|
||||||
|
write!(f, " {priority}",)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{ignore}{over}{int}{tbl} {table_name} ",
|
||||||
table_name = table_name,
|
table_name = table_name,
|
||||||
ignore = if *ignore { " IGNORE" } else { "" },
|
ignore = if *ignore { " IGNORE" } else { "" },
|
||||||
over = if *overwrite { " OVERWRITE" } else { "" },
|
over = if *overwrite { " OVERWRITE" } else { "" },
|
||||||
int = if *into { " INTO" } else { "" },
|
int = if *into { " INTO" } else { "" },
|
||||||
tbl = if *table { " TABLE" } else { "" }
|
tbl = if *table { " TABLE" } else { "" },
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
|
@ -4522,6 +4537,31 @@ impl fmt::Display for SqliteOnConflict {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mysql specific syntax
|
||||||
|
///
|
||||||
|
/// See [Mysql documentation](https://dev.mysql.com/doc/refman/8.0/en/replace.html)
|
||||||
|
/// See [Mysql documentation](https://dev.mysql.com/doc/refman/8.0/en/insert.html)
|
||||||
|
/// for more details.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum MysqlInsertPriority {
|
||||||
|
LowPriority,
|
||||||
|
Delayed,
|
||||||
|
HighPriority,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for crate::ast::MysqlInsertPriority {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use MysqlInsertPriority::*;
|
||||||
|
match self {
|
||||||
|
LowPriority => write!(f, "LOW_PRIORITY"),
|
||||||
|
Delayed => write!(f, "DELAYED"),
|
||||||
|
HighPriority => write!(f, "HIGH_PRIORITY"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
|
|
@ -210,6 +210,7 @@ define_keywords!(
|
||||||
DECLARE,
|
DECLARE,
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
DEFERRED,
|
DEFERRED,
|
||||||
|
DELAYED,
|
||||||
DELETE,
|
DELETE,
|
||||||
DELIMITED,
|
DELIMITED,
|
||||||
DELIMITER,
|
DELIMITER,
|
||||||
|
@ -315,6 +316,7 @@ define_keywords!(
|
||||||
HASH,
|
HASH,
|
||||||
HAVING,
|
HAVING,
|
||||||
HEADER,
|
HEADER,
|
||||||
|
HIGH_PRIORITY,
|
||||||
HISTORY,
|
HISTORY,
|
||||||
HIVEVAR,
|
HIVEVAR,
|
||||||
HOLD,
|
HOLD,
|
||||||
|
|
|
@ -490,6 +490,7 @@ impl<'a> Parser<'a> {
|
||||||
Keyword::FETCH => Ok(self.parse_fetch_statement()?),
|
Keyword::FETCH => Ok(self.parse_fetch_statement()?),
|
||||||
Keyword::DELETE => Ok(self.parse_delete()?),
|
Keyword::DELETE => Ok(self.parse_delete()?),
|
||||||
Keyword::INSERT => Ok(self.parse_insert()?),
|
Keyword::INSERT => Ok(self.parse_insert()?),
|
||||||
|
Keyword::REPLACE => Ok(self.parse_replace()?),
|
||||||
Keyword::UNCACHE => Ok(self.parse_uncache_table()?),
|
Keyword::UNCACHE => Ok(self.parse_uncache_table()?),
|
||||||
Keyword::UPDATE => Ok(self.parse_update()?),
|
Keyword::UPDATE => Ok(self.parse_update()?),
|
||||||
Keyword::ALTER => Ok(self.parse_alter()?),
|
Keyword::ALTER => Ok(self.parse_alter()?),
|
||||||
|
@ -7379,6 +7380,20 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an REPLACE statement
|
||||||
|
pub fn parse_replace(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
if !dialect_of!(self is MySqlDialect | GenericDialect) {
|
||||||
|
return parser_err!("Unsupported statement REPLACE", self.peek_token().location);
|
||||||
|
}
|
||||||
|
|
||||||
|
let insert = &mut self.parse_insert().unwrap();
|
||||||
|
if let Statement::Insert { replace_into, .. } = insert {
|
||||||
|
*replace_into = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(insert.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse an INSERT statement
|
/// Parse an INSERT statement
|
||||||
pub fn parse_insert(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_insert(&mut self) -> Result<Statement, ParserError> {
|
||||||
let or = if !dialect_of!(self is SQLiteDialect) {
|
let or = if !dialect_of!(self is SQLiteDialect) {
|
||||||
|
@ -7399,9 +7414,23 @@ impl<'a> Parser<'a> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let priority = if !dialect_of!(self is MySqlDialect | GenericDialect) {
|
||||||
|
None
|
||||||
|
} else if self.parse_keyword(Keyword::LOW_PRIORITY) {
|
||||||
|
Some(MysqlInsertPriority::LowPriority)
|
||||||
|
} else if self.parse_keyword(Keyword::DELAYED) {
|
||||||
|
Some(MysqlInsertPriority::Delayed)
|
||||||
|
} else if self.parse_keyword(Keyword::HIGH_PRIORITY) {
|
||||||
|
Some(MysqlInsertPriority::HighPriority)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let ignore = dialect_of!(self is MySqlDialect | GenericDialect)
|
let ignore = dialect_of!(self is MySqlDialect | GenericDialect)
|
||||||
&& self.parse_keyword(Keyword::IGNORE);
|
&& self.parse_keyword(Keyword::IGNORE);
|
||||||
|
|
||||||
|
let replace_into = false;
|
||||||
|
|
||||||
let action = self.parse_one_of_keywords(&[Keyword::INTO, Keyword::OVERWRITE]);
|
let action = self.parse_one_of_keywords(&[Keyword::INTO, Keyword::OVERWRITE]);
|
||||||
let into = action == Some(Keyword::INTO);
|
let into = action == Some(Keyword::INTO);
|
||||||
let overwrite = action == Some(Keyword::OVERWRITE);
|
let overwrite = action == Some(Keyword::OVERWRITE);
|
||||||
|
@ -7511,6 +7540,8 @@ impl<'a> Parser<'a> {
|
||||||
table,
|
table,
|
||||||
on,
|
on,
|
||||||
returning,
|
returning,
|
||||||
|
replace_into,
|
||||||
|
priority,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,17 @@ fn parse_insert_values() {
|
||||||
verified_stmt("INSERT INTO customer WITH foo AS (SELECT 1) SELECT * FROM foo UNION VALUES (1)");
|
verified_stmt("INSERT INTO customer WITH foo AS (SELECT 1) SELECT * FROM foo UNION VALUES (1)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_replace_into() {
|
||||||
|
let dialect = PostgreSqlDialect {};
|
||||||
|
let sql = "REPLACE INTO public.customer (id, name, active) VALUES (1, 2, 3)";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Unsupported statement REPLACE at Line: 1, Column 9".to_string()),
|
||||||
|
Parser::parse_sql(&dialect, sql,).unwrap_err(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_insert_default_values() {
|
fn parse_insert_default_values() {
|
||||||
let insert_with_default_values = verified_stmt("INSERT INTO test_table DEFAULT VALUES");
|
let insert_with_default_values = verified_stmt("INSERT INTO test_table DEFAULT VALUES");
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
use matches::assert_matches;
|
use matches::assert_matches;
|
||||||
use sqlparser::ast::Expr;
|
use sqlparser::ast::Expr;
|
||||||
|
use sqlparser::ast::MysqlInsertPriority::{Delayed, HighPriority, LowPriority};
|
||||||
use sqlparser::ast::Value;
|
use sqlparser::ast::Value;
|
||||||
use sqlparser::ast::*;
|
use sqlparser::ast::*;
|
||||||
use sqlparser::dialect::{GenericDialect, MySqlDialect};
|
use sqlparser::dialect::{GenericDialect, MySqlDialect};
|
||||||
|
@ -1035,6 +1036,130 @@ fn parse_ignore_insert() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_priority_insert() {
|
||||||
|
let sql = r"INSERT HIGH_PRIORITY INTO tasks (title, priority) VALUES ('Test Some Inserts', 1)";
|
||||||
|
|
||||||
|
match mysql_and_generic().verified_stmt(sql) {
|
||||||
|
Statement::Insert {
|
||||||
|
table_name,
|
||||||
|
columns,
|
||||||
|
source,
|
||||||
|
on,
|
||||||
|
priority,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!(ObjectName(vec![Ident::new("tasks")]), table_name);
|
||||||
|
assert_eq!(vec![Ident::new("title"), Ident::new("priority")], columns);
|
||||||
|
assert!(on.is_none());
|
||||||
|
assert_eq!(priority, Some(HighPriority));
|
||||||
|
assert_eq!(
|
||||||
|
Some(Box::new(Query {
|
||||||
|
with: None,
|
||||||
|
body: Box::new(SetExpr::Values(Values {
|
||||||
|
explicit_row: false,
|
||||||
|
rows: vec![vec![
|
||||||
|
Expr::Value(Value::SingleQuotedString("Test Some Inserts".to_string())),
|
||||||
|
Expr::Value(number("1"))
|
||||||
|
]]
|
||||||
|
})),
|
||||||
|
order_by: vec![],
|
||||||
|
limit: None,
|
||||||
|
limit_by: vec![],
|
||||||
|
offset: None,
|
||||||
|
fetch: None,
|
||||||
|
locks: vec![],
|
||||||
|
for_clause: None,
|
||||||
|
})),
|
||||||
|
source
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let sql2 = r"INSERT LOW_PRIORITY INTO tasks (title, priority) VALUES ('Test Some Inserts', 1)";
|
||||||
|
|
||||||
|
match mysql().verified_stmt(sql2) {
|
||||||
|
Statement::Insert {
|
||||||
|
table_name,
|
||||||
|
columns,
|
||||||
|
source,
|
||||||
|
on,
|
||||||
|
priority,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!(ObjectName(vec![Ident::new("tasks")]), table_name);
|
||||||
|
assert_eq!(vec![Ident::new("title"), Ident::new("priority")], columns);
|
||||||
|
assert!(on.is_none());
|
||||||
|
assert_eq!(priority, Some(LowPriority));
|
||||||
|
assert_eq!(
|
||||||
|
Some(Box::new(Query {
|
||||||
|
with: None,
|
||||||
|
body: Box::new(SetExpr::Values(Values {
|
||||||
|
explicit_row: false,
|
||||||
|
rows: vec![vec![
|
||||||
|
Expr::Value(Value::SingleQuotedString("Test Some Inserts".to_string())),
|
||||||
|
Expr::Value(number("1"))
|
||||||
|
]]
|
||||||
|
})),
|
||||||
|
order_by: vec![],
|
||||||
|
limit: None,
|
||||||
|
limit_by: vec![],
|
||||||
|
offset: None,
|
||||||
|
fetch: None,
|
||||||
|
locks: vec![],
|
||||||
|
for_clause: None,
|
||||||
|
})),
|
||||||
|
source
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_replace_insert() {
|
||||||
|
let sql = r"REPLACE DELAYED INTO tasks (title, priority) VALUES ('Test Some Inserts', 1)";
|
||||||
|
match mysql().verified_stmt(sql) {
|
||||||
|
Statement::Insert {
|
||||||
|
table_name,
|
||||||
|
columns,
|
||||||
|
source,
|
||||||
|
on,
|
||||||
|
replace_into,
|
||||||
|
priority,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!(ObjectName(vec![Ident::new("tasks")]), table_name);
|
||||||
|
assert_eq!(vec![Ident::new("title"), Ident::new("priority")], columns);
|
||||||
|
assert!(on.is_none());
|
||||||
|
assert!(replace_into);
|
||||||
|
assert_eq!(priority, Some(Delayed));
|
||||||
|
assert_eq!(
|
||||||
|
Some(Box::new(Query {
|
||||||
|
with: None,
|
||||||
|
body: Box::new(SetExpr::Values(Values {
|
||||||
|
explicit_row: false,
|
||||||
|
rows: vec![vec![
|
||||||
|
Expr::Value(Value::SingleQuotedString("Test Some Inserts".to_string())),
|
||||||
|
Expr::Value(number("1"))
|
||||||
|
]]
|
||||||
|
})),
|
||||||
|
order_by: vec![],
|
||||||
|
limit: None,
|
||||||
|
limit_by: vec![],
|
||||||
|
offset: None,
|
||||||
|
fetch: None,
|
||||||
|
locks: vec![],
|
||||||
|
for_clause: None,
|
||||||
|
})),
|
||||||
|
source
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_empty_row_insert() {
|
fn parse_empty_row_insert() {
|
||||||
let sql = "INSERT INTO tb () VALUES (), ()";
|
let sql = "INSERT INTO tb () VALUES (), ()";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue