This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new a4308389 Snowflake: ALTER USER and KeyValueOptions Refactoring (#2035)
a4308389 is described below
commit a43083897468946d03b219f6bba31389e4bb4543
Author: Yoav Cohen <[email protected]>
AuthorDate: Thu Sep 25 21:59:11 2025 +0200
Snowflake: ALTER USER and KeyValueOptions Refactoring (#2035)
---
src/ast/dml.rs | 7 +-
src/ast/helpers/key_value_options.rs | 45 +++---
src/ast/helpers/stmt_create_database.rs | 2 +-
src/ast/mod.rs | 217 +++++++++++++++++++++++++++++
src/ast/spans.rs | 1 +
src/dialect/snowflake.rs | 25 ++--
src/keywords.rs | 12 ++
src/parser/alter.rs | 192 ++++++++++++++++++++++++-
src/parser/mod.rs | 85 ++++++++----
tests/sqlparser_common.rs | 239 ++++++++++++++++++++++++++++++--
tests/sqlparser_snowflake.rs | 119 ++++++++--------
11 files changed, 809 insertions(+), 135 deletions(-)
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<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)?;
+ }
+ KeyValueOptionKind::Multi(values) => {
+ write!(
+ f,
+ "{}=({})",
+ self.option_name,
+ display_comma_separated(values)
+ )?;
}
- KeyValueOptionType::ENUM | KeyValueOptionType::BOOLEAN |
KeyValueOptionType::NUMBER => {
- write!(f, "{}={}", self.option_name, self.value)?;
+ 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 \] \[ <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 ...
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<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())),
});
}
}
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 ] [ <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()?;
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<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()),
}
}
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");
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]