diff --git a/crates/sqlquack-syntax/src/parser/_helpers/ddl/mod.rs b/crates/sqlquack-syntax/src/parser/_helpers/ddl/mod.rs index bcc1c2284..21543329a 100644 --- a/crates/sqlquack-syntax/src/parser/_helpers/ddl/mod.rs +++ b/crates/sqlquack-syntax/src/parser/_helpers/ddl/mod.rs @@ -8,5 +8,6 @@ pub(crate) mod create_table_forms; pub(crate) mod explain; pub(crate) mod scripting; pub(crate) mod snowflake_alter; +pub(crate) mod snowflake_alter_objects; pub(crate) mod snowflake_alter_resources; pub(crate) mod table_options; diff --git a/crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter.rs b/crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter.rs index f92b640db..e650afc10 100644 --- a/crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter.rs +++ b/crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter.rs @@ -15,6 +15,9 @@ use crate::tokens::types::Result; impl Parser { /// Parse a Snowflake ALTER of a kind this module owns, or None when the next words name another kind. pub(crate) fn parse_alter_snowflake_object(&mut self) -> Result> { + if let Some(expr) = self.parse_alter_snowflake_objects()? { + return Ok(Some(expr)); + } if self.check_identifier("TASK") && self.check_next_identifier("GRAPH") && !self diff --git a/crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter_objects.rs b/crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter_objects.rs new file mode 100644 index 000000000..3e051aae8 --- /dev/null +++ b/crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter_objects.rs @@ -0,0 +1,831 @@ +//! Snowflake ALTER of APPLICATION, APPLICATION PACKAGE, APPLICATION SERVICE, BACKUP SET, CORTEX SEARCH SERVICE, DATABASE, SEMANTIC VIEW and STREAMLIT. + +use crate::expressions::names::models::Identifier; +use crate::expressions::names::models::TableRef; +use crate::expressions::schema_objects::models::ObjectProperty; +use crate::expressions::tree::models::Expression; +use crate::expressions::types::models::Tuple; +use crate::parser::_helpers::ddl::snowflake_alter::alter_object; +use crate::parser::_helpers::ddl::snowflake_alter::property; +use crate::parser::constants::CONTACT; +use crate::parser::constants::TAG; +use crate::parser::models::Parser; +use crate::tokens::models::TokenType; +use crate::tokens::types::Result; + +/// The options that may follow a release directive's VERSION and PATCH. +const UPGRADE_OPTIONS: &[&str] = &[ + "UPGRADE_AFTER", + "UPGRADE_IN_MAINTENANCE_WINDOW", + "UPGRADE_DEADLINE", +]; + +type Parts = (Vec, Vec); + +impl Parser { + /// Parse a Snowflake ALTER of a kind this module owns, or None when the next words name another kind. + pub(crate) fn parse_alter_snowflake_objects(&mut self) -> Result> { + if self.match_text_seq(&["APPLICATION", "PACKAGE"]) { + return self.parse_alter_application_package().map(Some); + } + if self.match_text_seq(&["APPLICATION", "SERVICE"]) { + return self.parse_alter_application_service().map(Some); + } + if self.match_text_seq(&["APPLICATION"]) { + return self.parse_alter_application().map(Some); + } + if self.match_text_seq(&["BACKUP", "SET"]) { + return self.parse_alter_backup_set().map(Some); + } + if self.match_text_seq(&["CORTEX", "SEARCH", "SERVICE"]) { + return self.parse_alter_cortex_search_service().map(Some); + } + if self.match_token(TokenType::Database) { + return self.parse_alter_database().map(Some); + } + if self.match_text_seq(&["SEMANTIC", "VIEW"]) { + return self.parse_alter_semantic_view().map(Some); + } + if self.match_text_seq(&["STREAMLIT"]) { + return self.parse_alter_streamlit().map(Some); + } + if self.check_text_seq(&["CORTEX"]) + || self.check_text_seq(&["SEMANTIC"]) + || self.check_text_seq(&["BACKUP"]) && !self.check_text_seq(&["BACKUP", "POLICY"]) + { + return Err(self.parse_error(format!( + "Snowflake has no ALTER {} {}", + self.peek_text().to_ascii_uppercase(), + self.text_at(1).unwrap_or_default() + ))); + } + Ok(None) + } + + /// Parse an object name as an operand. + fn object_operand(&mut self) -> Result { + Ok(Expression::Table(Box::new(self.parse_object_name()?))) + } + + /// Parse a comma-separated list of object names as one operand. + fn object_list_operand(&mut self) -> Result { + let mut names = vec![self.object_operand()?]; + while self.match_token(TokenType::Comma) { + names.push(self.object_operand()?); + } + Ok(tuple(names)) + } + + /// Parse a comma-separated list of account identifiers, each `[ . ] `. + fn account_list_operand(&mut self) -> Result { + let mut accounts = vec![Expression::Identifier(self.parse_simple_qualified_name()?)]; + while self.match_token(TokenType::Comma) { + accounts.push(Expression::Identifier(self.parse_simple_qualified_name()?)); + } + Ok(tuple(accounts)) + } + + /// Parse a parenthesized list of account identifiers. + fn parse_account_list(&mut self) -> Result { + self.expect_token(TokenType::LParen)?; + let list = self.account_list_operand()?; + self.expect_token(TokenType::RParen)?; + Ok(list) + } + + /// Parse an integer literal. + fn number_operand(&mut self) -> Result { + if !self.check(TokenType::Number) { + return Err(self.parse_error("Expected a number")); + } + self.parse_primary() + } + + /// Parse `RENAME TO ` or `SWAP WITH ` after its first word. + fn parse_alter_target(&mut self, second: &str) -> Result { + self.expect_alter_words(&[&[second]])?; + Ok((vec![self.object_operand()?], vec![])) + } + + /// Parse a property value: a parenthesized list, NULL, a literal or a name. + fn parse_alter_value(&mut self) -> Result { + if self.match_token(TokenType::LParen) { + let mut items: Vec = Vec::new(); + loop { + let item = self.parse_object_value()?; + if self.match_token(TokenType::Eq) { + items.push(tuple(vec![item, self.parse_object_value()?])); + } else { + items.push(item); + } + if !self.match_token(TokenType::Comma) { + break; + } + } + self.expect_token(TokenType::RParen)?; + return Ok(tuple(items)); + } + if self.check(TokenType::Null) { + return self.parse_primary(); + } + self.parse_object_value() + } + + /// Parse a parenthesized list of string literals. + fn parse_string_list(&mut self) -> Result { + self.expect_token(TokenType::LParen)?; + let mut items = vec![self.parse_string_value()?]; + while self.match_token(TokenType::Comma) { + items.push(self.parse_string_value()?); + } + self.expect_token(TokenType::RParen)?; + Ok(tuple(items)) + } + + /// Parse a parenthesized list of object names. + fn parse_name_list(&mut self) -> Result { + self.expect_token(TokenType::LParen)?; + let list = self.object_list_operand()?; + self.expect_token(TokenType::RParen)?; + Ok(list) + } + + /// Parse ` = ` for each name of `names` that comes next, in any order. + fn parse_named_options(&mut self, names: &[&str]) -> Result> { + let mut properties: Vec = Vec::new(); + while let Some(name) = names + .iter() + .find(|name| self.check_text_seq(&[name]) && self.check_next(TokenType::Eq)) + { + self.skip(); + self.skip(); + properties.push(property(name, None, Some(self.parse_alter_value()?))); + } + Ok(properties) + } + + /// Parse ` = ` for one required name. + fn parse_required_option(&mut self, name: &str) -> Result { + self.expect_alter_words(&[&[name]])?; + self.expect_token(TokenType::Eq)?; + Ok(property(name, None, Some(self.parse_alter_value()?))) + } + + /// Parse the properties of a SET (`set`) or UNSET: TAG and CONTACT lists, PRIMARY KEY, or named properties. + fn parse_alter_settings(&mut self, set: bool) -> Result> { + let mut properties: Vec = Vec::new(); + let mut contact_list_ends_set = false; + loop { + let named = self.check_next(TokenType::Eq) || self.check_next(TokenType::Comma) || { + let next = self.current + 1; + next >= self.tokens.len() || self.tokens[next].token_type == TokenType::Semicolon + }; + if !named && (self.check_text_seq(&["TAG"]) || self.check_text_seq(&["CONTACT"])) { + let kind = self.peek_text().to_ascii_uppercase(); + self.skip(); + loop { + let name = if kind == TAG { + self.parse_simple_qualified_name()? + } else { + self.parse_simple_name()? + }; + let value = if !set { + None + } else { + self.expect_token(TokenType::Eq)?; + Some(if kind == CONTACT { + self.object_operand()? + } else { + self.parse_object_value()? + }) + }; + properties.push(property(&kind, Some(name), value)); + if !self.match_token(TokenType::Comma) { + break; + } + if kind == CONTACT { + contact_list_ends_set = true; + } + } + } else if self.match_text_seq(&["PRIMARY", "KEY"]) { + let value = if set { + self.expect_token(TokenType::Eq)?; + Some(self.parse_name_list()?) + } else { + None + }; + properties.push(property("PRIMARY KEY", None, value)); + } else { + let name = self.parse_simple_name()?; + let value = if set { + self.expect_token(TokenType::Eq)?; + Some(self.parse_alter_value()?) + } else if self.match_token(TokenType::Eq) { + Some(self.object_operand()?) + } else { + None + }; + properties.push(property(&name.name.to_ascii_uppercase(), None, value)); + } + let comma = self.match_token(TokenType::Comma); + if self.is_at_end() || self.check(TokenType::Semicolon) || !set && !comma { + return Ok(properties); + } + if contact_list_ends_set { + return Err(self.parse_error("Expected the end of the statement after CONTACT")); + } + } + } + + /// Parse `SET ...` or `UNSET ...` after the word. + fn parse_alter_set(&mut self, set: bool) -> Result { + Ok((vec![], self.parse_alter_settings(set)?)) + } + + /// Parse ` [ FORCE ]` after `{ FEATURE | MAINTENANCE | BACKUP } POLICY`, with the action's suffix. + fn parse_alter_policy(&mut self) -> Result<(Parts, String)> { + let policy = self.object_operand()?; + let suffix = if self.match_text_seq(&["FORCE"]) { + " FORCE" + } else { + "" + }; + Ok(((vec![policy], vec![]), suffix.to_owned())) + } + + /// Parse ALTER DATABASE after its kind. + fn parse_alter_database(&mut self) -> Result { + let head = self.parse_alter_object_head()?; + let mut action = self.expect_alter_words(&[ + &["RENAME"], + &["SWAP"], + &["RESUME", "DISCOVERY"], + &["SUSPEND", "DISCOVERY"], + &["REFRESH"], + &["PRIMARY"], + &["ENABLE", "REPLICATION"], + &["ENABLE", "FAILOVER"], + &["DISABLE", "REPLICATION"], + &["DISABLE", "FAILOVER"], + &["SET", "FEATURE", "POLICY"], + &["UNSET", "FEATURE", "POLICY"], + &["UPDATE", "LINKED_CATALOG"], + &["SET"], + &["UNSET"], + ])?; + let parts = match action.as_str() { + "RENAME" => self.parse_alter_target("TO")?, + "SWAP" => self.parse_alter_target("WITH")?, + "SET FEATURE POLICY" => { + let (parts, suffix) = self.parse_alter_policy()?; + action.push_str(&suffix); + parts + } + "SET" => self.parse_alter_set(true)?, + "UNSET" => self.parse_alter_set(false)?, + "UPDATE LINKED_CATALOG" => { + let (parts, suffix) = self.parse_linked_catalog_update()?; + action.push_str(&suffix); + parts + } + _ if action.starts_with("ENABLE") || action.starts_with("DISABLE") => { + let mut operands: Vec = Vec::new(); + if action.starts_with("ENABLE") || self.check_text_seq(&["TO"]) { + self.expect_alter_words(&[&["TO", "ACCOUNTS"]])?; + operands.push(self.account_list_operand()?); + } + if matches!(action.as_str(), "ENABLE REPLICATION") + && self.match_text_seq(&["IGNORE", "EDITION", "CHECK"]) + { + action.push_str(" IGNORE EDITION CHECK"); + } + (operands, vec![]) + } + _ => (vec![], vec![]), + }; + Ok(alter_object("DATABASE", head, action, parts)) + } + + /// Parse the change after `UPDATE LINKED_CATALOG`, with the action's suffix. + fn parse_linked_catalog_update(&mut self) -> Result<(Parts, String)> { + let change = self.expect_alter_words(&[&["ADD"], &["REMOVE"], &["SET"], &["UNSET"]])?; + let mut suffix: String = format!(" {change}"); + let namespaces: &[&[&str]] = &[&["ALLOWED_NAMESPACES"], &["BLOCKED_NAMESPACES"]]; + match change.as_str() { + "ADD" | "REMOVE" => { + let list = self.parse_string_list()?; + let word = match change.as_str() { + "ADD" => "TO", + _ => "FROM", + }; + self.expect_alter_words(&[&[word]])?; + let target = self.expect_alter_words(namespaces)?; + suffix.push_str(&format!(" {word} {target}")); + Ok(((vec![list], vec![]), suffix)) + } + "SET" => { + let name = self.expect_alter_words(&[ + &["ALLOWED_WRITE_OPERATIONS"], + &["SYNC_INTERVAL_SECONDS"], + ])?; + self.expect_token(TokenType::Eq)?; + let value = match name.as_str() { + "SYNC_INTERVAL_SECONDS" => self.parse_object_value()?, + _ => { + let mode = self.expect_alter_words(&[&["NONE"], &["ALL"]])?; + Expression::Identifier(Identifier::new(mode)) + } + }; + Ok(((vec![], vec![property(&name, None, Some(value))]), suffix)) + } + _ => { + let target = self.expect_alter_words(namespaces)?; + suffix.push_str(&format!(" {target}")); + Ok(((vec![], vec![]), suffix)) + } + } + } + + /// Parse ALTER APPLICATION after its kind. + fn parse_alter_application(&mut self) -> Result { + if let Some(action) = [ + ["SET", "SPECIFICATION"].as_slice(), + &["DROP", "SPECIFICATION"], + &["SET", "CONFIGURATION", "DEFINITION"], + &["DROP", "CONFIGURATION", "DEFINITION"], + ] + .into_iter() + .find(|words| self.check_text_seq(words)) + { + self.current += action.len(); + let name = self.object_operand()?; + let properties = if matches!(action[0], "SET") { + self.parse_alter_settings(true)? + } else { + vec![] + }; + let head = (TableRef::new(""), false); + return Ok(alter_object( + "APPLICATION", + head, + action.join(" "), + (vec![name], properties), + )); + } + let head = self.parse_alter_object_head()?; + let mut action = self.expect_alter_words(&[ + &["RENAME"], + &["SET", "FEATURE", "POLICY"], + &["SET", "MAINTENANCE", "POLICY"], + &["SET", "SHARED", "TELEMETRY", "EVENTS"], + &["SET", "CONFIGURATION"], + &["UNSET", "FEATURE", "POLICY"], + &["UNSET", "MAINTENANCE", "POLICY"], + &["UNSET", "REFERENCES"], + &["UNSET", "CONFIGURATION"], + &["SET"], + &["UNSET"], + &["UPGRADE"], + &["APPROVE", "SPECIFICATION"], + &["DECLINE", "SPECIFICATION"], + ])?; + let parts = match action.as_str() { + "RENAME" => self.parse_alter_target("TO")?, + "SET FEATURE POLICY" | "SET MAINTENANCE POLICY" => { + let (parts, suffix) = self.parse_alter_policy()?; + action.push_str(&suffix); + parts + } + "SET SHARED TELEMETRY EVENTS" => (vec![self.parse_string_list()?], vec![]), + "SET CONFIGURATION" => { + let name = self.object_operand()?; + (vec![name], vec![self.parse_required_option("VALUE")?]) + } + "UNSET CONFIGURATION" => (vec![self.object_operand()?], vec![]), + "UNSET REFERENCES" => { + if self.check(TokenType::LParen) { + (vec![self.parse_string_list()?], vec![]) + } else { + (vec![], vec![]) + } + } + "SET" => self.parse_alter_set(true)?, + "UNSET" => self.parse_alter_set(false)?, + "UPGRADE" => { + if self.match_text_seq(&["USING", "VERSION"]) { + action.push_str(" USING VERSION"); + let mut operands = vec![self.object_operand()?]; + if self.match_text_seq(&["PATCH"]) { + action.push_str(" PATCH"); + operands.push(self.number_operand()?); + } + (operands, vec![]) + } else if self.match_text_seq(&["USING"]) { + action.push_str(" USING"); + (vec![self.parse_string_value()?], vec![]) + } else { + (vec![], vec![]) + } + } + "APPROVE SPECIFICATION" | "DECLINE SPECIFICATION" => { + let name = self.object_operand()?; + ( + vec![name], + vec![self.parse_required_option("SEQUENCE_NUMBER")?], + ) + } + _ => (vec![], vec![]), + }; + Ok(alter_object("APPLICATION", head, action, parts)) + } + + /// Parse ALTER APPLICATION PACKAGE after its kind. + fn parse_alter_application_package(&mut self) -> Result { + let head = self.parse_alter_object_head()?; + let mut action = self.expect_alter_words(&[ + &["RENAME"], + &["ADD", "VERSION"], + &["REGISTER", "VERSION"], + &["DEREGISTER", "VERSION"], + &["DROP", "VERSION"], + &["ADD", "PATCH"], + &["MODIFY", "RELEASE", "CHANNEL"], + &["MODIFY", "RELEASE", "DIRECTIVE"], + &["SET", "DEFAULT", "RELEASE", "DIRECTIVE"], + &["SET", "RELEASE", "DIRECTIVE"], + &["UNSET", "RELEASE", "DIRECTIVE"], + &["SET"], + &["UNSET"], + ])?; + let parts = match action.as_str() { + "RENAME" => self.parse_alter_target("TO")?, + "ADD VERSION" | "REGISTER VERSION" => { + let mut operands: Vec = Vec::new(); + if !self.check_text_seq(&["USING"]) { + operands.push(self.object_operand()?); + } + self.parse_version_source(operands)? + } + "ADD PATCH" => { + let mut operands: Vec = Vec::new(); + if self.check(TokenType::Number) { + operands.push(self.number_operand()?); + } + self.expect_alter_words(&[&["FOR", "VERSION"]])?; + if !self.check_text_seq(&["USING"]) { + operands.push(self.object_operand()?); + } + self.parse_version_source(operands)? + } + "DEREGISTER VERSION" | "DROP VERSION" => (vec![self.object_operand()?], vec![]), + "MODIFY RELEASE CHANNEL" => { + let channel = self.object_operand()?; + let change = self.expect_alter_words(&[ + &["ADD", "VERSION"], + &["DROP", "VERSION"], + &["ADD", "ACCOUNTS"], + &["REMOVE", "ACCOUNTS"], + &["SET", "ACCOUNTS"], + &["MODIFY", "RELEASE", "DIRECTIVE"], + &["SET", "DEFAULT", "RELEASE", "DIRECTIVE"], + &["SET", "RELEASE", "DIRECTIVE"], + &["UNSET", "RELEASE", "DIRECTIVE"], + ])?; + action.push_str(&format!(" {change}")); + let (mut operands, properties) = match change.as_str() { + "ADD VERSION" | "DROP VERSION" => (vec![self.object_operand()?], vec![]), + "ADD ACCOUNTS" | "REMOVE ACCOUNTS" | "SET ACCOUNTS" => { + self.expect_token(TokenType::Eq)?; + (vec![self.parse_account_list()?], vec![]) + } + _ => { + let (parts, suffix) = self.parse_release_directive(&change, &action)?; + action.push_str(&suffix); + parts + } + }; + operands.insert(0, channel); + (operands, properties) + } + _ if action.contains("RELEASE DIRECTIVE") => { + let change = action.clone(); + let (parts, suffix) = self.parse_release_directive(&change, &change)?; + action.push_str(&suffix); + parts + } + "SET" => self.parse_alter_set(true)?, + _ => { + let parts = self.parse_alter_set(false)?; + if parts.1.len() > 1 && parts.1.iter().any(|p| p.kind != TAG) { + return Err(self.parse_error("Snowflake unsets one package property at a time")); + } + parts + } + }; + Ok(alter_object("APPLICATION PACKAGE", head, action, parts)) + } + + /// Parse `USING [ LABEL = '