diff --git a/src/ast/dml.rs b/src/ast/dml.rs index 63d6b86c..e4d99bcf 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -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")] diff --git a/src/ast/helpers/key_value_options.rs b/src/ast/helpers/key_value_options.rs index 7f1bb0fd..745c3a65 100644 --- a/src/ast/helpers/key_value_options.rs +++ b/src/ast/helpers/key_value_options.rs @@ -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), + KeyValueOptions(Box), } 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(()) diff --git a/src/ast/helpers/stmt_create_database.rs b/src/ast/helpers/stmt_create_database.rs index 94997bfa..58a7b090 100644 --- a/src/ast/helpers/stmt_create_database.rs +++ b/src/ast/helpers/stmt_create_database.rs @@ -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}; diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8df636f8..4c1743fe 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -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 \] \[ \] + /// ``` + /// [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 ] [ ] [ 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: + pub rename_to: Option, + pub reset_password: bool, + pub abort_all_queries: bool, + pub add_role_delegation: Option, + pub remove_role_delegation: Option, + pub enroll_mfa: bool, + pub set_default_mfa_method: Option, + pub remove_mfa_method: Option, + pub modify_mfa_method: Option, + pub add_mfa_method_otp: Option, + pub set_policy: Option, + pub unset_policy: Option, + pub set_tag: KeyValueOptions, + pub unset_tag: Vec, + pub set_props: KeyValueOptions, + pub unset_props: Vec, +} + +/// ```sql +/// ALTER USER [ IF EXISTS ] [ ] ADD DELEGATED AUTHORIZATION OF ROLE TO SECURITY INTEGRATION +/// ``` +#[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 ] [ ] REMOVE DELEGATED { AUTHORIZATION OF ROLE | AUTHORIZATIONS } FROM SECURITY INTEGRATION +/// ``` +#[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, + 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, +} + +/// ```sql +/// ALTER USER [ IF EXISTS ] [ ] MODIFY MFA METHOD SET COMMENT = '' +/// ``` +#[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 ] [ ] SET { AUTHENTICATION | PASSWORD | SESSION } POLICY +/// ``` +#[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 ... diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 5913fe16..4c53e55c 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -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(), } } } diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 3bb36010..825fd45f 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -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 { // 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 { // 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 ` the copy options do not have a shared key // like in `COPY INTO ` - 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 Result { 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())), }); } } diff --git a/src/keywords.rs b/src/keywords.rs index e0590b69..3c985522 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -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, diff --git a/src/parser/alter.rs b/src/parser/alter.rs index bff462ee..b3e3c99e 100644 --- a/src/parser/alter.rs +++ b/src/parser/alter.rs @@ -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 ] [ ] [ OPTIONS ] + /// ``` + pub fn parse_alter_user(&mut self) -> Result { + 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 { + 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 { let role_name = self.parse_identifier()?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f47d4b9b..66089be7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -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 { 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, ParserError> { + ) -> Result { let mut options: Vec = 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 { 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()), } } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index f46abc7d..b9434581 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -16761,11 +16761,13 @@ fn parse_create_user() { verified_stmt("CREATE OR REPLACE USER u1"); verified_stmt("CREATE OR REPLACE USER IF NOT EXISTS u1"); verified_stmt("CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret'"); - verified_stmt( + let dialects = all_dialects_where(|d| d.supports_boolean_literals()); + dialects.one_statement_parses_to( "CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret' MUST_CHANGE_PASSWORD=TRUE", + "CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret' MUST_CHANGE_PASSWORD=true", ); - verified_stmt("CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret' MUST_CHANGE_PASSWORD=TRUE TYPE=SERVICE TAG (t1='v1')"); - let create = verified_stmt("CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret' MUST_CHANGE_PASSWORD=TRUE TYPE=SERVICE WITH TAG (t1='v1', t2='v2')"); + dialects.verified_stmt("CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret' MUST_CHANGE_PASSWORD=true TYPE=SERVICE TAG (t1='v1')"); + let create = dialects.verified_stmt("CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret' MUST_CHANGE_PASSWORD=false TYPE=SERVICE WITH TAG (t1='v1', t2='v2')"); match create { Statement::CreateUser(stmt) => { assert_eq!(stmt.name, Ident::new("u1")); @@ -16778,18 +16780,19 @@ fn parse_create_user() { options: vec![ KeyValueOption { option_name: "PASSWORD".to_string(), - value: "secret".to_string(), - option_type: KeyValueOptionType::STRING + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "secret".to_string() + )), }, KeyValueOption { option_name: "MUST_CHANGE_PASSWORD".to_string(), - value: "TRUE".to_string(), - option_type: KeyValueOptionType::BOOLEAN + option_value: KeyValueOptionKind::Single(Value::Boolean(false)), }, KeyValueOption { option_name: "TYPE".to_string(), - value: "SERVICE".to_string(), - option_type: KeyValueOptionType::ENUM + option_value: KeyValueOptionKind::Single(Value::Placeholder( + "SERVICE".to_string() + )), }, ], }, @@ -16802,13 +16805,15 @@ fn parse_create_user() { options: vec![ KeyValueOption { option_name: "t1".to_string(), - value: "v1".to_string(), - option_type: KeyValueOptionType::STRING + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "v1".to_string() + )), }, KeyValueOption { option_name: "t2".to_string(), - value: "v2".to_string(), - option_type: KeyValueOptionType::STRING + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "v2".to_string() + )), }, ] } @@ -17246,3 +17251,211 @@ fn parse_invisible_column() { _ => panic!("Unexpected statement {stmt}"), } } + +#[test] +fn test_parse_alter_user() { + verified_stmt("ALTER USER u1"); + verified_stmt("ALTER USER IF EXISTS u1"); + let stmt = verified_stmt("ALTER USER IF EXISTS u1 RENAME TO u2"); + match stmt { + Statement::AlterUser(alter) => { + assert!(alter.if_exists); + assert_eq!(alter.name, Ident::new("u1")); + assert_eq!(alter.rename_to, Some(Ident::new("u2"))); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER IF EXISTS u1 RESET PASSWORD"); + verified_stmt("ALTER USER IF EXISTS u1 ABORT ALL QUERIES"); + verified_stmt( + "ALTER USER IF EXISTS u1 ADD DELEGATED AUTHORIZATION OF ROLE r1 TO SECURITY INTEGRATION i1", + ); + verified_stmt("ALTER USER IF EXISTS u1 REMOVE DELEGATED AUTHORIZATION OF ROLE r1 FROM SECURITY INTEGRATION i1"); + verified_stmt( + "ALTER USER IF EXISTS u1 REMOVE DELEGATED AUTHORIZATIONS FROM SECURITY INTEGRATION i1", + ); + verified_stmt("ALTER USER IF EXISTS u1 ENROLL MFA"); + let stmt = verified_stmt("ALTER USER u1 SET DEFAULT_MFA_METHOD PASSKEY"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!(alter.set_default_mfa_method, Some(MfaMethodKind::PassKey)) + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 SET DEFAULT_MFA_METHOD TOTP"); + verified_stmt("ALTER USER u1 SET DEFAULT_MFA_METHOD DUO"); + let stmt = verified_stmt("ALTER USER u1 REMOVE MFA METHOD PASSKEY"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!(alter.remove_mfa_method, Some(MfaMethodKind::PassKey)) + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 REMOVE MFA METHOD TOTP"); + verified_stmt("ALTER USER u1 REMOVE MFA METHOD DUO"); + let stmt = verified_stmt("ALTER USER u1 MODIFY MFA METHOD PASSKEY SET COMMENT 'abc'"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!( + alter.modify_mfa_method, + Some(AlterUserModifyMfaMethod { + method: MfaMethodKind::PassKey, + comment: "abc".to_string() + }) + ); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 ADD MFA METHOD OTP"); + verified_stmt("ALTER USER u1 ADD MFA METHOD OTP COUNT = 8"); + + let stmt = verified_stmt("ALTER USER u1 SET AUTHENTICATION POLICY p1"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!( + alter.set_policy, + Some(AlterUserSetPolicy { + policy_kind: UserPolicyKind::Authentication, + policy: Ident::new("p1") + }) + ); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 SET PASSWORD POLICY p1"); + verified_stmt("ALTER USER u1 SET SESSION POLICY p1"); + let stmt = verified_stmt("ALTER USER u1 UNSET AUTHENTICATION POLICY"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!(alter.unset_policy, Some(UserPolicyKind::Authentication)); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 UNSET PASSWORD POLICY"); + verified_stmt("ALTER USER u1 UNSET SESSION POLICY"); + + let stmt = verified_stmt("ALTER USER u1 SET TAG k1='v1'"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!( + alter.set_tag.options, + vec![KeyValueOption { + option_name: "k1".to_string(), + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "v1".to_string() + )), + },] + ); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 SET TAG k1='v1', k2='v2'"); + let stmt = verified_stmt("ALTER USER u1 UNSET TAG k1"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!(alter.unset_tag, vec!["k1".to_string()]); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 UNSET TAG k1, k2, k3"); + + let dialects = all_dialects_where(|d| d.supports_boolean_literals()); + dialects.one_statement_parses_to( + "ALTER USER u1 SET PASSWORD='secret', MUST_CHANGE_PASSWORD=TRUE, MINS_TO_UNLOCK=10", + "ALTER USER u1 SET PASSWORD='secret', MUST_CHANGE_PASSWORD=true, MINS_TO_UNLOCK=10", + ); + + let stmt = dialects.verified_stmt( + "ALTER USER u1 SET PASSWORD='secret', MUST_CHANGE_PASSWORD=true, MINS_TO_UNLOCK=10", + ); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!( + alter.set_props, + KeyValueOptions { + delimiter: KeyValueOptionsDelimiter::Comma, + options: vec![ + KeyValueOption { + option_name: "PASSWORD".to_string(), + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "secret".to_string() + )), + }, + KeyValueOption { + option_name: "MUST_CHANGE_PASSWORD".to_string(), + option_value: KeyValueOptionKind::Single(Value::Boolean(true)), + }, + KeyValueOption { + option_name: "MINS_TO_UNLOCK".to_string(), + option_value: KeyValueOptionKind::Single(number("10")), + }, + ] + } + ); + } + _ => unreachable!(), + } + + let stmt = verified_stmt("ALTER USER u1 UNSET PASSWORD"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!(alter.unset_props, vec!["PASSWORD".to_string()]); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 UNSET PASSWORD, MUST_CHANGE_PASSWORD, MINS_TO_UNLOCK"); + + let stmt = verified_stmt("ALTER USER u1 SET DEFAULT_SECONDARY_ROLES=('ALL')"); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!( + alter.set_props.options, + vec![KeyValueOption { + option_name: "DEFAULT_SECONDARY_ROLES".to_string(), + option_value: KeyValueOptionKind::Multi(vec![Value::SingleQuotedString( + "ALL".to_string() + )]) + }] + ); + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 SET DEFAULT_SECONDARY_ROLES=()"); + verified_stmt("ALTER USER u1 SET DEFAULT_SECONDARY_ROLES=('R1', 'R2', 'R3')"); + verified_stmt("ALTER USER u1 SET PASSWORD='secret', DEFAULT_SECONDARY_ROLES=('ALL')"); + verified_stmt("ALTER USER u1 SET DEFAULT_SECONDARY_ROLES=('ALL'), PASSWORD='secret'"); + let stmt = verified_stmt( + "ALTER USER u1 SET WORKLOAD_IDENTITY=(TYPE=AWS, ARN='arn:aws:iam::123456789:r1/')", + ); + match stmt { + Statement::AlterUser(alter) => { + assert_eq!( + alter.set_props.options, + vec![KeyValueOption { + option_name: "WORKLOAD_IDENTITY".to_string(), + option_value: KeyValueOptionKind::KeyValueOptions(Box::new(KeyValueOptions { + delimiter: KeyValueOptionsDelimiter::Comma, + options: vec![ + KeyValueOption { + option_name: "TYPE".to_string(), + option_value: KeyValueOptionKind::Single(Value::Placeholder( + "AWS".to_string() + )), + }, + KeyValueOption { + option_name: "ARN".to_string(), + option_value: KeyValueOptionKind::Single( + Value::SingleQuotedString( + "arn:aws:iam::123456789:r1/".to_string() + ) + ), + }, + ] + })) + }] + ) + } + _ => unreachable!(), + } + verified_stmt("ALTER USER u1 SET DEFAULT_SECONDARY_ROLES=('ALL'), PASSWORD='secret', WORKLOAD_IDENTITY=(TYPE=AWS, ARN='arn:aws:iam::123456789:r1/')"); +} diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 7c9e5261..e04bfaf5 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -19,7 +19,7 @@ //! Test SQL syntax specific to Snowflake. The parser based on the //! generic dialect is also tested (on the inputs it can handle). -use sqlparser::ast::helpers::key_value_options::{KeyValueOption, KeyValueOptionType}; +use sqlparser::ast::helpers::key_value_options::{KeyValueOption, KeyValueOptionKind}; use sqlparser::ast::helpers::stmt_data_loading::{StageLoadSelectItem, StageLoadSelectItemKind}; use sqlparser::ast::*; use sqlparser::dialect::{Dialect, GenericDialect, SnowflakeDialect}; @@ -2116,23 +2116,27 @@ fn test_create_stage_with_stage_params() { ); assert!(stage_params.credentials.options.contains(&KeyValueOption { option_name: "AWS_KEY_ID".to_string(), - option_type: KeyValueOptionType::STRING, - value: "1a2b3c".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "1a2b3c".to_string() + )), })); assert!(stage_params.credentials.options.contains(&KeyValueOption { option_name: "AWS_SECRET_KEY".to_string(), - option_type: KeyValueOptionType::STRING, - value: "4x5y6z".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "4x5y6z".to_string() + )), })); assert!(stage_params.encryption.options.contains(&KeyValueOption { option_name: "MASTER_KEY".to_string(), - option_type: KeyValueOptionType::STRING, - value: "key".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "key".to_string() + )), })); assert!(stage_params.encryption.options.contains(&KeyValueOption { option_name: "TYPE".to_string(), - option_type: KeyValueOptionType::STRING, - value: "AWS_SSE_KMS".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "AWS_SSE_KMS".to_string() + )), })); } _ => unreachable!(), @@ -2146,7 +2150,7 @@ fn test_create_stage_with_directory_table_params() { let sql = concat!( "CREATE OR REPLACE STAGE my_ext_stage ", "URL='s3://load/files/' ", - "DIRECTORY=(ENABLE=TRUE REFRESH_ON_CREATE=FALSE NOTIFICATION_INTEGRATION='some-string')" + "DIRECTORY=(ENABLE=true REFRESH_ON_CREATE=false NOTIFICATION_INTEGRATION='some-string')" ); match snowflake().verified_stmt(sql) { @@ -2156,18 +2160,17 @@ fn test_create_stage_with_directory_table_params() { } => { assert!(directory_table_params.options.contains(&KeyValueOption { option_name: "ENABLE".to_string(), - option_type: KeyValueOptionType::BOOLEAN, - value: "TRUE".to_string() + option_value: KeyValueOptionKind::Single(Value::Boolean(true)), })); assert!(directory_table_params.options.contains(&KeyValueOption { option_name: "REFRESH_ON_CREATE".to_string(), - option_type: KeyValueOptionType::BOOLEAN, - value: "FALSE".to_string() + option_value: KeyValueOptionKind::Single(Value::Boolean(false)), })); assert!(directory_table_params.options.contains(&KeyValueOption { option_name: "NOTIFICATION_INTEGRATION".to_string(), - option_type: KeyValueOptionType::STRING, - value: "some-string".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "some-string".to_string() + )), })); } _ => unreachable!(), @@ -2187,18 +2190,17 @@ fn test_create_stage_with_file_format() { Statement::CreateStage { file_format, .. } => { assert!(file_format.options.contains(&KeyValueOption { option_name: "COMPRESSION".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "AUTO".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder("AUTO".to_string())), })); assert!(file_format.options.contains(&KeyValueOption { option_name: "BINARY_FORMAT".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "HEX".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder("HEX".to_string())), })); assert!(file_format.options.contains(&KeyValueOption { option_name: "ESCAPE".to_string(), - option_type: KeyValueOptionType::STRING, - value: r#"\\"#.to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + r#"\\"#.to_string() + )), })); } _ => unreachable!(), @@ -2214,19 +2216,19 @@ fn test_create_stage_with_copy_options() { let sql = concat!( "CREATE OR REPLACE STAGE my_ext_stage ", "URL='s3://load/files/' ", - "COPY_OPTIONS=(ON_ERROR=CONTINUE FORCE=TRUE)" + "COPY_OPTIONS=(ON_ERROR=CONTINUE FORCE=true)" ); match snowflake().verified_stmt(sql) { Statement::CreateStage { copy_options, .. } => { assert!(copy_options.options.contains(&KeyValueOption { option_name: "ON_ERROR".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "CONTINUE".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder( + "CONTINUE".to_string() + )), })); assert!(copy_options.options.contains(&KeyValueOption { option_name: "FORCE".to_string(), - option_type: KeyValueOptionType::BOOLEAN, - value: "TRUE".to_string() + option_value: KeyValueOptionKind::Single(Value::Boolean(true)), })); } _ => unreachable!(), @@ -2357,23 +2359,27 @@ fn test_copy_into_with_stage_params() { ); assert!(stage_params.credentials.options.contains(&KeyValueOption { option_name: "AWS_KEY_ID".to_string(), - option_type: KeyValueOptionType::STRING, - value: "1a2b3c".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "1a2b3c".to_string() + )), })); assert!(stage_params.credentials.options.contains(&KeyValueOption { option_name: "AWS_SECRET_KEY".to_string(), - option_type: KeyValueOptionType::STRING, - value: "4x5y6z".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "4x5y6z".to_string() + )), })); assert!(stage_params.encryption.options.contains(&KeyValueOption { option_name: "MASTER_KEY".to_string(), - option_type: KeyValueOptionType::STRING, - value: "key".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "key".to_string() + )), })); assert!(stage_params.encryption.options.contains(&KeyValueOption { option_name: "TYPE".to_string(), - option_type: KeyValueOptionType::STRING, - value: "AWS_SSE_KMS".to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + "AWS_SSE_KMS".to_string() + )), })); } _ => unreachable!(), @@ -2524,18 +2530,17 @@ fn test_copy_into_file_format() { Statement::CopyIntoSnowflake { file_format, .. } => { assert!(file_format.options.contains(&KeyValueOption { option_name: "COMPRESSION".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "AUTO".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder("AUTO".to_string())), })); assert!(file_format.options.contains(&KeyValueOption { option_name: "BINARY_FORMAT".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "HEX".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder("HEX".to_string())), })); assert!(file_format.options.contains(&KeyValueOption { option_name: "ESCAPE".to_string(), - option_type: KeyValueOptionType::STRING, - value: r#"\\"#.to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + r#"\\"#.to_string() + )), })); } _ => unreachable!(), @@ -2563,18 +2568,17 @@ fn test_copy_into_file_format() { Statement::CopyIntoSnowflake { file_format, .. } => { assert!(file_format.options.contains(&KeyValueOption { option_name: "COMPRESSION".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "AUTO".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder("AUTO".to_string())), })); assert!(file_format.options.contains(&KeyValueOption { option_name: "BINARY_FORMAT".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "HEX".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder("HEX".to_string())), })); assert!(file_format.options.contains(&KeyValueOption { option_name: "ESCAPE".to_string(), - option_type: KeyValueOptionType::STRING, - value: r#"\\"#.to_string() + option_value: KeyValueOptionKind::Single(Value::SingleQuotedString( + r#"\\"#.to_string() + )), })); } _ => unreachable!(), @@ -2588,20 +2592,20 @@ fn test_copy_into_copy_options() { "FROM 'gcs://mybucket/./../a.csv' ", "FILES = ('file1.json', 'file2.json') ", "PATTERN = '.*employees0[1-5].csv.gz' ", - "COPY_OPTIONS=(ON_ERROR=CONTINUE FORCE=TRUE)" + "COPY_OPTIONS=(ON_ERROR=CONTINUE FORCE=true)" ); match snowflake().verified_stmt(sql) { Statement::CopyIntoSnowflake { copy_options, .. } => { assert!(copy_options.options.contains(&KeyValueOption { option_name: "ON_ERROR".to_string(), - option_type: KeyValueOptionType::ENUM, - value: "CONTINUE".to_string() + option_value: KeyValueOptionKind::Single(Value::Placeholder( + "CONTINUE".to_string() + )), })); assert!(copy_options.options.contains(&KeyValueOption { option_name: "FORCE".to_string(), - option_type: KeyValueOptionType::BOOLEAN, - value: "TRUE".to_string() + option_value: KeyValueOptionKind::Single(Value::Boolean(true)), })); } _ => unreachable!(), @@ -3863,17 +3867,20 @@ fn test_alter_session() { "sql parser error: expected at least one option" ); - snowflake().verified_stmt("ALTER SESSION SET AUTOCOMMIT=TRUE"); - snowflake().verified_stmt("ALTER SESSION SET AUTOCOMMIT=FALSE QUERY_TAG='tag'"); + snowflake().one_statement_parses_to( + "ALTER SESSION SET AUTOCOMMIT=TRUE", + "ALTER SESSION SET AUTOCOMMIT=true", + ); + snowflake().verified_stmt("ALTER SESSION SET AUTOCOMMIT=false QUERY_TAG='tag'"); snowflake().verified_stmt("ALTER SESSION UNSET AUTOCOMMIT"); snowflake().verified_stmt("ALTER SESSION UNSET AUTOCOMMIT, QUERY_TAG"); snowflake().one_statement_parses_to( "ALTER SESSION SET A=false, B='tag';", - "ALTER SESSION SET A=FALSE B='tag'", + "ALTER SESSION SET A=false B='tag'", ); snowflake().one_statement_parses_to( "ALTER SESSION SET A=true \nB='tag'", - "ALTER SESSION SET A=TRUE B='tag'", + "ALTER SESSION SET A=true B='tag'", ); snowflake().one_statement_parses_to("ALTER SESSION UNSET a\nB", "ALTER SESSION UNSET a, B"); }