diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index deb5719d5..4f23ac689 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -948,12 +948,6 @@ pub trait Dialect: Debug + Any { 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 /// e.g. `SELECT CONNECT_BY_ROOT name FROM Tbl2` (Snowflake) fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] { @@ -1012,7 +1006,13 @@ 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. + /// 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] fn is_table_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool { !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw) diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 3b1eff39a..0ce119822 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -45,6 +45,82 @@ use super::keywords::RESERVED_FOR_IDENTIFIER; use sqlparser::ast::StorageSerializationPolicy; const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT]; + +// See: +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/) #[derive(Debug, Default)] pub struct SnowflakeDialect; @@ -427,6 +503,21 @@ impl Dialect for SnowflakeDialect { } } + fn is_table_factor(&self, kw: &Keyword, parser: &mut Parser) -> bool { + match kw { + Keyword::LIMIT + if matches!( + parser.peek_token().token, + Token::Number(_, _) | Token::Placeholder(_) + ) => + { + false + } + + _ => !RESERVED_KEYWORDS_FOR_TABLE_FACTOR.contains(kw), + } + } + /// See: fn supports_timestamp_versioning(&self) -> bool { true diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 47b63da87..f03acae27 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4369,11 +4369,7 @@ impl<'a> Parser<'a> { self.parse_comma_separated_with_trailing_commas( Parser::parse_table_and_joins, trailing_commas, - |kw, _parser| { - self.dialect - .get_reserved_keywords_for_table_factor() - .contains(kw) - }, + |kw, parser| !self.dialect.is_table_factor(kw, parser), ) } diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 65546bee0..e724d7352 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -3543,6 +3543,18 @@ fn test_sql_keywords_as_table_aliases() { } } +#[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] fn test_timetravel_at_before() { snowflake().verified_only_select("SELECT * FROM tbl AT(TIMESTAMP => '2024-12-15 00:00:00')");