fixed references to same column, but from different TableReferences (different qualifiers)

This commit is contained in:
Steve Ebersole 2019-10-15 15:30:03 -05:00
parent 39391c86f2
commit 63b22c4c6b
9 changed files with 170 additions and 24 deletions

View File

@ -0,0 +1,13 @@
/*
* 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.metamodel.mapping;
/**
* @author Steve Ebersole
*/
public interface BasicEntityIdentifierMapping extends EntityIdentifierMapping, BasicValuedModelPart {
}

View File

@ -26,6 +26,7 @@ import org.hibernate.sql.ast.Clause;
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.results.internal.domain.basic.BasicFetch;
import org.hibernate.sql.results.internal.domain.basic.BasicResult;
import org.hibernate.sql.results.spi.DomainResult;
@ -109,10 +110,13 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu
private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final TableReference tableReference = tableGroup.resolveTableReference( getContainingTableExpression() );
return expressionResolver.resolveSqlSelection(
expressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
getContainingTableExpression(),
tableReference,
getMappedColumnExpression()
),
sqlAstProcessingState -> tableGroup.resolveColumnReference(
@ -120,7 +124,7 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu
getMappedColumnExpression(),
() -> new ColumnReference(
getMappedColumnExpression(),
tableGroup.resolveTableReference( getContainingTableExpression() ).getIdentificationVariable(),
tableReference.getIdentificationVariable(),
jdbcMapping,
creationState.getSqlAstCreationState().getCreationContext().getSessionFactory()
)
@ -136,18 +140,20 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
// the act of resolving the selection creates the selection if it not already part of the collected selections
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final TableReference tableReference = tableGroup.resolveTableReference( getContainingTableExpression() );
expressionResolver.resolveSqlSelection(
expressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
getContainingTableExpression(),
tableReference,
getMappedColumnExpression()
),
sqlAstProcessingState -> new ColumnReference(
getMappedColumnExpression(),
tableGroup.resolveTableReference( getContainingTableExpression() ).getIdentificationVariable(),
tableReference.getIdentificationVariable(),
jdbcMapping,
creationState.getSqlAstCreationState().getCreationContext().getSessionFactory()
)

View File

@ -184,7 +184,7 @@ public class EmbeddedAttributeMapping
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver().resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
getContainingTableExpression(),
tableReference,
attrColumnExpr
),
sqlAstProcessingState -> tableGroup.resolveColumnReference(

View File

@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.internal;
import java.io.Serializable;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchStyle;
@ -19,6 +20,7 @@ import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
@ -39,9 +41,13 @@ import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.internal.domain.basic.BasicFetch;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.internal.domain.basic.BasicResult;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.sql.results.spi.Fetch;
import org.hibernate.sql.results.spi.FetchParent;
import org.hibernate.type.BasicType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
@ -63,12 +69,15 @@ public class MappingModelCreationHelper {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EntityIdentifier
public static EntityIdentifierMapping buildSimpleIdentifierMapping(
public static BasicEntityIdentifierMapping buildSimpleIdentifierMapping(
EntityPersister entityPersister,
String rootTable,
String pkColumnName,
BasicType idType,
MappingModelCreationProcess creationProcess) {
assert entityPersister.hasIdentifierProperty();
assert entityPersister.getIdentifierPropertyName() != null;
final PersistentClass bootEntityDescriptor = creationProcess.getCreationContext()
.getBootModel()
.getEntityBinding( entityPersister.getEntityName() );
@ -76,7 +85,7 @@ public class MappingModelCreationHelper {
final PropertyAccess propertyAccess = entityPersister.getRepresentationStrategy()
.resolvePropertyAccess( bootEntityDescriptor.getIdentifierProperty() );
return new EntityIdentifierMapping() {
return new BasicEntityIdentifierMapping() {
@Override
public PropertyAccess getPropertyAccess() {
return propertyAccess;
@ -121,12 +130,13 @@ public class MappingModelCreationHelper {
String resultVariable,
DomainResultCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final TableReference rootTableReference = tableGroup.resolveTableReference( rootTable );
final Expression expression = expressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( rootTable, pkColumnName ),
SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ),
sqlAstProcessingState -> new ColumnReference(
pkColumnName,
tableGroup.resolveTableReference( rootTable ).getIdentificationVariable(),
rootTableReference.getIdentificationVariable(),
( (BasicValuedMapping) entityPersister.getIdentifierType() ).getJdbcMapping(),
creationProcess.getCreationContext().getSessionFactory()
)
@ -152,14 +162,10 @@ public class MappingModelCreationHelper {
TableGroup tableGroup,
DomainResultCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
// todo (6.0) : in the original 6.0 work `#resolveSqlExpression` worked based on an overload to handle qualifiable versus un-qualifiable expressables.
// - that gets awkward in terms of managing which overloaded form to call. Perhaps a better
// option would be to use heterogeneous keys - e.g. an array for a qualifiable expressable (alias + expressable)
// or a String concatenation
final TableReference rootTableReference = tableGroup.resolveTableReference( rootTable );
final Expression expression = expressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( rootTable, pkColumnName ),
SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ),
sqlAstProcessingState -> new ColumnReference(
pkColumnName,
rootTable,
@ -176,7 +182,58 @@ public class MappingModelCreationHelper {
);
}
@Override
public String getContainingTableExpression() {
return rootTable;
}
@Override
public BasicValueConverter getConverter() {
return null;
}
@Override
public String getMappedColumnExpression() {
return pkColumnName;
}
@Override
public JdbcMapping getJdbcMapping() {
return idType;
}
@Override
public String getFetchableName() {
return entityPersister.getIdentifierPropertyName();
}
@Override
public FetchStrategy getMappedFetchStrategy() {
return FetchStrategy.IMMEDIATE_JOIN;
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
LockMode lockMode,
String resultVariable,
DomainResultCreationState creationState) {
return new BasicFetch<>(
0,
fetchParent,
fetchablePath,
this,
false,
null,
FetchTiming.IMMEDIATE,
creationState
);
}
};
}
public static EntityIdentifierMapping buildEncapsulatedCompositeIdentifierMapping(

View File

@ -1270,7 +1270,7 @@ public abstract class AbstractEntityPersister
final String rootPkColumnName = rootPkColumnNames[ columnIndex ];
final Expression pkColumnExpression = sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
rootTableReference.getTableExpression(),
rootTableReference,
rootPkColumnName
),
sqlAstProcessingState -> new ColumnReference(
@ -1284,7 +1284,7 @@ public abstract class AbstractEntityPersister
final String fkColumnName = fkColumnNames[ columnIndex ];
final Expression fkColumnExpression = sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
joinedTableReference.getTableExpression(),
joinedTableReference,
fkColumnName
),
sqlAstProcessingState -> new ColumnReference(
@ -6310,6 +6310,10 @@ public abstract class AbstractEntityPersister
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
LOG.tracef( "#findSubPart(`%s`)", name );
if ( isIdentifierReference( name ) ) {
return identifierMapping;
}
final AttributeMapping declaredAttribute = declaredAttributeMappings.get( name );
if ( declaredAttribute != null ) {
return declaredAttribute;
@ -6335,6 +6339,23 @@ public abstract class AbstractEntityPersister
return null;
}
private boolean isIdentifierReference(String name) {
if ( EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( name ) ) {
return true;
}
if ( entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
if ( hasIdentifierProperty() ) {
final String identifierPropertyName = getIdentifierPropertyName();
if ( identifierPropertyName.equals( name ) ) {
return true;
}
}
}
return "id".equals( name );
}
@Override
public void visitSubParts(
Consumer<ModelPart> consumer,

View File

@ -10,6 +10,7 @@ import java.util.function.Function;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
@ -39,6 +40,17 @@ public interface SqlExpressionResolver {
static String createColumnReferenceKey(String tableExpression, String columnExpression) {
return tableExpression + columnExpression;
}
/**
* Helper for generating an expression key for a column reference.
*
* @see #resolveSqlExpression
*/
static String createColumnReferenceKey(TableReference tableReference, String columnExpression) {
final String qualifier = tableReference.getIdentificationVariable() == null
? tableReference.getTableExpression()
: tableReference.getIdentificationVariable();
return qualifier + columnExpression;
}
/**
* Given a qualifier + a qualifiable SqlExpressable, resolve the

View File

@ -21,6 +21,7 @@ import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
@ -43,15 +44,16 @@ public class BasicValuedPathInterpretation<T> implements AssignableSqmPathInterp
null
);
final TableReference tableReference = tableGroup.resolveTableReference( mapping.getContainingTableExpression() );
final ColumnReference columnReference = (ColumnReference) sqlAstCreationState.getSqlExpressionResolver().resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
mapping.getContainingTableExpression(),
tableReference,
mapping.getMappedColumnExpression()
),
sacs -> new ColumnReference(
mapping.getMappedColumnExpression(),
tableGroup.resolveTableReference( mapping.getContainingTableExpression() )
.getIdentificationVariable(),
tableReference.getIdentificationVariable(),
mapping.getJdbcMapping(),
sqlAstCreationState.getCreationContext().getSessionFactory()
)

View File

@ -68,7 +68,7 @@ public abstract class AbstractColumnReferenceQualifier implements ColumnReferenc
String columnExpression,
Supplier<ColumnReference> creator) {
return columnReferenceMap.computeIfAbsent(
SqlExpressionResolver.createColumnReferenceKey( tableExpression, columnExpression ),
SqlExpressionResolver.createColumnReferenceKey( resolveTableReference( tableExpression ), columnExpression ),
s -> creator.get()
);
}
@ -76,7 +76,7 @@ public abstract class AbstractColumnReferenceQualifier implements ColumnReferenc
@Override
public ColumnReference resolveColumnReference(String tableExpression, String columnExpression) {
return columnReferenceMap.get(
SqlExpressionResolver.createColumnReferenceKey( tableExpression, columnExpression )
SqlExpressionResolver.createColumnReferenceKey( resolveTableReference( tableExpression ), columnExpression )
);
}
}

View File

@ -24,6 +24,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
@ -198,6 +199,40 @@ public class SmokeTests {
);
}
@Test
public void testColumnQualification(SessionFactoryScope scope) {
// make sure column references to the same column qualified by different table references works properly
// first, let's create a second entity
scope.inTransaction(
session -> {
SimpleEntity simpleEntity = new SimpleEntity();
simpleEntity.setId( 2 );
simpleEntity.setGender( MALE );
simpleEntity.setName( "Andrea" );
simpleEntity.setGender2( FEMALE );
simpleEntity.setComponent( new Component( "b1", "b2" ) );
session.save( simpleEntity );
}
);
scope.inTransaction(
session -> {
final Object[] result = session.createQuery( "select e, e2 from SimpleEntity e, SimpleEntity e2 where e.id = 1 and e2.id = 2", Object[].class )
.uniqueResult();
assertThat( result, notNullValue() );
assertThat( result[0], instanceOf( SimpleEntity.class ) );
assertThat( ( (SimpleEntity) result[0] ).getId(), is( 1 ) );
assertThat( ( (SimpleEntity) result[0] ).getComponent().getAttribute1(), is( "a1" ) );
assertThat( result[1], instanceOf( SimpleEntity.class ) );
assertThat( ( (SimpleEntity) result[1] ).getId(), is( 2 ) );
assertThat( ( (SimpleEntity) result[1] ).getComponent().getAttribute1(), is( "b1" ) );
}
);
}
@Test
public void testHqlQueryReuseWithDiffParameterBinds(SessionFactoryScope scope) {
// first, let's create a second entity