@@ -27,11 +27,13 @@ use crate::ast::helpers::stmt_data_loading::{
2727 FileStagingCommand , StageLoadSelectItem , StageLoadSelectItemKind , StageParamsObject ,
2828} ;
2929use crate :: ast:: {
30- AlterExternalVolumeOperation , AlterFileFormatOperation , AlterStageOperation , AlterTable ,
30+ AlterExternalVolumeOperation , AlterFileFormatOperation , AlterProcedure ,
31+ AlterProcedureOperation , AlterStageOperation , AlterTable ,
3132 AlterTableOperation , AlterTableType , CatalogRestAuthentication , CatalogRestConfig ,
3233 CatalogSource , CatalogSyncNamespaceMode , CatalogTableFormat , ColumnOption , ColumnPolicy ,
3334 ColumnPolicyProperty , ContactEntry , CopyIntoSnowflakeKind , CreateTable , CreateTableLikeKind ,
34- DollarQuotedString , Expr , ExternalVolumeEncryption , ExternalVolumeStorageLocation , Ident ,
35+ DollarQuotedString , Expr , ExternalVolumeEncryption , ExternalVolumeStorageLocation ,
36+ Ident , ProcedureExecuteAs ,
3537 IdentityParameters , IdentityProperty , IdentityPropertyFormatKind , IdentityPropertyKind ,
3638 IdentityPropertyOrder , InitializeKind , Insert , MultiTableInsertIntoClause ,
3739 MultiTableInsertType , MultiTableInsertValue , MultiTableInsertValues ,
@@ -201,6 +203,12 @@ impl Dialect for SnowflakeDialect {
201203 true
202204 }
203205
206+ /// Snowflake scripting accepts `CALL p(...) INTO :var` to capture a
207+ /// procedure result into local variables.
208+ fn supports_call_into ( & self ) -> bool {
209+ true
210+ }
211+
204212 /// See <https://docs.snowflake.com/en/developer-guide/snowflake-scripting/cursors>
205213 fn supports_for_loop_over_cursor ( & self ) -> bool {
206214 true
@@ -290,6 +298,24 @@ impl Dialect for SnowflakeDialect {
290298 return Some ( parser. parse_begin_exception_end ( ) ) ;
291299 }
292300
301+ // Snowflake scripting `FETCH <cursor> INTO <var> [, ...]` has no
302+ // direction and no FROM/IN, so it can't go through the ISO parser.
303+ // Intercept only that shape; anything else falls through untouched.
304+ if parser. peek_keyword ( Keyword :: FETCH ) {
305+ if let Ok ( Some ( stmt) ) = parser. maybe_parse ( parse_fetch_into) {
306+ return Some ( Ok ( stmt) ) ;
307+ }
308+ }
309+
310+ // Snowflake anonymous procedure: `WITH <name> AS PROCEDURE ...`. Every
311+ // other `WITH` (ordinary CTE) fails the `AS PROCEDURE` probe and falls
312+ // through to the standard query parser.
313+ if parser. peek_keyword ( Keyword :: WITH ) {
314+ if let Ok ( Some ( stmt) ) = parser. maybe_parse ( parse_with_procedure) {
315+ return Some ( Ok ( stmt) ) ;
316+ }
317+ }
318+
293319 if parser. parse_keywords ( & [ Keyword :: ALTER , Keyword :: DYNAMIC , Keyword :: TABLE ] ) {
294320 // ALTER DYNAMIC TABLE
295321 return Some ( parse_alter_dynamic_table ( parser) ) ;
@@ -315,6 +341,11 @@ impl Dialect for SnowflakeDialect {
315341 return Some ( parse_alter_storage_integration ( parser) ) ;
316342 }
317343
344+ if parser. parse_keywords ( & [ Keyword :: ALTER , Keyword :: PROCEDURE ] ) {
345+ // ALTER PROCEDURE
346+ return Some ( parse_alter_procedure ( parser) ) ;
347+ }
348+
318349 if parser. parse_keywords ( & [ Keyword :: ALTER , Keyword :: FILE , Keyword :: FORMAT ] ) {
319350 // ALTER FILE FORMAT
320351 return Some ( parse_alter_file_format ( parser) ) ;
@@ -1140,6 +1171,126 @@ fn parse_alter_dynamic_table_property(
11401171 } )
11411172}
11421173
1174+ /// Parse Snowflake scripting `FETCH <cursor> INTO <var> [, <var> ...]`.
1175+ ///
1176+ /// The caller has verified the next keyword is `FETCH` and runs this via
1177+ /// `maybe_parse`, so a non-scripting `FETCH` simply errors out and rewinds.
1178+ fn parse_fetch_into ( parser : & mut Parser ) -> Result < Statement , ParserError > {
1179+ parser. expect_keyword ( Keyword :: FETCH ) ?;
1180+ let cursor = parser. parse_identifier ( ) ?;
1181+ parser. expect_keyword ( Keyword :: INTO ) ?;
1182+ let into = parser. parse_scripting_into_targets ( ) ?;
1183+ Ok ( Statement :: FetchInto { cursor, into } )
1184+ }
1185+
1186+ /// Parse `ALTER PROCEDURE [IF EXISTS] <name> ( [<arg_type> [, ...]] )
1187+ /// { RENAME TO ... | SET COMMENT = ... | UNSET COMMENT | EXECUTE AS CALLER|OWNER }`.
1188+ ///
1189+ /// The `ALTER PROCEDURE` keywords are already consumed by the caller.
1190+ fn parse_alter_procedure ( parser : & mut Parser ) -> Result < Statement , ParserError > {
1191+ let if_exists = parser. parse_keywords ( & [ Keyword :: IF , Keyword :: EXISTS ] ) ;
1192+ let name = parser. parse_object_name ( false ) ?;
1193+
1194+ parser. expect_token ( & Token :: LParen ) ?;
1195+ let args = if parser. peek_token_ref ( ) . token == Token :: RParen {
1196+ vec ! [ ]
1197+ } else {
1198+ parser. parse_comma_separated ( Parser :: parse_data_type) ?
1199+ } ;
1200+ parser. expect_token ( & Token :: RParen ) ?;
1201+
1202+ let operation = if parser. parse_keywords ( & [ Keyword :: RENAME , Keyword :: TO ] ) {
1203+ AlterProcedureOperation :: RenameTo {
1204+ new_name : parser. parse_object_name ( false ) ?,
1205+ }
1206+ } else if parser. parse_keywords ( & [ Keyword :: EXECUTE , Keyword :: AS ] ) {
1207+ let execute_as = if parser. parse_keyword ( Keyword :: CALLER ) {
1208+ ProcedureExecuteAs :: Caller
1209+ } else {
1210+ parser. expect_keyword_is ( Keyword :: OWNER ) ?;
1211+ ProcedureExecuteAs :: Owner
1212+ } ;
1213+ AlterProcedureOperation :: ExecuteAs ( execute_as)
1214+ } else if parser. parse_keyword ( Keyword :: SET ) {
1215+ parser. expect_keyword_is ( Keyword :: COMMENT ) ?;
1216+ parser. expect_token ( & Token :: Eq ) ?;
1217+ AlterProcedureOperation :: SetComment {
1218+ comment : parser. parse_expr ( ) ?,
1219+ }
1220+ } else if parser. parse_keyword ( Keyword :: UNSET ) {
1221+ parser. expect_keyword_is ( Keyword :: COMMENT ) ?;
1222+ AlterProcedureOperation :: UnsetComment
1223+ } else {
1224+ return parser. expected_ref (
1225+ "RENAME TO, SET COMMENT, UNSET COMMENT, or EXECUTE AS after ALTER PROCEDURE" ,
1226+ parser. peek_token_ref ( ) ,
1227+ ) ;
1228+ } ;
1229+
1230+ Ok ( Statement :: AlterProcedure ( AlterProcedure {
1231+ if_exists,
1232+ name,
1233+ args,
1234+ operation,
1235+ } ) )
1236+ }
1237+
1238+ /// Parse Snowflake anonymous procedure:
1239+ /// `WITH <name> AS PROCEDURE (<args>) RETURNS <type> LANGUAGE <lang>
1240+ /// [EXECUTE AS ...] AS <body> CALL <name>(<args>)`.
1241+ ///
1242+ /// The caller runs this via `maybe_parse`, so an ordinary CTE fails the
1243+ /// `AS PROCEDURE` probe and rewinds.
1244+ fn parse_with_procedure ( parser : & mut Parser ) -> Result < Statement , ParserError > {
1245+ parser. expect_keyword ( Keyword :: WITH ) ?;
1246+ let name = parser. parse_identifier ( ) ?;
1247+ parser. expect_keyword_is ( Keyword :: AS ) ?;
1248+ parser. expect_keyword_is ( Keyword :: PROCEDURE ) ?;
1249+
1250+ let params = parser. parse_optional_procedure_parameters ( ) ?;
1251+
1252+ let returns = if parser. parse_keyword ( Keyword :: RETURNS ) {
1253+ Some ( parser. parse_data_type ( ) ?)
1254+ } else {
1255+ None
1256+ } ;
1257+ // Snowflake allows a `NOT NULL` return-type annotation; drop it.
1258+ let _ = parser. parse_keywords ( & [ Keyword :: NOT , Keyword :: NULL ] ) ;
1259+
1260+ let language = if parser. parse_keyword ( Keyword :: LANGUAGE ) {
1261+ Some ( parser. parse_identifier ( ) ?)
1262+ } else {
1263+ None
1264+ } ;
1265+
1266+ let execute_as = if parser. parse_keywords ( & [ Keyword :: EXECUTE , Keyword :: AS ] ) {
1267+ if parser. parse_keyword ( Keyword :: CALLER ) {
1268+ Some ( ProcedureExecuteAs :: Caller )
1269+ } else {
1270+ parser. expect_keyword_is ( Keyword :: OWNER ) ?;
1271+ Some ( ProcedureExecuteAs :: Owner )
1272+ }
1273+ } else {
1274+ None
1275+ } ;
1276+
1277+ parser. expect_keyword_is ( Keyword :: AS ) ?;
1278+ let body = parser. parse_procedure_body ( ) ?;
1279+
1280+ parser. expect_keyword ( Keyword :: CALL ) ?;
1281+ let call = Box :: new ( parser. parse_call ( ) ?) ;
1282+
1283+ Ok ( Statement :: WithProcedure {
1284+ name,
1285+ params,
1286+ returns,
1287+ language,
1288+ execute_as,
1289+ body,
1290+ call,
1291+ } )
1292+ }
1293+
11431294/// Parse snowflake alter materialized view.
11441295/// <https://docs.snowflake.com/en/sql-reference/sql/alter-materialized-view>
11451296///
0 commit comments