Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/sqlquack-syntax/src/parser/_helpers/ddl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) mod snowflake_alter_dynamic;
pub(crate) mod snowflake_alter_objects;
pub(crate) mod snowflake_alter_resources;
pub(crate) mod snowflake_alter_tables;
pub(crate) mod snowflake_alter_user;
pub(crate) mod snowflake_commands;
pub(crate) mod snowflake_create_clone;
pub(crate) mod snowflake_create_from;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ impl Parser {
"Snowflake has ALTER ONLINE FEATURE TABLE and ALTER FEATURE POLICY only",
));
}
if self.match_identifier("USER") {
return self.parse_alter_user().map(Some);
}
if self.match_identifier("VARIABLE") {
return self.parse_alter_variable().map(Some);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl Parser {
}

/// Parse `<org_name>.<account_name> [ , ... ]`, refusing a reserved keyword as either part.
fn parse_account_names(&mut self) -> Result<Vec<Expression>> {
pub(super) fn parse_account_names(&mut self) -> Result<Vec<Expression>> {
let mut accounts: Vec<Expression> = Vec::new();
loop {
let mut parts: Vec<Identifier> = vec![self.parse_simple_name()?];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Parser {
} else if self.match_token(TokenType::View) {
self.parse_alter_kind_extension("VIEW", &[&["RENAME", "TO"]])
} else if self.match_identifier("SHARE") {
return self.parse_alter_share();
return self.parse_alter_share().map(Some);
} else {
Ok(None)
};
Expand Down Expand Up @@ -174,28 +174,6 @@ impl Parser {
Ok(Some(alter_object(kind, head, action, parts)))
}

/// Parse ALTER SHARE: UNSET COMMENT typed; ADD, REMOVE, SET and other UNSETs are left to the fallback.
fn parse_alter_share(&mut self) -> Result<Option<Expression>> {
let start = self.current - 1;
let head = self.parse_alter_object_head()?;
if self.match_text_seq(&["UNSET", "COMMENT"]) {
let parts = (vec![], vec![property("COMMENT", None, None)]);
return Ok(Some(alter_object("SHARE", head, "UNSET".to_owned(), parts)));
}
match (
self.text_at(0).unwrap_or_default().as_str(),
self.text_at(1).as_deref(),
) {
("UNSET", None | Some(";")) => {
return Err(self.parse_error("Expected a property after UNSET"))
}
("ADD" | "REMOVE" | "SET" | "UNSET", _) => {}
_ => return Err(self.parse_error("Expected ADD, REMOVE, SET or UNSET")),
}
self.current = start;
Ok(None)
}

/// Parse a name operand: an identifier, never a string or an unquoted reserved keyword.
fn parse_tag_operand(&mut self) -> Result<Expression> {
Ok(Expression::Table(Box::new(self.parse_object_name()?)))
Expand Down
288 changes: 288 additions & 0 deletions crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_alter_user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
//! Snowflake ALTER USER (credentials, MFA, delegated authorizations, policies, properties, tags) and ALTER SHARE.

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, property, property_kind};
use crate::parser::models::Parser;
use crate::tokens::models::TokenType;
use crate::tokens::types::Result;

/// The words that open an ALTER USER action; a user name is absent when one of them comes first.
const USER_ACTIONS: &[&str] = &[
"ABORT", "ADD", "ENROLL", "MODIFY", "REMOVE", "RENAME", "RESET", "ROTATE", "SET", "UNSET",
];

/// The credential kinds ADD, MODIFY, REMOVE and ROTATE take, longest first; PAT is the short spelling.
const CREDENTIALS: &[&[&str]] = &[
&["PROGRAMMATIC", "ACCESS", "TOKEN"],
&["WORKLOAD", "IDENTITY"],
&["KEY", "PAIR"],
&["PAT"],
];

/// The policies a user SET or UNSET names.
const USER_POLICIES: &[&[&str]] = &[
&["AUTHENTICATION", "POLICY"],
&["INGRESS", "POLICY"],
&["PASSWORD", "POLICY"],
&["SESSION", "POLICY"],
];

/// The operands and properties of one action.
type ActionParts = (Vec<Expression>, Vec<ObjectProperty>);

/// An action's words with its operands and properties.
type Action = (String, ActionParts);

impl Parser {
/// Parse ALTER USER after its kind; the name may be left out for the current user.
pub(super) fn parse_alter_user(&mut self) -> Result<Expression> {
let if_exists = self.match_keywords(&[TokenType::If, TokenType::Exists]);
let unnamed = self
.text_at(0)
.is_some_and(|word| USER_ACTIONS.contains(&word.as_str()))
&& !self
.text_at(1)
.is_some_and(|word| USER_ACTIONS.contains(&word.as_str()));
let name = match unnamed {
true => TableRef::new(""),
false => self.parse_object_name()?,
};
let action = self.expect_alter_words(&[
&["ABORT", "ALL", "QUERIES"],
&["ENROLL", "MFA"],
&["RESET", "PASSWORD"],
&["RENAME", "TO"],
&["ADD"],
&["MODIFY"],
&["REMOVE"],
&["ROTATE"],
&["SET"],
&["UNSET"],
])?;
let (action, parts): Action = match action.as_str() {
"RENAME TO" => (action, (vec![self.parse_user_operand()?], vec![])),
"ADD" | "MODIFY" | "REMOVE" | "ROTATE" => self.parse_user_credential_action(action)?,
"SET" | "UNSET" => self.parse_user_set(action)?,
_ => (action, (vec![], vec![])),
};
Ok(alter_object("USER", (name, if_exists), action, parts))
}

/// The object of ADD, MODIFY, REMOVE or ROTATE: a credential, an MFA method or a delegated authorization.
fn parse_user_credential_action(&mut self, mut action: String) -> Result<Action> {
let verb = action.clone();
if self.match_text_seq(&["MFA", "METHOD"]) {
action.push_str(" MFA METHOD");
return self.parse_user_mfa_method(&verb, action);
}
if self.check_identifier("DELEGATED") && matches!(verb.as_str(), "ADD" | "REMOVE") {
self.skip();
action.push_str(" DELEGATED");
return self.parse_user_delegation(&verb, action);
}
let kind = self.expect_alter_words(CREDENTIALS)?;
let kind = match kind.as_str() {
"PAT" => "PROGRAMMATIC ACCESS TOKEN".to_string(),
_ => kind,
};
action.push(' ');
action.push_str(&kind);
let token = matches!(kind.as_str(), "PROGRAMMATIC ACCESS TOKEN");
if token && matches!(verb.as_str(), "ADD") && self.check(TokenType::If) {
self.expect_alter_words(&[&["IF", "NOT", "EXISTS"]])?;
action.push_str(" IF NOT EXISTS");
} else if token && self.match_keywords(&[TokenType::If, TokenType::Exists]) {
action.push_str(" IF EXISTS");
}
let operands = vec![self.parse_user_operand()?];
let properties = match verb.as_str() {
"MODIFY" => {
let change = self.expect_alter_words(&[&["RENAME", "TO"], &["SET"], &["UNSET"]])?;
action.push(' ');
action.push_str(&change);
let parts: ActionParts = match change.as_str() {
"RENAME TO" => (
vec![operands[0].clone(), self.parse_user_operand()?],
vec![],
),
"SET" => (operands, self.parse_user_properties()?),
_ => (operands, self.parse_user_unset_names()?),
};
return Ok((action, parts));
}
"REMOVE" => vec![],
_ if self.is_statement_end_at(0) => vec![],
_ => self.parse_user_properties()?,
};
Ok((action, (operands, properties)))
}

/// ADD MFA METHOD OTP [ COUNT = n ], REMOVE MFA METHOD <m> or MODIFY MFA METHOD <m> SET COMMENT = '<string>'.
fn parse_user_mfa_method(&mut self, verb: &str, mut action: String) -> Result<Action> {
if matches!(verb, "ROTATE") {
return Err(self.parse_error("Snowflake has no ROTATE MFA METHOD"));
}
let method = self.parse_user_operand()?;
if matches!(verb, "MODIFY") {
action.push_str(" SET");
self.expect_alter_words(&[&["SET"]])?;
return Ok((action, (vec![method], self.parse_user_properties()?)));
}
let properties = match verb {
"ADD" if !self.is_statement_end_at(0) => self.parse_user_properties()?,
_ => vec![],
};
Ok((action, (vec![method], properties)))
}

/// ADD DELEGATED AUTHORIZATION OF ROLE <r> TO SECURITY INTEGRATION <i>, or its REMOVE forms, AUTHORIZATIONS [ OF ANY ROLE ] included.
fn parse_user_delegation(&mut self, verb: &str, mut action: String) -> Result<Action> {
let mut operands: Vec<Expression> = Vec::new();
let what = match verb {
"ADD" => self.expect_alter_words(&[&["AUTHORIZATION", "OF", "ROLE"]])?,
_ => self.expect_alter_words(&[
&["AUTHORIZATION", "OF", "ROLE"],
&["AUTHORIZATIONS", "OF", "ANY", "ROLE"],
&["AUTHORIZATIONS"],
])?,
};
action.push(' ');
action.push_str(&what);
if matches!(what.as_str(), "AUTHORIZATION OF ROLE") {
operands.push(self.parse_user_operand()?);
}
let preposition = match verb {
"ADD" => self.expect_alter_words(&[&["TO", "SECURITY", "INTEGRATION"]])?,
_ => self.expect_alter_words(&[&["FROM", "SECURITY", "INTEGRATION"]])?,
};
action.push(' ');
action.push_str(&preposition);
operands.push(self.parse_user_operand()?);
Ok((action, (operands, vec![])))
}

/// SET or UNSET: a policy, tags, or properties and parameters.
fn parse_user_set(&mut self, mut action: String) -> Result<Action> {
let set = matches!(action.as_str(), "SET");
if let Some(policy) = USER_POLICIES
.iter()
.find(|words| self.match_text_seq(words))
{
action.push(' ');
action.push_str(&policy.join(" "));
if !set {
return Ok((action, (vec![], vec![])));
}
let operands = vec![self.parse_user_operand()?];
if self.match_identifier("FORCE") {
action.push_str(" FORCE");
}
return Ok((action, (operands, vec![])));
}
if self.check_identifier("TAG") && !self.check_next(TokenType::Eq) {
action.push_str(" TAG");
let tags = self.parse_alter_object_properties(set, false)?;
return Ok((action, (vec![], tags)));
}
let properties = match set {
true => self.parse_user_properties()?,
false => self.parse_user_unset_names()?,
};
Ok((action, (vec![], properties)))
}

/// `<name> = <value>` properties separated by blanks or commas; a value may be a parenthesized list.
fn parse_user_properties(&mut self) -> Result<Vec<ObjectProperty>> {
let mut properties: Vec<ObjectProperty> = Vec::new();
loop {
let name = self.parse_simple_name()?;
self.expect_token(TokenType::Eq)?;
let value = match self.check(TokenType::LParen) {
true => self.parse_routine_value_list()?,
false => self.parse_object_value()?,
};
properties.push(property(&property_kind(&name), None, Some(value)));
let comma = self.match_token(TokenType::Comma);
if self.is_statement_end_at(0) {
if comma {
return Err(self.parse_error("Expected a property after the comma"));
}
return Ok(properties);
}
}
}

/// UNSET names, separated by commas.
fn parse_user_unset_names(&mut self) -> Result<Vec<ObjectProperty>> {
let mut properties: Vec<ObjectProperty> = Vec::new();
loop {
let name = self.parse_simple_name()?;
properties.push(property(&property_kind(&name), None, None));
if !self.match_token(TokenType::Comma) {
return Ok(properties);
}
}
}

/// A user operand: a credential, method, role, integration or policy name.
fn parse_user_operand(&mut self) -> Result<Expression> {
Ok(Expression::Table(Box::new(self.parse_object_name()?)))
}

/// Parse ALTER SHARE after its kind: ADD or REMOVE ACCOUNTS, SET and UNSET.
pub(super) fn parse_alter_share(&mut self) -> Result<Expression> {
let head = self.parse_alter_object_head()?;
let mut action = self.expect_alter_words(&[&["ADD"], &["REMOVE"], &["SET"], &["UNSET"]])?;
let parts: ActionParts = match action.as_str() {
"ADD" | "REMOVE" => {
self.expect_alter_words(&[&["ACCOUNTS"]])?;
action.push_str(" ACCOUNTS");
self.expect_token(TokenType::Eq)?;
let accounts = self.parse_account_names()?;
let mut properties: Vec<ObjectProperty> = Vec::new();
if matches!(action.as_str(), "ADD ACCOUNTS")
&& self.match_identifier("SHARE_RESTRICTIONS")
{
self.expect_token(TokenType::Eq)?;
properties.push(property(
"SHARE_RESTRICTIONS",
None,
Some(self.parse_object_value()?),
));
}
(accounts, properties)
}
"SET" if self.check_identifier("TAG") && !self.check_next(TokenType::Eq) => {
action.push_str(" TAG");
(vec![], self.parse_alter_object_properties(true, false)?)
}
"SET" => (vec![], self.parse_share_set()?),
_ => (vec![], self.parse_alter_object_properties(false, false)?),
};
Ok(alter_object("SHARE", head, action, parts))
}

/// A share's SET properties; ACCOUNTS takes a list of accounts.
fn parse_share_set(&mut self) -> Result<Vec<ObjectProperty>> {
let mut properties: Vec<ObjectProperty> = Vec::new();
loop {
let name = self.parse_simple_name()?;
self.expect_token(TokenType::Eq)?;
let kind = property_kind(&name);
let value = match kind.as_str() {
"ACCOUNTS" => Expression::Tuple(Box::new(Tuple {
expressions: self.parse_account_names()?,
})),
_ => self.parse_object_value()?,
};
properties.push(property(&kind, None, Some(value)));
self.match_token(TokenType::Comma);
if self.is_statement_end_at(0) {
return Ok(properties);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,11 @@ impl Parser {
properties.push(property("TARGET_LAG", None, Some(value)));
} else if dynamic && self.match_text_seq(&["WAREHOUSE"]) {
self.expect_token(TokenType::Eq)?;
let warehouse = self.parse_object_name()?;
properties.push(property(
"WAREHOUSE",
None,
Some(Expression::Table(Box::new(warehouse))),
));
let warehouse = match self.check(TokenType::String) {
true => self.parse_string_value()?,
false => Expression::Table(Box::new(self.parse_object_name()?)),
};
properties.push(property("WAREHOUSE", None, Some(warehouse)));
} else if dynamic && self.match_text_seq(&["EXECUTE"]) {
self.expect_alter_words(&[&["AS", "USER"]])?;
let user = self.parse_object_name()?;
Expand Down
Loading
Loading