@@ -1336,58 +1336,91 @@ struct StageProperties {
13361336/// Parse the stage property groups (`internalStageParams`/`externalStageParams`,
13371337/// `DIRECTORY`, `FILE_FORMAT`, `COPY_OPTIONS`, `COMMENT`) shared by
13381338/// `CREATE STAGE` and `ALTER STAGE ... SET`.
1339+ ///
1340+ /// Snowflake accepts these groups in any order (e.g. `FILE_FORMAT = (...) URL =
1341+ /// '...'`), so the groups are matched in a loop until none remains rather than
1342+ /// in a fixed sequence. Each group may appear at most once.
13391343fn parse_stage_properties ( parser : & mut Parser ) -> Result < StageProperties , ParserError > {
1340- // [ internalStageParams | externalStageParams ]
1341- let stage_params = parse_stage_params ( parser) ?;
1342-
1344+ let empty_options = || KeyValueOptions {
1345+ options : vec ! [ ] ,
1346+ delimiter : KeyValueOptionsDelimiter :: Space ,
1347+ } ;
1348+ let ( mut url, mut storage_integration, mut endpoint) = ( None , None , None ) ;
1349+ let mut encryption = empty_options ( ) ;
1350+ let mut credentials = empty_options ( ) ;
13431351 let mut directory_table_params = Vec :: new ( ) ;
13441352 let mut file_format = Vec :: new ( ) ;
13451353 let mut copy_options = Vec :: new ( ) ;
13461354 let mut comment = None ;
13471355
1348- // [ directoryTableParams ]
1349- if parser. parse_keyword ( Keyword :: DIRECTORY ) {
1350- parser. expect_token ( & Token :: Eq ) ?;
1351- directory_table_params = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1352- }
1353-
1354- // [ file_format]
1355- if parser. parse_keyword ( Keyword :: FILE_FORMAT ) {
1356- parser. expect_token ( & Token :: Eq ) ?;
1357- if parser. peek_token ( ) . token == Token :: LParen {
1358- file_format = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1356+ loop {
1357+ // [ internalStageParams | externalStageParams ]
1358+ if url. is_none ( ) && parser. parse_keyword ( Keyword :: URL ) {
1359+ parser. expect_token ( & Token :: Eq ) ?;
1360+ url = Some ( match parser. next_token ( ) . token {
1361+ Token :: SingleQuotedString ( word) => Ok ( word) ,
1362+ _ => parser. expected_ref ( "a URL statement" , parser. peek_token_ref ( ) ) ,
1363+ } ?) ;
1364+ } else if storage_integration. is_none ( )
1365+ && parser. parse_keyword ( Keyword :: STORAGE_INTEGRATION )
1366+ {
1367+ parser. expect_token ( & Token :: Eq ) ?;
1368+ storage_integration = Some ( parser. next_token ( ) . token . to_string ( ) ) ;
1369+ } else if endpoint. is_none ( ) && parser. parse_keyword ( Keyword :: ENDPOINT ) {
1370+ parser. expect_token ( & Token :: Eq ) ?;
1371+ endpoint = Some ( match parser. next_token ( ) . token {
1372+ Token :: SingleQuotedString ( word) => Ok ( word) ,
1373+ _ => parser. expected_ref ( "an endpoint statement" , parser. peek_token_ref ( ) ) ,
1374+ } ?) ;
1375+ } else if credentials. options . is_empty ( ) && parser. parse_keyword ( Keyword :: CREDENTIALS ) {
1376+ parser. expect_token ( & Token :: Eq ) ?;
1377+ credentials. options = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1378+ } else if encryption. options . is_empty ( ) && parser. parse_keyword ( Keyword :: ENCRYPTION ) {
1379+ parser. expect_token ( & Token :: Eq ) ?;
1380+ encryption. options = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1381+ } else if directory_table_params. is_empty ( ) && parser. parse_keyword ( Keyword :: DIRECTORY ) {
1382+ parser. expect_token ( & Token :: Eq ) ?;
1383+ directory_table_params = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1384+ } else if file_format. is_empty ( ) && parser. parse_keyword ( Keyword :: FILE_FORMAT ) {
1385+ parser. expect_token ( & Token :: Eq ) ?;
1386+ if parser. peek_token ( ) . token == Token :: LParen {
1387+ file_format = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1388+ } else {
1389+ // Shorthand `FILE_FORMAT = '<name>'` / `FILE_FORMAT = <ident>`
1390+ // is sugar for `FILE_FORMAT = (FORMAT_NAME = <name>)` —
1391+ // normalize it.
1392+ let tok = parser. peek_token ( ) ;
1393+ let value = match tok. token {
1394+ Token :: Word ( w) => {
1395+ parser. next_token ( ) ;
1396+ Value :: Placeholder ( w. value . clone ( ) ) . with_span ( tok. span )
1397+ }
1398+ _ => parser. parse_value ( ) ?,
1399+ } ;
1400+ file_format = vec ! [ KeyValueOption {
1401+ option_name: "FORMAT_NAME" . to_string( ) ,
1402+ option_value: KeyValueOptionKind :: Single ( value) ,
1403+ } ] ;
1404+ }
1405+ } else if copy_options. is_empty ( ) && parser. parse_keyword ( Keyword :: COPY_OPTIONS ) {
1406+ parser. expect_token ( & Token :: Eq ) ?;
1407+ copy_options = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1408+ } else if comment. is_none ( ) && parser. parse_keyword ( Keyword :: COMMENT ) {
1409+ parser. expect_token ( & Token :: Eq ) ?;
1410+ comment = Some ( parser. parse_comment_value ( ) ?) ;
13591411 } else {
1360- // Shorthand `FILE_FORMAT = '<name>'` / `FILE_FORMAT = <ident>` is
1361- // sugar for `FILE_FORMAT = (FORMAT_NAME = <name>)` — normalize it.
1362- let tok = parser. peek_token ( ) ;
1363- let value = match tok. token {
1364- Token :: Word ( w) => {
1365- parser. next_token ( ) ;
1366- Value :: Placeholder ( w. value . clone ( ) ) . with_span ( tok. span )
1367- }
1368- _ => parser. parse_value ( ) ?,
1369- } ;
1370- file_format = vec ! [ KeyValueOption {
1371- option_name: "FORMAT_NAME" . to_string( ) ,
1372- option_value: KeyValueOptionKind :: Single ( value) ,
1373- } ] ;
1412+ break ;
13741413 }
13751414 }
13761415
1377- // [ copy_options ]
1378- if parser. parse_keyword ( Keyword :: COPY_OPTIONS ) {
1379- parser. expect_token ( & Token :: Eq ) ?;
1380- copy_options = parser. parse_key_value_options ( true , & [ ] ) ?. options ;
1381- }
1382-
1383- // [ comment ]
1384- if parser. parse_keyword ( Keyword :: COMMENT ) {
1385- parser. expect_token ( & Token :: Eq ) ?;
1386- comment = Some ( parser. parse_comment_value ( ) ?) ;
1387- }
1388-
13891416 Ok ( StageProperties {
1390- stage_params,
1417+ stage_params : StageParamsObject {
1418+ url,
1419+ encryption,
1420+ endpoint,
1421+ storage_integration,
1422+ credentials,
1423+ } ,
13911424 directory_table_params : KeyValueOptions {
13921425 options : directory_table_params,
13931426 delimiter : KeyValueOptionsDelimiter :: Space ,
0 commit comments