Repository navigation
Hibernate 7: write the rows of lists and maps that share a join table - #16756
borinquenkid wants to merge 3 commits into
Conversation
…able Two unidirectional hasMany lists (or maps) to the same class that share a join table could not be saved (#16700). Hibernate keys the join table of a list or a map with a primary key over its key and index columns (IndexedCollection.createPrimaryKey), which the next collection of a shared table replaces with its own and which makes those columns not null; a row of one list leaves the other list's columns null, so every insert but the last list's failed with "NULL not allowed for column ROLES_IDX". SharedJoinTableUniqueKeyBinder now gives each list or map of a shared table nullable key and index columns, drops that primary key, and a uniqueness of its own over its key and index columns, in the shape SharedJoinTableUniqueness gives sets: a unique key per list, or on Oracle and Db2 LUW one key over the columns of every list that shares a column with it. A position or a map key is taken once, while an element may repeat. SharedJoinTableUniqueness on those databases creates the key when there is none to widen, as a list has no implicit unique key. SharedJoinTableListHibernate7Spec: both shapes of shared table keep their lists in order; removing from one list keeps the other; an element may repeat and clearing one list keeps the other; two maps keep their entries; and on H2, PostgreSQL, MySQL, MariaDB, Oracle and Db2 (CI) both shapes are written and read, and a second row at a taken position is rejected (without the uniqueness every database accepts it). SharedJoinTableUniqueKeyBinderSpec: the DDL of two shared lists on H2, Oracle, Db2 LUW, IBM i and SQL Server. Docs: the upgrade note, with the migration an older Grails 8 schema needs, and the one-to-many chapter. Claude-Session: https://claude.ai/code/session_01MDHn7fC3hsGJ27vFHD2o1p
|
CI run 40132431052 is green, with its tests run ( |
jamesfredley
left a comment
There was a problem hiding this comment.
Not approving. Lists and maps that share a table can be inserted, but two lists that also share the element column still load and delete each other's rows.
…in tables SharedJoinTableUniqueKeyBinder restricts each list and map of a shared join table by its index column, and unshared element columns if any, ensuring that lists and maps sharing both foreign-key and element columns isolate their rows during reads, single-element removals, and bulk deletes. Updated documentation in the one-to-many chapter and the 8.0 upgrade guide.
|
Resolved the row collision when collections share both foreign-key and element columns:
|
jamesfredley
left a comment
There was a problem hiding this comment.
The earlier comment is fixed. Each shared list is restricted by its own index column, clearing one keeps the other, and loading no longer selects the other list's rows. That thread is resolved. Not approving yet, only because the migration steps leave an existing table unable to take rows from the other list.
…n table migration Clarify that upgrading an existing Grails 8 schema with lists or maps sharing a join table requires making both the owner-key and index columns nullable after dropping the primary key.
✅ All tests passed ✅🏷️ Commit: a99fd4b Learn more about TestLens at testlens.app/docs. |
jamesfredley
left a comment
There was a problem hiding this comment.
Approving. The upgrade note now says to drop the primary key and make the owner-key and index columns nullable, which is what the binder does. That comment is resolved.
Fixes #16700. Stacked on #16750 (it builds on the shared join table uniqueness of #16701 and the Db2 rules of #16750); retarget to
8.0.xonce those merge.Problem
Two unidirectional
hasManylists (or maps) to the same class that share one join table could not be saved:Hibernate keys the join table of a list or a map with a primary key over its key and index columns (
IndexedCollection.createPrimaryKey). On a shared table the next collection replaces it with its own, and it makes those columnsNOT NULL, while a row of one list leaves the other list's columns null.Change
SharedJoinTableUniqueKeyBinder, for each list or map of a shared table: makes its key and index columns nullable, drops that primary key, and gives it a uniqueness of its own over its key and index columns throughSharedJoinTableUniqueness, the same per-database rules sets get:create unique where not null indexper listA position or a map key is taken once; an element may repeat, as a list allows. On Oracle and Db2 LUW
SharedJoinTableUniquenessnow creates the key when there is none to widen, since a list has no implicit unique key (a set always has one, so sets are unchanged).Tests
SharedJoinTableListHibernate7Spec(new): both shapes keep their lists in order; removing from one list keeps the other and the order of the rest; an element may repeat and clearing one list keeps the other; two maps keep their entries. A container feature writes and reads both shapes on H2, PostgreSQL, MySQL, MariaDB, Oracle and Db2 (CI, amd64), and checks that a second row at a taken position is rejected; with the uniqueness removed every database accepts it.SharedJoinTableUniqueKeyBinderSpec: the DDL of two shared lists on H2, Oracle, Db2 LUW, IBM i and SQL Server (55/0).grails-data-hibernate7-coresuite locally: 4293 tests, 0 failures (the sharedDatabaseContainersof Hibernate 7: support Db2 in unique key null handling, regular expressions and native SQL reading #16750 makes the Oracle rows pass locally too).Not covered: a set that shares a join table with a list.
Docs
The shared join table note of the 8.0.x upgrade guide, with the migration a schema created by an earlier Grails 8 release needs (drop the primary key, make the index columns nullable;
dbCreate: updatechanges neither), and the one-to-many chapter.https://claude.ai/code/session_01MDHn7fC3hsGJ27vFHD2o1p