From cf3a248e09180201950ff80d729b68b6223d2356 Mon Sep 17 00:00:00 2001 From: Mervyn Lobo Date: Sat, 25 Apr 2026 00:29:12 +0530 Subject: [PATCH 1/3] [SPARK-56611][SQL] Fix ALTER TABLE RENAME TO with catalog-qualified and table-only targets --- .../datasources/v2/DataSourceV2Strategy.scala | 22 ++++++++++++-- .../sql/connector/DataSourceV2SQLSuite.scala | 30 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) 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 6730673cab025..528f106afa3dc 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 @@ -448,14 +448,32 @@ class DataSourceV2Strategy(session: SparkSession) extends Strategy with Predicat case _: NoopCommand => LocalTableScanExec(Nil, Nil, None) :: Nil - case RenameTable(r @ ResolvedTable(catalog, oldIdent, _, _), newIdent, isView) => + case RenameTable(r @ ResolvedTable(catalog, oldIdent, _, _), rawNewNameParts, isView) => if (isView) { throw QueryCompilationErrors.cannotRenameTableWithAlterViewError() } + + // Strip catalog prefix if the identifier is catalog-qualified. + val newNameParts = + if (rawNewNameParts.length > 1 && rawNewNameParts.head == catalog.name()) { + rawNewNameParts.tail + } else { + rawNewNameParts + } + + val namespace = + if (newNameParts.length == 1) { + oldIdent.namespace() + } else { + newNameParts.dropRight(1).toArray + } + + val newIdent = Identifier.of(namespace, newNameParts.last) + RenameTableExec( catalog, oldIdent, - newIdent.asIdentifier, + newIdent, invalidateTableCache(r), session.sharedState.cacheManager.cacheQuery) :: Nil 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 d2cc342f48112..6ec8d1ac14ebd 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 @@ -2486,6 +2486,36 @@ class DataSourceV2SQLSuiteV1Filter ExpectedContext("testcat.ns.tbl", 11, 10 + "testcat.ns.tbl".length)) } + test("SPARK-56611: rename table with catalog-qualified identifier") { + withTable("testcat.ns.t1", "testcat.ns.t1_renamed") { + sql("CREATE TABLE testcat.ns.t1 USING foo AS SELECT id, data FROM source") + checkAnswer(sql("SHOW TABLES FROM testcat.ns"), Seq(Row("ns", "t1", false))) + + // rename with just table name, inherits namespace from source + sql("ALTER TABLE testcat.ns.t1 RENAME TO t1_renamed") + checkAnswer(sql("SHOW TABLES FROM testcat.ns"), Seq(Row("ns", "t1_renamed", false))) + + // rename with namespace-qualified name + sql("ALTER TABLE testcat.ns.t1_renamed RENAME TO ns.t1") + checkAnswer(sql("SHOW TABLES FROM testcat.ns"), Seq(Row("ns", "t1", false))) + + // rename with catalog-qualified target identifier + sql("ALTER TABLE testcat.ns.t1 RENAME TO testcat.ns.t1_renamed") + checkAnswer(sql("SHOW TABLES FROM testcat.ns"), Seq(Row("ns", "t1_renamed", false))) + } + } + + test("SPARK-56611: rename table across namespace should work for V2 catalogs") { + withTable("testcat.ns1.t1", "testcat.ns2.t1") { + sql("CREATE TABLE testcat.ns1.t1 USING foo AS SELECT id, data FROM source") + checkAnswer(sql("SHOW TABLES FROM testcat.ns1"), Seq(Row("ns1", "t1", false))) + + sql("ALTER TABLE testcat.ns1.t1 RENAME TO ns2.t1") + checkAnswer(sql("SHOW TABLES FROM testcat.ns1"), Nil) + checkAnswer(sql("SHOW TABLES FROM testcat.ns2"), Seq(Row("ns2", "t1", false))) + } + } + test("ANALYZE TABLE") { val t = "testcat.ns1.ns2.tbl" withTable(t) { From c57ac4b2f1d63db1b78c060a85c7b3bc8a3b4c25 Mon Sep 17 00:00:00 2001 From: Mervyn Lobo Date: Tue, 28 Apr 2026 14:45:35 +0530 Subject: [PATCH 2/3] [SPARK-56611][SQL] Fix case-insensitive catalog comparison and address review feedback --- .../datasources/v2/DataSourceV2Strategy.scala | 23 +++++++++---------- .../sql/connector/DataSourceV2SQLSuite.scala | 6 ++++- 2 files changed, 16 insertions(+), 13 deletions(-) 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 528f106afa3dc..cc9f2a840ac56 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 @@ -454,19 +454,18 @@ class DataSourceV2Strategy(session: SparkSession) extends Strategy with Predicat } // Strip catalog prefix if the identifier is catalog-qualified. - val newNameParts = - if (rawNewNameParts.length > 1 && rawNewNameParts.head == catalog.name()) { - rawNewNameParts.tail - } else { - rawNewNameParts - } + val newNameParts = if (rawNewNameParts.length > 1 && + SQLConf.get.resolver(rawNewNameParts.head, catalog.name())) { + rawNewNameParts.tail + } else { + rawNewNameParts + } - val namespace = - if (newNameParts.length == 1) { - oldIdent.namespace() - } else { - newNameParts.dropRight(1).toArray - } + val namespace = if (newNameParts.length == 1) { + oldIdent.namespace() + } else { + newNameParts.init.toArray + } val newIdent = Identifier.of(namespace, newNameParts.last) 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 6ec8d1ac14ebd..38577df9898be 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 @@ -2502,10 +2502,14 @@ class DataSourceV2SQLSuiteV1Filter // rename with catalog-qualified target identifier sql("ALTER TABLE testcat.ns.t1 RENAME TO testcat.ns.t1_renamed") checkAnswer(sql("SHOW TABLES FROM testcat.ns"), Seq(Row("ns", "t1_renamed", false))) + + // rename with case-insensitive catalog-qualified target identifier + sql("ALTER TABLE testcat.ns.t1_renamed RENAME TO TesTcaT.ns.t1") + checkAnswer(sql("SHOW TABLES FROM testcat.ns"), Seq(Row("ns", "t1", false))) } } - test("SPARK-56611: rename table across namespace should work for V2 catalogs") { + test("SPARK-56611: rename table across namespaces") { withTable("testcat.ns1.t1", "testcat.ns2.t1") { sql("CREATE TABLE testcat.ns1.t1 USING foo AS SELECT id, data FROM source") checkAnswer(sql("SHOW TABLES FROM testcat.ns1"), Seq(Row("ns1", "t1", false))) From 615866926b931458a512e758f19505b2194b97ea Mon Sep 17 00:00:00 2001 From: Mervyn Lobo Date: Wed, 29 Apr 2026 14:18:19 +0530 Subject: [PATCH 3/3] [SPARK-56611][SQL] Adopt review suggestion and add test for cross-catalog namespace resolution --- .../datasources/v2/DataSourceV2Strategy.scala | 8 +++----- .../spark/sql/connector/DataSourceV2SQLSuite.scala | 13 +++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) 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 cc9f2a840ac56..9b42fad725478 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 @@ -461,14 +461,12 @@ class DataSourceV2Strategy(session: SparkSession) extends Strategy with Predicat rawNewNameParts } - val namespace = if (newNameParts.length == 1) { - oldIdent.namespace() + val newIdent = if (newNameParts.length == 1) { + Identifier.of(oldIdent.namespace(), newNameParts.last) } else { - newNameParts.init.toArray + newNameParts.asIdentifier } - val newIdent = Identifier.of(namespace, newNameParts.last) - RenameTableExec( catalog, oldIdent, 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 38577df9898be..37dc1b1c0964c 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 @@ -2520,6 +2520,19 @@ class DataSourceV2SQLSuiteV1Filter } } + test("SPARK-56611: rename target is resolved within source catalog") { + withTable("testcat.ns1.t1", "testcat.testcat2.ns2.t1") { + sql("CREATE TABLE testcat.ns1.t1 USING foo AS SELECT id, data FROM source") + + // testcat2 is a valid catalog name, but in the rename target it is + // treated as a namespace within testcat (the source table's catalog). + sql("ALTER TABLE testcat.ns1.t1 RENAME TO testcat2.ns2.t1") + checkAnswer(sql("SHOW TABLES FROM testcat.ns1"), Nil) + checkAnswer( + sql("SHOW TABLES FROM testcat.testcat2.ns2"), Seq(Row("testcat2.ns2", "t1", false))) + } + } + test("ANALYZE TABLE") { val t = "testcat.ns1.ns2.tbl" withTable(t) {