BigQuery: Support trailing commas in column definitions list (#1682)

This commit is contained in:
Ifeanyi Ubah 2025-01-25 16:01:33 +01:00 committed by GitHub
parent ef072be9e1
commit fd6c98e933
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 65 additions and 20 deletions

View file

@ -31,6 +31,11 @@ impl Dialect for BigQueryDialect {
true
}
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_statement>
fn supports_column_definition_trailing_commas(&self) -> bool {
true
}
fn is_identifier_start(&self, ch: char) -> bool {
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_'
}

View file

@ -405,11 +405,18 @@ pub trait Dialect: Debug + Any {
}
/// Returns true if the dialect supports trailing commas in the `FROM` clause of a `SELECT` statement.
/// /// Example: `SELECT 1 FROM T, U, LIMIT 1`
/// Example: `SELECT 1 FROM T, U, LIMIT 1`
fn supports_from_trailing_commas(&self) -> bool {
false
}
/// Returns true if the dialect supports trailing commas in the
/// column definitions list of a `CREATE` statement.
/// Example: `CREATE TABLE T (x INT, y TEXT,)`
fn supports_column_definition_trailing_commas(&self) -> bool {
false
}
/// Returns true if the dialect supports double dot notation for object names
///
/// Example

View file

@ -6718,7 +6718,11 @@ impl<'a> Parser<'a> {
return self.expected("',' or ')' after column definition", self.peek_token());
};
if rparen && (!comma || self.options.trailing_commas) {
if rparen
&& (!comma
|| self.dialect.supports_column_definition_trailing_commas()
|| self.options.trailing_commas)
{
let _ = self.consume_token(&Token::RParen);
break;
}
@ -9298,7 +9302,11 @@ impl<'a> Parser<'a> {
self.next_token();
Ok(vec![])
} else {
let cols = self.parse_comma_separated(Parser::parse_view_column)?;
let cols = self.parse_comma_separated_with_trailing_commas(
Parser::parse_view_column,
self.dialect.supports_column_definition_trailing_commas(),
None,
)?;
self.expect_token(&Token::RParen)?;
Ok(cols)
}

View file

@ -10189,15 +10189,19 @@ fn parse_trailing_comma() {
"Expected: column name or constraint definition, found: )".to_string()
)
);
let unsupported_dialects = all_dialects_where(|d| !d.supports_trailing_commas());
assert_eq!(
unsupported_dialects
.parse_sql_statements("SELECT * FROM track ORDER BY milliseconds,")
.unwrap_err(),
ParserError::ParserError("Expected: an expression, found: EOF".to_string())
);
}
#[test]
fn parse_projection_trailing_comma() {
// Some dialects allow trailing commas only in the projection
let trailing_commas = TestedDialects::new(vec![
Box::new(SnowflakeDialect {}),
Box::new(BigQueryDialect {}),
]);
let trailing_commas = all_dialects_where(|d| d.supports_projection_trailing_commas());
trailing_commas.one_statement_parses_to(
"SELECT album_id, name, FROM track",
@ -10210,20 +10214,14 @@ fn parse_projection_trailing_comma() {
trailing_commas.verified_stmt("SELECT DISTINCT ON (album_id) name FROM track");
let unsupported_dialects = all_dialects_where(|d| {
!d.supports_projection_trailing_commas() && !d.supports_trailing_commas()
});
assert_eq!(
trailing_commas
.parse_sql_statements("SELECT * FROM track ORDER BY milliseconds,")
unsupported_dialects
.parse_sql_statements("SELECT album_id, name, FROM track")
.unwrap_err(),
ParserError::ParserError("Expected: an expression, found: EOF".to_string())
);
assert_eq!(
trailing_commas
.parse_sql_statements("CREATE TABLE employees (name text, age int,)")
.unwrap_err(),
ParserError::ParserError(
"Expected: column name or constraint definition, found: )".to_string()
),
ParserError::ParserError("Expected an expression, found: FROM".to_string())
);
}
@ -13061,6 +13059,33 @@ fn parse_overlaps() {
verified_stmt("SELECT (DATE '2016-01-10', DATE '2016-02-01') OVERLAPS (DATE '2016-01-20', DATE '2016-02-10')");
}
#[test]
fn parse_column_definition_trailing_commas() {
let dialects = all_dialects_where(|d| d.supports_column_definition_trailing_commas());
dialects.one_statement_parses_to("CREATE TABLE T (x INT64,)", "CREATE TABLE T (x INT64)");
dialects.one_statement_parses_to(
"CREATE TABLE T (x INT64, y INT64, )",
"CREATE TABLE T (x INT64, y INT64)",
);
dialects.one_statement_parses_to(
"CREATE VIEW T (x, y, ) AS SELECT 1",
"CREATE VIEW T (x, y) AS SELECT 1",
);
let unsupported_dialects = all_dialects_where(|d| {
!d.supports_projection_trailing_commas() && !d.supports_trailing_commas()
});
assert_eq!(
unsupported_dialects
.parse_sql_statements("CREATE TABLE employees (name text, age int,)")
.unwrap_err(),
ParserError::ParserError(
"Expected: column name or constraint definition, found: )".to_string()
),
);
}
#[test]
fn test_trailing_commas_in_from() {
let dialects = all_dialects_where(|d| d.supports_from_trailing_commas());