From 7edcaef97545e8d169d58e33114aab023ae398ba Mon Sep 17 00:00:00 2001 From: taichong Date: Thu, 21 Apr 2022 11:29:08 +0800 Subject: [PATCH] impl alter database rename --- common/ast/src/ast/statement.rs | 26 ++++ common/ast/src/parser/statement.rs | 24 ++++ common/meta/api/src/schema_api.rs | 7 + common/meta/api/src/schema_api_impl.rs | 96 +++++++++++++ common/meta/api/src/schema_api_test_suite.rs | 135 ++++++++++++++++++ .../meta/embedded/tests/it/schema_api_impl.rs | 6 + common/meta/grpc/src/grpc_action.rs | 8 ++ .../tests/it/state_machine/schema_api_impl.rs | 10 ++ common/meta/types/src/cmd.rs | 1 - common/meta/types/src/database.rs | 22 +++ common/meta/types/src/lib.rs | 2 + common/planners/src/lib.rs | 3 + common/planners/src/plan_database_rename.rs | 38 +++++ common/planners/src/plan_node.rs | 4 + .../planners/src/plan_node_display_indent.rs | 15 ++ common/planners/src/plan_node_rewriter.rs | 5 + common/planners/src/plan_node_visitor.rs | 6 + .../00-ddl/10-database/ddl-alter-database.md | 45 ++++++ .../tests/it/grpc/metasrv_grpc_schema_api.rs | 12 ++ query/src/catalogs/catalog.rs | 4 + .../catalogs/default/backends/meta_backend.rs | 10 ++ .../src/catalogs/default/database_catalog.rs | 25 ++++ .../src/catalogs/default/immutable_catalog.rs | 6 + query/src/catalogs/default/mutable_catalog.rs | 7 + query/src/catalogs/hive/hive_catalog.rs | 6 + .../interpreter_database_rename.rs | 70 +++++++++ query/src/interpreters/interpreter_factory.rs | 2 + query/src/interpreters/mod.rs | 2 + query/src/sql/parsers/parser_database.rs | 24 ++++ query/src/sql/sql_parser.rs | 1 + query/src/sql/sql_statement.rs | 2 + .../src/sql/statements/analyzer_statement.rs | 1 + query/src/sql/statements/mod.rs | 3 + .../statements/statement_alter_database.rs | 65 +++++++++ query/tests/it/catalogs/database_catalog.rs | 40 +++++- query/tests/it/catalogs/immutable_catalogs.rs | 27 ++++ .../interpreter_database_rename.rs | 60 ++++++++ query/tests/it/interpreters/mod.rs | 1 + query/tests/it/sql/plan_parser.rs | 38 +++-- .../05_ddl/05_0003_ddl_alter_database.result | 32 +++++ .../05_ddl/05_0003_ddl_alter_database.sql | 44 ++++++ 41 files changed, 922 insertions(+), 13 deletions(-) create mode 100644 common/planners/src/plan_database_rename.rs create mode 100644 docs/doc/30-reference/30-sql/00-ddl/10-database/ddl-alter-database.md create mode 100644 query/src/interpreters/interpreter_database_rename.rs create mode 100644 query/src/sql/statements/statement_alter_database.rs create mode 100644 query/tests/it/interpreters/interpreter_database_rename.rs create mode 100644 tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.result create mode 100644 tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.sql diff --git a/common/ast/src/ast/statement.rs b/common/ast/src/ast/statement.rs index 5a8b427fc5893..8aac85ff1a4ad 100644 --- a/common/ast/src/ast/statement.rs +++ b/common/ast/src/ast/statement.rs @@ -53,6 +53,11 @@ pub enum Statement<'a> { if_exists: bool, database: Identifier, }, + AlterDatabase { + if_exists: bool, + database: Identifier, + action: AlterDatabaseAction, + }, UseDatabase { database: Identifier, }, @@ -249,6 +254,11 @@ pub struct ColumnDefinition { pub default_expr: Option>, } +#[derive(Debug, Clone, PartialEq)] +pub enum AlterDatabaseAction { + RenameDatabase { new_db: Identifier }, +} + #[derive(Debug, Clone, PartialEq)] pub enum AlterTableAction { RenameTable { new_table: Identifier }, @@ -388,6 +398,22 @@ impl<'a> Display for Statement<'a> { } write!(f, " {database}")?; } + Statement::AlterDatabase { + if_exists, + database, + action, + } => { + write!(f, "ALTER DATABASE")?; + if *if_exists { + write!(f, "IF EXISTS")?; + } + write!(f, "{database}")?; + match action { + AlterDatabaseAction::RenameDatabase { new_db } => { + write!(f, " RENAME TO {new_db}")?; + } + } + } Statement::UseDatabase { database } => { write!(f, "USE {database}")?; } diff --git a/common/ast/src/parser/statement.rs b/common/ast/src/parser/statement.rs index 8a3116a8543a4..8cf2516b400cf 100644 --- a/common/ast/src/parser/statement.rs +++ b/common/ast/src/parser/statement.rs @@ -84,6 +84,16 @@ pub fn statement(i: Input) -> IResult { database, }, ); + let alter_database = map( + rule! { + ALTER ~ DATABASE ~ ( IF ~ EXISTS )? ~ #ident ~ #alter_database_action + }, + |(_, _, opt_if_exists, database, action)| Statement::AlterDatabase { + if_exists: opt_if_exists.is_some(), + database, + action, + }, + ); let use_database = map( rule! { USE ~ #ident @@ -403,6 +413,7 @@ pub fn statement(i: Input) -> IResult { | #show_create_database : "`SHOW CREATE DATABASE `" | #create_database : "`CREATE DATABASE [IF NOT EXIST] [ENGINE = ]`" | #drop_database : "`DROP DATABASE [IF EXISTS] `" + | #alter_database : "`ALTER DATABASE [IF EXISTS] `" | #use_database : "`USE `" | #show_tables : "`SHOW [FULL] TABLES [FROM ] []`" | #show_create_table : "`SHOW CREATE TABLE [.]`" @@ -530,6 +541,19 @@ pub fn create_table_source(i: Input) -> IResult { )(i) } +pub fn alter_database_action(i: Input) -> IResult { + let mut rename_database = map( + rule! { + RENAME ~ TO ~ #ident + }, + |(_, _, new_db)| AlterDatabaseAction::RenameDatabase { new_db }, + ); + + rule!( + #rename_database + )(i) +} + pub fn alter_table_action(i: Input) -> IResult { let mut rename_table = map( rule! { diff --git a/common/meta/api/src/schema_api.rs b/common/meta/api/src/schema_api.rs index 5d7851daf956c..56efcc80487ab 100644 --- a/common/meta/api/src/schema_api.rs +++ b/common/meta/api/src/schema_api.rs @@ -30,6 +30,8 @@ use common_meta_types::ListDatabaseReq; use common_meta_types::ListTableReq; use common_meta_types::MetaError; use common_meta_types::MetaId; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::TableIdent; @@ -57,6 +59,11 @@ pub trait SchemaApi: Send + Sync { req: ListDatabaseReq, ) -> Result>, MetaError>; + async fn rename_database( + &self, + req: RenameDatabaseReq, + ) -> Result; + // table async fn create_table(&self, req: CreateTableReq) -> Result; diff --git a/common/meta/api/src/schema_api_impl.rs b/common/meta/api/src/schema_api_impl.rs index a6110eb3e7191..68f029083573f 100644 --- a/common/meta/api/src/schema_api_impl.rs +++ b/common/meta/api/src/schema_api_impl.rs @@ -44,6 +44,8 @@ use common_meta_types::MatchSeqExt; use common_meta_types::MetaError; use common_meta_types::MetaId; use common_meta_types::Operation; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::TableAlreadyExists; @@ -192,6 +194,81 @@ impl SchemaApi for KV { } } + async fn rename_database( + &self, + req: RenameDatabaseReq, + ) -> Result { + let tenant_dbname = &req.name_ident; + let tenant_newdbname = DatabaseNameIdent { + tenant: tenant_dbname.tenant.clone(), + db_name: req.new_db_name.clone(), + }; + + loop { + // get old db, not exists return err + let res = get_db_or_err( + self, + tenant_dbname, + format!("rename_database: {}", &tenant_dbname), + ) + .await; + + let (_, old_db_id, _, old_db_meta) = match res { + Ok(x) => x, + Err(e) => { + if let MetaError::AppError(AppError::UnknownDatabase(_)) = e { + if req.if_exists { + // TODO(like rename table): if database not exists but sql hint if_exists is true + // the sql must be Ok but should not return database_id + return Ok(RenameDatabaseReply { db_id: 0 }); + } + } + return Err(e); + } + }; + + tracing::debug!( + old_db_id, + tenant_dbname = debug(&tenant_dbname), + "rename_database" + ); + let old_id_key = DatabaseId { db_id: old_db_id }; + + // get new db, exists return err + let (db_id_seq, _db_id) = get_id_value(self, &tenant_newdbname).await?; + db_has_to_not_exist(db_id_seq, &tenant_newdbname, "rename_database")?; + + let new_db_id = fetch_id(self, DatabaseIdGen {}).await?; + + // rename database + { + let txn_req = TxnRequest { + condition: vec![txn_cond_seq(&tenant_newdbname, Eq, 0)?], + if_then: vec![ + txn_op_del(tenant_dbname)?, // del old_db_name + txn_op_put(&tenant_newdbname, serialize_id(old_db_id)?)?, // (tenant, new_db_name) -> old_db_id + txn_op_put(&old_id_key, serialize_struct(&old_db_meta)?)?, // (db_id) -> old_db_meta + ], + else_then: vec![], + }; + + let (succ, _responses) = send_txn(self, txn_req).await?; + + tracing::debug!( + name = debug(&tenant_dbname), + to = debug(&tenant_newdbname), + database_id = debug(&old_db_id), + succ = display(succ), + "rename_database" + ); + + if succ { + return Ok(RenameDatabaseReply { db_id: new_db_id }); + } + } + } + } + async fn get_database(&self, req: GetDatabaseReq) -> Result, MetaError> { let name_key = &req.inner; @@ -820,6 +897,25 @@ fn table_has_to_exist( } } +/// Return OK if a db_id or db_meta does not exist by checking the seq. +/// +/// Otherwise returns DatabaseAlreadyExists error +fn db_has_to_not_exist( + seq: u64, + name_ident: &DatabaseNameIdent, + ctx: impl Display, +) -> Result<(), MetaError> { + if seq == 0 { + Ok(()) + } else { + tracing::debug!(seq, ?name_ident, "exist"); + + Err(MetaError::AppError(AppError::DatabaseAlreadyExists( + DatabaseAlreadyExists::new(&name_ident.db_name, format!("{}: {}", ctx, name_ident)), + ))) + } +} + /// Return OK if a table_id or table_meta does not exist by checking the seq. /// /// Otherwise returns TableAlreadyExists error diff --git a/common/meta/api/src/schema_api_test_suite.rs b/common/meta/api/src/schema_api_test_suite.rs index e54aae93704ec..c7d0168e48983 100644 --- a/common/meta/api/src/schema_api_test_suite.rs +++ b/common/meta/api/src/schema_api_test_suite.rs @@ -28,6 +28,7 @@ use common_meta_types::GetDatabaseReq; use common_meta_types::GetTableReq; use common_meta_types::ListDatabaseReq; use common_meta_types::ListTableReq; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReq; use common_meta_types::TableIdent; use common_meta_types::TableInfo; @@ -424,6 +425,140 @@ impl SchemaApiTestSuite { Ok(()) } + pub async fn database_rename(self, mt: &MT) -> anyhow::Result<()> { + let tenant = "tenant1"; + let db_name = "db1"; + let db2_name = "db2"; + let new_db_name = "db3"; + + tracing::info!("--- rename not exists db1 to not exists db2"); + { + let req = RenameDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: db_name.to_string(), + }, + new_db_name: new_db_name.to_string(), + }; + + let res = mt.rename_database(req).await; + tracing::info!("rename database res: {:?}", res); + assert!(res.is_err()); + assert_eq!( + ErrorCode::UnknownDatabase("").code(), + ErrorCode::from(res.unwrap_err()).code() + ); + } + + tracing::info!("--- prepare db1 and db2"); + { + // prepare db2 + let res = self.create_database(mt, tenant, "db1", "eng1").await?; + assert_eq!(1, res.db_id); + + tracing::info!("--- rename not exists db4 to exists db1"); + { + let req = RenameDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: "db4".to_string(), + }, + new_db_name: db_name.to_string(), + }; + + let res = mt.rename_database(req).await; + tracing::info!("rename database res: {:?}", res); + assert!(res.is_err()); + assert_eq!( + ErrorCode::UnknownDatabase("").code(), + ErrorCode::from(res.unwrap_err()).code() + ); + } + + // prepare db2 + let res = self.create_database(mt, tenant, "db2", "eng1").await?; + assert!(res.db_id > 1); + } + + tracing::info!("--- rename exists db db1 to exists db db2"); + { + let req = RenameDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: db_name.to_string(), + }, + new_db_name: db2_name.to_string(), + }; + + let res = mt.rename_database(req).await; + tracing::info!("rename database res: {:?}", res); + assert!(res.is_err()); + assert_eq!( + ErrorCode::DatabaseAlreadyExists("").code(), + ErrorCode::from(res.unwrap_err()).code() + ); + } + + tracing::info!("--- rename exists db db1 to not exists mutable db"); + { + let req = RenameDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: db_name.to_string(), + }, + + new_db_name: new_db_name.to_string(), + }; + let res = mt.rename_database(req).await; + tracing::info!("rename database res: {:?}", res); + assert!(res.is_ok()); + + let res = mt + .get_database(GetDatabaseReq::new(tenant, new_db_name)) + .await; + tracing::debug!("get present database res: {:?}", res); + let res = res?; + assert_eq!(1, res.ident.db_id, "db3 id is 1"); + assert_eq!("db3".to_string(), res.name_ident.db_name, "db3.db is db3"); + + tracing::info!("--- get old database after rename"); + { + let res = mt.get_database(GetDatabaseReq::new(tenant, db_name)).await; + let err = res.err().unwrap(); + assert_eq!( + ErrorCode::UnknownDatabase("").code(), + ErrorCode::from(err).code() + ); + } + } + + tracing::info!("--- drop db2 and db1"); + { + mt.drop_database(DropDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: db2_name.to_string(), + }, + }) + .await?; + mt.drop_database(DropDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: new_db_name.to_string(), + }, + }) + .await?; + } + + Ok(()) + } + pub async fn table_create_get_drop(&self, mt: &MT) -> anyhow::Result<()> { let tenant = "tenant1"; let db_name = "db1"; diff --git a/common/meta/embedded/tests/it/schema_api_impl.rs b/common/meta/embedded/tests/it/schema_api_impl.rs index 2e807683397d9..def811357aa4c 100644 --- a/common/meta/embedded/tests/it/schema_api_impl.rs +++ b/common/meta/embedded/tests/it/schema_api_impl.rs @@ -44,6 +44,12 @@ async fn test_meta_embedded_database_list_in_diff_tenant() -> anyhow::Result<()> .await } +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_meta_embedded_database_rename() -> anyhow::Result<()> { + let mt = MetaEmbedded::new_temp().await?; + SchemaApiTestSuite {}.database_rename(&mt).await +} + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_meta_embedded_table_create_get_drop() -> anyhow::Result<()> { let mt = MetaEmbedded::new_temp().await?; diff --git a/common/meta/grpc/src/grpc_action.rs b/common/meta/grpc/src/grpc_action.rs index 5380792d45c9a..9e77871be0e22 100644 --- a/common/meta/grpc/src/grpc_action.rs +++ b/common/meta/grpc/src/grpc_action.rs @@ -39,6 +39,8 @@ use common_meta_types::ListTableReq; use common_meta_types::MGetKVActionReply; use common_meta_types::MetaId; use common_meta_types::PrefixListReply; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::ShareInfo; @@ -179,6 +181,12 @@ impl RequestFor for DropDatabaseReq { type Reply = DropDatabaseReply; } +impl RequestFor for RenameDatabaseReq { + type Reply = RenameDatabaseReply; +} + +// == table actions == + impl RequestFor for CreateTableReq { type Reply = CreateTableReply; } diff --git a/common/meta/raft-store/tests/it/state_machine/schema_api_impl.rs b/common/meta/raft-store/tests/it/state_machine/schema_api_impl.rs index 8d910a0e3ef0c..25e9a09e10f73 100644 --- a/common/meta/raft-store/tests/it/state_machine/schema_api_impl.rs +++ b/common/meta/raft-store/tests/it/state_machine/schema_api_impl.rs @@ -62,6 +62,16 @@ async fn test_meta_embedded_database_list_in_diff_tenant() -> anyhow::Result<()> .await } +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_meta_embedded_database_rename() -> anyhow::Result<()> { + let (_log_guards, ut_span) = init_raft_store_ut!(); + let _ent = ut_span.enter(); + let tc = new_raft_test_context(); + let sm = StateMachine::open(&tc.raft_config, 1).await?; + + SchemaApiTestSuite {}.database_rename(&sm).await +} + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_meta_embedded_table_create_get_drop() -> anyhow::Result<()> { let (_log_guards, ut_span) = init_raft_store_ut!(); diff --git a/common/meta/types/src/cmd.rs b/common/meta/types/src/cmd.rs index 7c12e92ad99cb..80635ed6e2130 100644 --- a/common/meta/types/src/cmd.rs +++ b/common/meta/types/src/cmd.rs @@ -77,7 +77,6 @@ impl fmt::Display for Cmd { Cmd::RemoveNode { node_id } => { write!(f, "remove_node:{}", node_id) } - Cmd::UpsertKV { key, seq, diff --git a/common/meta/types/src/database.rs b/common/meta/types/src/database.rs index 8db21618e2775..92f99a7953fa0 100644 --- a/common/meta/types/src/database.rs +++ b/common/meta/types/src/database.rs @@ -118,6 +118,28 @@ pub struct CreateDatabaseReply { pub db_id: u64, } +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)] +pub struct RenameDatabaseReq { + pub if_exists: bool, + pub name_ident: DatabaseNameIdent, + pub new_db_name: String, +} + +impl Display for RenameDatabaseReq { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "rename_database:{}/{}=>{}", + self.name_ident.tenant, self.name_ident.db_name, self.new_db_name + ) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)] +pub struct RenameDatabaseReply { + pub db_id: u64, +} + #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)] pub struct DropDatabaseReq { pub if_exists: bool, diff --git a/common/meta/types/src/lib.rs b/common/meta/types/src/lib.rs index c6af53d5efb3e..6fe09fb8dcd98 100644 --- a/common/meta/types/src/lib.rs +++ b/common/meta/types/src/lib.rs @@ -85,6 +85,8 @@ pub use database::DropDatabaseReply; pub use database::DropDatabaseReq; pub use database::GetDatabaseReq; pub use database::ListDatabaseReq; +pub use database::RenameDatabaseReply; +pub use database::RenameDatabaseReq; pub use endpoint::Endpoint; pub use errors::ConflictSeq; pub use kv_message::GetKVActionReply; diff --git a/common/planners/src/lib.rs b/common/planners/src/lib.rs index 5d35a72cdda52..01940c5a846d4 100644 --- a/common/planners/src/lib.rs +++ b/common/planners/src/lib.rs @@ -19,6 +19,7 @@ mod plan_call; mod plan_copy; mod plan_database_create; mod plan_database_drop; +mod plan_database_rename; mod plan_database_show_create; mod plan_empty; mod plan_explain; @@ -107,6 +108,8 @@ pub use plan_copy::CopyPlan; pub use plan_copy::ValidationMode; pub use plan_database_create::CreateDatabasePlan; pub use plan_database_drop::DropDatabasePlan; +pub use plan_database_rename::RenameDatabaseEntity; +pub use plan_database_rename::RenameDatabasePlan; pub use plan_database_show_create::ShowCreateDatabasePlan; pub use plan_empty::EmptyPlan; pub use plan_explain::ExplainPlan; diff --git a/common/planners/src/plan_database_rename.rs b/common/planners/src/plan_database_rename.rs new file mode 100644 index 0000000000000..3f30f6d4b3ed8 --- /dev/null +++ b/common/planners/src/plan_database_rename.rs @@ -0,0 +1,38 @@ +// Copyright 2021 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 std::sync::Arc; + +use common_datavalues::DataSchema; +use common_datavalues::DataSchemaRef; + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)] +pub struct RenameDatabasePlan { + pub tenant: String, + pub entities: Vec, +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)] +pub struct RenameDatabaseEntity { + pub if_exists: bool, + pub catalog_name: String, + pub db: String, + pub new_db: String, +} + +impl RenameDatabasePlan { + pub fn schema(&self) -> DataSchemaRef { + Arc::new(DataSchema::empty()) + } +} diff --git a/common/planners/src/plan_node.rs b/common/planners/src/plan_node.rs index 8d2b19963012a..3ce03ddacc0b8 100644 --- a/common/planners/src/plan_node.rs +++ b/common/planners/src/plan_node.rs @@ -56,6 +56,7 @@ use crate::OptimizeTablePlan; use crate::ProjectionPlan; use crate::ReadDataSourcePlan; use crate::RemotePlan; +use crate::RenameDatabasePlan; use crate::RenameTablePlan; use crate::RevokePrivilegePlan; use crate::RevokeRolePlan; @@ -116,6 +117,7 @@ pub enum PlanNode { // Database. CreateDatabase(CreateDatabasePlan), DropDatabase(DropDatabasePlan), + RenameDatabase(RenameDatabasePlan), ShowCreateDatabase(ShowCreateDatabasePlan), // Table. @@ -213,6 +215,7 @@ impl PlanNode { PlanNode::CreateDatabase(v) => v.schema(), PlanNode::DropDatabase(v) => v.schema(), PlanNode::ShowCreateDatabase(v) => v.schema(), + PlanNode::RenameDatabase(v) => v.schema(), // Table. PlanNode::CreateTable(v) => v.schema(), @@ -311,6 +314,7 @@ impl PlanNode { PlanNode::CreateDatabase(_) => "CreateDatabasePlan", PlanNode::DropDatabase(_) => "DropDatabasePlan", PlanNode::ShowCreateDatabase(_) => "ShowCreateDatabasePlan", + PlanNode::RenameDatabase(_) => "RenameDatabase", // Table. PlanNode::CreateTable(_) => "CreateTablePlan", diff --git a/common/planners/src/plan_node_display_indent.rs b/common/planners/src/plan_node_display_indent.rs index 2cf31e451d599..857b868cef1ce 100644 --- a/common/planners/src/plan_node_display_indent.rs +++ b/common/planners/src/plan_node_display_indent.rs @@ -34,6 +34,7 @@ use crate::LimitPlan; use crate::PlanNode; use crate::ProjectionPlan; use crate::ReadDataSourcePlan; +use crate::RenameDatabasePlan; use crate::RenameTablePlan; use crate::SortPlan; use crate::StagePlan; @@ -76,6 +77,7 @@ impl<'a> fmt::Display for PlanNodeIndentFormatDisplay<'a> { PlanNode::ReadSource(plan) => Self::format_read_source(f, plan), PlanNode::CreateDatabase(plan) => Self::format_create_database(f, plan), PlanNode::DropDatabase(plan) => Self::format_drop_database(f, plan), + PlanNode::RenameDatabase(plan) => Self::format_rename_database(f, plan), PlanNode::CreateTable(plan) => Self::format_create_table(f, plan), PlanNode::DropTable(plan) => Self::format_drop_table(f, plan), PlanNode::RenameTable(plan) => Self::format_rename_table(f, plan), @@ -333,6 +335,19 @@ impl<'a> PlanNodeIndentFormatDisplay<'a> { write!(f, "]") } + fn format_rename_database(f: &mut Formatter, plan: &RenameDatabasePlan) -> fmt::Result { + write!(f, "Rename database,")?; + write!(f, " [")?; + for (i, entity) in plan.entities.iter().enumerate() { + write!(f, "{:} to {:}", entity.db, entity.new_db,)?; + + if i + 1 != plan.entities.len() { + write!(f, ", ")?; + } + } + write!(f, "]") + } + fn format_copy(f: &mut Formatter, plan: &CopyPlan) -> fmt::Result { write!(f, "{:?}", plan) } diff --git a/common/planners/src/plan_node_rewriter.rs b/common/planners/src/plan_node_rewriter.rs index 2844d1dc8417d..a31beb364b1a9 100644 --- a/common/planners/src/plan_node_rewriter.rs +++ b/common/planners/src/plan_node_rewriter.rs @@ -67,6 +67,7 @@ use crate::PlanNode; use crate::ProjectionPlan; use crate::ReadDataSourcePlan; use crate::RemotePlan; +use crate::RenameDatabasePlan; use crate::RenameTablePlan; use crate::RevokePrivilegePlan; use crate::RevokeRolePlan; @@ -143,6 +144,7 @@ pub trait PlanRewriter: Sized { PlanNode::CreateDatabase(plan) => self.rewrite_create_database(plan), PlanNode::DropDatabase(plan) => self.rewrite_drop_database(plan), PlanNode::ShowCreateDatabase(plan) => self.rewrite_show_create_database(plan), + PlanNode::RenameDatabase(plan) => self.rewrite_rename_database(plan), // Table. PlanNode::CreateTable(plan) => self.rewrite_create_table(plan), @@ -355,6 +357,9 @@ pub trait PlanRewriter: Sized { fn rewrite_rename_table(&mut self, plan: &RenameTablePlan) -> Result { Ok(PlanNode::RenameTable(plan.clone())) } + fn rewrite_rename_database(&mut self, plan: &RenameDatabasePlan) -> Result { + Ok(PlanNode::RenameDatabase(plan.clone())) + } fn rewrite_optimize_table(&mut self, plan: &OptimizeTablePlan) -> Result { Ok(PlanNode::OptimizeTable(plan.clone())) diff --git a/common/planners/src/plan_node_visitor.rs b/common/planners/src/plan_node_visitor.rs index 5db3148669be3..870cd372c536d 100644 --- a/common/planners/src/plan_node_visitor.rs +++ b/common/planners/src/plan_node_visitor.rs @@ -57,6 +57,7 @@ use crate::PlanNode; use crate::ProjectionPlan; use crate::ReadDataSourcePlan; use crate::RemotePlan; +use crate::RenameDatabasePlan; use crate::RenameTablePlan; use crate::RevokePrivilegePlan; use crate::RevokeRolePlan; @@ -155,6 +156,7 @@ pub trait PlanVisitor { PlanNode::CreateDatabase(plan) => self.visit_create_database(plan), PlanNode::DropDatabase(plan) => self.visit_drop_database(plan), PlanNode::ShowCreateDatabase(plan) => self.visit_show_create_database(plan), + PlanNode::RenameDatabase(plan) => self.visit_rename_database(plan), // Table. PlanNode::CreateTable(plan) => self.visit_create_table(plan), @@ -321,6 +323,10 @@ pub trait PlanVisitor { Ok(()) } + fn visit_rename_database(&mut self, _: &RenameDatabasePlan) -> Result<()> { + Ok(()) + } + fn visit_create_table(&mut self, _: &CreateTablePlan) -> Result<()> { Ok(()) } diff --git a/docs/doc/30-reference/30-sql/00-ddl/10-database/ddl-alter-database.md b/docs/doc/30-reference/30-sql/00-ddl/10-database/ddl-alter-database.md new file mode 100644 index 0000000000000..4916c8a7ac8e1 --- /dev/null +++ b/docs/doc/30-reference/30-sql/00-ddl/10-database/ddl-alter-database.md @@ -0,0 +1,45 @@ +--- +title: RENAME DATABASE +--- + +Changes the name of a database. + +## Syntax + +```sql +ALTER DATABASE [ IF EXISTS ] RENAME TO +``` + +## Examples + +```sql +CREATE DATABASE DATABEND; +``` + +```sql +SHOW DATABASES; ++--------------------+ +| Database | ++--------------------+ +| DATABEND | +| INFORMATION_SCHEMA | +| default | +| system | ++--------------------+ +``` + +```sql +ALTER DATABASE `DATABEND` RENAME TO `NEW_DATABEND`; +``` + +```sql +SHOW DATABASES; ++--------------------+ +| Database | ++--------------------+ +| INFORMATION_SCHEMA | +| NEW_DATABEND | +| default | +| system | ++--------------------+ +``` diff --git a/metasrv/tests/it/grpc/metasrv_grpc_schema_api.rs b/metasrv/tests/it/grpc/metasrv_grpc_schema_api.rs index 418b9f90e9793..5151b91e6cb28 100644 --- a/metasrv/tests/it/grpc/metasrv_grpc_schema_api.rs +++ b/metasrv/tests/it/grpc/metasrv_grpc_schema_api.rs @@ -75,6 +75,18 @@ async fn test_meta_grpc_client_database_list_in_diff_tenant() -> anyhow::Result< .await } +#[tokio::test(flavor = "multi_thread", worker_threads = 3)] +async fn test_meta_grpc_client_database_rename() -> anyhow::Result<()> { + let (_log_guards, ut_span) = init_meta_ut!(); + let _ent = ut_span.enter(); + + let (_tc, addr) = start_metasrv().await?; + + let client = MetaGrpcClient::try_create(addr.as_str(), "root", "xxx", None, None).await?; + + SchemaApiTestSuite {}.database_rename(&client).await +} + #[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn test_meta_grpc_client_table_create_get_drop() -> anyhow::Result<()> { let (_log_guards, ut_span) = init_meta_ut!(); diff --git a/query/src/catalogs/catalog.rs b/query/src/catalogs/catalog.rs index ce036c509fa2c..6f7b9ed9347b0 100644 --- a/query/src/catalogs/catalog.rs +++ b/query/src/catalogs/catalog.rs @@ -23,6 +23,8 @@ use common_meta_types::DropDatabaseReq; use common_meta_types::DropTableReply; use common_meta_types::DropTableReq; use common_meta_types::MetaId; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::TableIdent; @@ -68,6 +70,8 @@ pub trait Catalog: DynClone + Send + Sync { } } + async fn rename_database(&self, req: RenameDatabaseReq) -> Result; + /// /// Table. /// diff --git a/query/src/catalogs/default/backends/meta_backend.rs b/query/src/catalogs/default/backends/meta_backend.rs index 1f2bd911e0ae7..652d67a63e19c 100644 --- a/query/src/catalogs/default/backends/meta_backend.rs +++ b/query/src/catalogs/default/backends/meta_backend.rs @@ -32,6 +32,8 @@ use common_meta_types::ListDatabaseReq; use common_meta_types::ListTableReq; use common_meta_types::MetaError; use common_meta_types::MetaId; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::TableIdent; @@ -114,6 +116,14 @@ impl SchemaApi for MetaBackend { .await } + async fn rename_database( + &self, + req: RenameDatabaseReq, + ) -> Result { + self.query_backend(move |cli| async move { cli.rename_database(req).await }) + .await + } + async fn create_table( &self, req: CreateTableReq, diff --git a/query/src/catalogs/default/database_catalog.rs b/query/src/catalogs/default/database_catalog.rs index fb1dd1c4a8bd9..d538cdec96d35 100644 --- a/query/src/catalogs/default/database_catalog.rs +++ b/query/src/catalogs/default/database_catalog.rs @@ -24,6 +24,8 @@ use common_meta_types::DropDatabaseReq; use common_meta_types::DropTableReply; use common_meta_types::DropTableReq; use common_meta_types::MetaId; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::TableIdent; @@ -170,6 +172,29 @@ impl Catalog for DatabaseCatalog { self.mutable_catalog.drop_database(req).await } + async fn rename_database(&self, req: RenameDatabaseReq) -> Result { + if req.name_ident.tenant.is_empty() { + return Err(ErrorCode::TenantIsEmpty( + "Tenant can not empty(while rename database)", + )); + } + tracing::info!("Rename table from req:{:?}", req); + + if self + .immutable_catalog + .exists_database(&req.name_ident.tenant, &req.name_ident.db_name) + .await? + || self + .immutable_catalog + .exists_database(&req.name_ident.tenant, &req.new_db_name) + .await? + { + return self.immutable_catalog.rename_database(req).await; + } + + self.mutable_catalog.rename_database(req).await + } + fn get_table_by_info(&self, table_info: &TableInfo) -> Result> { let res = self.immutable_catalog.get_table_by_info(table_info); match res { diff --git a/query/src/catalogs/default/immutable_catalog.rs b/query/src/catalogs/default/immutable_catalog.rs index 8b7bd8143d9ec..175b2691cb36b 100644 --- a/query/src/catalogs/default/immutable_catalog.rs +++ b/query/src/catalogs/default/immutable_catalog.rs @@ -23,6 +23,8 @@ use common_meta_types::DropDatabaseReq; use common_meta_types::DropTableReply; use common_meta_types::DropTableReq; use common_meta_types::MetaId; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::TableIdent; @@ -95,6 +97,10 @@ impl Catalog for ImmutableCatalog { Err(ErrorCode::UnImplement("Cannot drop system database")) } + async fn rename_database(&self, _req: RenameDatabaseReq) -> Result { + Err(ErrorCode::UnImplement("Cannot rename system database")) + } + fn get_table_by_info(&self, table_info: &TableInfo) -> Result> { let table_id = table_info.ident.table_id; diff --git a/query/src/catalogs/default/mutable_catalog.rs b/query/src/catalogs/default/mutable_catalog.rs index 8c1683e31e364..5226251cf9bef 100644 --- a/query/src/catalogs/default/mutable_catalog.rs +++ b/query/src/catalogs/default/mutable_catalog.rs @@ -34,6 +34,8 @@ use common_meta_types::GetTableReq; use common_meta_types::ListDatabaseReq; use common_meta_types::ListTableReq; use common_meta_types::MetaId; +use common_meta_types::RenameDatabaseReply; +use common_meta_types::RenameDatabaseReq; use common_meta_types::RenameTableReply; use common_meta_types::RenameTableReq; use common_meta_types::TableIdent; @@ -194,6 +196,11 @@ impl Catalog for MutableCatalog { Ok(()) } + async fn rename_database(&self, req: RenameDatabaseReq) -> Result { + let res = self.ctx.meta.rename_database(req).await?; + Ok(res) + } + fn get_table_by_info(&self, table_info: &TableInfo) -> Result> { let storage = self.ctx.storage_factory.clone(); let ctx = StorageContext { diff --git a/query/src/catalogs/hive/hive_catalog.rs b/query/src/catalogs/hive/hive_catalog.rs index b180a7d8b03c0..b8490138e951b 100644 --- a/query/src/catalogs/hive/hive_catalog.rs +++ b/query/src/catalogs/hive/hive_catalog.rs @@ -91,6 +91,12 @@ impl Catalog for HiveCatalog { )) } + async fn rename_database(&self, _req: RenameDatabaseReq) -> Result { + Err(ErrorCode::UnImplement( + "Cannot rename database in HIVE catalog", + )) + } + fn get_table_by_info(&self, table_info: &TableInfo) -> Result> { let res: Arc = Arc::new(HiveTable::create(table_info.clone())); Ok(res) diff --git a/query/src/interpreters/interpreter_database_rename.rs b/query/src/interpreters/interpreter_database_rename.rs new file mode 100644 index 0000000000000..b1a60df312218 --- /dev/null +++ b/query/src/interpreters/interpreter_database_rename.rs @@ -0,0 +1,70 @@ +// Copyright 2021 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 std::sync::Arc; + +use common_exception::Result; +use common_meta_types::DatabaseNameIdent; +use common_meta_types::RenameDatabaseReq; +use common_planners::RenameDatabasePlan; +use common_streams::DataBlockStream; +use common_streams::SendableDataBlockStream; + +use crate::interpreters::Interpreter; +use crate::interpreters::InterpreterPtr; +use crate::sessions::QueryContext; + +pub struct RenameDatabaseInterpreter { + ctx: Arc, + plan: RenameDatabasePlan, +} + +impl RenameDatabaseInterpreter { + pub fn try_create(ctx: Arc, plan: RenameDatabasePlan) -> Result { + Ok(Arc::new(RenameDatabaseInterpreter { ctx, plan })) + } +} + +#[async_trait::async_trait] +impl Interpreter for RenameDatabaseInterpreter { + fn name(&self) -> &str { + "RenameDatabaseInterpreter" + } + + async fn execute( + &self, + _input_stream: Option, + ) -> Result { + for entity in &self.plan.entities { + let catalog = self.ctx.get_catalog(&entity.catalog_name)?; + let tenant = self.plan.tenant.clone(); + catalog + .rename_database(RenameDatabaseReq { + if_exists: entity.if_exists, + name_ident: DatabaseNameIdent { + tenant, + db_name: entity.db.clone(), + }, + new_db_name: entity.new_db.clone(), + }) + .await?; + } + + Ok(Box::pin(DataBlockStream::create( + self.plan.schema(), + None, + vec![], + ))) + } +} diff --git a/query/src/interpreters/interpreter_factory.rs b/query/src/interpreters/interpreter_factory.rs index 9de7dab24b4b8..2fb8f029e402a 100644 --- a/query/src/interpreters/interpreter_factory.rs +++ b/query/src/interpreters/interpreter_factory.rs @@ -52,6 +52,7 @@ use crate::interpreters::InterceptorInterpreter; use crate::interpreters::Interpreter; use crate::interpreters::KillInterpreter; use crate::interpreters::OptimizeTableInterpreter; +use crate::interpreters::RenameDatabaseInterpreter; use crate::interpreters::RevokePrivilegeInterpreter; use crate::interpreters::RevokeRoleInterpreter; use crate::interpreters::SelectInterpreter; @@ -126,6 +127,7 @@ impl InterpreterFactory { PlanNode::ShowCreateDatabase(v) => { ShowCreateDatabaseInterpreter::try_create(ctx_clone, v) } + PlanNode::RenameDatabase(v) => RenameDatabaseInterpreter::try_create(ctx_clone, v), // Table related transforms PlanNode::CreateTable(v) => CreateTableInterpreter::try_create(ctx_clone, v), diff --git a/query/src/interpreters/mod.rs b/query/src/interpreters/mod.rs index bd2c6e06aba82..189896513d4d1 100644 --- a/query/src/interpreters/mod.rs +++ b/query/src/interpreters/mod.rs @@ -19,6 +19,7 @@ mod interpreter_common; mod interpreter_copy; mod interpreter_database_create; mod interpreter_database_drop; +mod interpreter_database_rename; mod interpreter_database_show_create; mod interpreter_empty; mod interpreter_explain; @@ -79,6 +80,7 @@ pub use interpreter_call::CallInterpreter; pub use interpreter_copy::CopyInterpreter; pub use interpreter_database_create::CreateDatabaseInterpreter; pub use interpreter_database_drop::DropDatabaseInterpreter; +pub use interpreter_database_rename::RenameDatabaseInterpreter; pub use interpreter_database_show_create::ShowCreateDatabaseInterpreter; pub use interpreter_empty::EmptyInterpreter; pub use interpreter_explain::ExplainInterpreter; diff --git a/query/src/sql/parsers/parser_database.rs b/query/src/sql/parsers/parser_database.rs index b5512446a7063..f1ea208a5b3b3 100644 --- a/query/src/sql/parsers/parser_database.rs +++ b/query/src/sql/parsers/parser_database.rs @@ -21,6 +21,8 @@ use sqlparser::keywords::Keyword; use sqlparser::parser::ParserError; use sqlparser::tokenizer::Token; +use crate::sql::statements::AlterDatabaseAction; +use crate::sql::statements::DfAlterDatabase; use crate::sql::statements::DfCreateDatabase; use crate::sql::statements::DfDropDatabase; use crate::sql::statements::DfShowCreateDatabase; @@ -84,4 +86,26 @@ impl<'a> DfParser<'a> { }; Ok((engine, options)) } + + //ALTER DATABASE [ IF EXISTS ] RENAME TO + pub(crate) fn parse_alter_database(&mut self) -> Result, ParserError> { + let if_exists = self.parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let database_name = self.parser.parse_object_name()?; + + if self.parser.parse_keywords(&[Keyword::RENAME, Keyword::TO]) { + let new_database_name = self.parser.parse_object_name()?; + + let rename = DfAlterDatabase { + if_exists, + database_name, + action: AlterDatabaseAction::RenameDatabase(new_database_name), + }; + + Ok(DfStatement::AlterDatabase(rename)) + } else { + Err(ParserError::ParserError(String::from( + "Alter database only support rename for now!", + ))) + } + } } diff --git a/query/src/sql/sql_parser.rs b/query/src/sql/sql_parser.rs index 99853ae3fe09e..7862608056f4f 100644 --- a/query/src/sql/sql_parser.rs +++ b/query/src/sql/sql_parser.rs @@ -291,6 +291,7 @@ impl<'a> DfParser<'a> { Keyword::FUNCTION => self.parse_alter_udf(), Keyword::TABLE => self.parse_alter_table(), Keyword::VIEW => self.parse_alter_view(), + Keyword::DATABASE => self.parse_alter_database(), _ => self.expected("keyword USER or FUNCTION", Token::Word(w)), }, unexpected => self.expected("alter statement", unexpected), diff --git a/query/src/sql/sql_statement.rs b/query/src/sql/sql_statement.rs index 6c86f4ffb7a53..727b20b5de696 100644 --- a/query/src/sql/sql_statement.rs +++ b/query/src/sql/sql_statement.rs @@ -29,6 +29,7 @@ use super::statements::DfDropView; use super::statements::DfGrantRoleStatement; use super::statements::DfList; use super::statements::DfRevokeRoleStatement; +use crate::sql::statements::DfAlterDatabase; use crate::sql::statements::DfAlterTable; use crate::sql::statements::DfAlterUDF; use crate::sql::statements::DfAlterUser; @@ -82,6 +83,7 @@ pub enum DfStatement<'a> { CreateDatabase(DfCreateDatabase), DropDatabase(DfDropDatabase), UseDatabase(DfUseDatabase), + AlterDatabase(DfAlterDatabase), // Tables. ShowTables(DfShowTables), diff --git a/query/src/sql/statements/analyzer_statement.rs b/query/src/sql/statements/analyzer_statement.rs index 394446394e20f..578195cd2d19e 100644 --- a/query/src/sql/statements/analyzer_statement.rs +++ b/query/src/sql/statements/analyzer_statement.rs @@ -146,6 +146,7 @@ impl<'a> AnalyzableStatement for DfStatement<'a> { DfStatement::ShowCreateDatabase(v) => v.analyze(ctx).await, DfStatement::CreateDatabase(v) => v.analyze(ctx).await, DfStatement::DropDatabase(v) => v.analyze(ctx).await, + DfStatement::AlterDatabase(v) => v.analyze(ctx).await, DfStatement::CreateTable(v) => v.analyze(ctx).await, DfStatement::DescribeTable(v) => v.analyze(ctx).await, DfStatement::DropTable(v) => v.analyze(ctx).await, diff --git a/query/src/sql/statements/mod.rs b/query/src/sql/statements/mod.rs index e23391f41b4da..ced65ed9ec45d 100644 --- a/query/src/sql/statements/mod.rs +++ b/query/src/sql/statements/mod.rs @@ -17,6 +17,7 @@ pub mod query; mod analyzer_expr; mod analyzer_statement; mod analyzer_value_expr; +mod statement_alter_database; mod statement_alter_table; mod statement_alter_udf; mod statement_alter_user; @@ -75,6 +76,8 @@ pub use analyzer_statement::AnalyzedResult; pub use analyzer_statement::QueryAnalyzeState; pub use analyzer_statement::QueryRelation; pub use query::QueryASTIR; +pub use statement_alter_database::AlterDatabaseAction; +pub use statement_alter_database::DfAlterDatabase; pub use statement_alter_table::AlterTableAction; pub use statement_alter_table::DfAlterTable; pub use statement_alter_udf::DfAlterUDF; diff --git a/query/src/sql/statements/statement_alter_database.rs b/query/src/sql/statements/statement_alter_database.rs new file mode 100644 index 0000000000000..2f82801b30cb5 --- /dev/null +++ b/query/src/sql/statements/statement_alter_database.rs @@ -0,0 +1,65 @@ +// Copyright 2021 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 std::sync::Arc; + +use common_exception::Result; +use common_planners::PlanNode; +use common_planners::RenameDatabaseEntity; +use common_planners::RenameDatabasePlan; +use common_tracing::tracing; +use sqlparser::ast::ObjectName; + +use crate::sessions::QueryContext; +use crate::sql::statements::resolve_database; +use crate::sql::statements::AnalyzableStatement; +use crate::sql::statements::AnalyzedResult; + +#[derive(Debug, Clone, PartialEq)] +pub struct DfAlterDatabase { + pub if_exists: bool, + pub database_name: ObjectName, + pub action: AlterDatabaseAction, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum AlterDatabaseAction { + RenameDatabase(ObjectName), +} + +#[async_trait::async_trait] +impl AnalyzableStatement for DfAlterDatabase { + #[tracing::instrument(level = "debug", skip(self, ctx), fields(ctx.id = ctx.get_id().as_str()))] + async fn analyze(&self, ctx: Arc) -> Result { + let tenant = ctx.get_tenant(); + let (catalog, db) = resolve_database(&ctx, &self.database_name, "ALTER DATABASE")?; + + match &self.action { + AlterDatabaseAction::RenameDatabase(o) => { + let mut entities = Vec::new(); + let (_new_catalog, new_db) = resolve_database(&ctx, o, "ALTER DATABASE")?; + entities.push(RenameDatabaseEntity { + if_exists: self.if_exists, + catalog_name: catalog, + db, + new_db, + }); + + Ok(AnalyzedResult::SimpleQuery(Box::new( + PlanNode::RenameDatabase(RenameDatabasePlan { tenant, entities }), + ))) + } + } + } +} diff --git a/query/tests/it/catalogs/database_catalog.rs b/query/tests/it/catalogs/database_catalog.rs index f3dd08274e2e2..919a91a2e0142 100644 --- a/query/tests/it/catalogs/database_catalog.rs +++ b/query/tests/it/catalogs/database_catalog.rs @@ -24,6 +24,7 @@ use common_meta_types::DatabaseMeta; use common_meta_types::DatabaseNameIdent; use common_meta_types::DropDatabaseReq; use common_meta_types::DropTableReq; +use common_meta_types::RenameDatabaseReq; use common_meta_types::TableMeta; use common_meta_types::TableNameIdent; use databend_query::catalogs::Catalog; @@ -90,9 +91,31 @@ async fn test_catalogs_database() -> Result<()> { assert!(res.is_err()); } - // Drop. + // Rename. { - let mut req = DropDatabaseReq { + let mut req = RenameDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: "db1".to_string(), + }, + new_db_name: "db2".to_string(), + }; + let res = catalog.rename_database(req.clone()).await; + assert!(res.is_ok()); + + let db_list_1 = catalog.list_databases(tenant).await?; + assert_eq!(db_list_1.len(), db_count + 1); + + // Tenant empty. + req.name_ident.tenant = "".to_string(); + let res = catalog.rename_database(req).await; + assert!(res.is_err()); + } + + // Drop old db. + { + let req = DropDatabaseReq { if_exists: false, name_ident: DatabaseNameIdent { tenant: tenant.to_string(), @@ -100,6 +123,19 @@ async fn test_catalogs_database() -> Result<()> { }, }; let res = catalog.drop_database(req.clone()).await; + assert!(res.is_err()); + } + + // Drop renamed db. + { + let mut req = DropDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: "db2".to_string(), + }, + }; + let res = catalog.drop_database(req.clone()).await; assert!(res.is_ok()); let db_list_drop = catalog.list_databases(tenant).await?; diff --git a/query/tests/it/catalogs/immutable_catalogs.rs b/query/tests/it/catalogs/immutable_catalogs.rs index 1b12022ca1649..3c787c4f20d73 100644 --- a/query/tests/it/catalogs/immutable_catalogs.rs +++ b/query/tests/it/catalogs/immutable_catalogs.rs @@ -17,6 +17,7 @@ use common_exception::Result; use common_meta_types::CreateDatabaseReq; use common_meta_types::DatabaseNameIdent; use common_meta_types::DropDatabaseReq; +use common_meta_types::RenameDatabaseReq; use databend_query::catalogs::default::ImmutableCatalog; use databend_query::catalogs::Catalog; @@ -62,6 +63,32 @@ async fn test_immutable_catalogs_database() -> Result<()> { let drop_db_req = catalog.drop_database(drop_db_req).await; assert!(drop_db_req.is_err()); + // rename database should failed + let rename_db_req = RenameDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: "system".to_string(), + }, + + new_db_name: "test".to_string(), + }; + let rename_db_req = catalog.rename_database(rename_db_req).await; + assert!(rename_db_req.is_err()); + + // rename database should failed + let rename_db_req = RenameDatabaseReq { + if_exists: false, + name_ident: DatabaseNameIdent { + tenant: tenant.to_string(), + db_name: "test".to_string(), + }, + + new_db_name: "system".to_string(), + }; + let rename_db_req = catalog.rename_database(rename_db_req).await; + assert!(rename_db_req.is_err()); + Ok(()) } diff --git a/query/tests/it/interpreters/interpreter_database_rename.rs b/query/tests/it/interpreters/interpreter_database_rename.rs new file mode 100644 index 0000000000000..4868effcfb2bf --- /dev/null +++ b/query/tests/it/interpreters/interpreter_database_rename.rs @@ -0,0 +1,60 @@ +// Copyright 2021 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_base::base::tokio; +use common_exception::Result; +use databend_query::interpreters::*; +use databend_query::sql::PlanParser; +use futures::TryStreamExt; +use pretty_assertions::assert_eq; + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_rename_database_interpreter() -> Result<()> { + common_tracing::init_default_ut_tracing(); + + let ctx = crate::tests::create_query_context().await?; + + // Create DB + { + let query = "create database test1"; + + let plan = PlanParser::parse(ctx.clone(), query).await?; + let executor = InterpreterFactory::get(ctx.clone(), plan.clone())?; + let _ = executor.execute(None).await?; + } + + // Rename DB + { + let plan = PlanParser::parse(ctx.clone(), "alter database test1 rename to test2").await?; + let executor = InterpreterFactory::get(ctx.clone(), plan.clone())?; + assert_eq!(executor.name(), "RenameDatabaseInterpreter"); + let stream = executor.execute(None).await?; + let result = stream.try_collect::>().await?; + let expected = vec!["++", "++"]; + common_datablocks::assert_blocks_sorted_eq(expected, result.as_slice()); + } + + // Drop DB + { + let plan = PlanParser::parse(ctx.clone(), "drop database test2").await?; + let executor = InterpreterFactory::get(ctx, plan.clone())?; + assert_eq!(executor.name(), "DropDatabaseInterpreter"); + let stream = executor.execute(None).await?; + let result = stream.try_collect::>().await?; + let expected = vec!["++", "++"]; + common_datablocks::assert_blocks_sorted_eq(expected, result.as_slice()); + } + + Ok(()) +} diff --git a/query/tests/it/interpreters/mod.rs b/query/tests/it/interpreters/mod.rs index e4869495cb710..52d83b070b678 100644 --- a/query/tests/it/interpreters/mod.rs +++ b/query/tests/it/interpreters/mod.rs @@ -16,6 +16,7 @@ mod access; mod interpreter_call; mod interpreter_database_create; mod interpreter_database_drop; +mod interpreter_database_rename; mod interpreter_database_show_create; mod interpreter_empty; mod interpreter_explain; diff --git a/query/tests/it/sql/plan_parser.rs b/query/tests/it/sql/plan_parser.rs index 2338108e044c0..2aaca778d09b2 100644 --- a/query/tests/it/sql/plan_parser.rs +++ b/query/tests/it/sql/plan_parser.rs @@ -63,6 +63,24 @@ async fn test_plan_parser() -> Result<()> { expect: "Drop database db1, if_exists:true", error: "", }, + Test { + name: "rename-database-passed", + sql: "ALTER DATABASE IF EXISTS db1 RENAME TO db2", + expect: "Rename database, [db1 to db2]", + error: "" + }, + Test { + name: "rename-database-if-exists-passed", + sql: "ALTER DATABASE IF EXISTS db1 RENAME TO db2", + expect: "Rename database, [db1 to db2]", + error: "" + }, + Test { + name: "rename-database-to-immutable-passed", + sql: "ALTER DATABASE IF EXISTS db1 RENAME TO system", + expect: "Rename database, [db1 to system]", + error: "" + }, Test { name: "create-table-passed", sql: "CREATE TABLE t(c1 int, c2 bigint, c3 varchar(255) ) ENGINE = Parquet location = 'foo.parquet' ", @@ -175,16 +193,16 @@ async fn test_plan_parser() -> Result<()> { }, Test { name: "interval-unsupported", - sql: "SELECT INTERVAL '1 year 1 day'", - expect: "", - error: "Code: 1002, displayText = invalid digit found in string (while in analyze select projection).", - }, - Test { - name: "interval-out-of-range", - sql: "SELECT INTERVAL '100000000000000000 day'", - expect: "", - error: "Code: 1002, displayText = number too large to fit in target type (while in analyze select projection).", - }, + sql: "SELECT INTERVAL '1 year 1 day'", + expect: "", + error: "Code: 1002, displayText = invalid digit found in string (while in analyze select projection).", + }, + Test { + name: "interval-out-of-range", + sql: "SELECT INTERVAL '100000000000000000 day'", + expect: "", + error: "Code: 1002, displayText = number too large to fit in target type (while in analyze select projection).", + }, Test { name: "insert-simple", sql: "insert into t(col1, col2) values(1,2), (3,4)", diff --git a/tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.result b/tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.result new file mode 100644 index 0000000000000..5efc1494172c1 --- /dev/null +++ b/tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.result @@ -0,0 +1,32 @@ +system +INFORMATION_SCHEMA +a +b +default +system +INFORMATION_SCHEMA +a +b +default +system +INFORMATION_SCHEMA +a +b +default +system +INFORMATION_SCHEMA +a +b +default +system +INFORMATION_SCHEMA +A +b +default +1 +10 +system +INFORMATION_SCHEMA +A +b +default diff --git a/tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.sql b/tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.sql new file mode 100644 index 0000000000000..93aa3af2ba6f4 --- /dev/null +++ b/tests/suites/0_stateless/05_ddl/05_0003_ddl_alter_database.sql @@ -0,0 +1,44 @@ +DROP DATABASE IF EXISTS a; +DROP DATABASE IF EXISTS b; +DROP DATABASE IF EXISTS c; +DROP DATABASE IF EXISTS A; + +CREATE DATABASE a; +CREATE DATABASE b; +CREATE TABLE a.t1 (id int); +INSERT INTO a.t1 VALUES (1); +CREATE TABLE b.t2 (id int); +INSERT INTO b.t2 VALUES (10); + +-- RENAME NOT EXISTS DB TO EXISTS DB +ALTER DATABASE c RENAME TO a; -- {ErrorCode 1003} +ALTER DATABASE IF EXISTS c RENAME TO a; +SELECT * FROM system.databases; + +-- RENAME NOT EXISTS DB TO NOT EXISTS DB +ALTER DATABASE IF EXISTS c RENAME TO C; +ALTER DATABASE c RENAME TO C; -- {ErrorCode 1003} +SELECT * FROM system.databases; + +-- RENAME IMMUTABLE DB +ALTER DATABASE IF EXISTS system RENAME TO C; -- {ErrorCode 1002} +ALTER DATABASE system RENAME TO C; -- {ErrorCode 1002} +SELECT * FROM system.databases; + +-- RENAME EXISTS DB TO EXISTS DB +ALTER DATABASE a RENAME TO b; -- {ErrorCode 2301} +ALTER DATABASE IF EXISTS a RENAME TO b; -- {ErrorCode 2301} +SELECT * FROM system.databases; + +-- RENAME EXISTS DB TO NOT EXISTS DB +ALTER DATABASE a RENAME TO A; +SELECT * FROM system.databases; +SELECT * FROM A.t1; + +-- RENAME EXISTS DB TO IMMUTABLE DB +ALTER DATABASE b RENAME TO system; -- {ErrorCode 1002} +ALTER DATABASE IF EXISTS b RENAME TO system; -- {ErrorCode 1002} +SELECT * FROM b.t2; +SELECT * FROM system.databases; +DROP DATABASE b; +DROP DATABASE A;