mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 23:49:10 +00:00
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
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:
parent
54a24e76a9
commit
a430838974
11 changed files with 809 additions and 135 deletions
|
@ -16,12 +16,7 @@
|
||||||
// under the License.
|
// under the License.
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{
|
use alloc::{boxed::Box, format, string::ToString, vec::Vec};
|
||||||
boxed::Box,
|
|
||||||
format,
|
|
||||||
string::{String, ToString},
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use core::fmt::{self, Display};
|
use core::fmt::{self, Display};
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
|
@ -19,9 +19,7 @@
|
||||||
//! See [this page](https://docs.snowflake.com/en/sql-reference/commands-data-loading) for more details.
|
//! See [this page](https://docs.snowflake.com/en/sql-reference/commands-data-loading) for more details.
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::string::String;
|
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::fmt::Formatter;
|
use core::fmt::Formatter;
|
||||||
|
|
||||||
|
@ -31,7 +29,7 @@ use serde::{Deserialize, Serialize};
|
||||||
#[cfg(feature = "visitor")]
|
#[cfg(feature = "visitor")]
|
||||||
use sqlparser_derive::{Visit, VisitMut};
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -52,20 +50,23 @@ pub enum KeyValueOptionsDelimiter {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub enum KeyValueOptionType {
|
pub struct KeyValueOption {
|
||||||
STRING,
|
pub option_name: String,
|
||||||
BOOLEAN,
|
pub option_value: KeyValueOptionKind,
|
||||||
ENUM,
|
|
||||||
NUMBER,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub struct KeyValueOption {
|
pub enum KeyValueOptionKind {
|
||||||
pub option_name: String,
|
Single(Value),
|
||||||
pub option_type: KeyValueOptionType,
|
Multi(Vec<Value>),
|
||||||
pub value: String,
|
KeyValueOptions(Box<KeyValueOptions>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for KeyValueOptions {
|
impl fmt::Display for KeyValueOptions {
|
||||||
|
@ -80,12 +81,20 @@ impl fmt::Display for KeyValueOptions {
|
||||||
|
|
||||||
impl fmt::Display for KeyValueOption {
|
impl fmt::Display for KeyValueOption {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.option_type {
|
match &self.option_value {
|
||||||
KeyValueOptionType::STRING => {
|
KeyValueOptionKind::Single(value) => {
|
||||||
write!(f, "{}='{}'", self.option_name, self.value)?;
|
write!(f, "{}={value}", self.option_name)?;
|
||||||
}
|
}
|
||||||
KeyValueOptionType::ENUM | KeyValueOptionType::BOOLEAN | KeyValueOptionType::NUMBER => {
|
KeyValueOptionKind::Multi(values) => {
|
||||||
write!(f, "{}={}", self.option_name, self.value)?;
|
write!(
|
||||||
|
f,
|
||||||
|
"{}=({})",
|
||||||
|
self.option_name,
|
||||||
|
display_comma_separated(values)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
KeyValueOptionKind::KeyValueOptions(options) => {
|
||||||
|
write!(f, "{}=({options})", self.option_name)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
// under the License.
|
// under the License.
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
|
use alloc::{format, string::String, vec::Vec};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
217
src/ast/mod.rs
217
src/ast/mod.rs
|
@ -4310,6 +4310,11 @@ pub enum Statement {
|
||||||
/// ```
|
/// ```
|
||||||
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-user)
|
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-user)
|
||||||
CreateUser(CreateUser),
|
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
|
/// Re-sorts rows and reclaims space in either a specified table or all tables in the current database
|
||||||
///
|
///
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
@ -6183,6 +6188,7 @@ impl fmt::Display for Statement {
|
||||||
Statement::CreateUser(s) => write!(f, "{s}"),
|
Statement::CreateUser(s) => write!(f, "{s}"),
|
||||||
Statement::AlterSchema(s) => write!(f, "{s}"),
|
Statement::AlterSchema(s) => write!(f, "{s}"),
|
||||||
Statement::Vacuum(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.
|
/// Specifies how to create a new table based on an existing table's schema.
|
||||||
/// '''sql
|
/// '''sql
|
||||||
/// CREATE TABLE new LIKE old ...
|
/// CREATE TABLE new LIKE old ...
|
||||||
|
|
|
@ -555,6 +555,7 @@ impl Spanned for Statement {
|
||||||
Statement::CreateUser(..) => Span::empty(),
|
Statement::CreateUser(..) => Span::empty(),
|
||||||
Statement::AlterSchema(s) => s.span(),
|
Statement::AlterSchema(s) => s.span(),
|
||||||
Statement::Vacuum(..) => Span::empty(),
|
Statement::Vacuum(..) => Span::empty(),
|
||||||
|
Statement::AlterUser(..) => Span::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use crate::alloc::string::ToString;
|
use crate::alloc::string::ToString;
|
||||||
use crate::ast::helpers::key_value_options::{
|
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_database::CreateDatabaseBuilder;
|
||||||
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
||||||
|
@ -30,7 +30,7 @@ use crate::ast::{
|
||||||
CopyIntoSnowflakeKind, CreateTableLikeKind, DollarQuotedString, Ident, IdentityParameters,
|
CopyIntoSnowflakeKind, CreateTableLikeKind, DollarQuotedString, Ident, IdentityParameters,
|
||||||
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
|
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
|
||||||
InitializeKind, ObjectName, ObjectNamePart, RefreshModeKind, RowAccessPolicy, ShowObjects,
|
InitializeKind, ObjectName, ObjectNamePart, RefreshModeKind, RowAccessPolicy, ShowObjects,
|
||||||
SqlOption, Statement, StorageSerializationPolicy, TagsColumnOption, WrappedCollection,
|
SqlOption, Statement, StorageSerializationPolicy, TagsColumnOption, Value, WrappedCollection,
|
||||||
};
|
};
|
||||||
use crate::dialect::{Dialect, Precedence};
|
use crate::dialect::{Dialect, Precedence};
|
||||||
use crate::keywords::Keyword;
|
use crate::keywords::Keyword;
|
||||||
|
@ -1004,19 +1004,19 @@ pub fn parse_create_stage(
|
||||||
// [ directoryTableParams ]
|
// [ directoryTableParams ]
|
||||||
if parser.parse_keyword(Keyword::DIRECTORY) {
|
if parser.parse_keyword(Keyword::DIRECTORY) {
|
||||||
parser.expect_token(&Token::Eq)?;
|
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]
|
// [ file_format]
|
||||||
if parser.parse_keyword(Keyword::FILE_FORMAT) {
|
if parser.parse_keyword(Keyword::FILE_FORMAT) {
|
||||||
parser.expect_token(&Token::Eq)?;
|
parser.expect_token(&Token::Eq)?;
|
||||||
file_format = parser.parse_key_value_options(true, &[])?;
|
file_format = parser.parse_key_value_options(true, &[])?.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
// [ copy_options ]
|
// [ copy_options ]
|
||||||
if parser.parse_keyword(Keyword::COPY_OPTIONS) {
|
if parser.parse_keyword(Keyword::COPY_OPTIONS) {
|
||||||
parser.expect_token(&Token::Eq)?;
|
parser.expect_token(&Token::Eq)?;
|
||||||
copy_options = parser.parse_key_value_options(true, &[])?;
|
copy_options = parser.parse_key_value_options(true, &[])?.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
// [ comment ]
|
// [ comment ]
|
||||||
|
@ -1182,7 +1182,7 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||||
// FILE_FORMAT
|
// FILE_FORMAT
|
||||||
if parser.parse_keyword(Keyword::FILE_FORMAT) {
|
if parser.parse_keyword(Keyword::FILE_FORMAT) {
|
||||||
parser.expect_token(&Token::Eq)?;
|
parser.expect_token(&Token::Eq)?;
|
||||||
file_format = parser.parse_key_value_options(true, &[])?;
|
file_format = parser.parse_key_value_options(true, &[])?.options;
|
||||||
// PARTITION BY
|
// PARTITION BY
|
||||||
} else if parser.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
} else if parser.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
||||||
partition = Some(Box::new(parser.parse_expr()?))
|
partition = Some(Box::new(parser.parse_expr()?))
|
||||||
|
@ -1220,14 +1220,14 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||||
// COPY OPTIONS
|
// COPY OPTIONS
|
||||||
} else if parser.parse_keyword(Keyword::COPY_OPTIONS) {
|
} else if parser.parse_keyword(Keyword::COPY_OPTIONS) {
|
||||||
parser.expect_token(&Token::Eq)?;
|
parser.expect_token(&Token::Eq)?;
|
||||||
copy_options = parser.parse_key_value_options(true, &[])?;
|
copy_options = parser.parse_key_value_options(true, &[])?.options;
|
||||||
} else {
|
} else {
|
||||||
match parser.next_token().token {
|
match parser.next_token().token {
|
||||||
Token::SemiColon | Token::EOF => break,
|
Token::SemiColon | Token::EOF => break,
|
||||||
Token::Comma => continue,
|
Token::Comma => continue,
|
||||||
// In `COPY INTO <location>` the copy options do not have a shared key
|
// In `COPY INTO <location>` the copy options do not have a shared key
|
||||||
// like in `COPY INTO <table>`
|
// 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()),
|
_ => 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) {
|
if parser.parse_keyword(Keyword::CREDENTIALS) {
|
||||||
parser.expect_token(&Token::Eq)?;
|
parser.expect_token(&Token::Eq)?;
|
||||||
credentials = KeyValueOptions {
|
credentials = KeyValueOptions {
|
||||||
options: parser.parse_key_value_options(true, &[])?,
|
options: parser.parse_key_value_options(true, &[])?.options,
|
||||||
delimiter: KeyValueOptionsDelimiter::Space,
|
delimiter: KeyValueOptionsDelimiter::Space,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1396,7 +1396,7 @@ fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserEr
|
||||||
if parser.parse_keyword(Keyword::ENCRYPTION) {
|
if parser.parse_keyword(Keyword::ENCRYPTION) {
|
||||||
parser.expect_token(&Token::Eq)?;
|
parser.expect_token(&Token::Eq)?;
|
||||||
encryption = KeyValueOptions {
|
encryption = KeyValueOptions {
|
||||||
options: parser.parse_key_value_options(true, &[])?,
|
options: parser.parse_key_value_options(true, &[])?.options,
|
||||||
delimiter: KeyValueOptionsDelimiter::Space,
|
delimiter: KeyValueOptionsDelimiter::Space,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1431,13 +1431,12 @@ fn parse_session_options(
|
||||||
Token::Word(key) => {
|
Token::Word(key) => {
|
||||||
parser.advance_token();
|
parser.advance_token();
|
||||||
if set {
|
if set {
|
||||||
let option = parser.parse_key_value_option(key)?;
|
let option = parser.parse_key_value_option(&key)?;
|
||||||
options.push(option);
|
options.push(option);
|
||||||
} else {
|
} else {
|
||||||
options.push(KeyValueOption {
|
options.push(KeyValueOption {
|
||||||
option_name: key.value,
|
option_name: key.value,
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder(empty())),
|
||||||
value: empty(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@ define_keywords!(
|
||||||
AUDIT,
|
AUDIT,
|
||||||
AUTHENTICATION,
|
AUTHENTICATION,
|
||||||
AUTHORIZATION,
|
AUTHORIZATION,
|
||||||
|
AUTHORIZATIONS,
|
||||||
AUTO,
|
AUTO,
|
||||||
AUTOEXTEND_SIZE,
|
AUTOEXTEND_SIZE,
|
||||||
AUTOINCREMENT,
|
AUTOINCREMENT,
|
||||||
|
@ -279,6 +280,8 @@ define_keywords!(
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
DEFAULTS,
|
DEFAULTS,
|
||||||
DEFAULT_DDL_COLLATION,
|
DEFAULT_DDL_COLLATION,
|
||||||
|
DEFAULT_MFA_METHOD,
|
||||||
|
DEFAULT_SECONDARY_ROLES,
|
||||||
DEFERRABLE,
|
DEFERRABLE,
|
||||||
DEFERRED,
|
DEFERRED,
|
||||||
DEFINE,
|
DEFINE,
|
||||||
|
@ -286,6 +289,7 @@ define_keywords!(
|
||||||
DEFINER,
|
DEFINER,
|
||||||
DELAYED,
|
DELAYED,
|
||||||
DELAY_KEY_WRITE,
|
DELAY_KEY_WRITE,
|
||||||
|
DELEGATED,
|
||||||
DELETE,
|
DELETE,
|
||||||
DELIMITED,
|
DELIMITED,
|
||||||
DELIMITER,
|
DELIMITER,
|
||||||
|
@ -314,6 +318,7 @@ define_keywords!(
|
||||||
DOY,
|
DOY,
|
||||||
DROP,
|
DROP,
|
||||||
DRY,
|
DRY,
|
||||||
|
DUO,
|
||||||
DUPLICATE,
|
DUPLICATE,
|
||||||
DYNAMIC,
|
DYNAMIC,
|
||||||
EACH,
|
EACH,
|
||||||
|
@ -336,6 +341,7 @@ define_keywords!(
|
||||||
ENFORCED,
|
ENFORCED,
|
||||||
ENGINE,
|
ENGINE,
|
||||||
ENGINE_ATTRIBUTE,
|
ENGINE_ATTRIBUTE,
|
||||||
|
ENROLL,
|
||||||
ENUM,
|
ENUM,
|
||||||
ENUM16,
|
ENUM16,
|
||||||
ENUM8,
|
ENUM8,
|
||||||
|
@ -586,6 +592,7 @@ define_keywords!(
|
||||||
METHOD,
|
METHOD,
|
||||||
METRIC,
|
METRIC,
|
||||||
METRICS,
|
METRICS,
|
||||||
|
MFA,
|
||||||
MICROSECOND,
|
MICROSECOND,
|
||||||
MICROSECONDS,
|
MICROSECONDS,
|
||||||
MILLENIUM,
|
MILLENIUM,
|
||||||
|
@ -685,6 +692,7 @@ define_keywords!(
|
||||||
ORDINALITY,
|
ORDINALITY,
|
||||||
ORGANIZATION,
|
ORGANIZATION,
|
||||||
OTHER,
|
OTHER,
|
||||||
|
OTP,
|
||||||
OUT,
|
OUT,
|
||||||
OUTER,
|
OUTER,
|
||||||
OUTPUT,
|
OUTPUT,
|
||||||
|
@ -709,6 +717,7 @@ define_keywords!(
|
||||||
PARTITIONED,
|
PARTITIONED,
|
||||||
PARTITIONS,
|
PARTITIONS,
|
||||||
PASSING,
|
PASSING,
|
||||||
|
PASSKEY,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
PAST,
|
PAST,
|
||||||
PATH,
|
PATH,
|
||||||
|
@ -753,6 +762,7 @@ define_keywords!(
|
||||||
PURGE,
|
PURGE,
|
||||||
QUALIFY,
|
QUALIFY,
|
||||||
QUARTER,
|
QUARTER,
|
||||||
|
QUERIES,
|
||||||
QUERY,
|
QUERY,
|
||||||
QUOTE,
|
QUOTE,
|
||||||
RAISE,
|
RAISE,
|
||||||
|
@ -969,6 +979,7 @@ define_keywords!(
|
||||||
TO,
|
TO,
|
||||||
TOP,
|
TOP,
|
||||||
TOTALS,
|
TOTALS,
|
||||||
|
TOTP,
|
||||||
TRACE,
|
TRACE,
|
||||||
TRAILING,
|
TRAILING,
|
||||||
TRANSACTION,
|
TRANSACTION,
|
||||||
|
@ -1067,6 +1078,7 @@ define_keywords!(
|
||||||
WITHOUT,
|
WITHOUT,
|
||||||
WITHOUT_ARRAY_WRAPPER,
|
WITHOUT_ARRAY_WRAPPER,
|
||||||
WORK,
|
WORK,
|
||||||
|
WORKLOAD_IDENTITY,
|
||||||
WRAPPER,
|
WRAPPER,
|
||||||
WRITE,
|
WRITE,
|
||||||
XML,
|
XML,
|
||||||
|
|
|
@ -13,13 +13,16 @@
|
||||||
//! SQL Parser for ALTER
|
//! SQL Parser for ALTER
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec;
|
use alloc::{string::ToString, vec};
|
||||||
|
|
||||||
use super::{Parser, ParserError};
|
use super::{Parser, ParserError};
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
AlterConnectorOwner, AlterPolicyOperation, AlterRoleOperation, Expr, Password, ResetConfig,
|
helpers::key_value_options::{KeyValueOptions, KeyValueOptionsDelimiter},
|
||||||
RoleOption, SetConfigValue, Statement,
|
AlterConnectorOwner, AlterPolicyOperation, AlterRoleOperation, AlterUser,
|
||||||
|
AlterUserAddMfaMethodOtp, AlterUserAddRoleDelegation, AlterUserModifyMfaMethod,
|
||||||
|
AlterUserRemoveRoleDelegation, AlterUserSetPolicy, Expr, MfaMethodKind, Password,
|
||||||
|
ResetConfig, RoleOption, SetConfigValue, Statement, UserPolicyKind,
|
||||||
},
|
},
|
||||||
dialect::{MsSqlDialect, PostgreSqlDialect},
|
dialect::{MsSqlDialect, PostgreSqlDialect},
|
||||||
keywords::Keyword,
|
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> {
|
fn parse_mssql_alter_role(&mut self) -> Result<Statement, ParserError> {
|
||||||
let role_name = self.parse_identifier()?;
|
let role_name = self.parse_identifier()?;
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ use IsOptional::*;
|
||||||
|
|
||||||
use crate::ast::helpers::{
|
use crate::ast::helpers::{
|
||||||
key_value_options::{
|
key_value_options::{
|
||||||
KeyValueOption, KeyValueOptionType, KeyValueOptions, KeyValueOptionsDelimiter,
|
KeyValueOption, KeyValueOptionKind, KeyValueOptions, KeyValueOptionsDelimiter,
|
||||||
},
|
},
|
||||||
stmt_create_table::{CreateTableBuilder, CreateTableConfiguration},
|
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> {
|
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 if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||||
let name = self.parse_identifier()?;
|
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 with_tags = self.parse_keyword(Keyword::WITH);
|
||||||
let tags = if self.parse_keyword(Keyword::TAG) {
|
let tags = if self.parse_keyword(Keyword::TAG) {
|
||||||
self.parse_key_value_options(true, &[])?
|
self.parse_key_value_options(true, &[])?.options
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
@ -9277,6 +9279,7 @@ impl<'a> Parser<'a> {
|
||||||
Keyword::CONNECTOR,
|
Keyword::CONNECTOR,
|
||||||
Keyword::ICEBERG,
|
Keyword::ICEBERG,
|
||||||
Keyword::SCHEMA,
|
Keyword::SCHEMA,
|
||||||
|
Keyword::USER,
|
||||||
])?;
|
])?;
|
||||||
match object_type {
|
match object_type {
|
||||||
Keyword::SCHEMA => {
|
Keyword::SCHEMA => {
|
||||||
|
@ -9312,6 +9315,7 @@ impl<'a> Parser<'a> {
|
||||||
Keyword::ROLE => self.parse_alter_role(),
|
Keyword::ROLE => self.parse_alter_role(),
|
||||||
Keyword::POLICY => self.parse_alter_policy(),
|
Keyword::POLICY => self.parse_alter_policy(),
|
||||||
Keyword::CONNECTOR => self.parse_alter_connector(),
|
Keyword::CONNECTOR => self.parse_alter_connector(),
|
||||||
|
Keyword::USER => self.parse_alter_user(),
|
||||||
// unreachable because expect_one_of_keywords used above
|
// unreachable because expect_one_of_keywords used above
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -17488,8 +17492,9 @@ impl<'a> Parser<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
parenthesized: bool,
|
parenthesized: bool,
|
||||||
end_words: &[Keyword],
|
end_words: &[Keyword],
|
||||||
) -> Result<Vec<KeyValueOption>, ParserError> {
|
) -> Result<KeyValueOptions, ParserError> {
|
||||||
let mut options: Vec<KeyValueOption> = Vec::new();
|
let mut options: Vec<KeyValueOption> = Vec::new();
|
||||||
|
let mut delimiter = KeyValueOptionsDelimiter::Space;
|
||||||
if parenthesized {
|
if parenthesized {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
}
|
}
|
||||||
|
@ -17503,9 +17508,12 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Token::EOF => break,
|
Token::EOF => break,
|
||||||
Token::Comma => continue,
|
Token::Comma => {
|
||||||
|
delimiter = KeyValueOptionsDelimiter::Comma;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Token::Word(w) if !end_words.contains(&w.keyword) => {
|
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) => {
|
Token::Word(w) if end_words.contains(&w.keyword) => {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
|
@ -17514,40 +17522,67 @@ impl<'a> Parser<'a> {
|
||||||
_ => return self.expected("another option, EOF, Comma or ')'", self.peek_token()),
|
_ => 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
|
/// Parses a `KEY = VALUE` construct based on the specified key
|
||||||
pub(crate) fn parse_key_value_option(
|
pub(crate) fn parse_key_value_option(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: Word,
|
key: &Word,
|
||||||
) -> Result<KeyValueOption, ParserError> {
|
) -> Result<KeyValueOption, ParserError> {
|
||||||
self.expect_token(&Token::Eq)?;
|
self.expect_token(&Token::Eq)?;
|
||||||
match self.next_token().token {
|
match self.peek_token().token {
|
||||||
Token::SingleQuotedString(value) => Ok(KeyValueOption {
|
Token::SingleQuotedString(_) => Ok(KeyValueOption {
|
||||||
option_name: key.value,
|
option_name: key.value.clone(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(self.parse_value()?.into()),
|
||||||
value,
|
|
||||||
}),
|
}),
|
||||||
Token::Word(word)
|
Token::Word(word)
|
||||||
if word.keyword == Keyword::TRUE || word.keyword == Keyword::FALSE =>
|
if word.keyword == Keyword::TRUE || word.keyword == Keyword::FALSE =>
|
||||||
{
|
{
|
||||||
Ok(KeyValueOption {
|
Ok(KeyValueOption {
|
||||||
option_name: key.value,
|
option_name: key.value.clone(),
|
||||||
option_type: KeyValueOptionType::BOOLEAN,
|
option_value: KeyValueOptionKind::Single(self.parse_value()?.into()),
|
||||||
value: word.value.to_uppercase(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Token::Word(word) => Ok(KeyValueOption {
|
Token::Number(..) => Ok(KeyValueOption {
|
||||||
option_name: key.value,
|
option_name: key.value.clone(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(self.parse_value()?.into()),
|
||||||
value: word.value,
|
|
||||||
}),
|
|
||||||
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()),
|
_ => self.expected("expected option value", self.peek_token()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16761,11 +16761,13 @@ fn parse_create_user() {
|
||||||
verified_stmt("CREATE OR REPLACE USER u1");
|
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");
|
||||||
verified_stmt("CREATE OR REPLACE USER IF NOT EXISTS u1 PASSWORD='secret'");
|
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",
|
||||||
|
"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')");
|
dialects.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')");
|
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 {
|
match create {
|
||||||
Statement::CreateUser(stmt) => {
|
Statement::CreateUser(stmt) => {
|
||||||
assert_eq!(stmt.name, Ident::new("u1"));
|
assert_eq!(stmt.name, Ident::new("u1"));
|
||||||
|
@ -16778,18 +16780,19 @@ fn parse_create_user() {
|
||||||
options: vec![
|
options: vec![
|
||||||
KeyValueOption {
|
KeyValueOption {
|
||||||
option_name: "PASSWORD".to_string(),
|
option_name: "PASSWORD".to_string(),
|
||||||
value: "secret".to_string(),
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
option_type: KeyValueOptionType::STRING
|
"secret".to_string()
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
KeyValueOption {
|
KeyValueOption {
|
||||||
option_name: "MUST_CHANGE_PASSWORD".to_string(),
|
option_name: "MUST_CHANGE_PASSWORD".to_string(),
|
||||||
value: "TRUE".to_string(),
|
option_value: KeyValueOptionKind::Single(Value::Boolean(false)),
|
||||||
option_type: KeyValueOptionType::BOOLEAN
|
|
||||||
},
|
},
|
||||||
KeyValueOption {
|
KeyValueOption {
|
||||||
option_name: "TYPE".to_string(),
|
option_name: "TYPE".to_string(),
|
||||||
value: "SERVICE".to_string(),
|
option_value: KeyValueOptionKind::Single(Value::Placeholder(
|
||||||
option_type: KeyValueOptionType::ENUM
|
"SERVICE".to_string()
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -16802,13 +16805,15 @@ fn parse_create_user() {
|
||||||
options: vec![
|
options: vec![
|
||||||
KeyValueOption {
|
KeyValueOption {
|
||||||
option_name: "t1".to_string(),
|
option_name: "t1".to_string(),
|
||||||
value: "v1".to_string(),
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
option_type: KeyValueOptionType::STRING
|
"v1".to_string()
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
KeyValueOption {
|
KeyValueOption {
|
||||||
option_name: "t2".to_string(),
|
option_name: "t2".to_string(),
|
||||||
value: "v2".to_string(),
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
option_type: KeyValueOptionType::STRING
|
"v2".to_string()
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -17246,3 +17251,211 @@ fn parse_invisible_column() {
|
||||||
_ => panic!("Unexpected statement {stmt}"),
|
_ => 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/')");
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
//! Test SQL syntax specific to Snowflake. The parser based on the
|
//! Test SQL syntax specific to Snowflake. The parser based on the
|
||||||
//! generic dialect is also tested (on the inputs it can handle).
|
//! 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::helpers::stmt_data_loading::{StageLoadSelectItem, StageLoadSelectItemKind};
|
||||||
use sqlparser::ast::*;
|
use sqlparser::ast::*;
|
||||||
use sqlparser::dialect::{Dialect, GenericDialect, SnowflakeDialect};
|
use sqlparser::dialect::{Dialect, GenericDialect, SnowflakeDialect};
|
||||||
|
@ -2116,23 +2116,27 @@ fn test_create_stage_with_stage_params() {
|
||||||
);
|
);
|
||||||
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
||||||
option_name: "AWS_KEY_ID".to_string(),
|
option_name: "AWS_KEY_ID".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "1a2b3c".to_string()
|
"1a2b3c".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
||||||
option_name: "AWS_SECRET_KEY".to_string(),
|
option_name: "AWS_SECRET_KEY".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "4x5y6z".to_string()
|
"4x5y6z".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
||||||
option_name: "MASTER_KEY".to_string(),
|
option_name: "MASTER_KEY".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "key".to_string()
|
"key".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
||||||
option_name: "TYPE".to_string(),
|
option_name: "TYPE".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "AWS_SSE_KMS".to_string()
|
"AWS_SSE_KMS".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2146,7 +2150,7 @@ fn test_create_stage_with_directory_table_params() {
|
||||||
let sql = concat!(
|
let sql = concat!(
|
||||||
"CREATE OR REPLACE STAGE my_ext_stage ",
|
"CREATE OR REPLACE STAGE my_ext_stage ",
|
||||||
"URL='s3://load/files/' ",
|
"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) {
|
match snowflake().verified_stmt(sql) {
|
||||||
|
@ -2156,18 +2160,17 @@ fn test_create_stage_with_directory_table_params() {
|
||||||
} => {
|
} => {
|
||||||
assert!(directory_table_params.options.contains(&KeyValueOption {
|
assert!(directory_table_params.options.contains(&KeyValueOption {
|
||||||
option_name: "ENABLE".to_string(),
|
option_name: "ENABLE".to_string(),
|
||||||
option_type: KeyValueOptionType::BOOLEAN,
|
option_value: KeyValueOptionKind::Single(Value::Boolean(true)),
|
||||||
value: "TRUE".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(directory_table_params.options.contains(&KeyValueOption {
|
assert!(directory_table_params.options.contains(&KeyValueOption {
|
||||||
option_name: "REFRESH_ON_CREATE".to_string(),
|
option_name: "REFRESH_ON_CREATE".to_string(),
|
||||||
option_type: KeyValueOptionType::BOOLEAN,
|
option_value: KeyValueOptionKind::Single(Value::Boolean(false)),
|
||||||
value: "FALSE".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(directory_table_params.options.contains(&KeyValueOption {
|
assert!(directory_table_params.options.contains(&KeyValueOption {
|
||||||
option_name: "NOTIFICATION_INTEGRATION".to_string(),
|
option_name: "NOTIFICATION_INTEGRATION".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "some-string".to_string()
|
"some-string".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2187,18 +2190,17 @@ fn test_create_stage_with_file_format() {
|
||||||
Statement::CreateStage { file_format, .. } => {
|
Statement::CreateStage { file_format, .. } => {
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "COMPRESSION".to_string(),
|
option_name: "COMPRESSION".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder("AUTO".to_string())),
|
||||||
value: "AUTO".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "BINARY_FORMAT".to_string(),
|
option_name: "BINARY_FORMAT".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder("HEX".to_string())),
|
||||||
value: "HEX".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "ESCAPE".to_string(),
|
option_name: "ESCAPE".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: r#"\\"#.to_string()
|
r#"\\"#.to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2214,19 +2216,19 @@ fn test_create_stage_with_copy_options() {
|
||||||
let sql = concat!(
|
let sql = concat!(
|
||||||
"CREATE OR REPLACE STAGE my_ext_stage ",
|
"CREATE OR REPLACE STAGE my_ext_stage ",
|
||||||
"URL='s3://load/files/' ",
|
"URL='s3://load/files/' ",
|
||||||
"COPY_OPTIONS=(ON_ERROR=CONTINUE FORCE=TRUE)"
|
"COPY_OPTIONS=(ON_ERROR=CONTINUE FORCE=true)"
|
||||||
);
|
);
|
||||||
match snowflake().verified_stmt(sql) {
|
match snowflake().verified_stmt(sql) {
|
||||||
Statement::CreateStage { copy_options, .. } => {
|
Statement::CreateStage { copy_options, .. } => {
|
||||||
assert!(copy_options.options.contains(&KeyValueOption {
|
assert!(copy_options.options.contains(&KeyValueOption {
|
||||||
option_name: "ON_ERROR".to_string(),
|
option_name: "ON_ERROR".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder(
|
||||||
value: "CONTINUE".to_string()
|
"CONTINUE".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(copy_options.options.contains(&KeyValueOption {
|
assert!(copy_options.options.contains(&KeyValueOption {
|
||||||
option_name: "FORCE".to_string(),
|
option_name: "FORCE".to_string(),
|
||||||
option_type: KeyValueOptionType::BOOLEAN,
|
option_value: KeyValueOptionKind::Single(Value::Boolean(true)),
|
||||||
value: "TRUE".to_string()
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2357,23 +2359,27 @@ fn test_copy_into_with_stage_params() {
|
||||||
);
|
);
|
||||||
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
||||||
option_name: "AWS_KEY_ID".to_string(),
|
option_name: "AWS_KEY_ID".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "1a2b3c".to_string()
|
"1a2b3c".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
assert!(stage_params.credentials.options.contains(&KeyValueOption {
|
||||||
option_name: "AWS_SECRET_KEY".to_string(),
|
option_name: "AWS_SECRET_KEY".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "4x5y6z".to_string()
|
"4x5y6z".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
||||||
option_name: "MASTER_KEY".to_string(),
|
option_name: "MASTER_KEY".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "key".to_string()
|
"key".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
assert!(stage_params.encryption.options.contains(&KeyValueOption {
|
||||||
option_name: "TYPE".to_string(),
|
option_name: "TYPE".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: "AWS_SSE_KMS".to_string()
|
"AWS_SSE_KMS".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2524,18 +2530,17 @@ fn test_copy_into_file_format() {
|
||||||
Statement::CopyIntoSnowflake { file_format, .. } => {
|
Statement::CopyIntoSnowflake { file_format, .. } => {
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "COMPRESSION".to_string(),
|
option_name: "COMPRESSION".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder("AUTO".to_string())),
|
||||||
value: "AUTO".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "BINARY_FORMAT".to_string(),
|
option_name: "BINARY_FORMAT".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder("HEX".to_string())),
|
||||||
value: "HEX".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "ESCAPE".to_string(),
|
option_name: "ESCAPE".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: r#"\\"#.to_string()
|
r#"\\"#.to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2563,18 +2568,17 @@ fn test_copy_into_file_format() {
|
||||||
Statement::CopyIntoSnowflake { file_format, .. } => {
|
Statement::CopyIntoSnowflake { file_format, .. } => {
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "COMPRESSION".to_string(),
|
option_name: "COMPRESSION".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder("AUTO".to_string())),
|
||||||
value: "AUTO".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "BINARY_FORMAT".to_string(),
|
option_name: "BINARY_FORMAT".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder("HEX".to_string())),
|
||||||
value: "HEX".to_string()
|
|
||||||
}));
|
}));
|
||||||
assert!(file_format.options.contains(&KeyValueOption {
|
assert!(file_format.options.contains(&KeyValueOption {
|
||||||
option_name: "ESCAPE".to_string(),
|
option_name: "ESCAPE".to_string(),
|
||||||
option_type: KeyValueOptionType::STRING,
|
option_value: KeyValueOptionKind::Single(Value::SingleQuotedString(
|
||||||
value: r#"\\"#.to_string()
|
r#"\\"#.to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2588,20 +2592,20 @@ fn test_copy_into_copy_options() {
|
||||||
"FROM 'gcs://mybucket/./../a.csv' ",
|
"FROM 'gcs://mybucket/./../a.csv' ",
|
||||||
"FILES = ('file1.json', 'file2.json') ",
|
"FILES = ('file1.json', 'file2.json') ",
|
||||||
"PATTERN = '.*employees0[1-5].csv.gz' ",
|
"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) {
|
match snowflake().verified_stmt(sql) {
|
||||||
Statement::CopyIntoSnowflake { copy_options, .. } => {
|
Statement::CopyIntoSnowflake { copy_options, .. } => {
|
||||||
assert!(copy_options.options.contains(&KeyValueOption {
|
assert!(copy_options.options.contains(&KeyValueOption {
|
||||||
option_name: "ON_ERROR".to_string(),
|
option_name: "ON_ERROR".to_string(),
|
||||||
option_type: KeyValueOptionType::ENUM,
|
option_value: KeyValueOptionKind::Single(Value::Placeholder(
|
||||||
value: "CONTINUE".to_string()
|
"CONTINUE".to_string()
|
||||||
|
)),
|
||||||
}));
|
}));
|
||||||
assert!(copy_options.options.contains(&KeyValueOption {
|
assert!(copy_options.options.contains(&KeyValueOption {
|
||||||
option_name: "FORCE".to_string(),
|
option_name: "FORCE".to_string(),
|
||||||
option_type: KeyValueOptionType::BOOLEAN,
|
option_value: KeyValueOptionKind::Single(Value::Boolean(true)),
|
||||||
value: "TRUE".to_string()
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -3863,17 +3867,20 @@ fn test_alter_session() {
|
||||||
"sql parser error: expected at least one option"
|
"sql parser error: expected at least one option"
|
||||||
);
|
);
|
||||||
|
|
||||||
snowflake().verified_stmt("ALTER SESSION SET AUTOCOMMIT=TRUE");
|
snowflake().one_statement_parses_to(
|
||||||
snowflake().verified_stmt("ALTER SESSION SET AUTOCOMMIT=FALSE QUERY_TAG='tag'");
|
"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");
|
||||||
snowflake().verified_stmt("ALTER SESSION UNSET AUTOCOMMIT, QUERY_TAG");
|
snowflake().verified_stmt("ALTER SESSION UNSET AUTOCOMMIT, QUERY_TAG");
|
||||||
snowflake().one_statement_parses_to(
|
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'",
|
"ALTER SESSION SET A=false B='tag'",
|
||||||
);
|
);
|
||||||
snowflake().one_statement_parses_to(
|
snowflake().one_statement_parses_to(
|
||||||
"ALTER SESSION SET A=true \nB='tag'",
|
"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");
|
snowflake().one_statement_parses_to("ALTER SESSION UNSET a\nB", "ALTER SESSION UNSET a, B");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue