diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs
index 716174391..25d8456af 100644
--- a/src/dialect/bigquery.rs
+++ b/src/dialect/bigquery.rs
@@ -16,6 +16,28 @@
// under the License.
use crate::dialect::Dialect;
+use crate::keywords::Keyword;
+use crate::parser::Parser;
+
+/// These keywords are disallowed as column identifiers. Such that
+/// `SELECT 5 AS
FROM T` is rejected by BigQuery.
+const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
+ Keyword::WITH,
+ Keyword::SELECT,
+ Keyword::WHERE,
+ Keyword::GROUP,
+ Keyword::HAVING,
+ Keyword::ORDER,
+ Keyword::LATERAL,
+ Keyword::LIMIT,
+ Keyword::FETCH,
+ Keyword::UNION,
+ Keyword::EXCEPT,
+ Keyword::INTERSECT,
+ Keyword::FROM,
+ Keyword::INTO,
+ Keyword::END,
+];
/// A [`Dialect`] for [Google Bigquery](https://cloud.google.com/bigquery/)
#[derive(Debug, Default)]
@@ -87,4 +109,8 @@ impl Dialect for BigQueryDialect {
fn supports_timestamp_versioning(&self) -> bool {
true
}
+
+ fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
+ !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
+ }
}
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 9fc16cd56..22e539df9 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -788,7 +788,7 @@ pub trait Dialect: Debug + Any {
keywords::RESERVED_FOR_IDENTIFIER.contains(&kw)
}
- // Returns reserved keywords when looking to parse a [TableFactor].
+ /// Returns reserved keywords when looking to parse a `TableFactor`.
/// See [Self::supports_from_trailing_commas]
fn get_reserved_keywords_for_table_factor(&self) -> &[Keyword] {
keywords::RESERVED_FOR_TABLE_FACTOR
@@ -828,11 +828,17 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if the specified keyword should be parsed as a column identifier.
+ /// See [keywords::RESERVED_FOR_COLUMN_ALIAS]
+ fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
+ !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw)
+ }
+
/// Returns true if the specified keyword should be parsed as a select item alias.
/// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided
/// to enable looking ahead if needed.
- fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
- explicit || !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw)
+ fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
+ explicit || self.is_column_alias(kw, parser)
}
/// Returns true if the specified keyword should be parsed as a table factor alias.
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 0d2973c7f..7e83fdb85 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -3944,7 +3944,7 @@ impl<'a> Parser<'a> {
self.parse_comma_separated_with_trailing_commas(
|p| p.parse_select_item(),
trailing_commas,
- None,
+ Self::is_reserved_for_column_alias,
)
}
@@ -3978,30 +3978,42 @@ impl<'a> Parser<'a> {
self.parse_comma_separated_with_trailing_commas(
Parser::parse_table_and_joins,
trailing_commas,
- Some(self.dialect.get_reserved_keywords_for_table_factor()),
+ |kw, _parser| {
+ self.dialect
+ .get_reserved_keywords_for_table_factor()
+ .contains(kw)
+ },
)
}
/// Parse the comma of a comma-separated syntax element.
+ /// `R` is a predicate that should return true if the next
+ /// keyword is a reserved keyword.
/// Allows for control over trailing commas
+ ///
/// Returns true if there is a next element
- fn is_parse_comma_separated_end_with_trailing_commas(
+ fn is_parse_comma_separated_end_with_trailing_commas(
&mut self,
trailing_commas: bool,
- reserved_keywords: Option<&[Keyword]>,
- ) -> bool {
- let reserved_keywords = reserved_keywords.unwrap_or(keywords::RESERVED_FOR_COLUMN_ALIAS);
+ is_reserved_keyword: &R,
+ ) -> bool
+ where
+ R: Fn(&Keyword, &mut Parser) -> bool,
+ {
if !self.consume_token(&Token::Comma) {
true
} else if trailing_commas {
- let token = self.peek_token().token;
- match token {
- Token::Word(ref kw) if reserved_keywords.contains(&kw.keyword) => true,
+ let token = self.next_token().token;
+ let is_end = match token {
+ Token::Word(ref kw) if is_reserved_keyword(&kw.keyword, self) => true,
Token::RParen | Token::SemiColon | Token::EOF | Token::RBracket | Token::RBrace => {
true
}
_ => false,
- }
+ };
+ self.prev_token();
+
+ is_end
} else {
false
}
@@ -4010,7 +4022,10 @@ impl<'a> Parser<'a> {
/// Parse the comma of a comma-separated syntax element.
/// Returns true if there is a next element
fn is_parse_comma_separated_end(&mut self) -> bool {
- self.is_parse_comma_separated_end_with_trailing_commas(self.options.trailing_commas, None)
+ self.is_parse_comma_separated_end_with_trailing_commas(
+ self.options.trailing_commas,
+ &Self::is_reserved_for_column_alias,
+ )
}
/// Parse a comma-separated list of 1+ items accepted by `F`
@@ -4018,26 +4033,33 @@ impl<'a> Parser<'a> {
where
F: FnMut(&mut Parser<'a>) -> Result,
{
- self.parse_comma_separated_with_trailing_commas(f, self.options.trailing_commas, None)
+ self.parse_comma_separated_with_trailing_commas(
+ f,
+ self.options.trailing_commas,
+ Self::is_reserved_for_column_alias,
+ )
}
- /// Parse a comma-separated list of 1+ items accepted by `F`
- /// Allows for control over trailing commas
- fn parse_comma_separated_with_trailing_commas(
+ /// Parse a comma-separated list of 1+ items accepted by `F`.
+ /// `R` is a predicate that should return true if the next
+ /// keyword is a reserved keyword.
+ /// Allows for control over trailing commas.
+ fn parse_comma_separated_with_trailing_commas(
&mut self,
mut f: F,
trailing_commas: bool,
- reserved_keywords: Option<&[Keyword]>,
+ is_reserved_keyword: R,
) -> Result, ParserError>
where
F: FnMut(&mut Parser<'a>) -> Result,
+ R: Fn(&Keyword, &mut Parser) -> bool,
{
let mut values = vec![];
loop {
values.push(f(self)?);
if self.is_parse_comma_separated_end_with_trailing_commas(
trailing_commas,
- reserved_keywords,
+ &is_reserved_keyword,
) {
break;
}
@@ -4111,6 +4133,13 @@ impl<'a> Parser<'a> {
self.parse_comma_separated(f)
}
+ /// Default implementation of a predicate that returns true if
+ /// the specified keyword is reserved for column alias.
+ /// See [Dialect::is_column_alias]
+ fn is_reserved_for_column_alias(kw: &Keyword, parser: &mut Parser) -> bool {
+ !parser.dialect.is_column_alias(kw, parser)
+ }
+
/// Run a parser method `f`, reverting back to the current position if unsuccessful.
/// Returns `None` if `f` returns an error
pub fn maybe_parse(&mut self, f: F) -> Result