@@ -28,13 +28,14 @@ use crate::ast::helpers::stmt_data_loading::{
2828} ;
2929use crate :: ast:: {
3030 AlterTable , AlterTableOperation , AlterTableType , CatalogSyncNamespaceMode , ColumnOption ,
31- ColumnPolicy , ColumnPolicyProperty , ContactEntry , CopyIntoSnowflakeKind , CreateTable ,
32- CreateTableLikeKind , DollarQuotedString , Ident , IdentityParameters , IdentityProperty ,
33- IdentityPropertyFormatKind , IdentityPropertyKind , IdentityPropertyOrder , InitializeKind ,
34- Insert , MultiTableInsertIntoClause , MultiTableInsertType , MultiTableInsertValue ,
35- MultiTableInsertValues , MultiTableInsertWhenClause , ObjectName , ObjectNamePart ,
36- RefreshModeKind , RowAccessPolicy , ShowObjects , SqlOption , Statement , StorageLifecyclePolicy ,
37- StorageSerializationPolicy , TableObject , TagsColumnOption , Value , WrappedCollection ,
31+ ColumnPolicy , ColumnPolicyProperty , ContactEntry , CopyIntoSnowflakeKind , CreateExternalVolume ,
32+ CreateTable , CreateTableLikeKind , DollarQuotedString , Ident , IdentityParameters ,
33+ IdentityProperty , IdentityPropertyFormatKind , IdentityPropertyKind , IdentityPropertyOrder ,
34+ InitializeKind , Insert , MultiTableInsertIntoClause , MultiTableInsertType ,
35+ MultiTableInsertValue , MultiTableInsertValues , MultiTableInsertWhenClause , ObjectName ,
36+ ObjectNamePart , RefreshModeKind , RowAccessPolicy , ShowObjects , SqlOption , Statement ,
37+ StorageLifecyclePolicy , StorageSerializationPolicy , TableObject , TagsColumnOption , Value ,
38+ WrappedCollection ,
3839} ;
3940use crate :: dialect:: { Dialect , Precedence } ;
4041use crate :: keywords:: Keyword ;
@@ -312,6 +313,12 @@ impl Dialect for SnowflakeDialect {
312313 // possibly CREATE STAGE
313314 //[ OR REPLACE ]
314315 let or_replace = parser. parse_keywords ( & [ Keyword :: OR , Keyword :: REPLACE ] ) ;
316+
317+ // CREATE [OR REPLACE] EXTERNAL VOLUME
318+ if parser. parse_keywords ( & [ Keyword :: EXTERNAL , Keyword :: VOLUME ] ) {
319+ return Some ( parse_create_external_volume ( or_replace, parser) ) ;
320+ }
321+
315322 // LOCAL | GLOBAL
316323 let global = match parser. parse_one_of_keywords ( & [ Keyword :: LOCAL , Keyword :: GLOBAL ] ) {
317324 Some ( Keyword :: LOCAL ) => Some ( false ) ,
@@ -2018,3 +2025,63 @@ fn parse_multi_table_insert_when_clauses(
20182025
20192026 Ok ( ( when_clauses, else_clause) )
20202027}
2028+
2029+ /// Parse `CREATE [OR REPLACE] EXTERNAL VOLUME [IF NOT EXISTS] <name> ...`
2030+ ///
2031+ /// Each storage location is parsed by [`parse_external_volume_storage_location`];
2032+ /// the trailing `ALLOW_WRITES` and `COMMENT` properties are accepted in any
2033+ /// order.
2034+ fn parse_create_external_volume (
2035+ or_replace : bool ,
2036+ parser : & mut Parser ,
2037+ ) -> Result < Statement , ParserError > {
2038+ let if_not_exists = parser. parse_keywords ( & [ Keyword :: IF , Keyword :: NOT , Keyword :: EXISTS ] ) ;
2039+ let name = parser. parse_object_name ( false ) ?;
2040+
2041+ parser. expect_keyword_is ( Keyword :: STORAGE_LOCATIONS ) ?;
2042+ parser. expect_token ( & Token :: Eq ) ?;
2043+ parser. expect_token ( & Token :: LParen ) ?;
2044+
2045+ let storage_locations = parser. parse_comma_separated ( parse_external_volume_storage_location) ?;
2046+ parser. expect_token ( & Token :: RParen ) ?;
2047+
2048+ let mut allow_writes = None ;
2049+ let mut comment = None ;
2050+
2051+ loop {
2052+ if parser. parse_keyword ( Keyword :: ALLOW_WRITES ) {
2053+ parser. expect_token ( & Token :: Eq ) ?;
2054+ allow_writes = Some ( parser. parse_boolean_string ( ) ?) ;
2055+ } else if parser. parse_keyword ( Keyword :: COMMENT ) {
2056+ parser. expect_token ( & Token :: Eq ) ?;
2057+ comment = Some ( parser. parse_comment_value ( ) ?) ;
2058+ } else {
2059+ break ;
2060+ }
2061+ }
2062+
2063+ Ok ( CreateExternalVolume {
2064+ or_replace,
2065+ if_not_exists,
2066+ name,
2067+ storage_locations,
2068+ allow_writes,
2069+ comment,
2070+ }
2071+ . into ( ) )
2072+ }
2073+
2074+ /// Parse one parenthesized storage-location option list, e.g.
2075+ /// `(NAME='loc1' STORAGE_PROVIDER='S3' ...)`. The options (and the
2076+ /// `ENCRYPTION = (...)` sub-list) are parsed generically via
2077+ /// [`Parser::parse_key_value_options`]; only an empty list is rejected,
2078+ /// field order and the exact option set are left to the consumer.
2079+ fn parse_external_volume_storage_location (
2080+ parser : & mut Parser ,
2081+ ) -> Result < KeyValueOptions , ParserError > {
2082+ let location = parser. parse_key_value_options ( true , & [ ] ) ?;
2083+ if location. options . is_empty ( ) {
2084+ return parser. expected ( "storage location options" , parser. peek_token ( ) ) ;
2085+ }
2086+ Ok ( location)
2087+ }
0 commit comments