borinquenkid opened a new issue, #16699:
URL: https://github.com/apache/grails-core/issues/16699

   ### Expected Behavior
   
   An owner with two unidirectional `hasMany` to the same class that share one 
join table (no `joinTable` mapping)
   
   ```groovy
   class Item {
       static hasMany = [roles: Role, denyRoles: Role]
   }
   ```
   
   writes a role added to either collection to the join table, or fails the 
save with an error. `failOnError: true` must not pass while rows are dropped.
   
   ### Actual Behaviour
   
   On Hibernate 7 the classic domain binder keeps the rule from #10811 in 
`CollectionKeyColumnUpdater`: when the owner has more than one unidirectional 
to-many sharing the collection's join table, the key of each collection is made 
nullable **and not updatable**. Hibernate 7's collection persister writes no 
rows for a collection whose key is not updatable, so `addToRoles` and 
`addToDenyRoles` are both dropped silently and the collections reload empty, 
even with `failOnError: true`.
   
   #16686 keeps the key updatable for collections with join tables of their own 
(the reporter's first shape in #16665) and leaves this shared-table case as it 
was. This issue is the follow-up it points to.
   
   ### Analysis
   
   - A local probe by @jdaugherty on #16686 made the key always updatable: both 
collections of `hasMany = [roles: Role, denyRoles: Role]` were written to the 
shared table and read back correctly, and the `hasmany` package and the specs 
of #16686 still passed. So the #10811 rule may not be needed on Hibernate 7 at 
all, as the reporter suggested in #16665.
   - The native domain generator of the 9.x branches already treats the rule as 
a defect rather than a rule to copy and always makes the key updatable (see the 
javadoc of `GrailsDomainGenerator.collectionKeyFacets`), so dropping it on 8.x 
brings the classic binder in line with native binding.
   - What needs a look before dropping it: the shared join table has three 
columns (`item_id`, `item_roles_id`, `item_deny_roles_id`) but the classic 
binder's unique key covers only the **first** collection's key column with the 
element column, `UNIQUE (item_roles_id, item_id)`. Rows of the second 
collection leave `item_roles_id` null. H2 creates the key `NULLS DISTINCT`, but 
databases that treat nulls as equal in a unique constraint (SQL Server, for 
example) would reject two `denyRoles` rows for the same role from different 
owners. Either the unique key has to cover every key column of the table, or 
each collection needs a unique key of its own, or the binder should give each 
collection its own join table by default.
   
   ### Steps To Reproduce
   
   Register the `Item`/`Role` classes above against the Hibernate 7 datastore 
of 8.0.x, add a role to each collection, flush, clear the session and reload: 
both collections are empty. `MultipleUnidirectionalHasManySpec` (from #16686) 
has the per-collection join table shape; the shared-table shape is 
`CKCUSharedTableOwner` in `CollectionKeyColumnUpdaterSpec`, which currently 
asserts the key is not updatable.
   
   ### Environment Information
   
   - Grails 8.0.x, `grails-data-hibernate7`, classic domain binding.
   
   Related: #16665, #16686, #10811.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to