(#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,