Snowflake: Improve support for reserved keywords for table factor (#1942)
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run

This commit is contained in:
Yoav Cohen 2025-07-31 10:41:33 +02:00 committed by GitHub
parent 85fa881379
commit 3d2db8c69b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 103 additions and 12 deletions

View file

@ -963,12 +963,6 @@ pub trait Dialect: Debug + Any {
keywords::RESERVED_FOR_IDENTIFIER.contains(&kw) keywords::RESERVED_FOR_IDENTIFIER.contains(&kw)
} }
/// 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
}
/// Returns reserved keywords that may prefix a select item expression /// Returns reserved keywords that may prefix a select item expression
/// e.g. `SELECT CONNECT_BY_ROOT name FROM Tbl2` (Snowflake) /// e.g. `SELECT CONNECT_BY_ROOT name FROM Tbl2` (Snowflake)
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] { fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
@ -1027,7 +1021,13 @@ pub trait Dialect: Debug + Any {
explicit || self.is_column_alias(kw, parser) explicit || self.is_column_alias(kw, parser)
} }
/// Returns true if the specified keyword should be parsed as a table identifier. /// Returns true if the specified keyword should be parsed as a table factor identifier.
/// See [keywords::RESERVED_FOR_TABLE_FACTOR]
fn is_table_factor(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
!keywords::RESERVED_FOR_TABLE_FACTOR.contains(kw)
}
/// Returns true if the specified keyword should be parsed as a table factor alias.
/// See [keywords::RESERVED_FOR_TABLE_ALIAS] /// See [keywords::RESERVED_FOR_TABLE_ALIAS]
fn is_table_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool { fn is_table_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
!keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw) !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)

View file

@ -47,6 +47,82 @@ use super::keywords::RESERVED_FOR_IDENTIFIER;
use sqlparser::ast::StorageSerializationPolicy; use sqlparser::ast::StorageSerializationPolicy;
const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT]; const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT];
// See: <https://docs.snowflake.com/en/sql-reference/reserved-keywords>
const RESERVED_KEYWORDS_FOR_TABLE_FACTOR: &[Keyword] = &[
Keyword::ALL,
Keyword::ALTER,
Keyword::AND,
Keyword::ANY,
Keyword::AS,
Keyword::BETWEEN,
Keyword::BY,
Keyword::CHECK,
Keyword::COLUMN,
Keyword::CONNECT,
Keyword::CREATE,
Keyword::CROSS,
Keyword::CURRENT,
Keyword::DELETE,
Keyword::DISTINCT,
Keyword::DROP,
Keyword::ELSE,
Keyword::EXISTS,
Keyword::FOLLOWING,
Keyword::FOR,
Keyword::FROM,
Keyword::FULL,
Keyword::GRANT,
Keyword::GROUP,
Keyword::HAVING,
Keyword::ILIKE,
Keyword::IN,
Keyword::INCREMENT,
Keyword::INNER,
Keyword::INSERT,
Keyword::INTERSECT,
Keyword::INTO,
Keyword::IS,
Keyword::JOIN,
Keyword::LEFT,
Keyword::LIKE,
Keyword::MINUS,
Keyword::NATURAL,
Keyword::NOT,
Keyword::NULL,
Keyword::OF,
Keyword::ON,
Keyword::OR,
Keyword::ORDER,
Keyword::QUALIFY,
Keyword::REGEXP,
Keyword::REVOKE,
Keyword::RIGHT,
Keyword::RLIKE,
Keyword::ROW,
Keyword::ROWS,
Keyword::SAMPLE,
Keyword::SELECT,
Keyword::SET,
Keyword::SOME,
Keyword::START,
Keyword::TABLE,
Keyword::TABLESAMPLE,
Keyword::THEN,
Keyword::TO,
Keyword::TRIGGER,
Keyword::UNION,
Keyword::UNIQUE,
Keyword::UPDATE,
Keyword::USING,
Keyword::VALUES,
Keyword::WHEN,
Keyword::WHENEVER,
Keyword::WHERE,
Keyword::WINDOW,
Keyword::WITH,
];
/// A [`Dialect`] for [Snowflake](https://www.snowflake.com/) /// A [`Dialect`] for [Snowflake](https://www.snowflake.com/)
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct SnowflakeDialect; pub struct SnowflakeDialect;
@ -433,6 +509,13 @@ impl Dialect for SnowflakeDialect {
} }
} }
fn is_table_factor(&self, kw: &Keyword, parser: &mut Parser) -> bool {
match kw {
Keyword::LIMIT if peek_for_limit_options(parser) => false,
_ => !RESERVED_KEYWORDS_FOR_TABLE_FACTOR.contains(kw),
}
}
/// See: <https://docs.snowflake.com/en/sql-reference/constructs/at-before> /// See: <https://docs.snowflake.com/en/sql-reference/constructs/at-before>
fn supports_timestamp_versioning(&self) -> bool { fn supports_timestamp_versioning(&self) -> bool {
true true

View file

@ -4449,11 +4449,7 @@ impl<'a> Parser<'a> {
self.parse_comma_separated_with_trailing_commas( self.parse_comma_separated_with_trailing_commas(
Parser::parse_table_and_joins, Parser::parse_table_and_joins,
trailing_commas, trailing_commas,
|kw, _parser| { |kw, parser| !self.dialect.is_table_factor(kw, parser),
self.dialect
.get_reserved_keywords_for_table_factor()
.contains(kw)
},
) )
} }

View file

@ -3606,6 +3606,18 @@ fn test_sql_keywords_as_table_aliases() {
snowflake().verified_stmt("SELECT * FROM tbl LIMIT $$$$"); snowflake().verified_stmt("SELECT * FROM tbl LIMIT $$$$");
} }
#[test]
fn test_sql_keywords_as_table_factor() {
// LIMIT is a table factor, Snowflake does not reserve it
snowflake().one_statement_parses_to("SELECT * FROM tbl, LIMIT", "SELECT * FROM tbl, LIMIT");
// LIMIT is not a table factor
snowflake().one_statement_parses_to("SELECT * FROM tbl, LIMIT 1", "SELECT * FROM tbl LIMIT 1");
// ORDER is reserved
assert!(snowflake()
.parse_sql_statements("SELECT * FROM tbl, order")
.is_err());
}
#[test] #[test]
fn test_timetravel_at_before() { fn test_timetravel_at_before() {
snowflake().verified_only_select("SELECT * FROM tbl AT(TIMESTAMP => '2024-12-15 00:00:00')"); snowflake().verified_only_select("SELECT * FROM tbl AT(TIMESTAMP => '2024-12-15 00:00:00')");