You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by al...@apache.org on 2022/05/31 12:20:05 UTC

[arrow-datafusion] branch master updated: Implement DESCRIBE (#2642)
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 be57cf62d Implement DESCRIBE <table> (#2642)
be57cf62d is described below

commit be57cf62d3f71034de0bdb591dbae7a8ca04ffd7
Author: Yuhui Liu <27...@qq.com>
AuthorDate: Tue May 31 20:20:01 2022 +0800

    Implement DESCRIBE <table> (#2642)
    
    * Implement DESCRIBE <table>
    
    * Address comment
    
    * Handle table name not exists and add test cases
---
 datafusion/core/tests/sql/information_schema.rs | 35 +++++++++++++++++++++++++
 datafusion/sql/src/parser.rs                    | 24 +++++++++++++++++
 datafusion/sql/src/planner.rs                   | 28 +++++++++++++++++++-
 3 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/datafusion/core/tests/sql/information_schema.rs b/datafusion/core/tests/sql/information_schema.rs
index a7b6bdb45..c6ba61644 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 da4638764..31939f286 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<SQLStatement>),
     /// 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<Statement, ParserError> {
+        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<Statement, ParserError> {
         if self.parser.parse_keyword(Keyword::EXTERNAL) {
diff --git a/datafusion/sql/src/planner.rs b/datafusion/sql/src/planner.rs
index 3feb870ba..b21550567 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<LogicalPlan> {
+        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,