Skip to content

[SPARK-29728][SQL] Datasource V2: Support ALTER TABLE RENAME TO #26539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ statement
'(' columns=multipartIdentifierList ')' #dropTableColumns
| ALTER TABLE multipartIdentifier
DROP (COLUMN | COLUMNS) columns=multipartIdentifierList #dropTableColumns
| ALTER (TABLE | VIEW) from=tableIdentifier
RENAME TO to=tableIdentifier #renameTable
| ALTER (TABLE | VIEW) from=multipartIdentifier
RENAME TO to=multipartIdentifier #renameTable
| ALTER (TABLE | VIEW) multipartIdentifier
SET TBLPROPERTIES tablePropertyList #setTableProperties
| ALTER (TABLE | VIEW) multipartIdentifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ class ResolveCatalogs(val catalogManager: CatalogManager)
AlterNamespaceSetProperties(
catalog.asNamespaceCatalog, nameParts, Map("location" -> location))

case RenameTableStatement(NonSessionCatalog(catalog, oldName), newNameParts, isView) =>
if (isView) {
throw new AnalysisException("Renaming view is not supported in v2 catalogs.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test coverage for this too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. I will add one.

}
RenameTable(catalog.asTableCatalog, oldName.asIdentifier, newNameParts.asIdentifier)

case DescribeTableStatement(
nameParts @ NonSessionCatalog(catalog, tableName), partitionSpec, isExtended) =>
if (partitionSpec.nonEmpty) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3245,6 +3245,22 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
query = plan(ctx.query))
}

/**
* Create a [[RenameTableStatement]] command.
*
* For example:
* {{{
* ALTER TABLE multi_part_name1 RENAME TO multi_part_name2;
* ALTER VIEW multi_part_name1 RENAME TO multi_part_name2;
* }}}
*/
override def visitRenameTable(ctx: RenameTableContext): LogicalPlan = withOrigin(ctx) {
RenameTableStatement(
visitMultipartIdentifier(ctx.from),
visitMultipartIdentifier(ctx.to),
ctx.VIEW != null)
}

/**
* A command for users to list the properties for a table. If propertyKey is specified, the value
* for the propertyKey is returned. If propertyKey is not specified, all the keys and their
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ case class AlterViewAsStatement(
originalText: String,
query: LogicalPlan) extends ParsedStatement

/**
* ALTER TABLE ... RENAME TO command, as parsed from SQL.
*/
case class RenameTableStatement(
oldName: Seq[String],
newName: Seq[String],
isView: Boolean) extends ParsedStatement

/**
* A DROP TABLE statement, as parsed from SQL.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,14 @@ case class AlterTable(
}
}

/**
* The logical plan of the ALTER TABLE RENAME command that works for v2 tables.
*/
case class RenameTable(
catalog: TableCatalog,
oldIdent: Identifier,
newIdent: Identifier) extends Command

/**
* The logical plan of the SHOW TABLE command that works for v2 catalogs.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,15 @@ class DDLParserSuite extends AnalysisTest {
}
}

test("alter table/view: rename table/view") {
comparePlans(
parsePlan("ALTER TABLE a.b.c RENAME TO x.y.z"),
RenameTableStatement(Seq("a", "b", "c"), Seq("x", "y", "z"), isView = false))
comparePlans(
parsePlan("ALTER VIEW a.b.c RENAME TO x.y.z"),
RenameTableStatement(Seq("a", "b", "c"), Seq("x", "y", "z"), isView = true))
}

test("describe table column") {
comparePlans(parsePlan("DESCRIBE t col"),
DescribeColumnStatement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ class ResolveSessionCatalog(
}
AlterDatabaseSetLocationCommand(nameParts.head, location)

case RenameTableStatement(SessionCatalog(_, oldName), newNameParts, isView) =>
AlterTableRenameCommand(oldName.asTableIdentifier, newNameParts.asTableIdentifier, isView)

case DescribeTableStatement(
nameParts @ SessionCatalog(catalog, tableName), partitionSpec, isExtended) =>
loadTable(catalog, tableName.asIdentifier).collect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,22 +316,6 @@ class SparkSqlAstBuilder(conf: SQLConf) extends AstBuilder(conf) {
ctx.TEMPORARY != null)
}

/**
* Create a [[AlterTableRenameCommand]] command.
*
* For example:
* {{{
* ALTER TABLE table1 RENAME TO table2;
* ALTER VIEW view1 RENAME TO view2;
* }}}
*/
override def visitRenameTable(ctx: RenameTableContext): LogicalPlan = withOrigin(ctx) {
AlterTableRenameCommand(
visitTableIdentifier(ctx.from),
visitTableIdentifier(ctx.to),
ctx.VIEW != null)
}

/**
* Convert a nested constants list into a sequence of string sequences.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.{AlterNamespaceSetProperties, AlterTable, AppendData, CreateNamespace, CreateTableAsSelect, CreateV2Table, DeleteFromTable, DescribeNamespace, DescribeTable, DropNamespace, DropTable, LogicalPlan, OverwriteByExpression, OverwritePartitionsDynamic, RefreshTable, Repartition, ReplaceTable, ReplaceTableAsSelect, SetCatalogAndNamespace, ShowCurrentNamespace, ShowNamespaces, ShowTableProperties, ShowTables}
import org.apache.spark.sql.catalyst.plans.logical.{AlterNamespaceSetProperties, AlterTable, AppendData, CreateNamespace, CreateTableAsSelect, CreateV2Table, DeleteFromTable, DescribeNamespace, DescribeTable, DropNamespace, DropTable, LogicalPlan, OverwriteByExpression, OverwritePartitionsDynamic, RefreshTable, RenameTable, Repartition, ReplaceTable, ReplaceTableAsSelect, SetCatalogAndNamespace, ShowCurrentNamespace, ShowNamespaces, ShowTableProperties, 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}
Expand Down Expand Up @@ -204,6 +204,9 @@ object DataSourceV2Strategy extends Strategy with PredicateHelper {
case AlterTable(catalog, ident, _, changes) =>
AlterTableExec(catalog, ident, changes) :: Nil

case RenameTable(catalog, oldIdent, newIdent) =>
RenameTableExec(catalog, oldIdent, newIdent) :: Nil

case AlterNamespaceSetProperties(catalog, namespace, properties) =>
AlterNamespaceSetPropertiesExec(catalog, namespace, properties) :: Nil

Expand Down
Original file line number Diff line number Diff line change
@@ -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.expressions.Attribute
import org.apache.spark.sql.connector.catalog.{Identifier, TableCatalog}

/**
* Physical plan node for renaming a table.
*/
case class RenameTableExec(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any specific reasons for naming it as RenameTableExec instead of AlterTableRenameExec similarly to other alter table exec nodes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there was a specific reason. Most likely a byproduct of following TableCatalog.renameTable and visitRenameTable naming.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a big deal, and I prefer RenameTableExec as it's shorter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DropPartitionExec is shorter than AlterTableDropPartitionExec too but we have the AlterTable prefix in all ALTER TABLE exec nodes.

Looks slightly strange that all alter table commands begin from AlterTable except only one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many AlterTable* v2 commands out there? If it's a lot then let's update this one as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a a lot but we have for now:

  • AlterTableAddPartitionExec
  • AlterTableRenamePartitionExec
  • AlterTableDropPartitionExec
  • AlterTableExec

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it's probably better to change them to AddPartitionExec, etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the PR #31584

catalog: TableCatalog,
oldIdent: Identifier,
newIdent: Identifier) extends V2CommandExec {

override def output: Seq[Attribute] = Seq.empty

override protected def run(): Seq[InternalRow] = {
catalog.invalidateTable(oldIdent)
catalog.renameTable(oldIdent, newIdent)

Seq.empty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,24 @@ class DataSourceV2SQLSuite
}
}

test("AlterTable: rename table basic test") {
withTable("testcat.ns1.new") {
sql(s"CREATE TABLE testcat.ns1.ns2.old USING foo AS SELECT id, data FROM source")
checkAnswer(sql("SHOW TABLES FROM testcat.ns1.ns2"), Seq(Row("ns1.ns2", "old")))

sql(s"ALTER TABLE testcat.ns1.ns2.old RENAME TO ns1.new")
checkAnswer(sql("SHOW TABLES FROM testcat.ns1.ns2"), Seq.empty)
checkAnswer(sql("SHOW TABLES FROM testcat.ns1"), Seq(Row("ns1", "new")))
}
}

test("AlterTable: renaming views are not supported") {
val e = intercept[AnalysisException] {
sql(s"ALTER VIEW testcat.ns.tbl RENAME TO ns.view")
}
assert(e.getMessage.contains("Renaming view is not supported in v2 catalogs"))
}

test("ANALYZE TABLE") {
val t = "testcat.ns1.ns2.tbl"
withTable(t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,32 +373,6 @@ class DDLParserSuite extends AnalysisTest with SharedSparkSession {
"Directory path and 'path' in OPTIONS should be specified one, but not both"))
}

// ALTER TABLE table_name RENAME TO new_table_name;
// ALTER VIEW view_name RENAME TO new_view_name;
test("alter table/view: rename table/view") {
val sql_table = "ALTER TABLE table_name RENAME TO new_table_name"
val sql_view = sql_table.replace("TABLE", "VIEW")
val parsed_table = parser.parsePlan(sql_table)
val parsed_view = parser.parsePlan(sql_view)
val expected_table = AlterTableRenameCommand(
TableIdentifier("table_name"),
TableIdentifier("new_table_name"),
isView = false)
val expected_view = AlterTableRenameCommand(
TableIdentifier("table_name"),
TableIdentifier("new_table_name"),
isView = true)
comparePlans(parsed_table, expected_table)
comparePlans(parsed_view, expected_view)
}

test("alter table: rename table with database") {
val query = "ALTER TABLE db1.tbl RENAME TO db1.tbl2"
val plan = parseAs[AlterTableRenameCommand](query)
assert(plan.oldName == TableIdentifier("tbl", Some("db1")))
assert(plan.newName == TableIdentifier("tbl2", Some("db1")))
}

test("alter table - property values must be set") {
assertUnsupported(
sql = "ALTER TABLE my_tab SET TBLPROPERTIES('key_without_value', 'key_with_value'='x')",
Expand Down