diff --git a/datafusion/core/src/execution/context.rs b/datafusion/core/src/execution/context.rs index 0b16181467a8e..c5dcb7a595e06 100644 --- a/datafusion/core/src/execution/context.rs +++ b/datafusion/core/src/execution/context.rs @@ -102,7 +102,8 @@ use crate::variable::{VarProvider, VarType}; use async_trait::async_trait; use chrono::{DateTime, Utc}; use datafusion_common::ScalarValue; -use datafusion_expr::TableSource; +use datafusion_expr::logical_plan::DropView; +use datafusion_expr::{TableSource, TableType}; use datafusion_optimizer::decorrelate_where_exists::DecorrelateWhereExists; use datafusion_optimizer::decorrelate_where_in::DecorrelateWhereIn; use datafusion_optimizer::filter_null_join_keys::FilterNullJoinKeys; @@ -269,10 +270,7 @@ impl SessionContext { }; let table = self.table(name.as_str()); match (if_not_exists, table) { - (true, Ok(_)) => { - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) - } + (true, Ok(_)) => self.return_empty_dataframe(), (_, Err(_)) => { // TODO make schema in CreateExternalTable optional instead of empty let provided_schema = if schema.fields().is_empty() { @@ -294,8 +292,7 @@ impl SessionContext { provided_schema, ) .await?; - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) + self.return_empty_dataframe() } (false, Ok(_)) => Err(DataFusionError::Execution(format!( "Table '{:?}' already exists", @@ -313,10 +310,7 @@ impl SessionContext { let table = self.table(name.as_str()); match (if_not_exists, or_replace, table) { - (true, false, Ok(_)) => { - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) - } + (true, false, Ok(_)) => self.return_empty_dataframe(), (false, true, Ok(_)) => { self.deregister_table(name.as_str())?; let plan = self.optimize(&input)?; @@ -371,16 +365,14 @@ impl SessionContext { Arc::new(ViewTable::try_new((*input).clone(), definition)?); self.register_table(name.as_str(), table)?; - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) + self.return_empty_dataframe() } (_, Err(_)) => { let table = Arc::new(ViewTable::try_new((*input).clone(), definition)?); self.register_table(name.as_str(), table)?; - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) + self.return_empty_dataframe() } (false, Ok(_)) => Err(DataFusionError::Execution(format!( "Table '{:?}' already exists", @@ -392,15 +384,28 @@ impl SessionContext { LogicalPlan::DropTable(DropTable { name, if_exists, .. }) => { - let returned = self.deregister_table(name.as_str())?; - if !if_exists && returned.is_none() { - Err(DataFusionError::Execution(format!( - "Memory table {:?} doesn't exist.", + let result = self.find_and_deregister(name.as_str(), TableType::Base); + match (result, if_exists) { + (Ok(true), _) => self.return_empty_dataframe(), + (_, true) => self.return_empty_dataframe(), + (_, _) => Err(DataFusionError::Execution(format!( + "Table {:?} doesn't exist.", name - ))) - } else { - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) + ))), + } + } + + LogicalPlan::DropView(DropView { + name, if_exists, .. + }) => { + let result = self.find_and_deregister(name.as_str(), TableType::View); + match (result, if_exists) { + (Ok(true), _) => self.return_empty_dataframe(), + (_, true) => self.return_empty_dataframe(), + (_, _) => Err(DataFusionError::Execution(format!( + "View {:?} doesn't exist.", + name + ))), } } LogicalPlan::CreateCatalogSchema(CreateCatalogSchema { @@ -429,15 +434,11 @@ impl SessionContext { let schema = catalog.schema(schema_name); match (if_not_exists, schema) { - (true, Some(_)) => { - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) - } + (true, Some(_)) => self.return_empty_dataframe(), (true, None) | (false, None) => { let schema = Arc::new(MemorySchemaProvider::new()); catalog.register_schema(schema_name, schema)?; - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) + self.return_empty_dataframe() } (false, Some(_)) => Err(DataFusionError::Execution(format!( "Schema '{:?}' already exists", @@ -453,18 +454,14 @@ impl SessionContext { let catalog = self.catalog(catalog_name.as_str()); match (if_not_exists, catalog) { - (true, Some(_)) => { - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) - } + (true, Some(_)) => self.return_empty_dataframe(), (true, None) | (false, None) => { let new_catalog = Arc::new(MemoryCatalogProvider::new()); self.state .write() .catalog_list .register_catalog(catalog_name, new_catalog); - let plan = LogicalPlanBuilder::empty(false).build()?; - Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) + self.return_empty_dataframe() } (false, Some(_)) => Err(DataFusionError::Execution(format!( "Catalog '{:?}' already exists", @@ -477,6 +474,32 @@ impl SessionContext { } } + // return an empty dataframe + fn return_empty_dataframe(&self) -> Result> { + let plan = LogicalPlanBuilder::empty(false).build()?; + Ok(Arc::new(DataFrame::new(self.state.clone(), &plan))) + } + + fn find_and_deregister<'a>( + &self, + table_ref: impl Into>, + table_type: TableType, + ) -> Result { + let table_ref = table_ref.into(); + let table_provider = self + .state + .read() + .schema_for_ref(table_ref)? + .table(table_ref.table()); + + if let Some(table_provider) = table_provider { + if table_provider.table_type() == table_type { + self.deregister_table(table_ref)?; + return Ok(true); + } + } + Ok(false) + } /// Creates a logical plan. /// /// This function is intended for internal use and should not be called directly. diff --git a/datafusion/core/src/physical_plan/planner.rs b/datafusion/core/src/physical_plan/planner.rs index 2cdab3b0fe723..f727306ad1810 100644 --- a/datafusion/core/src/physical_plan/planner.rs +++ b/datafusion/core/src/physical_plan/planner.rs @@ -1042,7 +1042,7 @@ impl DefaultPhysicalPlanner { "Unsupported logical plan: CreateCatalog".to_string(), )) } - | LogicalPlan::CreateMemoryTable(_) | LogicalPlan::DropTable (_) | LogicalPlan::CreateView(_) => { + | LogicalPlan::CreateMemoryTable(_) | LogicalPlan::DropTable(_) | LogicalPlan::DropView(_) | LogicalPlan::CreateView(_) => { // Create a dummy exec. Ok(Arc::new(EmptyExec::new( false, diff --git a/datafusion/core/tests/sql/create_drop.rs b/datafusion/core/tests/sql/create_drop.rs index cca742c4a1d6d..820e570fac475 100644 --- a/datafusion/core/tests/sql/create_drop.rs +++ b/datafusion/core/tests/sql/create_drop.rs @@ -112,6 +112,104 @@ async fn drop_table() -> Result<()> { Ok(()) } +#[tokio::test] +async fn drop_view() -> Result<()> { + let ctx = + SessionContext::with_config(SessionConfig::new().with_information_schema(true)); + plan_and_collect(&ctx, "CREATE VIEW v AS SELECT 1").await?; + let rb = plan_and_collect( + &ctx, + "select * from information_schema.tables where table_name = 'v' and table_type = 'VIEW'", + ) + .await?; + assert_eq!(rb[0].num_rows(), 1); + + plan_and_collect(&ctx, "DROP VIEW v").await?; + let rb = plan_and_collect( + &ctx, + "select * from information_schema.tables where table_name = 'v' and table_type = 'VIEW'", + ) + .await?; + assert!(rb.is_empty()); + Ok(()) +} + +#[tokio::test] +#[should_panic(expected = "doesn't exist")] +async fn drop_view_nonexistent() { + let ctx = SessionContext::new(); + ctx.sql("DROP VIEW non_existent_view") + .await + .unwrap() + .collect() + .await + .unwrap(); +} + +#[tokio::test] +#[should_panic(expected = "doesn't exist")] +async fn drop_view_cant_drop_table() { + let ctx = SessionContext::new(); + ctx.sql("CREATE TABLE t AS SELECT 1") + .await + .unwrap() + .collect() + .await + .unwrap(); + ctx.sql("DROP VIEW t") + .await + .unwrap() + .collect() + .await + .unwrap(); +} + +#[tokio::test] +#[should_panic(expected = "doesn't exist")] +async fn drop_table_cant_drop_view() { + let ctx = SessionContext::new(); + ctx.sql("CREATE VIEW v AS SELECT 1") + .await + .unwrap() + .collect() + .await + .unwrap(); + ctx.sql("DROP TABLE v") + .await + .unwrap() + .collect() + .await + .unwrap(); +} + +#[tokio::test] +async fn drop_view_if_exists() -> Result<()> { + let ctx = + SessionContext::with_config(SessionConfig::new().with_information_schema(true)); + plan_and_collect(&ctx, "CREATE VIEW v AS SELECT 1").await?; + let rb = plan_and_collect(&ctx, "DROP VIEW IF EXISTS non_existent_view").await?; + // make sure we get an empty response back + assert!(rb.is_empty()); + + let rb = plan_and_collect( + &ctx, + "select * from information_schema.views where table_name = 'v'", + ) + .await?; + // confirm view exists + assert_eq!(rb[0].num_rows(), 1); + + plan_and_collect(&ctx, "DROP VIEW IF EXISTS v").await?; + let rb = plan_and_collect( + &ctx, + "select * from information_schema.views where table_name = 'v'", + ) + .await?; + // confirm view is gone + assert!(rb.is_empty()); + Ok(()) +} + #[tokio::test] async fn csv_query_create_external_table() { let ctx = SessionContext::new(); @@ -287,3 +385,8 @@ async fn create_csv_table_empty_file() -> Result<()> { Ok(()) } + +/// Execute SQL and return results +async fn plan_and_collect(ctx: &SessionContext, sql: &str) -> Result> { + ctx.sql(sql).await?.collect().await +} diff --git a/datafusion/expr/src/logical_plan/mod.rs b/datafusion/expr/src/logical_plan/mod.rs index ff0f5925406ef..9917d69a7910b 100644 --- a/datafusion/expr/src/logical_plan/mod.rs +++ b/datafusion/expr/src/logical_plan/mod.rs @@ -23,10 +23,10 @@ mod plan; pub use builder::{table_scan, LogicalPlanBuilder}; pub use plan::{ Aggregate, Analyze, CreateCatalog, CreateCatalogSchema, CreateExternalTable, - CreateMemoryTable, CreateView, CrossJoin, Distinct, DropTable, EmptyRelation, - Explain, Extension, FileType, Filter, Join, JoinConstraint, JoinType, Limit, - LogicalPlan, Partitioning, PlanType, PlanVisitor, Projection, Repartition, Sort, - StringifiedPlan, Subquery, SubqueryAlias, TableScan, ToStringifiedPlan, Union, + CreateMemoryTable, CreateView, CrossJoin, Distinct, DropTable, DropView, + EmptyRelation, Explain, Extension, FileType, Filter, Join, JoinConstraint, JoinType, + Limit, LogicalPlan, Partitioning, PlanType, PlanVisitor, Projection, Repartition, + Sort, StringifiedPlan, Subquery, SubqueryAlias, TableScan, ToStringifiedPlan, Union, Values, Window, }; diff --git a/datafusion/expr/src/logical_plan/plan.rs b/datafusion/expr/src/logical_plan/plan.rs index cec55bfc1b1e0..12e9e0738d9dd 100644 --- a/datafusion/expr/src/logical_plan/plan.rs +++ b/datafusion/expr/src/logical_plan/plan.rs @@ -86,6 +86,8 @@ pub enum LogicalPlan { CreateCatalog(CreateCatalog), /// Drops a table. DropTable(DropTable), + /// Drops a view. + DropView(DropView), /// Values expression. See /// [Postgres VALUES](https://www.postgresql.org/docs/current/queries-values.html) /// documentation for more details. @@ -137,6 +139,7 @@ impl LogicalPlan { } LogicalPlan::CreateCatalog(CreateCatalog { schema, .. }) => schema, LogicalPlan::DropTable(DropTable { schema, .. }) => schema, + LogicalPlan::DropView(DropView { schema, .. }) => schema, } } @@ -193,7 +196,7 @@ impl LogicalPlan { | LogicalPlan::CreateView(CreateView { input, .. }) | LogicalPlan::Filter(Filter { input, .. }) => input.all_schemas(), LogicalPlan::Distinct(Distinct { input, .. }) => input.all_schemas(), - LogicalPlan::DropTable(_) => vec![], + LogicalPlan::DropTable(_) | LogicalPlan::DropView(_) => vec![], } } @@ -253,6 +256,7 @@ impl LogicalPlan { | LogicalPlan::CreateCatalogSchema(_) | LogicalPlan::CreateCatalog(_) | LogicalPlan::DropTable(_) + | LogicalPlan::DropView(_) | LogicalPlan::CrossJoin(_) | LogicalPlan::Analyze { .. } | LogicalPlan::Explain { .. } @@ -296,7 +300,8 @@ impl LogicalPlan { | LogicalPlan::CreateExternalTable(_) | LogicalPlan::CreateCatalogSchema(_) | LogicalPlan::CreateCatalog(_) - | LogicalPlan::DropTable(_) => vec![], + | LogicalPlan::DropTable(_) + | LogicalPlan::DropView(_) => vec![], } } @@ -449,7 +454,8 @@ impl LogicalPlan { | LogicalPlan::CreateExternalTable(_) | LogicalPlan::CreateCatalogSchema(_) | LogicalPlan::CreateCatalog(_) - | LogicalPlan::DropTable(_) => true, + | LogicalPlan::DropTable(_) + | LogicalPlan::DropView(_) => true, }; if !recurse { return Ok(false); @@ -920,6 +926,11 @@ impl LogicalPlan { }) => { write!(f, "DropTable: {:?} if not exist:={}", name, if_exists) } + LogicalPlan::DropView(DropView { + name, if_exists, .. + }) => { + write!(f, "DropView: {:?} if not exist:={}", name, if_exists) + } LogicalPlan::Distinct(Distinct { .. }) => { write!(f, "Distinct:") } @@ -1019,6 +1030,17 @@ pub struct DropTable { pub schema: DFSchemaRef, } +/// Drops a view. +#[derive(Clone)] +pub struct DropView { + /// The view name + pub name: String, + /// If the view exists + pub if_exists: bool, + /// Dummy schema + pub schema: DFSchemaRef, +} + /// Produces no rows: An empty relation with an empty schema #[derive(Clone)] pub struct EmptyRelation { diff --git a/datafusion/expr/src/utils.rs b/datafusion/expr/src/utils.rs index 4d3f1bc33d7ec..65c4be24849b0 100644 --- a/datafusion/expr/src/utils.rs +++ b/datafusion/expr/src/utils.rs @@ -540,6 +540,7 @@ pub fn from_plan( | LogicalPlan::TableScan { .. } | LogicalPlan::CreateExternalTable(_) | LogicalPlan::DropTable(_) + | LogicalPlan::DropView(_) | LogicalPlan::CreateCatalogSchema(_) | LogicalPlan::CreateCatalog(_) => { // All of these plan types have no inputs / exprs so should not be called diff --git a/datafusion/optimizer/src/common_subexpr_eliminate.rs b/datafusion/optimizer/src/common_subexpr_eliminate.rs index 1bbd090070200..606c6b46fe6ce 100644 --- a/datafusion/optimizer/src/common_subexpr_eliminate.rs +++ b/datafusion/optimizer/src/common_subexpr_eliminate.rs @@ -226,6 +226,7 @@ fn optimize( | LogicalPlan::CreateCatalogSchema(_) | LogicalPlan::CreateCatalog(_) | LogicalPlan::DropTable(_) + | LogicalPlan::DropView(_) | LogicalPlan::Distinct(_) | LogicalPlan::Extension { .. } => { // apply the optimization to all inputs of the plan diff --git a/datafusion/optimizer/src/projection_push_down.rs b/datafusion/optimizer/src/projection_push_down.rs index c458923e9d9aa..79364eb1f109b 100644 --- a/datafusion/optimizer/src/projection_push_down.rs +++ b/datafusion/optimizer/src/projection_push_down.rs @@ -497,6 +497,7 @@ fn optimize_plan( | LogicalPlan::CreateCatalogSchema(_) | LogicalPlan::CreateCatalog(_) | LogicalPlan::DropTable(_) + | LogicalPlan::DropView(_) | LogicalPlan::CrossJoin(_) | LogicalPlan::Distinct(_) | LogicalPlan::Extension { .. } => { diff --git a/datafusion/proto/src/logical_plan.rs b/datafusion/proto/src/logical_plan.rs index bcdb9796f17ee..ca0487d39c0eb 100644 --- a/datafusion/proto/src/logical_plan.rs +++ b/datafusion/proto/src/logical_plan.rs @@ -1215,6 +1215,9 @@ impl AsLogicalPlan for LogicalPlanNode { LogicalPlan::DropTable(_) => Err(proto_error( "LogicalPlan serde is not yet implemented for DropTable", )), + LogicalPlan::DropView(_) => Err(proto_error( + "LogicalPlan serde is not yet implemented for DropView", + )), } } } diff --git a/datafusion/sql/src/planner.rs b/datafusion/sql/src/planner.rs index 65e38973ee456..967272694a557 100644 --- a/datafusion/sql/src/planner.rs +++ b/datafusion/sql/src/planner.rs @@ -26,7 +26,7 @@ use datafusion_expr::expr_rewriter::normalize_col_with_schemas; use datafusion_expr::logical_plan::{ Analyze, CreateCatalog, CreateCatalogSchema, CreateExternalTable as PlanCreateExternalTable, CreateMemoryTable, CreateView, - DropTable, Explain, FileType, JoinType, LogicalPlan, LogicalPlanBuilder, + DropTable, DropView, Explain, FileType, JoinType, LogicalPlan, LogicalPlanBuilder, Partitioning, PlanType, ToStringifiedPlan, }; use datafusion_expr::utils::{ @@ -245,20 +245,29 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { schema: Arc::new(DFSchema::empty()), })), Statement::Drop { - object_type: ObjectType::Table, + object_type, if_exists, names, cascade: _, purge: _, - } => - // We don't support cascade and purge for now. - { - Ok(LogicalPlan::DropTable(DropTable { + // We don't support cascade and purge for now. + // nor do we support multiple object names + } => match object_type { + ObjectType::Table => Ok(LogicalPlan::DropTable(DropTable { name: names.get(0).unwrap().to_string(), if_exists, schema: DFSchemaRef::new(DFSchema::empty()), - })) - } + })), + ObjectType::View => Ok(LogicalPlan::DropView(DropView { + name: names.get(0).unwrap().to_string(), + if_exists, + schema: DFSchemaRef::new(DFSchema::empty()), + })), + _ => Err(DataFusionError::NotImplemented( + "Only `DROP TABLE/VIEW ...` statement is supported currently" + .to_string(), + )), + }, Statement::ShowTables { extended, @@ -4170,7 +4179,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------- /// WindowAgg (cost=69.83..87.33 rows=1000 width=8) @@ -4189,7 +4198,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------------------- /// WindowAgg (cost=137.16..154.66 rows=1000 width=12) @@ -4277,7 +4286,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ----------------------------------------------------------------------------------- /// WindowAgg (cost=142.16..162.16 rows=1000 width=16) @@ -4300,7 +4309,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------------------------- /// WindowAgg (cost=139.66..172.16 rows=1000 width=24) @@ -4325,7 +4334,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------------------- /// WindowAgg (cost=69.83..117.33 rows=1000 width=24) @@ -4350,7 +4359,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------------------------- /// WindowAgg (cost=139.66..172.16 rows=1000 width=24) @@ -4379,7 +4388,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------- /// WindowAgg (cost=69.83..89.83 rows=1000 width=12) @@ -4399,7 +4408,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------- /// WindowAgg (cost=69.83..89.83 rows=1000 width=12) @@ -4419,7 +4428,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ---------------------------------------------------------------------------------- /// WindowAgg (cost=142.16..162.16 rows=1000 width=16) @@ -4443,7 +4452,7 @@ mod tests { } /// psql result - /// ``` + /// ```text /// QUERY PLAN /// ----------------------------------------------------------------------------- /// WindowAgg (cost=69.83..109.83 rows=1000 width=24) diff --git a/docs/source/user-guide/sql/ddl.md b/docs/source/user-guide/sql/ddl.md index ee73370e0c25e..8d24a8e4ce58c 100644 --- a/docs/source/user-guide/sql/ddl.md +++ b/docs/source/user-guide/sql/ddl.md @@ -94,14 +94,28 @@ CREATE TABLE memtable as select * from valuetable; ## DROP TABLE -The table can be deleted. +Removes the table from DataFusion's catalog. -``` -DROP TABLE [ IF EXISTS ] name -``` +
+DROP TABLE [ IF EXISTS ] table_name;
+
```sql CREATE TABLE users AS VALUES(1,2),(2,3); - DROP TABLE users; +-- or use 'if exists' to silently ignore if the table doesn't exist +DROP TABLE IF EXISTS nonexistent_table; +``` + +## DROP VIEW + +Removes the view from DataFusion's catalog. + +
+DROP VIEW [ IF EXISTS ] view_name;
+
+ +```sql +-- drop users_v view from the customer_a schema +DROP VIEW IF EXISTS customer_a.users_v; ```