This is an automated email from the ASF dual-hosted git repository.

szehon-ho pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new 4eb9cb7c3d Spark 4.2: Fix view rename target namespace (#18255)
4eb9cb7c3d is described below

commit 4eb9cb7c3d53cb8080724eebf6e6898871ee23b0
Author: Manu Zhang <[email protected]>
AuthorDate: Fri Oct 9 09:33:58 2026 +0800

    Spark 4.2: Fix view rename target namespace (#18255)
---
 .../v2/ExtendedDataSourceV2Strategy.scala          | 25 ++++++++++++++-----
 .../apache/iceberg/spark/extensions/TestViews.java | 28 ++++++++++++++++++++++
 .../java/org/apache/iceberg/spark/Spark3Util.java  |  3 +++
 3 files changed, 50 insertions(+), 6 deletions(-)

diff --git 
a/spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ExtendedDataSourceV2Strategy.scala
 
b/spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ExtendedDataSourceV2Strategy.scala
index 7d46b2e045..17d734deec 100644
--- 
a/spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ExtendedDataSourceV2Strategy.scala
+++ 
b/spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ExtendedDataSourceV2Strategy.scala
@@ -125,12 +125,8 @@ case class ExtendedDataSourceV2Strategy(spark: 
SparkSession) extends Strategy wi
           ResolvedV2View(oldCatalog: ViewCatalog, oldIdent, _),
           newName,
           isView @ true) =>
-      val newIdent = Spark3Util.catalogAndIdentifier(spark, 
newName.toList.asJava)
-      if (oldCatalog.name != newIdent.catalog().name()) {
-        throw new IcebergAnalysisException(
-          s"Cannot move view between catalogs: from=${oldCatalog.name} and 
to=${newIdent.catalog().name()}")
-      }
-      IcebergRenameV2ViewExec(oldCatalog, oldIdent, newIdent.identifier()) :: 
Nil
+      val targetIdent = resolveViewRenameTarget(oldCatalog, newName)
+      IcebergRenameV2ViewExec(oldCatalog, oldIdent, targetIdent) :: Nil
 
     case DropIcebergView(ResolvedIdentifier(viewCatalog: ViewCatalog, ident), 
ifExists) =>
       DropV2ViewExec(viewCatalog, ident, ifExists) :: Nil
@@ -189,6 +185,23 @@ case class ExtendedDataSourceV2Strategy(spark: 
SparkSession) extends Strategy wi
     case _ => Nil
   }
 
+  private def resolveViewRenameTarget(
+      sourceCatalog: ViewCatalog,
+      targetName: Seq[String]): Identifier = {
+    if (targetName.length == 1) {
+      // An unqualified target keeps the source view's namespace
+      Identifier.of(Array.empty[String], targetName.head)
+    } else {
+      val target = Spark3Util.catalogAndIdentifier(spark, 
targetName.toList.asJava, sourceCatalog)
+      if (sourceCatalog.name != target.catalog().name()) {
+        throw new IcebergAnalysisException(
+          s"Cannot move view between catalogs: from=${sourceCatalog.name} and 
to=${target.catalog().name()}")
+      }
+
+      target.identifier()
+    }
+  }
+
   private object IcebergCatalogAndIdentifier {
     def unapply(identifier: Seq[String]): Option[(TableCatalog, Identifier)] = 
{
       val catalogAndIdentifier = Spark3Util.catalogAndIdentifier(spark, 
identifier.asJava)
diff --git 
a/spark/v4.2/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestViews.java
 
b/spark/v4.2/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestViews.java
index 254c45f64d..2d3953f549 100644
--- 
a/spark/v4.2/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestViews.java
+++ 
b/spark/v4.2/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestViews.java
@@ -893,6 +893,34 @@ public class TestViews extends ExtensionsTestBase {
     assertThat(viewCatalog().viewExists(TableIdentifier.of(NAMESPACE, 
renamedView))).isTrue();
   }
 
+  @TestTemplate
+  public void renameQualifiedViewToUnqualifiedTargetKeepsSourceNamespace() {
+    Namespace sourceNamespace = Namespace.of(viewName("rename_source_ns"));
+    Namespace currentNamespace = Namespace.of(viewName("rename_current_ns"));
+    String viewName = viewName("originalView");
+    String renamedView = viewName("renamedView");
+    String sql = String.format("SELECT id FROM %s.%s.%s", catalogName, 
NAMESPACE, tableName);
+
+    sql("CREATE NAMESPACE IF NOT EXISTS %s.%s", catalogName, sourceNamespace);
+    sql("CREATE NAMESPACE IF NOT EXISTS %s.%s", catalogName, currentNamespace);
+
+    ViewCatalog viewCatalog = viewCatalog();
+    viewCatalog
+        .buildView(TableIdentifier.of(sourceNamespace, viewName))
+        .withQuery("spark", sql)
+        .withDefaultNamespace(sourceNamespace)
+        .withDefaultCatalog(catalogName)
+        .withSchema(schema(sql))
+        .create();
+
+    sql("USE %s.%s", catalogName, currentNamespace);
+    sql("ALTER VIEW %s.%s RENAME TO %s", sourceNamespace, viewName, 
renamedView);
+
+    assertThat(viewCatalog.viewExists(TableIdentifier.of(sourceNamespace, 
viewName))).isFalse();
+    assertThat(viewCatalog.viewExists(TableIdentifier.of(sourceNamespace, 
renamedView))).isTrue();
+    assertThat(viewCatalog.viewExists(TableIdentifier.of(currentNamespace, 
renamedView))).isFalse();
+  }
+
   @TestTemplate
   public void renameViewHiddenByTempView() throws NoSuchTableException {
     insertRows(10);
diff --git 
a/spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java 
b/spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java
index 92ff7926cc..567743927e 100644
--- a/spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java
+++ b/spark/v4.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java
@@ -821,6 +821,9 @@ public class Spark3Util {
    * A modified version of Spark's LookupCatalog.CatalogAndIdentifier.unapply 
Attempts to find the
    * catalog and identifier a multipart identifier represents
    *
+   * <p>A single-part name is resolved in the session's current namespace, or 
in the default
+   * namespace of {@code defaultCatalog} if it is not the current catalog.
+   *
    * @param spark Spark session to use for resolution
    * @param nameParts Multipart identifier representing a table
    * @param defaultCatalog Catalog to use if none is specified

Reply via email to