Skip to content

Commit

Permalink
Support MySQL ROWS syntax for VALUES (#737)
Browse files Browse the repository at this point in the history
* Adapt VALUES to MySQL dialect

* Update src/ast/query.rs

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>

* remove *requirement* for ROW

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
  • Loading branch information
aljazerzen and alamb committed Dec 1, 2022
1 parent faf75b7 commit 8e1c90c
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 35 deletions.
12 changes: 9 additions & 3 deletions src/ast/query.rs
Expand Up @@ -797,16 +797,22 @@ impl fmt::Display for Top {

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Values(pub Vec<Vec<Expr>>);
pub struct Values {
/// Was there an explict ROWs keyword (MySQL)?
/// <https://dev.mysql.com/doc/refman/8.0/en/values.html>
pub explicit_row: bool,
pub rows: Vec<Vec<Expr>>,
}

impl fmt::Display for Values {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "VALUES ")?;
let prefix = if self.explicit_row { "ROW" } else { "" };
let mut delim = "";
for row in &self.0 {
for row in &self.rows {
write!(f, "{}", delim)?;
delim = ", ";
write!(f, "({})", display_comma_separated(row))?;
write!(f, "{prefix}({})", display_comma_separated(row))?;
}
Ok(())
}
Expand Down
10 changes: 8 additions & 2 deletions src/parser.rs
Expand Up @@ -5642,13 +5642,19 @@ impl<'a> Parser<'a> {
}

pub fn parse_values(&mut self) -> Result<Values, ParserError> {
let values = self.parse_comma_separated(|parser| {
let mut explicit_row = false;

let rows = self.parse_comma_separated(|parser| {
if parser.parse_keyword(Keyword::ROW) {
explicit_row = true;
}

parser.expect_token(&Token::LParen)?;
let exprs = parser.parse_comma_separated(Parser::parse_expr)?;
parser.expect_token(&Token::RParen)?;
Ok(exprs)
})?;
Ok(Values(values))
Ok(Values { explicit_row, rows })
}

pub fn parse_start_transaction(&mut self) -> Result<Statement, ParserError> {
Expand Down
19 changes: 13 additions & 6 deletions tests/sqlparser_common.rs
Expand Up @@ -88,7 +88,9 @@ fn parse_insert_values() {
assert_eq!(column, &Ident::new(expected_columns[index].clone()));
}
match &*source.body {
SetExpr::Values(Values(values)) => assert_eq!(values.as_slice(), expected_rows),
SetExpr::Values(Values { rows, .. }) => {
assert_eq!(rows.as_slice(), expected_rows)
}
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -460,6 +462,7 @@ fn parse_top_level() {
verified_stmt("(SELECT 1)");
verified_stmt("((SELECT 1))");
verified_stmt("VALUES (1)");
verified_stmt("VALUES ROW(1, true, 'a'), ROW(2, false, 'b')");
}

#[test]
Expand Down Expand Up @@ -4268,6 +4271,7 @@ fn parse_values() {
verified_stmt("SELECT * FROM (VALUES (1), (2), (3))");
verified_stmt("SELECT * FROM (VALUES (1), (2), (3)), (VALUES (1, 2, 3))");
verified_stmt("SELECT * FROM (VALUES (1)) UNION VALUES (1)");
verified_stmt("SELECT * FROM (VALUES ROW(1, true, 'a'), ROW(2, false, 'b')) AS t (a, b, c)");
}

#[test]
Expand Down Expand Up @@ -5608,11 +5612,14 @@ fn parse_merge() {
MergeClause::NotMatched {
predicate: None,
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
values: Values(vec![vec![
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]),
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]),
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]),
]]),
values: Values {
explicit_row: false,
rows: vec![vec![
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]),
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]),
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]),
]]
},
},
MergeClause::MatchedUpdate {
predicate: Some(Expr::BinaryOp {
Expand Down
62 changes: 39 additions & 23 deletions tests/sqlparser_mysql.rs
Expand Up @@ -660,20 +660,25 @@ fn parse_simple_insert() {
assert_eq!(
Box::new(Query {
with: None,
body: Box::new(SetExpr::Values(Values(vec![
vec![
Expr::Value(Value::SingleQuotedString("Test Some Inserts".to_string())),
Expr::Value(Value::Number("1".to_string(), false))
],
vec![
Expr::Value(Value::SingleQuotedString("Test Entry 2".to_string())),
Expr::Value(Value::Number("2".to_string(), false))
],
vec![
Expr::Value(Value::SingleQuotedString("Test Entry 3".to_string())),
Expr::Value(Value::Number("3".to_string(), false))
body: Box::new(SetExpr::Values(Values {
explicit_row: false,
rows: vec![
vec![
Expr::Value(Value::SingleQuotedString(
"Test Some Inserts".to_string()
)),
Expr::Value(Value::Number("1".to_string(), false))
],
vec![
Expr::Value(Value::SingleQuotedString("Test Entry 2".to_string())),
Expr::Value(Value::Number("2".to_string(), false))
],
vec![
Expr::Value(Value::SingleQuotedString("Test Entry 3".to_string())),
Expr::Value(Value::Number("3".to_string(), false))
]
]
]))),
})),
order_by: vec![],
limit: None,
offset: None,
Expand Down Expand Up @@ -717,16 +722,21 @@ fn parse_insert_with_on_duplicate_update() {
assert_eq!(
Box::new(Query {
with: None,
body: Box::new(SetExpr::Values(Values(vec![vec![
Expr::Value(Value::SingleQuotedString("accounting_manager".to_string())),
Expr::Value(Value::SingleQuotedString(
"Some description about the group".to_string()
)),
Expr::Value(Value::Boolean(true)),
Expr::Value(Value::Boolean(true)),
Expr::Value(Value::Boolean(true)),
Expr::Value(Value::Boolean(true)),
]]))),
body: Box::new(SetExpr::Values(Values {
explicit_row: false,
rows: vec![vec![
Expr::Value(Value::SingleQuotedString(
"accounting_manager".to_string()
)),
Expr::Value(Value::SingleQuotedString(
"Some description about the group".to_string()
)),
Expr::Value(Value::Boolean(true)),
Expr::Value(Value::Boolean(true)),
Expr::Value(Value::Boolean(true)),
Expr::Value(Value::Boolean(true)),
]]
})),
order_by: vec![],
limit: None,
offset: None,
Expand Down Expand Up @@ -1209,3 +1219,9 @@ fn mysql_and_generic() -> TestedDialects {
dialects: vec![Box::new(MySqlDialect {}), Box::new(GenericDialect {})],
}
}

#[test]
fn parse_values() {
mysql().verified_stmt("VALUES ROW(1, true, 'a')");
mysql().verified_stmt("SELECT a, c FROM (VALUES ROW(1, true, 'a'), ROW(2, false, 'b'), ROW(3, false, 'c')) AS t (a, b, c)");
}
4 changes: 3 additions & 1 deletion tests/sqlparser_postgres.rs
Expand Up @@ -1067,7 +1067,9 @@ fn parse_prepare() {
Expr::Identifier("a3".into()),
]];
match &*source.body {
SetExpr::Values(Values(values)) => assert_eq!(values.as_slice(), &expected_values),
SetExpr::Values(Values { rows, .. }) => {
assert_eq!(rows.as_slice(), &expected_values)
}
_ => unreachable!(),
}
}
Expand Down

0 comments on commit 8e1c90c

Please sign in to comment.