HHH-15349 Fix rendering of EntityValuedPathInterpretation when comparing different model parts
This commit is contained in:
parent
b3d0addaeb
commit
9cff075a89
|
@ -2,6 +2,8 @@ package org.hibernate.userguide.associations;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.FetchType;
|
import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
@ -14,6 +16,7 @@ import org.hibernate.annotations.NotFound;
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
|
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -29,6 +32,13 @@ import static org.junit.Assert.assertNull;
|
||||||
*/
|
*/
|
||||||
public class NotFoundTest extends BaseEntityManagerFunctionalTestCase {
|
public class NotFoundTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
private SQLStatementInterceptor sqlStatementInterceptor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addConfigOptions(Map options) {
|
||||||
|
sqlStatementInterceptor = new SQLStatementInterceptor( options );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Class<?>[] getAnnotatedClasses() {
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
return new Class<?>[] {
|
return new Class<?>[] {
|
||||||
|
@ -111,19 +121,25 @@ public class NotFoundTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
breakForeignKey();
|
breakForeignKey();
|
||||||
|
|
||||||
inTransaction( entityManagerFactory(), (entityManager) -> {
|
inTransaction( entityManagerFactory(), (entityManager) -> {
|
||||||
|
sqlStatementInterceptor.clear();
|
||||||
//tag::associations-not-found-fk-function-example[]
|
//tag::associations-not-found-fk-function-example[]
|
||||||
final List<Person> nullResults = entityManager
|
final List<String> nullResults = entityManager
|
||||||
.createSelectionQuery( "from Person p where fk( p.city ) is null", Person.class )
|
.createSelectionQuery( "select p.name from Person p where fk( p.city ) is null", String.class )
|
||||||
.list();
|
.list();
|
||||||
|
|
||||||
assertThat( nullResults ).isEmpty();
|
assertThat( nullResults ).isEmpty();
|
||||||
|
|
||||||
final List<Person> nonNullResults = entityManager
|
final List<String> nonNullResults = entityManager
|
||||||
.createSelectionQuery( "from Person p where fk( p.city ) is not null", Person.class )
|
.createSelectionQuery( "select p.name from Person p where fk( p.city ) is not null", String.class )
|
||||||
.list();
|
.list();
|
||||||
assertThat( nonNullResults ).hasSize( 1 );
|
assertThat( nonNullResults ).hasSize( 1 );
|
||||||
assertThat( nonNullResults.get( 0 ).getName() ).isEqualTo( "John Doe" );
|
assertThat( nonNullResults.get( 0 ) ).isEqualTo( "John Doe" );
|
||||||
//end::associations-not-found-fk-function-example[]
|
//end::associations-not-found-fk-function-example[]
|
||||||
|
|
||||||
|
// In addition, make sure that the two executed queries do not create a join
|
||||||
|
assertThat( sqlStatementInterceptor.getQueryCount() ).isEqualTo( 2 );
|
||||||
|
assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
||||||
|
assertThat( sqlStatementInterceptor.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,10 @@ public class BinderHelper {
|
||||||
String syntheticPropertyName =
|
String syntheticPropertyName =
|
||||||
"_" + associatedClass.getEntityName().replace('.', '_') +
|
"_" + associatedClass.getEntityName().replace('.', '_') +
|
||||||
"_" + columns[0].getPropertyName().replace('.', '_');
|
"_" + columns[0].getPropertyName().replace('.', '_');
|
||||||
|
if ( inverse ) {
|
||||||
|
// Use a different name for inverse synthetic properties to avoid duplicate properties for self-referencing models
|
||||||
|
syntheticPropertyName += "_inverse";
|
||||||
|
}
|
||||||
//find properties associated to a certain column
|
//find properties associated to a certain column
|
||||||
Object columnOwner = findColumnOwner( ownerEntity, columns[0].getReferencedColumn(), context );
|
Object columnOwner = findColumnOwner( ownerEntity, columns[0].getReferencedColumn(), context );
|
||||||
List<Property> properties = findPropertiesByColumns( columnOwner, columns, context );
|
List<Property> properties = findPropertiesByColumns( columnOwner, columns, context );
|
||||||
|
|
|
@ -2271,7 +2271,7 @@ public abstract class CollectionBinder {
|
||||||
}
|
}
|
||||||
String referencedPropertyName =
|
String referencedPropertyName =
|
||||||
buildingContext.getMetadataCollector().getPropertyReferencedAssociation(
|
buildingContext.getMetadataCollector().getPropertyReferencedAssociation(
|
||||||
"inverse__" + referencedEntity.getEntityName(), mappedBy
|
referencedEntity.getEntityName(), mappedBy
|
||||||
);
|
);
|
||||||
if ( referencedPropertyName != null ) {
|
if ( referencedPropertyName != null ) {
|
||||||
//TODO always a many to one?
|
//TODO always a many to one?
|
||||||
|
|
|
@ -27,6 +27,10 @@ public interface EntityAssociationMapping extends ModelPart, Association, TableG
|
||||||
*/
|
*/
|
||||||
ModelPart getKeyTargetMatchPart();
|
ModelPart getKeyTargetMatchPart();
|
||||||
|
|
||||||
|
boolean isReferenceToPrimaryKey();
|
||||||
|
|
||||||
|
boolean isFkOptimizationAllowed();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean incrementFetchDepth(){
|
default boolean incrementFetchDepth(){
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -50,6 +50,15 @@ public interface ForeignKeyDescriptor extends VirtualModelPart, ValueMapping {
|
||||||
|
|
||||||
String getTargetTable();
|
String getTargetTable();
|
||||||
|
|
||||||
|
default String getTable(Nature nature) {
|
||||||
|
if ( nature == Nature.KEY ) {
|
||||||
|
return getKeyTable();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return getTargetTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ModelPart getKeyPart();
|
ModelPart getKeyPart();
|
||||||
|
|
||||||
ModelPart getTargetPart();
|
ModelPart getTargetPart();
|
||||||
|
|
|
@ -341,9 +341,14 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
|
||||||
SqlAstCreationContext creationContext) {
|
SqlAstCreationContext creationContext) {
|
||||||
final TableReference lhsTableReference = targetSideTableGroup.resolveTableReference(
|
final TableReference lhsTableReference = targetSideTableGroup.resolveTableReference(
|
||||||
targetSideTableGroup.getNavigablePath(),
|
targetSideTableGroup.getNavigablePath(),
|
||||||
targetTable
|
targetTable,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
final TableReference rhsTableKeyReference = keySideTableGroup.resolveTableReference(
|
||||||
|
null,
|
||||||
|
keyTable,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
final TableReference rhsTableKeyReference = keySideTableGroup.resolveTableReference( keyTable );
|
|
||||||
|
|
||||||
return generateJoinPredicate(
|
return generateJoinPredicate(
|
||||||
lhsTableReference,
|
lhsTableReference,
|
||||||
|
@ -459,27 +464,6 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TableReference getTableReference(TableGroup lhs, TableGroup tableGroup, String table) {
|
|
||||||
TableReference tableReference = lhs.getPrimaryTableReference().resolveTableReference( table );
|
|
||||||
if ( tableReference != null ) {
|
|
||||||
return tableReference;
|
|
||||||
}
|
|
||||||
tableReference = tableGroup.getPrimaryTableReference().resolveTableReference( table );
|
|
||||||
if ( tableReference != null ) {
|
|
||||||
return tableReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
tableReference = lhs.resolveTableReference(
|
|
||||||
lhs.getNavigablePath().append( getNavigableRole().getNavigableName() ),
|
|
||||||
table
|
|
||||||
);
|
|
||||||
if ( tableReference != null ) {
|
|
||||||
return tableReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException( "Could not resolve binding for table `" + table + "`" );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int visitKeySelectables(int offset, SelectableConsumer consumer) {
|
public int visitKeySelectables(int offset, SelectableConsumer consumer) {
|
||||||
return keySelectableMappings.forEachSelectable( offset, consumer );
|
return keySelectableMappings.forEachSelectable( offset, consumer );
|
||||||
|
|
|
@ -600,6 +600,20 @@ public class EntityCollectionPart
|
||||||
: ForeignKeyDescriptor.Nature.KEY;
|
: ForeignKeyDescriptor.Nature.KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReferenceToPrimaryKey() {
|
||||||
|
return fkDescriptor.getSide( getSideNature().inverse() ).getModelPart() instanceof EntityIdentifierMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFkOptimizationAllowed() {
|
||||||
|
return !collectionDescriptor.isOneToMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionPersister getCollectionDescriptor() {
|
||||||
|
return collectionDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FetchStyle getStyle() {
|
public FetchStyle getStyle() {
|
||||||
return FetchStyle.JOIN;
|
return FetchStyle.JOIN;
|
||||||
|
@ -710,7 +724,9 @@ public class EntityCollectionPart
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetKeyPropertyNames.contains( relativePath );
|
// Empty relative path means the navigable paths are equal,
|
||||||
|
// in which case we allow resolving the parent table group
|
||||||
|
return relativePath.isEmpty() || targetKeyPropertyNames.contains( relativePath );
|
||||||
},
|
},
|
||||||
this,
|
this,
|
||||||
explicitSourceAlias,
|
explicitSourceAlias,
|
||||||
|
|
|
@ -316,10 +316,13 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
|
||||||
SqlAstCreationContext creationContext) {
|
SqlAstCreationContext creationContext) {
|
||||||
final TableReference lhsTableReference = targetSideTableGroup.resolveTableReference(
|
final TableReference lhsTableReference = targetSideTableGroup.resolveTableReference(
|
||||||
targetSideTableGroup.getNavigablePath(),
|
targetSideTableGroup.getNavigablePath(),
|
||||||
targetSide.getModelPart().getContainingTableExpression()
|
targetSide.getModelPart().getContainingTableExpression(),
|
||||||
|
false
|
||||||
);
|
);
|
||||||
final TableReference rhsTableKeyReference = keySideTableGroup.resolveTableReference(
|
final TableReference rhsTableKeyReference = keySideTableGroup.resolveTableReference(
|
||||||
keySide.getModelPart().getContainingTableExpression()
|
null,
|
||||||
|
keySide.getModelPart().getContainingTableExpression(),
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
return generateJoinPredicate(
|
return generateJoinPredicate(
|
||||||
|
@ -352,23 +355,6 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
|
||||||
|| ( lhs.equals( targetExpression ) && rhs.equals( keyExpression ) );
|
|| ( lhs.equals( targetExpression ) && rhs.equals( keyExpression ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TableReference getTableReference(TableGroup lhs, TableGroup tableGroup, String table) {
|
|
||||||
final NavigablePath navigablePath = lhs.getNavigablePath().append( getTargetPart().getFetchableName() );
|
|
||||||
if ( lhs.getPrimaryTableReference().getTableReference( navigablePath, table ) != null ) {
|
|
||||||
return lhs.getPrimaryTableReference();
|
|
||||||
}
|
|
||||||
else if ( tableGroup.getPrimaryTableReference().getTableReference( navigablePath, table ) != null ) {
|
|
||||||
return tableGroup.getPrimaryTableReference();
|
|
||||||
}
|
|
||||||
|
|
||||||
final TableReference tableReference = lhs.resolveTableReference( navigablePath, table );
|
|
||||||
if ( tableReference != null ) {
|
|
||||||
return tableReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException( "Could not resolve binding for table `" + table + "`" );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MappingType getPartMappingType() {
|
public MappingType getPartMappingType() {
|
||||||
return targetSide.getModelPart().getMappedType();
|
return targetSide.getModelPart().getMappedType();
|
||||||
|
|
|
@ -703,10 +703,16 @@ public class ToOneAttributeMapping
|
||||||
return sideNature;
|
return sideNature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isReferenceToPrimaryKey() {
|
public boolean isReferenceToPrimaryKey() {
|
||||||
return foreignKeyDescriptor.getSide( sideNature.inverse() ).getModelPart() instanceof EntityIdentifierMapping;
|
return foreignKeyDescriptor.getSide( sideNature.inverse() ).getModelPart() instanceof EntityIdentifierMapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFkOptimizationAllowed() {
|
||||||
|
return canUseParentTableGroup;
|
||||||
|
}
|
||||||
|
|
||||||
public String getReferencedPropertyName() {
|
public String getReferencedPropertyName() {
|
||||||
return referencedPropertyName;
|
return referencedPropertyName;
|
||||||
}
|
}
|
||||||
|
@ -1576,7 +1582,9 @@ public class ToOneAttributeMapping
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetKeyPropertyNames.contains( relativePath );
|
// Empty relative path means the navigable paths are equal,
|
||||||
|
// in which case we allow resolving the parent table group
|
||||||
|
return relativePath.isEmpty() || targetKeyPropertyNames.contains( relativePath );
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
null
|
null
|
||||||
|
@ -1606,23 +1614,31 @@ public class ToOneAttributeMapping
|
||||||
|
|
||||||
final TableReference lhsTableReference = lhs.resolveTableReference( navigablePath, identifyingColumnsTableExpression );
|
final TableReference lhsTableReference = lhs.resolveTableReference( navigablePath, identifyingColumnsTableExpression );
|
||||||
|
|
||||||
lazyTableGroup.setTableGroupInitializerCallback( (tableGroup) -> join.applyPredicate(
|
lazyTableGroup.setTableGroupInitializerCallback(
|
||||||
|
tableGroup -> {
|
||||||
|
join.applyPredicate(
|
||||||
foreignKeyDescriptor.generateJoinPredicate(
|
foreignKeyDescriptor.generateJoinPredicate(
|
||||||
sideNature == ForeignKeyDescriptor.Nature.TARGET ? lhsTableReference : tableGroup.getPrimaryTableReference(),
|
sideNature == ForeignKeyDescriptor.Nature.TARGET ?
|
||||||
sideNature == ForeignKeyDescriptor.Nature.TARGET ? tableGroup.getPrimaryTableReference() : lhsTableReference,
|
lhsTableReference :
|
||||||
|
tableGroup.getPrimaryTableReference(),
|
||||||
|
sideNature == ForeignKeyDescriptor.Nature.TARGET ?
|
||||||
|
tableGroup.getPrimaryTableReference() :
|
||||||
|
lhsTableReference,
|
||||||
sqlExpressionResolver,
|
sqlExpressionResolver,
|
||||||
creationContext
|
creationContext
|
||||||
)
|
)
|
||||||
) );
|
);
|
||||||
|
|
||||||
if ( hasNotFoundAction() ) {
|
if ( hasNotFoundAction() ) {
|
||||||
getAssociatedEntityMappingType().applyWhereRestrictions(
|
getAssociatedEntityMappingType().applyWhereRestrictions(
|
||||||
join::applyPredicate,
|
join::applyPredicate,
|
||||||
lazyTableGroup.getTableGroup(),
|
tableGroup,
|
||||||
true,
|
true,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return join;
|
return join;
|
||||||
}
|
}
|
||||||
|
@ -1697,7 +1713,9 @@ public class ToOneAttributeMapping
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetKeyPropertyNames.contains( relativePath );
|
// Empty relative path means the navigable paths are equal,
|
||||||
|
// in which case we allow resolving the parent table group
|
||||||
|
return relativePath.isEmpty() || targetKeyPropertyNames.contains( relativePath );
|
||||||
},
|
},
|
||||||
tableGroupProducer,
|
tableGroupProducer,
|
||||||
explicitSourceAlias,
|
explicitSourceAlias,
|
||||||
|
|
|
@ -145,16 +145,16 @@ public class SqmMappingModelHelper {
|
||||||
MappingMetamodel domainModel,
|
MappingMetamodel domainModel,
|
||||||
Function<NavigablePath,TableGroup> tableGroupLocator) {
|
Function<NavigablePath,TableGroup> tableGroupLocator) {
|
||||||
|
|
||||||
if ( sqmPath instanceof SqmTreatedPath ) {
|
if ( sqmPath instanceof SqmTreatedPath<?, ?> ) {
|
||||||
final SqmTreatedPath treatedPath = (SqmTreatedPath) sqmPath;
|
final SqmTreatedPath<?, ?> treatedPath = (SqmTreatedPath<?, ?>) sqmPath;
|
||||||
final EntityDomainType treatTargetType = treatedPath.getTreatTarget();
|
final EntityDomainType<?> treatTargetType = treatedPath.getTreatTarget();
|
||||||
return domainModel.findEntityDescriptor( treatTargetType.getHibernateEntityName() );
|
return domainModel.findEntityDescriptor( treatTargetType.getHibernateEntityName() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if the LHS is treated
|
// see if the LHS is treated
|
||||||
if ( sqmPath.getLhs() instanceof SqmTreatedPath ) {
|
if ( sqmPath.getLhs() instanceof SqmTreatedPath<?, ?> ) {
|
||||||
final SqmTreatedPath treatedPath = (SqmTreatedPath) sqmPath.getLhs();
|
final SqmTreatedPath<?, ?> treatedPath = (SqmTreatedPath<?, ?>) sqmPath.getLhs();
|
||||||
final EntityDomainType treatTargetType = treatedPath.getTreatTarget();
|
final EntityDomainType<?> treatTargetType = treatedPath.getTreatTarget();
|
||||||
final EntityPersister container = domainModel.findEntityDescriptor( treatTargetType.getHibernateEntityName() );
|
final EntityPersister container = domainModel.findEntityDescriptor( treatTargetType.getHibernateEntityName() );
|
||||||
|
|
||||||
return container.findSubPart( sqmPath.getNavigablePath().getLocalName(), container );
|
return container.findSubPart( sqmPath.getNavigablePath().getLocalName(), container );
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.hibernate.metamodel.mapping.ConvertibleModelPart;
|
||||||
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
||||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
|
@ -350,6 +351,10 @@ public class SqmUtil {
|
||||||
if ( parameterType == null ) {
|
if ( parameterType == null ) {
|
||||||
throw new SqlTreeCreationException( "Unable to interpret mapping-model type for Query parameter : " + domainParam );
|
throw new SqlTreeCreationException( "Unable to interpret mapping-model type for Query parameter : " + domainParam );
|
||||||
}
|
}
|
||||||
|
else if ( parameterType instanceof PluralAttributeMapping ) {
|
||||||
|
// Default to the collection element
|
||||||
|
parameterType = ( (PluralAttributeMapping) parameterType ).getElementDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
if ( parameterType instanceof EntityIdentifierMapping ) {
|
if ( parameterType instanceof EntityIdentifierMapping ) {
|
||||||
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
|
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
|
||||||
|
@ -368,6 +373,13 @@ public class SqmUtil {
|
||||||
}
|
}
|
||||||
else if ( parameterType instanceof EntityAssociationMapping ) {
|
else if ( parameterType instanceof EntityAssociationMapping ) {
|
||||||
EntityAssociationMapping association = (EntityAssociationMapping) parameterType;
|
EntityAssociationMapping association = (EntityAssociationMapping) parameterType;
|
||||||
|
if ( association.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) {
|
||||||
|
// If the association is the target, we must use the identifier of the EntityMappingType
|
||||||
|
bindValue = association.getAssociatedEntityMappingType().getIdentifierMapping()
|
||||||
|
.getIdentifier( bindValue );
|
||||||
|
parameterType = association.getAssociatedEntityMappingType().getIdentifierMapping();
|
||||||
|
}
|
||||||
|
else {
|
||||||
bindValue = association.getForeignKeyDescriptor().getAssociationKeyFromSide(
|
bindValue = association.getForeignKeyDescriptor().getAssociationKeyFromSide(
|
||||||
bindValue,
|
bindValue,
|
||||||
association.getSideNature().inverse(),
|
association.getSideNature().inverse(),
|
||||||
|
@ -375,10 +387,6 @@ public class SqmUtil {
|
||||||
);
|
);
|
||||||
parameterType = association.getForeignKeyDescriptor();
|
parameterType = association.getForeignKeyDescriptor();
|
||||||
}
|
}
|
||||||
else if ( parameterType instanceof PluralAttributeMapping ) {
|
|
||||||
// we'd expect the values to refer to the collection element
|
|
||||||
// for now, let's blow up and see where this happens and fix the specifics...
|
|
||||||
throw new NotYetImplementedFor6Exception( "Binding parameters whose inferred type comes from plural attribute not yet implemented" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = jdbcParameterBindings.registerParametersForEachJdbcValue(
|
int offset = jdbcParameterBindings.registerParametersForEachJdbcValue(
|
||||||
|
|
|
@ -332,6 +332,7 @@ import org.hibernate.sql.ast.tree.from.SyntheticVirtualTableGroup;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
|
||||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||||
import org.hibernate.sql.ast.tree.from.VirtualTableGroup;
|
import org.hibernate.sql.ast.tree.from.VirtualTableGroup;
|
||||||
import org.hibernate.sql.ast.tree.insert.InsertStatement;
|
import org.hibernate.sql.ast.tree.insert.InsertStatement;
|
||||||
|
@ -1080,8 +1081,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
rootTableGroup
|
rootTableGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( !rootTableGroup.getTableReferenceJoins().isEmpty()
|
if ( hasJoins( rootTableGroup ) ) {
|
||||||
|| !rootTableGroup.getTableGroupJoins().isEmpty() ) {
|
|
||||||
throw new SemanticException( "Not expecting multiple table references for an SQM INSERT-SELECT" );
|
throw new SemanticException( "Not expecting multiple table references for an SQM INSERT-SELECT" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1109,6 +1109,24 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
return insertStatement;
|
return insertStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean hasJoins(TableGroup rootTableGroup) {
|
||||||
|
if ( !rootTableGroup.getTableReferenceJoins().isEmpty() ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return hasJoins( rootTableGroup.getTableGroupJoins() ) || hasJoins( rootTableGroup.getNestedTableGroupJoins() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasJoins(List<TableGroupJoin> tableGroupJoins) {
|
||||||
|
for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
|
||||||
|
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
|
||||||
|
if ( joinedGroup instanceof LazyTableGroup && ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup() == null ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Insert-values statement
|
// Insert-values statement
|
||||||
|
|
||||||
|
@ -3097,6 +3115,32 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
implicitJoinChecker = BaseSqmToSqlAstConverter::verifyManipulationImplicitJoin;
|
implicitJoinChecker = BaseSqmToSqlAstConverter::verifyManipulationImplicitJoin;
|
||||||
}
|
}
|
||||||
prepareReusablePath( fromClauseIndex, sqmPath, implicitJoinChecker );
|
prepareReusablePath( fromClauseIndex, sqmPath, implicitJoinChecker );
|
||||||
|
|
||||||
|
// Create the table group for every path that can potentially require one,
|
||||||
|
// as some paths require joining the target table i.e. inverse one-to-one
|
||||||
|
// Note that this will not necessarily create joins immediately, as table groups are lazy
|
||||||
|
if ( sqmPath instanceof SqmEntityValuedSimplePath<?>
|
||||||
|
|| sqmPath instanceof SqmEmbeddedValuedSimplePath<?>
|
||||||
|
|| sqmPath instanceof SqmAnyValuedSimplePath<?> ) {
|
||||||
|
final TableGroup existingTableGroup = fromClauseIndex.findTableGroup( sqmPath.getNavigablePath() );
|
||||||
|
if ( existingTableGroup == null ) {
|
||||||
|
final TableGroup createdTableGroup = createTableGroup(
|
||||||
|
fromClauseIndex.getTableGroup( sqmPath.getLhs().getNavigablePath() ),
|
||||||
|
sqmPath
|
||||||
|
);
|
||||||
|
if ( createdTableGroup != null ) {
|
||||||
|
if ( sqmPath instanceof SqmTreatedPath<?, ?> ) {
|
||||||
|
fromClauseIndex.register( sqmPath, createdTableGroup );
|
||||||
|
}
|
||||||
|
if ( sqmPath instanceof SqmFrom<?, ?> ) {
|
||||||
|
registerTreatUsage( (SqmFrom<?, ?>) sqmPath, createdTableGroup );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( sqmPath instanceof SqmFrom<?, ?> ) {
|
||||||
|
registerTreatUsage( (SqmFrom<?, ?>) sqmPath, existingTableGroup );
|
||||||
|
}
|
||||||
|
}
|
||||||
return supplier.get();
|
return supplier.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3190,11 +3234,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
if ( tableGroup == null ) {
|
if ( tableGroup == null ) {
|
||||||
prepareReusablePath( path, () -> null );
|
prepareReusablePath( path, () -> null );
|
||||||
|
|
||||||
|
if ( !( path instanceof SqmEntityValuedSimplePath<?>
|
||||||
|
|| path instanceof SqmEmbeddedValuedSimplePath<?>
|
||||||
|
|| path instanceof SqmAnyValuedSimplePath<?> ) ) {
|
||||||
|
// Since this is a selection, we must create a table group for the path as a DomainResult will be created
|
||||||
|
// But only create it for paths that are not handled by #prepareReusablePath anyway
|
||||||
final NavigablePath navigablePath;
|
final NavigablePath navigablePath;
|
||||||
if ( CollectionPart.Nature.fromNameExact( path.getNavigablePath().getLocalName() ) != null ) {
|
if ( path instanceof SqmTreatedRoot<?, ?> ) {
|
||||||
navigablePath = path.getLhs().getLhs().getNavigablePath();
|
|
||||||
}
|
|
||||||
else if ( path instanceof SqmTreatedRoot<?, ?> ) {
|
|
||||||
navigablePath = ( (SqmTreatedRoot<?, ?>) path ).getWrappedPath().getNavigablePath();
|
navigablePath = ( (SqmTreatedRoot<?, ?>) path ).getWrappedPath().getNavigablePath();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -3213,6 +3259,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if ( path instanceof SqmFrom<?, ?> ) {
|
else if ( path instanceof SqmFrom<?, ?> ) {
|
||||||
registerTreatUsage( (SqmFrom<?, ?>) path, tableGroup );
|
registerTreatUsage( (SqmFrom<?, ?>) path, tableGroup );
|
||||||
}
|
}
|
||||||
|
@ -3288,7 +3335,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
|
|
||||||
fromClauseIndex.register( joinedPath, tableGroup );
|
fromClauseIndex.register( joinedPath, tableGroup );
|
||||||
registerPluralTableGroupParts( tableGroup );
|
registerPluralTableGroupParts( joinedPath.getNavigablePath(), tableGroup );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tableGroup = null;
|
tableGroup = null;
|
||||||
|
@ -3322,17 +3369,25 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerPluralTableGroupParts(TableGroup tableGroup) {
|
private void registerPluralTableGroupParts(TableGroup tableGroup) {
|
||||||
|
registerPluralTableGroupParts( null, tableGroup );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerPluralTableGroupParts(NavigablePath navigablePath, TableGroup tableGroup) {
|
||||||
if ( tableGroup instanceof PluralTableGroup ) {
|
if ( tableGroup instanceof PluralTableGroup ) {
|
||||||
final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup;
|
final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup;
|
||||||
if ( pluralTableGroup.getElementTableGroup() != null ) {
|
if ( pluralTableGroup.getElementTableGroup() != null ) {
|
||||||
getFromClauseAccess().registerTableGroup(
|
getFromClauseAccess().registerTableGroup(
|
||||||
pluralTableGroup.getElementTableGroup().getNavigablePath(),
|
navigablePath == null || navigablePath == tableGroup.getNavigablePath()
|
||||||
|
? pluralTableGroup.getElementTableGroup().getNavigablePath()
|
||||||
|
: navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ),
|
||||||
pluralTableGroup.getElementTableGroup()
|
pluralTableGroup.getElementTableGroup()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if ( pluralTableGroup.getIndexTableGroup() != null ) {
|
if ( pluralTableGroup.getIndexTableGroup() != null ) {
|
||||||
getFromClauseAccess().registerTableGroup(
|
getFromClauseAccess().registerTableGroup(
|
||||||
pluralTableGroup.getIndexTableGroup().getNavigablePath(),
|
navigablePath == null || navigablePath == tableGroup.getNavigablePath()
|
||||||
|
? pluralTableGroup.getIndexTableGroup().getNavigablePath()
|
||||||
|
: navigablePath.append( CollectionPart.Nature.INDEX.getName() ),
|
||||||
pluralTableGroup.getIndexTableGroup()
|
pluralTableGroup.getIndexTableGroup()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3422,95 +3477,130 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) {
|
private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) {
|
||||||
final ModelPartContainer modelPart;
|
final ModelPartContainer tableGroupModelPart = tableGroup.getModelPart();
|
||||||
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
final ModelPart actualModelPart;
|
||||||
// For plain SqmFrom node uses, prefer the mapping type from the context if possible
|
final NavigablePath navigablePath;
|
||||||
if ( !( inferredValueMapping instanceof ModelPartContainer ) ) {
|
if ( tableGroupModelPart instanceof PluralAttributeMapping ) {
|
||||||
modelPart = tableGroup.getModelPart();
|
actualModelPart = ( (PluralAttributeMapping) tableGroupModelPart ).getElementDescriptor();
|
||||||
|
navigablePath = tableGroup.getNavigablePath().append( actualModelPart.getPartName() );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
modelPart = (ModelPartContainer) inferredValueMapping;
|
actualModelPart = tableGroupModelPart;
|
||||||
|
navigablePath = tableGroup.getNavigablePath();
|
||||||
}
|
}
|
||||||
|
final Expression result;
|
||||||
|
if ( actualModelPart instanceof EntityValuedModelPart ) {
|
||||||
|
final EntityValuedModelPart entityValuedModelPart = (EntityValuedModelPart) actualModelPart;
|
||||||
|
final EntityValuedModelPart inferredEntityMapping = (EntityValuedModelPart) getInferredValueMapping();
|
||||||
final ModelPart resultModelPart;
|
final ModelPart resultModelPart;
|
||||||
final ModelPart interpretationModelPart;
|
final ModelPart interpretationModelPart;
|
||||||
final TableGroup parentGroupToUse;
|
final TableGroup tableGroupToUse;
|
||||||
if ( modelPart instanceof ToOneAttributeMapping ) {
|
if ( inferredEntityMapping == null ) {
|
||||||
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) modelPart;
|
// When the inferred mapping is null, we try to resolve to the FK by default,
|
||||||
final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart(
|
// which is fine because expansion to all target columns for select and group by clauses is handled below
|
||||||
toOneAttributeMapping.getSideNature().inverse()
|
if ( entityValuedModelPart instanceof EntityAssociationMapping && ( (EntityAssociationMapping) entityValuedModelPart ).getSideNature() != ForeignKeyDescriptor.Nature.TARGET ) {
|
||||||
|
// If the table group uses an association mapping that is not a one-to-many,
|
||||||
|
// we make use of the FK model part
|
||||||
|
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) entityValuedModelPart;
|
||||||
|
final ModelPart targetPart = associationMapping.getForeignKeyDescriptor().getPart(
|
||||||
|
associationMapping.getSideNature()
|
||||||
);
|
);
|
||||||
if ( tableGroup.getModelPart().getPartMappingType() == modelPart.getPartMappingType() ) {
|
if ( entityValuedModelPart.getPartMappingType() == associationMapping.getPartMappingType() ) {
|
||||||
resultModelPart = targetPart;
|
resultModelPart = targetPart;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// If the table group is for a different mapping type i.e. an inheritance subtype,
|
// If the table group is for a different mapping type i.e. an inheritance subtype,
|
||||||
// lookup the target part on that mapping type
|
// lookup the target part on that mapping type
|
||||||
resultModelPart = tableGroup.getModelPart().findSubPart( targetPart.getPartName(), null );
|
resultModelPart = entityValuedModelPart.findSubPart( targetPart.getPartName(), null );
|
||||||
}
|
|
||||||
interpretationModelPart = modelPart;
|
|
||||||
parentGroupToUse = null;
|
|
||||||
}
|
|
||||||
else if ( modelPart instanceof PluralAttributeMapping ) {
|
|
||||||
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
|
|
||||||
final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor();
|
|
||||||
if ( elementDescriptor instanceof EntityCollectionPart ) {
|
|
||||||
// Usually, we need to resolve to the PK for visitTableGroup
|
|
||||||
final EntityCollectionPart collectionPart = (EntityCollectionPart) elementDescriptor;
|
|
||||||
final ModelPart collectionTargetPart = collectionPart.getForeignKeyDescriptor()
|
|
||||||
.getPart( collectionPart.getSideNature().inverse() );
|
|
||||||
final EntityIdentifierMapping identifierMapping = collectionPart.getEntityMappingType()
|
|
||||||
.getIdentifierMapping();
|
|
||||||
// If the FK points to the PK, we can use the FK part though, if this is not a root
|
|
||||||
if ( collectionTargetPart == identifierMapping && !( path instanceof SqmRoot<?> ) ) {
|
|
||||||
resultModelPart = collectionPart.getForeignKeyDescriptor().getPart( collectionPart.getSideNature() );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resultModelPart = identifierMapping;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resultModelPart = elementDescriptor;
|
// Otherwise, we use the identifier mapping of the target entity type
|
||||||
|
resultModelPart = entityValuedModelPart.getEntityMappingType().getIdentifierMapping();
|
||||||
}
|
}
|
||||||
interpretationModelPart = elementDescriptor;
|
interpretationModelPart = entityValuedModelPart;
|
||||||
parentGroupToUse = null;
|
tableGroupToUse = null;
|
||||||
}
|
}
|
||||||
else if ( modelPart instanceof EntityCollectionPart ) {
|
else if ( inferredEntityMapping instanceof ToOneAttributeMapping ) {
|
||||||
// Usually, we need to resolve to the PK for visitTableGroup
|
// If the inferred mapping is a to-one association,
|
||||||
final EntityCollectionPart collectionPart = (EntityCollectionPart) modelPart;
|
// we use the FK target part, which must be located on the entity mapping
|
||||||
final ModelPart collectionTargetPart = collectionPart.getForeignKeyDescriptor()
|
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) inferredEntityMapping;
|
||||||
.getPart( collectionPart.getSideNature().inverse() );
|
final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart(
|
||||||
final EntityIdentifierMapping identifierMapping = collectionPart.getEntityMappingType()
|
toOneAttributeMapping.getSideNature().inverse()
|
||||||
.getIdentifierMapping();
|
);
|
||||||
// If the FK points to the PK, we can use the FK part though, if this is not a root
|
if ( entityValuedModelPart.getPartMappingType() == toOneAttributeMapping.getPartMappingType() ) {
|
||||||
if ( collectionTargetPart == identifierMapping && !( path instanceof SqmRoot<?> ) ) {
|
resultModelPart = targetPart;
|
||||||
resultModelPart = collectionPart.getForeignKeyDescriptor().getPart( collectionPart.getSideNature() );
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resultModelPart = identifierMapping;
|
// If the table group is for a different mapping type i.e. an inheritance subtype,
|
||||||
|
// lookup the target part on that mapping type
|
||||||
|
resultModelPart = entityValuedModelPart.findSubPart( targetPart.getPartName(), null );
|
||||||
}
|
}
|
||||||
interpretationModelPart = modelPart;
|
interpretationModelPart = toOneAttributeMapping;
|
||||||
parentGroupToUse = findTableGroup( tableGroup.getNavigablePath().getParent() );
|
tableGroupToUse = null;
|
||||||
}
|
}
|
||||||
else if ( modelPart instanceof EntityMappingType ) {
|
else if ( inferredEntityMapping instanceof EntityCollectionPart ) {
|
||||||
resultModelPart = ( (EntityMappingType) modelPart ).getIdentifierMapping();
|
// If the inferred mapping is a collection part, we try to make use of the FK again to avoid joins
|
||||||
interpretationModelPart = modelPart;
|
final EntityCollectionPart collectionPart = (EntityCollectionPart) inferredEntityMapping;
|
||||||
parentGroupToUse = null;
|
final TableGroup collectionTableGroup;
|
||||||
|
if ( tableGroup.getModelPart() instanceof CollectionPart ) {
|
||||||
|
collectionTableGroup = findTableGroup( tableGroup.getNavigablePath().getParent() );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resultModelPart = modelPart;
|
collectionTableGroup = tableGroup;
|
||||||
interpretationModelPart = modelPart;
|
|
||||||
parentGroupToUse = null;
|
|
||||||
}
|
|
||||||
final NavigablePath navigablePath;
|
|
||||||
if ( interpretationModelPart == modelPart ) {
|
|
||||||
navigablePath = tableGroup.getNavigablePath();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
navigablePath = tableGroup.getNavigablePath().append( interpretationModelPart.getPartName() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Expression result;
|
if ( entityValuedModelPart == collectionPart ) {
|
||||||
if ( interpretationModelPart instanceof EntityValuedModelPart ) {
|
// When we compare the same collection parts, we can just use the FK part
|
||||||
|
resultModelPart = collectionPart.getForeignKeyDescriptor()
|
||||||
|
.getPart( collectionPart.getSideNature() );
|
||||||
|
}
|
||||||
|
else if ( entityValuedModelPart instanceof EntityAssociationMapping ) {
|
||||||
|
// If the table group model part is an association, we check if the FK targets are compatible
|
||||||
|
final EntityAssociationMapping tableGroupAssociation = (EntityAssociationMapping) entityValuedModelPart;
|
||||||
|
final ModelPart pathTargetPart = tableGroupAssociation.getForeignKeyDescriptor()
|
||||||
|
.getPart( tableGroupAssociation.getSideNature().inverse() );
|
||||||
|
final ModelPart inferredTargetPart = collectionPart.getForeignKeyDescriptor()
|
||||||
|
.getPart( collectionPart.getSideNature().inverse() );
|
||||||
|
// If the inferred association and table group association targets are the same,
|
||||||
|
// or the table group association refers to the primary key, we can safely use the FK part
|
||||||
|
if ( pathTargetPart == inferredTargetPart || tableGroupAssociation.isReferenceToPrimaryKey() ) {
|
||||||
|
resultModelPart = tableGroupAssociation.getForeignKeyDescriptor()
|
||||||
|
.getPart( tableGroupAssociation.getSideNature() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise, we must force the use of the identifier mapping and possibly create a join,
|
||||||
|
// because comparing by primary key is the only sensible thing to do in this case.
|
||||||
|
// Note that EntityValuedPathInterpretation does the same
|
||||||
|
resultModelPart = collectionPart.getAssociatedEntityMappingType().getIdentifierMapping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Since the table group model part is an EntityMappingType,
|
||||||
|
// we can render the FK target model part of the inferred collection part,
|
||||||
|
// which might be a UK, but usually a PK
|
||||||
|
assert entityValuedModelPart instanceof EntityMappingType;
|
||||||
|
if ( collectionPart.getCollectionDescriptor().isOneToMany() ) {
|
||||||
|
// When the inferred mapping is a one-to-many collection part,
|
||||||
|
// we will render the entity identifier mapping for that collection part,
|
||||||
|
// so we will have to do the same for the EntityMappingType side
|
||||||
|
resultModelPart = collectionPart.getAssociatedEntityMappingType().getIdentifierMapping();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resultModelPart = collectionPart.getForeignKeyDescriptor()
|
||||||
|
.getPart( collectionPart.getSideNature().inverse() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interpretationModelPart = inferredEntityMapping;
|
||||||
|
tableGroupToUse = collectionTableGroup;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Render the identifier mapping if the inferred mapping is an EntityMappingType
|
||||||
|
assert inferredEntityMapping instanceof EntityMappingType;
|
||||||
|
resultModelPart = ( (EntityMappingType) inferredEntityMapping ).getIdentifierMapping();
|
||||||
|
interpretationModelPart = inferredEntityMapping;
|
||||||
|
tableGroupToUse = null;
|
||||||
|
}
|
||||||
final boolean expandToAllColumns;
|
final boolean expandToAllColumns;
|
||||||
if ( currentClauseStack.getCurrent() == Clause.GROUP ) {
|
if ( currentClauseStack.getCurrent() == Clause.GROUP ) {
|
||||||
// When the table group is known to be fetched i.e. a fetch join
|
// When the table group is known to be fetched i.e. a fetch join
|
||||||
|
@ -3523,28 +3613,29 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
|
|
||||||
final EntityValuedModelPart mapping = (EntityValuedModelPart) interpretationModelPart;
|
final EntityValuedModelPart mapping = (EntityValuedModelPart) interpretationModelPart;
|
||||||
EntityMappingType mappingType;
|
final EntityMappingType treatedMapping;
|
||||||
if ( path instanceof SqmTreatedPath ) {
|
if ( path instanceof SqmTreatedPath ) {
|
||||||
mappingType = creationContext.getSessionFactory()
|
treatedMapping = creationContext.getSessionFactory()
|
||||||
.getRuntimeMetamodels()
|
.getRuntimeMetamodels()
|
||||||
.getMappingMetamodel()
|
.getMappingMetamodel()
|
||||||
.findEntityDescriptor( ( (SqmTreatedPath<?,?>) path ).getTreatTarget().getHibernateEntityName() );
|
.findEntityDescriptor( ( (SqmTreatedPath<?,?>) path ).getTreatTarget().getHibernateEntityName() );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mappingType = mapping.getEntityMappingType();
|
treatedMapping = mapping.getEntityMappingType();
|
||||||
}
|
}
|
||||||
|
|
||||||
result = EntityValuedPathInterpretation.from(
|
result = EntityValuedPathInterpretation.from(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
parentGroupToUse == null ? tableGroup : parentGroupToUse,
|
tableGroupToUse == null ? tableGroup : tableGroupToUse,
|
||||||
expandToAllColumns ? null : resultModelPart,
|
expandToAllColumns ? null : resultModelPart,
|
||||||
|
true,
|
||||||
(EntityValuedModelPart) interpretationModelPart,
|
(EntityValuedModelPart) interpretationModelPart,
|
||||||
mappingType,
|
treatedMapping,
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if ( interpretationModelPart instanceof EmbeddableValuedModelPart ) {
|
else if ( actualModelPart instanceof EmbeddableValuedModelPart ) {
|
||||||
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) resultModelPart;
|
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) actualModelPart;
|
||||||
result = new EmbeddableValuedPathInterpretation<>(
|
result = new EmbeddableValuedPathInterpretation<>(
|
||||||
mapping.toSqlExpression(
|
mapping.toSqlExpression(
|
||||||
tableGroup,
|
tableGroup,
|
||||||
|
@ -3553,15 +3644,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
getSqlAstCreationState()
|
getSqlAstCreationState()
|
||||||
),
|
),
|
||||||
navigablePath,
|
navigablePath,
|
||||||
(EmbeddableValuedModelPart) interpretationModelPart,
|
mapping,
|
||||||
tableGroup
|
tableGroup
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert interpretationModelPart instanceof BasicValuedModelPart;
|
assert actualModelPart instanceof BasicValuedModelPart;
|
||||||
final BasicValuedModelPart mapping = (BasicValuedModelPart) resultModelPart;
|
final BasicValuedModelPart mapping = (BasicValuedModelPart) actualModelPart;
|
||||||
final TableReference tableReference = tableGroup.resolveTableReference(
|
final TableReference tableReference = tableGroup.resolveTableReference(
|
||||||
navigablePath.append( resultModelPart.getPartName() ),
|
navigablePath.append( actualModelPart.getPartName() ),
|
||||||
mapping.getContainingTableExpression()
|
mapping.getContainingTableExpression()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3591,7 +3682,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
result = new BasicValuedPathInterpretation<>(
|
result = new BasicValuedPathInterpretation<>(
|
||||||
columnReference,
|
columnReference,
|
||||||
navigablePath,
|
navigablePath,
|
||||||
(BasicValuedModelPart) interpretationModelPart,
|
mapping,
|
||||||
tableGroup
|
tableGroup
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3599,7 +3690,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
return withTreatRestriction( result, path );
|
return withTreatRestriction( result, path );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// SqmPath
|
// SqmPath
|
||||||
|
|
||||||
|
@ -3756,15 +3846,18 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object visitFkExpression(SqmFkExpression<?> fkExpression) {
|
public Object visitFkExpression(SqmFkExpression<?> fkExpression) {
|
||||||
final EntityValuedPathInterpretation<?> toOneInterpretation = (EntityValuedPathInterpretation<?>) visitEntityValuedPath( fkExpression.getToOnePath() );
|
final SqmPath<?> lhs = fkExpression.getToOnePath().getLhs();
|
||||||
assert toOneInterpretation.getExpressionType() instanceof ToOneAttributeMapping;
|
prepareReusablePath( lhs, () -> null );
|
||||||
|
final TableGroup tableGroup = getFromClauseIndex().findTableGroup( lhs.getNavigablePath() );
|
||||||
|
final ModelPart subPart = tableGroup.getModelPart()
|
||||||
|
.findSubPart( fkExpression.getToOnePath().getModel().getPathName(), null );
|
||||||
|
assert subPart instanceof ToOneAttributeMapping;
|
||||||
|
|
||||||
final ToOneAttributeMapping toOneMapping = (ToOneAttributeMapping) toOneInterpretation.getExpressionType();
|
final ToOneAttributeMapping toOneMapping = (ToOneAttributeMapping) subPart;
|
||||||
final ForeignKeyDescriptor fkDescriptor = toOneMapping.getForeignKeyDescriptor();
|
final ForeignKeyDescriptor fkDescriptor = toOneMapping.getForeignKeyDescriptor();
|
||||||
final TableGroup tableGroup = toOneInterpretation.getTableGroup();
|
final TableReference tableReference = tableGroup.resolveTableReference( fkDescriptor.getTable( toOneMapping.getSideNature() ) );
|
||||||
final TableReference tableReference = tableGroup.resolveTableReference( fkDescriptor.getKeyTable() );
|
|
||||||
|
|
||||||
final ModelPart fkKeyPart = fkDescriptor.getKeyPart();
|
final ModelPart fkKeyPart = fkDescriptor.getPart( toOneMapping.getSideNature() );
|
||||||
if ( fkKeyPart instanceof BasicValuedModelPart ) {
|
if ( fkKeyPart instanceof BasicValuedModelPart ) {
|
||||||
final BasicValuedModelPart basicFkPart = (BasicValuedModelPart) fkKeyPart;
|
final BasicValuedModelPart basicFkPart = (BasicValuedModelPart) fkKeyPart;
|
||||||
|
|
||||||
|
@ -4686,9 +4779,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
final EntityValuedModelPart entityValuedModelPart = (EntityValuedModelPart) expressible;
|
final EntityValuedModelPart entityValuedModelPart = (EntityValuedModelPart) expressible;
|
||||||
final Object associationKey;
|
final Object associationKey;
|
||||||
final ModelPart associationKeyPart;
|
final ModelPart associationKeyPart;
|
||||||
if ( entityValuedModelPart instanceof Association ) {
|
if ( entityValuedModelPart instanceof EntityAssociationMapping ) {
|
||||||
final Association association = (Association) entityValuedModelPart;
|
final EntityAssociationMapping association = (EntityAssociationMapping) entityValuedModelPart;
|
||||||
final ForeignKeyDescriptor foreignKeyDescriptor = association.getForeignKeyDescriptor();
|
final ForeignKeyDescriptor foreignKeyDescriptor = association.getForeignKeyDescriptor();
|
||||||
|
if ( association.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) {
|
||||||
|
// If the association is the target, we must use the identifier of the EntityMappingType
|
||||||
|
associationKey = association.getAssociatedEntityMappingType().getIdentifierMapping()
|
||||||
|
.getIdentifier( literal.getLiteralValue() );
|
||||||
|
associationKeyPart = association.getAssociatedEntityMappingType().getIdentifierMapping();
|
||||||
|
}
|
||||||
|
else {
|
||||||
associationKey = foreignKeyDescriptor.getAssociationKeyFromSide(
|
associationKey = foreignKeyDescriptor.getAssociationKeyFromSide(
|
||||||
literal.getLiteralValue(),
|
literal.getLiteralValue(),
|
||||||
association.getSideNature().inverse(),
|
association.getSideNature().inverse(),
|
||||||
|
@ -4696,6 +4796,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
);
|
);
|
||||||
associationKeyPart = foreignKeyDescriptor.getPart( association.getSideNature() );
|
associationKeyPart = foreignKeyDescriptor.getPart( association.getSideNature() );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
final EntityIdentifierMapping identifierMapping = entityValuedModelPart.getEntityMappingType()
|
final EntityIdentifierMapping identifierMapping = entityValuedModelPart.getEntityMappingType()
|
||||||
.getIdentifierMapping();
|
.getIdentifierMapping();
|
||||||
|
@ -4974,11 +5075,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
final MappingModelExpressible<?> inferredMapping = resolveInferredType();
|
final MappingModelExpressible<?> inferredMapping = resolveInferredType();
|
||||||
if ( inferredMapping != null ) {
|
if ( inferredMapping != null ) {
|
||||||
if ( inferredMapping instanceof PluralAttributeMapping ) {
|
if ( inferredMapping instanceof PluralAttributeMapping ) {
|
||||||
final CollectionPart elementDescriptor = ( (PluralAttributeMapping) inferredMapping ).getElementDescriptor();
|
return ( (PluralAttributeMapping) inferredMapping ).getElementDescriptor();
|
||||||
if ( elementDescriptor instanceof EntityCollectionPart ) {
|
|
||||||
return ( (EntityCollectionPart) elementDescriptor ).getEntityMappingType();
|
|
||||||
}
|
|
||||||
return elementDescriptor;
|
|
||||||
}
|
}
|
||||||
else if ( !( inferredMapping instanceof JavaObjectType ) ) {
|
else if ( !( inferredMapping instanceof JavaObjectType ) ) {
|
||||||
// Never report back the "object type" as inferred type and instead rely on the value type
|
// Never report back the "object type" as inferred type and instead rely on the value type
|
||||||
|
@ -5005,7 +5102,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
if ( paramType == null ) {
|
if ( paramType == null ) {
|
||||||
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
||||||
if ( inferredValueMapping != null ) {
|
if ( inferredValueMapping != null ) {
|
||||||
return inferredValueMapping;
|
return resolveInferredValueMappingForParameter( inferredValueMapping );
|
||||||
}
|
}
|
||||||
// Default to the Object type
|
// Default to the Object type
|
||||||
return basicType( Object.class );
|
return basicType( Object.class );
|
||||||
|
@ -5019,7 +5116,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
final JdbcMapping inferredJdbcMapping = inferredValueMapping.getJdbcMappings().get( 0 );
|
final JdbcMapping inferredJdbcMapping = inferredValueMapping.getJdbcMappings().get( 0 );
|
||||||
// If the bind type has a different JDBC type, we prefer that
|
// If the bind type has a different JDBC type, we prefer that
|
||||||
if ( paramJdbcMapping.getJdbcType() == inferredJdbcMapping.getJdbcType() ) {
|
if ( paramJdbcMapping.getJdbcType() == inferredJdbcMapping.getJdbcType() ) {
|
||||||
return inferredValueMapping;
|
return resolveInferredValueMappingForParameter( inferredValueMapping );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return paramModelType;
|
return paramModelType;
|
||||||
|
@ -5030,7 +5127,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
// inferrable type and fallback to resolving the binding type
|
// inferrable type and fallback to resolving the binding type
|
||||||
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
||||||
if ( inferredValueMapping != null ) {
|
if ( inferredValueMapping != null ) {
|
||||||
return inferredValueMapping;
|
return resolveInferredValueMappingForParameter( inferredValueMapping );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( paramType instanceof EntityDomainType ) {
|
else if ( paramType instanceof EntityDomainType ) {
|
||||||
|
@ -5039,7 +5136,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
// otherwise this would default to binding the PK which might be wrong
|
// otherwise this would default to binding the PK which might be wrong
|
||||||
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
||||||
if ( inferredValueMapping != null ) {
|
if ( inferredValueMapping != null ) {
|
||||||
return inferredValueMapping;
|
return resolveInferredValueMappingForParameter( inferredValueMapping );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5060,7 +5157,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
modelPart = getFromClauseAccess().getTableGroup( navigablePath ).getModelPart();
|
modelPart = getFromClauseAccess().getTableGroup( navigablePath ).getModelPart();
|
||||||
}
|
}
|
||||||
if ( modelPart instanceof PluralAttributeMapping ) {
|
if ( modelPart instanceof PluralAttributeMapping ) {
|
||||||
return ( (PluralAttributeMapping) modelPart ).getElementDescriptor();
|
return resolveInferredValueMappingForParameter( ( (PluralAttributeMapping) modelPart ).getElementDescriptor() );
|
||||||
}
|
}
|
||||||
return modelPart;
|
return modelPart;
|
||||||
}
|
}
|
||||||
|
@ -5073,7 +5170,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
// Try to infer the value mapping since the other side apparently is a path source
|
// Try to infer the value mapping since the other side apparently is a path source
|
||||||
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
||||||
if ( inferredValueMapping != null ) {
|
if ( inferredValueMapping != null ) {
|
||||||
return inferredValueMapping;
|
return resolveInferredValueMappingForParameter( inferredValueMapping );
|
||||||
}
|
}
|
||||||
throw new NotYetImplementedFor6Exception( "Support for embedded-valued parameters not yet implemented" );
|
throw new NotYetImplementedFor6Exception( "Support for embedded-valued parameters not yet implemented" );
|
||||||
}
|
}
|
||||||
|
@ -5082,21 +5179,21 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
// Try to infer the value mapping since the other side apparently is a path source
|
// Try to infer the value mapping since the other side apparently is a path source
|
||||||
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
|
||||||
if ( inferredValueMapping != null ) {
|
if ( inferredValueMapping != null ) {
|
||||||
return inferredValueMapping;
|
return resolveInferredValueMappingForParameter( inferredValueMapping );
|
||||||
}
|
}
|
||||||
|
|
||||||
final BasicType basicTypeForJavaType = getTypeConfiguration().getBasicTypeForJavaType(
|
final BasicType<?> basicTypeForJavaType = getTypeConfiguration().getBasicTypeForJavaType(
|
||||||
paramSqmType.getExpressibleJavaType().getJavaTypeClass()
|
paramSqmType.getExpressibleJavaType().getJavaTypeClass()
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( basicTypeForJavaType == null ) {
|
if ( basicTypeForJavaType == null ) {
|
||||||
if ( paramSqmType instanceof EntityDomainType ) {
|
if ( paramSqmType instanceof EntityDomainType ) {
|
||||||
return getIdType( (EntityDomainType) paramSqmType );
|
return resolveEntityPersister( (EntityDomainType<?>) paramSqmType );
|
||||||
}
|
}
|
||||||
else if ( paramSqmType instanceof SingularAttribute ) {
|
else if ( paramSqmType instanceof SingularAttribute ) {
|
||||||
final Type type = ( (SingularAttribute) paramSqmType ).getType();
|
final Type<?> type = ( (SingularAttribute<?, ?>) paramSqmType ).getType();
|
||||||
if ( type instanceof EntityDomainType ) {
|
if ( type instanceof EntityDomainType ) {
|
||||||
return getIdType( (EntityDomainType) type );
|
return resolveEntityPersister( (EntityDomainType<?>) type );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5107,13 +5204,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
throw new ConversionException( "Could not determine ValueMapping for SqmParameter: " + sqmParameter );
|
throw new ConversionException( "Could not determine ValueMapping for SqmParameter: " + sqmParameter );
|
||||||
}
|
}
|
||||||
|
|
||||||
private BasicType getIdType(EntityDomainType entityDomainType) {
|
private MappingModelExpressible<?> resolveInferredValueMappingForParameter(MappingModelExpressible<?> inferredValueMapping) {
|
||||||
final SimpleDomainType idType = entityDomainType.getIdType();
|
if ( inferredValueMapping instanceof PluralAttributeMapping ) {
|
||||||
if ( idType != null ) {
|
// For parameters, we resolve to the element descriptor
|
||||||
return getTypeConfiguration().getBasicTypeForJavaType(
|
inferredValueMapping = ( (PluralAttributeMapping) inferredValueMapping ).getElementDescriptor();
|
||||||
idType.getExpressibleJavaType().getJavaTypeClass() );
|
|
||||||
}
|
}
|
||||||
return null;
|
if ( inferredValueMapping instanceof EntityCollectionPart ) {
|
||||||
|
// For parameters, we resolve to the entity mapping type to bind the primary key
|
||||||
|
return ( (EntityCollectionPart) inferredValueMapping ).getAssociatedEntityMappingType();
|
||||||
|
}
|
||||||
|
return inferredValueMapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolveSqmParameter(
|
private void resolveSqmParameter(
|
||||||
|
@ -6293,16 +6393,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) determineValueMapping(
|
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) determineValueMapping(
|
||||||
pluralPath );
|
pluralPath );
|
||||||
|
|
||||||
if ( pluralAttributeMapping.getElementDescriptor() instanceof EntityCollectionPart ) {
|
|
||||||
inferrableTypeAccessStack.push(
|
|
||||||
() -> ( (EntityCollectionPart) pluralAttributeMapping.getElementDescriptor() ).getKeyTargetMatchPart() );
|
|
||||||
}
|
|
||||||
else if ( pluralAttributeMapping.getElementDescriptor() instanceof EmbeddedCollectionPart ) {
|
|
||||||
inferrableTypeAccessStack.push(pluralAttributeMapping::getElementDescriptor);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
inferrableTypeAccessStack.push( () -> pluralAttributeMapping );
|
inferrableTypeAccessStack.push( () -> pluralAttributeMapping );
|
||||||
}
|
|
||||||
|
|
||||||
final Expression lhs;
|
final Expression lhs;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||||
|
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
||||||
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
|
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
|
||||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||||
|
@ -21,16 +22,21 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||||
import org.hibernate.metamodel.mapping.ModelPart;
|
import org.hibernate.metamodel.mapping.ModelPart;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
|
||||||
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||||
import org.hibernate.spi.NavigablePath;
|
import org.hibernate.spi.NavigablePath;
|
||||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||||
import org.hibernate.sql.ast.SqlAstWalker;
|
import org.hibernate.sql.ast.SqlAstWalker;
|
||||||
|
import org.hibernate.sql.ast.spi.FromClauseAccess;
|
||||||
|
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.spi.SqlExpressionResolver;
|
||||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||||
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
|
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.TableGroup;
|
||||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||||
import org.hibernate.sql.ast.tree.update.Assignable;
|
import org.hibernate.sql.ast.tree.update.Assignable;
|
||||||
|
@ -50,13 +56,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
|
||||||
SqmToSqlAstConverter sqlAstCreationState) {
|
SqmToSqlAstConverter sqlAstCreationState) {
|
||||||
final TableGroup tableGroup = sqlAstCreationState
|
final TableGroup tableGroup = sqlAstCreationState
|
||||||
.getFromClauseAccess()
|
.getFromClauseAccess()
|
||||||
.findTableGroup( sqmPath.getLhs().getNavigablePath() );
|
.findTableGroup( sqmPath.getNavigablePath() );
|
||||||
final EntityValuedModelPart pathMapping = (EntityValuedModelPart) sqlAstCreationState
|
final EntityValuedModelPart pathMapping = (EntityValuedModelPart) tableGroup.getModelPart();
|
||||||
.getFromClauseAccess()
|
|
||||||
.findTableGroup( sqmPath.getLhs().getNavigablePath() )
|
|
||||||
.getModelPart()
|
|
||||||
.findSubPart( sqmPath.getReferencedPathSource().getPathName(), null );
|
|
||||||
final EntityValuedModelPart mapping;
|
|
||||||
if ( inferredMapping instanceof EntityAssociationMapping ) {
|
if ( inferredMapping instanceof EntityAssociationMapping ) {
|
||||||
final EntityAssociationMapping inferredAssociation = (EntityAssociationMapping) inferredMapping;
|
final EntityAssociationMapping inferredAssociation = (EntityAssociationMapping) inferredMapping;
|
||||||
if ( pathMapping instanceof EntityAssociationMapping && inferredMapping != pathMapping ) {
|
if ( pathMapping instanceof EntityAssociationMapping && inferredMapping != pathMapping ) {
|
||||||
|
@ -67,54 +68,207 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
|
||||||
.getPart( pathAssociation.getSideNature().inverse() );
|
.getPart( pathAssociation.getSideNature().inverse() );
|
||||||
final ModelPart inferredTargetPart = inferredAssociation.getForeignKeyDescriptor()
|
final ModelPart inferredTargetPart = inferredAssociation.getForeignKeyDescriptor()
|
||||||
.getPart( inferredAssociation.getSideNature().inverse() );
|
.getPart( inferredAssociation.getSideNature().inverse() );
|
||||||
|
|
||||||
// If the inferred association and path association targets are the same, we can use the path mapping type
|
// If the inferred association and path association targets are the same, we can use the path mapping type
|
||||||
// which will render the FK of the path association
|
// which will render the FK of the path association
|
||||||
if ( pathTargetPart == inferredTargetPart ) {
|
if ( pathTargetPart == inferredTargetPart ) {
|
||||||
mapping = pathMapping;
|
return from(
|
||||||
|
sqmPath.getNavigablePath(),
|
||||||
|
tableGroup,
|
||||||
|
pathMapping,
|
||||||
|
inferredMapping,
|
||||||
|
sqlAstCreationState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// When the inferred mapping and the path mapping differ,
|
||||||
|
// we always render the PK for the inferred and mapping path
|
||||||
|
else {
|
||||||
|
// If the path mapping refers to the primary key though,
|
||||||
|
// we can also render FK as that is equivalent
|
||||||
|
if ( pathAssociation.isReferenceToPrimaryKey() ) {
|
||||||
|
return from(
|
||||||
|
sqmPath.getNavigablePath(),
|
||||||
|
tableGroup,
|
||||||
|
pathMapping,
|
||||||
|
inferredMapping,
|
||||||
|
sqlAstCreationState
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Otherwise, we need to use the entity mapping type to force rendering the PK
|
// but if the association FK does not point to the primary key,
|
||||||
// for e.g. `a.assoc1 = a.assoc2` when both associations have different target join columns
|
// we can't allow FK optimizations as that is problematic for self-referential associations,
|
||||||
mapping = pathMapping.getEntityMappingType();
|
// because then we would render the PK of the association owner instead of the target
|
||||||
|
return from(
|
||||||
|
sqmPath.getNavigablePath(),
|
||||||
|
tableGroup,
|
||||||
|
pathMapping.getEntityMappingType().getIdentifierMapping(),
|
||||||
|
false,
|
||||||
|
pathMapping,
|
||||||
|
pathMapping,
|
||||||
|
sqlAstCreationState
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// This is the case when the inferred mapping is an association, but the path mapping is not,
|
// This is the case when the inferred mapping is an association, but the path mapping is not,
|
||||||
// or the path mapping and the inferred mapping are for the same association
|
// or the path mapping and the inferred mapping are for the same association,
|
||||||
mapping = (EntityValuedModelPart) inferredMapping;
|
// in which case we render this path like the inferred mapping
|
||||||
|
return from(
|
||||||
|
sqmPath.getNavigablePath(),
|
||||||
|
tableGroup,
|
||||||
|
(EntityValuedModelPart) inferredMapping,
|
||||||
|
inferredMapping,
|
||||||
|
sqlAstCreationState
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mapping = pathMapping;
|
// No inferred mapping available or it refers to an EntityMappingType
|
||||||
|
return from(
|
||||||
|
sqmPath.getNavigablePath(),
|
||||||
|
tableGroup,
|
||||||
|
pathMapping,
|
||||||
|
inferredMapping,
|
||||||
|
sqlAstCreationState
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> EntityValuedPathInterpretation<T> from(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
EntityValuedModelPart mapping,
|
||||||
|
MappingModelExpressible<?> inferredMapping,
|
||||||
|
SqmToSqlAstConverter sqlAstCreationState) {
|
||||||
|
final boolean allowFkOptimization;
|
||||||
final ModelPart resultModelPart;
|
final ModelPart resultModelPart;
|
||||||
|
final TableGroup resultTableGroup;
|
||||||
|
// For association mappings where the FK optimization i.e. use of the parent table group is allowed,
|
||||||
|
// we try to make use of it and the FK model part if possible based on the inferred mapping
|
||||||
if ( mapping instanceof EntityAssociationMapping ) {
|
if ( mapping instanceof EntityAssociationMapping ) {
|
||||||
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
|
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
|
||||||
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
|
final ModelPart associationKeyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
|
||||||
if ( keyTargetMatchPart instanceof ToOneAttributeMapping ) {
|
final ModelPart keyTargetMatchPart;
|
||||||
resultModelPart = ( (ToOneAttributeMapping) keyTargetMatchPart ).getKeyTargetMatchPart();
|
if ( associationKeyTargetMatchPart instanceof ToOneAttributeMapping ) {
|
||||||
|
keyTargetMatchPart = ( (ToOneAttributeMapping) associationKeyTargetMatchPart ).getKeyTargetMatchPart();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keyTargetMatchPart = associationKeyTargetMatchPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( associationMapping.isFkOptimizationAllowed() ) {
|
||||||
|
final boolean forceUsingForeignKeyAssociationSidePart;
|
||||||
|
// The following is an optimization for EntityCollectionPart path mappings with a join table.
|
||||||
|
// We can possibly avoid doing the join of the target table by using the parent table group
|
||||||
|
// and forcing to use the foreign key part of the association side
|
||||||
|
if ( inferredMapping != null && hasJoinTable( associationMapping ) ) {
|
||||||
|
// But we need to make sure the inferred mapping points to the same FK target
|
||||||
|
if ( inferredMapping instanceof EntityMappingType ) {
|
||||||
|
// If the inferred mapping is an EntityMappingType, meaning it's some sort of root path,
|
||||||
|
// we only have to make sure the FK target is part of the mapping type
|
||||||
|
forceUsingForeignKeyAssociationSidePart = ( (EntityMappingType) inferredMapping ).findSubPart( keyTargetMatchPart.getPartName() ) != null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise, it must be an association mapping
|
||||||
|
assert inferredMapping instanceof EntityAssociationMapping;
|
||||||
|
// Comparing UK-based toOne with PK-based collection part
|
||||||
|
// In this case, we compare based on PK/FK, and can use the FK part if it points to the PK
|
||||||
|
forceUsingForeignKeyAssociationSidePart = ( (EntityAssociationMapping) inferredMapping ).getKeyTargetMatchPart() != keyTargetMatchPart
|
||||||
|
&& associationMapping.isReferenceToPrimaryKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
forceUsingForeignKeyAssociationSidePart = false;
|
||||||
|
}
|
||||||
|
if ( forceUsingForeignKeyAssociationSidePart ) {
|
||||||
|
if ( associationKeyTargetMatchPart instanceof ToOneAttributeMapping ) {
|
||||||
|
resultModelPart = keyTargetMatchPart;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resultModelPart = associationMapping.getForeignKeyDescriptor()
|
||||||
|
.getPart( associationMapping.getSideNature() );
|
||||||
|
}
|
||||||
|
resultTableGroup = sqlAstCreationState.getFromClauseAccess()
|
||||||
|
.findTableGroup( tableGroup.getNavigablePath().getParent() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( isCorrelated( tableGroup, sqlAstCreationState ) ) {
|
||||||
|
// Access to the parent table group is forbidden for correlated table groups. For more details,
|
||||||
|
// see: `ToOneAttributeMapping.createRootTableGroupJoin`
|
||||||
|
// Due to that, we forcefully use the model part to which this association points to i.e. the target
|
||||||
|
resultModelPart = associationMapping.getForeignKeyDescriptor()
|
||||||
|
.getPart( associationMapping.getSideNature().inverse() );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resultModelPart = keyTargetMatchPart;
|
resultModelPart = keyTargetMatchPart;
|
||||||
}
|
}
|
||||||
|
resultTableGroup = tableGroup;
|
||||||
|
}
|
||||||
|
allowFkOptimization = true;
|
||||||
|
}
|
||||||
|
else if ( inferredMapping == null && hasNotFound( mapping ) ) {
|
||||||
|
// This is necessary to allow expression like `where root.notFoundAssociation is null`
|
||||||
|
// to render to `alias.not_found_fk is null`, but IMO this shouldn't be done
|
||||||
|
// todo: discuss removing this part and create a left joined table group instead?
|
||||||
|
resultModelPart = keyTargetMatchPart;
|
||||||
|
resultTableGroup = sqlAstCreationState.getFromClauseAccess()
|
||||||
|
.findTableGroup( tableGroup.getNavigablePath().getParent() );
|
||||||
|
allowFkOptimization = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// If the mapping is an inverse association, use the PK and disallow FK optimizations
|
||||||
|
resultModelPart = ( (EntityAssociationMapping) mapping ).getAssociatedEntityMappingType().getIdentifierMapping();
|
||||||
|
resultTableGroup = tableGroup;
|
||||||
|
allowFkOptimization = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If the mapping is not an association, use the PK and disallow FK optimizations
|
||||||
resultModelPart = mapping.getEntityMappingType().getIdentifierMapping();
|
resultModelPart = mapping.getEntityMappingType().getIdentifierMapping();
|
||||||
|
resultTableGroup = tableGroup;
|
||||||
|
allowFkOptimization = false;
|
||||||
}
|
}
|
||||||
return from(
|
return from(
|
||||||
sqmPath.getNavigablePath(),
|
navigablePath,
|
||||||
tableGroup,
|
resultTableGroup,
|
||||||
resultModelPart,
|
resultModelPart,
|
||||||
|
allowFkOptimization,
|
||||||
mapping,
|
mapping,
|
||||||
mapping,
|
mapping,
|
||||||
sqlAstCreationState
|
sqlAstCreationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isCorrelated(TableGroup tableGroup, SqmToSqlAstConverter sqlAstCreationState) {
|
||||||
|
final SqlAstProcessingState processingState = sqlAstCreationState.getCurrentProcessingState();
|
||||||
|
if ( !( processingState instanceof SqlAstQueryPartProcessingState )
|
||||||
|
|| ( (SqlAstQueryPartProcessingState) processingState ).getInflightQueryPart().isRoot() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess();
|
||||||
|
|
||||||
|
TableGroup realParentTableGroup = fromClauseAccess.findTableGroup( tableGroup.getNavigablePath().getParent() );
|
||||||
|
while ( realParentTableGroup.getModelPart() instanceof EmbeddableValuedModelPart ) {
|
||||||
|
realParentTableGroup = fromClauseAccess.findTableGroup( realParentTableGroup.getNavigablePath().getParent() );
|
||||||
|
}
|
||||||
|
return realParentTableGroup instanceof CorrelatedTableGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasNotFound(EntityValuedModelPart mapping) {
|
||||||
|
return mapping instanceof ToOneAttributeMapping && ( (ToOneAttributeMapping) mapping ).hasNotFoundAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasJoinTable(EntityAssociationMapping associationMapping) {
|
||||||
|
return associationMapping instanceof EntityCollectionPart
|
||||||
|
&& !( (EntityCollectionPart) associationMapping ).getCollectionDescriptor().isOneToMany();
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> EntityValuedPathInterpretation<T> from(
|
public static <T> EntityValuedPathInterpretation<T> from(
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
TableGroup tableGroup,
|
TableGroup tableGroup,
|
||||||
ModelPart resultModelPart,
|
ModelPart resultModelPart,
|
||||||
|
boolean allowFkOptimization,
|
||||||
EntityValuedModelPart mapping,
|
EntityValuedModelPart mapping,
|
||||||
EntityValuedModelPart treatedMapping,
|
EntityValuedModelPart treatedMapping,
|
||||||
SqmToSqlAstConverter sqlAstCreationState) {
|
SqmToSqlAstConverter sqlAstCreationState) {
|
||||||
|
@ -163,7 +317,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
|
||||||
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) resultModelPart;
|
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) resultModelPart;
|
||||||
final TableReference tableReference = tableGroup.resolveTableReference(
|
final TableReference tableReference = tableGroup.resolveTableReference(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
basicValuedModelPart.getContainingTableExpression()
|
basicValuedModelPart.getContainingTableExpression(),
|
||||||
|
allowFkOptimization
|
||||||
);
|
);
|
||||||
sqlExpression = sqlExprResolver.resolveSqlExpression(
|
sqlExpression = sqlExprResolver.resolveSqlExpression(
|
||||||
createColumnReferenceKey( tableReference, basicValuedModelPart.getSelectionExpression() ),
|
createColumnReferenceKey( tableReference, basicValuedModelPart.getSelectionExpression() ),
|
||||||
|
@ -180,7 +335,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
|
||||||
(selectionIndex, selectableMapping) -> {
|
(selectionIndex, selectableMapping) -> {
|
||||||
final TableReference tableReference = tableGroup.resolveTableReference(
|
final TableReference tableReference = tableGroup.resolveTableReference(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
selectableMapping.getContainingTableExpression()
|
selectableMapping.getContainingTableExpression(),
|
||||||
|
allowFkOptimization
|
||||||
);
|
);
|
||||||
expressions.add(
|
expressions.add(
|
||||||
sqlExprResolver.resolveSqlExpression(
|
sqlExprResolver.resolveSqlExpression(
|
||||||
|
|
|
@ -235,7 +235,8 @@ public class NavigablePath implements DotIdentifierSequence, Serializable {
|
||||||
|
|
||||||
public String resolve() {
|
public String resolve() {
|
||||||
if ( buffer == null ) {
|
if ( buffer == null ) {
|
||||||
return null;
|
// Return an empty string instead of null in case the two navigable paths are equal
|
||||||
|
return matchedBase ? "" : null;
|
||||||
}
|
}
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,6 +202,17 @@ public class CollectionMapWithComponentValueTest extends BaseCoreFunctionalTestC
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-11433")
|
||||||
|
public void testJoinMapValue() {
|
||||||
|
doInHibernate( this::sessionFactory, s -> {
|
||||||
|
// Assert that a left join is used for joining the map key entity table
|
||||||
|
List keyValues= s.createQuery( "select v from BaseTestEntity bte left join bte.entities te left join te.values v" ).list();
|
||||||
|
System.out.println( keyValues );
|
||||||
|
assertEquals( 2, keyValues.size() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-11433")
|
@TestForIssue(jiraKey = "HHH-11433")
|
||||||
public void testJoinMapKey() {
|
public void testJoinMapKey() {
|
||||||
|
|
|
@ -0,0 +1,406 @@
|
||||||
|
package org.hibernate.orm.test.query;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.JoinTable;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
@DomainModel(
|
||||||
|
annotatedClasses = { CompareEntityValuedPathsTest.Person.class }
|
||||||
|
)
|
||||||
|
@SessionFactory(statementInspectorClass = SQLStatementInspector.class)
|
||||||
|
@TestForIssue(jiraKey = "HHH-15349")
|
||||||
|
public class CompareEntityValuedPathsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareOneToManyUK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p in (select c from p.childrenUk c)" ).list();
|
||||||
|
|
||||||
|
// Ensure that there are no joins and we compare by UK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.uk in(" +
|
||||||
|
"select c1_0.child_uk " +
|
||||||
|
"from children_uks c1_0 " +
|
||||||
|
"where p1_0.uk=c1_0.owner_uk" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareOneToManyPK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p in (select c from p.children c)" ).list();
|
||||||
|
|
||||||
|
// Ensure that there are no joins and we compare by PK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.id in(" +
|
||||||
|
"select c1_0.children_id " +
|
||||||
|
"from PERSON_TABLE_PERSON_TABLE c1_0 " +
|
||||||
|
"where p1_0.id=c1_0.Person_id" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManyToOneIsNull(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p.parent is null" ).list();
|
||||||
|
|
||||||
|
// Comparing a PK-ManyToOne against a UK-CollectionPart will force comparing by PK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.parent_id is null",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparePKWithOneToManyUK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p.parent in (select c from p.childrenUk c)" ).list();
|
||||||
|
|
||||||
|
// Comparing a PK-ManyToOne against a UK-CollectionPart will force comparing by PK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.parent_id in(" +
|
||||||
|
"select c1_1.id " +
|
||||||
|
"from children_uks c1_0 " +
|
||||||
|
"join PERSON_TABLE c1_1 on c1_1.uk=c1_0.child_uk " +
|
||||||
|
"where p1_0.uk=c1_0.owner_uk" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareUKWithOneToManyPK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p.parentUk in (select c from p.children c)" ).list();
|
||||||
|
|
||||||
|
// Comparing a UK-ManyToOne against a PK-CollectionPart will force comparing by PK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"join PERSON_TABLE p2_0 on p2_0.uk=p1_0.parent_uk " +
|
||||||
|
"where p2_0.id in(" +
|
||||||
|
"select c1_0.children_id " +
|
||||||
|
"from PERSON_TABLE_PERSON_TABLE c1_0 " +
|
||||||
|
"where p1_0.id=c1_0.Person_id" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparePKWithMappedByOneToManyPK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p in (select c from p.employees c)" ).list();
|
||||||
|
|
||||||
|
// Comparing a root against a PK-one-to-many CollectionPart allows comparing by FK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.id in(" +
|
||||||
|
"select e1_0.id " +
|
||||||
|
"from PERSON_TABLE e1_0 " +
|
||||||
|
"where p1_0.id=e1_0.supervisor_id" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparePKWithMappedByOneToManyUK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p in (select c from p.employeesUk c)" ).list();
|
||||||
|
|
||||||
|
// Comparing a root against a UK-one-to-many CollectionPart forces comparing by PK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.id in(" +
|
||||||
|
"select e1_0.id " +
|
||||||
|
"from PERSON_TABLE e1_0 " +
|
||||||
|
"where p1_0.uk=e1_0.supervisor_uk" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOneToManyUKIsNotNull(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p join p.childrenUk c where c is not null" ).list();
|
||||||
|
|
||||||
|
// Assert that we don't join the childrenUk target table, only the join table
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"join children_uks c1_0 on p1_0.uk=c1_0.owner_uk " +
|
||||||
|
"where c1_0.child_uk is not null",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOneToManyPKIsNotNull(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p join p.children c where c is not null" ).list();
|
||||||
|
|
||||||
|
// Assert that we don't join the children target table, only the join table
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"join PERSON_TABLE_PERSON_TABLE c1_0 on p1_0.id=c1_0.Person_id " +
|
||||||
|
"where c1_0.children_id is not null",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareOneToManyUKWithOneToManyPK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p join p.childrenUk c join p.children c2 where c = c2" ).list();
|
||||||
|
|
||||||
|
// Assert that we don't join the childrenUk target table, only the join table
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"join (children_uks c1_0 join PERSON_TABLE c1_1 on c1_1.uk=c1_0.child_uk) on p1_0.uk=c1_0.owner_uk " +
|
||||||
|
"join PERSON_TABLE_PERSON_TABLE c2_0 on p1_0.id=c2_0.Person_id " +
|
||||||
|
"where c1_1.id=c2_0.children_id",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareOneToManyPKWithOneToManyUK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p join p.children c join p.childrenUk c2 where c = c2" ).list();
|
||||||
|
|
||||||
|
// Assert that we don't join the children target table, only the join table
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"join PERSON_TABLE_PERSON_TABLE c1_0 on p1_0.id=c1_0.Person_id " +
|
||||||
|
"join (children_uks c2_0 join PERSON_TABLE c2_1 on c2_1.uk=c2_0.child_uk) on p1_0.uk=c2_0.owner_uk " +
|
||||||
|
"where c1_0.children_id=c2_1.id",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareOneToManyUKWithSubqueryOneToManyPK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p join p.childrenUk c where c in (select c2 from p.children c2)" ).list();
|
||||||
|
|
||||||
|
// Assert that we don't join the childrenUk target table, only the join table
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"join (children_uks c1_0 join PERSON_TABLE c1_1 on c1_1.uk=c1_0.child_uk) on p1_0.uk=c1_0.owner_uk " +
|
||||||
|
"where c1_1.id in(select c2_0.children_id from PERSON_TABLE_PERSON_TABLE c2_0 where p1_0.id=c2_0.Person_id)",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareOneToManyPKWithSubqueryOneToManyUK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p join p.children c where c in (select c2 from p.childrenUk c2)" ).list();
|
||||||
|
|
||||||
|
// Assert that we don't join the children target table, only the join table
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"join PERSON_TABLE_PERSON_TABLE c1_0 on p1_0.id=c1_0.Person_id " +
|
||||||
|
"where c1_0.children_id in(select c2_1.id from children_uks c2_0 join PERSON_TABLE c2_1 on c2_1.uk=c2_0.child_uk where p1_0.uk=c2_0.owner_uk)",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareManyToOneUK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p in (select p2.parentUk from Person p2)" ).list();
|
||||||
|
|
||||||
|
// Ensure that there are no joins and we compare by UK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.uk in(" +
|
||||||
|
"select p2_0.parent_uk " +
|
||||||
|
"from PERSON_TABLE p2_0" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareManyToOnePK(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.createQuery( "select 1 from Person p where p in (select p2.parent from Person p2)" ).list();
|
||||||
|
|
||||||
|
// Ensure that there are no joins and we compare by PK
|
||||||
|
assertEquals(
|
||||||
|
"select " +
|
||||||
|
"1 " +
|
||||||
|
"from PERSON_TABLE p1_0 " +
|
||||||
|
"where p1_0.id in(" +
|
||||||
|
"select p2_0.parent_id " +
|
||||||
|
"from PERSON_TABLE p2_0" +
|
||||||
|
")",
|
||||||
|
statementInspector.getSqlQueries().get( 0 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Person")
|
||||||
|
@Table(name = "PERSON_TABLE")
|
||||||
|
public static class Person {
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true)
|
||||||
|
private String uk;
|
||||||
|
private String name;
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
private Person parent;
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "parent_uk", referencedColumnName = "uk")
|
||||||
|
private Person parentUk;
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
private Person supervisor;
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "supervisor_uk", referencedColumnName = "uk")
|
||||||
|
private Person supervisorUk;
|
||||||
|
@OneToMany
|
||||||
|
private Set<Person> children;
|
||||||
|
@OneToMany
|
||||||
|
@JoinTable(name = "children_uks",
|
||||||
|
joinColumns = @JoinColumn(name = "owner_uk", referencedColumnName = "uk"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "child_uk", referencedColumnName = "uk")
|
||||||
|
)
|
||||||
|
private Set<Person> childrenUk;
|
||||||
|
@OneToMany(mappedBy = "supervisor")
|
||||||
|
private Set<Person> employees;
|
||||||
|
@OneToMany(mappedBy = "supervisorUk")
|
||||||
|
private Set<Person> employeesUk;
|
||||||
|
|
||||||
|
public Person() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
|
||||||
import org.hibernate.annotations.NaturalId;
|
import org.hibernate.annotations.NaturalId;
|
||||||
import org.hibernate.dialect.SybaseDialect;
|
import org.hibernate.dialect.SybaseDialect;
|
||||||
|
@ -29,6 +30,7 @@ import org.hibernate.query.sqm.sql.SqmTranslator;
|
||||||
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
|
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
|
||||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||||
|
@ -43,6 +45,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.hamcrest.core.IsInstanceOf.instanceOf;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
@ -202,9 +205,14 @@ public class EntityJoinTest {
|
||||||
assertThat( roots.size(), is( 1 ) );
|
assertThat( roots.size(), is( 1 ) );
|
||||||
|
|
||||||
final TableGroup rootTableGroup = roots.get( 0 );
|
final TableGroup rootTableGroup = roots.get( 0 );
|
||||||
assertThat( rootTableGroup.getTableGroupJoins().size(), is( 1 ) );
|
assertThat( rootTableGroup.getTableGroupJoins().size(), is( 2 ) );
|
||||||
|
|
||||||
final TableGroupJoin tableGroupJoin = rootTableGroup.getTableGroupJoins().get( 0 );
|
// The first table group is an uninitialized lazy table group for the path in the on clause
|
||||||
|
final TableGroupJoin firstTableGroupJoin = rootTableGroup.getTableGroupJoins().get( 0 );
|
||||||
|
assertThat( firstTableGroupJoin.getJoinedGroup(), instanceOf( LazyTableGroup.class ) );
|
||||||
|
assertThat( ((LazyTableGroup) firstTableGroupJoin.getJoinedGroup()).getUnderlyingTableGroup(), is( CoreMatchers.nullValue() ) );
|
||||||
|
|
||||||
|
final TableGroupJoin tableGroupJoin = rootTableGroup.getTableGroupJoins().get( 1 );
|
||||||
assertThat( tableGroupJoin.getJoinedGroup().getModelPart(), is( customerEntityDescriptor ) );
|
assertThat( tableGroupJoin.getJoinedGroup().getModelPart(), is( customerEntityDescriptor ) );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue