[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:
parent
35e6a9deee
commit
d782a1c456
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue