Skip to content

Commit 4329c5c

Browse files
cloud-fanyhuai
authored andcommitted
[SPARK-17873][SQL] ALTER TABLE RENAME TO should allow users to specify database in destination table name(but have to be same as source table)
## What changes were proposed in this pull request? Unlike Hive, in Spark SQL, ALTER TABLE RENAME TO cannot move a table from one database to another(e.g. `ALTER TABLE db1.tbl RENAME TO db2.tbl2`), and will report error if the database in source table and destination table is different. So in #14955 , we forbid users to specify database of destination table in ALTER TABLE RENAME TO, to be consistent with other database systems and also make it easier to rename tables in non-current database, e.g. users can write `ALTER TABLE db1.tbl RENAME TO tbl2`, instead of `ALTER TABLE db1.tbl RENAME TO db1.tbl2`. However, this is a breaking change. Users may already have queries that specify database of destination table in ALTER TABLE RENAME TO. This PR reverts most of #14955 , and simplify the usage of ALTER TABLE RENAME TO by making database of source table the default database of destination table, instead of current database, so that users can still write `ALTER TABLE db1.tbl RENAME TO tbl2`, which is consistent with other databases like MySQL, Postgres, etc. ## How was this patch tested? The added back tests and some new tests. Author: Wenchen Fan <[email protected]> Closes #15434 from cloud-fan/revert.
1 parent 2629cd7 commit 4329c5c

File tree

6 files changed

+87
-35
lines changed

6 files changed

+87
-35
lines changed

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/SessionCatalog.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,20 @@ class SessionCatalog(
462462
* If a database is specified in `oldName`, this will rename the table in that database.
463463
* If no database is specified, this will first attempt to rename a temporary table with
464464
* the same name, then, if that does not exist, rename the table in the current database.
465+
*
466+
* This assumes the database specified in `newName` matches the one in `oldName`.
465467
*/
466-
def renameTable(oldName: TableIdentifier, newName: String): Unit = synchronized {
468+
def renameTable(oldName: TableIdentifier, newName: TableIdentifier): Unit = synchronized {
467469
val db = formatDatabaseName(oldName.database.getOrElse(currentDb))
470+
newName.database.map(formatDatabaseName).foreach { newDb =>
471+
if (db != newDb) {
472+
throw new AnalysisException(
473+
s"RENAME TABLE source and destination databases do not match: '$db' != '$newDb'")
474+
}
475+
}
476+
468477
val oldTableName = formatTableName(oldName.table)
469-
val newTableName = formatTableName(newName)
478+
val newTableName = formatTableName(newName.table)
470479
if (db == globalTempViewManager.database) {
471480
globalTempViewManager.rename(oldTableName, newTableName)
472481
} else {
@@ -476,6 +485,11 @@ class SessionCatalog(
476485
requireTableNotExists(TableIdentifier(newTableName, Some(db)))
477486
externalCatalog.renameTable(db, oldTableName, newTableName)
478487
} else {
488+
if (newName.database.isDefined) {
489+
throw new AnalysisException(
490+
s"RENAME TEMPORARY TABLE from '$oldName' to '$newName': cannot specify database " +
491+
s"name '${newName.database.get}' in the destination table")
492+
}
479493
if (tempTables.contains(newTableName)) {
480494
throw new AnalysisException(s"RENAME TEMPORARY TABLE from '$oldName' to '$newName': " +
481495
"destination table already exists")

sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/catalog/SessionCatalogSuite.scala

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -273,27 +273,34 @@ class SessionCatalogSuite extends SparkFunSuite {
273273
val externalCatalog = newBasicCatalog()
274274
val sessionCatalog = new SessionCatalog(externalCatalog)
275275
assert(externalCatalog.listTables("db2").toSet == Set("tbl1", "tbl2"))
276-
sessionCatalog.renameTable(TableIdentifier("tbl1", Some("db2")), "tblone")
276+
sessionCatalog.renameTable(TableIdentifier("tbl1", Some("db2")), TableIdentifier("tblone"))
277277
assert(externalCatalog.listTables("db2").toSet == Set("tblone", "tbl2"))
278-
sessionCatalog.renameTable(TableIdentifier("tbl2", Some("db2")), "tbltwo")
278+
sessionCatalog.renameTable(TableIdentifier("tbl2", Some("db2")), TableIdentifier("tbltwo"))
279279
assert(externalCatalog.listTables("db2").toSet == Set("tblone", "tbltwo"))
280280
// Rename table without explicitly specifying database
281281
sessionCatalog.setCurrentDatabase("db2")
282-
sessionCatalog.renameTable(TableIdentifier("tbltwo"), "table_two")
282+
sessionCatalog.renameTable(TableIdentifier("tbltwo"), TableIdentifier("table_two"))
283283
assert(externalCatalog.listTables("db2").toSet == Set("tblone", "table_two"))
284+
// Renaming "db2.tblone" to "db1.tblones" should fail because databases don't match
285+
intercept[AnalysisException] {
286+
sessionCatalog.renameTable(
287+
TableIdentifier("tblone", Some("db2")), TableIdentifier("tblones", Some("db1")))
288+
}
284289
// The new table already exists
285290
intercept[TableAlreadyExistsException] {
286-
sessionCatalog.renameTable(TableIdentifier("tblone", Some("db2")), "table_two")
291+
sessionCatalog.renameTable(
292+
TableIdentifier("tblone", Some("db2")),
293+
TableIdentifier("table_two"))
287294
}
288295
}
289296

290297
test("rename table when database/table does not exist") {
291298
val catalog = new SessionCatalog(newBasicCatalog())
292299
intercept[NoSuchDatabaseException] {
293-
catalog.renameTable(TableIdentifier("tbl1", Some("unknown_db")), "tbl2")
300+
catalog.renameTable(TableIdentifier("tbl1", Some("unknown_db")), TableIdentifier("tbl2"))
294301
}
295302
intercept[NoSuchTableException] {
296-
catalog.renameTable(TableIdentifier("unknown_table", Some("db2")), "tbl2")
303+
catalog.renameTable(TableIdentifier("unknown_table", Some("db2")), TableIdentifier("tbl2"))
297304
}
298305
}
299306

@@ -306,12 +313,12 @@ class SessionCatalogSuite extends SparkFunSuite {
306313
assert(sessionCatalog.getTempView("tbl1") == Option(tempTable))
307314
assert(externalCatalog.listTables("db2").toSet == Set("tbl1", "tbl2"))
308315
// If database is not specified, temp table should be renamed first
309-
sessionCatalog.renameTable(TableIdentifier("tbl1"), "tbl3")
316+
sessionCatalog.renameTable(TableIdentifier("tbl1"), TableIdentifier("tbl3"))
310317
assert(sessionCatalog.getTempView("tbl1").isEmpty)
311318
assert(sessionCatalog.getTempView("tbl3") == Option(tempTable))
312319
assert(externalCatalog.listTables("db2").toSet == Set("tbl1", "tbl2"))
313320
// If database is specified, temp tables are never renamed
314-
sessionCatalog.renameTable(TableIdentifier("tbl2", Some("db2")), "tbl4")
321+
sessionCatalog.renameTable(TableIdentifier("tbl2", Some("db2")), TableIdentifier("tbl4"))
315322
assert(sessionCatalog.getTempView("tbl3") == Option(tempTable))
316323
assert(sessionCatalog.getTempView("tbl4").isEmpty)
317324
assert(externalCatalog.listTables("db2").toSet == Set("tbl1", "tbl4"))

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -689,15 +689,9 @@ class SparkSqlAstBuilder(conf: SQLConf) extends AstBuilder {
689689
* }}}
690690
*/
691691
override def visitRenameTable(ctx: RenameTableContext): LogicalPlan = withOrigin(ctx) {
692-
val fromName = visitTableIdentifier(ctx.from)
693-
val toName = visitTableIdentifier(ctx.to)
694-
if (toName.database.isDefined) {
695-
operationNotAllowed("Can not specify database in table/view name after RENAME TO", ctx)
696-
}
697-
698692
AlterTableRenameCommand(
699-
fromName,
700-
toName.table,
693+
visitTableIdentifier(ctx.from),
694+
visitTableIdentifier(ctx.to),
701695
ctx.VIEW != null)
702696
}
703697

sql/core/src/main/scala/org/apache/spark/sql/execution/command/tables.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ case class CreateTableCommand(table: CatalogTable, ifNotExists: Boolean) extends
146146
*/
147147
case class AlterTableRenameCommand(
148148
oldName: TableIdentifier,
149-
newName: String,
149+
newName: TableIdentifier,
150150
isView: Boolean)
151151
extends RunnableCommand {
152152

@@ -159,7 +159,6 @@ case class AlterTableRenameCommand(
159159
} else {
160160
val table = catalog.getTableMetadata(oldName)
161161
DDLUtils.verifyAlterTableType(catalog, table, isView)
162-
val newTblName = TableIdentifier(newName, oldName.database)
163162
// If an exception is thrown here we can just assume the table is uncached;
164163
// this can happen with Hive tables when the underlying catalog is in-memory.
165164
val wasCached = Try(sparkSession.catalog.isCached(oldName.unquotedString)).getOrElse(false)
@@ -172,7 +171,7 @@ case class AlterTableRenameCommand(
172171
}
173172
// For datasource tables, we also need to update the "path" serde property
174173
if (DDLUtils.isDatasourceTable(table) && table.tableType == CatalogTableType.MANAGED) {
175-
val newPath = catalog.defaultTablePath(newTblName)
174+
val newPath = catalog.defaultTablePath(newName)
176175
val newTable = table.withNewStorage(
177176
properties = table.storage.properties ++ Map("path" -> newPath))
178177
catalog.alterTable(newTable)
@@ -182,7 +181,7 @@ case class AlterTableRenameCommand(
182181
catalog.refreshTable(oldName)
183182
catalog.renameTable(oldName, newName)
184183
if (wasCached) {
185-
sparkSession.catalog.cacheTable(newTblName.unquotedString)
184+
sparkSession.catalog.cacheTable(newName.unquotedString)
186185
}
187186
}
188187
Seq.empty[Row]

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -387,20 +387,22 @@ class DDLCommandSuite extends PlanTest {
387387
val parsed_table = parser.parsePlan(sql_table)
388388
val parsed_view = parser.parsePlan(sql_view)
389389
val expected_table = AlterTableRenameCommand(
390-
TableIdentifier("table_name", None),
391-
"new_table_name",
390+
TableIdentifier("table_name"),
391+
TableIdentifier("new_table_name"),
392392
isView = false)
393393
val expected_view = AlterTableRenameCommand(
394-
TableIdentifier("table_name", None),
395-
"new_table_name",
394+
TableIdentifier("table_name"),
395+
TableIdentifier("new_table_name"),
396396
isView = true)
397397
comparePlans(parsed_table, expected_table)
398398
comparePlans(parsed_view, expected_view)
399+
}
399400

400-
val e = intercept[ParseException](
401-
parser.parsePlan("ALTER TABLE db1.tbl RENAME TO db1.tbl2")
402-
)
403-
assert(e.getMessage.contains("Can not specify database in table/view name after RENAME TO"))
401+
test("alter table: rename table with database") {
402+
val query = "ALTER TABLE db1.tbl RENAME TO db1.tbl2"
403+
val plan = parseAs[AlterTableRenameCommand](query)
404+
assert(plan.oldName == TableIdentifier("tbl", Some("db1")))
405+
assert(plan.newName == TableIdentifier("tbl2", Some("db1")))
404406
}
405407

406408
// ALTER TABLE table_name SET TBLPROPERTIES ('comment' = new_comment);

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -665,16 +665,27 @@ class DDLSuite extends QueryTest with SharedSQLContext with BeforeAndAfterEach {
665665
createDatabase(catalog, "dbx")
666666
createDatabase(catalog, "dby")
667667
createTable(catalog, tableIdent1)
668+
668669
assert(catalog.listTables("dbx") == Seq(tableIdent1))
669-
sql("ALTER TABLE dbx.tab1 RENAME TO tab2")
670+
sql("ALTER TABLE dbx.tab1 RENAME TO dbx.tab2")
670671
assert(catalog.listTables("dbx") == Seq(tableIdent2))
672+
673+
// The database in destination table name can be omitted, and we will use the database of source
674+
// table for it.
675+
sql("ALTER TABLE dbx.tab2 RENAME TO tab1")
676+
assert(catalog.listTables("dbx") == Seq(tableIdent1))
677+
671678
catalog.setCurrentDatabase("dbx")
672679
// rename without explicitly specifying database
673-
sql("ALTER TABLE tab2 RENAME TO tab1")
674-
assert(catalog.listTables("dbx") == Seq(tableIdent1))
680+
sql("ALTER TABLE tab1 RENAME TO tab2")
681+
assert(catalog.listTables("dbx") == Seq(tableIdent2))
675682
// table to rename does not exist
676683
intercept[AnalysisException] {
677-
sql("ALTER TABLE dbx.does_not_exist RENAME TO tab2")
684+
sql("ALTER TABLE dbx.does_not_exist RENAME TO dbx.tab2")
685+
}
686+
// destination database is different
687+
intercept[AnalysisException] {
688+
sql("ALTER TABLE dbx.tab1 RENAME TO dby.tab2")
678689
}
679690
}
680691

@@ -696,6 +707,31 @@ class DDLSuite extends QueryTest with SharedSQLContext with BeforeAndAfterEach {
696707
assert(spark.table("teachers").collect().toSeq == df.collect().toSeq)
697708
}
698709

710+
test("rename temporary table - destination table with database name") {
711+
withTempView("tab1") {
712+
sql(
713+
"""
714+
|CREATE TEMPORARY TABLE tab1
715+
|USING org.apache.spark.sql.sources.DDLScanSource
716+
|OPTIONS (
717+
| From '1',
718+
| To '10',
719+
| Table 'test1'
720+
|)
721+
""".stripMargin)
722+
723+
val e = intercept[AnalysisException] {
724+
sql("ALTER TABLE tab1 RENAME TO default.tab2")
725+
}
726+
assert(e.getMessage.contains(
727+
"RENAME TEMPORARY TABLE from '`tab1`' to '`default`.`tab2`': " +
728+
"cannot specify database name 'default' in the destination table"))
729+
730+
val catalog = spark.sessionState.catalog
731+
assert(catalog.listTables("default") == Seq(TableIdentifier("tab1")))
732+
}
733+
}
734+
699735
test("rename temporary table") {
700736
withTempView("tab1", "tab2") {
701737
spark.range(10).createOrReplaceTempView("tab1")
@@ -736,7 +772,7 @@ class DDLSuite extends QueryTest with SharedSQLContext with BeforeAndAfterEach {
736772
sql("ALTER TABLE tab1 RENAME TO tab2")
737773
}
738774
assert(e.getMessage.contains(
739-
"RENAME TEMPORARY TABLE from '`tab1`' to 'tab2': destination table already exists"))
775+
"RENAME TEMPORARY TABLE from '`tab1`' to '`tab2`': destination table already exists"))
740776

741777
val catalog = spark.sessionState.catalog
742778
assert(catalog.listTables("default") == Seq(TableIdentifier("tab1"), TableIdentifier("tab2")))

0 commit comments

Comments
 (0)