Skip to content

Commit 38667da

Browse files
Snowflake: parse ALTER DYNAMIC TABLE SET/UNSET/RENAME/CLUSTER natively
1 parent e561165 commit 38667da

1 file changed

Lines changed: 122 additions & 6 deletions

File tree

src/dialect/snowflake.rs

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
959960
fn 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>
9971113
fn parse_alter_external_table(parser: &mut Parser) -> Result<Statement, ParserError> {

0 commit comments

Comments
 (0)