Support Mysql REPLACE statement and PRIORITY clause of INSERT (#1072)

This commit is contained in:
Mehmet Emin KARAKAŞ 2023-12-24 15:24:53 +03:00 committed by GitHub
parent 7ea47c71fb
commit c62ecb1100
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 211 additions and 2 deletions

View file

@ -1443,6 +1443,10 @@ pub enum Statement {
on: Option<OnInsert>,
/// RETURNING
returning: Option<Vec<SelectItem>>,
/// Only for mysql
replace_into: bool,
/// Only for mysql
priority: Option<MysqlInsertPriority>,
},
// TODO: Support ROW FORMAT
Directory {
@ -2404,18 +2408,29 @@ impl fmt::Display for Statement {
table,
on,
returning,
replace_into,
priority,
} => {
if let Some(action) = or {
write!(f, "INSERT OR {action} INTO {table_name} ")?;
} else {
write!(
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,
ignore = if *ignore { " IGNORE" } else { "" },
over = if *overwrite { " OVERWRITE" } else { "" },
int = if *into { " INTO" } else { "" },
tbl = if *table { " TABLE" } else { "" }
tbl = if *table { " TABLE" } else { "" },
)?;
}
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)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

View file

@ -210,6 +210,7 @@ define_keywords!(
DECLARE,
DEFAULT,
DEFERRED,
DELAYED,
DELETE,
DELIMITED,
DELIMITER,
@ -315,6 +316,7 @@ define_keywords!(
HASH,
HAVING,
HEADER,
HIGH_PRIORITY,
HISTORY,
HIVEVAR,
HOLD,

View file

@ -490,6 +490,7 @@ impl<'a> Parser<'a> {
Keyword::FETCH => Ok(self.parse_fetch_statement()?),
Keyword::DELETE => Ok(self.parse_delete()?),
Keyword::INSERT => Ok(self.parse_insert()?),
Keyword::REPLACE => Ok(self.parse_replace()?),
Keyword::UNCACHE => Ok(self.parse_uncache_table()?),
Keyword::UPDATE => Ok(self.parse_update()?),
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
pub fn parse_insert(&mut self) -> Result<Statement, ParserError> {
let or = if !dialect_of!(self is SQLiteDialect) {
@ -7399,9 +7414,23 @@ impl<'a> Parser<'a> {
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)
&& self.parse_keyword(Keyword::IGNORE);
let replace_into = false;
let action = self.parse_one_of_keywords(&[Keyword::INTO, Keyword::OVERWRITE]);
let into = action == Some(Keyword::INTO);
let overwrite = action == Some(Keyword::OVERWRITE);
@ -7511,6 +7540,8 @@ impl<'a> Parser<'a> {
table,
on,
returning,
replace_into,
priority,
})
}
}

View file

@ -107,6 +107,17 @@ fn parse_insert_values() {
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]
fn parse_insert_default_values() {
let insert_with_default_values = verified_stmt("INSERT INTO test_table DEFAULT VALUES");

View file

@ -16,6 +16,7 @@
use matches::assert_matches;
use sqlparser::ast::Expr;
use sqlparser::ast::MysqlInsertPriority::{Delayed, HighPriority, LowPriority};
use sqlparser::ast::Value;
use sqlparser::ast::*;
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]
fn parse_empty_row_insert() {
let sql = "INSERT INTO tb () VALUES (), ()";