This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-datafusion.git
The following commit(s) were added to refs/heads/master by this push:
new 5621e3bbd implement `drop view` (#3267)
5621e3bbd is described below
commit 5621e3bbd050eeb79646240ec0a09426badfa162
Author: kmitchener <[email protected]>
AuthorDate: Sat Sep 3 05:25:52 2022 -0400
implement `drop view` (#3267)
* add initial support for 'drop view'
* add tests
* add very basic docs
* add example to drop view doc
* ran prettier on the doc
* refactored, now done
* removed typo
* fix bad resolve merge conflict
---
datafusion/core/src/execution/context.rs | 93 ++++++++++++-------
datafusion/core/src/physical_plan/planner.rs | 2 +-
datafusion/core/tests/sql/create_drop.rs | 103 +++++++++++++++++++++
datafusion/expr/src/logical_plan/mod.rs | 8 +-
datafusion/expr/src/logical_plan/plan.rs | 28 +++++-
datafusion/expr/src/utils.rs | 1 +
.../optimizer/src/common_subexpr_eliminate.rs | 1 +
datafusion/optimizer/src/projection_push_down.rs | 1 +
datafusion/proto/src/logical_plan.rs | 3 +
datafusion/sql/src/planner.rs | 45 +++++----
docs/source/user-guide/sql/ddl.md | 24 ++++-
11 files changed, 243 insertions(+), 66 deletions(-)
diff --git a/datafusion/core/src/execution/context.rs
b/datafusion/core/src/execution/context.rs
index 0b1618146..c5dcb7a59 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<Arc<DataFrame>> {
+ 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<TableReference<'a>>,
+ table_type: TableType,
+ ) -> Result<bool> {
+ 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 2cdab3b0f..f727306ad 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 cca742c4a..820e570fa 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<Vec<RecordBatch>> {
+ 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 ff0f59254..9917d69a7 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 cec55bfc1..12e9e0738 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 4d3f1bc33..65c4be248 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 fcefd8fa7..2d148a087 100644
--- a/datafusion/optimizer/src/common_subexpr_eliminate.rs
+++ b/datafusion/optimizer/src/common_subexpr_eliminate.rs
@@ -232,6 +232,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 c458923e9..79364eb1f 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 bcdb9796f..ca0487d39 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 65e38973e..967272694 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 ee73370e0..8d24a8e4c 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
-```
+<pre>
+DROP TABLE [ IF EXISTS ] <b><i>table_name</i></b>;
+</pre>
```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.
+
+<pre>
+DROP VIEW [ IF EXISTS ] <b><i>view_name</i></b>;
+</pre>
+
+```sql
+-- drop users_v view from the customer_a schema
+DROP VIEW IF EXISTS customer_a.users_v;
```