{"record":{"id":"2652cc26cae7b4e7","repo":"apache/iceberg","slug":"cannot-move-view-between-catalogs-from-oldcatal-2652cc","errorCode":null,"errorMessage":"Cannot move view between catalogs: from=${oldCatalog.name} and to=${newIdent.catalog().name()}","messagePattern":"Cannot move view between catalogs: from=(.+?) and to=(.+?)","errorType":"exception","errorClass":"IcebergAnalysisException","httpStatus":null,"severity":"error","filePath":"spark/v4.0/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ExtendedDataSourceV2Strategy.scala","lineNumber":124,"sourceCode":"    case SetIdentifierFields(IcebergCatalogAndIdentifier(catalog, ident), fields) =>\n      SetIdentifierFieldsExec(catalog, ident, fields) :: Nil\n\n    case DropIdentifierFields(IcebergCatalogAndIdentifier(catalog, ident), fields) =>\n      DropIdentifierFieldsExec(catalog, ident, fields) :: Nil\n\n    case SetWriteDistributionAndOrdering(\n          IcebergCatalogAndIdentifier(catalog, ident),\n          distributionMode,\n          ordering) =>\n      SetWriteDistributionAndOrderingExec(catalog, ident, distributionMode, ordering) :: Nil\n\n    case OrderAwareCoalesce(numPartitions, coalescer, child) =>\n      OrderAwareCoalesceExec(numPartitions, coalescer, planLater(child)) :: Nil\n\n    case RenameTable(ResolvedV2View(oldCatalog: ViewCatalog, oldIdent), newName, isView @ true) =>\n      val newIdent = Spark3Util.catalogAndIdentifier(spark, newName.toList.asJava)\n      if (oldCatalog.name != newIdent.catalog().name()) {\n        throw new IcebergAnalysisException(\n          s\"Cannot move view between catalogs: from=${oldCatalog.name} and to=${newIdent.catalog().name()}\")\n      }\n      RenameV2ViewExec(oldCatalog, oldIdent, newIdent.identifier()) :: Nil\n\n    case DropIcebergView(ResolvedIdentifier(viewCatalog: ViewCatalog, ident), ifExists) =>\n      DropV2ViewExec(viewCatalog, ident, ifExists) :: Nil\n\n    case CreateIcebergView(\n          ResolvedIdentifier(viewCatalog: ViewCatalog, ident),\n          queryText,\n          query,\n          columnAliases,\n          columnComments,\n          queryColumnNames,\n          comment,\n          properties,\n          allowExisting,\n          replace,","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.0/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ExtendedDataSourceV2Strategy.scala#L106-L142","documentation":"ExtendedDataSourceV2Strategy throws IcebergAnalysisException when ALTER TABLE ... RENAME TO (on a view) resolves the destination to a different catalog than the source. Spark's RenameTable on ViewCatalog only supports renaming within the same catalog; cross-catalog moves are not implemented, so the plan is rejected before execution.","triggerScenarios":"ALTER VIEW old_catalog.ns.v RENAME TO other_catalog.ns.v2 where Spark3Util.catalogAndIdentifier resolves `other_catalog` as the destination catalog name differing from oldCatalog.name.","commonSituations":"Unqualified RENAME TO names that default to the session catalog while the view lives in a named Iceberg catalog; intentional cross-catalog reorganization attempts; copy-pasted rename statements that drop the catalog prefix.","solutions":["Rename within the same catalog: qualify the new name with the same catalog as the source.","If a cross-catalog move is needed, create the view in the target catalog (CREATE VIEW ... AS SELECT) and drop the original.","Check how the new name is parsed via Spark3Util.catalogAndIdentifier — use a fully qualified name to avoid accidental session-catalog resolution.","Include the full catalog.ns prefix in the new identifier."],"exampleFix":"// before\nALTER VIEW iceberg_catalog.db.events_v RENAME TO session_catalog.db.events_v;\n// after\nALTER VIEW iceberg_catalog.db.events_v RENAME TO iceberg_catalog.db.events_v2;","handlingStrategy":"validation","validationCode":"val (newCatalog, _) = Spark3Util.catalogAndIdentifier(spark, newName)\nif (newCatalog.name() != sourceCatalogName)\n  throw new IllegalArgumentException(\"Rename cannot move a view across catalogs; create a new view instead\")","typeGuard":"def sameCatalog(oldName: String, newName: String): Boolean =\n  Spark3Util.catalogAndIdentifier(spark, newName).catalog().name() ==\n    Spark3Util.catalogAndIdentifier(spark, oldName).catalog().name()","tryCatchPattern":"try { spark.sql(s\"ALTER VIEW $oldName RENAME TO $newName\") } catch { case e: IcebergAnalysisException if e.getMessage.startsWith(\"Cannot move view between catalogs\") => // fall back to create-then-drop across catalogs }","preventionTips":["Always use fully qualified catalog.namespace.view names in RENAME TO","Never assume RENAME TO without a catalog prefix stays in the source catalog","For cross-catalog reorganization, use CREATE VIEW AS SELECT then DROP VIEW","Test rename statements with Spark3Util.catalogAndIdentifier resolution before running in prod"],"tags":["spark","iceberg","view","rename"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}