This is an automated email from the ASF dual-hosted git repository. LucaCappelletti94 pushed a commit to branch snowflake-stage-table-factor-fixes in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 937d808ac74ebd4c308c2042fee3b2d902337a57 Author: LucaCappelletti94 <[email protected]> AuthorDate: Mon Sep 21 22:27:33 2026 +0200 Snowflake: Fix stage delimiter handling and gate stage table factor on dialect - Gate parse_snowflake_stage_table_factor behind Dialect::supports_stages(), preventing non-Snowflake dialects (such as PostgreSQL) from parsing @stage as a table factor. - In parse_stage_name_identifier, handle ',' and ';' delimiters without consuming them, and reject empty or bare '@' stage names. --- src/dialect/mod.rs | 7 +++++++ src/dialect/snowflake.rs | 15 +++++++++------ src/parser/mod.rs | 2 +- tests/sqlparser_common.rs | 15 +++++++++++++++ tests/sqlparser_postgres.rs | 9 +++++++++ tests/sqlparser_snowflake.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 7c4744c5..ddb6e219 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1584,6 +1584,13 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if this dialect supports Snowflake-style stages in table factors (e.g. `@stage`). + /// + /// [Snowflake](https://docs.snowflake.com/en/user-guide/querying-stage) + fn supports_stages(&self) -> bool { + false + } + /// Support quote delimited string literals, e.g. `Q'{...}'` /// /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA) diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 0bbd859e..41a8f870 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -672,6 +672,10 @@ impl Dialect for SnowflakeDialect { true } + fn supports_stages(&self) -> bool { + true + } + /// See <https://docs.snowflake.com/en/sql-reference/sql/select#parameters> fn supports_select_wildcard_replace(&self) -> bool { true @@ -1314,12 +1318,8 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserE let mut ident = String::new(); while let Some(next_token) = parser.next_token_no_skip() { match &next_token.token { - Token::Whitespace(_) | Token::SemiColon => break, - Token::Period => { - parser.prev_token(); - break; - } - Token::LParen | Token::RParen => { + Token::Whitespace(_) => break, + Token::Period | Token::Comma | Token::SemiColon | Token::LParen | Token::RParen => { parser.prev_token(); break; } @@ -1336,6 +1336,9 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserE _ => return parser.expected_ref("stage name identifier", parser.peek_token_ref()), } } + if ident.is_empty() || ident == "@" { + return parser.expected_ref("stage name identifier", parser.peek_token_ref()); + } Ok(Ident::new(ident)) } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 15f135ff..2191435d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -16742,7 +16742,7 @@ impl<'a> Parser<'a> { && self.peek_keyword_with_tokens(Keyword::SEMANTIC_VIEW, &[Token::LParen]) { self.parse_semantic_view_table_factor() - } else if self.peek_token_ref().token == Token::AtSign { + } else if self.dialect.supports_stages() && self.peek_token_ref().token == Token::AtSign { // Stage reference: @mystage or @namespace.stage (e.g. Snowflake) self.parse_snowflake_stage_table_factor() } else { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index c4aa607d..9e0042ef 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -20080,3 +20080,18 @@ fn parse_unary_minus_never_renders_line_comment() { all_dialects().verified_stmt("SELECT -1"); all_dialects().verified_stmt("SELECT -x"); } + +#[test] +fn parse_stage_table_factor() { + let supported = all_dialects_where(|d| d.supports_stages()); + supported.verified_stmt("SELECT * FROM @stage"); + supported.verified_stmt("SELECT * FROM @stage, my_table"); + + let unsupported = all_dialects_where(|d| !d.supports_stages() && !d.is_identifier_start('@')); + assert_eq!( + unsupported + .parse_sql_statements("SELECT * FROM @stage") + .unwrap_err(), + ParserError::ParserError("Expected: identifier, found: @".to_string()), + ); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ab2d4b8e..e4946fc1 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9984,3 +9984,12 @@ fn parse_unary_minus_before_pg_prefix_operators() { pg().verified_stmt("SELECT - @ 2"); pg().one_statement_parses_to("SELECT - #x", "SELECT - # x"); } + +#[test] +fn parse_stage_table_factor_rejected() { + let sql = "SELECT * FROM @stage"; + assert_eq!( + pg().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("Expected: identifier, found: @".to_string()), + ); +} diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 059560dc..174bd197 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -4912,3 +4912,43 @@ fn test_select_dollar_column_from_stage() { // With table function args, without alias snowflake().verified_stmt("SELECT $1, $2 FROM @mystage1(file_format => 'myformat')"); } + +#[test] +fn test_stage_name_delimiters() { + snowflake().verified_stmt("SELECT * FROM @stage1, @stage2"); + snowflake().verified_stmt("SELECT * FROM @stage, my_table"); + snowflake().verified_stmt("SELECT * FROM my_table, @stage"); + snowflake().verified_stmt("SELECT * FROM @namespace.stage_name, item"); + snowflake().verified_stmt("SELECT * FROM @stage(file_format => 'myformat'), my_table"); + snowflake().verified_stmt("SELECT * FROM @stage AS s, my_table"); + snowflake().verified_stmt("SELECT * FROM @stage s, my_table"); + let stmts = snowflake() + .parse_sql_statements("SELECT * FROM @stage; SELECT 1") + .unwrap(); + assert_eq!(stmts.len(), 2); + + assert_eq!( + snowflake() + .parse_sql_statements("SELECT * FROM @") + .unwrap_err(), + ParserError::ParserError("Expected: stage name identifier, found: EOF".to_string()), + ); + assert_eq!( + snowflake() + .parse_sql_statements("SELECT * FROM @;") + .unwrap_err(), + ParserError::ParserError("Expected: stage name identifier, found: ;".to_string()), + ); + assert_eq!( + snowflake() + .parse_sql_statements("SELECT * FROM @, item") + .unwrap_err(), + ParserError::ParserError("Expected: stage name identifier, found: ,".to_string()), + ); + assert_eq!( + snowflake() + .parse_sql_statements("SELECT * FROM @.stage") + .unwrap_err(), + ParserError::ParserError("Expected: stage name identifier, found: .".to_string()), + ); +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
