Skip to content

Commit bff25db

Browse files
committed
BigQuery: Fix column identifier reserved keywords list
Parser currently uses the same `RESERVED_FOR_COLUMN_ALIAS` keywords list to avoid lookahead when parsing column identifiers. This assumed that all listed keywords were reservered across all dialects which isn't the case. So that the following valid BigQuery statement previously failed due to `OFFSET` being flagged as a keyword. ```sql SELECT 1, OFFSET FROM T ``` This updates the parser to support dialect specific `RESERVED_FOR_COLUMN_ALIAS` list
1 parent c7c0de6 commit bff25db

File tree

5 files changed

+90
-17
lines changed

5 files changed

+90
-17
lines changed

src/dialect/bigquery.rs

+25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,27 @@
1616
// under the License.
1717

1818
use crate::dialect::Dialect;
19+
use crate::keywords::Keyword;
20+
21+
/// These keywords are disallowed as column identifiers. Such that
22+
/// `SELECT 5 AS <col> FROM T` is rejected by BigQuery.
23+
const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
24+
Keyword::WITH,
25+
Keyword::SELECT,
26+
Keyword::WHERE,
27+
Keyword::GROUP,
28+
Keyword::HAVING,
29+
Keyword::ORDER,
30+
Keyword::LATERAL,
31+
Keyword::LIMIT,
32+
Keyword::FETCH,
33+
Keyword::UNION,
34+
Keyword::EXCEPT,
35+
Keyword::INTERSECT,
36+
Keyword::FROM,
37+
Keyword::INTO,
38+
Keyword::END,
39+
];
1940

2041
/// A [`Dialect`] for [Google Bigquery](https://cloud.google.com/bigquery/)
2142
#[derive(Debug, Default)]
@@ -82,4 +103,8 @@ impl Dialect for BigQueryDialect {
82103
fn supports_timestamp_versioning(&self) -> bool {
83104
true
84105
}
106+
107+
fn get_reserved_keywords_for_column_identifier(&self) -> &'static [Keyword] {
108+
RESERVED_FOR_COLUMN_ALIAS
109+
}
85110
}

src/dialect/mod.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ pub trait Dialect: Debug + Any {
781781
keywords::RESERVED_FOR_IDENTIFIER.contains(&kw)
782782
}
783783

784-
// Returns reserved keywords when looking to parse a [TableFactor].
784+
/// Returns reserved keywords when looking to parse a [TableFactor].
785785
/// See [Self::supports_from_trailing_commas]
786786
fn get_reserved_keywords_for_table_factor(&self) -> &[Keyword] {
787787
keywords::RESERVED_FOR_TABLE_FACTOR
@@ -821,11 +821,24 @@ pub trait Dialect: Debug + Any {
821821
false
822822
}
823823

824+
/// Returns reserved keywords when looking to parse column
825+
/// identifiers.
826+
/// i.e. (unquoted) words that may not be used as column identifiers
827+
/// for the dialect.
828+
/// For example given: `SELECT <col> AS <alias> FROM T`. The returned
829+
/// keywords are not allowed in `<col>` and `<alias>`
830+
fn get_reserved_keywords_for_column_identifier(&self) -> &'static [Keyword] {
831+
keywords::RESERVED_FOR_COLUMN_ALIAS
832+
}
833+
824834
/// Returns true if the specified keyword should be parsed as a select item alias.
825835
/// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided
826836
/// to enable looking ahead if needed.
827837
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
828-
explicit || !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw)
838+
explicit
839+
|| !self
840+
.get_reserved_keywords_for_column_identifier()
841+
.contains(kw)
829842
}
830843

831844
/// Returns true if the specified keyword should be parsed as a table factor alias.

src/parser/mod.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -3989,7 +3989,8 @@ impl<'a> Parser<'a> {
39893989
trailing_commas: bool,
39903990
reserved_keywords: Option<&[Keyword]>,
39913991
) -> bool {
3992-
let reserved_keywords = reserved_keywords.unwrap_or(keywords::RESERVED_FOR_COLUMN_ALIAS);
3992+
let reserved_keywords = reserved_keywords
3993+
.unwrap_or_else(|| self.dialect.get_reserved_keywords_for_column_identifier());
39933994
if !self.consume_token(&Token::Comma) {
39943995
true
39953996
} else if trailing_commas {
@@ -11153,7 +11154,9 @@ impl<'a> Parser<'a> {
1115311154
};
1115411155

1115511156
let with_offset_alias = if with_offset {
11156-
match self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) {
11157+
match self.parse_optional_alias(
11158+
self.dialect.get_reserved_keywords_for_column_identifier(),
11159+
) {
1115711160
Ok(Some(alias)) => Some(alias),
1115811161
Ok(None) => None,
1115911162
Err(e) => return Err(e),

tests/sqlparser_bigquery.rs

+9
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ fn parse_raw_literal() {
213213
);
214214
}
215215

216+
#[test]
217+
fn parse_big_query_non_reserved_column_alias() {
218+
let sql = r#"SELECT OFFSET, EXPLAIN, ANALYZE, SORT, TOP, VIEW FROM T"#;
219+
bigquery().verified_stmt(sql);
220+
221+
let sql = r#"SELECT 1 AS OFFSET, 2 AS EXPLAIN, 3 AS ANALYZE FROM T"#;
222+
bigquery().verified_stmt(sql);
223+
}
224+
216225
#[test]
217226
fn parse_delete_statement() {
218227
let sql = "DELETE \"table\" WHERE 1";

tests/sqlparser_common.rs

+36-13
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,15 @@ fn parse_insert_default_values() {
253253

254254
#[test]
255255
fn parse_insert_select_returning() {
256-
verified_stmt("INSERT INTO t SELECT 1 RETURNING 2");
257-
let stmt = verified_stmt("INSERT INTO t SELECT x RETURNING x AS y");
256+
// Dialects that support `RETURNING` as a column identifier do
257+
// not support this syntax.
258+
let dialects = all_dialects_where(|d| {
259+
d.get_reserved_keywords_for_column_identifier()
260+
.contains(&Keyword::RETURNING)
261+
});
262+
263+
dialects.verified_stmt("INSERT INTO t SELECT 1 RETURNING 2");
264+
let stmt = dialects.verified_stmt("INSERT INTO t SELECT x RETURNING x AS y");
258265
match stmt {
259266
Statement::Insert(Insert {
260267
returning: Some(ret),
@@ -6897,9 +6904,6 @@ fn parse_union_except_intersect_minus() {
68976904
verified_stmt("SELECT 1 EXCEPT SELECT 2");
68986905
verified_stmt("SELECT 1 EXCEPT ALL SELECT 2");
68996906
verified_stmt("SELECT 1 EXCEPT DISTINCT SELECT 1");
6900-
verified_stmt("SELECT 1 MINUS SELECT 2");
6901-
verified_stmt("SELECT 1 MINUS ALL SELECT 2");
6902-
verified_stmt("SELECT 1 MINUS DISTINCT SELECT 1");
69036907
verified_stmt("SELECT 1 INTERSECT SELECT 2");
69046908
verified_stmt("SELECT 1 INTERSECT ALL SELECT 2");
69056909
verified_stmt("SELECT 1 INTERSECT DISTINCT SELECT 1");
@@ -6918,6 +6922,16 @@ fn parse_union_except_intersect_minus() {
69186922
verified_stmt("SELECT 1 AS x, 2 AS y INTERSECT BY NAME SELECT 9 AS y, 8 AS x");
69196923
verified_stmt("SELECT 1 AS x, 2 AS y INTERSECT ALL BY NAME SELECT 9 AS y, 8 AS x");
69206924
verified_stmt("SELECT 1 AS x, 2 AS y INTERSECT DISTINCT BY NAME SELECT 9 AS y, 8 AS x");
6925+
6926+
// Dialects that support `MINUS` as column identifier
6927+
// do not support `MINUS` as a set operator.
6928+
let dialects = all_dialects_where(|d| {
6929+
d.get_reserved_keywords_for_column_identifier()
6930+
.contains(&Keyword::MINUS)
6931+
});
6932+
dialects.verified_stmt("SELECT 1 MINUS SELECT 2");
6933+
dialects.verified_stmt("SELECT 1 MINUS ALL SELECT 2");
6934+
dialects.verified_stmt("SELECT 1 MINUS DISTINCT SELECT 1");
69216935
}
69226936

69236937
#[test]
@@ -7594,19 +7608,28 @@ fn parse_invalid_subquery_without_parens() {
75947608

75957609
#[test]
75967610
fn parse_offset() {
7611+
// Dialects that support `OFFSET` as column identifiers
7612+
// don't support this syntax.
7613+
let dialects = all_dialects_where(|d| {
7614+
d.get_reserved_keywords_for_column_identifier()
7615+
.contains(&Keyword::OFFSET)
7616+
});
7617+
75977618
let expect = Some(Offset {
75987619
value: Expr::Value(number("2")),
75997620
rows: OffsetRows::Rows,
76007621
});
7601-
let ast = verified_query("SELECT foo FROM bar OFFSET 2 ROWS");
7622+
let ast = dialects.verified_query("SELECT foo FROM bar OFFSET 2 ROWS");
76027623
assert_eq!(ast.offset, expect);
7603-
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS");
7624+
let ast = dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS");
76047625
assert_eq!(ast.offset, expect);
7605-
let ast = verified_query("SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS");
7626+
let ast = dialects.verified_query("SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS");
76067627
assert_eq!(ast.offset, expect);
7607-
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS");
7628+
let ast =
7629+
dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS");
76087630
assert_eq!(ast.offset, expect);
7609-
let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS");
7631+
let ast =
7632+
dialects.verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS");
76107633
assert_eq!(ast.offset, expect);
76117634
match *ast.body {
76127635
SetExpr::Select(s) => match only(s.from).relation {
@@ -7617,23 +7640,23 @@ fn parse_offset() {
76177640
},
76187641
_ => panic!("Test broke"),
76197642
}
7620-
let ast = verified_query("SELECT 'foo' OFFSET 0 ROWS");
7643+
let ast = dialects.verified_query("SELECT 'foo' OFFSET 0 ROWS");
76217644
assert_eq!(
76227645
ast.offset,
76237646
Some(Offset {
76247647
value: Expr::Value(number("0")),
76257648
rows: OffsetRows::Rows,
76267649
})
76277650
);
7628-
let ast = verified_query("SELECT 'foo' OFFSET 1 ROW");
7651+
let ast = dialects.verified_query("SELECT 'foo' OFFSET 1 ROW");
76297652
assert_eq!(
76307653
ast.offset,
76317654
Some(Offset {
76327655
value: Expr::Value(number("1")),
76337656
rows: OffsetRows::Row,
76347657
})
76357658
);
7636-
let ast = verified_query("SELECT 'foo' OFFSET 1");
7659+
let ast = dialects.verified_query("SELECT 'foo' OFFSET 1");
76377660
assert_eq!(
76387661
ast.offset,
76397662
Some(Offset {

0 commit comments

Comments
 (0)