Support MySQL ROWS syntax for VALUES (#737)

* 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>
This commit is contained in:
Aljaž Mur Eržen 2022-12-01 15:46:55 +01:00 committed by GitHub
parent faf75b7161
commit 8e1c90c0d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 35 deletions

View file

@ -797,16 +797,22 @@ impl fmt::Display for Top {
#[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))]
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 { impl fmt::Display for Values {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "VALUES ")?; write!(f, "VALUES ")?;
let prefix = if self.explicit_row { "ROW" } else { "" };
let mut delim = ""; let mut delim = "";
for row in &self.0 { for row in &self.rows {
write!(f, "{}", delim)?; write!(f, "{}", delim)?;
delim = ", "; delim = ", ";
write!(f, "({})", display_comma_separated(row))?; write!(f, "{prefix}({})", display_comma_separated(row))?;
} }
Ok(()) Ok(())
} }

View file

@ -5642,13 +5642,19 @@ impl<'a> Parser<'a> {
} }
pub fn parse_values(&mut self) -> Result<Values, ParserError> { 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)?; parser.expect_token(&Token::LParen)?;
let exprs = parser.parse_comma_separated(Parser::parse_expr)?; let exprs = parser.parse_comma_separated(Parser::parse_expr)?;
parser.expect_token(&Token::RParen)?; parser.expect_token(&Token::RParen)?;
Ok(exprs) Ok(exprs)
})?; })?;
Ok(Values(values)) Ok(Values { explicit_row, rows })
} }
pub fn parse_start_transaction(&mut self) -> Result<Statement, ParserError> { pub fn parse_start_transaction(&mut self) -> Result<Statement, ParserError> {

View file

@ -88,7 +88,9 @@ fn parse_insert_values() {
assert_eq!(column, &Ident::new(expected_columns[index].clone())); assert_eq!(column, &Ident::new(expected_columns[index].clone()));
} }
match &*source.body { 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!(), _ => unreachable!(),
} }
} }
@ -460,6 +462,7 @@ fn parse_top_level() {
verified_stmt("(SELECT 1)"); verified_stmt("(SELECT 1)");
verified_stmt("((SELECT 1))"); verified_stmt("((SELECT 1))");
verified_stmt("VALUES (1)"); verified_stmt("VALUES (1)");
verified_stmt("VALUES ROW(1, true, 'a'), ROW(2, false, 'b')");
} }
#[test] #[test]
@ -4268,6 +4271,7 @@ fn parse_values() {
verified_stmt("SELECT * FROM (VALUES (1), (2), (3))"); 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), (2), (3)), (VALUES (1, 2, 3))");
verified_stmt("SELECT * FROM (VALUES (1)) UNION VALUES (1)"); 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] #[test]
@ -5608,11 +5612,14 @@ fn parse_merge() {
MergeClause::NotMatched { MergeClause::NotMatched {
predicate: None, predicate: None,
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")], columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
values: Values(vec![vec![ values: Values {
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]), explicit_row: false,
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]), rows: vec![vec![
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]), 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 { MergeClause::MatchedUpdate {
predicate: Some(Expr::BinaryOp { predicate: Some(Expr::BinaryOp {

View file

@ -660,20 +660,25 @@ fn parse_simple_insert() {
assert_eq!( assert_eq!(
Box::new(Query { Box::new(Query {
with: None, with: None,
body: Box::new(SetExpr::Values(Values(vec![ body: Box::new(SetExpr::Values(Values {
vec![ explicit_row: false,
Expr::Value(Value::SingleQuotedString("Test Some Inserts".to_string())), rows: vec![
Expr::Value(Value::Number("1".to_string(), false)) vec![
], Expr::Value(Value::SingleQuotedString(
vec![ "Test Some Inserts".to_string()
Expr::Value(Value::SingleQuotedString("Test Entry 2".to_string())), )),
Expr::Value(Value::Number("2".to_string(), false)) Expr::Value(Value::Number("1".to_string(), false))
], ],
vec![ vec![
Expr::Value(Value::SingleQuotedString("Test Entry 3".to_string())), Expr::Value(Value::SingleQuotedString("Test Entry 2".to_string())),
Expr::Value(Value::Number("3".to_string(), false)) 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![], order_by: vec![],
limit: None, limit: None,
offset: None, offset: None,
@ -717,16 +722,21 @@ fn parse_insert_with_on_duplicate_update() {
assert_eq!( assert_eq!(
Box::new(Query { Box::new(Query {
with: None, with: None,
body: Box::new(SetExpr::Values(Values(vec![vec![ body: Box::new(SetExpr::Values(Values {
Expr::Value(Value::SingleQuotedString("accounting_manager".to_string())), explicit_row: false,
Expr::Value(Value::SingleQuotedString( rows: vec![vec![
"Some description about the group".to_string() Expr::Value(Value::SingleQuotedString(
)), "accounting_manager".to_string()
Expr::Value(Value::Boolean(true)), )),
Expr::Value(Value::Boolean(true)), Expr::Value(Value::SingleQuotedString(
Expr::Value(Value::Boolean(true)), "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)),
Expr::Value(Value::Boolean(true)),
]]
})),
order_by: vec![], order_by: vec![],
limit: None, limit: None,
offset: None, offset: None,
@ -1209,3 +1219,9 @@ fn mysql_and_generic() -> TestedDialects {
dialects: vec![Box::new(MySqlDialect {}), Box::new(GenericDialect {})], 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)");
}

View file

@ -1067,7 +1067,9 @@ fn parse_prepare() {
Expr::Identifier("a3".into()), Expr::Identifier("a3".into()),
]]; ]];
match &*source.body { 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!(), _ => unreachable!(),
} }
} }