diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index f756c4159..c95f0ef6e 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -1247,6 +1247,8 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result ident.push('/'), Token::Plus => ident.push('+'), Token::Minus => ident.push('-'), + Token::Eq => ident.push('='), + Token::Colon => ident.push(':'), Token::Number(n, _) => ident.push_str(n), Token::Word(w) => ident.push_str(&w.to_string()), _ => return parser.expected_ref("stage name identifier", parser.peek_token_ref()), diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index c51cf3bdf..6abb3ef54 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2640,6 +2640,21 @@ fn test_snowflake_copy_into_stage_name_ends_with_parens() { } } +#[test] +fn test_snowflake_stage_name_with_special_chars() { + // Stage path with '=' (Hive-style partitioning) + snowflake().verified_stmt("SELECT * FROM @stage/day=18/23.parquet"); + + // Stage path with ':' (time-based partitioning) + snowflake().verified_stmt("SELECT * FROM @stage/0:18:23/23.parquet"); + + // COPY INTO with '=' in stage path + snowflake().verified_stmt("COPY INTO my_table FROM @stage/day=18/file.parquet"); + + // COPY INTO with ':' in stage path + snowflake().verified_stmt("COPY INTO my_table FROM @stage/0:18:23/file.parquet"); +} + #[test] fn test_snowflake_trim() { let real_sql = r#"SELECT customer_id, TRIM(sub_items.value:item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#;