The GitHub Actions job "Coverage" on grails-core.git/8.0.x has succeeded.
Run started by GitHub user borinquenkid (triggered by borinquenkid).

Head commit for run:
bfa709384d5e03de3a4bd77c3e12e9b80d37e4e2 / Walter B Duque de Estrada 
<[email protected]>
Hibernate 7: name nested composite foreign key columns in the referenced key's 
order (#16539)

* fix: name the foreign key columns of a nested composite identifier in the 
referenced key's order

A foreign key to an entity whose composite identifier contains a to-one to 
another composite
entity (leaf -> middle(grandParent, name) -> grandParent(name String, 
luckyNumber Integer))
named the expanded columns in the mapped order of the nested parts, while 
Hibernate types them
positionally from the referenced key, whose nested parts are sorted by property 
name. The leaf
table got cfk_middle_grand_parent_name INTEGER and 
cfk_middle_grand_parent_lucky_number
VARCHAR. Data round-trips (the order is consistent positionally) but the 
columns are
mislabelled for schema tooling and native SQL. The nested parts are now sorted 
by name.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* test: prove the composite foreign key column order against a real mapping

The earlier assertions checked each column's name and type, which an
integer paired with a string would break but two same-typed parts written in
the wrong order would not. The Hibernate-backed spec now reads the imported
keys from JDBC metadata in KEY_SEQ order and asserts each foreign key
column against the referenced primary key column, including cfk_middle_name,
for parts declared out of name order and for the nested chain. It fails when
the sort in CompositeIdentifierToManyToOneBinder is removed. The mock-based
binder spec no longer claims the ordering.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* fix: permute the columns of a composite identifier by part, not by property

A foreign key into an entity whose composite identifier has a part that
spans several columns (a to-one to another composite entity) was sorted
with Hibernate's property permutation applied to the column list. That only
lines up when every part has one column, so a nested part declared after a
plain part that sorts before it left the foreign key columns pointing at the
wrong primary key columns and the row failed to save. The permutation is now
expanded to move each part with its whole column span, in both
sortOrIndexForeignKeyColumns and getReferencedIdentifierColumns.

Also drops the unused Transactional import from the spec.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* test: reference a composite key whose multi-column part sits between two 
plain parts

The two PrbSpanLeaf features checked the same PrbLeaf -> PrbMiddle key as the
features before them: composite('zed', 'middle', 'ace') only laid out
PrbSpanLeaf's own primary key, which Hibernate orders itself and nothing
references, so the column-span permutation of a multi-column part between two
plain parts never ran. PrbHubRef and the join table of PrbHub's unidirectional
one-to-many now reference such a key, so both the many-to-one and the
collection paths assert the KEY_SEQ pairs, and a hub with a tag and a
reference is saved and reloaded. With the plain property permutation the
pairs come out crossed and the save fails.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix: sort a composite foreign key once the identifier it references is bound

CompositeIdentifierToManyToOneBinder sorted a key against the referenced
composite identifier while binding the property. When the referenced entity
had not been bound yet, the columns kept their declared order, got sequential
type indexes and were marked sorted, so Hibernate's own ToOne.sortProperties()
skipped them too, and PropertyFromValueCreator created Hibernate's foreign key,
which pairs the columns by position. Whether a key came out right therefore
depended on the registration order of the entities, which an application does
not choose: PrbGrand, PrbLeaf, PrbMiddle left the leaf's key in declared order
while its values were bound in key order, and saving a leaf failed with a
referential integrity violation; a middle registered before its grand parent
crossed the alpha and zeta columns of every key referencing it.

The binder now aligns a key only once the referenced identifier is bound, down
to the identifiers its to-one parts reference. Otherwise it registers a
CompositeForeignKeySecondPass through InFlightMetadataCollector.addSecondPass,
the mechanism the binder already uses for collections, which Hibernate runs
before it compiles the foreign keys; this mirrors ToOne.sortProperties(), which
resolves the referenced identifier lazily from the second passes. The alignment
is idempotent and aligns the pending to-one parts of the referenced identifier
first, so a key referencing a middle whose own key is still pending sees the
middle's sorted columns; a key bound during the second passes is aligned at
once. PropertyFromValueCreator leaves the foreign key of a many-to-one to a
composite identifier to the composite binder, which pairs each column with the
identifier column it references; for a referenced entity registered first the
binder had already disabled Hibernate's key, so those keys are unchanged.

CompositeForeignKeyRegistrationOrderSpec boots the entities of
CompositeForeignKeyColumnTypesSpec in five registration orders and asserts the
KEY_SEQ pairs and a save and reload; four of them failed before this change.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix: leave the foreign key of a bidirectional one-to-many to its to-one side

The inverse collection's key added a second foreign key for the same
association. Collection.createAllKeys(), run from the collection's second
pass, creates a positional key for the key columns under the same implicit
name as the explicit key the composite binder creates for the to-one side:
the name is hashed from the two tables and the column names, not from the
referenced columns or their order. Both keys were in the metadata in every
registration order; the schema export created the first one registered and
logged "Constraint already exists" for the other. Registered as PrbMiddle,
PrbLeaf, PrbGrand, the collection's second pass ran before the to-one's
deferred alignment, so the positional key reached the database, paired the
wrong columns and saving a leaf failed with a referential integrity
violation. A bidirectional hasMany into the PrbHub layout failed the same
way registered as hub, child, grand.

BidirectionalOneToManyLinker.link now disables the key's foreign key, so
the to-one side's key is the only key of the association. The to-one side
always creates one: ToOne.createForeignKey(), called from
PropertyFromValueCreator, for a simple identifier, and the composite binder,
with the referenced identifier columns, for a composite one. This applies
to every inverse one-to-many rather than only to composite owners because
for a simple identifier the two keys were one and the same ForeignKey,
deduplicated by Table.createForeignKey, so the key that remains keeps the
implicit name and columns an existing schema has.

CompositeForeignKeyRegistrationOrderSpec now runs every registration order
(6 of the chain, 24 of the hub set, 6 of grand, hub and the new bidirectional
PrbHubChild) and asserts that no foreign key name appears twice in the
Hibernate metadata; a simple-identifier pair pins the unchanged implicit
name and columns of its single key. Before the fix the two orders above
failed with the integrity violation and every other composite order failed
the duplicate-name assertion.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* test: pin the empty inverse hasMany of a composite identifier declared out of 
name order

An inverse hasMany on an entity whose composite identifier is not declared
in name order loads no elements: BidirectionalOneToManyLinker.link copies
the to-one's columns, which are already in key order, and key.sortProperties()
applies the owner identifier's permutation a second time, so the collection
is loaded with the identifier values bound to the wrong columns. Declared in
name order, the permutation is the identity and the collection loads.

This predates the composite foreign key fixes and does not need a nested
composite, so it is left to a follow-up issue. The spec records the repro as
a @PendingFeature next to the name-order variant that passes, so the fix
will surface as "passes unexpectedly" and the annotation is removed with it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

---------

Co-authored-by: Claude Sonnet 5.5 <[email protected]>

Report URL: https://github.com/apache/grails-core/actions/runs/38078097353

With regards,
GitHub Actions via GitBox

Reply via email to