[SPARK-31224][SQL] Add view support to SHOW CREATE TABLE

### What changes were proposed in this pull request?

For now `SHOW CREATE TABLE` command doesn't support views, but `SHOW CREATE TABLE AS SERDE` supports it. Since the views syntax are the same between Hive DDL and Spark DDL, we should be able to support views in both two commands.

This is Hive syntax for creating views:

```
CREATE VIEW [IF NOT EXISTS] [db_name.]view_name [(column_name [COMMENT column_comment], ...) ]
  [COMMENT view_comment]
  [TBLPROPERTIES (property_name = property_value, ...)]
  AS SELECT ...;
```

https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateView

This is Spark syntax for creating views:

```
CREATE [OR REPLACE] [[GLOBAL] TEMPORARY] VIEW [IF NOT EXISTS [db_name.]view_name
    create_view_clauses
    AS query;
```
https://spark.apache.org/docs/3.0.0-preview/sql-ref-syntax-ddl-create-view.html

Looks like it is the same. We could support views in both commands.

This patch proposes to add views support to `SHOW CREATE TABLE`.

### Why are the changes needed?

To extend the view support of `SHOW CREATE TABLE`, so users can use `SHOW CREATE TABLE` to show Spark DDL for views.

### Does this PR introduce any user-facing change?

Yes. `SHOW CREATE TABLE` can be used to show Spark DDL for views.

### How was this patch tested?

Unit tests.

Closes #27984 from viirya/spark-view.

Authored-by: Liang-Chi Hsieh <viirya@gmail.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
This commit is contained in:
Liang-Chi Hsieh 2020-04-06 05:34:59 +00:00 committed by Wenchen Fan
parent 35e6a9deee
commit d782a1c456
5 changed files with 151 additions and 133 deletions

View file

@ -1057,6 +1057,42 @@ trait ShowCreateTableCommandBase {
protected def concatByMultiLines(iter: Iterable[String]): String = {
iter.mkString("(\n ", ",\n ", ")\n")
}
protected def showCreateView(metadata: CatalogTable, builder: StringBuilder): Unit = {
showViewDataColumns(metadata, builder)
showTableComment(metadata, builder)
showViewProperties(metadata, builder)
showViewText(metadata, builder)
}
private def showViewDataColumns(metadata: CatalogTable, builder: StringBuilder): Unit = {
if (metadata.schema.nonEmpty) {
val viewColumns = metadata.schema.map { f =>
val comment = f.getComment()
.map(escapeSingleQuotedString)
.map(" COMMENT '" + _ + "'")
// view columns shouldn't have data type info
s"${quoteIdentifier(f.name)}${comment.getOrElse("")}"
}
builder ++= concatByMultiLines(viewColumns)
}
}
private def showViewProperties(metadata: CatalogTable, builder: StringBuilder): Unit = {
val viewProps = metadata.properties.filterKeys(!_.startsWith(CatalogTable.VIEW_PREFIX))
if (viewProps.nonEmpty) {
val props = viewProps.map { case (key, value) =>
s"'${escapeSingleQuotedString(key)}' = '${escapeSingleQuotedString(value)}'"
}
builder ++= s"TBLPROPERTIES ${concatByMultiLines(props)}"
}
}
private def showViewText(metadata: CatalogTable, builder: StringBuilder): Unit = {
builder ++= metadata.viewText.mkString("AS ", "", "\n")
}
}
/**
@ -1100,10 +1136,6 @@ case class ShowCreateTableCommand(table: TableIdentifier)
)
}
if (tableMetadata.tableType == VIEW) {
throw new AnalysisException("Hive view isn't supported by SHOW CREATE TABLE")
}
if ("true".equalsIgnoreCase(tableMetadata.properties.getOrElse("transactional", "false"))) {
throw new AnalysisException(
"SHOW CREATE TABLE doesn't support transactional Hive table. " +
@ -1111,10 +1143,26 @@ case class ShowCreateTableCommand(table: TableIdentifier)
"to show Hive DDL instead.")
}
convertTableMetadata(tableMetadata)
if (tableMetadata.tableType == VIEW) {
tableMetadata
} else {
convertTableMetadata(tableMetadata)
}
}
val stmt = showCreateDataSourceTable(metadata)
val builder = StringBuilder.newBuilder
val stmt = if (tableMetadata.tableType == VIEW) {
builder ++= s"CREATE VIEW ${table.quotedString} "
showCreateView(metadata, builder)
builder.toString()
} else {
builder ++= s"CREATE TABLE ${table.quotedString} "
showCreateDataSourceTable(metadata, builder)
builder.toString()
}
Seq(Row(stmt))
}
@ -1194,18 +1242,13 @@ case class ShowCreateTableCommand(table: TableIdentifier)
}
}
private def showCreateDataSourceTable(metadata: CatalogTable): String = {
val builder = StringBuilder.newBuilder
builder ++= s"CREATE TABLE ${table.quotedString} "
private def showCreateDataSourceTable(metadata: CatalogTable, builder: StringBuilder): Unit = {
showDataSourceTableDataColumns(metadata, builder)
showDataSourceTableOptions(metadata, builder)
showDataSourceTableNonDataColumns(metadata, builder)
showTableComment(metadata, builder)
showTableLocation(metadata, builder)
showTableProperties(metadata, builder)
builder.toString()
}
}
@ -1264,10 +1307,7 @@ case class ShowCreateTableAsSerdeCommand(table: TableIdentifier)
builder ++= s"CREATE$tableTypeString ${table.quotedString}"
if (metadata.tableType == VIEW) {
showViewDataColumns(metadata, builder)
showTableComment(metadata, builder)
showViewProperties(metadata, builder)
showViewText(metadata, builder)
showCreateView(metadata, builder)
} else {
showHiveTableHeader(metadata, builder)
showTableComment(metadata, builder)
@ -1280,35 +1320,6 @@ case class ShowCreateTableAsSerdeCommand(table: TableIdentifier)
builder.toString()
}
private def showViewDataColumns(metadata: CatalogTable, builder: StringBuilder): Unit = {
if (metadata.schema.nonEmpty) {
val viewColumns = metadata.schema.map { f =>
val comment = f.getComment()
.map(escapeSingleQuotedString)
.map(" COMMENT '" + _ + "'")
// view columns shouldn't have data type info
s"${quoteIdentifier(f.name)}${comment.getOrElse("")}"
}
builder ++= concatByMultiLines(viewColumns)
}
}
private def showViewProperties(metadata: CatalogTable, builder: StringBuilder): Unit = {
val viewProps = metadata.properties.filterKeys(!_.startsWith(CatalogTable.VIEW_PREFIX))
if (viewProps.nonEmpty) {
val props = viewProps.map { case (key, value) =>
s"'${escapeSingleQuotedString(key)}' = '${escapeSingleQuotedString(value)}'"
}
builder ++= s"TBLPROPERTIES ${concatByMultiLines(props)}"
}
}
private def showViewText(metadata: CatalogTable, builder: StringBuilder): Unit = {
builder ++= metadata.viewText.mkString("AS ", "", "\n")
}
private def showHiveTableHeader(metadata: CatalogTable, builder: StringBuilder): Unit = {
val columns = metadata.schema.filterNot { column =>
metadata.partitionColumnNames.contains(column.name)

View file

@ -74,6 +74,9 @@ CREATE VIEW view_SPARK_30302 (aaa, bbb)
AS SELECT a, b FROM tbl;
SHOW CREATE TABLE view_SPARK_30302 AS SERDE;
SHOW CREATE TABLE view_SPARK_30302;
DROP VIEW view_SPARK_30302;
@ -83,6 +86,9 @@ COMMENT 'This is a comment with \'quoted text\' for view'
AS SELECT a, b FROM tbl;
SHOW CREATE TABLE view_SPARK_30302 AS SERDE;
SHOW CREATE TABLE view_SPARK_30302;
DROP VIEW view_SPARK_30302;
@ -92,13 +98,9 @@ TBLPROPERTIES ('a' = '1', 'b' = '2')
AS SELECT a, b FROM tbl;
SHOW CREATE TABLE view_SPARK_30302 AS SERDE;
DROP VIEW view_SPARK_30302;
-- SHOW CREATE TABLE does not support view
CREATE VIEW view_SPARK_30302 (aaa, bbb)
AS SELECT a, b FROM tbl;
SHOW CREATE TABLE view_SPARK_30302;
DROP VIEW view_SPARK_30302;
DROP TABLE tbl;

View file

@ -301,6 +301,17 @@ CREATE VIEW `default`.`view_SPARK_30302`(
AS SELECT a, b FROM tbl
-- !query
SHOW CREATE TABLE view_SPARK_30302
-- !query schema
struct<createtab_stmt:string>
-- !query output
CREATE VIEW `default`.`view_SPARK_30302` (
`aaa`,
`bbb`)
AS SELECT a, b FROM tbl
-- !query
DROP VIEW view_SPARK_30302
-- !query schema
@ -331,6 +342,18 @@ COMMENT 'This is a comment with \'quoted text\' for view'
AS SELECT a, b FROM tbl
-- !query
SHOW CREATE TABLE view_SPARK_30302
-- !query schema
struct<createtab_stmt:string>
-- !query output
CREATE VIEW `default`.`view_SPARK_30302` (
`aaa` COMMENT 'comment with \'quoted text\' for aaa',
`bbb`)
COMMENT 'This is a comment with \'quoted text\' for view'
AS SELECT a, b FROM tbl
-- !query
DROP VIEW view_SPARK_30302
-- !query schema
@ -363,30 +386,18 @@ TBLPROPERTIES (
AS SELECT a, b FROM tbl
-- !query
DROP VIEW view_SPARK_30302
-- !query schema
struct<>
-- !query output
-- !query
CREATE VIEW view_SPARK_30302 (aaa, bbb)
AS SELECT a, b FROM tbl
-- !query schema
struct<>
-- !query output
-- !query
SHOW CREATE TABLE view_SPARK_30302
-- !query schema
struct<>
struct<createtab_stmt:string>
-- !query output
org.apache.spark.sql.AnalysisException
Hive view isn't supported by SHOW CREATE TABLE;
CREATE VIEW `default`.`view_SPARK_30302` (
`aaa`,
`bbb`)
TBLPROPERTIES (
'a' = '1',
'b' = '2')
AS SELECT a, b FROM tbl
-- !query

View file

@ -188,18 +188,26 @@ abstract class ShowCreateTableSuite extends QueryTest with SQLTestUtils {
if (result.length > 1) result(0) + result(1) else result.head
}
protected def checkCreateTable(table: String): Unit = {
checkCreateTableOrView(TableIdentifier(table, Some("default")), "TABLE")
protected def checkCreateTable(table: String, serde: Boolean = false): Unit = {
checkCreateTableOrView(TableIdentifier(table, Some("default")), "TABLE", serde)
}
protected def checkCreateView(table: String): Unit = {
checkCreateTableOrView(TableIdentifier(table, Some("default")), "VIEW")
protected def checkCreateView(table: String, serde: Boolean = false): Unit = {
checkCreateTableOrView(TableIdentifier(table, Some("default")), "VIEW", serde)
}
private def checkCreateTableOrView(table: TableIdentifier, checkType: String): Unit = {
protected def checkCreateTableOrView(
table: TableIdentifier,
checkType: String,
serde: Boolean): Unit = {
val db = table.database.getOrElse("default")
val expected = spark.sharedState.externalCatalog.getTable(db, table.table)
val shownDDL = sql(s"SHOW CREATE TABLE ${table.quotedString}").head().getString(0)
val shownDDL = if (serde) {
sql(s"SHOW CREATE TABLE ${table.quotedString} AS SERDE").head().getString(0)
} else {
sql(s"SHOW CREATE TABLE ${table.quotedString}").head().getString(0)
}
sql(s"DROP $checkType ${table.quotedString}")
try {

View file

@ -42,16 +42,43 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
}
test("view") {
withView("v1") {
sql("CREATE VIEW v1 AS SELECT 1 AS a")
checkCreateHiveTableOrView("v1", "VIEW")
Seq(true, false).foreach { serde =>
withView("v1") {
sql("CREATE VIEW v1 AS SELECT 1 AS a")
checkCreateView("v1", serde)
}
}
}
test("view with output columns") {
withView("v1") {
sql("CREATE VIEW v1 (b) AS SELECT 1 AS a")
checkCreateHiveTableOrView("v1", "VIEW")
test("view with output columns") {
Seq(true, false).foreach { serde =>
withView("v1") {
sql("CREATE VIEW v1 (a, b COMMENT 'b column') AS SELECT 1 AS a, 2 AS b")
checkCreateView("v1", serde)
}
}
}
test("view with table comment and properties") {
Seq(true, false).foreach { serde =>
withView("v1") {
sql(
s"""
|CREATE VIEW v1 (
| c1 COMMENT 'bla',
| c2
|)
|COMMENT 'table comment'
|TBLPROPERTIES (
| 'prop1' = 'value1',
| 'prop2' = 'value2'
|)
|AS SELECT 1 AS c1, '2' AS c2
""".stripMargin
)
checkCreateView("v1", serde)
}
}
}
@ -69,7 +96,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
""".stripMargin
)
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
}
@ -89,7 +116,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
""".stripMargin
)
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
}
}
@ -109,7 +136,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
""".stripMargin
)
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
}
@ -127,7 +154,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
""".stripMargin
)
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
}
@ -142,7 +169,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
""".stripMargin
)
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
}
@ -164,7 +191,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
""".stripMargin
)
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
}
@ -177,7 +204,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
|INTO 2 BUCKETS
""".stripMargin
)
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
}
@ -222,27 +249,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
val shownDDL = getShowDDL("SHOW CREATE TABLE t1")
assert(shownDDL == "CREATE TABLE `default`.`t1` (`a` STRUCT<`b`: STRING>)")
checkCreateHiveTableOrView("t1")
}
}
/**
* This method compares the given table with the table created by the DDL generated by
* `SHOW CREATE TABLE AS SERDE`.
*/
private def checkCreateHiveTableOrView(tableName: String, checkType: String = "TABLE"): Unit = {
val table = TableIdentifier(tableName, Some("default"))
val db = table.database.getOrElse("default")
val expected = spark.sharedState.externalCatalog.getTable(db, table.table)
val shownDDL = sql(s"SHOW CREATE TABLE ${table.quotedString} AS SERDE").head().getString(0)
sql(s"DROP $checkType ${table.quotedString}")
try {
sql(shownDDL)
val actual = spark.sharedState.externalCatalog.getTable(db, table.table)
checkCatalogTables(expected, actual)
} finally {
sql(s"DROP $checkType IF EXISTS ${table.table}")
checkCreateTable("t1", serde = true)
}
}
@ -344,7 +351,7 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
)
val cause = intercept[AnalysisException] {
checkCreateHiveTableOrView("t1")
checkCreateTable("t1", serde = true)
}
assert(cause.getMessage.contains("Use `SHOW CREATE TABLE` without `AS SERDE` instead"))
@ -446,27 +453,6 @@ class HiveShowCreateTableSuite extends ShowCreateTableSuite with TestHiveSinglet
}
}
test("hive view is not supported by show create table without as serde") {
withTable("t1") {
withView("v1") {
sql("CREATE TABLE t1 (c1 STRING, c2 STRING)")
createRawHiveTable(
s"""
|CREATE VIEW v1
|AS SELECT * from t1
""".stripMargin
)
val cause = intercept[AnalysisException] {
sql("SHOW CREATE TABLE v1")
}
assert(cause.getMessage.contains("view isn't supported"))
}
}
}
test("partitioned, bucketed hive table in Spark DDL") {
withTable("t1") {
sql(