{"record":{"id":"fd8d96f37bfe9076","repo":"apache/iceberg","slug":"cannot-set-identifier-fields-in-non-iceberg-table-fd8d96","errorCode":null,"errorMessage":"Cannot set identifier fields in non-Iceberg table: $table","messagePattern":"Cannot set identifier fields in 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/SetIdentifierFieldsExec.scala","lineNumber":42,"sourceCode":"import org.apache.spark.sql.connector.catalog.Identifier\nimport org.apache.spark.sql.connector.catalog.TableCatalog\nimport scala.jdk.CollectionConverters._\n\ncase class SetIdentifierFieldsExec(catalog: TableCatalog, ident: Identifier, fields: Seq[String])\n    extends LeafV2CommandExec {\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        iceberg.table\n          .updateSchema()\n          .setIdentifierFields(fields.asJava)\n          .commit();\n      case table =>\n        throw new UnsupportedOperationException(\n          s\"Cannot set identifier fields in non-Iceberg table: $table\")\n    }\n\n    Nil\n  }\n\n  override def simpleString(maxFields: Int): String = {\n    s\"SetIdentifierFields ${catalog.name}.${ident.quoted} (${fields.quoted})\";\n  }\n}\n","sourceCodeStart":24,"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/SetIdentifierFieldsExec.scala#L24-L53","documentation":"SetIdentifierFieldsExec throws this when ALTER TABLE ... SET IDENTIFIER FIELDS is executed against a non-Iceberg table. Identifier fields are declared through Iceberg's UpdateSchema API; other V2 catalogs have no equivalent, so the executor throws UnsupportedOperationException when the table is not a SparkTable wrapping an Iceberg table.","triggerScenarios":"Running `ALTER TABLE ... SET IDENTIFIER FIELDS id` where the resolved table comes from a non-Iceberg DataSourceV2 catalog.","commonSituations":"Upsert-key setup scripts run against Delta tables; wrong catalog prefix; environments where the Iceberg extensions parse the SQL but the table is not Iceberg.","solutions":["Target the Iceberg catalog explicitly in the ALTER TABLE statement","Confirm the column exists in the Iceberg schema before setting it as an identifier field","Remove identifier-field DDL from non-Iceberg pipelines, or migrate the tables to Iceberg"],"exampleFix":"// before\nALTER TABLE spark_catalog.default.t SET IDENTIFIER FIELDS id;\n// after\nALTER TABLE iceberg_catalog.default.t SET IDENTIFIER FIELDS id;","handlingStrategy":"validation","validationCode":"val loaded = catalog.loadTable(ident)\nrequire(loaded.isInstanceOf[org.apache.iceberg.spark.source.SparkTable], s\"SET IDENTIFIER FIELDS 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 SET IDENTIFIER FIELDS id\") } catch { case e: UnsupportedOperationException if e.getMessage.contains(\"non-Iceberg\") => throw new IllegalStateException(s\"Upsert keys need an Iceberg table: $ident\", e) }","preventionTips":["Target the Iceberg catalog explicitly in DDL","Validate schema/columns before declaring identifier fields","Restrict Iceberg-only DDL to Iceberg catalogs"],"tags":["spark","spark-extensions","unsupported-operation","identifier-fields"],"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"}