From c6da9b34e15f10b6dc9f04d0ef8ed0abe4cab84a Mon Sep 17 00:00:00 2001 From: sinemora <31039007+cnissnzg@users.noreply.github.com> Date: Fri, 29 Jul 2022 18:17:22 +0800 Subject: [PATCH] feat(frontend): support user alter (#4261) * alter user * fix * fix * fix typo in e2e test * fmt * fix Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- e2e_test/ddl/user.slt | 12 +- proto/user.proto | 19 +++ src/frontend/src/handler/alter_user.rs | 154 +++++++++++++++++++++++ src/frontend/src/handler/create_user.rs | 22 ++-- src/frontend/src/handler/mod.rs | 2 + src/frontend/src/test_utils.rs | 26 +++- src/frontend/src/user/user_manager.rs | 7 +- src/frontend/src/user/user_service.rs | 9 +- src/meta/src/manager/user.rs | 46 +++++++ src/meta/src/rpc/service/user_service.rs | 27 ++++ src/rpc_client/src/meta_client.rs | 6 + src/sqlparser/src/ast/mod.rs | 5 + src/sqlparser/src/ast/statement.rs | 108 ++++++++++++---- src/sqlparser/src/parser.rs | 13 +- src/sqlparser/tests/sqlparser_common.rs | 8 +- src/sqlparser/tests/testdata/create.yaml | 6 + src/utils/pgwire/src/pg_response.rs | 1 + 17 files changed, 423 insertions(+), 48 deletions(-) create mode 100644 src/frontend/src/handler/alter_user.rs diff --git a/e2e_test/ddl/user.slt b/e2e_test/ddl/user.slt index acb304742a9bc..941bdab19c980 100644 --- a/e2e_test/ddl/user.slt +++ b/e2e_test/ddl/user.slt @@ -1,10 +1,18 @@ # Create a user. statement ok -CREATE USER ddl_user WITH NOSUPERUSER CREATEDB PASSWORD 'md5827ccb0eea8a706c4c34a16891f84e7b'; +CREATE USER user WITH SUPERUSER NOCREATEDB PASSWORD 'md5827ccb0eea8a706c4c34a16891f84e7b'; # Create another user with duplicate name. statement error -CREATE USER ddl_user; +CREATE USER user; + +# Alter user name. +statement ok +ALTER USER user RENAME TO ddl_user; + +# Alter user properties. +statement ok +ALTER USER ddl_user WITH NOSUPERUSER CREATEDB PASSWORD 'md59f2fa6a30871a92249bdd2f1eeee4ef6'; # Drop the user if exists. statement ok diff --git a/proto/user.proto b/proto/user.proto index 17d47cce955a6..918030393d4c0 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -80,6 +80,24 @@ message DropUserResponse { uint64 version = 2; } +message UpdateUserRequest { + enum UpdateField { + UNKNOWN = 0; + SUPER = 1; + LOGIN = 2; + CREATE_DB = 3; + AUTH_INFO = 4; + RENAME = 5; + } + UserInfo user = 1; + repeated UpdateField update_fields = 2; +} + +message UpdateUserResponse { + common.Status status = 1; + uint64 version = 2; +} + message GrantPrivilegeRequest { repeated uint32 user_ids = 1; repeated GrantPrivilege privileges = 2; @@ -111,6 +129,7 @@ service UserService { // https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/FieldMask.html. rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); rpc DropUser(DropUserRequest) returns (DropUserResponse); + rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse); // GrantPrivilege grants a privilege to a user. rpc GrantPrivilege(GrantPrivilegeRequest) returns (GrantPrivilegeResponse); diff --git a/src/frontend/src/handler/alter_user.rs b/src/frontend/src/handler/alter_user.rs new file mode 100644 index 0000000000000..520331f23be95 --- /dev/null +++ b/src/frontend/src/handler/alter_user.rs @@ -0,0 +1,154 @@ +// Copyright 2022 Singularity Data +// +// 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 pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::error::Result; +use risingwave_pb::user::update_user_request::UpdateField; +use risingwave_pb::user::{UpdateUserRequest, UserInfo}; +use risingwave_sqlparser::ast::{AlterUserStatement, UserOption, UserOptions}; + +use crate::binder::Binder; +use crate::catalog::CatalogError; +use crate::session::OptimizerContext; +use crate::user::user_authentication::{encrypt_default, encrypted_password}; + +fn alter_prost_user_info( + mut user_info: UserInfo, + options: &UserOptions, +) -> Result { + let mut update_fields = Vec::new(); + for option in &options.0 { + match option { + UserOption::SuperUser => { + user_info.is_supper = true; + update_fields.push(UpdateField::Super as i32); + } + UserOption::NoSuperUser => { + user_info.is_supper = false; + update_fields.push(UpdateField::Super as i32); + } + UserOption::CreateDB => { + user_info.can_create_db = true; + update_fields.push(UpdateField::CreateDb as i32); + } + UserOption::NoCreateDB => { + user_info.can_create_db = false; + update_fields.push(UpdateField::CreateDb as i32); + } + UserOption::Login => { + user_info.can_login = true; + update_fields.push(UpdateField::Login as i32); + } + UserOption::NoLogin => { + user_info.can_login = false; + update_fields.push(UpdateField::Login as i32); + } + UserOption::EncryptedPassword(p) => { + if !p.0.is_empty() { + user_info.auth_info = Some(encrypt_default(&user_info.name, &p.0)); + update_fields.push(UpdateField::AuthInfo as i32); + } + } + UserOption::Password(opt) => { + if let Some(password) = opt { + user_info.auth_info = encrypted_password(&user_info.name, &password.0); + update_fields.push(UpdateField::AuthInfo as i32); + } + } + } + } + let request = UpdateUserRequest { + user: Some(user_info), + update_fields, + }; + Ok(request) +} + +pub async fn handle_alter_user( + context: OptimizerContext, + stmt: AlterUserStatement, +) -> Result { + let session = context.session_ctx; + let user_name = Binder::resolve_user_name(stmt.user_name.clone())?; + let mut old_info = { + let user_reader = session.env().user_info_reader(); + let reader = user_reader.read_guard(); + if let Some(origin_info) = reader.get_user_by_name(&user_name) { + origin_info.clone() + } else { + return Err(CatalogError::NotFound("user", user_name).into()); + } + }; + let request = match stmt.mode { + risingwave_sqlparser::ast::AlterUserMode::Options(options) => { + alter_prost_user_info(old_info, &options)? + } + risingwave_sqlparser::ast::AlterUserMode::Rename(new_name) => { + old_info.name = Binder::resolve_user_name(new_name)?; + UpdateUserRequest { + user: Some(old_info), + update_fields: vec![UpdateField::Rename as i32], + } + } + }; + let user_info_writer = session.env().user_info_writer(); + user_info_writer.update_user(request).await?; + Ok(PgResponse::empty_result(StatementType::UPDATE_USER)) +} + +#[cfg(test)] +mod tests { + use risingwave_pb::user::auth_info::EncryptionType; + use risingwave_pb::user::AuthInfo; + + use crate::test_utils::LocalFrontend; + + #[tokio::test] + async fn test_alter_user() { + let frontend = LocalFrontend::new(Default::default()).await; + let session = frontend.session_ref(); + let user_info_reader = session.env().user_info_reader(); + + frontend.run_sql("CREATE USER userB WITH SUPERUSER NOCREATEDB PASSWORD 'md5827ccb0eea8a706c4c34a16891f84e7b'").await.unwrap(); + frontend + .run_sql("ALTER USER userB RENAME TO user") + .await + .unwrap(); + assert!(user_info_reader + .read_guard() + .get_user_by_name("userB") + .is_none()); + assert!(user_info_reader + .read_guard() + .get_user_by_name("user") + .is_some()); + + frontend.run_sql("ALTER USER user WITH NOSUPERUSER CREATEDB PASSWORD 'md59f2fa6a30871a92249bdd2f1eeee4ef6'").await.unwrap(); + + let user_info = user_info_reader + .read_guard() + .get_user_by_name("user") + .cloned() + .unwrap(); + assert!(!user_info.is_supper); + assert!(user_info.can_create_db); + assert_eq!( + user_info.auth_info, + Some(AuthInfo { + encryption_type: EncryptionType::Md5 as i32, + encrypted_value: b"9f2fa6a30871a92249bdd2f1eeee4ef6".to_vec() + }) + ); + } +} diff --git a/src/frontend/src/handler/create_user.rs b/src/frontend/src/handler/create_user.rs index cc523bfdfc1fb..b8ec5773a25bf 100644 --- a/src/frontend/src/handler/create_user.rs +++ b/src/frontend/src/handler/create_user.rs @@ -15,16 +15,14 @@ use pgwire::pg_response::{PgResponse, StatementType}; use risingwave_common::error::Result; use risingwave_pb::user::UserInfo; -use risingwave_sqlparser::ast::{ - CreateUserOption, CreateUserStatement, CreateUserWithOptions, ObjectName, -}; +use risingwave_sqlparser::ast::{CreateUserStatement, ObjectName, UserOption, UserOptions}; use crate::binder::Binder; use crate::catalog::CatalogError; use crate::session::OptimizerContext; use crate::user::user_authentication::{encrypt_default, encrypted_password}; -fn make_prost_user_info(name: ObjectName, options: &CreateUserWithOptions) -> Result { +pub(crate) fn make_prost_user_info(name: ObjectName, options: &UserOptions) -> Result { let mut user_info = UserInfo { name: Binder::resolve_user_name(name)?, // the LOGIN option is implied if it is not explicitly specified. @@ -33,18 +31,18 @@ fn make_prost_user_info(name: ObjectName, options: &CreateUserWithOptions) -> Re }; for option in &options.0 { match option { - CreateUserOption::SuperUser => user_info.is_supper = true, - CreateUserOption::NoSuperUser => user_info.is_supper = false, - CreateUserOption::CreateDB => user_info.can_create_db = true, - CreateUserOption::NoCreateDB => user_info.can_create_db = false, - CreateUserOption::Login => user_info.can_login = true, - CreateUserOption::NoLogin => user_info.can_login = false, - CreateUserOption::EncryptedPassword(p) => { + UserOption::SuperUser => user_info.is_supper = true, + UserOption::NoSuperUser => user_info.is_supper = false, + UserOption::CreateDB => user_info.can_create_db = true, + UserOption::NoCreateDB => user_info.can_create_db = false, + UserOption::Login => user_info.can_login = true, + UserOption::NoLogin => user_info.can_login = false, + UserOption::EncryptedPassword(p) => { if !p.0.is_empty() { user_info.auth_info = Some(encrypt_default(&user_info.name, &p.0)); } } - CreateUserOption::Password(opt) => { + UserOption::Password(opt) => { if let Some(password) = opt { user_info.auth_info = encrypted_password(&user_info.name, &password.0); } diff --git a/src/frontend/src/handler/mod.rs b/src/frontend/src/handler/mod.rs index 0912dcdc299f8..11af119f7030a 100644 --- a/src/frontend/src/handler/mod.rs +++ b/src/frontend/src/handler/mod.rs @@ -21,6 +21,7 @@ use risingwave_sqlparser::ast::{DropStatement, ObjectType, Statement, WithProper use crate::session::{OptimizerContext, SessionImpl}; +pub mod alter_user; mod create_database; pub mod create_index; pub mod create_mv; @@ -84,6 +85,7 @@ pub async fn handle( .. } => create_schema::handle_create_schema(context, schema_name, if_not_exists).await, Statement::CreateUser(stmt) => create_user::handle_create_user(context, stmt).await, + Statement::AlterUser(stmt) => alter_user::handle_alter_user(context, stmt).await, Statement::Grant { .. } => handle_privilege::handle_grant_privilege(context, stmt).await, Statement::Revoke { .. } => handle_privilege::handle_revoke_privilege(context, stmt).await, Statement::Describe { name } => describe::handle_describe(context, name), diff --git a/src/frontend/src/test_utils.rs b/src/frontend/src/test_utils.rs index 503783936a59a..c13471ec8d1a9 100644 --- a/src/frontend/src/test_utils.rs +++ b/src/frontend/src/test_utils.rs @@ -33,7 +33,8 @@ use risingwave_pb::catalog::{ use risingwave_pb::common::ParallelUnitMapping; use risingwave_pb::meta::list_table_fragments_response::TableFragmentInfo; use risingwave_pb::stream_plan::StreamFragmentGraph; -use risingwave_pb::user::{GrantPrivilege, UserInfo}; +use risingwave_pb::user::update_user_request::UpdateField; +use risingwave_pb::user::{GrantPrivilege, UpdateUserRequest, UserInfo}; use risingwave_rpc_client::error::Result as RpcResult; use risingwave_sqlparser::ast::Statement; use risingwave_sqlparser::parser::Parser; @@ -402,6 +403,29 @@ impl UserInfoWriter for MockUserInfoWriter { Ok(()) } + async fn update_user(&self, request: UpdateUserRequest) -> Result<()> { + let mut lock = self.user_info.write(); + let update_user = request.user.unwrap(); + let id = update_user.get_id(); + let old_name = lock.get_user_name_by_id(id).unwrap(); + let mut user_info = lock.get_user_by_name(&old_name).unwrap().clone(); + request.update_fields.into_iter().for_each(|field| { + if field == UpdateField::Super as i32 { + user_info.is_supper = update_user.is_supper; + } else if field == UpdateField::Login as i32 { + user_info.can_login = update_user.can_login; + } else if field == UpdateField::CreateDb as i32 { + user_info.can_create_db = update_user.can_create_db; + } else if field == UpdateField::AuthInfo as i32 { + user_info.auth_info = update_user.auth_info.clone(); + } else if field == UpdateField::Rename as i32 { + user_info.name = update_user.name.clone(); + } + }); + lock.update_user(update_user); + Ok(()) + } + /// In `MockUserInfoWriter`, we don't support expand privilege with `GrantAllTables` and /// `GrantAllSources` when grant privilege to user. async fn grant_privilege( diff --git a/src/frontend/src/user/user_manager.rs b/src/frontend/src/user/user_manager.rs index c6dc41d8606be..bd2c396d7c20d 100644 --- a/src/frontend/src/user/user_manager.rs +++ b/src/frontend/src/user/user_manager.rs @@ -72,7 +72,12 @@ impl UserInfoManager { pub fn update_user(&mut self, user_info: UserInfo) { let id = user_info.id; let name = user_info.name.clone(); - self.user_by_name.insert(name.clone(), user_info).unwrap(); + if let Some(old_name) = self.get_user_name_by_id(id) { + self.user_by_name.remove(&old_name); + self.user_by_name.insert(name.clone(), user_info); + } else { + self.user_by_name.insert(name.clone(), user_info).unwrap(); + } self.user_name_by_id.insert(id, name).unwrap(); } diff --git a/src/frontend/src/user/user_service.rs b/src/frontend/src/user/user_service.rs index 915f1fd376dcb..06058cca771d4 100644 --- a/src/frontend/src/user/user_service.rs +++ b/src/frontend/src/user/user_service.rs @@ -18,7 +18,7 @@ use parking_lot::lock_api::ArcRwLockReadGuard; use parking_lot::{RawRwLock, RwLock}; use risingwave_common::error::ErrorCode::InternalError; use risingwave_common::error::{Result, RwError}; -use risingwave_pb::user::{GrantPrivilege, UserInfo}; +use risingwave_pb::user::{GrantPrivilege, UpdateUserRequest, UserInfo}; use risingwave_rpc_client::MetaClient; use tokio::sync::watch::Receiver; @@ -45,6 +45,8 @@ pub trait UserInfoWriter: Send + Sync { async fn drop_user(&self, id: UserId) -> Result<()>; + async fn update_user(&self, request: UpdateUserRequest) -> Result<()>; + async fn grant_privilege( &self, users: Vec, @@ -82,6 +84,11 @@ impl UserInfoWriter for UserInfoWriterImpl { self.wait_version(version).await } + async fn update_user(&self, request: UpdateUserRequest) -> Result<()> { + let version = self.meta_client.update_user(request).await?; + self.wait_version(version).await + } + async fn grant_privilege( &self, users: Vec, diff --git a/src/meta/src/manager/user.rs b/src/meta/src/manager/user.rs index da4c751b0a376..905a7b985e495 100644 --- a/src/meta/src/manager/user.rs +++ b/src/meta/src/manager/user.rs @@ -23,6 +23,7 @@ use risingwave_common::error::ErrorCode::{InternalError, PermissionDenied}; use risingwave_common::error::{Result, RwError}; use risingwave_pb::meta::subscribe_response::{Info, Operation}; use risingwave_pb::user::grant_privilege::{ActionWithGrantOption, Object}; +use risingwave_pb::user::update_user_request::UpdateField; use risingwave_pb::user::{GrantPrivilege, UserInfo}; use tokio::sync::{Mutex, MutexGuard}; @@ -81,6 +82,25 @@ impl UserManagerInner { self.user_info.insert(user.id, user); } + fn update_user(&mut self, update_user: &UserInfo, update_fields: &[UpdateField]) -> UserInfo { + let mut user = self.user_info.get(&update_user.id).unwrap().clone(); + update_fields.iter().for_each(|&field| match field { + UpdateField::Unknown => unreachable!(), + UpdateField::Super => user.is_supper = update_user.is_supper, + UpdateField::Login => user.can_login = update_user.can_login, + UpdateField::CreateDb => user.can_create_db = update_user.can_create_db, + UpdateField::AuthInfo => user.auth_info = update_user.auth_info.clone(), + UpdateField::Rename => { + self.all_users.remove(&user.name); + user.name = update_user.name.clone(); + self.all_users.insert(update_user.name.clone()); + } + }); + + self.user_info.insert(update_user.id, user.clone()); + user + } + fn drop_user(&mut self, user_id: UserId) { // user in user_grant_relation (as key or value) are already checked before entering this // function. @@ -162,6 +182,32 @@ impl UserManager { Ok(version) } + pub async fn update_user( + &self, + user: &UserInfo, + update_fields: &[UpdateField], + ) -> Result { + let mut core = self.core.lock().await; + let rename_flag = update_fields + .iter() + .any(|&field| field == UpdateField::Rename); + if rename_flag && core.all_users.contains(&user.name) { + return Err(RwError::from(PermissionDenied(format!( + "User {} already exists", + user.name + )))); + } + user.insert(self.env.meta_store()).await?; + let new_user = core.update_user(user, update_fields); + + let version = self + .env + .notification_manager() + .notify_frontend(Operation::Update, Info::User(new_user)) + .await; + Ok(version) + } + pub async fn get_user(&self, id: UserId) -> Result { let core = self.core.lock().await; diff --git a/src/meta/src/rpc/service/user_service.rs b/src/meta/src/rpc/service/user_service.rs index 10da0129f1873..7d8249a217d82 100644 --- a/src/meta/src/rpc/service/user_service.rs +++ b/src/meta/src/rpc/service/user_service.rs @@ -12,12 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use itertools::Itertools; use risingwave_common::error::{tonic_err, Result as RwResult}; use risingwave_pb::user::grant_privilege::Object; +use risingwave_pb::user::update_user_request::UpdateField; use risingwave_pb::user::user_service_server::UserService; use risingwave_pb::user::{ CreateUserRequest, CreateUserResponse, DropUserRequest, DropUserResponse, GrantPrivilege, GrantPrivilegeRequest, GrantPrivilegeResponse, RevokePrivilegeRequest, RevokePrivilegeResponse, + UpdateUserRequest, UpdateUserResponse, }; use tonic::{Request, Response, Status}; @@ -144,6 +147,30 @@ impl UserService for UserServiceImpl { })) } + #[cfg_attr(coverage, no_coverage)] + async fn update_user( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let update_fields = req + .update_fields + .iter() + .map(|i| UpdateField::from_i32(*i).unwrap()) + .collect_vec(); + let user = req.get_user().map_err(tonic_err)?.clone(); + let version = self + .user_manager + .update_user(&user, &update_fields) + .await + .map_err(tonic_err)?; + + Ok(Response::new(UpdateUserResponse { + status: None, + version, + })) + } + #[cfg_attr(coverage, no_coverage)] async fn grant_privilege( &self, diff --git a/src/rpc_client/src/meta_client.rs b/src/rpc_client/src/meta_client.rs index 5ca1ba87ff051..b6c4d887beae2 100644 --- a/src/rpc_client/src/meta_client.rs +++ b/src/rpc_client/src/meta_client.rs @@ -256,6 +256,11 @@ impl MetaClient { Ok(resp.version) } + pub async fn update_user(&self, request: UpdateUserRequest) -> Result { + let resp = self.inner.update_user(request).await?; + Ok(resp.version) + } + pub async fn grant_privilege( &self, user_ids: Vec, @@ -635,6 +640,7 @@ macro_rules! for_all_meta_rpc { ,{ hummock_client, get_compaction_groups, GetCompactionGroupsRequest, GetCompactionGroupsResponse } ,{ hummock_client, trigger_manual_compaction, TriggerManualCompactionRequest, TriggerManualCompactionResponse } ,{ user_client, create_user, CreateUserRequest, CreateUserResponse } + ,{ user_client, update_user, UpdateUserRequest, UpdateUserResponse } ,{ user_client, drop_user, DropUserRequest, DropUserResponse } ,{ user_client, grant_privilege, GrantPrivilegeRequest, GrantPrivilegeResponse } ,{ user_client, revoke_privilege, RevokePrivilegeRequest, RevokePrivilegeResponse } diff --git a/src/sqlparser/src/ast/mod.rs b/src/sqlparser/src/ast/mod.rs index 8bdc45a712924..440a305951123 100644 --- a/src/sqlparser/src/ast/mod.rs +++ b/src/sqlparser/src/ast/mod.rs @@ -939,6 +939,8 @@ pub enum Statement { }, /// CREATE USER CreateUser(CreateUserStatement), + /// ALTER USER + AlterUser(AlterUserStatement), /// FLUSH the current barrier. /// /// Note: RisingWave specific statement. @@ -1329,6 +1331,9 @@ impl fmt::Display for Statement { Statement::CreateUser(statement) => { write!(f, "CREATE USER {}", statement) } + Statement::AlterUser(statement) => { + write!(f, "ALTER USER {}", statement) + } Statement::Flush => { write!(f, "FLUSH") } diff --git a/src/sqlparser/src/ast/statement.rs b/src/sqlparser/src/ast/statement.rs index 03dacce499422..4c1cb6163f725 100644 --- a/src/sqlparser/src/ast/statement.rs +++ b/src/sqlparser/src/ast/statement.rs @@ -343,12 +343,26 @@ impl From> for Option { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CreateUserStatement { pub user_name: ObjectName, - pub with_options: CreateUserWithOptions, + pub with_options: UserOptions, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum CreateUserOption { +pub struct AlterUserStatement { + pub user_name: ObjectName, + pub mode: AlterUserMode, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum AlterUserMode { + Options(UserOptions), + Rename(ObjectName), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum UserOption { SuperUser, NoSuperUser, CreateDB, @@ -359,27 +373,27 @@ pub enum CreateUserOption { Password(Option), } -impl fmt::Display for CreateUserOption { +impl fmt::Display for UserOption { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - CreateUserOption::SuperUser => write!(f, "SUPERUSER"), - CreateUserOption::NoSuperUser => write!(f, "NOSUPERUSER"), - CreateUserOption::CreateDB => write!(f, "CREATEDB"), - CreateUserOption::NoCreateDB => write!(f, "NOCREATEDB"), - CreateUserOption::Login => write!(f, "LOGIN"), - CreateUserOption::NoLogin => write!(f, "NOLOGIN"), - CreateUserOption::EncryptedPassword(p) => write!(f, "ENCRYPTED PASSWORD {}", p), - CreateUserOption::Password(None) => write!(f, "PASSWORD NULL"), - CreateUserOption::Password(Some(p)) => write!(f, "PASSWORD {}", p), + UserOption::SuperUser => write!(f, "SUPERUSER"), + UserOption::NoSuperUser => write!(f, "NOSUPERUSER"), + UserOption::CreateDB => write!(f, "CREATEDB"), + UserOption::NoCreateDB => write!(f, "NOCREATEDB"), + UserOption::Login => write!(f, "LOGIN"), + UserOption::NoLogin => write!(f, "NOLOGIN"), + UserOption::EncryptedPassword(p) => write!(f, "ENCRYPTED PASSWORD {}", p), + UserOption::Password(None) => write!(f, "PASSWORD NULL"), + UserOption::Password(Some(p)) => write!(f, "PASSWORD {}", p), } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct CreateUserWithOptions(pub Vec); +pub struct UserOptions(pub Vec); -impl ParseTo for CreateUserWithOptions { +impl ParseTo for UserOptions { fn parse_to(parser: &mut Parser) -> Result { let mut options = vec![]; if parser.parse_keyword(Keyword::WITH) { @@ -392,22 +406,22 @@ impl ParseTo for CreateUserWithOptions { if let Token::Word(ref w) = token { parser.next_token(); let option = match w.keyword { - Keyword::SUPERUSER => CreateUserOption::SuperUser, - Keyword::NOSUPERUSER => CreateUserOption::NoSuperUser, - Keyword::CREATEDB => CreateUserOption::CreateDB, - Keyword::NOCREATEDB => CreateUserOption::NoCreateDB, - Keyword::LOGIN => CreateUserOption::Login, - Keyword::NOLOGIN => CreateUserOption::NoLogin, + Keyword::SUPERUSER => UserOption::SuperUser, + Keyword::NOSUPERUSER => UserOption::NoSuperUser, + Keyword::CREATEDB => UserOption::CreateDB, + Keyword::NOCREATEDB => UserOption::NoCreateDB, + Keyword::LOGIN => UserOption::Login, + Keyword::NOLOGIN => UserOption::NoLogin, Keyword::PASSWORD => { if parser.parse_keyword(Keyword::NULL) { - CreateUserOption::Password(None) + UserOption::Password(None) } else { - CreateUserOption::Password(Some(AstString::parse_to(parser)?)) + UserOption::Password(Some(AstString::parse_to(parser)?)) } } Keyword::ENCRYPTED => { parser.expect_keyword(Keyword::PASSWORD)?; - CreateUserOption::EncryptedPassword(AstString::parse_to(parser)?) + UserOption::EncryptedPassword(AstString::parse_to(parser)?) } _ => parser.expected( "SUPERUSER | NOSUPERUSER | CREATEDB | NOCREATEDB | LOGIN \ @@ -429,7 +443,7 @@ impl ParseTo for CreateUserWithOptions { } } -impl fmt::Display for CreateUserWithOptions { +impl fmt::Display for UserOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if !self.0.is_empty() { write!(f, "WITH {}", display_separated(self.0.as_slice(), " ")) @@ -442,7 +456,7 @@ impl fmt::Display for CreateUserWithOptions { impl ParseTo for CreateUserStatement { fn parse_to(p: &mut Parser) -> Result { impl_parse_to!(user_name: ObjectName, p); - impl_parse_to!(with_options: CreateUserWithOptions, p); + impl_parse_to!(with_options: UserOptions, p); Ok(CreateUserStatement { user_name, @@ -460,6 +474,50 @@ impl fmt::Display for CreateUserStatement { } } +impl fmt::Display for AlterUserMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AlterUserMode::Options(options) => { + write!(f, "{}", options) + } + AlterUserMode::Rename(new_name) => { + write!(f, "RENAME TO {}", new_name) + } + } + } +} + +impl fmt::Display for AlterUserStatement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut v: Vec = vec![]; + impl_fmt_display!(user_name, v, self); + impl_fmt_display!(mode, v, self); + v.iter().join(" ").fmt(f) + } +} + +impl ParseTo for AlterUserStatement { + fn parse_to(p: &mut Parser) -> Result { + impl_parse_to!(user_name: ObjectName, p); + impl_parse_to!(mode: AlterUserMode, p); + + Ok(AlterUserStatement { user_name, mode }) + } +} + +impl ParseTo for AlterUserMode { + fn parse_to(p: &mut Parser) -> Result { + if p.parse_keyword(Keyword::RENAME) { + p.expect_keyword(Keyword::TO)?; + impl_parse_to!(new_name: ObjectName, p); + Ok(AlterUserMode::Rename(new_name)) + } else { + impl_parse_to!(with_options: UserOptions, p); + Ok(AlterUserMode::Options(with_options)) + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct DropStatement { diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index dd11ba880e6c9..b7e062b8308f5 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -1798,8 +1798,17 @@ impl Parser { } pub fn parse_alter(&mut self) -> Result { - self.expect_keyword(Keyword::TABLE)?; - self.parse_alter_table() + if self.parse_keyword(Keyword::TABLE) { + self.parse_alter_table() + } else if self.parse_keyword(Keyword::USER) { + self.parse_alter_user() + } else { + self.expected("TABLE or USER after ALTER", self.peek_token()) + } + } + + pub fn parse_alter_user(&mut self) -> Result { + Ok(Statement::AlterUser(AlterUserStatement::parse_to(self)?)) } pub fn parse_alter_table(&mut self) -> Result { diff --git a/src/sqlparser/tests/sqlparser_common.rs b/src/sqlparser/tests/sqlparser_common.rs index bd1548638efab..8db26ae6469ce 100644 --- a/src/sqlparser/tests/sqlparser_common.rs +++ b/src/sqlparser/tests/sqlparser_common.rs @@ -3090,10 +3090,10 @@ fn parse_create_user() { assert_eq!( stmt.with_options.0, vec![ - CreateUserOption::NoSuperUser, - CreateUserOption::CreateDB, - CreateUserOption::Login, - CreateUserOption::Password(Some(AstString( + UserOption::NoSuperUser, + UserOption::CreateDB, + UserOption::Login, + UserOption::Password(Some(AstString( "md5827ccb0eea8a706c4c34a16891f84e7b".into() ))), ] diff --git a/src/sqlparser/tests/testdata/create.yaml b/src/sqlparser/tests/testdata/create.yaml index 48dfba05fc1a0..17abcee641bc2 100644 --- a/src/sqlparser/tests/testdata/create.yaml +++ b/src/sqlparser/tests/testdata/create.yaml @@ -48,6 +48,12 @@ - input: CREATE USER user WITH SUPERUSER CREATEDB PASSWORD 'password' formatted_sql: CREATE USER user WITH SUPERUSER CREATEDB PASSWORD 'password' +- input: ALTER USER user WITH SUPERUSER CREATEDB PASSWORD 'password' + formatted_sql: ALTER USER user WITH SUPERUSER CREATEDB PASSWORD 'password' + +- input: ALTER USER user RENAME TO another + formatted_sql: ALTER USER user RENAME TO another + - input: CREATE SINK snk error_msg: | sql parser error: Expected FROM, found: EOF diff --git a/src/utils/pgwire/src/pg_response.rs b/src/utils/pgwire/src/pg_response.rs index 0686e73af418a..99b592f850cd3 100644 --- a/src/utils/pgwire/src/pg_response.rs +++ b/src/utils/pgwire/src/pg_response.rs @@ -53,6 +53,7 @@ pub enum StatementType { SHOW_PARAMETERS, SHOW_COMMAND, START_TRANSACTION, + UPDATE_USER, ABORT, FLUSH, OTHER,