Snowflake: ALTER USER and KeyValueOptions Refactoring (#2035)
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run

This commit is contained in:
Yoav Cohen 2025-09-25 21:59:11 +02:00 committed by GitHub
parent 54a24e76a9
commit a430838974
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 809 additions and 135 deletions

View file

@ -16,12 +16,7 @@
// under the License.
#[cfg(not(feature = "std"))]
use alloc::{
boxed::Box,
format,
string::{String, ToString},
vec::Vec,
};
use alloc::{boxed::Box, format, string::ToString, vec::Vec};
use core::fmt::{self, Display};
#[cfg(feature = "serde")]

View file

@ -19,9 +19,7 @@
//! See [this page](https://docs.snowflake.com/en/sql-reference/commands-data-loading) for more details.
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use alloc::{boxed::Box, string::String, vec::Vec};
use core::fmt;
use core::fmt::Formatter;
@ -31,7 +29,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};
use crate::ast::display_separated;
use crate::ast::{display_comma_separated, display_separated, Value};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -52,20 +50,23 @@ pub enum KeyValueOptionsDelimiter {
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum KeyValueOptionType {
STRING,
BOOLEAN,
ENUM,
NUMBER,
pub struct KeyValueOption {
pub option_name: String,
pub option_value: KeyValueOptionKind,
}
/// An option can have a single value, multiple values or a nested list of values.
///
/// A value can be numeric, boolean, etc. Enum-style values are represented
/// as Value::Placeholder. For example: MFA_METHOD=SMS will be represented as
/// `Value::Placeholder("SMS".to_string)`.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct KeyValueOption {
pub option_name: String,
pub option_type: KeyValueOptionType,
pub value: String,
pub enum KeyValueOptionKind {
Single(Value),
Multi(Vec<Value>),
KeyValueOptions(Box<KeyValueOptions>),
}
impl fmt::Display for KeyValueOptions {
@ -80,12 +81,20 @@ impl fmt::Display for KeyValueOptions {
impl fmt::Display for KeyValueOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.option_type {
KeyValueOptionType::STRING => {
write!(f, "{}='{}'", self.option_name, self.value)?;
match &self.option_value {
KeyValueOptionKind::Single(value) => {
write!(f, "{}={value}", self.option_name)?;
}
KeyValueOptionType::ENUM | KeyValueOptionType::BOOLEAN | KeyValueOptionType::NUMBER => {
write!(f, "{}={}", self.option_name, self.value)?;
KeyValueOptionKind::Multi(values) => {
write!(
f,
"{}=({})",
self.option_name,
display_comma_separated(values)
)?;
}
KeyValueOptionKind::KeyValueOptions(options) => {
write!(f, "{}=({options})", self.option_name)?;
}
}
Ok(())

View file

@ -16,7 +16,7 @@
// under the License.
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
use alloc::{format, string::String, vec::Vec};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

View file

@ -4310,6 +4310,11 @@ pub enum Statement {
/// ```
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-user)
CreateUser(CreateUser),
/// ```sql
/// ALTER USER \[ IF EXISTS \] \[ <name> \]
/// ```
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/alter-user)
AlterUser(AlterUser),
/// Re-sorts rows and reclaims space in either a specified table or all tables in the current database
///
/// ```sql
@ -6183,6 +6188,7 @@ impl fmt::Display for Statement {
Statement::CreateUser(s) => write!(f, "{s}"),
Statement::AlterSchema(s) => write!(f, "{s}"),
Statement::Vacuum(s) => write!(f, "{s}"),
Statement::AlterUser(s) => write!(f, "{s}"),
}
}
}
@ -10558,6 +10564,217 @@ impl fmt::Display for CreateUser {
}
}
/// Modifies the properties of a user
///
/// Syntax:
/// ```sql
/// ALTER USER [ IF EXISTS ] [ <name> ] [ OPTIONS ]
/// ```
///
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/alter-user)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct AlterUser {
pub if_exists: bool,
pub name: Ident,
/// The following fields are Snowflake-specific: <https://docs.snowflake.com/en/sql-reference/sql/alter-user#syntax>
pub rename_to: Option<Ident>,
pub reset_password: bool,
pub abort_all_queries: bool,
pub add_role_delegation: Option<AlterUserAddRoleDelegation>,
pub remove_role_delegation: Option<AlterUserRemoveRoleDelegation>,
pub enroll_mfa: bool,
pub set_default_mfa_method: Option<MfaMethodKind>,
pub remove_mfa_method: Option<MfaMethodKind>,
pub modify_mfa_method: Option<AlterUserModifyMfaMethod>,
pub add_mfa_method_otp: Option<AlterUserAddMfaMethodOtp>,
pub set_policy: Option<AlterUserSetPolicy>,
pub unset_policy: Option<UserPolicyKind>,
pub set_tag: KeyValueOptions,
pub unset_tag: Vec<String>,
pub set_props: KeyValueOptions,
pub unset_props: Vec<String>,
}
/// ```sql
/// ALTER USER [ IF EXISTS ] [ <name> ] ADD DELEGATED AUTHORIZATION OF ROLE <role_name> TO SECURITY INTEGRATION <integration_name>
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct AlterUserAddRoleDelegation {
pub role: Ident,
pub integration: Ident,
}
/// ```sql
/// ALTER USER [ IF EXISTS ] [ <name> ] REMOVE DELEGATED { AUTHORIZATION OF ROLE <role_name> | AUTHORIZATIONS } FROM SECURITY INTEGRATION <integration_name>
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct AlterUserRemoveRoleDelegation {
pub role: Option<Ident>,
pub integration: Ident,
}
/// ```sql
/// ADD MFA METHOD OTP [ COUNT = number ]
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct AlterUserAddMfaMethodOtp {
pub count: Option<Value>,
}
/// ```sql
/// ALTER USER [ IF EXISTS ] [ <name> ] MODIFY MFA METHOD <mfa_method> SET COMMENT = '<string>'
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct AlterUserModifyMfaMethod {
pub method: MfaMethodKind,
pub comment: String,
}
/// Types of MFA methods
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MfaMethodKind {
PassKey,
Totp,
Duo,
}
impl fmt::Display for MfaMethodKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MfaMethodKind::PassKey => write!(f, "PASSKEY"),
MfaMethodKind::Totp => write!(f, "TOTP"),
MfaMethodKind::Duo => write!(f, "DUO"),
}
}
}
/// ```sql
/// ALTER USER [ IF EXISTS ] [ <name> ] SET { AUTHENTICATION | PASSWORD | SESSION } POLICY <policy_name>
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct AlterUserSetPolicy {
pub policy_kind: UserPolicyKind,
pub policy: Ident,
}
/// Types of user-based policies
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum UserPolicyKind {
Authentication,
Password,
Session,
}
impl fmt::Display for UserPolicyKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UserPolicyKind::Authentication => write!(f, "AUTHENTICATION"),
UserPolicyKind::Password => write!(f, "PASSWORD"),
UserPolicyKind::Session => write!(f, "SESSION"),
}
}
}
impl fmt::Display for AlterUser {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ALTER")?;
write!(f, " USER")?;
if self.if_exists {
write!(f, " IF EXISTS")?;
}
write!(f, " {}", self.name)?;
if let Some(new_name) = &self.rename_to {
write!(f, " RENAME TO {new_name}")?;
}
if self.reset_password {
write!(f, " RESET PASSWORD")?;
}
if self.abort_all_queries {
write!(f, " ABORT ALL QUERIES")?;
}
if let Some(role_delegation) = &self.add_role_delegation {
let role = &role_delegation.role;
let integration = &role_delegation.integration;
write!(
f,
" ADD DELEGATED AUTHORIZATION OF ROLE {role} TO SECURITY INTEGRATION {integration}"
)?;
}
if let Some(role_delegation) = &self.remove_role_delegation {
write!(f, " REMOVE DELEGATED")?;
match &role_delegation.role {
Some(role) => write!(f, " AUTHORIZATION OF ROLE {role}")?,
None => write!(f, " AUTHORIZATIONS")?,
}
let integration = &role_delegation.integration;
write!(f, " FROM SECURITY INTEGRATION {integration}")?;
}
if self.enroll_mfa {
write!(f, " ENROLL MFA")?;
}
if let Some(method) = &self.set_default_mfa_method {
write!(f, " SET DEFAULT_MFA_METHOD {method}")?
}
if let Some(method) = &self.remove_mfa_method {
write!(f, " REMOVE MFA METHOD {method}")?;
}
if let Some(modify) = &self.modify_mfa_method {
let method = &modify.method;
let comment = &modify.comment;
write!(
f,
" MODIFY MFA METHOD {method} SET COMMENT '{}'",
value::escape_single_quote_string(comment)
)?;
}
if let Some(add_mfa_method_otp) = &self.add_mfa_method_otp {
write!(f, " ADD MFA METHOD OTP")?;
if let Some(count) = &add_mfa_method_otp.count {
write!(f, " COUNT = {count}")?;
}
}
if let Some(policy) = &self.set_policy {
let policy_kind = &policy.policy_kind;
let name = &policy.policy;
write!(f, " SET {policy_kind} POLICY {name}")?;
}
if let Some(policy_kind) = &self.unset_policy {
write!(f, " UNSET {policy_kind} POLICY")?;
}
if !self.set_tag.options.is_empty() {
write!(f, " SET TAG {}", self.set_tag)?;
}
if !self.unset_tag.is_empty() {
write!(f, " UNSET TAG {}", display_comma_separated(&self.unset_tag))?;
}
let has_props = !self.set_props.options.is_empty();
if has_props {
write!(f, " SET")?;
write!(f, " {}", &self.set_props)?;
}
if !self.unset_props.is_empty() {
write!(f, " UNSET {}", display_comma_separated(&self.unset_props))?;
}
Ok(())
}
}
/// Specifies how to create a new table based on an existing table's schema.
/// '''sql
/// CREATE TABLE new LIKE old ...

View file

@ -555,6 +555,7 @@ impl Spanned for Statement {
Statement::CreateUser(..) => Span::empty(),
Statement::AlterSchema(s) => s.span(),
Statement::Vacuum(..) => Span::empty(),
Statement::AlterUser(..) => Span::empty(),
}
}
}

View file

@ -18,7 +18,7 @@
#[cfg(not(feature = "std"))]
use crate::alloc::string::ToString;
use crate::ast::helpers::key_value_options::{
KeyValueOption, KeyValueOptionType, KeyValueOptions, KeyValueOptionsDelimiter,
KeyValueOption, KeyValueOptionKind, KeyValueOptions, KeyValueOptionsDelimiter,
};
use crate::ast::helpers::stmt_create_database::CreateDatabaseBuilder;
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
@ -30,7 +30,7 @@ use crate::ast::{
CopyIntoSnowflakeKind, CreateTableLikeKind, DollarQuotedString, Ident, IdentityParameters,
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
InitializeKind, ObjectName, ObjectNamePart, RefreshModeKind, RowAccessPolicy, ShowObjects,
SqlOption, Statement, StorageSerializationPolicy, TagsColumnOption, WrappedCollection,
SqlOption, Statement, StorageSerializationPolicy, TagsColumnOption, Value, WrappedCollection,
};
use crate::dialect::{Dialect, Precedence};
use crate::keywords::Keyword;
@ -1004,19 +1004,19 @@ pub fn parse_create_stage(
// [ directoryTableParams ]
if parser.parse_keyword(Keyword::DIRECTORY) {
parser.expect_token(&Token::Eq)?;
directory_table_params = parser.parse_key_value_options(true, &[])?;
directory_table_params = parser.parse_key_value_options(true, &[])?.options;
}
// [ file_format]
if parser.parse_keyword(Keyword::FILE_FORMAT) {
parser.expect_token(&Token::Eq)?;
file_format = parser.parse_key_value_options(true, &[])?;
file_format = parser.parse_key_value_options(true, &[])?.options;
}
// [ copy_options ]
if parser.parse_keyword(Keyword::COPY_OPTIONS) {
parser.expect_token(&Token::Eq)?;
copy_options = parser.parse_key_value_options(true, &[])?;
copy_options = parser.parse_key_value_options(true, &[])?.options;
}
// [ comment ]
@ -1182,7 +1182,7 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
// FILE_FORMAT
if parser.parse_keyword(Keyword::FILE_FORMAT) {
parser.expect_token(&Token::Eq)?;
file_format = parser.parse_key_value_options(true, &[])?;
file_format = parser.parse_key_value_options(true, &[])?.options;
// PARTITION BY
} else if parser.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
partition = Some(Box::new(parser.parse_expr()?))
@ -1220,14 +1220,14 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
// COPY OPTIONS
} else if parser.parse_keyword(Keyword::COPY_OPTIONS) {
parser.expect_token(&Token::Eq)?;
copy_options = parser.parse_key_value_options(true, &[])?;
copy_options = parser.parse_key_value_options(true, &[])?.options;
} else {
match parser.next_token().token {
Token::SemiColon | Token::EOF => break,
Token::Comma => continue,
// In `COPY INTO <location>` the copy options do not have a shared key
// like in `COPY INTO <table>`
Token::Word(key) => copy_options.push(parser.parse_key_value_option(key)?),
Token::Word(key) => copy_options.push(parser.parse_key_value_option(&key)?),
_ => return parser.expected("another copy option, ; or EOF'", parser.peek_token()),
}
}
@ -1387,7 +1387,7 @@ fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserEr
if parser.parse_keyword(Keyword::CREDENTIALS) {
parser.expect_token(&Token::Eq)?;
credentials = KeyValueOptions {
options: parser.parse_key_value_options(true, &[])?,
options: parser.parse_key_value_options(true, &[])?.options,
delimiter: KeyValueOptionsDelimiter::Space,
};
}
@ -1396,7 +1396,7 @@ fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserEr
if parser.parse_keyword(Keyword::ENCRYPTION) {
parser.expect_token(&Token::Eq)?;
encryption = KeyValueOptions {
options: parser.parse_key_value_options(true, &[])?,
options: parser.parse_key_value_options(true, &[])?.options,
delimiter: KeyValueOptionsDelimiter::Space,
};
}
@ -1431,13 +1431,12 @@ fn parse_session_options(
Token::Word(key) => {
parser.advance_token();
if set {
let option = parser.parse_key_value_option(key)?;
let option = parser.parse_key_value_option(&key)?;
options.push(option);
} else {
options.push(KeyValueOption {
option_name: key.value,
option_type: KeyValueOptionType::STRING,
value: empty(),
option_value: KeyValueOptionKind::Single(Value::Placeholder(empty())),
});
}
}

View file

@ -119,6 +119,7 @@ define_keywords!(
AUDIT,
AUTHENTICATION,
AUTHORIZATION,
AUTHORIZATIONS,
AUTO,
AUTOEXTEND_SIZE,
AUTOINCREMENT,
@ -279,6 +280,8 @@ define_keywords!(
DEFAULT,
DEFAULTS,
DEFAULT_DDL_COLLATION,
DEFAULT_MFA_METHOD,
DEFAULT_SECONDARY_ROLES,
DEFERRABLE,
DEFERRED,
DEFINE,
@ -286,6 +289,7 @@ define_keywords!(
DEFINER,
DELAYED,
DELAY_KEY_WRITE,
DELEGATED,
DELETE,
DELIMITED,
DELIMITER,
@ -314,6 +318,7 @@ define_keywords!(
DOY,
DROP,
DRY,
DUO,
DUPLICATE,
DYNAMIC,
EACH,
@ -336,6 +341,7 @@ define_keywords!(
ENFORCED,
ENGINE,
ENGINE_ATTRIBUTE,
ENROLL,
ENUM,
ENUM16,
ENUM8,
@ -586,6 +592,7 @@ define_keywords!(
METHOD,
METRIC,
METRICS,
MFA,
MICROSECOND,
MICROSECONDS,
MILLENIUM,
@ -685,6 +692,7 @@ define_keywords!(
ORDINALITY,
ORGANIZATION,
OTHER,
OTP,
OUT,
OUTER,
OUTPUT,
@ -709,6 +717,7 @@ define_keywords!(
PARTITIONED,
PARTITIONS,
PASSING,
PASSKEY,
PASSWORD,
PAST,
PATH,
@ -753,6 +762,7 @@ define_keywords!(
PURGE,
QUALIFY,
QUARTER,
QUERIES,
QUERY,
QUOTE,
RAISE,
@ -969,6 +979,7 @@ define_keywords!(
TO,
TOP,
TOTALS,
TOTP,
TRACE,
TRAILING,
TRANSACTION,
@ -1067,6 +1078,7 @@ define_keywords!(
WITHOUT,
WITHOUT_ARRAY_WRAPPER,
WORK,
WORKLOAD_IDENTITY,
WRAPPER,
WRITE,
XML,

View file

@ -13,13 +13,16 @@
//! SQL Parser for ALTER
#[cfg(not(feature = "std"))]
use alloc::vec;
use alloc::{string::ToString, vec};
use super::{Parser, ParserError};
use crate::{
ast::{
AlterConnectorOwner, AlterPolicyOperation, AlterRoleOperation, Expr, Password, ResetConfig,
RoleOption, SetConfigValue, Statement,
helpers::key_value_options::{KeyValueOptions, KeyValueOptionsDelimiter},
AlterConnectorOwner, AlterPolicyOperation, AlterRoleOperation, AlterUser,
AlterUserAddMfaMethodOtp, AlterUserAddRoleDelegation, AlterUserModifyMfaMethod,
AlterUserRemoveRoleDelegation, AlterUserSetPolicy, Expr, MfaMethodKind, Password,
ResetConfig, RoleOption, SetConfigValue, Statement, UserPolicyKind,
},
dialect::{MsSqlDialect, PostgreSqlDialect},
keywords::Keyword,
@ -140,6 +143,189 @@ impl Parser<'_> {
})
}
/// Parse an `ALTER USER` statement
/// ```sql
/// ALTER USER [ IF EXISTS ] [ <name> ] [ OPTIONS ]
/// ```
pub fn parse_alter_user(&mut self) -> Result<Statement, ParserError> {
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
let name = self.parse_identifier()?;
let rename_to = if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
Some(self.parse_identifier()?)
} else {
None
};
let reset_password = self.parse_keywords(&[Keyword::RESET, Keyword::PASSWORD]);
let abort_all_queries =
self.parse_keywords(&[Keyword::ABORT, Keyword::ALL, Keyword::QUERIES]);
let add_role_delegation = if self.parse_keywords(&[
Keyword::ADD,
Keyword::DELEGATED,
Keyword::AUTHORIZATION,
Keyword::OF,
Keyword::ROLE,
]) {
let role = self.parse_identifier()?;
self.expect_keywords(&[Keyword::TO, Keyword::SECURITY, Keyword::INTEGRATION])?;
let integration = self.parse_identifier()?;
Some(AlterUserAddRoleDelegation { role, integration })
} else {
None
};
let remove_role_delegation = if self.parse_keywords(&[Keyword::REMOVE, Keyword::DELEGATED])
{
let role = if self.parse_keywords(&[Keyword::AUTHORIZATION, Keyword::OF, Keyword::ROLE])
{
Some(self.parse_identifier()?)
} else if self.parse_keyword(Keyword::AUTHORIZATIONS) {
None
} else {
return self.expected(
"REMOVE DELEGATED AUTHORIZATION OF ROLE | REMOVE DELEGATED AUTHORIZATIONS",
self.peek_token(),
);
};
self.expect_keywords(&[Keyword::FROM, Keyword::SECURITY, Keyword::INTEGRATION])?;
let integration = self.parse_identifier()?;
Some(AlterUserRemoveRoleDelegation { role, integration })
} else {
None
};
let enroll_mfa = self.parse_keywords(&[Keyword::ENROLL, Keyword::MFA]);
let set_default_mfa_method =
if self.parse_keywords(&[Keyword::SET, Keyword::DEFAULT_MFA_METHOD]) {
Some(self.parse_mfa_method()?)
} else {
None
};
let remove_mfa_method =
if self.parse_keywords(&[Keyword::REMOVE, Keyword::MFA, Keyword::METHOD]) {
Some(self.parse_mfa_method()?)
} else {
None
};
let modify_mfa_method =
if self.parse_keywords(&[Keyword::MODIFY, Keyword::MFA, Keyword::METHOD]) {
let method = self.parse_mfa_method()?;
self.expect_keywords(&[Keyword::SET, Keyword::COMMENT])?;
let comment = self.parse_literal_string()?;
Some(AlterUserModifyMfaMethod { method, comment })
} else {
None
};
let add_mfa_method_otp =
if self.parse_keywords(&[Keyword::ADD, Keyword::MFA, Keyword::METHOD, Keyword::OTP]) {
let count = if self.parse_keyword(Keyword::COUNT) {
self.expect_token(&Token::Eq)?;
Some(self.parse_value()?.into())
} else {
None
};
Some(AlterUserAddMfaMethodOtp { count })
} else {
None
};
let set_policy =
if self.parse_keywords(&[Keyword::SET, Keyword::AUTHENTICATION, Keyword::POLICY]) {
Some(AlterUserSetPolicy {
policy_kind: UserPolicyKind::Authentication,
policy: self.parse_identifier()?,
})
} else if self.parse_keywords(&[Keyword::SET, Keyword::PASSWORD, Keyword::POLICY]) {
Some(AlterUserSetPolicy {
policy_kind: UserPolicyKind::Password,
policy: self.parse_identifier()?,
})
} else if self.parse_keywords(&[Keyword::SET, Keyword::SESSION, Keyword::POLICY]) {
Some(AlterUserSetPolicy {
policy_kind: UserPolicyKind::Session,
policy: self.parse_identifier()?,
})
} else {
None
};
let unset_policy =
if self.parse_keywords(&[Keyword::UNSET, Keyword::AUTHENTICATION, Keyword::POLICY]) {
Some(UserPolicyKind::Authentication)
} else if self.parse_keywords(&[Keyword::UNSET, Keyword::PASSWORD, Keyword::POLICY]) {
Some(UserPolicyKind::Password)
} else if self.parse_keywords(&[Keyword::UNSET, Keyword::SESSION, Keyword::POLICY]) {
Some(UserPolicyKind::Session)
} else {
None
};
let set_tag = if self.parse_keywords(&[Keyword::SET, Keyword::TAG]) {
self.parse_key_value_options(false, &[])?
} else {
KeyValueOptions {
delimiter: KeyValueOptionsDelimiter::Comma,
options: vec![],
}
};
let unset_tag = if self.parse_keywords(&[Keyword::UNSET, Keyword::TAG]) {
self.parse_comma_separated(Parser::parse_identifier)?
.iter()
.map(|i| i.to_string())
.collect()
} else {
vec![]
};
let set_props = if self.parse_keyword(Keyword::SET) {
self.parse_key_value_options(false, &[])?
} else {
KeyValueOptions {
delimiter: KeyValueOptionsDelimiter::Comma,
options: vec![],
}
};
let unset_props = if self.parse_keyword(Keyword::UNSET) {
self.parse_comma_separated(Parser::parse_identifier)?
.iter()
.map(|i| i.to_string())
.collect()
} else {
vec![]
};
Ok(Statement::AlterUser(AlterUser {
if_exists,
name,
rename_to,
reset_password,
abort_all_queries,
add_role_delegation,
remove_role_delegation,
enroll_mfa,
set_default_mfa_method,
remove_mfa_method,
modify_mfa_method,
add_mfa_method_otp,
set_policy,
unset_policy,
set_tag,
unset_tag,
set_props,
unset_props,
}))
}
fn parse_mfa_method(&mut self) -> Result<MfaMethodKind, ParserError> {
if self.parse_keyword(Keyword::PASSKEY) {
Ok(MfaMethodKind::PassKey)
} else if self.parse_keyword(Keyword::TOTP) {
Ok(MfaMethodKind::Totp)
} else if self.parse_keyword(Keyword::DUO) {
Ok(MfaMethodKind::Duo)
} else {
self.expected("PASSKEY, TOTP or DUO", self.peek_token())
}
}
fn parse_mssql_alter_role(&mut self) -> Result<Statement, ParserError> {
let role_name = self.parse_identifier()?;

View file

@ -34,7 +34,7 @@ use IsOptional::*;
use crate::ast::helpers::{
key_value_options::{
KeyValueOption, KeyValueOptionType, KeyValueOptions, KeyValueOptionsDelimiter,
KeyValueOption, KeyValueOptionKind, KeyValueOptions, KeyValueOptionsDelimiter,
},
stmt_create_table::{CreateTableBuilder, CreateTableConfiguration},
};
@ -4796,10 +4796,12 @@ impl<'a> Parser<'a> {
fn parse_create_user(&mut self, or_replace: bool) -> Result<Statement, ParserError> {
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
let name = self.parse_identifier()?;
let options = self.parse_key_value_options(false, &[Keyword::WITH, Keyword::TAG])?;
let options = self
.parse_key_value_options(false, &[Keyword::WITH, Keyword::TAG])?
.options;
let with_tags = self.parse_keyword(Keyword::WITH);
let tags = if self.parse_keyword(Keyword::TAG) {
self.parse_key_value_options(true, &[])?
self.parse_key_value_options(true, &[])?.options
} else {
vec![]
};
@ -9277,6 +9279,7 @@ impl<'a> Parser<'a> {
Keyword::CONNECTOR,
Keyword::ICEBERG,
Keyword::SCHEMA,
Keyword::USER,
])?;
match object_type {
Keyword::SCHEMA => {
@ -9312,6 +9315,7 @@ impl<'a> Parser<'a> {
Keyword::ROLE => self.parse_alter_role(),
Keyword::POLICY => self.parse_alter_policy(),
Keyword::CONNECTOR => self.parse_alter_connector(),
Keyword::USER => self.parse_alter_user(),
// unreachable because expect_one_of_keywords used above
_ => unreachable!(),
}
@ -17488,8 +17492,9 @@ impl<'a> Parser<'a> {
&mut self,
parenthesized: bool,
end_words: &[Keyword],
) -> Result<Vec<KeyValueOption>, ParserError> {
) -> Result<KeyValueOptions, ParserError> {
let mut options: Vec<KeyValueOption> = Vec::new();
let mut delimiter = KeyValueOptionsDelimiter::Space;
if parenthesized {
self.expect_token(&Token::LParen)?;
}
@ -17503,9 +17508,12 @@ impl<'a> Parser<'a> {
}
}
Token::EOF => break,
Token::Comma => continue,
Token::Comma => {
delimiter = KeyValueOptionsDelimiter::Comma;
continue;
}
Token::Word(w) if !end_words.contains(&w.keyword) => {
options.push(self.parse_key_value_option(w)?)
options.push(self.parse_key_value_option(&w)?)
}
Token::Word(w) if end_words.contains(&w.keyword) => {
self.prev_token();
@ -17514,40 +17522,67 @@ impl<'a> Parser<'a> {
_ => return self.expected("another option, EOF, Comma or ')'", self.peek_token()),
};
}
Ok(options)
Ok(KeyValueOptions { delimiter, options })
}
/// Parses a `KEY = VALUE` construct based on the specified key
pub(crate) fn parse_key_value_option(
&mut self,
key: Word,
key: &Word,
) -> Result<KeyValueOption, ParserError> {
self.expect_token(&Token::Eq)?;
match self.next_token().token {
Token::SingleQuotedString(value) => Ok(KeyValueOption {
option_name: key.value,
option_type: KeyValueOptionType::STRING,
value,
match self.peek_token().token {
Token::SingleQuotedString(_) => Ok(KeyValueOption {
option_name: key.value.clone(),
option_value: KeyValueOptionKind::Single(self.parse_value()?.into()),
}),
Token::Word(word)
if word.keyword == Keyword::TRUE || word.keyword == Keyword::FALSE =>
{
Ok(KeyValueOption {
option_name: key.value,
option_type: KeyValueOptionType::BOOLEAN,
value: word.value.to_uppercase(),
option_name: key.value.clone(),
option_value: KeyValueOptionKind::Single(self.parse_value()?.into()),
})
}
Token::Word(word) => Ok(KeyValueOption {
option_name: key.value,
option_type: KeyValueOptionType::ENUM,
value: word.value,
}),
Token::Number(n, _) => Ok(KeyValueOption {
option_name: key.value,
option_type: KeyValueOptionType::NUMBER,
value: n,
Token::Number(..) => Ok(KeyValueOption {
option_name: key.value.clone(),
option_value: KeyValueOptionKind::Single(self.parse_value()?.into()),
}),
Token::Word(word) => {
self.next_token();
Ok(KeyValueOption {
option_name: key.value.clone(),
option_value: KeyValueOptionKind::Single(Value::Placeholder(
word.value.clone(),
)),
})
}
Token::LParen => {
// Can be a list of values or a list of key value properties.
// Try to parse a list of values and if that fails, try to parse
// a list of key-value properties.
match self.maybe_parse(|parser| {
parser.expect_token(&Token::LParen)?;
let values = parser.parse_comma_separated0(|p| p.parse_value(), Token::RParen);
parser.expect_token(&Token::RParen)?;
values
})? {
Some(values) => {
let values = values.into_iter().map(|v| v.value).collect();
Ok(KeyValueOption {
option_name: key.value.clone(),
option_value: KeyValueOptionKind::Multi(values),
})
}
None => Ok(KeyValueOption {
option_name: key.value.clone(),
option_value: KeyValueOptionKind::KeyValueOptions(Box::new(
self.parse_key_value_options(true, &[])?,
)),
}),
}
}
_ => self.expected("expected option value", self.peek_token()),
}
}