Skip to content
Open
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 @@ -119,12 +119,8 @@ case class ExtendedDataSourceV2Strategy(spark: SparkSession) extends Strategy wi
OrderAwareCoalesceExec(numPartitions, coalescer, planLater(child)) :: Nil

case RenameTable(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()}")
}
RenameV2ViewExec(oldCatalog, oldIdent, newIdent.identifier()) :: Nil
val targetIdent = resolveViewRenameTarget(oldCatalog, newName)
RenameV2ViewExec(oldCatalog, oldIdent, targetIdent) :: Nil

case DropIcebergView(ResolvedIdentifier(viewCatalog: ViewCatalog, ident), ifExists) =>
DropV2ViewExec(viewCatalog, ident, ifExists) :: Nil
Expand Down Expand Up @@ -176,6 +172,22 @@ case class ExtendedDataSourceV2Strategy(spark: SparkSession) extends Strategy wi
case _ => Nil
}

private def resolveViewRenameTarget(
sourceCatalog: ViewCatalog,
targetName: Seq[String]): Identifier = {
if (targetName.length == 1) {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ case class RenameV2ViewExec(catalog: ViewCatalog, oldIdent: Identifier, newIdent
override lazy val output: Seq[Attribute] = Nil

override protected def run(): Seq[InternalRow] = {
catalog.renameView(oldIdent, newIdent)
// Match Spark's v2 rename behavior: an unqualified target renames in place.
val qualifiedNewIdent =
if (newIdent.namespace().isEmpty) {
Identifier.of(oldIdent.namespace(), newIdent.name())
} else {
newIdent
}
catalog.renameView(oldIdent, qualifiedNewIdent)

Seq.empty
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,34 @@ public void renameView() throws NoSuchTableException {
.containsExactlyInAnyOrderElementsOf(expected);
}

@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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it seems a bit counter-intuitive to resolve this against the target namespace instead of resolving against the current namespace? I don't think we have other places where we would have a similar resolution strategy? Typically providing only the plain identifier should always resolve to whatever catalog/namespace is currently configured

@manuzhang manuzhang Jul 9, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you check whether there are other places in Spark where this behavior exists or is rename the only one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, it's a specific behavior in rename based on my research.


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);
Expand Down
Loading