diff --git a/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 b/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 index a9e3ca654986..b2b611e417c1 100644 --- a/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 +++ b/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4 @@ -200,6 +200,7 @@ statement | SHOW identifier? FUNCTIONS (LIKE? (qualifiedName | pattern=STRING))? #showFunctions | SHOW CREATE TABLE multipartIdentifier #showCreateTable + | SHOW CURRENT NAMESPACE #showCurrentNamespace | (DESC | DESCRIBE) FUNCTION EXTENDED? describeFuncName #describeFunction | (DESC | DESCRIBE) database EXTENDED? db=errorCapturingIdentifier #describeDatabase | (DESC | DESCRIBE) TABLE? option=(EXTENDED | FORMATTED)? 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 ddd60fa5ec11..bca07262a6f7 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 @@ -204,6 +204,9 @@ class ResolveCatalogs(val catalogManager: CatalogManager) val CatalogAndNamespace(catalog, namespace) = nameParts SetCatalogAndNamespace(catalogManager, Some(catalog.name()), namespace) } + + case ShowCurrentNamespaceStatement() => + ShowCurrentNamespace(catalogManager) } object NonSessionCatalog { 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 3de13abf5456..0ad4fc8d63af 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 @@ -2592,6 +2592,14 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging UseStatement(ctx.NAMESPACE != null, nameParts) } + /** + * Create a [[ShowCurrentNamespaceStatement]]. + */ + override def visitShowCurrentNamespace( + ctx: ShowCurrentNamespaceContext) : LogicalPlan = withOrigin(ctx) { + ShowCurrentNamespaceStatement() + } + /** * Create a [[ShowTablesStatement]] command. */ diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/statements.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/statements.scala index dd3f8f8ce6c4..e67d50437256 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/statements.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/statements.scala @@ -419,3 +419,8 @@ case class RefreshTableStatement(tableName: Seq[String]) extends ParsedStatement case class ShowColumnsStatement( table: Seq[String], namespace: Option[Seq[String]]) extends ParsedStatement + +/** + * A SHOW CURRENT NAMESPACE statement, as parsed from SQL + */ +case class ShowCurrentNamespaceStatement() extends ParsedStatement 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 3c625e9acb5a..a25224a6c299 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 @@ -362,3 +362,12 @@ case class SetCatalogAndNamespace( case class RefreshTable( catalog: TableCatalog, ident: Identifier) extends Command + +/** + * The logical plan of the SHOW CURRENT NAMESPACE command that works for v2 catalogs. + */ +case class ShowCurrentNamespace(catalogManager: CatalogManager) extends Command { + override val output: Seq[Attribute] = Seq( + AttributeReference("catalog", StringType, nullable = false)(), + AttributeReference("namespace", StringType, nullable = false)()) +} diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala index 10125627ba17..2e6073501f28 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala @@ -1278,6 +1278,12 @@ class DDLParserSuite extends AnalysisTest { comparePlans(parsed3_table, expected3_table) } + test("show current namespace") { + comparePlans( + parsePlan("SHOW CURRENT NAMESPACE"), + ShowCurrentNamespaceStatement()) + } + private case class TableSpec( name: Seq[String], schema: Option[StructType], 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 bc66c154b57a..0a7785b0e088 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 @@ -22,7 +22,7 @@ import scala.collection.JavaConverters._ import org.apache.spark.sql.{AnalysisException, Strategy} import org.apache.spark.sql.catalyst.expressions.{And, PredicateHelper, SubqueryExpression} import org.apache.spark.sql.catalyst.planning.PhysicalOperation -import org.apache.spark.sql.catalyst.plans.logical.{AlterTable, AppendData, CreateNamespace, CreateTableAsSelect, CreateV2Table, DeleteFromTable, DescribeTable, DropNamespace, DropTable, LogicalPlan, OverwriteByExpression, OverwritePartitionsDynamic, RefreshTable, Repartition, ReplaceTable, ReplaceTableAsSelect, SetCatalogAndNamespace, ShowNamespaces, ShowTables} +import org.apache.spark.sql.catalyst.plans.logical.{AlterTable, AppendData, CreateNamespace, CreateTableAsSelect, CreateV2Table, DeleteFromTable, DescribeTable, DropNamespace, DropTable, LogicalPlan, OverwriteByExpression, OverwritePartitionsDynamic, RefreshTable, Repartition, ReplaceTable, ReplaceTableAsSelect, SetCatalogAndNamespace, ShowCurrentNamespace, ShowNamespaces, ShowTables} import org.apache.spark.sql.connector.catalog.{StagingTableCatalog, TableCapability} import org.apache.spark.sql.connector.read.streaming.{ContinuousStream, MicroBatchStream} import org.apache.spark.sql.execution.{FilterExec, ProjectExec, SparkPlan} @@ -210,6 +210,9 @@ object DataSourceV2Strategy extends Strategy with PredicateHelper { case SetCatalogAndNamespace(catalogManager, catalogName, namespace) => SetCatalogAndNamespaceExec(catalogManager, catalogName, namespace) :: Nil + case r: ShowCurrentNamespace => + ShowCurrentNamespaceExec(r.output, r.catalogManager) :: Nil + case _ => Nil } } diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ShowCurrentNamespaceExec.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ShowCurrentNamespaceExec.scala new file mode 100644 index 000000000000..42b80a15080a --- /dev/null +++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ShowCurrentNamespaceExec.scala @@ -0,0 +1,40 @@ +/* + * 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 org.apache.spark.sql.catalyst.InternalRow +import org.apache.spark.sql.catalyst.encoders.RowEncoder +import org.apache.spark.sql.catalyst.expressions.{Attribute, GenericRowWithSchema} +import org.apache.spark.sql.connector.catalog.CatalogManager +import org.apache.spark.sql.connector.catalog.CatalogV2Implicits.NamespaceHelper + +/** + * Physical plan node for showing current catalog/namespace. + */ +case class ShowCurrentNamespaceExec( + output: Seq[Attribute], + catalogManager: CatalogManager) + extends V2CommandExec { + override protected def run(): Seq[InternalRow] = { + val encoder = RowEncoder(schema).resolveAndBind() + Seq(encoder + .toRow(new GenericRowWithSchema( + Array(catalogManager.currentCatalog.name, catalogManager.currentNamespace.quoted), schema)) + .copy()) + } +} diff --git a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala index 096ae9690cbd..3a3d22bc059a 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala @@ -990,6 +990,29 @@ class DataSourceV2SQLSuite assert(catalogManager.currentNamespace === Array("ns1", "ns2")) } + test("ShowCurrentNamespace: basic tests") { + def testShowCurrentNamespace(expectedCatalogName: String, expectedNamespace: String): Unit = { + val schema = new StructType() + .add("catalog", StringType, nullable = false) + .add("namespace", StringType, nullable = false) + val df = sql("SHOW CURRENT NAMESPACE") + val rows = df.collect + + assert(df.schema === schema) + assert(rows.length == 1) + assert(rows(0).getAs[String](0) === expectedCatalogName) + assert(rows(0).getAs[String](1) === expectedNamespace) + } + + // Initially, the v2 session catalog is set as a current catalog. + testShowCurrentNamespace("spark_catalog", "default") + + sql("USE testcat") + testShowCurrentNamespace("testcat", "") + sql("USE testcat.ns1.ns2") + testShowCurrentNamespace("testcat", "ns1.ns2") + } + test("tableCreation: partition column case insensitive resolution") { val testCatalog = catalog("testcat").asTableCatalog val sessionCatalog = catalog(SESSION_CATALOG_NAME).asTableCatalog