Skip to content

Commit 9e3cbc4

Browse files
barsela1iffyio
authored andcommitted
Support trailing commas in FROM clause (apache#1645)
Co-authored-by: Ifeanyi Ubah <[email protected]>
1 parent ef8487f commit 9e3cbc4

File tree

5 files changed

+88
-12
lines changed

5 files changed

+88
-12
lines changed

src/dialect/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,12 @@ pub trait Dialect: Debug + Any {
404404
self.supports_trailing_commas()
405405
}
406406

407+
/// 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`
409+
fn supports_from_trailing_commas(&self) -> bool {
410+
false
411+
}
412+
407413
/// Returns true if the dialect supports double dot notation for object names
408414
///
409415
/// Example
@@ -775,6 +781,12 @@ pub trait Dialect: Debug + Any {
775781
keywords::RESERVED_FOR_IDENTIFIER.contains(&kw)
776782
}
777783

784+
// Returns reserved keywords when looking to parse a [TableFactor].
785+
/// See [Self::supports_from_trailing_commas]
786+
fn get_reserved_keywords_for_table_factor(&self) -> &[Keyword] {
787+
keywords::RESERVED_FOR_TABLE_FACTOR
788+
}
789+
778790
/// Returns true if this dialect supports the `TABLESAMPLE` option
779791
/// before the table alias option. For example:
780792
///

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ impl Dialect for SnowflakeDialect {
5454
true
5555
}
5656

57+
fn supports_from_trailing_commas(&self) -> bool {
58+
true
59+
}
60+
5761
// Snowflake supports double-dot notation when the schema name is not specified
5862
// In this case the default PUBLIC schema is used
5963
//

src/keywords.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,16 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
999999
Keyword::END,
10001000
];
10011001

1002+
// Global list of reserved keywords alloweed after FROM.
1003+
// Parser should call Dialect::get_reserved_keyword_after_from
1004+
// to allow for each dialect to customize the list.
1005+
pub const RESERVED_FOR_TABLE_FACTOR: &[Keyword] = &[
1006+
Keyword::INTO,
1007+
Keyword::LIMIT,
1008+
Keyword::HAVING,
1009+
Keyword::WHERE,
1010+
];
1011+
10021012
/// Global list of reserved keywords that cannot be parsed as identifiers
10031013
/// without special handling like quoting. Parser should call `Dialect::is_reserved_for_identifier`
10041014
/// to allow for each dialect to customize the list.

src/parser/mod.rs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3950,7 +3950,11 @@ impl<'a> Parser<'a> {
39503950
let trailing_commas =
39513951
self.options.trailing_commas | self.dialect.supports_projection_trailing_commas();
39523952

3953-
self.parse_comma_separated_with_trailing_commas(|p| p.parse_select_item(), trailing_commas)
3953+
self.parse_comma_separated_with_trailing_commas(
3954+
|p| p.parse_select_item(),
3955+
trailing_commas,
3956+
None,
3957+
)
39543958
}
39553959

39563960
pub fn parse_actions_list(&mut self) -> Result<Vec<ParsedAction>, ParserError> {
@@ -3976,20 +3980,32 @@ impl<'a> Parser<'a> {
39763980
Ok(values)
39773981
}
39783982

3983+
/// Parse a list of [TableWithJoins]
3984+
fn parse_table_with_joins(&mut self) -> Result<Vec<TableWithJoins>, ParserError> {
3985+
let trailing_commas = self.dialect.supports_from_trailing_commas();
3986+
3987+
self.parse_comma_separated_with_trailing_commas(
3988+
Parser::parse_table_and_joins,
3989+
trailing_commas,
3990+
Some(self.dialect.get_reserved_keywords_for_table_factor()),
3991+
)
3992+
}
3993+
39793994
/// Parse the comma of a comma-separated syntax element.
39803995
/// Allows for control over trailing commas
39813996
/// Returns true if there is a next element
3982-
fn is_parse_comma_separated_end_with_trailing_commas(&mut self, trailing_commas: bool) -> bool {
3997+
fn is_parse_comma_separated_end_with_trailing_commas(
3998+
&mut self,
3999+
trailing_commas: bool,
4000+
reserved_keywords: Option<&[Keyword]>,
4001+
) -> bool {
4002+
let reserved_keywords = reserved_keywords.unwrap_or(keywords::RESERVED_FOR_COLUMN_ALIAS);
39834003
if !self.consume_token(&Token::Comma) {
39844004
true
39854005
} else if trailing_commas {
39864006
let token = self.peek_token().token;
39874007
match token {
3988-
Token::Word(ref kw)
3989-
if keywords::RESERVED_FOR_COLUMN_ALIAS.contains(&kw.keyword) =>
3990-
{
3991-
true
3992-
}
4008+
Token::Word(ref kw) if reserved_keywords.contains(&kw.keyword) => true,
39934009
Token::RParen | Token::SemiColon | Token::EOF | Token::RBracket | Token::RBrace => {
39944010
true
39954011
}
@@ -4003,15 +4019,15 @@ impl<'a> Parser<'a> {
40034019
/// Parse the comma of a comma-separated syntax element.
40044020
/// Returns true if there is a next element
40054021
fn is_parse_comma_separated_end(&mut self) -> bool {
4006-
self.is_parse_comma_separated_end_with_trailing_commas(self.options.trailing_commas)
4022+
self.is_parse_comma_separated_end_with_trailing_commas(self.options.trailing_commas, None)
40074023
}
40084024

40094025
/// Parse a comma-separated list of 1+ items accepted by `F`
40104026
pub fn parse_comma_separated<T, F>(&mut self, f: F) -> Result<Vec<T>, ParserError>
40114027
where
40124028
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
40134029
{
4014-
self.parse_comma_separated_with_trailing_commas(f, self.options.trailing_commas)
4030+
self.parse_comma_separated_with_trailing_commas(f, self.options.trailing_commas, None)
40154031
}
40164032

40174033
/// Parse a comma-separated list of 1+ items accepted by `F`
@@ -4020,14 +4036,18 @@ impl<'a> Parser<'a> {
40204036
&mut self,
40214037
mut f: F,
40224038
trailing_commas: bool,
4039+
reserved_keywords: Option<&[Keyword]>,
40234040
) -> Result<Vec<T>, ParserError>
40244041
where
40254042
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
40264043
{
40274044
let mut values = vec![];
40284045
loop {
40294046
values.push(f(self)?);
4030-
if self.is_parse_comma_separated_end_with_trailing_commas(trailing_commas) {
4047+
if self.is_parse_comma_separated_end_with_trailing_commas(
4048+
trailing_commas,
4049+
reserved_keywords,
4050+
) {
40314051
break;
40324052
}
40334053
}
@@ -10083,7 +10103,7 @@ impl<'a> Parser<'a> {
1008310103
// or `from`.
1008410104

1008510105
let from = if self.parse_keyword(Keyword::FROM) {
10086-
self.parse_comma_separated(Parser::parse_table_and_joins)?
10106+
self.parse_table_with_joins()?
1008710107
} else {
1008810108
vec![]
1008910109
};

tests/sqlparser_common.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12960,8 +12960,38 @@ fn parse_update_from_before_select() {
1296012960
parse_sql_statements(query).unwrap_err()
1296112961
);
1296212962
}
12963-
1296412963
#[test]
1296512964
fn parse_overlaps() {
1296612965
verified_stmt("SELECT (DATE '2016-01-10', DATE '2016-02-01') OVERLAPS (DATE '2016-01-20', DATE '2016-02-10')");
1296712966
}
12967+
12968+
#[test]
12969+
fn test_trailing_commas_in_from() {
12970+
let dialects = all_dialects_where(|d| d.supports_from_trailing_commas());
12971+
dialects.verified_only_select_with_canonical("SELECT 1, 2 FROM t,", "SELECT 1, 2 FROM t");
12972+
12973+
dialects
12974+
.verified_only_select_with_canonical("SELECT 1, 2 FROM t1, t2,", "SELECT 1, 2 FROM t1, t2");
12975+
12976+
let sql = "SELECT a, FROM b, LIMIT 1";
12977+
let _ = dialects.parse_sql_statements(sql).unwrap();
12978+
12979+
let sql = "INSERT INTO a SELECT b FROM c,";
12980+
let _ = dialects.parse_sql_statements(sql).unwrap();
12981+
12982+
let sql = "SELECT a FROM b, HAVING COUNT(*) > 1";
12983+
let _ = dialects.parse_sql_statements(sql).unwrap();
12984+
12985+
let sql = "SELECT a FROM b, WHERE c = 1";
12986+
let _ = dialects.parse_sql_statements(sql).unwrap();
12987+
12988+
// nasted
12989+
let sql = "SELECT 1, 2 FROM (SELECT * FROM t,),";
12990+
let _ = dialects.parse_sql_statements(sql).unwrap();
12991+
12992+
// multiple_subqueries
12993+
dialects.verified_only_select_with_canonical(
12994+
"SELECT 1, 2 FROM (SELECT * FROM t1), (SELECT * FROM t2),",
12995+
"SELECT 1, 2 FROM (SELECT * FROM t1), (SELECT * FROM t2)",
12996+
);
12997+
}

0 commit comments

Comments
 (0)