diff --git a/src/ast/query.rs b/src/ast/query.rs index 4f3d79cdf..689bfae57 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -709,16 +709,22 @@ impl fmt::Display for Top { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Values(pub Vec>); +pub struct Values { + /// Was there an explict ROWs keyword (MySQL)? + /// + pub explicit_row: bool, + pub rows: Vec>, +} 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(()) } diff --git a/src/parser.rs b/src/parser.rs index e537eefae..52fe57bcf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5483,13 +5483,19 @@ impl<'a> Parser<'a> { } pub fn parse_values(&mut self) -> Result { - 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 { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 6790bbff6..056c26b97 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -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!(), } } @@ -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] @@ -4236,6 +4239,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] @@ -5505,11 +5509,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 { diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index e91cea0ef..947f889e8 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -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, @@ -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, @@ -1183,3 +1193,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)"); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 5cc333935..05c601f0c 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -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!(), } }