HHH-16370 Using MapKey on ManyToMany leads to wrong insert SQL

This commit is contained in:
Andrea Boriero 2023-05-04 15:36:24 +02:00 committed by Christian Beikov
parent 2b3450ecc7
commit c201a44291
4 changed files with 100 additions and 44 deletions

View File

@ -507,7 +507,7 @@ public class MapBinder extends CollectionBinder {
private static void addSelectable(SimpleValue targetValue, Selectable selectable) {
if ( selectable instanceof Column ) {
targetValue.addColumn( ( (Column) selectable).clone() );
targetValue.addColumn( ( (Column) selectable).clone(), false, false );
}
else if ( selectable instanceof Formula ) {
targetValue.addFormula( new Formula( ( (Formula) selectable).getFormula() ) );

View File

@ -21,6 +21,7 @@ import org.hibernate.mapping.Map;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
@ -29,6 +30,7 @@ import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
@ -79,6 +81,8 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
LazyTableGroup.ParentTableGroupUseChecker {
private ForeignKeyDescriptor foreignKey;
private ValuedModelPart fkTargetModelPart;
private boolean[] isInsertable;
private boolean[] isUpdatable;
public ManyToManyCollectionPart(
Nature nature,
@ -122,9 +126,6 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
// This is not possible for one-to-many associations because we need to create the target table group eagerly,
// to preserve the cardinality. Also, the OneToManyTableGroup has no reference to the parent table group
if ( getTargetKeyPropertyNames().contains( name ) ) {
if ( fkTargetModelPart instanceof ToOneAttributeMapping ) {
return ( (ToOneAttributeMapping) fkTargetModelPart ).findSubPart( name, targetType );
}
final ModelPart keyPart = foreignKey.getKeyPart();
if ( keyPart instanceof EmbeddableValuedModelPart && keyPart instanceof VirtualModelPart ) {
return ( (ModelPartContainer) keyPart ).findSubPart( name, targetType );
@ -161,6 +162,32 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
return getJdbcTypeCount();
}
@Override
public void forEachInsertable(SelectableConsumer consumer) {
forEachSelectable(
(selectionIndex, selectableMapping) -> {
if ( !foreignKey.getKeyPart().getSelectable( selectionIndex ).isInsertable() ) {
return;
}
consumer.accept( selectionIndex, selectableMapping );
}
);
}
@Override
public void forEachUpdatable(SelectableConsumer consumer) {
forEachSelectable(
(selectionIndex, selectableMapping) -> {
if ( !foreignKey.getKeyPart().getSelectable( selectionIndex ).isUpdateable() ) {
return;
}
consumer.accept( selectionIndex, selectableMapping );
}
);
}
@Override
public <X, Y> int decompose(
Object domainValue,
@ -216,6 +243,9 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
@Override
public ModelPart getKeyTargetMatchPart() {
if ( fkTargetModelPart instanceof ToOneAttributeMapping ) {
return foreignKey.getKeyPart();
}
return fkTargetModelPart;
}
@ -418,8 +448,9 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
}
if ( getNature() == Nature.ELEMENT ) {
final Value element = bootCollectionDescriptor.getElement();
foreignKey = createForeignKeyDescriptor(
bootCollectionDescriptor.getElement(),
element,
(EntityType) collectionDescriptor.getElementType(),
fkTargetModelPart,
creationProcess,
@ -428,8 +459,9 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
}
else {
assert bootCollectionDescriptor.isIndexed();
final Value index = ( (IndexedCollection) bootCollectionDescriptor ).getIndex();
foreignKey = createForeignKeyDescriptor(
( (IndexedCollection) bootCollectionDescriptor ).getIndex(),
index,
(EntityType) collectionDescriptor.getIndexType(),
fkTargetModelPart,
creationProcess,
@ -565,7 +597,11 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
if ( toOneAttributeMapping.getForeignKeyDescriptor() == null ) {
throw new IllegalStateException( "Not yet ready: " + toOneAttributeMapping );
}
return toOneAttributeMapping.getForeignKeyDescriptor();
return determineForeignKey(
toOneAttributeMapping.getForeignKeyDescriptor(),
fkBootDescriptorSource,
creationProcess
);
}
if ( fkTargetModelPart instanceof ManyToManyCollectionPart ) {
@ -574,7 +610,11 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
if ( targetModelPart.getForeignKeyDescriptor() == null ) {
throw new IllegalStateException( "Not yet ready: " + targetModelPart );
}
return targetModelPart.getForeignKeyDescriptor();
return determineForeignKey(
targetModelPart.getForeignKeyDescriptor(),
fkBootDescriptorSource,
creationProcess
);
}
final String collectionTableName = ( (CollectionMutationTarget) getCollectionDescriptor() ).getCollectionTableMapping().getTableName();
@ -609,6 +649,46 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
);
}
private ForeignKeyDescriptor determineForeignKey(
ForeignKeyDescriptor foreignKeyDescriptor,
Value fkBootDescriptorSource,
MappingModelCreationProcess creationProcess) {
final int selectableCount = foreignKeyDescriptor.getJdbcTypeCount();
final ValuedModelPart keyPart = foreignKeyDescriptor.getKeyPart();
for ( int i = 0; i < selectableCount; i++ ) {
if ( keyPart.getSelectable( i ).isInsertable() != fkBootDescriptorSource.isColumnInsertable( i )
|| keyPart.getSelectable( i ).isUpdateable() != fkBootDescriptorSource.isColumnUpdateable( i ) ) {
final AttributeMapping attributeMapping = keyPart.asAttributeMapping();
final ManagedMappingType declaringType;
if ( attributeMapping == null ) {
declaringType = null;
}
else {
declaringType = attributeMapping.getDeclaringType();
}
final SelectableMappings selectableMappings = SelectableMappingsImpl.from(
keyPart.getContainingTableExpression(),
fkBootDescriptorSource,
getPropertyOrder( fkBootDescriptorSource, creationProcess ),
creationProcess.getCreationContext().getMetadata(),
creationProcess.getCreationContext().getTypeConfiguration(),
fkBootDescriptorSource.getColumnInsertability(),
fkBootDescriptorSource.getColumnUpdateability(),
creationProcess.getCreationContext().getDialect(),
creationProcess.getSqmFunctionRegistry()
);
return foreignKeyDescriptor.withKeySelectionMapping(
declaringType,
this,
selectableMappings::getSelectable,
creationProcess
);
}
}
return foreignKeyDescriptor;
}
private SimpleForeignKeyDescriptor createSimpleForeignKeyDescriptor(
Value fkBootDescriptorSource,
EntityType entityType,
@ -618,7 +698,7 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
BasicValuedModelPart basicFkTargetPart) {
final boolean columnInsertable;
final boolean columnUpdateable;
if ( getNature() == Nature.ELEMENT ) {
if ( getNature() == Nature.ELEMENT && !fkBootDescriptorSource.getSelectables().get( 0 ).isFormula() ) {
// Replicate behavior of AbstractCollectionPersister#elementColumnIsSettable
columnInsertable = true;
columnUpdateable = true;
@ -627,34 +707,26 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
columnInsertable = fkBootDescriptorSource.isColumnInsertable( 0 );
columnUpdateable = fkBootDescriptorSource.isColumnUpdateable( 0 );
}
final SimpleValue fkValue = (SimpleValue) fkBootDescriptorSource;
final SelectableMapping keySelectableMapping = SelectableMappingImpl.from(
fkKeyTableName,
fkBootDescriptorSource.getSelectables().get(0),
fkBootDescriptorSource.getSelectables().get( 0 ),
basicFkTargetPart.getJdbcMapping(),
creationProcess.getCreationContext().getTypeConfiguration(),
columnInsertable,
columnUpdateable,
((SimpleValue) fkBootDescriptorSource).isPartitionKey(),
fkValue.isPartitionKey(),
dialect,
creationProcess.getSqmFunctionRegistry()
);
final boolean hasConstraint;
if ( fkBootDescriptorSource instanceof SimpleValue ) {
hasConstraint = ( (SimpleValue) fkBootDescriptorSource ).isConstrained();
}
else {
// We assume there is a constraint if the key is not nullable
hasConstraint = !fkBootDescriptorSource.isNullable();
}
// here we build a ModelPart that represents the many-to-many table key referring to the element table
return new SimpleForeignKeyDescriptor(
getAssociatedEntityMappingType(),
keySelectableMapping,
basicFkTargetPart,
entityType.isReferenceToPrimaryKey(),
hasConstraint
fkValue.isConstrained()
);
}
}

View File

@ -209,14 +209,15 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
TableGroupProducer declaringTableGroupProducer,
IntFunction<SelectableMapping> selectableMappingAccess,
MappingModelCreationProcess creationProcess) {
final SelectableMapping selectableMapping = selectableMappingAccess.apply( 0 );
return new SimpleForeignKeyDescriptor(
declaringType,
keySide.getModelPart(),
( (PropertyBasedMapping) keySide.getModelPart() ).getPropertyAccess(),
selectableMappingAccess.apply( 0 ),
selectableMapping,
targetSide.getModelPart(),
keySide.getModelPart().isInsertable(),
keySide.getModelPart().isUpdateable(),
selectableMapping.isInsertable(),
selectableMapping.isUpdateable(),
refersToPrimaryKey,
hasConstraint
);

View File

@ -12,7 +12,6 @@ import java.util.List;
import java.util.function.Consumer;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
@ -37,16 +36,12 @@ import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.update.Assignable;
@ -163,14 +158,7 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
// we try to make use of it and the FK model part if possible based on the inferred mapping
if ( mapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
final ModelPart associationKeyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
final ModelPart keyTargetMatchPart;
if ( associationKeyTargetMatchPart instanceof ToOneAttributeMapping ) {
keyTargetMatchPart = ( (ToOneAttributeMapping) associationKeyTargetMatchPart ).getKeyTargetMatchPart();
}
else {
keyTargetMatchPart = associationKeyTargetMatchPart;
}
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
if ( associationMapping.isFkOptimizationAllowed() ) {
final boolean forceUsingForeignKeyAssociationSidePart;
@ -197,13 +185,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
forceUsingForeignKeyAssociationSidePart = false;
}
if ( forceUsingForeignKeyAssociationSidePart ) {
if ( associationKeyTargetMatchPart instanceof ToOneAttributeMapping ) {
resultModelPart = keyTargetMatchPart;
}
else {
resultModelPart = associationMapping.getForeignKeyDescriptor()
.getPart( associationMapping.getSideNature() );
}
resultModelPart = associationMapping.getForeignKeyDescriptor()
.getPart( associationMapping.getSideNature() );
resultTableGroup = sqlAstCreationState.getFromClauseAccess()
.findTableGroup( tableGroup.getNavigablePath().getParent() );
}