fixed fk predicate rendering into SQL AST for collections;

basic tests for loading + mapped-fetch strategy
This commit is contained in:
Steve Ebersole 2019-11-07 20:08:03 -06:00
parent d200fa9545
commit 3cb6e137bf
8 changed files with 404 additions and 31 deletions

View File

@ -52,7 +52,7 @@ import org.jboss.logging.Logger;
public class MetamodelSelectBuilderProcess {
private static final Logger log = Logger.getLogger( MetamodelSelectBuilderProcess.class );
interface SqlAstDescriptor {
public interface SqlAstDescriptor {
SelectStatement getSqlAst();
List<JdbcParameter> getJdbcParameters();
}

View File

@ -7,7 +7,11 @@
package org.hibernate.metamodel.mapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.JoinType;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
@ -16,4 +20,11 @@ import org.hibernate.sql.results.spi.DomainResultCreationState;
*/
public interface ForeignKeyDescriptor {
DomainResult createDomainResult(NavigablePath collectionPath, TableGroup tableGroup, DomainResultCreationState creationState);
Predicate generateJoinPredicate(
TableGroup lhs,
TableGroup tableGroup,
JoinType joinType,
SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext);
}

View File

@ -40,7 +40,6 @@ import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
@ -647,6 +646,7 @@ public class MappingModelCreationHelper {
bootProperty,
bootValueMapping,
collectionDescriptor,
declaringType,
dialect,
creationProcess
);
@ -849,14 +849,29 @@ public class MappingModelCreationHelper {
Property bootProperty,
Collection bootValueMapping,
CollectionPersister collectionDescriptor,
ManagedMappingType declaringType,
Dialect dialect,
MappingModelCreationProcess creationProcess) {
final Type keyType = bootValueMapping.getKey().getType();
final ModelPart fkTarget;
final String lhsPropertyName = collectionDescriptor.getCollectionType().getLHSPropertyName();
if ( lhsPropertyName == null ) {
fkTarget = collectionDescriptor.getOwnerEntityPersister().getIdentifierMapping();
}
else {
fkTarget = declaringType.findAttributeMapping( lhsPropertyName );
}
if ( keyType instanceof BasicType ) {
assert bootValueMapping.getKey().getColumnSpan() == 1;
assert fkTarget instanceof BasicValuedModelPart;
final BasicValuedModelPart simpleFkTarget = (BasicValuedModelPart) fkTarget;
return new SimpleForeignKeyDescriptor(
bootValueMapping.getKey().getColumnIterator().next().getText( dialect ),
simpleFkTarget.getContainingTableExpression(),
simpleFkTarget.getMappedColumnExpression(),
(BasicType) keyType
);
}
@ -872,31 +887,24 @@ public class MappingModelCreationHelper {
EntityPersister referencedEntityDescriptor,
Dialect dialect,
MappingModelCreationProcess creationProcess) {
if ( bootValueMapping.isReferenceToPrimaryKey() ) {
final EntityIdentifierMapping identifierMapping = referencedEntityDescriptor.getIdentifierMapping();
if ( identifierMapping instanceof BasicEntityIdentifierMapping ) {
final BasicEntityIdentifierMapping simpleIdMapping = (BasicEntityIdentifierMapping) identifierMapping;
assert bootValueMapping.getColumnSpan() == 1;
return new SimpleForeignKeyDescriptor(
bootValueMapping.getColumnIterator().next().getText( dialect ),
simpleIdMapping.getJdbcMapping()
);
}
final ModelPart fkTarget;
if ( bootValueMapping.isReferenceToPrimaryKey() ) {
fkTarget = referencedEntityDescriptor.getIdentifierMapping();
}
else {
final AttributeMapping attributeMapping = referencedEntityDescriptor.findAttributeMapping(
bootValueMapping.getReferencedPropertyName()
);
fkTarget = referencedEntityDescriptor.findSubPart( bootValueMapping.getReferencedPropertyName() );
}
if ( attributeMapping instanceof BasicValuedSingularAttributeMapping ) {
final BasicValuedSingularAttributeMapping basicMapping = (BasicValuedSingularAttributeMapping) attributeMapping;
assert bootValueMapping.getColumnSpan() == 1;
return new SimpleForeignKeyDescriptor(
bootValueMapping.getColumnIterator().next().getText( dialect ),
basicMapping.getJdbcMapping()
);
}
if ( fkTarget instanceof BasicValuedModelPart ) {
final BasicValuedModelPart simpleFkTarget = (BasicValuedModelPart) fkTarget;
return new SimpleForeignKeyDescriptor(
bootValueMapping.getColumnIterator().next().getText( dialect ),
simpleFkTarget.getContainingTableExpression(),
simpleFkTarget.getMappedColumnExpression(),
simpleFkTarget.getJdbcMapping()
);
}
throw new NotYetImplementedFor6Exception(

View File

@ -35,6 +35,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupBuilder;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReferenceCollector;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.internal.domain.collection.DelayedCollectionFetch;
import org.hibernate.sql.results.internal.domain.collection.EagerCollectionFetch;
import org.hibernate.sql.results.spi.DomainResultCreationState;
@ -161,9 +162,10 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
final TableGroup collectionTableGroup = sqlAstCreationState.getFromClauseAccess().resolveTableGroup(
fetchablePath,
p -> {
final TableGroup lhsTableGroup = sqlAstCreationState.getFromClauseAccess().getTableGroup( fetchParent.getNavigablePath() );
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
fetchablePath,
sqlAstCreationState.getFromClauseAccess().getTableGroup( fetchParent.getNavigablePath() ),
lhsTableGroup,
null,
JoinType.LEFT,
lockMode,
@ -171,6 +173,11 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
creationState.getSqlAstCreationState().getSqlExpressionResolver(),
creationState.getSqlAstCreationState().getCreationContext()
);
lhsTableGroup.addTableGroupJoin( tableGroupJoin );
sqlAstCreationState.getFromClauseAccess().registerTableGroup( fetchablePath, tableGroupJoin.getJoinedGroup() );
return tableGroupJoin.getJoinedGroup();
}
);
@ -228,7 +235,23 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
creationContext
);
return new TableGroupJoin( navigablePath, joinType, tableGroupBuilder.build() );
final TableGroup tableGroup = tableGroupBuilder.build();
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
navigablePath,
joinType,
tableGroup,
getKeyDescriptor().generateJoinPredicate(
lhs,
tableGroup,
joinType,
sqlExpressionResolver,
creationContext
)
);
lhs.addTableGroupJoin( tableGroupJoin );
return tableGroupJoin;
}
@Override

View File

@ -8,12 +8,18 @@ package org.hibernate.metamodel.mapping.internal;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.JoinType;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.internal.domain.basic.BasicResult;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
@ -23,10 +29,18 @@ import org.hibernate.sql.results.spi.DomainResultCreationState;
*/
public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor {
private final String keyColumnExpression;
private final String targetColumnContainingTable;
private final String targetColumnExpression;
private final JdbcMapping jdbcMapping;
public SimpleForeignKeyDescriptor(String keyColumnExpression, JdbcMapping jdbcMapping) {
public SimpleForeignKeyDescriptor(
String keyColumnExpression,
String targetColumnContainingTable,
String targetColumnExpression,
JdbcMapping jdbcMapping) {
this.keyColumnExpression = keyColumnExpression;
this.targetColumnContainingTable = targetColumnContainingTable;
this.targetColumnExpression = targetColumnExpression;
this.jdbcMapping = jdbcMapping;
}
@ -61,4 +75,43 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor {
jdbcMapping.getJavaTypeDescriptor()
);
}
@Override
public Predicate generateJoinPredicate(
TableGroup lhs,
TableGroup tableGroup,
JoinType joinType,
SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext) {
final TableReference tableReference = lhs.resolveTableReference( targetColumnContainingTable );
final ColumnReference targetReference = (ColumnReference) sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( tableReference, keyColumnExpression ),
s -> new ColumnReference(
tableReference.getIdentificationVariable(),
targetColumnExpression,
jdbcMapping,
creationContext.getSessionFactory()
)
);
final ColumnReference keyReference = (ColumnReference) sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableGroup.getPrimaryTableReference(),
keyColumnExpression
),
s -> new ColumnReference(
tableGroup.getPrimaryTableReference().getIdentificationVariable(),
keyColumnExpression,
jdbcMapping,
creationContext.getSessionFactory()
)
);
return new ComparisonPredicate(
targetReference,
ComparisonOperator.EQUAL,
keyReference
);
}
}

View File

@ -6150,7 +6150,6 @@ public abstract class AbstractEntityPersister
runtimeAttrDefinition,
bootProperty,
stateArrayPosition++,
this,
creationProcess
)
);
@ -6311,7 +6310,6 @@ public abstract class AbstractEntityPersister
NonIdentifierAttribute tupleAttrDefinition,
Property bootProperty,
int stateArrayPosition,
ManagedMappingType declaringType,
MappingModelCreationProcess creationProcess) {
final String attrName = tupleAttrDefinition.getName();
@ -6333,7 +6331,7 @@ public abstract class AbstractEntityPersister
attrName,
stateArrayPosition,
bootProperty,
declaringType,
this,
(BasicType) attrType,
tableExpression,
attrColumnNames[0],
@ -6347,7 +6345,7 @@ public abstract class AbstractEntityPersister
attrName,
stateArrayPosition,
bootProperty,
declaringType,
this,
(CompositeType) attrType,
tableExpression,
attrColumnNames,
@ -6361,7 +6359,7 @@ public abstract class AbstractEntityPersister
attrName,
stateArrayPosition,
bootProperty,
declaringType,
this,
propertyAccess,
tupleAttrDefinition.getCascadeStyle(),
creationProcess

View File

@ -1015,7 +1015,9 @@ public abstract class AbstractSqlAstWalker
// }
//
comparisonPredicate.getLeftHandExpression().accept( this );
appendSql( " " );
appendSql( comparisonPredicate.getOperator().sqlText() );
appendSql( " " );
comparisonPredicate.getRightHandExpression().accept( this );
}

View File

@ -0,0 +1,278 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.loading;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.Hibernate;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.internal.MetamodelSelectBuilderProcess;
import org.hibernate.metamodel.spi.DomainMetamodel;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.results.internal.domain.basic.BasicFetch;
import org.hibernate.sql.results.internal.domain.collection.DelayedCollectionFetch;
import org.hibernate.sql.results.internal.domain.collection.EagerCollectionFetch;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.EntityResult;
import org.hibernate.sql.results.spi.Fetch;
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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.hamcrest.CoreMatchers;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
@DomainModel(
annotatedClasses = {
MappedFetchTests.RootEntity.class,
MappedFetchTests.SimpleEntity.class
}
)
@SessionFactory
@SuppressWarnings("WeakerAccess")
public class MappedFetchTests {
@Test
public void baseline(SessionFactoryScope scope) {
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
final DomainMetamodel domainModel = sessionFactory.getDomainModel();
final EntityPersister rootEntityDescriptor = domainModel.getEntityDescriptor( RootEntity.class );
final MetamodelSelectBuilderProcess.SqlAstDescriptor sqlAstDescriptor = MetamodelSelectBuilderProcess.createSelect(
sessionFactory,
rootEntityDescriptor,
null,
rootEntityDescriptor.getIdentifierMapping(),
null,
1,
LoadQueryInfluencers.NONE,
LockOptions.NONE
);
assertThat( sqlAstDescriptor.getSqlAst().getDomainResultDescriptors().size(), is( 1 ) );
final DomainResult domainResult = sqlAstDescriptor.getSqlAst().getDomainResultDescriptors().get( 0 );
assertThat( domainResult, instanceOf( EntityResult.class ) );
final EntityResult entityResult = (EntityResult) domainResult;
final List<Fetch> fetches = entityResult.getFetches();
// name + both lists
assertThat( fetches.size(), is( 3 ) );
// order is alphabetical...
final Fetch nameFetch = fetches.get( 0 );
assertThat( nameFetch.getFetchedMapping().getFetchableName(), is( "name" ) );
assertThat( nameFetch, instanceOf( BasicFetch.class ) );
final Fetch nickNamesFetch = fetches.get( 1 );
assertThat( nickNamesFetch.getFetchedMapping().getFetchableName(), is( "nickNames" ) );
assertThat( nickNamesFetch, instanceOf( EagerCollectionFetch.class ) );
final Fetch simpleEntitiesFetch = fetches.get( 2 );
assertThat( simpleEntitiesFetch.getFetchedMapping().getFetchableName(), is( "simpleEntities" ) );
assertThat( simpleEntitiesFetch, instanceOf( DelayedCollectionFetch.class ) );
final QuerySpec querySpec = sqlAstDescriptor.getSqlAst().getQuerySpec();
final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 );
assertThat( tableGroup.getModelPart(), is( rootEntityDescriptor ) );
assertThat( tableGroup.getTableGroupJoins().size(), is( 1 ) );
final TableGroupJoin collectionJoin = tableGroup.getTableGroupJoins().iterator().next();
assertThat( collectionJoin.getJoinedGroup().getModelPart(), is( nickNamesFetch.getFetchedMapping() ) );
assertThat( collectionJoin.getPredicate(), notNullValue() );
assertThat( collectionJoin.getPredicate(), instanceOf( ComparisonPredicate.class ) );
}
@Test
public void smokeTest(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final RootEntity first = session.get( RootEntity.class, 1 );
assertThat( Hibernate.isInitialized( first ), is( true ) );
assertThat( Hibernate.isInitialized( first.getNickNames() ), is( true ) );
assertThat( first.getNickNames().size(), is( 2 ) );
assertThat( Hibernate.isInitialized( first.getSimpleEntities() ), is( false ) );
final RootEntity second = session.get( RootEntity.class, 2 );
assertThat( Hibernate.isInitialized( second ), is( true ) );
assertThat( Hibernate.isInitialized( second.getNickNames() ), is( true ) );
assertThat( second.getNickNames().size(), is( 2 ) );
assertThat( Hibernate.isInitialized( second.getSimpleEntities() ), is( false ) );
}
);
}
@BeforeEach
public void createTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final RootEntity root1 = new RootEntity( 1, "first" );
root1.addNickName( "1st" );
root1.addNickName( "primo" );
session.save( root1 );
final RootEntity root2 = new RootEntity( 2, "second" );
root2.addNickName( "2nd" );
root2.addNickName( "first loser" );
session.save( root2 );
}
);
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.doWork(
connection -> {
try ( Statement statement = connection.createStatement() ) {
statement.execute( "delete nick_names" );
statement.execute( "delete simple_entity" );
statement.execute( "delete root_entity" );
}
}
)
);
}
@Entity( name = "RootEntity" )
@Table( name = "root_entity" )
public static class RootEntity {
private Integer id;
private String name;
private List<String> nickNames;
private List<SimpleEntity> simpleEntities;
public RootEntity() {
}
public RootEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ElementCollection( fetch = FetchType.EAGER )
@CollectionTable( name = "nick_names" )
public List<String> getNickNames() {
return nickNames;
}
public void setNickNames(List<String> nickNames) {
this.nickNames = nickNames;
}
public void addNickName(String name) {
if ( nickNames == null ) {
nickNames = new ArrayList<>();
}
nickNames.add( name );
}
@OneToMany( fetch = FetchType.LAZY )
@JoinColumn( name = "simple_id" )
public List<SimpleEntity> getSimpleEntities() {
return simpleEntities;
}
public void setSimpleEntities(List<SimpleEntity> simpleEntities) {
this.simpleEntities = simpleEntities;
}
public void addSimpleEntity(SimpleEntity simpleEntity) {
if ( simpleEntities == null ) {
simpleEntities = new ArrayList<>();
}
simpleEntities.add( simpleEntity );
}
}
@Entity( name = "SimpleEntity" )
@Table( name = "simple_entity" )
public static class SimpleEntity {
private Integer id;
private String name;
public SimpleEntity() {
}
public SimpleEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}