mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-08 13:10:20 +00:00
Snowflake: Add support for CREATE USER
(#1950)
This commit is contained in:
parent
492184643a
commit
2ed2cbe291
6 changed files with 281 additions and 85 deletions
|
@ -31,11 +31,22 @@ use serde::{Deserialize, Serialize};
|
|||
#[cfg(feature = "visitor")]
|
||||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use crate::ast::display_separated;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct KeyValueOptions {
|
||||
pub options: Vec<KeyValueOption>,
|
||||
pub delimiter: 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 KeyValueOptionsDelimiter {
|
||||
Space,
|
||||
Comma,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -59,18 +70,11 @@ pub struct KeyValueOption {
|
|||
|
||||
impl fmt::Display for KeyValueOptions {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if !self.options.is_empty() {
|
||||
let mut first = false;
|
||||
for option in &self.options {
|
||||
if !first {
|
||||
first = true;
|
||||
} else {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
write!(f, "{option}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
let sep = match self.delimiter {
|
||||
KeyValueOptionsDelimiter::Space => " ",
|
||||
KeyValueOptionsDelimiter::Comma => ", ",
|
||||
};
|
||||
write!(f, "{}", display_separated(&self.options, sep))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4355,6 +4355,11 @@ pub enum Statement {
|
|||
///
|
||||
/// See [ReturnStatement]
|
||||
Return(ReturnStatement),
|
||||
/// ```sql
|
||||
/// CREATE [OR REPLACE] USER <user> [IF NOT EXISTS]
|
||||
/// ```
|
||||
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-user)
|
||||
CreateUser(CreateUser),
|
||||
}
|
||||
|
||||
/// ```sql
|
||||
|
@ -6193,6 +6198,7 @@ impl fmt::Display for Statement {
|
|||
Statement::Return(r) => write!(f, "{r}"),
|
||||
Statement::List(command) => write!(f, "LIST {command}"),
|
||||
Statement::Remove(command) => write!(f, "REMOVE {command}"),
|
||||
Statement::CreateUser(s) => write!(f, "{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10125,6 +10131,50 @@ impl fmt::Display for MemberOf {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a user
|
||||
///
|
||||
/// Syntax:
|
||||
/// ```sql
|
||||
/// CREATE [OR REPLACE] USER [IF NOT EXISTS] <name> [OPTIONS]
|
||||
/// ```
|
||||
///
|
||||
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-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 CreateUser {
|
||||
pub or_replace: bool,
|
||||
pub if_not_exists: bool,
|
||||
pub name: Ident,
|
||||
pub options: KeyValueOptions,
|
||||
pub with_tags: bool,
|
||||
pub tags: KeyValueOptions,
|
||||
}
|
||||
|
||||
impl fmt::Display for CreateUser {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "CREATE")?;
|
||||
if self.or_replace {
|
||||
write!(f, " OR REPLACE")?;
|
||||
}
|
||||
write!(f, " USER")?;
|
||||
if self.if_not_exists {
|
||||
write!(f, " IF NOT EXISTS")?;
|
||||
}
|
||||
write!(f, " {}", self.name)?;
|
||||
if !self.options.options.is_empty() {
|
||||
write!(f, " {}", self.options)?;
|
||||
}
|
||||
if !self.tags.options.is_empty() {
|
||||
if self.with_tags {
|
||||
write!(f, " WITH")?;
|
||||
}
|
||||
write!(f, " TAG ({})", self.tags)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tokenizer::Location;
|
||||
|
|
|
@ -531,6 +531,7 @@ impl Spanned for Statement {
|
|||
Statement::Print { .. } => Span::empty(),
|
||||
Statement::Return { .. } => Span::empty(),
|
||||
Statement::List(..) | Statement::Remove(..) => Span::empty(),
|
||||
Statement::CreateUser(..) => Span::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use crate::alloc::string::ToString;
|
||||
use crate::ast::helpers::key_value_options::{KeyValueOption, KeyValueOptionType, KeyValueOptions};
|
||||
use crate::ast::helpers::key_value_options::{
|
||||
KeyValueOption, KeyValueOptionType, KeyValueOptions, KeyValueOptionsDelimiter,
|
||||
};
|
||||
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
||||
use crate::ast::helpers::stmt_data_loading::{
|
||||
FileStagingCommand, StageLoadSelectItem, StageLoadSelectItemKind, StageParamsObject,
|
||||
|
@ -31,7 +33,7 @@ use crate::ast::{
|
|||
use crate::dialect::{Dialect, Precedence};
|
||||
use crate::keywords::Keyword;
|
||||
use crate::parser::{IsOptional, Parser, ParserError};
|
||||
use crate::tokenizer::{Token, Word};
|
||||
use crate::tokenizer::Token;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(not(feature = "std"))]
|
||||
|
@ -516,6 +518,7 @@ fn parse_alter_session(parser: &mut Parser, set: bool) -> Result<Statement, Pars
|
|||
set,
|
||||
session_params: KeyValueOptions {
|
||||
options: session_options,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -777,19 +780,19 @@ pub fn parse_create_stage(
|
|||
// [ directoryTableParams ]
|
||||
if parser.parse_keyword(Keyword::DIRECTORY) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
directory_table_params = parse_parentheses_options(parser)?;
|
||||
directory_table_params = parser.parse_key_value_options(true, &[])?;
|
||||
}
|
||||
|
||||
// [ file_format]
|
||||
if parser.parse_keyword(Keyword::FILE_FORMAT) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
file_format = parse_parentheses_options(parser)?;
|
||||
file_format = parser.parse_key_value_options(true, &[])?;
|
||||
}
|
||||
|
||||
// [ copy_options ]
|
||||
if parser.parse_keyword(Keyword::COPY_OPTIONS) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
copy_options = parse_parentheses_options(parser)?;
|
||||
copy_options = parser.parse_key_value_options(true, &[])?;
|
||||
}
|
||||
|
||||
// [ comment ]
|
||||
|
@ -806,12 +809,15 @@ pub fn parse_create_stage(
|
|||
stage_params,
|
||||
directory_table_params: KeyValueOptions {
|
||||
options: directory_table_params,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
file_format: KeyValueOptions {
|
||||
options: file_format,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
copy_options: KeyValueOptions {
|
||||
options: copy_options,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
comment,
|
||||
})
|
||||
|
@ -879,10 +885,16 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
|||
let mut from_stage = None;
|
||||
let mut stage_params = StageParamsObject {
|
||||
url: None,
|
||||
encryption: KeyValueOptions { options: vec![] },
|
||||
encryption: KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
endpoint: None,
|
||||
storage_integration: None,
|
||||
credentials: KeyValueOptions { options: vec![] },
|
||||
credentials: KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
};
|
||||
let mut from_query = None;
|
||||
let mut partition = None;
|
||||
|
@ -944,7 +956,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 = parse_parentheses_options(parser)?;
|
||||
file_format = parser.parse_key_value_options(true, &[])?;
|
||||
// PARTITION BY
|
||||
} else if parser.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
||||
partition = Some(Box::new(parser.parse_expr()?))
|
||||
|
@ -982,14 +994,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 = parse_parentheses_options(parser)?;
|
||||
copy_options = parser.parse_key_value_options(true, &[])?;
|
||||
} 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(parse_option(parser, key)?),
|
||||
Token::Word(key) => copy_options.push(parser.parse_key_value_option(key)?),
|
||||
_ => return parser.expected("another copy option, ; or EOF'", parser.peek_token()),
|
||||
}
|
||||
}
|
||||
|
@ -1008,9 +1020,11 @@ pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
|
|||
pattern,
|
||||
file_format: KeyValueOptions {
|
||||
options: file_format,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
copy_options: KeyValueOptions {
|
||||
options: copy_options,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
validation_mode,
|
||||
partition,
|
||||
|
@ -1110,8 +1124,14 @@ fn parse_select_item_for_data_load(
|
|||
|
||||
fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserError> {
|
||||
let (mut url, mut storage_integration, mut endpoint) = (None, None, None);
|
||||
let mut encryption: KeyValueOptions = KeyValueOptions { options: vec![] };
|
||||
let mut credentials: KeyValueOptions = KeyValueOptions { options: vec![] };
|
||||
let mut encryption: KeyValueOptions = KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
};
|
||||
let mut credentials: KeyValueOptions = KeyValueOptions {
|
||||
options: vec![],
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
};
|
||||
|
||||
// URL
|
||||
if parser.parse_keyword(Keyword::URL) {
|
||||
|
@ -1141,7 +1161,8 @@ fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserEr
|
|||
if parser.parse_keyword(Keyword::CREDENTIALS) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
credentials = KeyValueOptions {
|
||||
options: parse_parentheses_options(parser)?,
|
||||
options: parser.parse_key_value_options(true, &[])?,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1149,7 +1170,8 @@ fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserEr
|
|||
if parser.parse_keyword(Keyword::ENCRYPTION) {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
encryption = KeyValueOptions {
|
||||
options: parse_parentheses_options(parser)?,
|
||||
options: parser.parse_key_value_options(true, &[])?,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1183,7 +1205,7 @@ fn parse_session_options(
|
|||
Token::Word(key) => {
|
||||
parser.advance_token();
|
||||
if set {
|
||||
let option = parse_option(parser, key)?;
|
||||
let option = parser.parse_key_value_option(key)?;
|
||||
options.push(option);
|
||||
} else {
|
||||
options.push(KeyValueOption {
|
||||
|
@ -1207,63 +1229,6 @@ fn parse_session_options(
|
|||
}
|
||||
}
|
||||
|
||||
/// Parses options provided within parentheses like:
|
||||
/// ( ENABLE = { TRUE | FALSE }
|
||||
/// [ AUTO_REFRESH = { TRUE | FALSE } ]
|
||||
/// [ REFRESH_ON_CREATE = { TRUE | FALSE } ]
|
||||
/// [ NOTIFICATION_INTEGRATION = '<notification_integration_name>' ] )
|
||||
///
|
||||
fn parse_parentheses_options(parser: &mut Parser) -> Result<Vec<KeyValueOption>, ParserError> {
|
||||
let mut options: Vec<KeyValueOption> = Vec::new();
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
loop {
|
||||
match parser.next_token().token {
|
||||
Token::RParen => break,
|
||||
Token::Comma => continue,
|
||||
Token::Word(key) => options.push(parse_option(parser, key)?),
|
||||
_ => return parser.expected("another option or ')'", parser.peek_token()),
|
||||
};
|
||||
}
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
/// Parses a `KEY = VALUE` construct based on the specified key
|
||||
fn parse_option(parser: &mut Parser, key: Word) -> Result<KeyValueOption, ParserError> {
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
if parser.parse_keyword(Keyword::TRUE) {
|
||||
Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::BOOLEAN,
|
||||
value: "TRUE".to_string(),
|
||||
})
|
||||
} else if parser.parse_keyword(Keyword::FALSE) {
|
||||
Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::BOOLEAN,
|
||||
value: "FALSE".to_string(),
|
||||
})
|
||||
} else {
|
||||
match parser.next_token().token {
|
||||
Token::SingleQuotedString(value) => Ok(KeyValueOption {
|
||||
option_name: key.value,
|
||||
option_type: KeyValueOptionType::STRING,
|
||||
value,
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
_ => parser.expected("expected option value", parser.peek_token()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsing a property of identity or autoincrement column option
|
||||
/// Syntax:
|
||||
/// ```sql
|
||||
|
|
|
@ -32,7 +32,12 @@ use recursion::RecursionCounter;
|
|||
use IsLateral::*;
|
||||
use IsOptional::*;
|
||||
|
||||
use crate::ast::helpers::stmt_create_table::{CreateTableBuilder, CreateTableConfiguration};
|
||||
use crate::ast::helpers::{
|
||||
key_value_options::{
|
||||
KeyValueOption, KeyValueOptionType, KeyValueOptions, KeyValueOptionsDelimiter,
|
||||
},
|
||||
stmt_create_table::{CreateTableBuilder, CreateTableConfiguration},
|
||||
};
|
||||
use crate::ast::Statement::CreatePolicy;
|
||||
use crate::ast::*;
|
||||
use crate::dialect::*;
|
||||
|
@ -4680,6 +4685,8 @@ impl<'a> Parser<'a> {
|
|||
self.parse_create_macro(or_replace, temporary)
|
||||
} else if self.parse_keyword(Keyword::SECRET) {
|
||||
self.parse_create_secret(or_replace, temporary, persistent)
|
||||
} else if self.parse_keyword(Keyword::USER) {
|
||||
self.parse_create_user(or_replace)
|
||||
} else if or_replace {
|
||||
self.expected(
|
||||
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE",
|
||||
|
@ -4714,6 +4721,32 @@ 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 with_tags = self.parse_keyword(Keyword::WITH);
|
||||
let tags = if self.parse_keyword(Keyword::TAG) {
|
||||
self.parse_key_value_options(true, &[])?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
Ok(Statement::CreateUser(CreateUser {
|
||||
or_replace,
|
||||
if_not_exists,
|
||||
name,
|
||||
options: KeyValueOptions {
|
||||
options,
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
},
|
||||
with_tags,
|
||||
tags: KeyValueOptions {
|
||||
options: tags,
|
||||
delimiter: KeyValueOptionsDelimiter::Comma,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
/// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details.
|
||||
pub fn parse_create_secret(
|
||||
&mut self,
|
||||
|
@ -16612,6 +16645,78 @@ impl<'a> Parser<'a> {
|
|||
pub(crate) fn in_column_definition_state(&self) -> bool {
|
||||
matches!(self.state, ColumnDefinition)
|
||||
}
|
||||
|
||||
/// Parses options provided in key-value format.
|
||||
///
|
||||
/// * `parenthesized` - true if the options are enclosed in parenthesis
|
||||
/// * `end_words` - a list of keywords that any of them indicates the end of the options section
|
||||
pub(crate) fn parse_key_value_options(
|
||||
&mut self,
|
||||
parenthesized: bool,
|
||||
end_words: &[Keyword],
|
||||
) -> Result<Vec<KeyValueOption>, ParserError> {
|
||||
let mut options: Vec<KeyValueOption> = Vec::new();
|
||||
if parenthesized {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
}
|
||||
loop {
|
||||
match self.next_token().token {
|
||||
Token::RParen => {
|
||||
if parenthesized {
|
||||
break;
|
||||
} else {
|
||||
return self.expected(" another option or EOF", self.peek_token());
|
||||
}
|
||||
}
|
||||
Token::EOF => break,
|
||||
Token::Comma => continue,
|
||||
Token::Word(w) if !end_words.contains(&w.keyword) => {
|
||||
options.push(self.parse_key_value_option(w)?)
|
||||
}
|
||||
Token::Word(w) if end_words.contains(&w.keyword) => {
|
||||
self.prev_token();
|
||||
break;
|
||||
}
|
||||
_ => return self.expected("another option, EOF, Comma or ')'", self.peek_token()),
|
||||
};
|
||||
}
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
/// Parses a `KEY = VALUE` construct based on the specified key
|
||||
pub(crate) fn parse_key_value_option(
|
||||
&mut self,
|
||||
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,
|
||||
}),
|
||||
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(),
|
||||
})
|
||||
}
|
||||
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,
|
||||
}),
|
||||
_ => self.expected("expected option value", self.peek_token()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_prefixed_expr(expr: Expr, prefix: Option<Ident>) -> Expr {
|
||||
|
|
|
@ -27,6 +27,8 @@ extern crate core;
|
|||
|
||||
use helpers::attached_token::AttachedToken;
|
||||
use matches::assert_matches;
|
||||
use sqlparser::ast::helpers::key_value_options::*;
|
||||
use sqlparser::ast::helpers::key_value_options::{KeyValueOptions, KeyValueOptionsDelimiter};
|
||||
use sqlparser::ast::SelectItem::UnnamedExpr;
|
||||
use sqlparser::ast::TableFactor::{Pivot, Unpivot};
|
||||
use sqlparser::ast::*;
|
||||
|
@ -16256,3 +16258,72 @@ fn parse_notnull() {
|
|||
// for unsupported dialects, parsing should stop at `NOT NULL`
|
||||
notnull_unsupported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_user() {
|
||||
let create = verified_stmt("CREATE USER u1");
|
||||
match create {
|
||||
Statement::CreateUser(stmt) => {
|
||||
assert_eq!(stmt.name, Ident::new("u1"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
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(
|
||||
"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')");
|
||||
match create {
|
||||
Statement::CreateUser(stmt) => {
|
||||
assert_eq!(stmt.name, Ident::new("u1"));
|
||||
assert_eq!(stmt.or_replace, true);
|
||||
assert_eq!(stmt.if_not_exists, true);
|
||||
assert_eq!(
|
||||
stmt.options,
|
||||
KeyValueOptions {
|
||||
delimiter: KeyValueOptionsDelimiter::Space,
|
||||
options: vec![
|
||||
KeyValueOption {
|
||||
option_name: "PASSWORD".to_string(),
|
||||
value: "secret".to_string(),
|
||||
option_type: KeyValueOptionType::STRING
|
||||
},
|
||||
KeyValueOption {
|
||||
option_name: "MUST_CHANGE_PASSWORD".to_string(),
|
||||
value: "TRUE".to_string(),
|
||||
option_type: KeyValueOptionType::BOOLEAN
|
||||
},
|
||||
KeyValueOption {
|
||||
option_name: "TYPE".to_string(),
|
||||
value: "SERVICE".to_string(),
|
||||
option_type: KeyValueOptionType::ENUM
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
assert_eq!(stmt.with_tags, true);
|
||||
assert_eq!(
|
||||
stmt.tags,
|
||||
KeyValueOptions {
|
||||
delimiter: KeyValueOptionsDelimiter::Comma,
|
||||
options: vec![
|
||||
KeyValueOption {
|
||||
option_name: "t1".to_string(),
|
||||
value: "v1".to_string(),
|
||||
option_type: KeyValueOptionType::STRING
|
||||
},
|
||||
KeyValueOption {
|
||||
option_name: "t2".to_string(),
|
||||
value: "v2".to_string(),
|
||||
option_type: KeyValueOptionType::STRING
|
||||
},
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue