From 1d9240758937c446045a175e1a5d133224bc7f0b Mon Sep 17 00:00:00 2001 From: osipovartem Date: Wed, 16 Sep 2026 13:16:49 +0300 Subject: [PATCH] Snowflake: parse nested COPY transformation paths --- src/dialect/snowflake.rs | 12 ++++++++++++ tests/sqlparser_snowflake.rs | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 098399800..03f094a54 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -1760,6 +1760,18 @@ fn parse_select_item_for_data_load( } } + // A simple staged field has a dedicated compact AST node. More complex + // paths and casts must fall back to the standard expression parser so it + // can preserve the complete `JsonAccess` / `Cast` expression instead of + // leaving trailing tokens for the enclosing COPY parser. + if matches!( + parser.peek_token_ref().token, + Token::Colon | Token::Period | Token::LBracket | Token::DoubleColon + ) { + let token = parser.next_token(); + return parser.expected("end of simple staged field", token); + } + // as if parser.parse_keyword(Keyword::AS) { item_as = Some(match parser.next_token().token { diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index c4f91e7e9..b71d27772 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2668,6 +2668,29 @@ fn test_copy_into_with_transformations() { snowflake().parse_sql_statements(sql1).unwrap(); } +#[test] +fn test_copy_into_with_nested_transformation_path() { + let sql = concat!( + "COPY INTO cities FROM ", + "(SELECT $1:continent::VARCHAR, $1:country:name::VARCHAR ", + "FROM @sf_tut_stage/cities.parquet)" + ); + + let statement = snowflake().verified_stmt(sql); + assert_eq!(statement.to_string(), sql); + let Statement::CopyIntoSnowflake { + from_transformations: Some(transformations), + .. + } = statement + else { + panic!("expected COPY transformations"); + }; + assert!(transformations.iter().all(|item| matches!( + item, + StageLoadSelectItemKind::SelectItem(SelectItem::UnnamedExpr(Expr::Cast { .. })) + ))); +} + #[test] fn test_copy_into_file_format() { let sql = concat!(