This is an automated email from the ASF dual-hosted git repository.
wenchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new c91dbde2f94 [SPARK-33393][SQL] Support SHOW TABLE EXTENDED in v2
c91dbde2f94 is described below
commit c91dbde2f94c15d2007dccaeec5d72a159e9f4e2
Author: panbingkun <[email protected]>
AuthorDate: Thu Nov 16 13:15:27 2023 +0800
[SPARK-33393][SQL] Support SHOW TABLE EXTENDED in v2
### What changes were proposed in this pull request?
The pr aim to implement v2 SHOW TABLE EXTENDED as `ShowTableExec`
### Why are the changes needed?
To have feature parity with the datasource V1.
### Does this PR introduce _any_ user-facing change?
Yes, Support SHOW TABLE EXTENDED in v2.
### How was this patch tested?
Add new UT.
By running the unified tests for v2 implementation:
```
$ build/sbt -Phive-2.3 -Phive-thriftserver "test:testOnly *ShowTablesSuite"
$ build/sbt "test:testOnly *ShowTablesSuite"
```
Closes #37588 from panbingkun/v2_SHOW_TABLE_EXTENDED.
Authored-by: panbingkun <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
---
.../src/main/resources/error/error-classes.json | 5 -
.../sql/catalyst/analysis/CheckAnalysis.scala | 4 -
.../sql/catalyst/analysis/ResolveCatalogs.scala | 2 +-
.../sql/catalyst/catalog/SessionCatalog.scala | 19 ++
.../spark/sql/catalyst/parser/AstBuilder.scala | 30 +-
.../sql/catalyst/plans/logical/v2Commands.scala | 23 +-
.../spark/sql/errors/QueryCompilationErrors.scala | 19 +-
.../datasources/v2/DataSourceV2Implicits.scala | 5 +
.../catalyst/analysis/ResolveSessionCatalog.scala | 22 +-
.../datasources/v2/DataSourceV2Strategy.scala | 10 +
.../datasources/v2/ShowTablesExtendedExec.scala | 189 ++++++++++++
.../sql-tests/analyzer-results/show-tables.sql.out | 13 +-
.../sql-tests/results/show-tables.sql.out | 13 +-
.../execution/command/ShowTablesParserSuite.scala | 34 +--
.../execution/command/ShowTablesSuiteBase.scala | 334 ++++++++++++++++++++-
.../sql/execution/command/v1/ShowTablesSuite.scala | 70 ++++-
.../sql/execution/command/v2/ShowTablesSuite.scala | 71 ++---
.../hive/execution/command/ShowTablesSuite.scala | 77 +++++
18 files changed, 815 insertions(+), 125 deletions(-)
diff --git a/common/utils/src/main/resources/error/error-classes.json
b/common/utils/src/main/resources/error/error-classes.json
index b60d70cefc9..afcd841a2ce 100644
--- a/common/utils/src/main/resources/error/error-classes.json
+++ b/common/utils/src/main/resources/error/error-classes.json
@@ -4776,11 +4776,6 @@
"Invalid bound function '<bound>: there are <argsLen> arguments but
<inputTypesLen> parameters returned from 'inputTypes()'."
]
},
- "_LEGACY_ERROR_TEMP_1200" : {
- "message" : [
- "<name> is not supported for v2 tables."
- ]
- },
"_LEGACY_ERROR_TEMP_1201" : {
"message" : [
"Cannot resolve column name \"<colName>\" among (<fieldNames>)."
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
index 176a45a6f8e..3843901a2e0 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala
@@ -271,10 +271,6 @@ trait CheckAnalysis extends PredicateHelper with
LookupCatalog with QueryErrorsB
case _ =>
}
- // `ShowTableExtended` should have been converted to the v1 command if
the table is v1.
- case _: ShowTableExtended =>
- throw QueryCompilationErrors.commandUnsupportedInV2TableError("SHOW
TABLE EXTENDED")
-
case operator: LogicalPlan =>
operator transformExpressionsDown {
// Check argument data types of higher-order functions downwards
first.
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveCatalogs.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveCatalogs.scala
index 253c8eb190f..b0df76068ff 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveCatalogs.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveCatalogs.scala
@@ -50,7 +50,7 @@ class ResolveCatalogs(val catalogManager: CatalogManager)
case s @ ShowTables(UnresolvedNamespace(Seq()), _, _) =>
s.copy(namespace = ResolvedNamespace(currentCatalog,
catalogManager.currentNamespace.toImmutableArraySeq))
- case s @ ShowTableExtended(UnresolvedNamespace(Seq()), _, _, _) =>
+ case s @ ShowTablesExtended(UnresolvedNamespace(Seq()), _, _) =>
s.copy(namespace = ResolvedNamespace(currentCatalog,
catalogManager.currentNamespace.toImmutableArraySeq))
case s @ ShowViews(UnresolvedNamespace(Seq()), _, _) =>
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/SessionCatalog.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/SessionCatalog.scala
index e71865df94d..e9a02a243aa 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/SessionCatalog.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/SessionCatalog.scala
@@ -1090,6 +1090,25 @@ class SessionCatalog(
dbViews ++ listLocalTempViews(pattern)
}
+ /**
+ * List all matching temp views in the specified database, including
global/local temporary views.
+ */
+ def listTempViews(db: String, pattern: String): Seq[CatalogTable] = {
+ val globalTempViews = if (format(db) == globalTempViewManager.database) {
+ globalTempViewManager.listViewNames(pattern).flatMap { viewName =>
+ globalTempViewManager.get(viewName).map(_.tableMeta)
+ }
+ } else {
+ Seq.empty
+ }
+
+ val localTempViews = listLocalTempViews(pattern).flatMap { viewIndent =>
+ tempViews.get(viewIndent.table).map(_.tableMeta)
+ }
+
+ globalTempViews ++ localTempViews
+ }
+
/**
* List all matching local temporary views.
*/
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
index ddc91db5967..09161d8f8da 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
@@ -4086,19 +4086,31 @@ class AstBuilder extends DataTypeAstBuilder with
SQLConfHelper with Logging {
}
/**
- * Create a [[ShowTableExtended]] command.
+ * Create a [[ShowTablesExtended]] or [[ShowTablePartition]] command.
*/
override def visitShowTableExtended(
ctx: ShowTableExtendedContext): LogicalPlan = withOrigin(ctx) {
- val partitionKeys = Option(ctx.partitionSpec).map { specCtx =>
- UnresolvedPartitionSpec(visitNonOptionalPartitionSpec(specCtx), None)
- }
- val ns = if (ctx.identifierReference() != null) {
- withIdentClause(ctx.identifierReference, UnresolvedNamespace(_))
- } else {
- UnresolvedNamespace(Seq.empty[String])
+ Option(ctx.partitionSpec).map { spec =>
+ val table = withOrigin(ctx.pattern) {
+ if (ctx.identifierReference() != null) {
+ withIdentClause(ctx.identifierReference(), ns => {
+ val names = ns :+ string(visitStringLit(ctx.pattern))
+ UnresolvedTable(names, "SHOW TABLE EXTENDED ... PARTITION ...")
+ })
+ } else {
+ val names = Seq.empty[String] :+ string(visitStringLit(ctx.pattern))
+ UnresolvedTable(names, "SHOW TABLE EXTENDED ... PARTITION ...")
+ }
+ }
+ ShowTablePartition(table,
UnresolvedPartitionSpec(visitNonOptionalPartitionSpec(spec)))
+ }.getOrElse {
+ val ns = if (ctx.identifierReference() != null) {
+ withIdentClause(ctx.identifierReference, UnresolvedNamespace)
+ } else {
+ UnresolvedNamespace(Seq.empty[String])
+ }
+ ShowTablesExtended(ns, string(visitStringLit(ctx.pattern)))
}
- ShowTableExtended(ns, string(visitStringLit(ctx.pattern)), partitionKeys)
}
/**
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
index 55d71ff6c24..0a18532b134 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
@@ -886,19 +886,18 @@ object ShowTables {
}
/**
- * The logical plan of the SHOW TABLE EXTENDED command.
+ * The logical plan of the SHOW TABLE EXTENDED (without PARTITION) command.
*/
-case class ShowTableExtended(
+case class ShowTablesExtended(
namespace: LogicalPlan,
pattern: String,
- partitionSpec: Option[PartitionSpec],
- override val output: Seq[Attribute] = ShowTableExtended.getOutputAttrs)
extends UnaryCommand {
+ override val output: Seq[Attribute] = ShowTablesUtils.getOutputAttrs)
extends UnaryCommand {
override def child: LogicalPlan = namespace
- override protected def withNewChildInternal(newChild: LogicalPlan):
ShowTableExtended =
+ override protected def withNewChildInternal(newChild: LogicalPlan):
ShowTablesExtended =
copy(namespace = newChild)
}
-object ShowTableExtended {
+object ShowTablesUtils {
def getOutputAttrs: Seq[Attribute] = Seq(
AttributeReference("namespace", StringType, nullable = false)(),
AttributeReference("tableName", StringType, nullable = false)(),
@@ -906,6 +905,18 @@ object ShowTableExtended {
AttributeReference("information", StringType, nullable = false)())
}
+/**
+ * The logical plan of the SHOW TABLE EXTENDED ... PARTITION ... command.
+ */
+case class ShowTablePartition(
+ table: LogicalPlan,
+ partitionSpec: PartitionSpec,
+ override val output: Seq[Attribute] = ShowTablesUtils.getOutputAttrs)
+ extends V2PartitionCommand {
+ override protected def withNewChildInternal(newChild: LogicalPlan):
ShowTablePartition =
+ copy(table = newChild)
+}
+
/**
* The logical plan of the SHOW VIEWS command.
*
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
index 9cc99e9bfa3..603de520b18 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryCompilationErrors.scala
@@ -23,7 +23,7 @@ import org.apache.hadoop.fs.Path
import org.apache.spark.{SPARK_DOC_ROOT, SparkException, SparkThrowable,
SparkThrowableHelper, SparkUnsupportedOperationException}
import org.apache.spark.sql.AnalysisException
-import org.apache.spark.sql.catalyst.{ExtendedAnalysisException,
FunctionIdentifier, QualifiedTableName, TableIdentifier}
+import org.apache.spark.sql.catalyst.{ExtendedAnalysisException,
FunctionIdentifier, InternalRow, QualifiedTableName, TableIdentifier}
import
org.apache.spark.sql.catalyst.analysis.{CannotReplaceMissingTableException,
FunctionAlreadyExistsException, NamespaceAlreadyExistsException,
NoSuchFunctionException, NoSuchNamespaceException, NoSuchPartitionException,
NoSuchTableException, ResolvedTable, Star, TableAlreadyExistsException,
UnresolvedRegex}
import org.apache.spark.sql.catalyst.catalog.{CatalogTable,
InvalidUDFClassException}
import org.apache.spark.sql.catalyst.catalog.CatalogTypes.TablePartitionSpec
@@ -2145,12 +2145,6 @@ private[sql] object QueryCompilationErrors extends
QueryErrorsBase with Compilat
"inputTypesLen" -> bound.inputTypes().length.toString))
}
- def commandUnsupportedInV2TableError(name: String): Throwable = {
- new AnalysisException(
- errorClass = "_LEGACY_ERROR_TEMP_1200",
- messageParameters = Map("name" -> name))
- }
-
def cannotResolveColumnNameAmongAttributesError(
colName: String, fieldNames: String): Throwable = {
new AnalysisException(
@@ -2477,7 +2471,7 @@ private[sql] object QueryCompilationErrors extends
QueryErrorsBase with Compilat
errorClass = "_LEGACY_ERROR_TEMP_1231",
messageParameters = Map(
"key" -> key,
- "tblName" -> tblName))
+ "tblName" -> toSQLId(tblName)))
}
def invalidPartitionSpecError(
@@ -2489,7 +2483,7 @@ private[sql] object QueryCompilationErrors extends
QueryErrorsBase with Compilat
messageParameters = Map(
"specKeys" -> specKeys,
"partitionColumnNames" -> partitionColumnNames.mkString(", "),
- "tableName" -> tableName))
+ "tableName" -> toSQLId(tableName)))
}
def columnAlreadyExistsError(columnName: String): Throwable = {
@@ -2547,6 +2541,13 @@ private[sql] object QueryCompilationErrors extends
QueryErrorsBase with Compilat
new NoSuchPartitionException(db, table, partition)
}
+ def notExistPartitionError(
+ table: Identifier,
+ partitionIdent: InternalRow,
+ partitionSchema: StructType): Throwable = {
+ new NoSuchPartitionException(table.toString, partitionIdent,
partitionSchema)
+ }
+
def analyzingColumnStatisticsNotSupportedForColumnTypeError(
name: String,
dataType: DataType): Throwable = {
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Implicits.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Implicits.scala
index 795778f9869..6b884713bd5 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Implicits.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Implicits.scala
@@ -65,6 +65,11 @@ object DataSourceV2Implicits {
}
}
+ def supportsPartitions: Boolean = table match {
+ case _: SupportsPartitionManagement => true
+ case _ => false
+ }
+
def asPartitionable: SupportsPartitionManagement = {
table match {
case support: SupportsPartitionManagement =>
diff --git
a/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
b/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
index 078b988eaf0..5fab89f4879 100644
---
a/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
+++
b/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
@@ -241,19 +241,31 @@ class ResolveSessionCatalog(val catalogManager:
CatalogManager)
case ShowTables(DatabaseInSessionCatalog(db), pattern, output) if
conf.useV1Command =>
ShowTablesCommand(Some(db), pattern, output)
- case ShowTableExtended(
+ case ShowTablesExtended(
DatabaseInSessionCatalog(db),
pattern,
- partitionSpec @ (None | Some(UnresolvedPartitionSpec(_, _))),
output) =>
val newOutput = if
(conf.getConf(SQLConf.LEGACY_KEEP_COMMAND_OUTPUT_SCHEMA)) {
- assert(output.length == 4)
output.head.withName("database") +: output.tail
} else {
output
}
- val tablePartitionSpec =
partitionSpec.map(_.asInstanceOf[UnresolvedPartitionSpec].spec)
- ShowTablesCommand(Some(db), Some(pattern), newOutput, true,
tablePartitionSpec)
+ ShowTablesCommand(Some(db), Some(pattern), newOutput, isExtended = true)
+
+ case ShowTablePartition(
+ ResolvedTable(catalog, _, table: V1Table, _),
+ partitionSpec,
+ output) if isSessionCatalog(catalog) =>
+ val newOutput = if
(conf.getConf(SQLConf.LEGACY_KEEP_COMMAND_OUTPUT_SCHEMA)) {
+ output.head.withName("database") +: output.tail
+ } else {
+ output
+ }
+ val tablePartitionSpec = Option(partitionSpec).map(
+ _.asInstanceOf[UnresolvedPartitionSpec].spec)
+ ShowTablesCommand(table.catalogTable.identifier.database,
+ Some(table.catalogTable.identifier.table), newOutput,
+ isExtended = true, tablePartitionSpec)
// ANALYZE TABLE works on permanent views if the views are cached.
case AnalyzeTable(ResolvedV1TableOrViewIdentifier(ident), partitionSpec,
noScan) =>
diff --git
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
index e6bce7a0990..3f0dab11cda 100644
---
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
+++
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
@@ -406,6 +406,16 @@ class DataSourceV2Strategy(session: SparkSession) extends
Strategy with Predicat
case ShowTables(ResolvedNamespace(catalog, ns), pattern, output) =>
ShowTablesExec(output, catalog.asTableCatalog, ns, pattern) :: Nil
+ case ShowTablesExtended(
+ ResolvedNamespace(catalog, ns),
+ pattern,
+ output) =>
+ ShowTablesExtendedExec(output, catalog.asTableCatalog, ns, pattern) ::
Nil
+
+ case ShowTablePartition(r: ResolvedTable, part, output) =>
+ ShowTablePartitionExec(output, r.catalog, r.identifier,
+ r.table.asPartitionable, Seq(part).asResolvedPartitionSpecs.head) ::
Nil
+
case SetCatalogAndNamespace(ResolvedNamespace(catalog, ns)) =>
val catalogManager = session.sessionState.catalogManager
val namespace = if (ns.nonEmpty) Some(ns) else None
diff --git
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ShowTablesExtendedExec.scala
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ShowTablesExtendedExec.scala
new file mode 100644
index 00000000000..0b2d11a597d
--- /dev/null
+++
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ShowTablesExtendedExec.scala
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.execution.datasources.v2
+
+import scala.collection.mutable
+import scala.collection.mutable.ArrayBuffer
+import scala.jdk.CollectionConverters._
+
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.catalyst.analysis.ResolvedPartitionSpec
+import org.apache.spark.sql.catalyst.catalog.CatalogTableType
+import
org.apache.spark.sql.catalyst.catalog.ExternalCatalogUtils.escapePathName
+import org.apache.spark.sql.catalyst.expressions.{Attribute, Literal,
ToPrettyString}
+import org.apache.spark.sql.catalyst.util.{quoteIdentifier, StringUtils}
+import org.apache.spark.sql.connector.catalog.{CatalogV2Util, Identifier,
SupportsPartitionManagement, Table, TableCatalog}
+import org.apache.spark.sql.connector.catalog.CatalogV2Implicits._
+import org.apache.spark.sql.errors.QueryCompilationErrors
+import org.apache.spark.sql.execution.LeafExecNode
+import
org.apache.spark.sql.execution.datasources.v2.DataSourceV2Implicits.TableHelper
+
+/**
+ * Physical plan node for showing tables without partition, Show the
information of tables.
+ */
+case class ShowTablesExtendedExec(
+ output: Seq[Attribute],
+ catalog: TableCatalog,
+ namespace: Seq[String],
+ pattern: String) extends V2CommandExec with LeafExecNode {
+ override protected def run(): Seq[InternalRow] = {
+ val rows = new ArrayBuffer[InternalRow]()
+
+ // fetch tables
+ // TODO We need a new listTable overload that takes a pattern string.
+ val tables = catalog.listTables(namespace.toArray)
+ tables.map { tableIdent =>
+ if (StringUtils.filterPattern(Seq(tableIdent.name()), pattern).nonEmpty)
{
+ val table = catalog.loadTable(tableIdent)
+ val information = getTableDetails(catalog.name, tableIdent, table)
+ rows += toCatalystRow(tableIdent.namespace().quoted,
tableIdent.name(), false,
+ s"$information\n")
+ }
+ }
+
+ // fetch temp views, includes: global temp view, local temp view
+ val sessionCatalog = session.sessionState.catalog
+ val db = namespace match {
+ case Seq(db) => Some(db)
+ case _ => None
+ }
+ val tempViews = sessionCatalog.listTempViews(db.getOrElse(""), pattern)
+ tempViews.map { tempView =>
+ val database = tempView.identifier.database.getOrElse("")
+ val tableName = tempView.identifier.table
+ val information = tempView.simpleString
+ rows += toCatalystRow(database, tableName, true, s"$information\n")
+ }
+
+ rows.toSeq
+ }
+
+ private def getTableDetails(
+ catalogName: String,
+ identifier: Identifier,
+ table: Table): String = {
+ val results = new mutable.LinkedHashMap[String, String]()
+
+ results.put("Catalog", catalogName)
+ results.put("Namespace", identifier.namespace().quoted)
+ results.put("Table", identifier.name())
+ val tableType = if
(table.properties().containsKey(TableCatalog.PROP_EXTERNAL)) {
+ CatalogTableType.EXTERNAL
+ } else {
+ CatalogTableType.MANAGED
+ }
+ results.put("Type", tableType.name)
+
+ CatalogV2Util.TABLE_RESERVED_PROPERTIES
+ .filterNot(_ == TableCatalog.PROP_EXTERNAL)
+ .foreach(propKey => {
+ if (table.properties.containsKey(propKey)) {
+ results.put(propKey.capitalize, table.properties.get(propKey))
+ }
+ })
+
+ val properties =
+ conf.redactOptions(table.properties.asScala.toMap).toList
+ .filter(kv => !CatalogV2Util.TABLE_RESERVED_PROPERTIES.contains(kv._1))
+ .sortBy(_._1).map {
+ case (key, value) => key + "=" + value
+ }.mkString("[", ",", "]")
+ if (!table.properties().isEmpty) {
+ results.put("Table Properties", properties.mkString("[", ", ", "]"))
+ }
+
+ // Partition Provider & Partition Columns
+ if (table.supportsPartitions &&
table.asPartitionable.partitionSchema().nonEmpty) {
+ results.put("Partition Provider", "Catalog")
+ results.put("Partition Columns",
table.asPartitionable.partitionSchema().map(
+ field => quoteIdentifier(field.name)).mkString("[", ", ", "]"))
+ }
+
+ if (table.schema().nonEmpty) {
+ results.put("Schema", table.schema().treeString)
+ }
+
+ results.map { case (key, value) =>
+ if (value.isEmpty) key else s"$key: $value"
+ }.mkString("", "\n", "")
+ }
+}
+
+/**
+ * Physical plan node for showing tables with partition, Show the information
of partitions.
+ */
+case class ShowTablePartitionExec(
+ output: Seq[Attribute],
+ catalog: TableCatalog,
+ tableIndent: Identifier,
+ table: SupportsPartitionManagement,
+ partSpec: ResolvedPartitionSpec) extends V2CommandExec with LeafExecNode {
+ override protected def run(): Seq[InternalRow] = {
+ val rows = new ArrayBuffer[InternalRow]()
+ val information = getTablePartitionDetails(tableIndent,
+ table, partSpec)
+ rows += toCatalystRow(tableIndent.namespace.quoted,
+ tableIndent.name(), false, s"$information\n")
+
+ rows.toSeq
+ }
+
+ private def getTablePartitionDetails(
+ tableIdent: Identifier,
+ partitionTable: SupportsPartitionManagement,
+ partSpec: ResolvedPartitionSpec): String = {
+ val results = new mutable.LinkedHashMap[String, String]()
+
+ // "Partition Values"
+ val partitionSchema = partitionTable.partitionSchema()
+ val (names, ident) = (partSpec.names, partSpec.ident)
+ val partitionIdentifiers =
partitionTable.listPartitionIdentifiers(names.toArray, ident)
+ if (partitionIdentifiers.isEmpty) {
+ throw QueryCompilationErrors.notExistPartitionError(tableIdent, ident,
partitionSchema)
+ }
+ assert(partitionIdentifiers.length == 1)
+ val row = partitionIdentifiers.head
+ val len = partitionSchema.length
+ val partitions = new Array[String](len)
+ val timeZoneId = conf.sessionLocalTimeZone
+ for (i <- 0 until len) {
+ val dataType = partitionSchema(i).dataType
+ val partValueUTF8String = ToPrettyString(Literal(row.get(i, dataType),
dataType),
+ Some(timeZoneId)).eval(null)
+ val partValueStr = if (partValueUTF8String == null) "null" else
partValueUTF8String.toString
+ partitions(i) = escapePathName(partitionSchema(i).name) + "=" +
escapePathName(partValueStr)
+ }
+ val partitionValues = partitions.mkString("[", ", ", "]")
+ results.put("Partition Values", s"$partitionValues")
+
+ // "Partition Parameters"
+ val metadata = partitionTable.loadPartitionMetadata(ident)
+ if (!metadata.isEmpty) {
+ val metadataValues = metadata.asScala.map { case (key, value) =>
+ if (value.isEmpty) key else s"$key: $value"
+ }.mkString("{", ", ", "}")
+ results.put("Partition Parameters", metadataValues)
+ }
+
+ // TODO "Created Time", "Last Access", "Partition Statistics"
+
+ results.map { case (key, value) =>
+ if (value.isEmpty) key else s"$key: $value"
+ }.mkString("", "\n", "")
+ }
+}
diff --git
a/sql/core/src/test/resources/sql-tests/analyzer-results/show-tables.sql.out
b/sql/core/src/test/resources/sql-tests/analyzer-results/show-tables.sql.out
index 167e2f8622b..ce5f7995f5d 100644
--- a/sql/core/src/test/resources/sql-tests/analyzer-results/show-tables.sql.out
+++ b/sql/core/src/test/resources/sql-tests/analyzer-results/show-tables.sql.out
@@ -130,13 +130,20 @@ org.apache.spark.sql.catalyst.parser.ParseException
-- !query
SHOW TABLE EXTENDED LIKE 'show_t*' PARTITION(c='Us', d=1)
-- !query analysis
-org.apache.spark.sql.catalyst.analysis.NoSuchTableException
+org.apache.spark.sql.catalyst.ExtendedAnalysisException
{
"errorClass" : "TABLE_OR_VIEW_NOT_FOUND",
"sqlState" : "42P01",
"messageParameters" : {
- "relationName" : "`showdb`.`show_t*`"
- }
+ "relationName" : "`show_t*`"
+ },
+ "queryContext" : [ {
+ "objectType" : "",
+ "objectName" : "",
+ "startIndex" : 26,
+ "stopIndex" : 34,
+ "fragment" : "'show_t*'"
+ } ]
}
diff --git a/sql/core/src/test/resources/sql-tests/results/show-tables.sql.out
b/sql/core/src/test/resources/sql-tests/results/show-tables.sql.out
index a37cf630969..442f0fe5d5f 100644
--- a/sql/core/src/test/resources/sql-tests/results/show-tables.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/show-tables.sql.out
@@ -208,13 +208,20 @@ SHOW TABLE EXTENDED LIKE 'show_t*' PARTITION(c='Us', d=1)
-- !query schema
struct<>
-- !query output
-org.apache.spark.sql.catalyst.analysis.NoSuchTableException
+org.apache.spark.sql.catalyst.ExtendedAnalysisException
{
"errorClass" : "TABLE_OR_VIEW_NOT_FOUND",
"sqlState" : "42P01",
"messageParameters" : {
- "relationName" : "`showdb`.`show_t*`"
- }
+ "relationName" : "`show_t*`"
+ },
+ "queryContext" : [ {
+ "objectType" : "",
+ "objectName" : "",
+ "startIndex" : 26,
+ "stopIndex" : 34,
+ "fragment" : "'show_t*'"
+ } ]
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesParserSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesParserSuite.scala
index d68e1233f7a..d70853b6360 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesParserSuite.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesParserSuite.scala
@@ -17,9 +17,9 @@
package org.apache.spark.sql.execution.command
-import org.apache.spark.sql.catalyst.analysis.{AnalysisTest,
UnresolvedNamespace, UnresolvedPartitionSpec}
+import org.apache.spark.sql.catalyst.analysis.{AnalysisTest,
UnresolvedNamespace, UnresolvedPartitionSpec, UnresolvedTable}
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser.parsePlan
-import org.apache.spark.sql.catalyst.plans.logical.{ShowTableExtended,
ShowTables}
+import org.apache.spark.sql.catalyst.plans.logical.{ShowTablePartition,
ShowTables, ShowTablesExtended}
import org.apache.spark.sql.test.SharedSparkSession
class ShowTablesParserSuite extends AnalysisTest with SharedSparkSession {
@@ -52,32 +52,32 @@ class ShowTablesParserSuite extends AnalysisTest with
SharedSparkSession {
test("show table extended") {
comparePlans(
parsePlan("SHOW TABLE EXTENDED LIKE '*test*'"),
- ShowTableExtended(UnresolvedNamespace(Seq.empty[String]), "*test*",
None))
+ ShowTablesExtended(UnresolvedNamespace(Seq.empty[String]), "*test*"))
comparePlans(
parsePlan(s"SHOW TABLE EXTENDED FROM $catalog.ns1.ns2 LIKE '*test*'"),
- ShowTableExtended(UnresolvedNamespace(Seq(catalog, "ns1", "ns2")),
"*test*", None))
+ ShowTablesExtended(UnresolvedNamespace(Seq(catalog, "ns1", "ns2")),
"*test*"))
comparePlans(
parsePlan(s"SHOW TABLE EXTENDED IN $catalog.ns1.ns2 LIKE '*test*'"),
- ShowTableExtended(UnresolvedNamespace(Seq(catalog, "ns1", "ns2")),
"*test*", None))
+ ShowTablesExtended(UnresolvedNamespace(Seq(catalog, "ns1", "ns2")),
"*test*"))
+
comparePlans(
parsePlan("SHOW TABLE EXTENDED LIKE '*test*' PARTITION(ds='2008-04-09',
hr=11)"),
- ShowTableExtended(
- UnresolvedNamespace(Seq.empty[String]),
- "*test*",
- Some(UnresolvedPartitionSpec(Map("ds" -> "2008-04-09", "hr" ->
"11")))))
+ ShowTablePartition(
+ UnresolvedTable(Seq("*test*"), "SHOW TABLE EXTENDED ... PARTITION
..."),
+ UnresolvedPartitionSpec(Map("ds" -> "2008-04-09", "hr" -> "11"))))
comparePlans(
parsePlan(s"SHOW TABLE EXTENDED FROM $catalog.ns1.ns2 LIKE '*test*' " +
"PARTITION(ds='2008-04-09')"),
- ShowTableExtended(
- UnresolvedNamespace(Seq(catalog, "ns1", "ns2")),
- "*test*",
- Some(UnresolvedPartitionSpec(Map("ds" -> "2008-04-09")))))
+ ShowTablePartition(
+ UnresolvedTable(Seq(catalog, "ns1", "ns2", "*test*"),
+ "SHOW TABLE EXTENDED ... PARTITION ..."),
+ UnresolvedPartitionSpec(Map("ds" -> "2008-04-09"))))
comparePlans(
parsePlan(s"SHOW TABLE EXTENDED IN $catalog.ns1.ns2 LIKE '*test*' " +
"PARTITION(ds='2008-04-09')"),
- ShowTableExtended(
- UnresolvedNamespace(Seq(catalog, "ns1", "ns2")),
- "*test*",
- Some(UnresolvedPartitionSpec(Map("ds" -> "2008-04-09")))))
+ ShowTablePartition(
+ UnresolvedTable(Seq(catalog, "ns1", "ns2", "*test*"),
+ "SHOW TABLE EXTENDED ... PARTITION ..."),
+ UnresolvedPartitionSpec(Map("ds" -> "2008-04-09"))))
}
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesSuiteBase.scala
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesSuiteBase.scala
index 5f56b91db8f..c88217221ab 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesSuiteBase.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowTablesSuiteBase.scala
@@ -17,7 +17,7 @@
package org.apache.spark.sql.execution.command
-import org.apache.spark.sql.{QueryTest, Row}
+import org.apache.spark.sql.{AnalysisException, QueryTest, Row}
import org.apache.spark.sql.connector.catalog.CatalogV2Implicits._
import org.apache.spark.sql.internal.SQLConf
@@ -40,6 +40,42 @@ trait ShowTablesSuiteBase extends QueryTest with
DDLCommandTestUtils {
checkAnswer(df, expected)
}
+ // the error class & error parameters of
+ // `SHOW TABLE EXTENDED ... PARTITION ... in non-partitioned table`
+ protected def extendedPartInNonPartedTableError(
+ catalog: String,
+ namespace: String,
+ table: String): (String, Map[String, String])
+
+ protected def extendedPartExpectedResult: String =
+ "Partition Values: [id1=1, id2=2]"
+
+ protected def namespaceKey: String = "Database"
+
+ protected def extendedTableInfo: String
+
+ protected def extendedTableSchema: String =
+ s"""Schema: root
+ | |-- data: string (nullable = true)
+ | |-- id: long (nullable = true)""".stripMargin
+
+ private def extendedTableExpectedResult(
+ catalog: String,
+ namespace: String,
+ table: String,
+ dataColName: String,
+ partColName: String): String = {
+ s"""Catalog: $catalog
+ |$namespaceKey: $namespace
+ |Table: $table
+ |$extendedTableInfo
+ |Partition Provider: Catalog
+ |Partition Columns: [`$partColName`]
+ |Schema: root
+ | |-- $dataColName: string (nullable = true)
+ | |-- $partColName: long (nullable = true)""".stripMargin
+ }
+
test("show an existing table") {
withNamespaceAndTable("ns", "table") { t =>
sql(s"CREATE TABLE $t (name STRING, id INT) $defaultUsing")
@@ -126,4 +162,300 @@ trait ShowTablesSuiteBase extends QueryTest with
DDLCommandTestUtils {
}
}
}
+
+ test("show table in a not existing namespace") {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"SHOW TABLES IN $catalog.nonexist")
+ },
+ errorClass = "SCHEMA_NOT_FOUND",
+ parameters = Map("schemaName" -> "`nonexist`"))
+ }
+
+ test("show table extended in a not existing namespace") {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"SHOW TABLE EXTENDED IN $catalog.nonexist LIKE '*tbl*'")
+ },
+ errorClass = "SCHEMA_NOT_FOUND",
+ parameters = Map("schemaName" -> "`nonexist`"))
+ }
+
+ test("show table extended with no matching table") {
+ val namespace = "ns1"
+ val table = "nonexist"
+ withNamespaceAndTable(namespace, table, catalog) { _ =>
+ val result = sql(s"SHOW TABLE EXTENDED IN $catalog.$namespace LIKE
'*$table*'")
+ assert(result.schema.fieldNames ===
+ Seq("namespace", "tableName", "isTemporary", "information"))
+ assert(result.collect().isEmpty)
+ }
+ }
+
+ test("show table extended with a not existing partition") {
+ val namespace = "ns1"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { tbl =>
+ sql(s"CREATE TABLE $tbl (data string, id bigint) $defaultUsing
PARTITIONED BY (id)")
+ sql(s"ALTER TABLE $tbl ADD PARTITION (id = 1)")
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"SHOW TABLE EXTENDED IN $catalog.$namespace LIKE '$table'
PARTITION(id = 2)")
+ },
+ errorClass = "PARTITIONS_NOT_FOUND",
+ parameters = Map(
+ "partitionList" -> "PARTITION (`id` = 2)",
+ "tableName" -> "`ns1`.`tbl`"
+ )
+ )
+ }
+ }
+
+ test("show table extended in non-partitioned table") {
+ val namespace = "ns1"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { tbl =>
+ sql(s"CREATE TABLE $tbl (data string, id bigint) $defaultUsing")
+ val e = intercept[AnalysisException] {
+ sql(s"SHOW TABLE EXTENDED IN $catalog.$namespace LIKE '$table'
PARTITION(id = 1)")
+ }
+ val (errorClass, parameters) =
extendedPartInNonPartedTableError(catalog, namespace, table)
+ checkError(exception = e, errorClass = errorClass, parameters =
parameters)
+ }
+ }
+
+ test("show table extended in multi partition key - " +
+ "the command's partition parameters are complete") {
+ val namespace = "ns1"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { tbl =>
+ sql(s"CREATE TABLE $tbl (data string, id1 bigint, id2 bigint) " +
+ s"$defaultUsing PARTITIONED BY (id1, id2)")
+ sql(s"ALTER TABLE $tbl ADD PARTITION (id1 = 1, id2 = 2)")
+
+ val result = sql(s"SHOW TABLE EXTENDED FROM $catalog.$namespace " +
+ s"LIKE '$table' PARTITION(id1 = 1, id2 = 2)")
+ assert(result.schema.fieldNames ===
+ Seq("namespace", "tableName", "isTemporary", "information"))
+ val resultCollect = result.collect()
+ assert(resultCollect(0).length == 4)
+ assert(resultCollect(0)(0) === namespace)
+ assert(resultCollect(0)(1) === table)
+ assert(resultCollect(0)(2) === false)
+ val actualResult = replace(resultCollect(0)(3).toString)
+ assert(actualResult === extendedPartExpectedResult)
+ }
+ }
+
+ test("show table extended in multi partition key - " +
+ "the command's partition parameters are incomplete") {
+ val namespace = "ns1"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { tbl =>
+ sql(s"CREATE TABLE $tbl (data string, id1 bigint, id2 bigint) " +
+ s"$defaultUsing PARTITIONED BY (id1, id2)")
+ sql(s"ALTER TABLE $tbl ADD PARTITION (id1 = 1, id2 = 2)")
+
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"SHOW TABLE EXTENDED IN $catalog.$namespace " +
+ s"LIKE '$table' PARTITION(id1 = 1)")
+ },
+ errorClass = "_LEGACY_ERROR_TEMP_1232",
+ parameters = Map(
+ "specKeys" -> "id1",
+ "partitionColumnNames" -> "id1, id2",
+ "tableName" -> s"`$catalog`.`$namespace`.`$table`")
+ )
+ }
+ }
+
+ test("show table extended in multi tables") {
+ val namespace = "ns1"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { _ =>
+ sql(s"CREATE TABLE $catalog.$namespace.$table (data string, id bigint) "
+
+ s"$defaultUsing PARTITIONED BY (id)")
+ val table1 = "tbl1"
+ val table2 = "tbl2"
+ withTable(table1, table2) {
+ sql(s"CREATE TABLE $catalog.$namespace.$table1 (data1 string, id1
bigint) " +
+ s"$defaultUsing PARTITIONED BY (id1)")
+ sql(s"CREATE TABLE $catalog.$namespace.$table2 (data2 string, id2
bigint) " +
+ s"$defaultUsing PARTITIONED BY (id2)")
+
+ val result = sql(s"SHOW TABLE EXTENDED FROM $catalog.$namespace LIKE
'$table*'")
+ .sort("tableName")
+ assert(result.schema.fieldNames ===
+ Seq("namespace", "tableName", "isTemporary", "information"))
+ val resultCollect = result.collect()
+ assert(resultCollect.length == 3)
+
+ assert(resultCollect(0).length == 4)
+ assert(resultCollect(0)(1) === table)
+ assert(resultCollect(0)(2) === false)
+ // replace "Created Time", "Last Access", "Created By", "Location"
+ val actualResult_0_3 = replace(resultCollect(0)(3).toString)
+ val expectedResult_0_3 = extendedTableExpectedResult(
+ catalog, namespace, table, "data", "id")
+ assert(actualResult_0_3 === expectedResult_0_3)
+
+ assert(resultCollect(1).length == 4)
+ assert(resultCollect(1)(1) === table1)
+ assert(resultCollect(1)(2) === false)
+ val actualResult_1_3 = replace(resultCollect(1)(3).toString)
+ // replace "Table Properties"
+ val expectedResult_1_3 = extendedTableExpectedResult(
+ catalog, namespace, table1, "data1", "id1")
+ assert(actualResult_1_3 === expectedResult_1_3)
+
+ assert(resultCollect(2).length == 4)
+ assert(resultCollect(2)(1) === table2)
+ assert(resultCollect(2)(2) === false)
+ val actualResult_2_3 = replace(resultCollect(2)(3).toString)
+ // replace "Table Properties"
+ val expectedResult_2_3 = extendedTableExpectedResult(
+ catalog, namespace, table2, "data2", "id2")
+ assert(actualResult_2_3 === expectedResult_2_3)
+ }
+ }
+ }
+
+ test("show table extended with temp views") {
+ val namespace = "ns"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { t =>
+ sql(s"CREATE TABLE $t (id int) $defaultUsing")
+ val viewName = table + "_view"
+ val localTmpViewName = viewName + "_local_tmp"
+ val globalTmpViewName = viewName + "_global_tmp"
+ val globalNamespace = "global_temp"
+ withView(localTmpViewName, globalNamespace + "." + globalTmpViewName) {
+ sql(s"CREATE TEMPORARY VIEW $localTmpViewName AS SELECT id FROM $t")
+ sql(s"CREATE GLOBAL TEMPORARY VIEW $globalTmpViewName AS SELECT id
FROM $t")
+
+ // temp local view
+ val localResult = sql(s"SHOW TABLE EXTENDED LIKE
'$viewName*'").sort("tableName")
+ assert(localResult.schema.fieldNames ===
+ Seq("namespace", "tableName", "isTemporary", "information"))
+ val localResultCollect = localResult.collect()
+ assert(localResultCollect.length == 1)
+ assert(localResultCollect(0).length == 4)
+ assert(localResultCollect(0)(1) === localTmpViewName)
+ assert(localResultCollect(0)(2) === true)
+ val actualLocalResult = replace(localResultCollect(0)(3).toString)
+ val expectedLocalResult =
+ s"""Table: $localTmpViewName
+ |Created Time: <created time>
+ |Last Access: <last access>
+ |Created By: <created by>
+ |Type: VIEW
+ |View Text: SELECT id FROM $catalog.$namespace.$table
+ |View Catalog and Namespace: spark_catalog.default
+ |View Query Output Columns: [id]
+ |Schema: root
+ | |-- id: integer (nullable = true)""".stripMargin
+ assert(actualLocalResult === expectedLocalResult)
+
+ // temp global view
+ val globalResult = sql(s"SHOW TABLE EXTENDED IN global_temp LIKE
'$viewName*'").
+ sort("tableName")
+ assert(globalResult.schema.fieldNames ===
+ Seq("namespace", "tableName", "isTemporary", "information"))
+ val globalResultCollect = globalResult.collect()
+ assert(globalResultCollect.length == 2)
+
+ assert(globalResultCollect(0).length == 4)
+ assert(globalResultCollect(0)(1) === globalTmpViewName)
+ assert(globalResultCollect(0)(2) === true)
+ val actualGlobalResult1 = replace(globalResultCollect(0)(3).toString)
+ val expectedGlobalResult1 =
+ s"""Database: $globalNamespace
+ |Table: $globalTmpViewName
+ |Created Time: <created time>
+ |Last Access: <last access>
+ |Created By: <created by>
+ |Type: VIEW
+ |View Text: SELECT id FROM $catalog.$namespace.$table
+ |View Catalog and Namespace: spark_catalog.default
+ |View Query Output Columns: [id]
+ |Schema: root
+ | |-- id: integer (nullable = true)""".stripMargin
+ assert(actualGlobalResult1 === expectedGlobalResult1)
+
+ assert(globalResultCollect(1).length == 4)
+ assert(globalResultCollect(1)(1) === localTmpViewName)
+ assert(globalResultCollect(1)(2) === true)
+ val actualLocalResult2 = replace(globalResultCollect(1)(3).toString)
+ val expectedLocalResult2 =
+ s"""Table: $localTmpViewName
+ |Created Time: <created time>
+ |Last Access: <last access>
+ |Created By: <created by>
+ |Type: VIEW
+ |View Text: SELECT id FROM $catalog.$namespace.$table
+ |View Catalog and Namespace: spark_catalog.default
+ |View Query Output Columns: [id]
+ |Schema: root
+ | |-- id: integer (nullable = true)""".stripMargin
+ assert(actualLocalResult2 === expectedLocalResult2)
+ }
+ }
+ }
+
+ // Replace some non-deterministic values with deterministic value
+ // for easy comparison of results, such as `Created Time`, etc
+ protected def replace(text: String): String = {
+ text.split("\n").map {
+ case s"Created Time:$_" => "Created Time: <created time>"
+ case s"Last Access:$_" => "Last Access: <last access>"
+ case s"Created By:$_" => "Created By: <created by>"
+ case s"Location:$_" => "Location: <location>"
+ case s"Table Properties:$_" => "Table Properties: <table properties>"
+ case s"Partition Parameters:$_" => "Partition Parameters: <partition
parameters>"
+ case other => other
+ }.mkString("\n")
+ }
+
+ /**
+ * - V1: `show extended` and `select *` display the schema,
+ * the partition columns will be always displayed at the end.
+ * - V2: `show extended` and `select *` display the schema,
+ * the columns order will respect the original table schema.
+ */
+ protected def selectCommandSchema: Array[String] = Array("data", "id")
+ test("show table extended: the display order of the columns is different in
v1 and v2") {
+ val namespace = "ns1"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { _ =>
+ sql(s"CREATE TABLE $catalog.$namespace.$table (data string, id bigint) "
+
+ s"$defaultUsing PARTITIONED BY (id)")
+ sql(s"INSERT INTO $catalog.$namespace.$table PARTITION (id = 1) (data)
VALUES ('data1')")
+ val result = sql(s"SELECT * FROM $catalog.$namespace.$table")
+ assert(result.schema.fieldNames === Array("data", "id"))
+
+ val table1 = "tbl1"
+ withTable(table1) {
+ sql(s"CREATE TABLE $catalog.$namespace.$table1 (id bigint, data
string) " +
+ s"$defaultUsing PARTITIONED BY (id)")
+ sql(s"INSERT INTO $catalog.$namespace.$table1 PARTITION (id = 1)
(data) VALUES ('data2')")
+
+ val result1 = sql(s"SELECT * FROM $catalog.$namespace.$table1")
+ assert(result1.schema.fieldNames === selectCommandSchema)
+
+ val extendedResult = sql(s"SHOW TABLE EXTENDED IN $catalog.$namespace
LIKE '$table*'").
+ sort("tableName")
+ val extendedResultCollect = extendedResult.collect()
+
+ assert(extendedResultCollect(0)(1) === table)
+ assert(extendedResultCollect(0)(3).toString.contains(
+ s"""Schema: root
+ | |-- data: string (nullable = true)
+ | |-- id: long (nullable = true)""".stripMargin))
+
+ assert(extendedResultCollect(1)(1) === table1)
+
assert(extendedResultCollect(1)(3).toString.contains(extendedTableSchema))
+ }
+ }
+ }
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v1/ShowTablesSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v1/ShowTablesSuite.scala
index 5bda7d002dc..4b4742910bd 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v1/ShowTablesSuite.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v1/ShowTablesSuite.scala
@@ -18,7 +18,6 @@
package org.apache.spark.sql.execution.command.v1
import org.apache.spark.sql.{AnalysisException, Row, SaveMode}
-import org.apache.spark.sql.catalyst.analysis.NoSuchDatabaseException
import org.apache.spark.sql.execution.command
import org.apache.spark.sql.internal.SQLConf
@@ -134,16 +133,6 @@ trait ShowTablesSuiteBase extends
command.ShowTablesSuiteBase with command.Tests
}
}
}
-
- test("show table in a not existing namespace") {
- val e = intercept[NoSuchDatabaseException] {
- runShowTablesSql(s"SHOW TABLES IN $catalog.unknown", Seq())
- }
- checkError(e,
- errorClass = "SCHEMA_NOT_FOUND",
- parameters = Map("schemaName" -> "`unknown`"))
- }
-
}
/**
@@ -165,4 +154,63 @@ class ShowTablesSuite extends ShowTablesSuiteBase with
CommandSuiteBase {
}
}
}
+
+ override protected def extendedPartInNonPartedTableError(
+ catalog: String,
+ namespace: String,
+ table: String): (String, Map[String, String]) = {
+ ("_LEGACY_ERROR_TEMP_1251",
+ Map("action" -> "SHOW TABLE EXTENDED", "tableName" -> table))
+ }
+
+ protected override def extendedPartExpectedResult: String =
+ super.extendedPartExpectedResult +
+ """
+ |Location: <location>
+ |Created Time: <created time>
+ |Last Access: <last access>""".stripMargin
+
+ protected override def extendedTableInfo: String =
+ """Created Time: <created time>
+ |Last Access: <last access>
+ |Created By: <created by>
+ |Type: MANAGED
+ |Provider: parquet
+ |Location: <location>""".stripMargin
+
+ test("show table extended in permanent view") {
+ val namespace = "ns"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { t =>
+ sql(s"CREATE TABLE $t (id int) $defaultUsing")
+ val viewName = table + "_view"
+ withView(viewName) {
+ sql(s"CREATE VIEW $catalog.$namespace.$viewName AS SELECT id FROM $t")
+ val result = sql(s"SHOW TABLE EXTENDED in $namespace LIKE
'$viewName*'").sort("tableName")
+ assert(result.schema.fieldNames ===
+ Seq("namespace", "tableName", "isTemporary", "information"))
+ val resultCollect = result.collect()
+ assert(resultCollect.length == 1)
+ assert(resultCollect(0).length == 4)
+ assert(resultCollect(0)(1) === viewName)
+ assert(resultCollect(0)(2) === false)
+ val actualResult = replace(resultCollect(0)(3).toString)
+ val expectedResult =
+ s"""Catalog: $catalog
+ |Database: $namespace
+ |Table: $viewName
+ |Created Time: <created time>
+ |Last Access: <last access>
+ |Created By: <created by>
+ |Type: VIEW
+ |View Text: SELECT id FROM $catalog.$namespace.$table
+ |View Original Text: SELECT id FROM $catalog.$namespace.$table
+ |View Catalog and Namespace: $catalog.$namespace
+ |View Query Output Columns: [id]
+ |Schema: root
+ | |-- id: integer (nullable = true)""".stripMargin
+ assert(actualResult === expectedResult)
+ }
+ }
+ }
}
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/ShowTablesSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/ShowTablesSuite.scala
index 9a67eab055e..d66dca20d77 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/ShowTablesSuite.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/ShowTablesSuite.scala
@@ -17,9 +17,9 @@
package org.apache.spark.sql.execution.command.v2
-import org.apache.spark.sql.{AnalysisException, Row}
-import org.apache.spark.sql.catalyst.analysis.NoSuchNamespaceException
+import org.apache.spark.sql.Row
import org.apache.spark.sql.execution.command
+import org.apache.spark.util.Utils
/**
* The class contains tests for the `SHOW TABLES` command to check V2 table
catalogs.
@@ -49,57 +49,26 @@ class ShowTablesSuite extends command.ShowTablesSuiteBase
with CommandSuiteBase
}
}
- // The test fails for V1 catalog with the error:
- // org.apache.spark.sql.AnalysisException:
- // The namespace in session catalog must have exactly one name part:
spark_catalog.ns1.ns2.tbl
- test("SHOW TABLE EXTENDED not valid v1 database") {
- def testV1CommandNamespace(sqlCommand: String, namespace: String): Unit = {
- val e = intercept[AnalysisException] {
- sql(sqlCommand)
- }
- assert(e.message.contains(s"SHOW TABLE EXTENDED is not supported for v2
tables"))
- }
+ override protected def extendedPartInNonPartedTableError(
+ catalog: String,
+ namespace: String,
+ table: String): (String, Map[String, String]) = {
+ ("_LEGACY_ERROR_TEMP_1231",
+ Map("key" -> "id", "tblName" -> s"`$catalog`.`$namespace`.`$table`"))
+ }
- val namespace = s"$catalog.ns1.ns2"
- val table = "tbl"
- withTable(s"$namespace.$table") {
- sql(s"CREATE TABLE $namespace.$table (id bigint, data string) " +
- s"$defaultUsing PARTITIONED BY (id)")
+ protected override def namespaceKey: String = "Namespace"
- testV1CommandNamespace(s"SHOW TABLE EXTENDED FROM $namespace LIKE 'tb*'",
- namespace)
- testV1CommandNamespace(s"SHOW TABLE EXTENDED IN $namespace LIKE 'tb*'",
- namespace)
- testV1CommandNamespace("SHOW TABLE EXTENDED " +
- s"FROM $namespace LIKE 'tb*' PARTITION(id=1)",
- namespace)
- testV1CommandNamespace("SHOW TABLE EXTENDED " +
- s"IN $namespace LIKE 'tb*' PARTITION(id=1)",
- namespace)
- }
- }
+ protected override def extendedTableInfo: String =
+ s"""Type: MANAGED
+ |Provider: _
+ |Owner: ${Utils.getCurrentUserName()}
+ |Table Properties: <table properties>""".stripMargin
- // TODO(SPARK-33393): Support SHOW TABLE EXTENDED in DSv2
- test("SHOW TABLE EXTENDED: an existing table") {
- val table = "people"
- withTable(s"$catalog.$table") {
- sql(s"CREATE TABLE $catalog.$table (name STRING, id INT) $defaultUsing")
- checkError(
- exception = intercept[AnalysisException] {
- sql(s"SHOW TABLE EXTENDED FROM $catalog LIKE '*$table*'").collect()
- },
- errorClass = "_LEGACY_ERROR_TEMP_1200",
- parameters = Map("name" -> "SHOW TABLE EXTENDED")
- )
- }
- }
+ protected override def extendedTableSchema: String =
+ s"""Schema: root
+ | |-- id: long (nullable = true)
+ | |-- data: string (nullable = true)""".stripMargin
- test("show table in a not existing namespace") {
- val e = intercept[NoSuchNamespaceException] {
- runShowTablesSql(s"SHOW TABLES IN $catalog.unknown", Seq())
- }
- checkError(e,
- errorClass = "SCHEMA_NOT_FOUND",
- parameters = Map("schemaName" -> "`unknown`"))
- }
+ protected override def selectCommandSchema: Array[String] = Array("id",
"data")
}
diff --git
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/command/ShowTablesSuite.scala
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/command/ShowTablesSuite.scala
index 653a157e762..79b1eb6c096 100644
---
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/command/ShowTablesSuite.scala
+++
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/command/ShowTablesSuite.scala
@@ -18,6 +18,7 @@
package org.apache.spark.sql.hive.execution.command
import org.apache.spark.sql.execution.command.v1
+import org.apache.spark.util.Utils
/**
* The class contains tests for the `SHOW TABLES` command to check V1 Hive
external table catalog.
@@ -33,4 +34,80 @@ class ShowTablesSuite extends v1.ShowTablesSuiteBase with
CommandSuiteBase {
}
}
}
+
+ override protected def extendedPartInNonPartedTableError(
+ catalog: String,
+ namespace: String,
+ table: String): (String, Map[String, String]) = {
+ ("_LEGACY_ERROR_TEMP_1231",
+ Map("key" -> "id", "tblName" -> s"`$catalog`.`$namespace`.`$table`"))
+ }
+
+ protected override def extendedPartExpectedResult: String =
+ super.extendedPartExpectedResult +
+ """
+ |Location: <location>
+ |Serde Library: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
+ |InputFormat: org.apache.hadoop.mapred.TextInputFormat
+ |OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
+ |Storage Properties: [serialization.format=1]
+ |Partition Parameters: <partition parameters>
+ |Created Time: <created time>
+ |Last Access: <last access>""".stripMargin
+
+ protected override def extendedTableInfo: String =
+ s"""Owner: ${Utils.getCurrentUserName()}
+ |Created Time: <created time>
+ |Last Access: <last access>
+ |Created By: <created by>
+ |Type: MANAGED
+ |Provider: hive
+ |Table Properties: <table properties>
+ |Location: <location>
+ |Serde Library: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
+ |InputFormat: org.apache.hadoop.mapred.TextInputFormat
+ |OutputFormat:
org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
+ |Storage Properties: [serialization.format=1]""".stripMargin
+
+ test("show table extended in permanent view") {
+ val namespace = "ns"
+ val table = "tbl"
+ withNamespaceAndTable(namespace, table, catalog) { t =>
+ sql(s"CREATE TABLE $t (id int) $defaultUsing")
+ val viewName = table + "_view"
+ withView(viewName) {
+ sql(s"CREATE VIEW $catalog.$namespace.$viewName AS SELECT id FROM $t")
+ val result = sql(s"SHOW TABLE EXTENDED in $namespace LIKE
'$viewName*'").sort("tableName")
+ assert(result.schema.fieldNames ===
+ Seq("namespace", "tableName", "isTemporary", "information"))
+ val resultCollect = result.collect()
+ assert(resultCollect.length == 1)
+ assert(resultCollect(0).length == 4)
+ assert(resultCollect(0)(1) === viewName)
+ assert(resultCollect(0)(2) === false)
+ val actualResult = replace(resultCollect(0)(3).toString)
+ val expectedResult =
+ s"""Catalog: $catalog
+ |Database: $namespace
+ |Table: $viewName
+ |Owner: ${Utils.getCurrentUserName()}
+ |Created Time: <created time>
+ |Last Access: <last access>
+ |Created By: <created by>
+ |Type: VIEW
+ |View Text: SELECT id FROM $catalog.$namespace.$table
+ |View Original Text: SELECT id FROM $catalog.$namespace.$table
+ |View Catalog and Namespace: $catalog.$namespace
+ |View Query Output Columns: [id]
+ |Table Properties: <table properties>
+ |Serde Library: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
+ |InputFormat: org.apache.hadoop.mapred.SequenceFileInputFormat
+ |OutputFormat:
org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat
+ |Storage Properties: [serialization.format=1]
+ |Schema: root
+ | |-- id: integer (nullable = true)""".stripMargin
+ assert(actualResult === expectedResult)
+ }
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]