{"record":{"id":"09aa64174a628f18","repo":"hibernate/hibernate-orm","slug":"cannot-add-primary-key-constraint-in-cloud-spanner","errorCode":null,"errorMessage":"Cannot add primary key constraint in Cloud Spanner.","messagePattern":"Cannot add primary key constraint in Cloud Spanner\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java","lineNumber":1215,"sourceCode":"\tpublic String getCurrentSchemaCommand() {\n\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\"No current schema syntax supported by \" + getClass().getName() );\n\t}\n\n\t@Override\n\tpublic SchemaNameResolver getSchemaNameResolver() {\n\t\t// Spanner does not have a notion of database name schemas, so return \"\".\n\t\treturn (connection, dialect) -> \"\";\n\t}\n\n\t@Override\n\tpublic boolean qualifyIndexName() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic String getAddPrimaryKeyConstraintString(String constraintName) {\n\t\tthrow new UnsupportedOperationException( \"Cannot add primary key constraint in Cloud Spanner.\" );\n\t}\n\n\t// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\t// Lock acquisition functions\n\n\t@Override\n\tpublic LockingSupport getLockingSupport() {\n\t\treturn SPANNER_LOCKING_SUPPORT;\n\t}\n\n\t@Override\n\tpublic LockingClauseStrategy getLockingClauseStrategy(QuerySpec querySpec, LockOptions lockOptions) {\n\t\tif ( getPessimisticLockStyle() != PessimisticLockStyle.CLAUSE || lockOptions == null ) {\n\t\t\treturn NON_CLAUSE_STRATEGY;\n\t\t}\n\t\tfinal var lockKind = PessimisticLockKind.interpret( lockOptions.getLockMode() );\n\t\tif ( lockKind == PessimisticLockKind.NONE ) {\n\t\t\treturn NON_CLAUSE_STRATEGY;","sourceCodeStart":1197,"sourceCodeEnd":1233,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java#L1197-L1233","documentation":"SpannerDialect.getAddPrimaryKeyConstraintString() throws UnsupportedOperationException because Cloud Spanner does not support ALTER TABLE ... ADD PRIMARY KEY — a primary key must be declared inline in CREATE TABLE. This guard fires whenever Hibernate's schema migrator tries to emit an add-PK alter statement, which on Spanner is always invalid. It signals a mapping/tooling path that expects additive DDL Spanner cannot perform.","triggerScenarios":"SchemaUpdate/hbm2ddl alter passes where an entity lacks an inline PK at create time or a constraint is added later (e.g. `@PrimaryKeyJoinColumn` handling or manual AlterTable commands); executing `new SchemaMigrator()` that generates 'alter table T add constraint ... primary key' against Spanner; secondary mapping mistakes where the key is not part of the CREATE TABLE definition.","commonSituations":"Running hibernate.hbm2ddl.auto=update on evolving Spanner schemas; migrations authored for databases that permit deferred PK addition; entities whose identifier is only discovered as a constraint by the tooling rather than declared in the table DDL.","solutions":["Ensure every @Entity declares @Id/@EmbeddedId so the CREATE TABLE generated for Spanner includes PRIMARY KEY inline, and recreate the table rather than altering it.","Replace hbm2ddl schema update with explicit migrations using Cloud Spanner DDL (CREATE TABLE ... PRIMARY KEY (...)) via the Spanner migration tooling.","If you are issuing alters programmatically, check `dialect.getAddPrimaryKeyConstraintString` availability or skip PK alters for Spanner before calling.","For existing tables needing a new key, create a new table, copy data, and swap — Spanner cannot retrofit a PK."],"exampleFix":"// before: migrate later with alter\n// alter table orders add constraint pk_orders primary key (id) -- Unsupported\n\n// after: key declared inline at create time\n@Entity\npublic class Order {\n  @Id String id;\n  ...\n}\n// -> create table orders (id string not null, ..., ) primary key (id)","handlingStrategy":"fallback","validationCode":"if (dialect instanceof SpannerDialect) {\n  // verify PK exists inline in table DDL instead of planning an ALTER\n  Table t = metadata.getEntityBinding(Entity.class.getName()).getTable();\n  if (t.getPrimaryKey() == null || t.getPrimaryKey().columnSpan() == 0) {\n    throw new IllegalStateException(\"Spanner requires PRIMARY KEY inline in CREATE TABLE for \" + t.getName());\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  migrator.performMigration(migration, executionOptions, target);\n} catch (UnsupportedOperationException e) {\n  if (e.getMessage().contains(\"primary key\")) { /* recreate table with inline PK and copy data instead */ }\n  throw e;\n}","preventionTips":["Always declare @Id/@EmbeddedId so CREATE TABLE includes PRIMARY KEY on Spanner.","Use explicit Spanner migrations rather than hbm2ddl update.","Remember PKs cannot be retrofitted on Spanner — plan keys up front."],"tags":["hibernate","cloud-spanner","dialect","ddl","primary-key","schema-migration","unsupportedoperationexception"],"backgroundTag":"alter-table-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}