Skip to content

Commit 98a5627

Browse files
committed
Add support for Snowflake column aliases that use SQL keywords
1 parent 8cfc462 commit 98a5627

File tree

4 files changed

+111
-1
lines changed

4 files changed

+111
-1
lines changed

src/dialect/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,13 @@ pub trait Dialect: Debug + Any {
782782
fn supports_insert_set(&self) -> bool {
783783
false
784784
}
785+
786+
/// Returns true if the specified keyword should be parsed as a select item alias.
787+
/// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided
788+
/// to enable looking ahead if needed.
789+
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
790+
explicit || !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw)
791+
}
785792
}
786793

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

src/dialect/snowflake.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,50 @@ impl Dialect for SnowflakeDialect {
247247
fn supports_partiql(&self) -> bool {
248248
true
249249
}
250+
251+
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
252+
explicit
253+
|| match kw {
254+
// The following keywords can be considered an alias as long as
255+
// they are not followed by other tokens that may change their meaning
256+
// e.g. `SELECT * EXCEPT (col1) FROM tbl`
257+
Keyword::EXCEPT
258+
// e.g. `SELECT 1 LIMIT 5`
259+
| Keyword::LIMIT
260+
// e.g. `SELECT 1 OFFSET 5 ROWS`
261+
| Keyword::OFFSET
262+
// e.g. `INSERT INTO t SELECT 1 RETURNING *`
263+
| Keyword::RETURNING if !matches!(parser.peek_token_ref().token, Token::Comma | Token::EOF) =>
264+
{
265+
false
266+
}
267+
268+
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
269+
// which would give it a different meanins, for example: `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
270+
Keyword::FETCH
271+
if parser.peek_keyword(Keyword::FIRST) || parser.peek_keyword(Keyword::NEXT) =>
272+
{
273+
false
274+
}
275+
276+
// Reserved keywords by the Snowflake dialect, which seem to be less strictive
277+
// than what is listed in `keywords::RESERVED_FOR_COLUMN_ALIAS`. The following
278+
// keywords were tested with the this statement: `SELECT 1 <KW>`.
279+
Keyword::FROM
280+
| Keyword::GROUP
281+
| Keyword::HAVING
282+
| Keyword::INTERSECT
283+
| Keyword::INTO
284+
| Keyword::ORDER
285+
| Keyword::SELECT
286+
| Keyword::UNION
287+
| Keyword::WHERE
288+
| Keyword::WITH => false,
289+
290+
// Any other word is considered an alias
291+
_ => true,
292+
}
293+
}
250294
}
251295

252296
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {

src/parser/mod.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8738,6 +8738,34 @@ impl<'a> Parser<'a> {
87388738
Ok(IdentWithAlias { ident, alias })
87398739
}
87408740

8741+
// Optionally parses an alias for a select list item
8742+
fn maybe_parse_select_item_alias(&mut self) -> Result<Option<Ident>, ParserError> {
8743+
let after_as = self.parse_keyword(Keyword::AS);
8744+
let next_token = self.next_token();
8745+
match next_token.token {
8746+
// Dialect-specific behavior for words that may be reserved from parsed
8747+
// as select item aliases.
8748+
Token::Word(w)
8749+
if self
8750+
.dialect
8751+
.is_select_item_alias(after_as, &w.keyword, self) =>
8752+
{
8753+
Ok(Some(w.into_ident(next_token.span)))
8754+
}
8755+
// MSSQL supports single-quoted strings as aliases for columns
8756+
Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s))),
8757+
// Support for MySql dialect double-quoted string, `AS "HOUR"` for example
8758+
Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s))),
8759+
_ => {
8760+
if after_as {
8761+
return self.expected("an identifier after AS", next_token);
8762+
}
8763+
self.prev_token();
8764+
Ok(None) // no alias found
8765+
}
8766+
}
8767+
}
8768+
87418769
/// Parse `AS identifier` (or simply `identifier` if it's not a reserved keyword)
87428770
/// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`,
87438771
/// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar`
@@ -8756,6 +8784,8 @@ impl<'a> Parser<'a> {
87568784
Token::Word(w) if after_as || !reserved_kwds.contains(&w.keyword) => {
87578785
Ok(Some(w.into_ident(next_token.span)))
87588786
}
8787+
// Left the next two patterns for backwards-compatibility (despite not breaking
8788+
// any tests).
87598789
// MSSQL supports single-quoted strings as aliases for columns
87608790
// We accept them as table aliases too, although MSSQL does not.
87618791
//
@@ -12361,7 +12391,7 @@ impl<'a> Parser<'a> {
1236112391
})
1236212392
}
1236312393
expr => self
12364-
.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS)
12394+
.maybe_parse_select_item_alias()
1236512395
.map(|alias| match alias {
1236612396
Some(alias) => SelectItem::ExprWithAlias { expr, alias },
1236712397
None => SelectItem::UnnamedExpr(expr),

tests/sqlparser_snowflake.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,3 +3022,32 @@ fn parse_ls_and_rm() {
30223022

30233023
snowflake().verified_stmt(r#"LIST @"STAGE_WITH_QUOTES""#);
30243024
}
3025+
3026+
#[test]
3027+
fn test_sql_keywords_as_select_item_aliases() {
3028+
// Some keywords that should be parsed as an alias
3029+
let unreserved_kws = vec!["CLUSTER", "FETCH", "RETURNING", "LIMIT", "EXCEPT"];
3030+
for kw in unreserved_kws {
3031+
snowflake()
3032+
.one_statement_parses_to(&format!("SELECT 1 {kw}"), &format!("SELECT 1 AS {kw}"));
3033+
}
3034+
3035+
// Some keywords that should not be parsed as an alias
3036+
let reserved_kws = vec![
3037+
"FROM",
3038+
"GROUP",
3039+
"HAVING",
3040+
"INTERSECT",
3041+
"INTO",
3042+
"ORDER",
3043+
"SELECT",
3044+
"UNION",
3045+
"WHERE",
3046+
"WITH",
3047+
];
3048+
for kw in reserved_kws {
3049+
assert!(snowflake()
3050+
.parse_sql_statements(&format!("SELECT 1 {kw}"))
3051+
.is_err());
3052+
}
3053+
}

0 commit comments

Comments
 (0)