jdaugherty commented on code in PR #16720:
URL: https://github.com/apache/grails-core/pull/16720#discussion_r4352212930


##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/CreateKeyForProps.java:
##########
@@ -70,9 +72,15 @@ public void createKeyForProps(HibernatePersistentProperty 
grailsProp, String pat
                     throw new MappingException(
                             owner.getJavaClass().getName() + " references an 
unknown property " + propertyName);
                 }
-                String otherColumnName =
-                        
columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(otherProp, 
path, null);
-                keyList.add(new Column(otherColumnName));
+                List<ColumnConfig> columns = 
otherProp.getHibernateMappedForm().getColumns();
+                if (columns.isEmpty()) {

Review Comment:
   This only resolves the right columns if the association is already bound. 
For a composite-id association without a `column` mapping, this list stays 
empty until 
`CompositeIdentifierToManyToOneBinder.bindCompositeIdentifierToManyToOne` adds 
the generated configs (`<parent table>_code`, `<parent table>_revision`) while 
it binds the association. Properties bind in declaration order. When the 
constrained property is declared first, this code therefore falls back to the 
single default name `parent_id`, and no column has that name:
   
   ```groovy
   @Entity
   class EnumUniqueDeclaredFirstChild implements 
HibernateEntity<EnumUniqueDeclaredFirstChild> {
       EnumUniqueCompositeState state
       EnumUniqueCompositeParent parent
   
       static mapping = {
           state unique: 'parent'
       }
   }
   ```
   
   On this head the `create table` ends with `unique (parent_id, state)`. With 
`hibernate.hbm2ddl.halt_on_error`, the datastore fails to start with `Column 
"PARENT_ID" not found`. With the default setting, the datastore starts without 
the table, and the first save throws `Table ... not found`. If the two 
declarations are swapped, the mapping works and the key is `(<parent 
table>_code, <parent table>_revision, state)`. 8.0.x fails the same way on this 
mapping. Enum binding did not create group keys before #16533, so for an enum 
this is still part of the #16713 regression. A `String` member declared first 
fails the same way too, on 8.0.x as well.
   
   The result shouldn't depend on declaration order. I see two possible routes:
   - Resolve an unbound to-one member's columns from the referenced composite 
identity, using the naming that `CompositeIdentifierToManyToOneBinder` applies 
(shared, not copied).
   - Build the group key once every property of the entity is bound, from the 
bound `Column`s of the member's `Property`. This would also keep their quoting.
   
   Could you fix this and add the declared-first case, for both an enum and a 
`String`, to `EnumUniqueGroupAssociationSpec`?



##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/CreateKeyForProps.java:
##########
@@ -70,9 +72,15 @@ public void createKeyForProps(HibernatePersistentProperty 
grailsProp, String pat
                     throw new MappingException(
                             owner.getJavaClass().getName() + " references an 
unknown property " + propertyName);
                 }
-                String otherColumnName =
-                        
columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(otherProp, 
path, null);
-                keyList.add(new Column(otherColumnName));
+                List<ColumnConfig> columns = 
otherProp.getHibernateMappedForm().getColumns();
+                if (columns.isEmpty()) {
+                    columns = Collections.singletonList(null);
+                }
+                for (ColumnConfig column : columns) {
+                    String otherColumnName =
+                            
columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(otherProp, 
path, column);
+                    keyList.add(new Column(otherColumnName));
+                }
             }
 
             uniqueKeyForColumnsCreator.createUniqueKeyForColumns(table, 
keyList);

Review Comment:
   Non-blocking, and older than this PR. This key is built once per bound 
column of the constrained property: `ColumnBinder.bindColumn` calls 
`createKeyForProps` for each column. As a result, the reverse mapping, where 
the multi-column association is the constrained property, still produces the 
wrong keys:
   
   ```groovy
   static mapping = {
       parent(unique: 'state') {
           column name: 'parent_code'
           column name: 'parent_revision'
       }
   }
   ```
   
   This gives two keys, `(parent_code, state)` and `(parent_revision, state)`. 
A child is then rejected when its parent shares only `code` with the parent of 
an existing child that has the same state. Without the `column` mapping, the 
datastore fails to start with `MappingException: UniqueKey UK… already exists`. 
8.0.x behaves the same. #16695 handles this shape for a composite user type by 
creating one key across all of the property's columns (`createKeyForColumns`), 
and a multi-column to-one could do the same. A follow-up issue is fine if you'd 
rather keep this PR to #16713.



-- 
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