@@ -31,12 +31,13 @@ use crate::ast::{
3131 AlterTableOperation , AlterTableType , CatalogRestAuthentication , CatalogRestConfig ,
3232 CatalogSource , CatalogSyncNamespaceMode , CatalogTableFormat , ColumnOption , ColumnPolicy ,
3333 ColumnPolicyProperty , ContactEntry , CopyIntoSnowflakeKind , CreateTable , CreateTableLikeKind ,
34- DollarQuotedString , ExternalVolumeEncryption , ExternalVolumeStorageLocation , Ident ,
34+ DollarQuotedString , Expr , ExternalVolumeEncryption , ExternalVolumeStorageLocation , Ident ,
3535 IdentityParameters , IdentityProperty , IdentityPropertyFormatKind , IdentityPropertyKind ,
3636 IdentityPropertyOrder , InitializeKind , Insert , MultiTableInsertIntoClause ,
3737 MultiTableInsertType , MultiTableInsertValue , MultiTableInsertValues ,
3838 MultiTableInsertWhenClause , ObjectName , ObjectNamePart , ObjectType , OperateFunctionArg ,
39- RefreshModeKind , RowAccessPolicy , ShowKeysKind , ShowObjects , SqlOption , Statement ,
39+ RefreshModeKind , RenameTableNameKind , RowAccessPolicy , ShowKeysKind , ShowObjects , SqlOption ,
40+ Statement ,
4041 StorageLifecyclePolicy , StorageSerializationPolicy , Tag , TableObject , TagsColumnOption , Value ,
4142 WrappedCollection ,
4243} ;
@@ -955,21 +956,43 @@ fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statem
955956}
956957
957958/// Parse snowflake alter dynamic table.
958- /// <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
959+ /// <https://docs.snowflake.com/en/sql-reference/sql/alter-dynamic- table>
959960fn parse_alter_dynamic_table ( parser : & mut Parser ) -> Result < Statement , ParserError > {
961+ let if_exists = parser. parse_keywords ( & [ Keyword :: IF , Keyword :: EXISTS ] ) ;
960962 // Use parse_object_name(true) to support IDENTIFIER() function
961963 let table_name = parser. parse_object_name ( true ) ?;
962964
963- // Parse the operation (REFRESH, SUSPEND, or RESUME)
964965 let operation = if parser. parse_keyword ( Keyword :: REFRESH ) {
965966 AlterTableOperation :: Refresh { subpath : None }
966967 } else if parser. parse_keyword ( Keyword :: SUSPEND ) {
967968 AlterTableOperation :: Suspend
968969 } else if parser. parse_keyword ( Keyword :: RESUME ) {
969970 AlterTableOperation :: Resume
971+ } else if parser. parse_keyword ( Keyword :: RENAME ) {
972+ parser. expect_keyword_is ( Keyword :: TO ) ?;
973+ let new_name = parser. parse_object_name ( false ) ?;
974+ AlterTableOperation :: RenameTable {
975+ table_name : RenameTableNameKind :: To ( new_name) ,
976+ }
977+ } else if parser. parse_keywords ( & [ Keyword :: CLUSTER , Keyword :: BY ] ) {
978+ parser. expect_token ( & Token :: LParen ) ?;
979+ let exprs = parser. parse_comma_separated ( |p| p. parse_expr ( ) ) ?;
980+ parser. expect_token ( & Token :: RParen ) ?;
981+ AlterTableOperation :: ClusterBy { exprs }
982+ } else if parser. parse_keywords ( & [ Keyword :: DROP , Keyword :: CLUSTERING , Keyword :: KEY ] ) {
983+ AlterTableOperation :: DropClusteringKey
984+ } else if parser. parse_keyword ( Keyword :: SET ) {
985+ AlterTableOperation :: SetOptionsParens {
986+ options : parse_alter_dynamic_table_properties ( parser, false ) ?,
987+ }
988+ } else if parser. parse_keyword ( Keyword :: UNSET ) {
989+ AlterTableOperation :: SetOptionsParens {
990+ options : parse_alter_dynamic_table_properties ( parser, true ) ?,
991+ }
970992 } else {
971993 return parser. expected_ref (
972- "REFRESH, SUSPEND, or RESUME after ALTER DYNAMIC TABLE" ,
994+ "REFRESH, SUSPEND, RESUME, RENAME, SET, UNSET, CLUSTER BY, \
995+ or DROP CLUSTERING KEY after ALTER DYNAMIC TABLE",
973996 parser. peek_token_ref ( ) ,
974997 ) ;
975998 } ;
@@ -982,7 +1005,7 @@ fn parse_alter_dynamic_table(parser: &mut Parser) -> Result<Statement, ParserErr
9821005
9831006 Ok ( Statement :: AlterTable ( AlterTable {
9841007 name : table_name,
985- if_exists : false ,
1008+ if_exists,
9861009 only : false ,
9871010 operations : vec ! [ operation] ,
9881011 location : None ,
@@ -992,6 +1015,99 @@ fn parse_alter_dynamic_table(parser: &mut Parser) -> Result<Statement, ParserErr
9921015 } ) )
9931016}
9941017
1018+ /// Parse the property list of `ALTER DYNAMIC TABLE … SET/UNSET`. Properties are
1019+ /// space- or comma-separated. Each becomes an [`SqlOption::KeyValue`] whose
1020+ /// value is a string literal — including identifier values (`WAREHOUSE = wh`)
1021+ /// and keyword values (`TARGET_LAG = DOWNSTREAM`) — so the identifier
1022+ /// canonicalizer never rewrites the user's casing (SHOW echoes it verbatim).
1023+ /// `UNSET <prop>` is encoded as `<prop> = NULL`.
1024+ fn parse_alter_dynamic_table_properties (
1025+ parser : & mut Parser ,
1026+ unset : bool ,
1027+ ) -> Result < Vec < SqlOption > , ParserError > {
1028+ let mut options = vec ! [ parse_alter_dynamic_table_property( parser, unset) ?] ;
1029+ loop {
1030+ let _ = parser. consume_token ( & Token :: Comma ) ;
1031+ if matches ! ( parser. peek_token( ) . token, Token :: EOF | Token :: SemiColon ) {
1032+ break ;
1033+ }
1034+ options. push ( parse_alter_dynamic_table_property ( parser, unset) ?) ;
1035+ }
1036+ Ok ( options)
1037+ }
1038+
1039+ /// Parse one `ALTER DYNAMIC TABLE … SET/UNSET` property into an
1040+ /// [`SqlOption::KeyValue`]. See [`parse_alter_dynamic_table_properties`].
1041+ fn parse_alter_dynamic_table_property (
1042+ parser : & mut Parser ,
1043+ unset : bool ,
1044+ ) -> Result < SqlOption , ParserError > {
1045+ let key_token = parser. next_token ( ) ;
1046+ let key = match & key_token. token {
1047+ Token :: Word ( w) if w. quote_style . is_none ( ) => w. value . to_uppercase ( ) ,
1048+ _ => return parser. expected ( "a dynamic table property name" , key_token) ,
1049+ } ;
1050+
1051+ if key == "IMMUTABLE" {
1052+ parser. expect_keyword_is ( Keyword :: WHERE ) ?;
1053+ let value = if unset {
1054+ Value :: Null
1055+ } else {
1056+ parser. expect_token ( & Token :: LParen ) ?;
1057+ let predicate = parser. parse_expr ( ) ?;
1058+ parser. expect_token ( & Token :: RParen ) ?;
1059+ Value :: SingleQuotedString ( predicate. to_string ( ) )
1060+ } ;
1061+ return Ok ( SqlOption :: KeyValue {
1062+ key : Ident :: new ( "IMMUTABLE_WHERE" ) ,
1063+ value : Expr :: Value ( value. into ( ) ) ,
1064+ } ) ;
1065+ }
1066+
1067+ if unset {
1068+ if !matches ! (
1069+ key. as_str( ) ,
1070+ "COMMENT" | "INITIALIZATION_WAREHOUSE" | "SCHEDULER"
1071+ ) {
1072+ return parser. expected (
1073+ "COMMENT, INITIALIZATION_WAREHOUSE, SCHEDULER, or IMMUTABLE WHERE after UNSET" ,
1074+ key_token,
1075+ ) ;
1076+ }
1077+ return Ok ( SqlOption :: KeyValue {
1078+ key : Ident :: new ( key) ,
1079+ value : Expr :: Value ( Value :: Null . into ( ) ) ,
1080+ } ) ;
1081+ }
1082+
1083+ if !matches ! (
1084+ key. as_str( ) ,
1085+ "TARGET_LAG" | "WAREHOUSE" | "INITIALIZATION_WAREHOUSE" | "COMMENT" | "SCHEDULER"
1086+ ) {
1087+ return parser. expected ( "a dynamic table property name" , key_token) ;
1088+ }
1089+ parser. expect_token ( & Token :: Eq ) ?;
1090+ let value_token = parser. next_token ( ) ;
1091+ let value = match & value_token. token {
1092+ Token :: SingleQuotedString ( s) => s. clone ( ) ,
1093+ // WAREHOUSE / INITIALIZATION_WAREHOUSE keep the user's verbatim
1094+ // spelling; other keyword values (bare DOWNSTREAM) are uppercased.
1095+ Token :: Word ( w)
1096+ if w. quote_style . is_none ( )
1097+ && key != "WAREHOUSE"
1098+ && key != "INITIALIZATION_WAREHOUSE" =>
1099+ {
1100+ w. value . to_uppercase ( )
1101+ }
1102+ Token :: Word ( w) if w. quote_style . is_none ( ) => w. value . clone ( ) ,
1103+ _ => return parser. expected ( "a property value" , value_token) ,
1104+ } ;
1105+ Ok ( SqlOption :: KeyValue {
1106+ key : Ident :: new ( key) ,
1107+ value : Expr :: Value ( Value :: SingleQuotedString ( value) . into ( ) ) ,
1108+ } )
1109+ }
1110+
9951111/// Parse snowflake alter external table.
9961112/// <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
9971113fn parse_alter_external_table ( parser : & mut Parser ) -> Result < Statement , ParserError > {
0 commit comments