Skip to content

Commit 936d4f8

Browse files
szehon-hocloud-fan
authored andcommitted
[SPARK-51615][SQL] Refactor ShowNamespaces to use RunnableCommand
### What changes were proposed in this pull request? Refactor ShowNamespaces to use RunnableCommand. ### Why are the changes needed? RunnableCommand is the latest way to run commands. The advantage over the old way is that we have a single node (no need for a logicalPlan and physical exec node). ### Does this PR introduce _any_ user-facing change? No ### How was this patch tested? Existing unit tests ### Was this patch authored or co-authored using generative AI tooling? No Closes #50393 from szehon-ho/show-namespaces-refactor. Authored-by: Szehon Ho <[email protected]> Signed-off-by: Wenchen Fan <[email protected]>
1 parent 38a1958 commit 936d4f8

File tree

10 files changed

+119
-123
lines changed

10 files changed

+119
-123
lines changed

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.spark.sql.catalyst.analysis
1919

20-
import org.apache.spark.sql.catalyst.plans.logical.{DescribeNamespace, LogicalPlan, ShowNamespaces, ShowTableProperties, ShowTables}
20+
import org.apache.spark.sql.catalyst.plans.logical.{DescribeNamespace, LogicalPlan, ShowTableProperties, ShowTables}
2121
import org.apache.spark.sql.catalyst.rules.Rule
2222
import org.apache.spark.sql.catalyst.trees.TreePattern.COMMAND
2323
import org.apache.spark.sql.internal.SQLConf
@@ -36,9 +36,6 @@ object KeepLegacyOutputs extends Rule[LogicalPlan] {
3636
assert(s.output.length == 3)
3737
val newOutput = s.output.head.withName("database") +: s.output.tail
3838
s.copy(output = newOutput)
39-
case s: ShowNamespaces =>
40-
assert(s.output.length == 1)
41-
s.copy(output = Seq(s.output.head.withName("databaseName")))
4239
case d: DescribeNamespace =>
4340
assert(d.output.length == 2)
4441
d.copy(output = Seq(d.output.head.withName("database_description_item"),

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

-10
Original file line numberDiff line numberDiff line change
@@ -4408,16 +4408,6 @@ class AstBuilder extends DataTypeAstBuilder
44084408
}
44094409
}
44104410

4411-
/**
4412-
* Create a [[ShowNamespaces]] command.
4413-
*/
4414-
override def visitShowNamespaces(ctx: ShowNamespacesContext): LogicalPlan = withOrigin(ctx) {
4415-
val multiPart = Option(ctx.multipartIdentifier).map(visitMultipartIdentifier)
4416-
ShowNamespaces(
4417-
UnresolvedNamespace(multiPart.getOrElse(Seq.empty[String])),
4418-
Option(ctx.pattern).map(x => string(visitStringLit(x))))
4419-
}
4420-
44214411
/**
44224412
* Create a [[DescribeNamespace]].
44234413
*

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

-18
Original file line numberDiff line numberDiff line change
@@ -656,24 +656,6 @@ case class SetNamespaceLocation(
656656
copy(namespace = newChild)
657657
}
658658

659-
/**
660-
* The logical plan of the SHOW NAMESPACES command.
661-
*/
662-
case class ShowNamespaces(
663-
namespace: LogicalPlan,
664-
pattern: Option[String],
665-
override val output: Seq[Attribute] = ShowNamespaces.getOutputAttrs) extends UnaryCommand {
666-
override def child: LogicalPlan = namespace
667-
override protected def withNewChildInternal(newChild: LogicalPlan): ShowNamespaces =
668-
copy(namespace = newChild)
669-
}
670-
671-
object ShowNamespaces {
672-
def getOutputAttrs: Seq[Attribute] = {
673-
Seq(AttributeReference("namespace", StringType, nullable = false)())
674-
}
675-
}
676-
677659
/**
678660
* The logical plan of the DESCRIBE relation_name command.
679661
*/

sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala

+2
Original file line numberDiff line numberDiff line change
@@ -6687,6 +6687,8 @@ class SQLConf extends Serializable with Logging with SqlApiConf {
66876687

66886688
def legacyEvalCurrentTime: Boolean = getConf(SQLConf.LEGACY_EVAL_CURRENT_TIME)
66896689

6690+
def legacyOutputSchema: Boolean = getConf(SQLConf.LEGACY_KEEP_COMMAND_OUTPUT_SCHEMA)
6691+
66906692
/** ********************** SQLConf functionality methods ************ */
66916693

66926694
/** Set Spark SQL configuration properties. */

sql/core/src/main/scala/org/apache/spark/sql/classic/Catalog.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ import org.apache.spark.sql.catalyst.catalog._
3030
import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
3131
import org.apache.spark.sql.catalyst.expressions.{Expression, Literal}
3232
import org.apache.spark.sql.catalyst.parser.ParseException
33-
import org.apache.spark.sql.catalyst.plans.logical.{ColumnDefinition, CreateTable, LocalRelation, LogicalPlan, OptionList, RecoverPartitions, ShowFunctions, ShowNamespaces, ShowTables, UnresolvedTableSpec, View}
33+
import org.apache.spark.sql.catalyst.plans.logical.{ColumnDefinition, CreateTable, LocalRelation, LogicalPlan, OptionList, RecoverPartitions, ShowFunctions, ShowTables, UnresolvedTableSpec, View}
3434
import org.apache.spark.sql.catalyst.types.DataTypeUtils
3535
import org.apache.spark.sql.catalyst.util.CaseInsensitiveMap
3636
import org.apache.spark.sql.connector.catalog.{CatalogManager, SupportsNamespaces, TableCatalog}
3737
import org.apache.spark.sql.connector.catalog.CatalogV2Implicits.{CatalogHelper, MultipartIdentifierHelper, NamespaceHelper, TransformHelper}
3838
import org.apache.spark.sql.errors.QueryCompilationErrors
39-
import org.apache.spark.sql.execution.command.ShowTablesCommand
39+
import org.apache.spark.sql.execution.command.{ShowNamespacesCommand, ShowTablesCommand}
4040
import org.apache.spark.sql.execution.datasources.{DataSource, LogicalRelation}
4141
import org.apache.spark.sql.execution.datasources.v2.DataSourceV2Relation
4242
import org.apache.spark.sql.internal.connector.V1Function
@@ -105,10 +105,10 @@ class Catalog(sparkSession: SparkSession) extends catalog.Catalog {
105105
listDatabasesInternal(Some(pattern))
106106

107107
private def listDatabasesInternal(patternOpt: Option[String]): Dataset[Database] = {
108-
val plan = ShowNamespaces(UnresolvedNamespace(Nil), patternOpt)
108+
val plan = ShowNamespacesCommand(UnresolvedNamespace(Nil), patternOpt)
109109
val qe = sparkSession.sessionState.executePlan(plan)
110110
val catalog = qe.analyzed.collectFirst {
111-
case ShowNamespaces(r: ResolvedNamespace, _, _) => r.catalog
111+
case ShowNamespacesCommand(r: ResolvedNamespace, _, _) => r.catalog
112112
}.get
113113
val databases = qe.toRdd.collect().map { row =>
114114
// dbName can either be a quoted identifier (single or multi part) or an unquoted single part

sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala

+7
Original file line numberDiff line numberDiff line change
@@ -1188,4 +1188,11 @@ class SparkSqlAstBuilder extends AstBuilder {
11881188
}
11891189
ShowProceduresCommand(ns)
11901190
}
1191+
1192+
override def visitShowNamespaces(ctx: ShowNamespacesContext): LogicalPlan = withOrigin(ctx) {
1193+
val multiPart = Option(ctx.multipartIdentifier).map(visitMultipartIdentifier)
1194+
ShowNamespacesCommand(
1195+
UnresolvedNamespace(multiPart.getOrElse(Seq.empty[String])),
1196+
Option(ctx.pattern).map(x => string(visitStringLit(x))))
1197+
}
11911198
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.execution.command
19+
20+
import org.apache.spark.sql.{Row, SparkSession}
21+
import org.apache.spark.sql.catalyst.analysis.ResolvedNamespace
22+
import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeReference}
23+
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
24+
import org.apache.spark.sql.catalyst.util.StringUtils
25+
import org.apache.spark.sql.connector.catalog.CatalogV2Implicits.{CatalogHelper, NamespaceHelper}
26+
import org.apache.spark.sql.internal.SQLConf
27+
import org.apache.spark.sql.types.StringType
28+
29+
/**
30+
* The command for `SHOW NAMESPACES`.
31+
*/
32+
case class ShowNamespacesCommand(
33+
child: LogicalPlan,
34+
pattern: Option[String],
35+
override val output: Seq[Attribute] = ShowNamespacesCommand.output)
36+
extends UnaryRunnableCommand {
37+
38+
override def run(sparkSession: SparkSession): Seq[Row] = {
39+
val ResolvedNamespace(cat, ns, _) = child
40+
val nsCatalog = cat.asNamespaceCatalog
41+
val namespaces = if (ns.nonEmpty) {
42+
nsCatalog.listNamespaces(ns.toArray)
43+
} else {
44+
nsCatalog.listNamespaces()
45+
}
46+
47+
// The legacy SHOW DATABASES command does not quote the database names.
48+
assert(output.length == 1)
49+
val namespaceNames = if (output.head.name == "databaseName"
50+
&& namespaces.forall(_.length == 1)) {
51+
namespaces.map(_.head)
52+
} else {
53+
namespaces.map(_.quoted)
54+
}
55+
56+
namespaceNames
57+
.filter{ns => pattern.forall(StringUtils.filterPattern(Seq(ns), _).nonEmpty)}
58+
.map(Row(_))
59+
.toSeq
60+
}
61+
62+
override protected def withNewChildInternal(newChild: LogicalPlan): LogicalPlan = {
63+
copy(child = newChild)
64+
}
65+
}
66+
67+
object ShowNamespacesCommand {
68+
def output: Seq[AttributeReference] = {
69+
Seq(
70+
if (SQLConf.get.legacyOutputSchema) {
71+
AttributeReference("databaseName", StringType, nullable = false)()
72+
} else {
73+
AttributeReference("namespace", StringType, nullable = false)()
74+
}
75+
)
76+
}
77+
}

sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala

-3
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,6 @@ class DataSourceV2Strategy(session: SparkSession) extends Strategy with Predicat
411411
case DropNamespace(ResolvedNamespace(catalog, ns, _), ifExists, cascade) =>
412412
DropNamespaceExec(catalog, ns, ifExists, cascade) :: Nil
413413

414-
case ShowNamespaces(ResolvedNamespace(catalog, ns, _), pattern, output) =>
415-
ShowNamespacesExec(output, catalog.asNamespaceCatalog, ns, pattern) :: Nil
416-
417414
case ShowTables(ResolvedNamespace(catalog, ns, _), pattern, output) =>
418415
ShowTablesExec(output, catalog.asTableCatalog, ns, pattern) :: Nil
419416

sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ShowNamespacesExec.scala

-63
This file was deleted.

sql/core/src/test/scala/org/apache/spark/sql/execution/command/ShowNamespacesParserSuite.scala sql/core/src/test/scala/org/apache/spark/sql/execution/ShowNamespacesParserSuite.scala

+28-21
Original file line numberDiff line numberDiff line change
@@ -15,52 +15,59 @@
1515
* limitations under the License.
1616
*/
1717

18-
package org.apache.spark.sql.execution.command
18+
package org.apache.spark.sql.execution
1919

2020
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, UnresolvedNamespace}
21-
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser.parsePlan
22-
import org.apache.spark.sql.catalyst.plans.logical.ShowNamespaces
21+
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
22+
import org.apache.spark.sql.execution.command.ShowNamespacesCommand
2323
import org.apache.spark.sql.test.SharedSparkSession
2424

2525
class ShowNamespacesParserSuite extends AnalysisTest with SharedSparkSession {
26+
27+
private lazy val parser = new SparkSqlParser()
28+
2629
private val keywords = Seq("NAMESPACES", "DATABASES", "SCHEMAS")
2730

31+
private def assertEqual(sqlCommand: String, plan: LogicalPlan): Unit = {
32+
comparePlans(parser.parsePlan(sqlCommand), plan)
33+
}
34+
2835
test("show namespaces in the current catalog") {
2936
keywords.foreach { keyword =>
30-
comparePlans(
31-
parsePlan(s"SHOW $keyword"),
32-
ShowNamespaces(UnresolvedNamespace(Seq.empty[String]), None))
37+
assertEqual(
38+
s"SHOW $keyword",
39+
ShowNamespacesCommand(UnresolvedNamespace(Seq.empty[String]), None))
3340
}
3441
}
3542

3643
test("show namespaces with a pattern") {
3744
keywords.foreach { keyword =>
38-
comparePlans(
39-
parsePlan(s"SHOW $keyword LIKE 'defau*'"),
40-
ShowNamespaces(UnresolvedNamespace(Seq.empty[String]), Some("defau*")))
45+
assertEqual(
46+
s"SHOW $keyword LIKE 'defau*'",
47+
ShowNamespacesCommand(UnresolvedNamespace(Seq.empty[String]), Some("defau*")))
4148
// LIKE can be omitted.
42-
comparePlans(
43-
parsePlan(s"SHOW $keyword 'defau*'"),
44-
ShowNamespaces(UnresolvedNamespace(Seq.empty[String]), Some("defau*")))
49+
assertEqual(
50+
s"SHOW $keyword 'defau*'",
51+
ShowNamespacesCommand(UnresolvedNamespace(Seq.empty[String]), Some("defau*")))
4552
}
4653
}
4754

4855
test("show namespaces in/from a namespace") {
4956
keywords.foreach { keyword =>
50-
comparePlans(
51-
parsePlan(s"SHOW $keyword FROM testcat.ns1.ns2"),
52-
ShowNamespaces(UnresolvedNamespace(Seq("testcat", "ns1", "ns2")), None))
53-
comparePlans(
54-
parsePlan(s"SHOW $keyword IN testcat.ns1.ns2"),
55-
ShowNamespaces(UnresolvedNamespace(Seq("testcat", "ns1", "ns2")), None))
57+
assertEqual(
58+
s"SHOW $keyword FROM testcat.ns1.ns2",
59+
ShowNamespacesCommand(UnresolvedNamespace(Seq("testcat", "ns1", "ns2")), None))
60+
assertEqual(
61+
s"SHOW $keyword IN testcat.ns1.ns2",
62+
ShowNamespacesCommand(UnresolvedNamespace(Seq("testcat", "ns1", "ns2")), None))
5663
}
5764
}
5865

5966
test("namespaces by a pattern from another namespace") {
6067
keywords.foreach { keyword =>
61-
comparePlans(
62-
parsePlan(s"SHOW $keyword IN testcat.ns1 LIKE '*pattern*'"),
63-
ShowNamespaces(UnresolvedNamespace(Seq("testcat", "ns1")), Some("*pattern*")))
68+
assertEqual(
69+
s"SHOW $keyword IN testcat.ns1 LIKE '*pattern*'",
70+
ShowNamespacesCommand(UnresolvedNamespace(Seq("testcat", "ns1")), Some("*pattern*")))
6471
}
6572
}
6673
}

0 commit comments

Comments
 (0)