feat: add ALTER ROLE syntax of PostgreSQL and MS SQL Server (#942)

This commit is contained in:
r.4ntix 2023-08-17 20:05:54 +08:00 committed by GitHub
parent a7d28582e5
commit a49ea1908d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 666 additions and 2 deletions

195
src/ast/dcl.rs Normal file
View file

@ -0,0 +1,195 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! AST types specific to GRANT/REVOKE/ROLE variants of [`Statement`](crate::ast::Statement)
//! (commonly referred to as Data Control Language, or DCL)
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};
use super::{Expr, Ident, Password};
use crate::ast::{display_separated, ObjectName};
/// An option in `ROLE` statement.
///
/// <https://www.postgresql.org/docs/current/sql-createrole.html>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum RoleOption {
BypassRLS(bool),
ConnectionLimit(Expr),
CreateDB(bool),
CreateRole(bool),
Inherit(bool),
Login(bool),
Password(Password),
Replication(bool),
SuperUser(bool),
ValidUntil(Expr),
}
impl fmt::Display for RoleOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RoleOption::BypassRLS(value) => {
write!(f, "{}", if *value { "BYPASSRLS" } else { "NOBYPASSRLS" })
}
RoleOption::ConnectionLimit(expr) => {
write!(f, "CONNECTION LIMIT {expr}")
}
RoleOption::CreateDB(value) => {
write!(f, "{}", if *value { "CREATEDB" } else { "NOCREATEDB" })
}
RoleOption::CreateRole(value) => {
write!(f, "{}", if *value { "CREATEROLE" } else { "NOCREATEROLE" })
}
RoleOption::Inherit(value) => {
write!(f, "{}", if *value { "INHERIT" } else { "NOINHERIT" })
}
RoleOption::Login(value) => {
write!(f, "{}", if *value { "LOGIN" } else { "NOLOGIN" })
}
RoleOption::Password(password) => match password {
Password::Password(expr) => write!(f, "PASSWORD {expr}"),
Password::NullPassword => write!(f, "PASSWORD NULL"),
},
RoleOption::Replication(value) => {
write!(
f,
"{}",
if *value {
"REPLICATION"
} else {
"NOREPLICATION"
}
)
}
RoleOption::SuperUser(value) => {
write!(f, "{}", if *value { "SUPERUSER" } else { "NOSUPERUSER" })
}
RoleOption::ValidUntil(expr) => {
write!(f, "VALID UNTIL {expr}")
}
}
}
}
/// SET config value option:
/// * SET `configuration_parameter` { TO | = } { `value` | DEFAULT }
/// * SET `configuration_parameter` FROM CURRENT
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum SetConfigValue {
Default,
FromCurrent,
Value(Expr),
}
/// RESET config option:
/// * RESET `configuration_parameter`
/// * RESET ALL
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum ResetConfig {
ALL,
ConfigName(ObjectName),
}
/// An `ALTER ROLE` (`Statement::AlterRole`) operation
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum AlterRoleOperation {
/// Generic
RenameRole {
role_name: Ident,
},
/// MS SQL Server
/// <https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-role-transact-sql>
AddMember {
member_name: Ident,
},
DropMember {
member_name: Ident,
},
/// PostgreSQL
/// <https://www.postgresql.org/docs/current/sql-alterrole.html>
WithOptions {
options: Vec<RoleOption>,
},
Set {
config_name: ObjectName,
config_value: SetConfigValue,
in_database: Option<ObjectName>,
},
Reset {
config_name: ResetConfig,
in_database: Option<ObjectName>,
},
}
impl fmt::Display for AlterRoleOperation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AlterRoleOperation::RenameRole { role_name } => {
write!(f, "RENAME TO {role_name}")
}
AlterRoleOperation::AddMember { member_name } => {
write!(f, "ADD MEMBER {member_name}")
}
AlterRoleOperation::DropMember { member_name } => {
write!(f, "DROP MEMBER {member_name}")
}
AlterRoleOperation::WithOptions { options } => {
write!(f, "WITH {}", display_separated(options, " "))
}
AlterRoleOperation::Set {
config_name,
config_value,
in_database,
} => {
if let Some(database_name) = in_database {
write!(f, "IN DATABASE {} ", database_name)?;
}
match config_value {
SetConfigValue::Default => write!(f, "SET {config_name} TO DEFAULT"),
SetConfigValue::FromCurrent => write!(f, "SET {config_name} FROM CURRENT"),
SetConfigValue::Value(expr) => write!(f, "SET {config_name} TO {expr}"),
}
}
AlterRoleOperation::Reset {
config_name,
in_database,
} => {
if let Some(database_name) = in_database {
write!(f, "IN DATABASE {} ", database_name)?;
}
match config_name {
ResetConfig::ALL => write!(f, "RESET ALL"),
ResetConfig::ConfigName(name) => write!(f, "RESET {name}"),
}
}
}
}
}

View file

@ -28,6 +28,7 @@ use sqlparser_derive::{Visit, VisitMut};
pub use self::data_type::{
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, TimezoneInfo,
};
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue};
pub use self::ddl::{
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ProcedureParam, ReferentialAction,
@ -52,6 +53,7 @@ use crate::ast::helpers::stmt_data_loading::{
pub use visitor::*;
mod data_type;
mod dcl;
mod ddl;
pub mod helpers;
mod operator;
@ -1398,6 +1400,11 @@ pub enum Statement {
query: Box<Query>,
with_options: Vec<SqlOption>,
},
/// ALTER ROLE
AlterRole {
name: Ident,
operation: AlterRoleOperation,
},
/// DROP
Drop {
/// The type of the object to drop: TABLE, VIEW, etc.
@ -2585,6 +2592,9 @@ impl fmt::Display for Statement {
}
write!(f, " AS {query}")
}
Statement::AlterRole { name, operation } => {
write!(f, "ALTER ROLE {name} {operation}")
}
Statement::Drop {
object_type,
if_exists,

View file

@ -508,6 +508,7 @@ define_keywords!(
REPEATABLE,
REPLACE,
REPLICATION,
RESET,
RESTRICT,
RESULT,
RETAIN,

204
src/parser/alter.rs Normal file
View file

@ -0,0 +1,204 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! SQL Parser for ALTER
#[cfg(not(feature = "std"))]
use alloc::vec;
use super::{Parser, ParserError};
use crate::{
ast::{AlterRoleOperation, Expr, Password, ResetConfig, RoleOption, SetConfigValue, Statement},
dialect::{MsSqlDialect, PostgreSqlDialect},
keywords::Keyword,
tokenizer::Token,
};
impl<'a> Parser<'a> {
pub fn parse_alter_role(&mut self) -> Result<Statement, ParserError> {
if dialect_of!(self is PostgreSqlDialect) {
return self.parse_pg_alter_role();
} else if dialect_of!(self is MsSqlDialect) {
return self.parse_mssql_alter_role();
}
Err(ParserError::ParserError(
"ALTER ROLE is only support for PostgreSqlDialect, MsSqlDialect".into(),
))
}
fn parse_mssql_alter_role(&mut self) -> Result<Statement, ParserError> {
let role_name = self.parse_identifier()?;
let operation = if self.parse_keywords(&[Keyword::ADD, Keyword::MEMBER]) {
let member_name = self.parse_identifier()?;
AlterRoleOperation::AddMember { member_name }
} else if self.parse_keywords(&[Keyword::DROP, Keyword::MEMBER]) {
let member_name = self.parse_identifier()?;
AlterRoleOperation::DropMember { member_name }
} else if self.parse_keywords(&[Keyword::WITH, Keyword::NAME]) {
if self.consume_token(&Token::Eq) {
let role_name = self.parse_identifier()?;
AlterRoleOperation::RenameRole { role_name }
} else {
return self.expected("= after WITH NAME ", self.peek_token());
}
} else {
return self.expected("'ADD' or 'DROP' or 'WITH NAME'", self.peek_token());
};
Ok(Statement::AlterRole {
name: role_name,
operation,
})
}
fn parse_pg_alter_role(&mut self) -> Result<Statement, ParserError> {
let role_name = self.parse_identifier()?;
// [ IN DATABASE _`database_name`_ ]
let in_database = if self.parse_keywords(&[Keyword::IN, Keyword::DATABASE]) {
self.parse_object_name().ok()
} else {
None
};
let operation = if self.parse_keyword(Keyword::RENAME) {
if self.parse_keyword(Keyword::TO) {
let role_name = self.parse_identifier()?;
AlterRoleOperation::RenameRole { role_name }
} else {
return self.expected("TO after RENAME", self.peek_token());
}
// SET
} else if self.parse_keyword(Keyword::SET) {
let config_name = self.parse_object_name()?;
// FROM CURRENT
if self.parse_keywords(&[Keyword::FROM, Keyword::CURRENT]) {
AlterRoleOperation::Set {
config_name,
config_value: SetConfigValue::FromCurrent,
in_database,
}
// { TO | = } { value | DEFAULT }
} else if self.consume_token(&Token::Eq) || self.parse_keyword(Keyword::TO) {
if self.parse_keyword(Keyword::DEFAULT) {
AlterRoleOperation::Set {
config_name,
config_value: SetConfigValue::Default,
in_database,
}
} else if let Ok(expr) = self.parse_expr() {
AlterRoleOperation::Set {
config_name,
config_value: SetConfigValue::Value(expr),
in_database,
}
} else {
self.expected("config value", self.peek_token())?
}
} else {
self.expected("'TO' or '=' or 'FROM CURRENT'", self.peek_token())?
}
// RESET
} else if self.parse_keyword(Keyword::RESET) {
if self.parse_keyword(Keyword::ALL) {
AlterRoleOperation::Reset {
config_name: ResetConfig::ALL,
in_database,
}
} else {
let config_name = self.parse_object_name()?;
AlterRoleOperation::Reset {
config_name: ResetConfig::ConfigName(config_name),
in_database,
}
}
// option
} else {
// [ WITH ]
let _ = self.parse_keyword(Keyword::WITH);
// option
let mut options = vec![];
while let Some(opt) = self.maybe_parse(|parser| parser.parse_pg_role_option()) {
options.push(opt);
}
// check option
if options.is_empty() {
return self.expected("option", self.peek_token())?;
}
AlterRoleOperation::WithOptions { options }
};
Ok(Statement::AlterRole {
name: role_name,
operation,
})
}
fn parse_pg_role_option(&mut self) -> Result<RoleOption, ParserError> {
let option = match self.parse_one_of_keywords(&[
Keyword::BYPASSRLS,
Keyword::NOBYPASSRLS,
Keyword::CONNECTION,
Keyword::CREATEDB,
Keyword::NOCREATEDB,
Keyword::CREATEROLE,
Keyword::NOCREATEROLE,
Keyword::INHERIT,
Keyword::NOINHERIT,
Keyword::LOGIN,
Keyword::NOLOGIN,
Keyword::PASSWORD,
Keyword::REPLICATION,
Keyword::NOREPLICATION,
Keyword::SUPERUSER,
Keyword::NOSUPERUSER,
Keyword::VALID,
]) {
Some(Keyword::BYPASSRLS) => RoleOption::BypassRLS(true),
Some(Keyword::NOBYPASSRLS) => RoleOption::BypassRLS(false),
Some(Keyword::CONNECTION) => {
self.expect_keyword(Keyword::LIMIT)?;
RoleOption::ConnectionLimit(Expr::Value(self.parse_number_value()?))
}
Some(Keyword::CREATEDB) => RoleOption::CreateDB(true),
Some(Keyword::NOCREATEDB) => RoleOption::CreateDB(false),
Some(Keyword::CREATEROLE) => RoleOption::CreateRole(true),
Some(Keyword::NOCREATEROLE) => RoleOption::CreateRole(false),
Some(Keyword::INHERIT) => RoleOption::Inherit(true),
Some(Keyword::NOINHERIT) => RoleOption::Inherit(false),
Some(Keyword::LOGIN) => RoleOption::Login(true),
Some(Keyword::NOLOGIN) => RoleOption::Login(false),
Some(Keyword::PASSWORD) => {
let password = if self.parse_keyword(Keyword::NULL) {
Password::NullPassword
} else {
Password::Password(Expr::Value(self.parse_value()?))
};
RoleOption::Password(password)
}
Some(Keyword::REPLICATION) => RoleOption::Replication(true),
Some(Keyword::NOREPLICATION) => RoleOption::Replication(false),
Some(Keyword::SUPERUSER) => RoleOption::SuperUser(true),
Some(Keyword::NOSUPERUSER) => RoleOption::SuperUser(false),
Some(Keyword::VALID) => {
self.expect_keyword(Keyword::UNTIL)?;
RoleOption::ValidUntil(Expr::Value(self.parse_value()?))
}
_ => self.expected("option", self.peek_token())?,
};
Ok(option)
}
}

View file

@ -33,6 +33,8 @@ use crate::dialect::*;
use crate::keywords::{self, Keyword};
use crate::tokenizer::*;
mod alter;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParserError {
TokenizerError(String),
@ -3990,8 +3992,12 @@ impl<'a> Parser<'a> {
}
pub fn parse_alter(&mut self) -> Result<Statement, ParserError> {
let object_type =
self.expect_one_of_keywords(&[Keyword::VIEW, Keyword::TABLE, Keyword::INDEX])?;
let object_type = self.expect_one_of_keywords(&[
Keyword::VIEW,
Keyword::TABLE,
Keyword::INDEX,
Keyword::ROLE,
])?;
match object_type {
Keyword::VIEW => self.parse_alter_view(),
Keyword::TABLE => {
@ -4186,6 +4192,7 @@ impl<'a> Parser<'a> {
operation,
})
}
Keyword::ROLE => self.parse_alter_role(),
// unreachable because expect_one_of_keywords used above
_ => unreachable!(),
}

View file

@ -216,6 +216,60 @@ fn parse_mssql_create_role() {
}
}
#[test]
fn parse_alter_role() {
let sql = "ALTER ROLE old_name WITH NAME = new_name";
assert_eq!(
ms().parse_sql_statements(sql).unwrap(),
[Statement::AlterRole {
name: Ident {
value: "old_name".into(),
quote_style: None
},
operation: AlterRoleOperation::RenameRole {
role_name: Ident {
value: "new_name".into(),
quote_style: None
}
},
}]
);
let sql = "ALTER ROLE role_name ADD MEMBER new_member";
assert_eq!(
ms().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::AddMember {
member_name: Ident {
value: "new_member".into(),
quote_style: None
}
},
}
);
let sql = "ALTER ROLE role_name DROP MEMBER old_member";
assert_eq!(
ms().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::DropMember {
member_name: Ident {
value: "old_member".into(),
quote_style: None
}
},
}
);
}
#[test]
fn parse_delimited_identifiers() {
// check that quoted identifiers in any position remain quoted after serialization

View file

@ -2447,6 +2447,199 @@ fn parse_create_role() {
}
}
#[test]
fn parse_alter_role() {
let sql = "ALTER ROLE old_name RENAME TO new_name";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "old_name".into(),
quote_style: None
},
operation: AlterRoleOperation::RenameRole {
role_name: Ident {
value: "new_name".into(),
quote_style: None
}
},
}
);
let sql = "ALTER ROLE role_name WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 100 PASSWORD 'abcdef' VALID UNTIL '2025-01-01'";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::WithOptions {
options: vec![
RoleOption::SuperUser(true),
RoleOption::CreateDB(true),
RoleOption::CreateRole(true),
RoleOption::Inherit(true),
RoleOption::Login(true),
RoleOption::Replication(true),
RoleOption::BypassRLS(true),
RoleOption::ConnectionLimit(Expr::Value(number("100"))),
RoleOption::Password({
Password::Password(Expr::Value(Value::SingleQuotedString("abcdef".into())))
}),
RoleOption::ValidUntil(Expr::Value(Value::SingleQuotedString(
"2025-01-01".into(),
)))
]
},
}
);
let sql = "ALTER ROLE role_name WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS PASSWORD NULL";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::WithOptions {
options: vec![
RoleOption::SuperUser(false),
RoleOption::CreateDB(false),
RoleOption::CreateRole(false),
RoleOption::Inherit(false),
RoleOption::Login(false),
RoleOption::Replication(false),
RoleOption::BypassRLS(false),
RoleOption::Password(Password::NullPassword),
]
},
}
);
let sql = "ALTER ROLE role_name SET maintenance_work_mem FROM CURRENT";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::Set {
config_name: ObjectName(vec![Ident {
value: "maintenance_work_mem".into(),
quote_style: None
}]),
config_value: SetConfigValue::FromCurrent,
in_database: None
},
}
);
let sql = "ALTER ROLE role_name IN DATABASE database_name SET maintenance_work_mem = 100000";
assert_eq!(
pg().parse_sql_statements(sql).unwrap(),
[Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::Set {
config_name: ObjectName(vec![Ident {
value: "maintenance_work_mem".into(),
quote_style: None
}]),
config_value: SetConfigValue::Value(Expr::Value(number("100000"))),
in_database: Some(ObjectName(vec![Ident {
value: "database_name".into(),
quote_style: None
}]))
},
}]
);
let sql = "ALTER ROLE role_name IN DATABASE database_name SET maintenance_work_mem TO 100000";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::Set {
config_name: ObjectName(vec![Ident {
value: "maintenance_work_mem".into(),
quote_style: None
}]),
config_value: SetConfigValue::Value(Expr::Value(number("100000"))),
in_database: Some(ObjectName(vec![Ident {
value: "database_name".into(),
quote_style: None
}]))
},
}
);
let sql = "ALTER ROLE role_name IN DATABASE database_name SET maintenance_work_mem TO DEFAULT";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::Set {
config_name: ObjectName(vec![Ident {
value: "maintenance_work_mem".into(),
quote_style: None
}]),
config_value: SetConfigValue::Default,
in_database: Some(ObjectName(vec![Ident {
value: "database_name".into(),
quote_style: None
}]))
},
}
);
let sql = "ALTER ROLE role_name RESET ALL";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::Reset {
config_name: ResetConfig::ALL,
in_database: None
},
}
);
let sql = "ALTER ROLE role_name IN DATABASE database_name RESET maintenance_work_mem";
assert_eq!(
pg().verified_stmt(sql),
Statement::AlterRole {
name: Ident {
value: "role_name".into(),
quote_style: None
},
operation: AlterRoleOperation::Reset {
config_name: ResetConfig::ConfigName(ObjectName(vec![Ident {
value: "maintenance_work_mem".into(),
quote_style: None
}])),
in_database: Some(ObjectName(vec![Ident {
value: "database_name".into(),
quote_style: None
}]))
},
}
);
}
#[test]
fn parse_delimited_identifiers() {
// check that quoted identifiers in any position remain quoted after serialization