Snowflake Reserved SQL Keywords as Implicit Table Alias (#1934)

This commit is contained in:
Yoav Cohen 2025-07-11 11:13:20 +02:00 committed by GitHub
parent fd4934ec74
commit 15f35e1476
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 163 additions and 12 deletions

View file

@ -992,11 +992,17 @@ pub trait Dialect: Debug + Any {
explicit || self.is_column_alias(kw, parser)
}
/// Returns true if the specified keyword should be parsed as a table identifier.
/// See [keywords::RESERVED_FOR_TABLE_ALIAS]
fn is_table_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
!keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
}
/// Returns true if the specified keyword should be parsed as a table factor alias.
/// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided
/// to enable looking ahead if needed.
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
explicit || self.is_table_alias(kw, parser)
}
/// Returns true if this dialect supports querying historical table data

View file

@ -318,9 +318,11 @@ impl Dialect for SnowflakeDialect {
}
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
// which would give it a different meanings, for example: `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
Keyword::FETCH
if parser.peek_keyword(Keyword::FIRST) || parser.peek_keyword(Keyword::NEXT) =>
// which would give it a different meanings, for example:
// `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
// `SELECT 1 FETCH 10` - not an alias
Keyword::FETCH if parser.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]).is_some()
|| matches!(parser.peek_token().token, Token::Number(_, _)) =>
{
false
}
@ -345,6 +347,86 @@ impl Dialect for SnowflakeDialect {
}
}
fn is_table_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
match kw {
// The following keywords can be considered an alias as long as
// they are not followed by other tokens that may change their meaning
Keyword::LIMIT
| Keyword::RETURNING
| Keyword::INNER
| Keyword::USING
| Keyword::PIVOT
| Keyword::UNPIVOT
| Keyword::EXCEPT
| Keyword::MATCH_RECOGNIZE
| Keyword::OFFSET
if !matches!(parser.peek_token_ref().token, Token::SemiColon | Token::EOF) =>
{
false
}
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
// which would give it a different meanings, for example:
// `SELECT * FROM tbl FETCH FIRST 10 ROWS` - not an alias
// `SELECT * FROM tbl FETCH 10` - not an alias
Keyword::FETCH
if parser
.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])
.is_some()
|| matches!(parser.peek_token().token, Token::Number(_, _)) =>
{
false
}
// All sorts of join-related keywords can be considered aliases unless additional
// keywords change their meaning.
Keyword::RIGHT | Keyword::LEFT | Keyword::SEMI | Keyword::ANTI
if parser
.peek_one_of_keywords(&[Keyword::JOIN, Keyword::OUTER])
.is_some() =>
{
false
}
Keyword::GLOBAL if parser.peek_keyword(Keyword::FULL) => false,
// Reserved keywords by the Snowflake dialect, which seem to be less strictive
// than what is listed in `keywords::RESERVED_FOR_TABLE_ALIAS`. The following
// keywords were tested with the this statement: `SELECT <KW>.* FROM tbl <KW>`.
Keyword::WITH
| Keyword::ORDER
| Keyword::SELECT
| Keyword::WHERE
| Keyword::GROUP
| Keyword::HAVING
| Keyword::LATERAL
| Keyword::UNION
| Keyword::INTERSECT
| Keyword::MINUS
| Keyword::ON
| Keyword::JOIN
| Keyword::INNER
| Keyword::CROSS
| Keyword::FULL
| Keyword::LEFT
| Keyword::RIGHT
| Keyword::NATURAL
| Keyword::USING
| Keyword::ASOF
| Keyword::MATCH_CONDITION
| Keyword::SET
| Keyword::QUALIFY
| Keyword::FOR
| Keyword::START
| Keyword::CONNECT
| Keyword::SAMPLE
| Keyword::TABLESAMPLE
| Keyword::FROM => false,
// Any other word is considered an alias
_ => true,
}
}
/// See: <https://docs.snowflake.com/en/sql-reference/constructs/at-before>
fn supports_timestamp_versioning(&self) -> bool {
true