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(..) => Ok(KeyValueOption {
option_name: key.value.clone(),
option_value: KeyValueOptionKind::Single(self.parse_value()?.into()),
}),
Token::Number(n, _) => Ok(KeyValueOption {
option_name: key.value,
option_type: KeyValueOptionType::NUMBER,
value: n,
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()),
}
}

View file

@ -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/')");
}

View file

@ -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");
}