diff --git a/common/ast/src/ast/statement.rs b/common/ast/src/ast/statement.rs index 47150ec164b71..b1641ebcc5674 100644 --- a/common/ast/src/ast/statement.rs +++ b/common/ast/src/ast/statement.rs @@ -101,13 +101,7 @@ pub enum Statement<'a> { // User ShowUsers, CreateUser(CreateUserStmt), - AlterUser { - // None means current user - user: Option, - // None means no change to make - auth_option: Option, - role_options: Vec, - }, + AlterUser(AlterUserStmt), DropUser { if_exists: bool, user: UserIdentity, @@ -121,10 +115,11 @@ pub enum Statement<'a> { if_exists: bool, role_name: String, }, - Grant(GrantStatement), + Grant(AccountMgrStatement), ShowGrants { principal: Option, }, + Revoke(AccountMgrStatement), // UDF CreateUDF { @@ -458,6 +453,15 @@ pub struct CreateUserStmt { pub role_options: Vec, } +#[derive(Debug, Clone, PartialEq)] +pub struct AlterUserStmt { + // None means current user + pub user: Option, + // None means no change to make + pub auth_option: Option, + pub role_options: Vec, +} + #[derive(Debug, Clone, PartialEq, Default)] pub struct AuthOption { pub auth_type: Option, @@ -473,27 +477,27 @@ pub enum RoleOption { } #[derive(Debug, Clone, PartialEq)] -pub struct GrantStatement { - pub source: GrantSource, +pub struct AccountMgrStatement { + pub source: AccountMgrSource, pub principal: PrincipalIdentity, } #[derive(Debug, Clone, PartialEq)] -pub enum GrantSource { +pub enum AccountMgrSource { Role { role: String, }, Privs { privileges: Vec, - level: GrantLevel, + level: AccountMgrLevel, }, ALL { - level: GrantLevel, + level: AccountMgrLevel, }, } #[derive(Debug, Clone, PartialEq)] -pub enum GrantLevel { +pub enum AccountMgrLevel { Global, Database(Option), Table(Option, String), @@ -1014,11 +1018,11 @@ impl<'a> Display for Statement<'a> { } } } - Statement::AlterUser { + Statement::AlterUser(AlterUserStmt { user, auth_option, role_options, - } => { + }) => { write!(f, "ALTER USER")?; if let Some(user) = user { write!(f, " {user}")?; @@ -1068,50 +1072,49 @@ impl<'a> Display for Statement<'a> { } write!(f, " '{role}'")?; } - Statement::Grant(GrantStatement { source, principal }) => { + Statement::Grant(AccountMgrStatement { source, principal }) => { write!(f, "GRANT")?; match source { - GrantSource::Role { role } => write!(f, " ROLE {role}")?, - GrantSource::Privs { privileges, level } => { - write!( - f, - " {}", - privileges - .iter() - .map(|p| p.to_string()) - .collect::>() - .join(", ") - )?; + AccountMgrSource::Role { role } => write!(f, " ROLE {role}")?, + AccountMgrSource::Privs { privileges, level } => { + write!(f, " ")?; + write_comma_separated_list(f, privileges.iter().map(|p| p.to_string()))?; write!(f, " ON")?; match level { - GrantLevel::Global => write!(f, " *.*")?, - GrantLevel::Database(database_name) => { + AccountMgrLevel::Global => write!(f, " *.*")?, + AccountMgrLevel::Database(database_name) => { if let Some(database_name) = database_name { write!(f, " {database_name}.*")?; + } else { + write!(f, " *")?; } } - GrantLevel::Table(database_name, table_name) => { + AccountMgrLevel::Table(database_name, table_name) => { if let Some(database_name) = database_name { write!(f, " {database_name}.{table_name}")?; + } else { + write!(f, " {table_name}")?; } } } } - GrantSource::ALL { level, .. } => { + AccountMgrSource::ALL { level, .. } => { write!(f, " ALL PRIVILEGES")?; write!(f, " ON")?; match level { - GrantLevel::Global => write!(f, " *.*")?, - GrantLevel::Database(database_name) => { + AccountMgrLevel::Global => write!(f, " *.*")?, + AccountMgrLevel::Database(database_name) => { if let Some(database_name) = database_name { write!(f, " {database_name}.*")?; } else { write!(f, " *")?; } } - GrantLevel::Table(database_name, table_name) => { + AccountMgrLevel::Table(database_name, table_name) => { if let Some(database_name) = database_name { write!(f, " {database_name}.{table_name}")?; + } else { + write!(f, " {table_name}")?; } } } @@ -1134,6 +1137,61 @@ impl<'a> Display for Statement<'a> { } } } + Statement::Revoke(AccountMgrStatement { source, principal }) => { + write!(f, "REVOKE")?; + match source { + AccountMgrSource::Role { role } => write!(f, " ROLE {role}")?, + AccountMgrSource::Privs { privileges, level } => { + write!(f, " ")?; + write_comma_separated_list(f, privileges.iter().map(|p| p.to_string()))?; + write!(f, " ON")?; + match level { + AccountMgrLevel::Global => write!(f, " *.*")?, + AccountMgrLevel::Database(database_name) => { + if let Some(database_name) = database_name { + write!(f, " {database_name}.*")?; + } else { + write!(f, " *")?; + } + } + AccountMgrLevel::Table(database_name, table_name) => { + if let Some(database_name) = database_name { + write!(f, " {database_name}.{table_name}")?; + } else { + write!(f, " {table_name}")?; + } + } + } + } + AccountMgrSource::ALL { level, .. } => { + write!(f, " ALL PRIVILEGES")?; + write!(f, " ON")?; + match level { + AccountMgrLevel::Global => write!(f, " *.*")?, + AccountMgrLevel::Database(database_name) => { + if let Some(database_name) = database_name { + write!(f, " {database_name}.*")?; + } else { + write!(f, " *")?; + } + } + AccountMgrLevel::Table(database_name, table_name) => { + if let Some(database_name) = database_name { + write!(f, " {database_name}.{table_name}")?; + } else { + write!(f, " {table_name}")?; + } + } + } + } + } + + write!(f, " FROM")?; + match principal { + PrincipalIdentity::User(user) => write!(f, " USER {user}")?, + PrincipalIdentity::Role(role) => write!(f, " ROLE {role}")?, + } + } Statement::CreateUDF { if_not_exists, udf_name, diff --git a/common/ast/src/parser/statement.rs b/common/ast/src/parser/statement.rs index 89daba4e08836..2b36b7f160a15 100644 --- a/common/ast/src/parser/statement.rs +++ b/common/ast/src/parser/statement.rs @@ -395,15 +395,17 @@ pub fn statement(i: Input) -> IResult { ~ ( IDENTIFIED ~ ( WITH ~ ^#auth_type )? ~ ( BY ~ ^#literal_string )? )? ~ ( WITH ~ ^#role_option+ )? }, - |(_, _, user, opt_auth_option, opt_role_options)| Statement::AlterUser { - user, - auth_option: opt_auth_option.map(|(_, opt_auth_type, opt_password)| AuthOption { - auth_type: opt_auth_type.map(|(_, auth_type)| auth_type), - password: opt_password.map(|(_, password)| password), - }), - role_options: opt_role_options - .map(|(_, role_options)| role_options) - .unwrap_or_default(), + |(_, _, user, opt_auth_option, opt_role_options)| { + Statement::AlterUser(AlterUserStmt { + user, + auth_option: opt_auth_option.map(|(_, opt_auth_type, opt_password)| AuthOption { + auth_type: opt_auth_type.map(|(_, auth_type)| auth_type), + password: opt_password.map(|(_, password)| password), + }), + role_options: opt_role_options + .map(|(_, role_options)| role_options) + .unwrap_or_default(), + }) }, ); let drop_user = map( @@ -434,12 +436,12 @@ pub fn statement(i: Input) -> IResult { role_name, }, ); - let grant_priv = map( + let grant = map( rule! { GRANT ~ #grant_source ~ TO ~ #grant_option }, |(_, source, _, grant_option)| { - Statement::Grant(GrantStatement { + Statement::Grant(AccountMgrStatement { source, principal: grant_option, }) @@ -453,6 +455,17 @@ pub fn statement(i: Input) -> IResult { principal: opt_principal.map(|(_, principal)| principal), }, ); + let revoke = map( + rule! { + REVOKE ~ #grant_source ~ FROM ~ #grant_option + }, + |(_, source, _, grant_option)| { + Statement::Revoke(AccountMgrStatement { + source, + principal: grant_option, + }) + }, + ); let create_udf = map( rule! { CREATE ~ FUNCTION ~ ( IF ~ NOT ~ EXISTS )? @@ -663,8 +676,10 @@ pub fn statement(i: Input) -> IResult { | #drop_stage: "`DROP STAGE `" ), rule!( - #grant_priv : "`GRANT { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } TO { [ROLE ] | [USER] }`" + #grant : "`GRANT { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } TO { [ROLE ] | [USER] }`" | #show_grants : "`SHOW GRANTS [FOR { ROLE | [USER] }]`" + | #revoke : "`REVOKE { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } FROM { [ROLE ] | [USER] }`" + ), )); @@ -768,25 +783,25 @@ pub fn column_def(i: Input) -> IResult { )(i) } -pub fn grant_source(i: Input) -> IResult { +pub fn grant_source(i: Input) -> IResult { let role = map( rule! { ROLE ~ #literal_string }, - |(_, role_name)| GrantSource::Role { role: role_name }, + |(_, role_name)| AccountMgrSource::Role { role: role_name }, ); let privs = map( rule! { #comma_separated_list1(priv_type) ~ ON ~ #grant_level }, - |(privs, _, level)| GrantSource::Privs { + |(privs, _, level)| AccountMgrSource::Privs { privileges: privs, level, }, ); let all = map( rule! { ALL ~ PRIVILEGES? ~ ON ~ #grant_level }, - |(_, _, _, level)| GrantSource::ALL { level }, + |(_, _, _, level)| AccountMgrSource::ALL { level }, ); rule!( @@ -815,16 +830,16 @@ pub fn priv_type(i: Input) -> IResult { ))(i) } -pub fn grant_level(i: Input) -> IResult { +pub fn grant_level(i: Input) -> IResult { // *.* - let global = map(rule! { "*" ~ "." ~ "*" }, |_| GrantLevel::Global); + let global = map(rule! { "*" ~ "." ~ "*" }, |_| AccountMgrLevel::Global); // db.* // "*": as current db or "table" with current db let db = map( rule! { ( #ident ~ "." )? ~ "*" }, - |(database, _)| GrantLevel::Database(database.map(|(database, _)| database.name)), + |(database, _)| AccountMgrLevel::Database(database.map(|(database, _)| database.name)), ); // db.table let table = map( @@ -832,7 +847,7 @@ pub fn grant_level(i: Input) -> IResult { ( #ident ~ "." )? ~ #ident }, |(database, table)| { - GrantLevel::Table(database.map(|(database, _)| database.name), table.name) + AccountMgrLevel::Table(database.map(|(database, _)| database.name), table.name) }, ); diff --git a/common/ast/src/parser/token.rs b/common/ast/src/parser/token.rs index b2d54c9c6127a..a7aac05e5afde 100644 --- a/common/ast/src/parser/token.rs +++ b/common/ast/src/parser/token.rs @@ -535,6 +535,8 @@ pub enum TokenKind { PRIVILEGES, #[token("REMOVE", ignore(ascii_case))] REMOVE, + #[token("REVOKE", ignore(ascii_case))] + REVOKE, #[token("GRANTS", ignore(ascii_case))] GRANTS, #[token("RIGHT", ignore(ascii_case))] diff --git a/common/ast/tests/it/parser.rs b/common/ast/tests/it/parser.rs index 67cd3b466bcbc..d67a19e6ac954 100644 --- a/common/ast/tests/it/parser.rs +++ b/common/ast/tests/it/parser.rs @@ -141,10 +141,15 @@ fn test_statement() { r#"GRANT SELECT ON db01.tb1 TO 'test-grant'@'localhost';"#, r#"GRANT SELECT ON db01.tb1 TO USER 'test-grant'@'localhost';"#, r#"GRANT SELECT ON db01.tb1 TO ROLE 'role1';"#, + r#"GRANT SELECT ON tb1 TO ROLE 'role1';"#, + r#"GRANT ALL ON tb1 TO 'u1';"#, r#"SHOW GRANTS;"#, r#"SHOW GRANTS FOR 'test-grant'@'localhost';"#, r#"SHOW GRANTS FOR USER 'test-grant'@'localhost';"#, r#"SHOW GRANTS FOR ROLE 'role1';"#, + r#"REVOKE SELECT, CREATE ON * FROM 'test-grant'@'localhost';"#, + r#"REVOKE SELECT ON tb1 FROM ROLE 'role1';"#, + r#"REVOKE ALL ON tb1 FROM 'u1';"#, ]; for case in cases { @@ -187,6 +192,8 @@ fn test_statement_error() { r#"GRANT SELECT, ALL PRIVILEGES, CREATE ON * TO 'test-grant'@'localhost';"#, r#"GRANT SELECT, CREATE ON *.c TO 'test-grant'@'localhost';"#, r#"SHOW GRANT FOR ROLE role1;"#, + r#"REVOKE SELECT, CREATE, ALL PRIVILEGES ON * FROM 'test-grant'@'localhost';"#, + r#"REVOKE SELECT, CREATE ON * TO 'test-grant'@'localhost';"#, ]; for case in cases { diff --git a/common/ast/tests/it/testdata/statement-error.txt b/common/ast/tests/it/testdata/statement-error.txt index 68eb61636beff..1da6fac1e5610 100644 --- a/common/ast/tests/it/testdata/statement-error.txt +++ b/common/ast/tests/it/testdata/statement-error.txt @@ -225,3 +225,28 @@ error: | ^^^^^ expected `SETTINGS`, `STAGES`, `PROCESSLIST`, `METRICS`, `FUNCTIONS`, `DATABASES`, or 8 more ... +---------- Input ---------- +REVOKE SELECT, CREATE, ALL PRIVILEGES ON * FROM 'test-grant'@'localhost'; +---------- Output --------- +error: + --> SQL:1:24 + | +1 | REVOKE SELECT, CREATE, ALL PRIVILEGES ON * FROM 'test-grant'@'localhost'; + | ------ ------ ^^^ expected `USAGE`, `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `CREATE`, or 5 more ... + | | | + | | while parsing ON + | while parsing `REVOKE { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } FROM { [ROLE ] | [USER] }` + + +---------- Input ---------- +REVOKE SELECT, CREATE ON * TO 'test-grant'@'localhost'; +---------- Output --------- +error: + --> SQL:1:28 + | +1 | REVOKE SELECT, CREATE ON * TO 'test-grant'@'localhost'; + | ------ ^^ expected `FROM` or `.` + | | + | while parsing `REVOKE { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } FROM { [ROLE ] | [USER] }` + + diff --git a/common/ast/tests/it/testdata/statement.txt b/common/ast/tests/it/testdata/statement.txt index c1b29993caeff..4110c91a16d33 100644 --- a/common/ast/tests/it/testdata/statement.txt +++ b/common/ast/tests/it/testdata/statement.txt @@ -3122,23 +3122,25 @@ alter user 'test-e'@'localhost' identified by 'new-password'; ---------- Output --------- ALTER USER 'test-e'@'localhost' IDENTIFIED BY 'new-password' ---------- AST ------------ -AlterUser { - user: Some( - UserIdentity { - username: "test-e", - hostname: "localhost", - }, - ), - auth_option: Some( - AuthOption { - auth_type: None, - password: Some( - "new-password", - ), - }, - ), - role_options: [], -} +AlterUser( + AlterUserStmt { + user: Some( + UserIdentity { + username: "test-e", + hostname: "localhost", + }, + ), + auth_option: Some( + AuthOption { + auth_type: None, + password: Some( + "new-password", + ), + }, + ), + role_options: [], + }, +) ---------- Input ---------- @@ -3355,10 +3357,10 @@ CreateTable( ---------- Input ---------- GRANT SELECT, CREATE ON * TO 'test-grant'@'localhost'; ---------- Output --------- -GRANT SELECT, CREATE ON TO USER 'test-grant'@'localhost' +GRANT SELECT, CREATE ON * TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3381,10 +3383,10 @@ Grant( ---------- Input ---------- GRANT SELECT, CREATE ON * TO USER 'test-grant'@'localhost'; ---------- Output --------- -GRANT SELECT, CREATE ON TO USER 'test-grant'@'localhost' +GRANT SELECT, CREATE ON * TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3407,10 +3409,10 @@ Grant( ---------- Input ---------- GRANT SELECT, CREATE ON * TO ROLE 'role1'; ---------- Output --------- -GRANT SELECT, CREATE ON TO ROLE role1 +GRANT SELECT, CREATE ON * TO ROLE role1 ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3433,7 +3435,7 @@ GRANT ALL ON *.* TO 'test-grant'@'localhost'; GRANT ALL PRIVILEGES ON *.* TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: ALL { level: Global, }, @@ -3453,7 +3455,7 @@ GRANT ALL ON *.* TO ROLE 'role2'; GRANT ALL PRIVILEGES ON *.* TO ROLE role2 ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: ALL { level: Global, }, @@ -3470,7 +3472,7 @@ GRANT ALL PRIVILEGES ON * TO 'test-grant'@'localhost'; GRANT ALL PRIVILEGES ON * TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: ALL { level: Database( None, @@ -3492,7 +3494,7 @@ GRANT ALL PRIVILEGES ON * TO ROLE 'role3'; GRANT ALL PRIVILEGES ON * TO ROLE role3 ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: ALL { level: Database( None, @@ -3511,7 +3513,7 @@ GRANT ROLE 'test' TO 'test-user'; GRANT ROLE test TO USER 'test-user'@'%' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Role { role: "test", }, @@ -3531,7 +3533,7 @@ GRANT ROLE 'test' TO USER 'test-user'; GRANT ROLE test TO USER 'test-user'@'%' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Role { role: "test", }, @@ -3551,7 +3553,7 @@ GRANT ROLE 'test' TO ROLE 'test-user'; GRANT ROLE test TO ROLE test-user ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Role { role: "test", }, @@ -3568,7 +3570,7 @@ GRANT SELECT ON db01.* TO 'test-grant'@'localhost'; GRANT SELECT ON db01.* TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3595,7 +3597,7 @@ GRANT SELECT ON db01.* TO USER 'test-grant'@'localhost'; GRANT SELECT ON db01.* TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3622,7 +3624,7 @@ GRANT SELECT ON db01.* TO ROLE 'role1' GRANT SELECT ON db01.* TO ROLE role1 ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3646,7 +3648,7 @@ GRANT SELECT ON db01.tb1 TO 'test-grant'@'localhost'; GRANT SELECT ON db01.tb1 TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3674,7 +3676,7 @@ GRANT SELECT ON db01.tb1 TO USER 'test-grant'@'localhost'; GRANT SELECT ON db01.tb1 TO USER 'test-grant'@'localhost' ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3702,7 +3704,7 @@ GRANT SELECT ON db01.tb1 TO ROLE 'role1'; GRANT SELECT ON db01.tb1 TO ROLE role1 ---------- AST ------------ Grant( - GrantStatement { + AccountMgrStatement { source: Privs { privileges: [ Select, @@ -3721,6 +3723,52 @@ Grant( ) +---------- Input ---------- +GRANT SELECT ON tb1 TO ROLE 'role1'; +---------- Output --------- +GRANT SELECT ON tb1 TO ROLE role1 +---------- AST ------------ +Grant( + AccountMgrStatement { + source: Privs { + privileges: [ + Select, + ], + level: Table( + None, + "tb1", + ), + }, + principal: Role( + "role1", + ), + }, +) + + +---------- Input ---------- +GRANT ALL ON tb1 TO 'u1'; +---------- Output --------- +GRANT ALL PRIVILEGES ON tb1 TO USER 'u1'@'%' +---------- AST ------------ +Grant( + AccountMgrStatement { + source: ALL { + level: Table( + None, + "tb1", + ), + }, + principal: User( + UserIdentity { + username: "u1", + hostname: "%", + }, + ), + }, +) + + ---------- Input ---------- SHOW GRANTS; ---------- Output --------- @@ -3779,3 +3827,75 @@ ShowGrants { } +---------- Input ---------- +REVOKE SELECT, CREATE ON * FROM 'test-grant'@'localhost'; +---------- Output --------- +REVOKE SELECT, CREATE ON * FROM USER 'test-grant'@'localhost' +---------- AST ------------ +Revoke( + AccountMgrStatement { + source: Privs { + privileges: [ + Select, + Create, + ], + level: Database( + None, + ), + }, + principal: User( + UserIdentity { + username: "test-grant", + hostname: "localhost", + }, + ), + }, +) + + +---------- Input ---------- +REVOKE SELECT ON tb1 FROM ROLE 'role1'; +---------- Output --------- +REVOKE SELECT ON tb1 FROM ROLE role1 +---------- AST ------------ +Revoke( + AccountMgrStatement { + source: Privs { + privileges: [ + Select, + ], + level: Table( + None, + "tb1", + ), + }, + principal: Role( + "role1", + ), + }, +) + + +---------- Input ---------- +REVOKE ALL ON tb1 FROM 'u1'; +---------- Output --------- +REVOKE ALL PRIVILEGES ON tb1 FROM USER 'u1'@'%' +---------- AST ------------ +Revoke( + AccountMgrStatement { + source: ALL { + level: Table( + None, + "tb1", + ), + }, + principal: User( + UserIdentity { + username: "u1", + hostname: "%", + }, + ), + }, +) + + diff --git a/common/meta/types/src/user_privilege.rs b/common/meta/types/src/user_privilege.rs index 47f29c1bdc66e..40b4e90b3cb1b 100644 --- a/common/meta/types/src/user_privilege.rs +++ b/common/meta/types/src/user_privilege.rs @@ -75,6 +75,7 @@ const ALL_PRIVILEGES: BitFlags = make_bitflags!( | CreateUser | CreateRole | Grant + | CreateStage | Set } ); diff --git a/query/src/interpreters/interpreter_factory_v2.rs b/query/src/interpreters/interpreter_factory_v2.rs index 541255234b375..4eab9a07150bd 100644 --- a/query/src/interpreters/interpreter_factory_v2.rs +++ b/query/src/interpreters/interpreter_factory_v2.rs @@ -74,6 +74,8 @@ impl InterpreterFactoryV2 { | DfStatement::GrantPrivilege(_) | DfStatement::GrantRole(_) | DfStatement::ShowGrants(_) + | DfStatement::RevokeRole(_) + | DfStatement::RevokePrivilege(_) ) } @@ -216,6 +218,12 @@ impl InterpreterFactoryV2 { Plan::ShowGrants(show_grants) => { ShowGrantsInterpreter::try_create(ctx.clone(), *show_grants.clone()) } + Plan::RevokePriv(revoke_priv) => { + RevokePrivilegeInterpreter::try_create(ctx.clone(), *revoke_priv.clone()) + } + Plan::RevokeRole(revoke_role) => { + RevokeRoleInterpreter::try_create(ctx.clone(), *revoke_role.clone()) + } }?; Ok(Arc::new(InterceptorInterpreter::create( diff --git a/query/src/sql/planner/binder/ddl/account.rs b/query/src/sql/planner/binder/ddl/account.rs new file mode 100644 index 0000000000000..c0d52fb46eb77 --- /dev/null +++ b/query/src/sql/planner/binder/ddl/account.rs @@ -0,0 +1,216 @@ +// Copyright 2022 Datafuse Labs. +// +// 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. + +use common_ast::ast::AccountMgrLevel; +use common_ast::ast::AccountMgrSource; +use common_ast::ast::AccountMgrStatement; +use common_ast::ast::AlterUserStmt; +use common_ast::ast::CreateUserStmt; +use common_exception::Result; +use common_meta_types::AuthInfo; +use common_meta_types::GrantObject; +use common_meta_types::UserOption; +use common_meta_types::UserPrivilegeSet; +use common_planners::AlterUserPlan; +use common_planners::CreateUserPlan; +use common_planners::GrantPrivilegePlan; +use common_planners::GrantRolePlan; +use common_planners::RevokePrivilegePlan; +use common_planners::RevokeRolePlan; + +use crate::sql::plans::Plan; +use crate::sql::Binder; + +impl<'a> Binder { + pub(in crate::sql::planner::binder) async fn bind_grant( + &mut self, + stmt: &AccountMgrStatement, + ) -> Result { + let AccountMgrStatement { source, principal } = stmt; + + match source { + AccountMgrSource::Role { role } => { + let plan = GrantRolePlan { + principal: principal.clone(), + role: role.clone(), + }; + Ok(Plan::GrantRole(Box::new(plan))) + } + AccountMgrSource::ALL { level } => { + // ALL PRIVILEGES have different available privileges set on different grant objects + // Now in this case all is always true. + let grant_object = self.convert_to_grant_object(level); + let priv_types = grant_object.available_privileges(); + let plan = GrantPrivilegePlan { + principal: principal.clone(), + on: grant_object, + priv_types, + }; + Ok(Plan::GrantPriv(Box::new(plan))) + } + AccountMgrSource::Privs { privileges, level } => { + let grant_object = self.convert_to_grant_object(level); + let mut priv_types = UserPrivilegeSet::empty(); + for x in privileges { + priv_types.set_privilege(*x); + } + let plan = GrantPrivilegePlan { + principal: principal.clone(), + on: grant_object, + priv_types, + }; + Ok(Plan::GrantPriv(Box::new(plan))) + } + } + } + + pub(in crate::sql::planner::binder) async fn bind_revoke( + &mut self, + stmt: &AccountMgrStatement, + ) -> Result { + let AccountMgrStatement { source, principal } = stmt; + + match source { + AccountMgrSource::Role { role } => { + let plan = RevokeRolePlan { + principal: principal.clone(), + role: role.clone(), + }; + Ok(Plan::RevokeRole(Box::new(plan))) + } + AccountMgrSource::ALL { level } => { + // ALL PRIVILEGES have different available privileges set on different grant objects + // Now in this case all is always true. + let grant_object = self.convert_to_grant_object(level); + let priv_types = grant_object.available_privileges(); + let plan = RevokePrivilegePlan { + principal: principal.clone(), + on: grant_object, + priv_types, + }; + Ok(Plan::RevokePriv(Box::new(plan))) + } + AccountMgrSource::Privs { privileges, level } => { + let grant_object = self.convert_to_grant_object(level); + let mut priv_types = UserPrivilegeSet::empty(); + for x in privileges { + priv_types.set_privilege(*x); + } + let plan = RevokePrivilegePlan { + principal: principal.clone(), + on: grant_object, + priv_types, + }; + Ok(Plan::RevokePriv(Box::new(plan))) + } + } + } + + pub(in crate::sql::planner::binder) fn convert_to_grant_object( + &self, + source: &AccountMgrLevel, + ) -> GrantObject { + // TODO fetch real catalog + let catalog_name = self.ctx.get_current_catalog(); + match source { + AccountMgrLevel::Global => GrantObject::Global, + AccountMgrLevel::Table(database_name, table_name) => { + let database_name = database_name + .clone() + .unwrap_or_else(|| self.ctx.get_current_database()); + GrantObject::Table(catalog_name, database_name, table_name.clone()) + } + AccountMgrLevel::Database(database_name) => { + let database_name = database_name + .clone() + .unwrap_or_else(|| self.ctx.get_current_database()); + GrantObject::Database(catalog_name, database_name) + } + } + } + + pub(in crate::sql::planner::binder) async fn bind_create_user( + &mut self, + stmt: &CreateUserStmt, + ) -> Result { + let CreateUserStmt { + if_not_exists, + user, + auth_option, + role_options, + } = stmt; + let mut user_option = UserOption::default(); + for option in role_options { + option.apply(&mut user_option); + } + let plan = CreateUserPlan { + user: user.clone(), + auth_info: AuthInfo::create2(&auth_option.auth_type, &auth_option.password)?, + user_option, + if_not_exists: *if_not_exists, + }; + Ok(Plan::CreateUser(Box::new(plan))) + } + + pub(in crate::sql::planner::binder) async fn bind_alter_user( + &mut self, + stmt: &AlterUserStmt, + ) -> Result { + let AlterUserStmt { + user, + auth_option, + role_options, + } = stmt; + // None means current user + let user_info = if user.is_none() { + self.ctx.get_current_user()? + } else { + self.ctx + .get_user_manager() + .get_user(&self.ctx.get_tenant(), user.clone().unwrap()) + .await? + }; + + // None means no change to make + let new_auth_info = if let Some(auth_option) = &auth_option { + let auth_info = user_info + .auth_info + .alter2(&auth_option.auth_type, &auth_option.password)?; + if user_info.auth_info == auth_info { + None + } else { + Some(auth_info) + } + } else { + None + }; + + let mut user_option = user_info.option.clone(); + for option in role_options { + option.apply(&mut user_option); + } + let new_user_option = if user_option == user_info.option { + None + } else { + Some(user_option) + }; + let plan = AlterUserPlan { + user: user_info.identity(), + auth_info: new_auth_info, + user_option: new_user_option, + }; + + Ok(Plan::AlterUser(Box::new(plan))) + } +} diff --git a/query/src/sql/planner/binder/ddl/grant.rs b/query/src/sql/planner/binder/ddl/grant.rs deleted file mode 100644 index 2c4972ec3c167..0000000000000 --- a/query/src/sql/planner/binder/ddl/grant.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2022 Datafuse Labs. -// -// 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. - -use common_ast::ast::GrantLevel; -use common_ast::ast::GrantSource; -use common_ast::ast::GrantStatement; -use common_exception::Result; -use common_meta_types::GrantObject; -use common_meta_types::UserPrivilegeSet; -use common_planners::GrantPrivilegePlan; -use common_planners::GrantRolePlan; - -use crate::sql::plans::Plan; -use crate::sql::Binder; - -impl<'a> Binder { - pub(in crate::sql::planner::binder) async fn bind_grant( - &mut self, - stmt: &GrantStatement, - ) -> Result { - let GrantStatement { source, principal } = stmt; - - match source { - GrantSource::Role { role } => { - let plan = GrantRolePlan { - principal: principal.clone(), - role: role.clone(), - }; - Ok(Plan::GrantRole(Box::new(plan))) - } - GrantSource::ALL { level } => { - // ALL PRIVILEGES have different available privileges set on different grant objects - // Now in this case all is always true. - let grant_object = self.convert_to_grant_object(level); - let priv_types = grant_object.available_privileges(); - let plan = GrantPrivilegePlan { - principal: principal.clone(), - on: grant_object, - priv_types, - }; - Ok(Plan::GrantPriv(Box::new(plan))) - } - GrantSource::Privs { privileges, level } => { - let grant_object = self.convert_to_grant_object(level); - let mut priv_types = UserPrivilegeSet::empty(); - for x in privileges { - priv_types.set_privilege(*x); - } - let plan = GrantPrivilegePlan { - principal: principal.clone(), - on: grant_object, - priv_types, - }; - Ok(Plan::GrantPriv(Box::new(plan))) - } - } - } - - //Copy from query/src/sql/statements/statement_grant.rs - fn convert_to_grant_object(&self, source: &GrantLevel) -> GrantObject { - // TODO fetch real catalog - let catalog_name = self.ctx.get_current_catalog(); - match source { - GrantLevel::Global => GrantObject::Global, - GrantLevel::Table(database_name, table_name) => { - let database_name = database_name - .clone() - .unwrap_or_else(|| self.ctx.get_current_database()); - GrantObject::Table(catalog_name, database_name, table_name.clone()) - } - GrantLevel::Database(database_name) => { - let database_name = database_name - .clone() - .unwrap_or_else(|| self.ctx.get_current_database()); - GrantObject::Database(catalog_name, database_name) - } - } - } -} diff --git a/query/src/sql/planner/binder/ddl/mod.rs b/query/src/sql/planner/binder/ddl/mod.rs index 60892cd9f8100..4a6458ddf61f1 100644 --- a/query/src/sql/planner/binder/ddl/mod.rs +++ b/query/src/sql/planner/binder/ddl/mod.rs @@ -12,9 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod account; mod database; -mod grant; mod stage; mod table; -mod user; mod view; diff --git a/query/src/sql/planner/binder/ddl/user.rs b/query/src/sql/planner/binder/ddl/user.rs deleted file mode 100644 index 8dda71bf4370a..0000000000000 --- a/query/src/sql/planner/binder/ddl/user.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2022 Datafuse Labs. -// -// 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. - -use common_ast::ast::AuthOption; -use common_ast::ast::CreateUserStmt; -use common_ast::ast::RoleOption; -use common_exception::Result; -use common_meta_types::AuthInfo; -use common_meta_types::UserIdentity; -use common_meta_types::UserOption; -use common_planners::AlterUserPlan; -use common_planners::CreateUserPlan; - -use crate::sql::binder::Binder; -use crate::sql::plans::Plan; - -impl Binder { - pub(in crate::sql::planner::binder) async fn bind_create_user( - &mut self, - stmt: &CreateUserStmt, - ) -> Result { - let mut user_option = UserOption::default(); - for option in &stmt.role_options { - option.apply(&mut user_option); - } - let plan = CreateUserPlan { - user: stmt.user.clone(), - auth_info: AuthInfo::create2(&stmt.auth_option.auth_type, &stmt.auth_option.password)?, - user_option, - if_not_exists: stmt.if_not_exists, - }; - Ok(Plan::CreateUser(Box::new(plan))) - } - - pub(in crate::sql::planner::binder) async fn bind_alter_user( - &mut self, - user: &Option, - auth_option: &Option, - role_options: &Vec, - ) -> Result { - // None means current user - let user_info = if user.is_none() { - self.ctx.get_current_user()? - } else { - self.ctx - .get_user_manager() - .get_user(&self.ctx.get_tenant(), user.clone().unwrap()) - .await? - }; - - // None means no change to make - let new_auth_info = if let Some(auth_option) = &auth_option { - let auth_info = user_info - .auth_info - .alter2(&auth_option.auth_type, &auth_option.password)?; - if user_info.auth_info == auth_info { - None - } else { - Some(auth_info) - } - } else { - None - }; - - let mut user_option = user_info.option.clone(); - for option in role_options { - option.apply(&mut user_option); - } - let new_user_option = if user_option == user_info.option { - None - } else { - Some(user_option) - }; - let plan = AlterUserPlan { - user: user_info.identity(), - auth_info: new_auth_info, - user_option: new_user_option, - }; - - Ok(Plan::AlterUser(Box::new(plan))) - } -} diff --git a/query/src/sql/planner/binder/mod.rs b/query/src/sql/planner/binder/mod.rs index 9995552e3e4a9..1c21f5d8c0e81 100644 --- a/query/src/sql/planner/binder/mod.rs +++ b/query/src/sql/planner/binder/mod.rs @@ -144,14 +144,7 @@ impl<'a> Binder { user: user.clone(), })), Statement::ShowUsers => Plan::ShowUsers, - Statement::AlterUser { - user, - auth_option, - role_options, - } => { - self.bind_alter_user(user, auth_option, role_options) - .await? - } + Statement::AlterUser(stmt) => self.bind_alter_user(stmt).await?, // Roles Statement::ShowRoles => Plan::ShowRoles, @@ -198,6 +191,8 @@ impl<'a> Binder { principal: principal.clone(), })), + Statement::Revoke(stmt) => self.bind_revoke(stmt).await?, + _ => { return Err(ErrorCode::UnImplement(format!( "UnImplemented stmt {stmt} in binder" diff --git a/query/src/sql/planner/format/display_plan.rs b/query/src/sql/planner/format/display_plan.rs index 9b6fd88f3e2e7..aa9e5f9236984 100644 --- a/query/src/sql/planner/format/display_plan.rs +++ b/query/src/sql/planner/format/display_plan.rs @@ -59,17 +59,6 @@ impl Plan { Plan::AlterView(alter_view) => Ok(format!("{:?}", alter_view)), Plan::DropView(drop_view) => Ok(format!("{:?}", drop_view)), - // Users - Plan::ShowUsers => Ok("SHOW USERS".to_string()), - Plan::CreateUser(create_user) => Ok(format!("{:?}", create_user)), - Plan::DropUser(drop_user) => Ok(format!("{:?}", drop_user)), - Plan::AlterUser(alter_user) => Ok(format!("{:?}", alter_user)), - - // Roles - Plan::ShowRoles => Ok("SHOW ROLES".to_string()), - Plan::CreateRole(create_role) => Ok(format!("{:?}", create_role)), - Plan::DropRole(drop_role) => Ok(format!("{:?}", drop_role)), - // Stages Plan::ShowStages => Ok("SHOW STAGES".to_string()), Plan::ListStage(s) => Ok(format!("{:?}", s)), @@ -83,10 +72,19 @@ impl Plan { Plan::ShowProcessList => Ok("SHOW PROCESSLIST".to_string()), Plan::ShowSettings => Ok("SHOW SETTINGS".to_string()), - // Grant + // Account Plan::GrantRole(grant_role) => Ok(format!("{:?}", grant_role)), Plan::GrantPriv(grant_priv) => Ok(format!("{:?}", grant_priv)), Plan::ShowGrants(show_grants) => Ok(format!("{:?}", show_grants)), + Plan::RevokePriv(revoke_priv) => Ok(format!("{:?}", revoke_priv)), + Plan::RevokeRole(revoke_role) => Ok(format!("{:?}", revoke_role)), + Plan::ShowUsers => Ok("SHOW USERS".to_string()), + Plan::CreateUser(create_user) => Ok(format!("{:?}", create_user)), + Plan::DropUser(drop_user) => Ok(format!("{:?}", drop_user)), + Plan::AlterUser(alter_user) => Ok(format!("{:?}", alter_user)), + Plan::ShowRoles => Ok("SHOW ROLES".to_string()), + Plan::CreateRole(create_role) => Ok(format!("{:?}", create_role)), + Plan::DropRole(drop_role) => Ok(format!("{:?}", drop_role)), } } } diff --git a/query/src/sql/planner/plans/mod.rs b/query/src/sql/planner/plans/mod.rs index 1a7190efc30e2..98714c5a1e6d6 100644 --- a/query/src/sql/planner/plans/mod.rs +++ b/query/src/sql/planner/plans/mod.rs @@ -100,19 +100,19 @@ pub enum Plan { AlterView(Box), DropView(Box), - // Users + // Account ShowUsers, AlterUser(Box), CreateUser(Box), DropUser(Box), - - // Roles ShowRoles, CreateRole(Box), DropRole(Box), GrantRole(Box), GrantPriv(Box), ShowGrants(Box), + RevokePriv(Box), + RevokeRole(Box), // Stages ShowStages, @@ -167,6 +167,8 @@ impl Display for Plan { Plan::GrantRole(_) => write!(f, "GrantRole"), Plan::GrantPriv(_) => write!(f, "GrantPriv"), Plan::ShowGrants(_) => write!(f, "ShowGrants"), + Plan::RevokePriv(_) => write!(f, "RevokePriv"), + Plan::RevokeRole(_) => write!(f, "RevokeRole"), } } } diff --git a/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke.result b/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke.result new file mode 100644 index 0000000000000..54e2aa56c928e --- /dev/null +++ b/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke.result @@ -0,0 +1,4 @@ +GRANT SELECT ON 'default'.'b'.* TO 'test' +GRANT SELECT ON 'default'.'b'.* TO 'test' +GRANT SELECT ON 'default'.'b'.* TO 'test-priv'@'%' +GRANT SELECT ON 'default'.'b'.* TO 'test-priv'@'%' diff --git a/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke.sql b/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke.sql new file mode 100644 index 0000000000000..37f862d080a20 --- /dev/null +++ b/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke.sql @@ -0,0 +1,47 @@ +DROP USER IF EXISTS 'test-user'; +DROP USER IF EXISTS 'test-priv'; +DROP DATABASE IF EXISTS a; +DROP DATABASE IF EXISTS b; +DROP ROLE IF EXISTS 'test-role'; +DROP ROLE IF EXISTS 'test'; +DROP USER IF EXISTS 'test-user'; +DROP USER IF EXISTS 'test-priv'; +CREATE DATABASE a; +CREATE DATABASE b; + +REVOKE ROLE 'test' FROM 'test-user'; -- {ErrorCode 2201} +CREATE USER 'test-user' IDENTIFIED BY 'password'; +REVOKE ROLE 'test' FROM 'test-user'; +CREATE ROLE 'test'; +--REVOKE ROLE TEST +GRANT SELECT ON b.* TO ROLE 'test'; +SHOW GRANTS FOR ROLE 'test'; +GRANT ROLE 'test' TO 'test-user'; +SHOW GRANTS FOR 'test-user'; +REVOKE SELECT ON a.* FROM ROLE 'test'; +SHOW GRANTS FOR ROLE 'test'; +SHOW GRANTS FOR 'test-user'; +REVOKE SELECT ON b.* FROM ROLE 'test'; +SHOW GRANTS FOR 'test-user'; +SHOW GRANTS FOR ROLE 'test'; + +REVOKE ROLE 'test' FROM ROLE 'test-role'; -- {ErrorCode 2204} +CREATE ROLE 'test-role'; +REVOKE ROLE 'test' FROM ROLE 'test-role'; + +--REVOKE PRIV TEST +CREATE USER 'test-priv' IDENTIFIED BY 'A'; +GRANT SELECT ON b.* TO 'test-priv'; +SHOW GRANTS FOR 'test-priv'; +REVOKE SELECT ON a.* FROM 'test-priv'; +SHOW GRANTS FOR 'test-priv'; +REVOKE SELECT ON b.* FROM 'test-priv'; +SHOW GRANTS FOR 'test-priv'; + +DROP ROLE 'test'; +DROP ROLE 'test-role'; +DROP USER 'test-user'; +DROP USER 'test-priv'; +DROP DATABASE a; +DROP DATABASE b; + diff --git a/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke_role.result b/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke_role.result deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke_role.sql b/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke_role.sql deleted file mode 100644 index 7af74dc9631f1..0000000000000 --- a/tests/suites/0_stateless/05_ddl/05_0018_ddl_revoke_role.sql +++ /dev/null @@ -1,10 +0,0 @@ -REVOKE ROLE 'test' FROM 'test-user'; -- {ErrorCode 2201} -CREATE USER 'test-user' IDENTIFIED BY 'password'; -REVOKE ROLE 'test' FROM 'test-user'; - -REVOKE ROLE 'test' FROM ROLE 'test-role'; -- {ErrorCode 2204} -CREATE ROLE 'test-role'; -REVOKE ROLE 'test' FROM ROLE 'test-role'; - -DROP ROLE 'test-role'; -DROP USER 'test-user'; diff --git a/tests/suites/0_stateless/05_ddl/05_0022_ddl_revoke_v2.result b/tests/suites/0_stateless/05_ddl/05_0022_ddl_revoke_v2.result new file mode 100644 index 0000000000000..54e2aa56c928e --- /dev/null +++ b/tests/suites/0_stateless/05_ddl/05_0022_ddl_revoke_v2.result @@ -0,0 +1,4 @@ +GRANT SELECT ON 'default'.'b'.* TO 'test' +GRANT SELECT ON 'default'.'b'.* TO 'test' +GRANT SELECT ON 'default'.'b'.* TO 'test-priv'@'%' +GRANT SELECT ON 'default'.'b'.* TO 'test-priv'@'%' diff --git a/tests/suites/0_stateless/05_ddl/05_0022_ddl_revoke_v2.sql b/tests/suites/0_stateless/05_ddl/05_0022_ddl_revoke_v2.sql new file mode 100644 index 0000000000000..4bfb906acf6c6 --- /dev/null +++ b/tests/suites/0_stateless/05_ddl/05_0022_ddl_revoke_v2.sql @@ -0,0 +1,49 @@ +SET enable_planner_v2=1; +DROP USER IF EXISTS 'test-user'; +DROP USER IF EXISTS 'test-priv'; +DROP DATABASE IF EXISTS a; +DROP DATABASE IF EXISTS b; +DROP ROLE IF EXISTS 'test-role'; +DROP ROLE IF EXISTS 'test'; +DROP USER IF EXISTS 'test-user'; +DROP USER IF EXISTS 'test-priv'; +CREATE DATABASE a; +CREATE DATABASE b; + +REVOKE ROLE 'test' FROM 'test-user'; -- {ErrorCode 2201} +CREATE USER 'test-user' IDENTIFIED BY 'password'; +REVOKE ROLE 'test' FROM 'test-user'; +CREATE ROLE 'test'; +--REVOKE ROLE TEST +GRANT SELECT ON b.* TO ROLE 'test'; +SHOW GRANTS FOR ROLE 'test'; +GRANT ROLE 'test' TO 'test-user'; +SHOW GRANTS FOR 'test-user'; +REVOKE SELECT ON a.* FROM ROLE 'test'; +SHOW GRANTS FOR ROLE 'test'; +SHOW GRANTS FOR 'test-user'; +REVOKE SELECT ON b.* FROM ROLE 'test'; +SHOW GRANTS FOR 'test-user'; +SHOW GRANTS FOR ROLE 'test'; + +REVOKE ROLE 'test' FROM ROLE 'test-role'; -- {ErrorCode 2204} +CREATE ROLE 'test-role'; +REVOKE ROLE 'test' FROM ROLE 'test-role'; + +--REVOKE PRIV TEST +CREATE USER 'test-priv' IDENTIFIED BY 'A'; +GRANT SELECT ON b.* TO 'test-priv'; +SHOW GRANTS FOR 'test-priv'; +REVOKE SELECT ON a.* FROM 'test-priv'; +SHOW GRANTS FOR 'test-priv'; +REVOKE SELECT ON b.* FROM 'test-priv'; +SHOW GRANTS FOR 'test-priv'; + +DROP ROLE 'test'; +DROP ROLE 'test-role'; +DROP USER 'test-user'; +DROP USER 'test-priv'; +DROP DATABASE a; +DROP DATABASE b; +SET enable_planner_v2=0; +