{"record":{"id":"18a72158fd07b95a","repo":"apache/iceberg","slug":"cannot-drop-tag-on-non-iceberg-table-table-18a721","errorCode":null,"errorMessage":"Cannot drop tag on non-Iceberg table: $table","messagePattern":"Cannot drop tag on non-Iceberg table: \\$table","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.1/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DropTagExec.scala","lineNumber":43,"sourceCode":"import org.apache.spark.sql.connector.catalog.TableCatalog\n\ncase class DropTagExec(catalog: TableCatalog, ident: Identifier, tag: String, ifExists: Boolean)\n    extends LeafV2CommandExec {\n\n  import org.apache.spark.sql.connector.catalog.CatalogV2Implicits._\n\n  override lazy val output: Seq[Attribute] = Nil\n\n  override protected def run(): Seq[InternalRow] = {\n    catalog.loadTable(ident) match {\n      case iceberg: SparkTable =>\n        val ref = iceberg.table().refs().get(tag)\n        if (ref != null || !ifExists) {\n          iceberg.table().manageSnapshots().removeTag(tag).commit()\n        }\n\n      case table =>\n        throw new UnsupportedOperationException(s\"Cannot drop tag on non-Iceberg table: $table\")\n    }\n\n    Nil\n  }\n\n  override def simpleString(maxFields: Int): String = {\n    s\"DropTag tag: ${tag} for table: ${ident.quoted}\"\n  }\n}\n","sourceCodeStart":25,"sourceCodeEnd":53,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.1/spark-extensions/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DropTagExec.scala#L25-L53","documentation":"DropTagExec throws this when ALTER TABLE ... DROP TAG is executed on a table that is not an Iceberg SparkTable. Tags (named snapshot references) exist only in Iceberg's ref model, so the extensions executor unwraps the table and throws UnsupportedOperationException for any other implementation. Like branches, tags cannot be dropped on foreign catalogs.","triggerScenarios":"Running `ALTER TABLE ... DROP TAG <tag>` where the resolved table comes from a non-Iceberg catalog, missing the `iceberg: SparkTable` match in run().","commonSituations":"Snapshot-retention scripts that manage tags applied to the wrong tables; catalogs switched after a migration; users confusing V2 catalog capabilities with Iceberg features.","solutions":["Ensure the target is an Iceberg table and qualify it with the Iceberg catalog name","List existing tags first (e.g. via metadata table or SELECT * FROM table.refs) to confirm tag management applies","Remove tag DDL from pipelines handling non-Iceberg tables, or migrate those tables to Iceberg"],"exampleFix":"// before\nALTER TABLE other_catalog.db.t DROP TAG etl_snapshot;\n// after\nALTER TABLE iceberg_catalog.db.t DROP TAG etl_snapshot;","handlingStrategy":"validation","validationCode":"val loaded = catalog.loadTable(ident)\nrequire(loaded.isInstanceOf[org.apache.iceberg.spark.source.SparkTable], s\"Tag management requires Iceberg: $ident\")","typeGuard":"def isIcebergTable(t: org.apache.spark.sql.connector.catalog.Table): Boolean = t.isInstanceOf[org.apache.iceberg.spark.source.SparkTable]","tryCatchPattern":"try { spark.sql(s\"ALTER TABLE $ident DROP TAG $tag\") } catch { case e: UnsupportedOperationException if e.getMessage.contains(\"non-Iceberg\") => log.warn(s\"Skipping tag drop on non-Iceberg table $ident\") }","preventionTips":["Only run snapshot-reference DDL against Iceberg-qualified table names","Inspect table.refs / metadata before tag operations","Keep Iceberg maintenance jobs pointed at the Iceberg catalog explicitly"],"tags":["spark","spark-extensions","unsupported-operation","tag-management"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}