Skip to content

data-structures: a table may carry more than one unique constraint (#7343) - #7356

Open
delchev wants to merge 1 commit into
masterfrom
issue-7343-table-constraint-many-to-one
Open

data-structures: a table may carry more than one unique constraint (#7343)#7356
delchev wants to merge 1 commit into
masterfrom
issue-7343-table-constraint-many-to-one

Conversation

@delchev

@delchev delchev commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Cause

TableConstraint - the @MappedSuperclass behind TableConstraintUnique / …ForeignKey / …Check / …PrimaryKey - joined its owning TableConstraints with @OneToOne. The relation is genuinely many-to-one: one table's TableConstraints owns a list of uniques, a list of foreign keys and a list of checks. @OneToOne on the owning side makes Hibernate put a unique index on the join column, so DIRIGIBLE_DATA_TABLE_UNIQUES.CONSTRAINTS_ID could hold at most one row per table.

A table declaring a second unique key therefore failed its second insert with 23505, SchemasSynchronizer failed the whole .schema, and on a fresh instance none of that module's tables were ever created. Every step in front of that was green - the model, the generated .schema, the emission audit, the publish - and the declared key simply constrained nothing.

The change

  • TableConstraint.constraints is @ManyToOne. TableConstraints.primaryKey stays the @OneToOne(mappedBy = "constraints") it is (a table has one primary key); Hibernate maps that inverse side against a @ManyToOne owner without complaint, and its own UK_DIRIGIBLE_DATA_TABLE_PRIMARYKEYS_CONSTRAINTS_ID is left in place, being correct there.
  • Five Liquibase changesets drop what the old mapping materialized. Three name the constraints this changelog created on …_UNIQUES, …_FOREIGNKEYS and …_CHECKS, each guarded by uniqueConstraintExists + MARK_RAN. Two more sweep a deployment bootstrapped by hbm2ddl before this changelog existed, whose index carries the name Hibernate generated for it and which a drop-by-name cannot reach: one for H2 (EXECUTE IMMEDIATE over INFORMATION_SCHEMA) and one for PostgreSQL (a DO block over information_schema), both keyed on the column rather than the name and both no-ops where nothing is left, so the changelog stays idempotent (SystemChangelogIdempotencyTest applies it twice). MSSQL is deliberately not swept - it has no CI leg to verify the statement against, and untested DDL running at every customer's startup is worse than the stated omission.

Verification

  • SchemaTwoUniqueConstraintsIT (new) publishes a .schema with two unique keys on one table, asserts both reach the database's index catalog, and asserts each one refuses its own duplicate by the name in the refusal message. Run green on H2 and on PostgreSQL (both datasource and SystemDB pointed at a local PostgreSQL, as the CI leg does).
  • The same IT run against the unchanged mapping and changelog reproduces the report exactly: 32 occurrences of the duplicate-key error in the log, the .schema marked failed, and the table not created at all.
  • SchemaTemplateUniqueConstraintIT, SchemaTemplateForeignKeyIT, SchemaClobColumnIT, SchemaRepublishTypeToleranceIT, IntentCrossModelUniqueIT, EdmUniqueKeyRoundTripIT - green.
  • Unit suites of data-structures, core-liquibase and build/application (which carries SystemSchemaChangelogCoverageTest) - green.
  • mvn formatter:validate with the cache wiped, and the release-profile javadoc build on data-structures - green.

Not verified: MSSQL (no CI leg), and the legacy sweeps were exercised as raw SQL against H2 and PostgreSQL rather than against a genuinely pre-changelog database.

Fixes #7343

🤖 Generated with Claude Code

…7343)

TableConstraint, the @MappedSuperclass behind the unique / foreign-key /
check / primary-key constraints, joined its owning TableConstraints with
@OnetoOne. The relation is many-to-one - one table's TableConstraints owns
a LIST of uniques, a list of foreign keys and a list of checks - and the
@OnetoOne made Hibernate put a unique index on the join column, so each of
those tables could hold at most one row per table.

A table declaring a second unique key therefore failed its second insert
with a duplicate key, SchemasSynchronizer failed the whole .schema, and on
a fresh instance none of that module's tables were ever created. Nothing in
front of it said so: the model, the generated .schema and the publish were
all green, and the key the author declared simply constrained nothing.

The mapping is @manytoone now. The index it created is already materialized
on every deployed instance, so it is dropped by three changesets naming the
constraints this changelog created, plus an H2 and a PostgreSQL sweep for a
deployment bootstrapped by hbm2ddl before the changelog existed, whose index
carries the name Hibernate generated for it. Both sweeps are no-ops where
nothing is left, so the changelog stays idempotent.

SchemaTwoUniqueConstraintsIT publishes a .schema with two unique keys on one
table and asserts both reach the database and each refuses its own duplicate.
It fails on master with the reported 23505 and no table created at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant