diff --git a/datafusion/core/tests/sql/information_schema.rs b/datafusion/core/tests/sql/information_schema.rs index a7b6bdb45175..c6ba61644eaa 100644 --- a/datafusion/core/tests/sql/information_schema.rs +++ b/datafusion/core/tests/sql/information_schema.rs @@ -224,6 +224,41 @@ async fn information_schema_show_tables_no_information_schema() { assert_eq!(err.to_string(), "Error during planning: SHOW TABLES is not supported unless information_schema is enabled"); } +#[tokio::test] +async fn information_schema_describe_table() { + let ctx = + SessionContext::with_config(SessionConfig::new().with_information_schema(true)); + + let sql = "CREATE OR REPLACE TABLE y AS VALUES (1,2),(3,4);"; + ctx.sql(sql).await.unwrap(); + + let sql_all = "describe y;"; + let results_all = execute_to_batches(&ctx, sql_all).await; + + let expected = vec![ + "+-------------+-----------+-------------+", + "| column_name | data_type | is_nullable |", + "+-------------+-----------+-------------+", + "| column1 | Int64 | YES |", + "| column2 | Int64 | YES |", + "+-------------+-----------+-------------+", + ]; + + assert_batches_eq!(expected, &results_all); +} + +#[tokio::test] +async fn information_schema_describe_table_not_exists() { + let ctx = SessionContext::with_config(SessionConfig::new()); + + let sql_all = "describe table;"; + let err = plan_and_collect(&ctx, sql_all).await.unwrap_err(); + assert_eq!( + err.to_string(), + "Error during planning: 'datafusion.public.table' not found" + ); +} + #[tokio::test] async fn information_schema_show_tables() { let ctx = diff --git a/datafusion/sql/src/parser.rs b/datafusion/sql/src/parser.rs index da4638764d7b..31939f286652 100644 --- a/datafusion/sql/src/parser.rs +++ b/datafusion/sql/src/parser.rs @@ -69,6 +69,13 @@ pub struct CreateExternalTable { pub if_not_exists: bool, } +/// DataFusion extension DDL for `DESCRIBE TABLE` +#[derive(Debug, Clone, PartialEq)] +pub struct DescribeTable { + /// Table name + pub table_name: String, +} + /// DataFusion Statement representations. /// /// Tokens parsed by `DFParser` are converted into these values. @@ -78,6 +85,8 @@ pub enum Statement { Statement(Box), /// Extension: `CREATE EXTERNAL TABLE` CreateExternalTable(CreateExternalTable), + /// Extension: `DESCRIBE TABLE` + DescribeTable(DescribeTable), } /// SQL Parser @@ -155,6 +164,12 @@ impl<'a> DFParser<'a> { // use custom parsing self.parse_create() } + Keyword::DESCRIBE => { + // move one token forward + self.parser.next_token(); + // use custom parsing + self.parse_describe() + } _ => { // use the native parser Ok(Statement::Statement(Box::from( @@ -172,6 +187,15 @@ impl<'a> DFParser<'a> { } } + pub fn parse_describe(&mut self) -> Result { + let table_name = self.parser.parse_object_name()?; + + let des = DescribeTable { + table_name: table_name.to_string(), + }; + Ok(Statement::DescribeTable(des)) + } + /// Parse a SQL CREATE statement pub fn parse_create(&mut self) -> Result { if self.parser.parse_keyword(Keyword::EXTERNAL) { diff --git a/datafusion/sql/src/planner.rs b/datafusion/sql/src/planner.rs index 3feb870baa1c..b21550567f53 100644 --- a/datafusion/sql/src/planner.rs +++ b/datafusion/sql/src/planner.rs @@ -17,7 +17,7 @@ //! SQL Query Planner (produces logical plan from SQL AST) -use crate::parser::{CreateExternalTable, Statement as DFStatement}; +use crate::parser::{CreateExternalTable, DescribeTable, Statement as DFStatement}; use arrow::datatypes::*; use datafusion_common::ToDFSchema; use datafusion_expr::expr_rewriter::normalize_col; @@ -139,6 +139,7 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { match statement { DFStatement::CreateExternalTable(s) => self.external_table_to_plan(s), DFStatement::Statement(s) => self.sql_statement_to_plan(*s), + DFStatement::DescribeTable(s) => self.describe_table_to_plan(s), } } @@ -353,6 +354,31 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { } } + pub fn describe_table_to_plan( + &self, + statement: DescribeTable, + ) -> Result { + let table_name = statement.table_name; + let table_ref: TableReference = table_name.as_str().into(); + + // check if table_name exists + if let Err(e) = self.schema_provider.get_table_provider(table_ref) { + return Err(e); + } + + if self.has_table("information_schema", "tables") { + let sql = format!("SELECT column_name, data_type, is_nullable \ + FROM information_schema.columns WHERE table_name = '{table_name}';"); + let mut rewrite = DFParser::parse_sql(&sql[..])?; + self.statement_to_plan(rewrite.pop_front().unwrap()) + } else { + Err(DataFusionError::Plan( + "DESCRIBE TABLE is not supported unless information_schema is enabled" + .to_string(), + )) + } + } + /// Generate a logical plan from a CREATE EXTERNAL TABLE statement pub fn external_table_to_plan( &self,