Skip to content

Commit 883ae33

Browse files
cloud-fangatorsmile
authored andcommitted
[SPARK-30497][SQL] migrate DESCRIBE TABLE to the new framework
### What changes were proposed in this pull request? Use the new framework to resolve the DESCRIBE TABLE command. The v1 DESCRIBE TABLE command supports both table and view. Checked with Hive and Presto, they don't have DESCRIBE TABLE syntax but only DESCRIBE, which supports both table and view: 1. https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-DescribeTable/View/MaterializedView/Column 2. https://prestodb.io/docs/current/sql/describe.html We should make it clear that DESCRIBE support both table and view, by renaming the command to `DescribeRelation`. This PR also tunes the framework a little bit to support the case that a command accepts both table and view. ### Why are the changes needed? This is a part of effort to make the relation lookup behavior consistent: SPARK-29900. Note that I make a separate PR here instead of #26921, as I need to update the framework to support a new use case: accept both table and view. ### Does this PR introduce any user-facing change? no ### How was this patch tested? existing tests Closes #27187 from cloud-fan/describe. Authored-by: Wenchen Fan <wenchen@databricks.com> Signed-off-by: Xiao Li <gatorsmile@gmail.com>
1 parent 8a926e4 commit 883ae33

File tree

21 files changed

+186
-181
lines changed

21 files changed

+186
-181
lines changed

sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ statement
218218
| (DESC | DESCRIBE) namespace EXTENDED?
219219
multipartIdentifier #describeNamespace
220220
| (DESC | DESCRIBE) TABLE? option=(EXTENDED | FORMATTED)?
221-
multipartIdentifier partitionSpec? describeColName? #describeTable
221+
multipartIdentifier partitionSpec? describeColName? #describeRelation
222222
| (DESC | DESCRIBE) QUERY? query #describeQuery
223223
| COMMENT ON namespace multipartIdentifier IS
224224
comment=(STRING | NULL) #commentNamespace

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,8 @@ class Analyzer(
759759
u.failAnalysis(s"${ident.quoted} is a temp view not table.")
760760
}
761761
u
762+
case u @ UnresolvedTableOrView(ident) =>
763+
lookupTempView(ident).map(_ => ResolvedView(ident.asIdentifier)).getOrElse(u)
762764
}
763765

764766
def lookupTempView(identifier: Seq[String]): Option[LogicalPlan] = {
@@ -803,16 +805,16 @@ class Analyzer(
803805
.map(ResolvedTable(catalog.asTableCatalog, ident, _))
804806
.getOrElse(u)
805807

808+
case u @ UnresolvedTableOrView(NonSessionCatalogAndIdentifier(catalog, ident)) =>
809+
CatalogV2Util.loadTable(catalog, ident)
810+
.map(ResolvedTable(catalog.asTableCatalog, ident, _))
811+
.getOrElse(u)
812+
806813
case i @ InsertIntoStatement(u: UnresolvedRelation, _, _, _, _) if i.query.resolved =>
807814
lookupV2Relation(u.multipartIdentifier)
808815
.map(v2Relation => i.copy(table = v2Relation))
809816
.getOrElse(i)
810817

811-
case desc @ DescribeTable(u: UnresolvedV2Relation, _) =>
812-
CatalogV2Util.loadRelation(u.catalog, u.tableName)
813-
.map(rel => desc.copy(table = rel))
814-
.getOrElse(desc)
815-
816818
case alter @ AlterTable(_, _, u: UnresolvedV2Relation, _) =>
817819
CatalogV2Util.loadRelation(u.catalog, u.tableName)
818820
.map(rel => alter.copy(table = rel))
@@ -889,17 +891,28 @@ class Analyzer(
889891
case u: UnresolvedRelation =>
890892
lookupRelation(u.multipartIdentifier).map(resolveViews).getOrElse(u)
891893

892-
case u @ UnresolvedTable(identifier: Seq[String]) =>
893-
expandRelationName(identifier) match {
894-
case SessionCatalogAndIdentifier(catalog, ident) =>
895-
CatalogV2Util.loadTable(catalog, ident) match {
896-
case Some(v1Table: V1Table) if v1Table.v1Table.tableType == CatalogTableType.VIEW =>
897-
u.failAnalysis(s"$ident is a view not table.")
898-
case Some(table) => ResolvedTable(catalog.asTableCatalog, ident, table)
899-
case None => u
900-
}
901-
case _ => u
902-
}
894+
case u @ UnresolvedTable(identifier) =>
895+
lookupTableOrView(identifier).map {
896+
case v: ResolvedView =>
897+
u.failAnalysis(s"${v.identifier.quoted} is a view not table.")
898+
case table => table
899+
}.getOrElse(u)
900+
901+
case u @ UnresolvedTableOrView(identifier) =>
902+
lookupTableOrView(identifier).getOrElse(u)
903+
}
904+
905+
private def lookupTableOrView(identifier: Seq[String]): Option[LogicalPlan] = {
906+
expandRelationName(identifier) match {
907+
case SessionCatalogAndIdentifier(catalog, ident) =>
908+
CatalogV2Util.loadTable(catalog, ident).map {
909+
case v1Table: V1Table if v1Table.v1Table.tableType == CatalogTableType.VIEW =>
910+
ResolvedView(ident)
911+
case table =>
912+
ResolvedTable(catalog.asTableCatalog, ident, table)
913+
}
914+
case _ => None
915+
}
903916
}
904917

905918
// Look up a relation from the session catalog with the following logic:

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ trait CheckAnalysis extends PredicateHelper {
9898
case u: UnresolvedTable =>
9999
u.failAnalysis(s"Table not found: ${u.multipartIdentifier.quoted}")
100100

101+
case u: UnresolvedTableOrView =>
102+
u.failAnalysis(s"Table or view not found: ${u.multipartIdentifier.quoted}")
103+
101104
case u: UnresolvedRelation =>
102105
u.failAnalysis(s"Table or view not found: ${u.multipartIdentifier.quoted}")
103106

@@ -118,13 +121,6 @@ trait CheckAnalysis extends PredicateHelper {
118121
case AlterTable(_, _, u: UnresolvedV2Relation, _) =>
119122
failAnalysis(s"Table not found: ${u.originalNameParts.quoted}")
120123

121-
case DescribeTable(u: UnresolvedV2Relation, _) if isView(u.originalNameParts) =>
122-
u.failAnalysis(
123-
s"Invalid command: '${u.originalNameParts.quoted}' is a view not a table.")
124-
125-
case DescribeTable(u: UnresolvedV2Relation, _) =>
126-
failAnalysis(s"Table not found: ${u.originalNameParts.quoted}")
127-
128124
case operator: LogicalPlan =>
129125
// Check argument data types of higher-order functions downwards first.
130126
// If the arguments of the higher-order functions are resolved but the type check fails,

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveCatalogs.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,6 @@ class ResolveCatalogs(val catalogManager: CatalogManager)
115115
}
116116
RenameTable(catalog.asTableCatalog, oldName.asIdentifier, newNameParts.asIdentifier)
117117

118-
case DescribeTableStatement(
119-
nameParts @ NonSessionCatalogAndTable(catalog, tbl), partitionSpec, isExtended) =>
120-
if (partitionSpec.nonEmpty) {
121-
throw new AnalysisException("DESCRIBE TABLE does not support partition for v2 tables.")
122-
}
123-
val r = UnresolvedV2Relation(nameParts, catalog.asTableCatalog, tbl.asIdentifier)
124-
DescribeTable(r, isExtended)
125-
126118
case DescribeColumnStatement(
127119
NonSessionCatalogAndTable(catalog, tbl), colNameParts, isExtended) =>
128120
throw new AnalysisException("Describing columns is not supported for v2 tables.")

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/namespace.scala

Lines changed: 0 additions & 33 deletions
This file was deleted.

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/table.scala

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.catalyst.analysis
19+
20+
import org.apache.spark.sql.catalyst.expressions.Attribute
21+
import org.apache.spark.sql.catalyst.plans.logical.{LeafNode, LogicalPlan}
22+
import org.apache.spark.sql.connector.catalog.{Identifier, SupportsNamespaces, Table, TableCatalog}
23+
24+
/**
25+
* Holds the name of a namespace that has yet to be looked up in a catalog. It will be resolved to
26+
* [[ResolvedNamespace]] during analysis.
27+
*/
28+
case class UnresolvedNamespace(multipartIdentifier: Seq[String]) extends LeafNode {
29+
override lazy val resolved: Boolean = false
30+
31+
override def output: Seq[Attribute] = Nil
32+
}
33+
34+
/**
35+
* Holds the name of a table that has yet to be looked up in a catalog. It will be resolved to
36+
* [[ResolvedTable]] during analysis.
37+
*/
38+
case class UnresolvedTable(multipartIdentifier: Seq[String]) extends LeafNode {
39+
override lazy val resolved: Boolean = false
40+
41+
override def output: Seq[Attribute] = Nil
42+
}
43+
44+
/**
45+
* Holds the name of a table or view that has yet to be looked up in a catalog. It will
46+
* be resolved to [[ResolvedTable]] or [[ResolvedView]] during analysis.
47+
*/
48+
case class UnresolvedTableOrView(multipartIdentifier: Seq[String]) extends LeafNode {
49+
override lazy val resolved: Boolean = false
50+
override def output: Seq[Attribute] = Nil
51+
}
52+
53+
/**
54+
* A plan containing resolved namespace.
55+
*/
56+
case class ResolvedNamespace(catalog: SupportsNamespaces, namespace: Seq[String])
57+
extends LeafNode {
58+
override def output: Seq[Attribute] = Nil
59+
}
60+
61+
/**
62+
* A plan containing resolved table.
63+
*/
64+
case class ResolvedTable(catalog: TableCatalog, identifier: Identifier, table: Table)
65+
extends LeafNode {
66+
override def output: Seq[Attribute] = Nil
67+
}
68+
69+
/**
70+
* A plan containing resolved (temp) views.
71+
*/
72+
// TODO: create a generic representation for temp view, v1 view and v2 view, after we add view
73+
// support to v2 catalog. For now we only need the identifier to fallback to v1 command.
74+
case class ResolvedView(identifier: Identifier) extends LeafNode {
75+
override def output: Seq[Attribute] = Nil
76+
}

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3062,9 +3062,9 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
30623062
}
30633063

30643064
/**
3065-
* Create a [[DescribeColumnStatement]] or [[DescribeTableStatement]] commands.
3065+
* Create a [[DescribeColumnStatement]] or [[DescribeRelation]] commands.
30663066
*/
3067-
override def visitDescribeTable(ctx: DescribeTableContext): LogicalPlan = withOrigin(ctx) {
3067+
override def visitDescribeRelation(ctx: DescribeRelationContext): LogicalPlan = withOrigin(ctx) {
30683068
val isExtended = ctx.EXTENDED != null || ctx.FORMATTED != null
30693069
if (ctx.describeColName != null) {
30703070
if (ctx.partitionSpec != null) {
@@ -3086,8 +3086,8 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
30863086
} else {
30873087
Map.empty[String, String]
30883088
}
3089-
DescribeTableStatement(
3090-
visitMultipartIdentifier(ctx.multipartIdentifier()),
3089+
DescribeRelation(
3090+
UnresolvedTableOrView(visitMultipartIdentifier(ctx.multipartIdentifier())),
30913091
partitionSpec,
30923092
isExtended)
30933093
}

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/statements.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,14 +292,6 @@ case class DropViewStatement(
292292
viewName: Seq[String],
293293
ifExists: Boolean) extends ParsedStatement
294294

295-
/**
296-
* A DESCRIBE TABLE tbl_name statement, as parsed from SQL.
297-
*/
298-
case class DescribeTableStatement(
299-
tableName: Seq[String],
300-
partitionSpec: TablePartitionSpec,
301-
isExtended: Boolean) extends ParsedStatement
302-
303295
/**
304296
* A DESCRIBE TABLE tbl_name col_name statement, as parsed from SQL.
305297
*/

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.spark.sql.catalyst.plans.logical
1919

2020
import org.apache.spark.sql.catalyst.analysis.{NamedRelation, UnresolvedException}
21+
import org.apache.spark.sql.catalyst.catalog.CatalogTypes.TablePartitionSpec
2122
import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeReference, Expression, Unevaluable}
2223
import org.apache.spark.sql.catalyst.plans.DescribeTableSchema
2324
import org.apache.spark.sql.connector.catalog._
@@ -314,12 +315,13 @@ case class ShowNamespaces(
314315
}
315316

316317
/**
317-
* The logical plan of the DESCRIBE TABLE command that works for v2 tables.
318+
* The logical plan of the DESCRIBE relation_name command that works for v2 tables.
318319
*/
319-
case class DescribeTable(table: NamedRelation, isExtended: Boolean) extends Command {
320-
321-
override lazy val resolved: Boolean = table.resolved
322-
320+
case class DescribeRelation(
321+
relation: LogicalPlan,
322+
partitionSpec: TablePartitionSpec,
323+
isExtended: Boolean) extends Command {
324+
override def children: Seq[LogicalPlan] = Seq(relation)
323325
override def output: Seq[Attribute] = DescribeTableSchema.describeTableAttributes()
324326
}
325327

sql/catalyst/src/main/scala/org/apache/spark/sql/connector/catalog/CatalogV2Implicits.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ private[sql] object CatalogV2Implicits {
9898
}
9999

100100
def asMultipartIdentifier: Seq[String] = ident.namespace :+ ident.name
101+
102+
def asTableIdentifier: TableIdentifier = ident.namespace match {
103+
case ns if ns.isEmpty => TableIdentifier(ident.name)
104+
case Array(dbName) => TableIdentifier(ident.name, Some(dbName))
105+
case _ =>
106+
throw new AnalysisException(
107+
s"$quoted is not a valid TableIdentifier as it has more than 2 name parts.")
108+
}
101109
}
102110

103111
implicit class MultipartIdentifierHelper(parts: Seq[String]) {

sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ package org.apache.spark.sql.catalyst.parser
2020
import java.util.Locale
2121

2222
import org.apache.spark.sql.AnalysisException
23-
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, GlobalTempView, LocalTempView, PersistedView, UnresolvedAttribute, UnresolvedNamespace, UnresolvedRelation, UnresolvedStar, UnresolvedTable}
23+
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, GlobalTempView, LocalTempView, PersistedView, UnresolvedAttribute, UnresolvedNamespace, UnresolvedRelation, UnresolvedStar, UnresolvedTable, UnresolvedTableOrView}
2424
import org.apache.spark.sql.catalyst.catalog.{ArchiveResource, BucketSpec, FileResource, FunctionResource, FunctionResourceType, JarResource}
2525
import org.apache.spark.sql.catalyst.expressions.{EqualTo, Literal}
2626
import org.apache.spark.sql.catalyst.plans.logical._
@@ -791,13 +791,13 @@ class DDLParserSuite extends AnalysisTest {
791791

792792
test("SPARK-17328 Fix NPE with EXPLAIN DESCRIBE TABLE") {
793793
comparePlans(parsePlan("describe t"),
794-
DescribeTableStatement(Seq("t"), Map.empty, isExtended = false))
794+
DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = false))
795795
comparePlans(parsePlan("describe table t"),
796-
DescribeTableStatement(Seq("t"), Map.empty, isExtended = false))
796+
DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = false))
797797
comparePlans(parsePlan("describe table extended t"),
798-
DescribeTableStatement(Seq("t"), Map.empty, isExtended = true))
798+
DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = true))
799799
comparePlans(parsePlan("describe table formatted t"),
800-
DescribeTableStatement(Seq("t"), Map.empty, isExtended = true))
800+
DescribeRelation(UnresolvedTableOrView(Seq("t")), Map.empty, isExtended = true))
801801
}
802802

803803
test("insert table: basic append") {

0 commit comments

Comments
 (0)