Skip to content

Commit beab9f1

Browse files
iffyioVedin
authored andcommitted
BigQuery: Support trailing commas in column definitions list (apache#1682)
1 parent d092637 commit beab9f1

File tree

4 files changed

+65
-20
lines changed

4 files changed

+65
-20
lines changed

src/dialect/bigquery.rs

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ impl Dialect for BigQueryDialect {
3131
true
3232
}
3333

34+
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_statement>
35+
fn supports_column_definition_trailing_commas(&self) -> bool {
36+
true
37+
}
38+
3439
fn is_identifier_start(&self, ch: char) -> bool {
3540
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_'
3641
}

src/dialect/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -405,11 +405,18 @@ pub trait Dialect: Debug + Any {
405405
}
406406

407407
/// Returns true if the dialect supports trailing commas in the `FROM` clause of a `SELECT` statement.
408-
/// /// Example: `SELECT 1 FROM T, U, LIMIT 1`
408+
/// Example: `SELECT 1 FROM T, U, LIMIT 1`
409409
fn supports_from_trailing_commas(&self) -> bool {
410410
false
411411
}
412412

413+
/// Returns true if the dialect supports trailing commas in the
414+
/// column definitions list of a `CREATE` statement.
415+
/// Example: `CREATE TABLE T (x INT, y TEXT,)`
416+
fn supports_column_definition_trailing_commas(&self) -> bool {
417+
false
418+
}
419+
413420
/// Returns true if the dialect supports double dot notation for object names
414421
///
415422
/// Example

src/parser/mod.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -6718,7 +6718,11 @@ impl<'a> Parser<'a> {
67186718
return self.expected("',' or ')' after column definition", self.peek_token());
67196719
};
67206720

6721-
if rparen && (!comma || self.options.trailing_commas) {
6721+
if rparen
6722+
&& (!comma
6723+
|| self.dialect.supports_column_definition_trailing_commas()
6724+
|| self.options.trailing_commas)
6725+
{
67226726
let _ = self.consume_token(&Token::RParen);
67236727
break;
67246728
}
@@ -9298,7 +9302,11 @@ impl<'a> Parser<'a> {
92989302
self.next_token();
92999303
Ok(vec![])
93009304
} else {
9301-
let cols = self.parse_comma_separated(Parser::parse_view_column)?;
9305+
let cols = self.parse_comma_separated_with_trailing_commas(
9306+
Parser::parse_view_column,
9307+
self.dialect.supports_column_definition_trailing_commas(),
9308+
None,
9309+
)?;
93029310
self.expect_token(&Token::RParen)?;
93039311
Ok(cols)
93049312
}

tests/sqlparser_common.rs

+42-17
Original file line numberDiff line numberDiff line change
@@ -10189,15 +10189,19 @@ fn parse_trailing_comma() {
1018910189
"Expected: column name or constraint definition, found: )".to_string()
1019010190
)
1019110191
);
10192+
10193+
let unsupported_dialects = all_dialects_where(|d| !d.supports_trailing_commas());
10194+
assert_eq!(
10195+
unsupported_dialects
10196+
.parse_sql_statements("SELECT * FROM track ORDER BY milliseconds,")
10197+
.unwrap_err(),
10198+
ParserError::ParserError("Expected: an expression, found: EOF".to_string())
10199+
);
1019210200
}
1019310201

1019410202
#[test]
1019510203
fn parse_projection_trailing_comma() {
10196-
// Some dialects allow trailing commas only in the projection
10197-
let trailing_commas = TestedDialects::new(vec![
10198-
Box::new(SnowflakeDialect {}),
10199-
Box::new(BigQueryDialect {}),
10200-
]);
10204+
let trailing_commas = all_dialects_where(|d| d.supports_projection_trailing_commas());
1020110205

1020210206
trailing_commas.one_statement_parses_to(
1020310207
"SELECT album_id, name, FROM track",
@@ -10210,20 +10214,14 @@ fn parse_projection_trailing_comma() {
1021010214

1021110215
trailing_commas.verified_stmt("SELECT DISTINCT ON (album_id) name FROM track");
1021210216

10217+
let unsupported_dialects = all_dialects_where(|d| {
10218+
!d.supports_projection_trailing_commas() && !d.supports_trailing_commas()
10219+
});
1021310220
assert_eq!(
10214-
trailing_commas
10215-
.parse_sql_statements("SELECT * FROM track ORDER BY milliseconds,")
10221+
unsupported_dialects
10222+
.parse_sql_statements("SELECT album_id, name, FROM track")
1021610223
.unwrap_err(),
10217-
ParserError::ParserError("Expected: an expression, found: EOF".to_string())
10218-
);
10219-
10220-
assert_eq!(
10221-
trailing_commas
10222-
.parse_sql_statements("CREATE TABLE employees (name text, age int,)")
10223-
.unwrap_err(),
10224-
ParserError::ParserError(
10225-
"Expected: column name or constraint definition, found: )".to_string()
10226-
),
10224+
ParserError::ParserError("Expected an expression, found: FROM".to_string())
1022710225
);
1022810226
}
1022910227

@@ -13061,6 +13059,33 @@ fn parse_overlaps() {
1306113059
verified_stmt("SELECT (DATE '2016-01-10', DATE '2016-02-01') OVERLAPS (DATE '2016-01-20', DATE '2016-02-10')");
1306213060
}
1306313061

13062+
#[test]
13063+
fn parse_column_definition_trailing_commas() {
13064+
let dialects = all_dialects_where(|d| d.supports_column_definition_trailing_commas());
13065+
13066+
dialects.one_statement_parses_to("CREATE TABLE T (x INT64,)", "CREATE TABLE T (x INT64)");
13067+
dialects.one_statement_parses_to(
13068+
"CREATE TABLE T (x INT64, y INT64, )",
13069+
"CREATE TABLE T (x INT64, y INT64)",
13070+
);
13071+
dialects.one_statement_parses_to(
13072+
"CREATE VIEW T (x, y, ) AS SELECT 1",
13073+
"CREATE VIEW T (x, y) AS SELECT 1",
13074+
);
13075+
13076+
let unsupported_dialects = all_dialects_where(|d| {
13077+
!d.supports_projection_trailing_commas() && !d.supports_trailing_commas()
13078+
});
13079+
assert_eq!(
13080+
unsupported_dialects
13081+
.parse_sql_statements("CREATE TABLE employees (name text, age int,)")
13082+
.unwrap_err(),
13083+
ParserError::ParserError(
13084+
"Expected: column name or constraint definition, found: )".to_string()
13085+
),
13086+
);
13087+
}
13088+
1306413089
#[test]
1306513090
fn test_trailing_commas_in_from() {
1306613091
let dialects = all_dialects_where(|d| d.supports_from_trailing_commas());

0 commit comments

Comments
 (0)