diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 277b9e3ba..d31832a62 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -284,6 +284,8 @@ pub enum Expr { Cube(Vec>), /// The `ROLLUP` expr. Rollup(Vec>), + /// ROW / TUPLE a single value, such as `SELECT (1, 2)` + Tuple(Vec), } impl fmt::Display for Expr { @@ -445,6 +447,9 @@ impl fmt::Display for Expr { write!(f, ")") } + Expr::Tuple(exprs) => { + write!(f, "({})", display_comma_separated(exprs)) + } } } } diff --git a/src/parser.rs b/src/parser.rs index 5faa2ca44..29833f946 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -494,7 +494,12 @@ impl<'a> Parser<'a> { self.prev_token(); Expr::Subquery(Box::new(self.parse_query()?)) } else { - Expr::Nested(Box::new(self.parse_expr()?)) + let exprs = self.parse_comma_separated(Parser::parse_expr)?; + match exprs.len() { + 0 => unreachable!(), // parse_comma_separated ensures 1 or more + 1 => Expr::Nested(Box::new(exprs.into_iter().next().unwrap())), + _ => Expr::Tuple(exprs), + } }; self.expect_token(&Token::RParen)?; Ok(expr) @@ -2680,19 +2685,8 @@ impl<'a> Parser<'a> { None }; - // Not Sure if Top should be cheked here as well. Trino doesn't support TOP. - let is_l_paren = if distinct { - self.consume_token(&Token::LParen) - } else { - false - }; - let projection = self.parse_comma_separated(Parser::parse_select_item)?; - if is_l_paren && !self.consume_token(&Token::RParen) { - return self.expected(")", self.peek_token()); - } - // Note that for keywords to be properly handled here, they need to be // added to `RESERVED_FOR_COLUMN_ALIAS` / `RESERVED_FOR_TABLE_ALIAS`, // otherwise they may be parsed as an alias as part of the `projection` diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 07d9f8560..a7a8e5048 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -332,7 +332,6 @@ fn parse_select_distinct_two_fields() { let sql = "SELECT DISTINCT name, id FROM customer"; let select = verified_only_select(sql); assert!(select.distinct); - one_statement_parses_to("SELECT DISTINCT (name, id) FROM customer", sql); assert_eq!( &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name"))), &select.projection[0] @@ -343,6 +342,19 @@ fn parse_select_distinct_two_fields() { ); } +#[test] +fn parse_select_distinct_tuple() { + let sql = "SELECT DISTINCT (name, id) FROM customer"; + let select = verified_only_select(sql); + assert_eq!( + &vec![SelectItem::UnnamedExpr(Expr::Tuple(vec![ + Expr::Identifier(Ident::new("name")), + Expr::Identifier(Ident::new("id")), + ]))], + &select.projection + ); +} + #[test] fn parse_select_distinct_missing_paren() { let result = parse_sql_statements("SELECT DISTINCT (name, id FROM customer"); @@ -1033,6 +1045,44 @@ fn parse_between_with_expr() { ) } +#[test] +fn parse_tuples() { + let sql = "SELECT (1, 2), (1), ('foo', 3, baz)"; + let select = verified_only_select(sql); + assert_eq!( + vec![ + SelectItem::UnnamedExpr(Expr::Tuple(vec![ + Expr::Value(number("1")), + Expr::Value(number("2")) + ])), + SelectItem::UnnamedExpr(Expr::Nested(Box::new(Expr::Value(number("1"))))), + SelectItem::UnnamedExpr(Expr::Tuple(vec![ + Expr::Value(Value::SingleQuotedString("foo".into())), + Expr::Value(number("3")), + Expr::Identifier(Ident::new("baz")) + ])) + ], + select.projection + ); +} + +#[test] +fn parse_tuple_invalid() { + let sql = "select (1"; + let res = parse_sql_statements(sql); + assert_eq!( + ParserError::ParserError("Expected ), found: EOF".to_string()), + res.unwrap_err() + ); + + let sql = "select (), 2"; + let res = parse_sql_statements(sql); + assert_eq!( + ParserError::ParserError("Expected an expression:, found: )".to_string()), + res.unwrap_err() + ); +} + #[test] fn parse_select_order_by() { fn chk(sql: &str) { @@ -1121,6 +1171,12 @@ fn parse_select_group_by() { ], select.group_by ); + + // Tuples can also be in the set + one_statement_parses_to( + "SELECT id, fname, lname FROM customer GROUP BY (lname, fname)", + "SELECT id, fname, lname FROM customer GROUP BY (lname, fname)", + ); } #[test]