{"record":{"id":"ab02e1e5a7e6461e","repo":"apache/iceberg","slug":"invalid-primary-key-s-column-s-is-not-a-phy-ab02e1","errorCode":null,"errorMessage":"Invalid primary key '%s'. Column '%s' is not a physical column.","messagePattern":"Invalid primary key '(.+?)'\\. Column '(.+?)' is not a physical column\\.","errorType":"validation","errorClass":"ValidationException","httpStatus":null,"severity":"error","filePath":"flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/FlinkSchemaUtil.java","lineNumber":365,"sourceCode":"\n    if (!duplicateColumns.isEmpty()) {\n      throw new ValidationException(\n          String.format(\n              \"Invalid primary key '%s'. A primary key must not contain duplicate columns. Found: %s\",\n              primaryKey.getName(), duplicateColumns));\n    }\n\n    for (String columnName : primaryKey.getColumns()) {\n      Column column = columnsByNameLookup.get(columnName);\n      if (column == null) {\n        throw new ValidationException(\n            String.format(\n                \"Invalid primary key '%s'. Column '%s' does not exist.\",\n                primaryKey.getName(), columnName));\n      }\n\n      if (!column.isPhysical()) {\n        throw new ValidationException(\n            String.format(\n                \"Invalid primary key '%s'. Column '%s' is not a physical column.\",\n                primaryKey.getName(), columnName));\n      }\n\n      final LogicalType columnType = column.getDataType().getLogicalType();\n      if (columnType.isNullable()) {\n        throw new ValidationException(\n            String.format(\n                \"Invalid primary key '%s'. Column '%s' is nullable.\",\n                primaryKey.getName(), columnName));\n      }\n    }\n  }\n}\n","sourceCodeStart":347,"sourceCodeEnd":381,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/FlinkSchemaUtil.java#L347-L381","documentation":"validatePrimaryKey throws this ValidationException when a primary-key column exists but is not a physical column (e.g. a computed or metadata column). Iceberg primary keys (identifier fields) must map to physical table columns, so non-physical columns are rejected during toResolvedSchema conversion.","triggerScenarios":"Declaring PRIMARY KEY over a computed column (`name AS expr`) or metadata column (`ts TIMESTAMP(3) WITH LOCAL TIME ZONE METADATA FROM ...`) in Flink DDL for an Iceberg table.","commonSituations":"Developers model derived keys as computed columns, or migrate DDL from other connectors that allow virtual key columns; Iceberg rejects them because identifiers must be stored fields.","solutions":["Move the key expression into a real physical column and use that column in PRIMARY KEY.","Drop the computed column from the key and pick an underlying physical column.","If the expression is needed, materialize it at write time via an INSERT INTO ... SELECT that computes the column."],"exampleFix":"// before\nCREATE TABLE t (\n  id BIGINT,\n  name AS upper(id || '') ,\n  PRIMARY KEY (name) NOT ENFORCED\n);\n// after\nCREATE TABLE t (\n  id BIGINT,\n  key_name STRING,\n  PRIMARY KEY (id) NOT ENFORCED\n);","handlingStrategy":"validation","validationCode":"for (String name : pkColumns) {\n  Column c = resolvedSchema.getColumn(name).orElseThrow();\n  Preconditions.checkArgument(c.isPhysical(), \"PK column %s must be physical\", name);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only reference physical columns in PRIMARY KEY clauses.","Avoid computed/metadata columns as keys; materialize them instead."],"tags":["flink","primary-key","computed-column"],"backgroundTag":"schema-validation-failed","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"}