Skip to content

Commit dd5be2f

Browse files
committed
Re-use optional alias parsing logic, add option to customize table alias parsing
1 parent 74a9ac9 commit dd5be2f

File tree

3 files changed

+77
-80
lines changed

3 files changed

+77
-80
lines changed

src/dialect/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,13 @@ pub trait Dialect: Debug + Any {
827827
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
828828
explicit || !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw)
829829
}
830+
831+
/// Returns true if the specified keyword should be parsed as a table factor alias.
832+
/// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided
833+
/// to enable looking ahead if needed.
834+
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
835+
explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
836+
}
830837
}
831838

832839
/// This represents the operators for which precedence must be defined

src/dialect/snowflake.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ impl Dialect for SnowflakeDialect {
285285
| Keyword::HAVING
286286
| Keyword::INTERSECT
287287
| Keyword::INTO
288+
| Keyword::MINUS
288289
| Keyword::ORDER
289290
| Keyword::SELECT
290291
| Keyword::UNION

src/parser/mod.rs

Lines changed: 69 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -8837,68 +8837,76 @@ impl<'a> Parser<'a> {
88378837
Ok(IdentWithAlias { ident, alias })
88388838
}
88398839

8840-
// Optionally parses an alias for a select list item
8840+
/// Optionally parses an alias for a select list item
88418841
fn maybe_parse_select_item_alias(&mut self) -> Result<Option<Ident>, ParserError> {
8842-
let after_as = self.parse_keyword(Keyword::AS);
8843-
let next_token = self.next_token();
8844-
match next_token.token {
8845-
// Dialect-specific behavior for words that may be reserved from parsed
8846-
// as select item aliases.
8847-
Token::Word(w)
8848-
if self
8849-
.dialect
8850-
.is_select_item_alias(after_as, &w.keyword, self) =>
8851-
{
8852-
Ok(Some(w.into_ident(next_token.span)))
8853-
}
8854-
// MSSQL supports single-quoted strings as aliases for columns
8855-
Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s))),
8856-
// Support for MySql dialect double-quoted string, `AS "HOUR"` for example
8857-
Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s))),
8858-
_ => {
8859-
if after_as {
8860-
return self.expected("an identifier after AS", next_token);
8861-
}
8862-
self.prev_token();
8863-
Ok(None) // no alias found
8842+
fn validator(explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
8843+
parser.dialect.is_select_item_alias(explicit, kw, parser)
8844+
}
8845+
self.parse_optional_alias_inner(None, validator)
8846+
}
8847+
8848+
/// Optionally parses an alias for a table like in `... FROM generate_series(1, 10) AS t (col)`.
8849+
/// In this case, the alias is allowed to optionally name the columns in the table, in
8850+
/// addition to the table itself.
8851+
pub fn maybe_parse_table_alias(&mut self) -> Result<Option<TableAlias>, ParserError> {
8852+
fn validator(explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
8853+
parser.dialect.is_table_factor_alias(explicit, kw, parser)
8854+
}
8855+
match self.parse_optional_alias_inner(None, validator)? {
8856+
Some(name) => {
8857+
let columns = self.parse_table_alias_column_defs()?;
8858+
Ok(Some(TableAlias { name, columns }))
88648859
}
8860+
None => Ok(None),
88658861
}
88668862
}
88678863

8868-
/// Parse `AS identifier` (or simply `identifier` if it's not a reserved keyword)
8869-
/// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`,
8870-
/// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar`
8864+
/// Wrapper for parse_optional_alias_inner, left for backwards-compatibility
8865+
/// but new flows should use the context-specific methods such as `maybe_parse_select_item_alias`
8866+
/// and `maybe_parse_table_alias`.
88718867
pub fn parse_optional_alias(
88728868
&mut self,
88738869
reserved_kwds: &[Keyword],
88748870
) -> Result<Option<Ident>, ParserError> {
8871+
fn validator(_explicit: bool, _kw: &Keyword, _parser: &mut Parser) -> bool {
8872+
false
8873+
}
8874+
self.parse_optional_alias_inner(Some(reserved_kwds), validator)
8875+
}
8876+
8877+
/// Parses an optional alias after a SQL element such as a select list item
8878+
/// or a table name.
8879+
///
8880+
/// This method accepts an optional list of reserved keywords or a function
8881+
/// to call to validate if a keyword should be parsed as an alias, to allow
8882+
/// callers to customize the parsing logic based on their context.
8883+
fn parse_optional_alias_inner<F>(
8884+
&mut self,
8885+
reserved_kwds: Option<&[Keyword]>,
8886+
validator: F,
8887+
) -> Result<Option<Ident>, ParserError>
8888+
where
8889+
F: Fn(bool, &Keyword, &mut Parser) -> bool,
8890+
{
88758891
let after_as = self.parse_keyword(Keyword::AS);
8892+
88768893
let next_token = self.next_token();
88778894
match next_token.token {
8878-
// Accept any identifier after `AS` (though many dialects have restrictions on
8879-
// keywords that may appear here). If there's no `AS`: don't parse keywords,
8880-
// which may start a construct allowed in this position, to be parsed as aliases.
8881-
// (For example, in `FROM t1 JOIN` the `JOIN` will always be parsed as a keyword,
8882-
// not an alias.)
8883-
Token::Word(w) if after_as || !reserved_kwds.contains(&w.keyword) => {
8895+
// By default, if a word is located after the `AS` keyword we consider it an alias
8896+
// as long as it's not reserved.
8897+
Token::Word(w)
8898+
if after_as || reserved_kwds.map_or(false, |x| !x.contains(&w.keyword)) =>
8899+
{
88848900
Ok(Some(w.into_ident(next_token.span)))
88858901
}
8886-
// Left the next two patterns for backwards-compatibility (despite not breaking
8887-
// any tests).
8888-
// MSSQL supports single-quoted strings as aliases for columns
8889-
// We accept them as table aliases too, although MSSQL does not.
8890-
//
8891-
// Note, that this conflicts with an obscure rule from the SQL
8892-
// standard, which we don't implement:
8893-
// https://crate.io/docs/sql-99/en/latest/chapters/07.html#character-string-literal-s
8894-
// "[Obscure Rule] SQL allows you to break a long <character
8895-
// string literal> up into two or more smaller <character string
8896-
// literal>s, split by a <separator> that includes a newline
8897-
// character. When it sees such a <literal>, your DBMS will
8898-
// ignore the <separator> and treat the multiple strings as
8899-
// a single <literal>."
8902+
// This pattern allows for customizing the acceptance of words as aliases based on the caller's
8903+
// context, such as to what SQL element this word is a potential alias of (select item alias, table name
8904+
// alias, etc.) or dialect-specific logic that goes beyond a simple list of reserved keywords.
8905+
Token::Word(w) if validator(after_as, &w.keyword, self) => {
8906+
Ok(Some(w.into_ident(next_token.span)))
8907+
}
8908+
// For backwards-compatibility, we accept quoted strings as aliases regardless of the context.
89008909
Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s))),
8901-
// Support for MySql dialect double-quoted string, `AS "HOUR"` for example
89028910
Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s))),
89038911
_ => {
89048912
if after_as {
@@ -8910,23 +8918,6 @@ impl<'a> Parser<'a> {
89108918
}
89118919
}
89128920

8913-
/// Parse `AS identifier` when the AS is describing a table-valued object,
8914-
/// like in `... FROM generate_series(1, 10) AS t (col)`. In this case
8915-
/// the alias is allowed to optionally name the columns in the table, in
8916-
/// addition to the table itself.
8917-
pub fn parse_optional_table_alias(
8918-
&mut self,
8919-
reserved_kwds: &[Keyword],
8920-
) -> Result<Option<TableAlias>, ParserError> {
8921-
match self.parse_optional_alias(reserved_kwds)? {
8922-
Some(name) => {
8923-
let columns = self.parse_table_alias_column_defs()?;
8924-
Ok(Some(TableAlias { name, columns }))
8925-
}
8926-
None => Ok(None),
8927-
}
8928-
}
8929-
89308921
pub fn parse_optional_group_by(&mut self) -> Result<Option<GroupByExpr>, ParserError> {
89318922
if self.parse_keywords(&[Keyword::GROUP, Keyword::BY]) {
89328923
let expressions = if self.parse_keyword(Keyword::ALL) {
@@ -10928,7 +10919,7 @@ impl<'a> Parser<'a> {
1092810919
let name = self.parse_object_name(false)?;
1092910920
self.expect_token(&Token::LParen)?;
1093010921
let args = self.parse_optional_args()?;
10931-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
10922+
let alias = self.maybe_parse_table_alias()?;
1093210923
Ok(TableFactor::Function {
1093310924
lateral: true,
1093410925
name,
@@ -10941,7 +10932,7 @@ impl<'a> Parser<'a> {
1094110932
self.expect_token(&Token::LParen)?;
1094210933
let expr = self.parse_expr()?;
1094310934
self.expect_token(&Token::RParen)?;
10944-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
10935+
let alias = self.maybe_parse_table_alias()?;
1094510936
Ok(TableFactor::TableFunction { expr, alias })
1094610937
} else if self.consume_token(&Token::LParen) {
1094710938
// A left paren introduces either a derived table (i.e., a subquery)
@@ -10990,7 +10981,7 @@ impl<'a> Parser<'a> {
1099010981
#[allow(clippy::if_same_then_else)]
1099110982
if !table_and_joins.joins.is_empty() {
1099210983
self.expect_token(&Token::RParen)?;
10993-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
10984+
let alias = self.maybe_parse_table_alias()?;
1099410985
Ok(TableFactor::NestedJoin {
1099510986
table_with_joins: Box::new(table_and_joins),
1099610987
alias,
@@ -11003,7 +10994,7 @@ impl<'a> Parser<'a> {
1100310994
// (B): `table_and_joins` (what we found inside the parentheses)
1100410995
// is a nested join `(foo JOIN bar)`, not followed by other joins.
1100510996
self.expect_token(&Token::RParen)?;
11006-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
10997+
let alias = self.maybe_parse_table_alias()?;
1100710998
Ok(TableFactor::NestedJoin {
1100810999
table_with_joins: Box::new(table_and_joins),
1100911000
alias,
@@ -11017,9 +11008,7 @@ impl<'a> Parser<'a> {
1101711008
// [AS alias])`) as well.
1101811009
self.expect_token(&Token::RParen)?;
1101911010

11020-
if let Some(outer_alias) =
11021-
self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?
11022-
{
11011+
if let Some(outer_alias) = self.maybe_parse_table_alias()? {
1102311012
// Snowflake also allows specifying an alias *after* parens
1102411013
// e.g. `FROM (mytable) AS alias`
1102511014
match &mut table_and_joins.relation {
@@ -11072,7 +11061,7 @@ impl<'a> Parser<'a> {
1107211061
// SELECT * FROM VALUES (1, 'a'), (2, 'b') AS t (col1, col2)
1107311062
// where there are no parentheses around the VALUES clause.
1107411063
let values = SetExpr::Values(self.parse_values(false)?);
11075-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11064+
let alias = self.maybe_parse_table_alias()?;
1107611065
Ok(TableFactor::Derived {
1107711066
lateral: false,
1107811067
subquery: Box::new(Query {
@@ -11098,7 +11087,7 @@ impl<'a> Parser<'a> {
1109811087
self.expect_token(&Token::RParen)?;
1109911088

1110011089
let with_ordinality = self.parse_keywords(&[Keyword::WITH, Keyword::ORDINALITY]);
11101-
let alias = match self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS) {
11090+
let alias = match self.maybe_parse_table_alias() {
1110211091
Ok(Some(alias)) => Some(alias),
1110311092
Ok(None) => None,
1110411093
Err(e) => return Err(e),
@@ -11135,7 +11124,7 @@ impl<'a> Parser<'a> {
1113511124
let columns = self.parse_comma_separated(Parser::parse_json_table_column_def)?;
1113611125
self.expect_token(&Token::RParen)?;
1113711126
self.expect_token(&Token::RParen)?;
11138-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11127+
let alias = self.maybe_parse_table_alias()?;
1113911128
Ok(TableFactor::JsonTable {
1114011129
json_expr,
1114111130
json_path,
@@ -11180,7 +11169,7 @@ impl<'a> Parser<'a> {
1118011169
}
1118111170
}
1118211171

11183-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11172+
let alias = self.maybe_parse_table_alias()?;
1118411173

1118511174
// MSSQL-specific table hints:
1118611175
let mut with_hints = vec![];
@@ -11358,7 +11347,7 @@ impl<'a> Parser<'a> {
1135811347
} else {
1135911348
Vec::new()
1136011349
};
11361-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11350+
let alias = self.maybe_parse_table_alias()?;
1136211351
Ok(TableFactor::OpenJsonTable {
1136311352
json_expr,
1136411353
json_path,
@@ -11457,7 +11446,7 @@ impl<'a> Parser<'a> {
1145711446

1145811447
self.expect_token(&Token::RParen)?;
1145911448

11460-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11449+
let alias = self.maybe_parse_table_alias()?;
1146111450

1146211451
Ok(TableFactor::MatchRecognize {
1146311452
table: Box::new(table),
@@ -11701,7 +11690,7 @@ impl<'a> Parser<'a> {
1170111690
) -> Result<TableFactor, ParserError> {
1170211691
let subquery = self.parse_query()?;
1170311692
self.expect_token(&Token::RParen)?;
11704-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11693+
let alias = self.maybe_parse_table_alias()?;
1170511694
Ok(TableFactor::Derived {
1170611695
lateral: match lateral {
1170711696
Lateral => true,
@@ -11795,7 +11784,7 @@ impl<'a> Parser<'a> {
1179511784
};
1179611785

1179711786
self.expect_token(&Token::RParen)?;
11798-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11787+
let alias = self.maybe_parse_table_alias()?;
1179911788
Ok(TableFactor::Pivot {
1180011789
table: Box::new(table),
1180111790
aggregate_functions,
@@ -11817,7 +11806,7 @@ impl<'a> Parser<'a> {
1181711806
self.expect_keyword_is(Keyword::IN)?;
1181811807
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
1181911808
self.expect_token(&Token::RParen)?;
11820-
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
11809+
let alias = self.maybe_parse_table_alias()?;
1182111810
Ok(TableFactor::Unpivot {
1182211811
table: Box::new(table),
1182311812
value,

0 commit comments

Comments
 (0)