diff --git a/src/parser.rs b/src/parser.rs index a8181e3fe..728b62c7b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1621,6 +1621,33 @@ impl<'a> Parser<'a> { } } + /// Parse a comma-separated list of 1+ SelectItem + pub fn parse_projection(&mut self) -> Result, ParserError> { + let mut values = vec![]; + loop { + values.push(self.parse_select_item()?); + if !self.consume_token(&Token::Comma) { + break; + } else if dialect_of!(self is BigQueryDialect) { + // BigQuery allows trailing commas. + // e.g. `SELECT 1, 2, FROM t` + // https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#trailing_commas + match self.peek_token() { + Token::Word(kw) + if keywords::RESERVED_FOR_COLUMN_ALIAS + .iter() + .any(|d| kw.keyword == *d) => + { + break + } + Token::RParen | Token::EOF => break, + _ => continue, + } + } + } + Ok(values) + } + /// Parse a comma-separated list of 1+ items accepted by `F` pub fn parse_comma_separated(&mut self, mut f: F) -> Result, ParserError> where @@ -3468,7 +3495,7 @@ impl<'a> Parser<'a> { None }; - let projection = self.parse_comma_separated(Parser::parse_select_item)?; + let projection = self.parse_projection()?; let into = if self.parse_keyword(Keyword::INTO) { let temporary = self diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index d866fc2f5..0a606c3ec 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -94,6 +94,21 @@ fn parse_table_identifiers() { test_table_ident("abc5.GROUP", vec![Ident::new("abc5"), Ident::new("GROUP")]); } +#[test] +fn parse_trailing_comma() { + for (sql, canonical) in [ + ("SELECT a,", "SELECT a"), + ("SELECT a, b,", "SELECT a, b"), + ("SELECT a, b AS c,", "SELECT a, b AS c"), + ("SELECT a, b AS c, FROM t", "SELECT a, b AS c FROM t"), + ("SELECT a, b, FROM t", "SELECT a, b FROM t"), + ("SELECT a, b, LIMIT 1", "SELECT a, b LIMIT 1"), + ("SELECT a, (SELECT 1, )", "SELECT a, (SELECT 1)"), + ] { + bigquery().one_statement_parses_to(sql, canonical); + } +} + #[test] fn parse_cast_type() { let sql = r#"SELECT SAFE_CAST(1 AS INT64)"#;