Various fixes and move tests from test.jpa

* Remodel `@MapKey` support to not create subqueries in the on-clause anymore
* Make sure the index table group is reused for the to-one association a `@MapKey` refers to
* Consistently register collection part table groups
* Implement support for FK optimization for EntityCollectionPart
This commit is contained in:
Christian Beikov 2021-11-10 16:56:06 +01:00
parent 38d1c122eb
commit 9a329f4991
86 changed files with 1585 additions and 728 deletions

View File

@ -57,6 +57,7 @@ import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.sql.Template;
@ -185,6 +186,7 @@ public class MapBinder extends CollectionBinder {
mapProperty.getValue(), map, targetPropertyName, associatedClass, targetPropertyPersistentClass, buildingContext
);
map.setIndex( indexValue );
map.setMapKeyPropertyName( mapKeyPropertyName );
}
else {
//this is a true Map mapping
@ -411,13 +413,14 @@ public class MapBinder extends CollectionBinder {
PersistentClass associatedClass,
PersistentClass targetPropertyPersistentClass,
MetadataBuildingContext buildingContext) {
final Value element = collection.getElement();
final String fromAndWhere = resolveFromAndWhere(
collection,
associatedClass,
targetPropertyPersistentClass,
element
);
final Table mapKeyTable;
// HHH-11005 - only if we are OneToMany and location of map key property is at a different level, need to add a select
if ( !associatedClass.equals( targetPropertyPersistentClass ) ) {
mapKeyTable = targetPropertyPersistentClass.getTable();
}
else {
mapKeyTable = associatedClass.getTable();
}
if ( value instanceof Component ) {
Component component = (Component) value;
@ -452,37 +455,23 @@ public class MapBinder extends CollectionBinder {
final BasicValue sourceValue = (BasicValue) value;
final DependantBasicValue dependantBasicValue = new DependantBasicValue(
getBuildingContext(),
collection.getTable(),
mapKeyTable,
sourceValue,
false,
false
);
String formulaString;
final Selectable sourceValueColumn = sourceValue.getColumn();
if ( sourceValueColumn instanceof Column ) {
formulaString = ( (Column) sourceValueColumn ).getQuotedName();
dependantBasicValue.addColumn( ( (Column) sourceValueColumn ).clone() );
}
else if ( sourceValueColumn instanceof Formula ) {
formulaString = ( (Formula) sourceValueColumn ).getFormula();
dependantBasicValue.addFormula( new Formula( ( (Formula) sourceValueColumn ).getFormula() ) );
}
else {
throw new AssertionFailure( "Unknown element column type : " + sourceValueColumn.getClass() );
}
if ( fromAndWhere != null ) {
final Dialect dialect = buildingContext.getBootstrapContext().getServiceRegistry()
.getService( JdbcServices.class )
.getDialect();
formulaString = Template.renderWhereStringTemplate( formulaString, "$alias$", dialect );
formulaString = "(select " + formulaString + fromAndWhere + ")";
formulaString = StringHelper.replace( formulaString, "$alias$", "a987" );
}
final Formula formula = new Formula( formulaString );
dependantBasicValue.addFormula( formula );
return dependantBasicValue;
}
else if ( value instanceof SimpleValue ) {
@ -490,7 +479,7 @@ public class MapBinder extends CollectionBinder {
SimpleValue targetValue;
if ( value instanceof ManyToOne ) {
ManyToOne sourceManyToOne = (ManyToOne) sourceValue;
ManyToOne targetManyToOne = new ManyToOne( getBuildingContext(), collection.getCollectionTable() );
ManyToOne targetManyToOne = new ManyToOne( getBuildingContext(), mapKeyTable );
targetManyToOne.setFetchMode( FetchMode.DEFAULT );
targetManyToOne.setLazy( true );
//targetValue.setIgnoreNotFound( ); does not make sense for a map key
@ -498,39 +487,21 @@ public class MapBinder extends CollectionBinder {
targetValue = targetManyToOne;
}
else {
targetValue = new BasicValue( getBuildingContext(), collection.getCollectionTable() );
targetValue = new BasicValue( getBuildingContext(), mapKeyTable );
targetValue.copyTypeFrom( sourceValue );
}
Iterator columns = sourceValue.getColumnIterator();
Random random = new Random();
final Iterator<Selectable> columns = sourceValue.getColumnIterator();
while ( columns.hasNext() ) {
Object current = columns.next();
Formula formula = new Formula();
String formulaString;
Selectable current = columns.next();
if ( current instanceof Column ) {
formulaString = ( (Column) current ).getQuotedName();
targetValue.addColumn( ( (Column) current ).clone() );
}
else if ( current instanceof Formula ) {
formulaString = ( (Formula) current ).getFormula();
targetValue.addFormula( new Formula( ( (Formula) current ).getFormula() ) );
}
else {
throw new AssertionFailure( "Unknown element in column iterator: " + current.getClass() );
}
if ( fromAndWhere != null ) {
final Dialect dialect = buildingContext.getBootstrapContext().getServiceRegistry()
.getService( JdbcServices.class )
.getDialect();
formulaString = Template.renderWhereStringTemplate( formulaString, "$alias$", dialect );
formulaString = "(select " + formulaString + fromAndWhere + ")";
formulaString = StringHelper.replace(
formulaString,
"$alias$",
"a" + random.nextInt( 16 )
);
}
formula.setFormula( formulaString );
targetValue.addFormula( formula );
}
return targetValue;
}
@ -538,76 +509,4 @@ public class MapBinder extends CollectionBinder {
throw new AssertionFailure( "Unknown type encountered for map key: " + value.getClass() );
}
}
private String resolveFromAndWhere(
Collection collection,
PersistentClass associatedClass,
PersistentClass targetPropertyPersistentClass,
Value element) {
if ( ! OneToMany.class.isInstance( element ) ) {
String referencedPropertyName = null;
if ( element instanceof ToOne ) {
referencedPropertyName = ( (ToOne) element ).getReferencedPropertyName();
}
else if ( element instanceof DependantValue ) {
//TODO this never happen I think
if ( propertyName != null ) {
referencedPropertyName = collection.getReferencedPropertyName();
}
else {
throw new AnnotationException( "SecondaryTable JoinColumn cannot reference a non primary key" );
}
}
Iterator<Selectable> referencedEntityColumns;
if ( referencedPropertyName == null ) {
referencedEntityColumns = associatedClass.getIdentifier().getColumnIterator();
}
else {
Property referencedProperty = associatedClass.getRecursiveProperty( referencedPropertyName );
referencedEntityColumns = referencedProperty.getColumnIterator();
}
return getFromAndWhereFormula(
associatedClass.getTable().getQualifiedTableName().toString(),
element.getColumnIterator(),
referencedEntityColumns
);
}
else {
// HHH-11005 - only if we are OneToMany and location of map key property is at a different level, need to add a select
if ( !associatedClass.equals( targetPropertyPersistentClass ) ) {
return getFromAndWhereFormula(
targetPropertyPersistentClass.getTable()
.getQualifiedTableName()
.toString(),
element.getColumnIterator(),
associatedClass.getIdentifier().getColumnIterator()
);
}
}
return null;
}
private String getFromAndWhereFormula(
String tableName,
Iterator<Selectable> collectionTableColumns,
Iterator<Selectable> referencedEntityColumns) {
String alias = "$alias$";
StringBuilder fromAndWhereSb = new StringBuilder( " from " )
.append( tableName )
//.append(" as ") //Oracle doesn't support it in subqueries
.append( " " )
.append( alias ).append( " where " );
while ( collectionTableColumns.hasNext() ) {
Column colColumn = (Column) collectionTableColumns.next();
Column refColumn = (Column) referencedEntityColumns.next();
fromAndWhereSb.append( alias )
.append( '.' )
.append( refColumn.getQuotedName() )
.append( '=' )
.append( colColumn.getQuotedName() )
.append( " and " );
}
return fromAndWhereSb.substring( 0, fromAndWhereSb.length() - 5 );
}
}

View File

@ -44,7 +44,6 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.Joinable;
@ -52,6 +51,7 @@ import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl;
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
@ -60,6 +60,7 @@ import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
@ -395,6 +396,7 @@ public class LoaderSelectBuilder {
rootQuerySpec.getFromClause().addRoot( rootTableGroup );
sqlAstCreationState.getFromClauseAccess().registerTableGroup( rootNavigablePath, rootTableGroup );
registerPluralTableGroupParts( sqlAstCreationState.getFromClauseAccess(), rootTableGroup );
if ( partsToSelect != null && !partsToSelect.isEmpty() ) {
domainResults = new ArrayList<>( partsToSelect.size() );
@ -409,11 +411,13 @@ public class LoaderSelectBuilder {
null,
SqlAstJoinType.LEFT,
true,
false,
sqlAstCreationState
);
rootTableGroup.addTableGroupJoin( tableGroupJoin );
tableGroup = tableGroupJoin.getJoinedGroup();
sqlAstCreationState.getFromClauseAccess().registerTableGroup( navigablePath, tableGroup );
registerPluralTableGroupParts( sqlAstCreationState.getFromClauseAccess(), tableGroup );
}
else {
tableGroup = rootTableGroup;
@ -470,7 +474,7 @@ public class LoaderSelectBuilder {
if ( loadable instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) loadable;
applyFiltering( rootQuerySpec, rootTableGroup, pluralAttributeMapping );
applyFiltering( rootQuerySpec, rootTableGroup, pluralAttributeMapping, sqlAstCreationState.getFromClauseAccess() );
applyOrdering( rootTableGroup, pluralAttributeMapping );
}
else if ( loadable instanceof Joinable ) {
@ -580,10 +584,10 @@ public class LoaderSelectBuilder {
private void applyFiltering(
QuerySpec querySpec,
TableGroup tableGroup,
PluralAttributeMapping pluralAttributeMapping) {
PluralAttributeMapping pluralAttributeMapping,
FromClauseAccess fromClauseAccess) {
final CollectionPersister collectionPersister = pluralAttributeMapping.getCollectionDescriptor();
final Joinable joinable = collectionPersister
.getCollectionType()
final Joinable joinable = collectionPersister.getCollectionType()
.getAssociatedJoinable( creationContext.getSessionFactory() );
final Predicate filterPredicate = FilterHelper.createFilterPredicate(
loadQueryInfluencers,
@ -601,18 +605,23 @@ public class LoaderSelectBuilder {
tableGroup
);
if ( manyToManyFilterPredicate != null ) {
TableGroupJoin elementTableGroupJoin = null;
for ( TableGroupJoin nestedTableGroupJoin : tableGroup.getNestedTableGroupJoins() ) {
final NavigablePath navigablePath = nestedTableGroupJoin.getNavigablePath();
if ( navigablePath.getParent() == tableGroup.getNavigablePath()
&& CollectionPart.Nature.ELEMENT.getName().equals( navigablePath.getUnaliasedLocalName() ) ) {
elementTableGroupJoin = nestedTableGroupJoin;
final NavigablePath parentNavigablePath = tableGroup.getNavigablePath().getParent();
if ( parentNavigablePath == null ) {
querySpec.applyPredicate( manyToManyFilterPredicate );
}
else {
final TableGroup parentTableGroup = fromClauseAccess.getTableGroup( parentNavigablePath );
TableGroupJoin pluralTableGroupJoin = null;
for ( TableGroupJoin nestedTableGroupJoin : parentTableGroup.getTableGroupJoins() ) {
if ( nestedTableGroupJoin.getNavigablePath() == tableGroup.getNavigablePath() ) {
pluralTableGroupJoin = nestedTableGroupJoin;
break;
}
}
assert elementTableGroupJoin != null;
elementTableGroupJoin.applyPredicate( manyToManyFilterPredicate );
assert pluralTableGroupJoin != null;
pluralTableGroupJoin.applyPredicate( manyToManyFilterPredicate );
}
}
}
}
@ -852,7 +861,8 @@ public class LoaderSelectBuilder {
applyFiltering(
querySpec,
joinTableGroup,
pluralAttributeMapping
pluralAttributeMapping,
creationState.getFromClauseAccess()
);
applyOrdering(
querySpec,
@ -945,6 +955,7 @@ public class LoaderSelectBuilder {
rootQuerySpec.getFromClause().addRoot( rootTableGroup );
sqlAstCreationState.getFromClauseAccess().registerTableGroup( rootNavigablePath, rootTableGroup );
registerPluralTableGroupParts( sqlAstCreationState.getFromClauseAccess(), rootTableGroup );
// generate and apply the restriction
applySubSelectRestriction(
@ -956,7 +967,7 @@ public class LoaderSelectBuilder {
);
// NOTE : no need to check - we are explicitly processing a plural-attribute
applyFiltering( rootQuerySpec, rootTableGroup, attributeMapping );
applyFiltering( rootQuerySpec, rootTableGroup, attributeMapping, sqlAstCreationState.getFromClauseAccess() );
applyOrdering( rootTableGroup, attributeMapping );
// register the jdbc-parameters
@ -1103,5 +1114,23 @@ public class LoaderSelectBuilder {
return subQuery;
}
private void registerPluralTableGroupParts(FromClauseAccess fromClauseAccess, TableGroup tableGroup) {
if ( tableGroup instanceof PluralTableGroup ) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup;
if ( pluralTableGroup.getElementTableGroup() != null ) {
fromClauseAccess.registerTableGroup(
pluralTableGroup.getElementTableGroup().getNavigablePath(),
pluralTableGroup.getElementTableGroup()
);
}
if ( pluralTableGroup.getIndexTableGroup() != null ) {
fromClauseAccess.registerTableGroup(
pluralTableGroup.getIndexTableGroup().getNavigablePath(),
pluralTableGroup.getIndexTableGroup()
);
}
}
}
}

View File

@ -13,6 +13,7 @@ import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
@ -56,6 +57,7 @@ public interface Loadable extends ModelPart, RootTableGroupProducer {
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver expressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
}

View File

@ -54,15 +54,16 @@ public abstract class IndexedCollection extends Collection {
pk.addColumns( getKey().getColumnIterator() );
// index should be last column listed
boolean isFormula = false;
Iterator iter = getIndex().getColumnIterator();
boolean indexIsPartOfElement = false;
final Iterator<Selectable> iter = getIndex().getColumnIterator();
while ( iter.hasNext() ) {
if ( ( (Selectable) iter.next() ).isFormula() ) {
isFormula=true;
final Selectable selectable = iter.next();
if ( selectable.isFormula() || !getCollectionTable().containsColumn( (Column) selectable ) ) {
indexIsPartOfElement = true;
}
}
if (isFormula) {
//if it is a formula index, use the element columns in the PK
if ( indexIsPartOfElement ) {
//if it is part of the element, use the element columns in the PK
pk.addColumns( getElement().getColumnIterator() );
}
else {

View File

@ -22,6 +22,9 @@ import org.hibernate.type.SortedMapType;
* the key columns + index columns.
*/
public class Map extends IndexedCollection {
private String mapKeyPropertyName;
public Map(MetadataBuildingContext buildingContext, PersistentClass owner) {
super( buildingContext, owner );
}
@ -30,6 +33,14 @@ public class Map extends IndexedCollection {
return true;
}
public String getMapKeyPropertyName() {
return mapKeyPropertyName;
}
public void setMapKeyPropertyName(String mapKeyPropertyName) {
this.mapKeyPropertyName = mapKeyPropertyName;
}
@Override
public CollectionSemantics getDefaultCollectionSemantics() {
if ( isSorted() ) {

View File

@ -31,6 +31,7 @@ import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -38,7 +39,7 @@ import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReference;
@ -170,8 +171,10 @@ public abstract class AbstractCompositeIdentifierMapping
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
@ -182,6 +185,7 @@ public abstract class AbstractCompositeIdentifierMapping
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
@ -198,8 +202,9 @@ public abstract class AbstractCompositeIdentifierMapping
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
return new CompositeTableGroup( navigablePath, this, lhs, fetched );
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
}
@Override

View File

@ -22,6 +22,7 @@ import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -196,6 +197,31 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
return superMappingType.getRootEntityDescriptor();
}
/**
* Adapts the table group and its table reference as well as table reference joins
* in a way such that unnecessary tables or joins are omitted if possible,
* based on the given treated entity names.
*
* The goal is to e.g. remove join inheritance "branches" or union selects that are impossible.
*
* Consider the following example:
* <code>
* class BaseEntity {}
* class Sub1 extends BaseEntity {}
* class Sub1Sub1 extends Sub1 {}
* class Sub1Sub2 extends Sub1 {}
* class Sub2 extends BaseEntity {}
* class Sub2Sub1 extends Sub2 {}
* class Sub2Sub2 extends Sub2 {}
* </code>
*
* If the <code>treatedEntityNames</code> only contains <code>Sub1</code> or any of its subtypes,
* this means that <code>Sub2</code> and all subtypes are impossible,
* thus the joins/selects for these types shall be omitted in the given table group.
*
* @param tableGroup The table group to prune subclass tables for
* @param treatedEntityNames The entity names for which path usages were registered
*/
default void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
}
@ -297,6 +323,7 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
additionalPredicateCollectorAccess,
creationState.getSqlAliasBaseGenerator().createSqlAliasBase( getSqlAliasStem() ),
creationState.getSqlExpressionResolver(),
creationState.getFromClauseAccess(),
creationContext
);
}
@ -309,6 +336,7 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver expressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
return getEntityPersister().createRootTableGroup(
canUseInnerJoins,
@ -317,6 +345,7 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
additionalPredicateCollectorAccess,
sqlAliasBase,
expressionResolver,
fromClauseAccess,
creationContext
);
}

View File

@ -35,6 +35,7 @@ public interface PluralAttributeMapping
interface IndexMetadata {
CollectionPart getIndexDescriptor();
int getListIndexBase();
String getIndexPropertyName();
}
IndexMetadata getIndexMetadata();

View File

@ -34,6 +34,20 @@ public interface Queryable extends ModelPart {
*/
ModelPart findSubPart(String name, EntityMappingType treatTargetType);
default ModelPart findByPath(String path) {
int nextStart = 0;
int dotIndex;
Queryable modelPartContainer = this;
while ( ( dotIndex = path.indexOf( '.', nextStart ) ) != -1 ) {
modelPartContainer = (Queryable) modelPartContainer.findSubPart(
path.substring( nextStart, dotIndex ),
null
);
nextStart = dotIndex + 1;
}
return modelPartContainer.findSubPart( path.substring( nextStart ), null );
}
default ModelPart resolveSubPart(DotIdentifierSequence path) {
return path.resolve(
(ModelPart) this,

View File

@ -233,7 +233,6 @@ public class BasicAttributeMapping
getContainingTableExpression(),
allowFkOptimization
);
final String tableAlias = tableReference.getIdentificationVariable();
return expressionResolver.resolveSqlSelection(
expressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
@ -241,7 +240,7 @@ public class BasicAttributeMapping
mappedColumnExpression
),
sqlAstProcessingState -> new ColumnReference(
tableAlias,
tableReference,
this,
creationState.getSqlAstCreationState().getCreationContext().getSessionFactory()
)

View File

@ -31,7 +31,9 @@ import org.hibernate.sql.ast.Clause;
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.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.ResultsLogger;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
@ -129,7 +131,7 @@ public class BasicValuedCollectionPart
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
final SqlSelection sqlSelection = resolveSqlSelection( tableGroup, creationState );
final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, true, creationState );
//noinspection unchecked
return new BasicResult(
@ -141,17 +143,35 @@ public class BasicValuedCollectionPart
);
}
private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) {
private SqlSelection resolveSqlSelection(
NavigablePath navigablePath,
TableGroup tableGroup,
boolean allowFkOptimization,
DomainResultCreationState creationState) {
final SqlExpressionResolver exprResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final TableGroup targetTableGroup;
// If the index is part of the element table group, we must use that explicitly here because the index is basic
// and thus there is no index table group registered. The logic in the PluralTableGroup prevents from looking
// into the element table group though because the element table group navigable path is not the parent of this navigable path
if ( nature == Nature.INDEX && collectionDescriptor.getAttributeMapping().getIndexMetadata().getIndexPropertyName() != null ) {
targetTableGroup = ( (PluralTableGroup) tableGroup ).getElementTableGroup();
}
else {
targetTableGroup = tableGroup;
}
final TableReference tableReference = targetTableGroup.resolveTableReference(
navigablePath,
getContainingTableExpression(),
allowFkOptimization
);
return exprResolver.resolveSqlSelection(
exprResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableGroup.getPrimaryTableReference(),
tableReference,
selectableMapping.getSelectionExpression()
),
sqlAstProcessingState -> new ColumnReference(
tableGroup.getPrimaryTableReference().getIdentificationVariable(),
tableReference,
selectableMapping,
creationState.getSqlAstCreationState().getCreationContext().getSessionFactory()
)
@ -164,7 +184,7 @@ public class BasicValuedCollectionPart
@Override
public void applySqlSelections(
NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) {
resolveSqlSelection( tableGroup, creationState );
resolveSqlSelection( navigablePath, tableGroup, true, creationState );
}
@Override
@ -173,7 +193,7 @@ public class BasicValuedCollectionPart
TableGroup tableGroup,
DomainResultCreationState creationState,
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
selectionConsumer.accept( resolveSqlSelection( tableGroup, creationState ), getJdbcMapping() );
selectionConsumer.accept( resolveSqlSelection( navigablePath, tableGroup, true, creationState ), getJdbcMapping() );
}
@Override
@ -223,7 +243,7 @@ public class BasicValuedCollectionPart
final TableGroup tableGroup = creationState.getSqlAstCreationState()
.getFromClauseAccess()
.findTableGroup( parentNavigablePath );
final SqlSelection sqlSelection = resolveSqlSelection( tableGroup, creationState );
final SqlSelection sqlSelection = resolveSqlSelection( fetchablePath, tableGroup, true, creationState );
return new BasicFetch<>(
sqlSelection.getValuesArrayPosition(),

View File

@ -14,10 +14,8 @@ import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
@ -32,6 +30,7 @@ import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -40,7 +39,7 @@ 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.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
@ -291,8 +290,10 @@ public class EmbeddedAttributeMapping
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
@ -303,6 +304,7 @@ public class EmbeddedAttributeMapping
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
@ -319,8 +321,9 @@ public class EmbeddedAttributeMapping
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
return new CompositeTableGroup(
return new StandardVirtualTableGroup(
navigablePath,
this,
lhs,

View File

@ -29,6 +29,7 @@ import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -37,7 +38,8 @@ 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.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.predicate.Predicate;
@ -97,6 +99,8 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
// Make sure the pre-created table group for the part is registered under its navigable path
resolveTableGroup( navigablePath, creationState );
return new EmbeddableResultImpl<>(
navigablePath,
this,
@ -153,6 +157,8 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
// Make sure the pre-created table group for the part is registered under its navigable path
resolveTableGroup( fetchablePath, creationState );
return new EmbeddableFetchImpl(
fetchablePath,
this,
@ -163,6 +169,24 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
);
}
private TableGroup resolveTableGroup(NavigablePath fetchablePath, DomainResultCreationState creationState) {
final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess();
return fromClauseAccess.resolveTableGroup(
fetchablePath,
np -> {
final PluralTableGroup parentTableGroup = (PluralTableGroup) fromClauseAccess.getTableGroup( np.getParent() );
switch ( nature ) {
case ELEMENT:
return parentTableGroup.getElementTableGroup();
case INDEX:
return parentTableGroup.getIndexTableGroup();
}
throw new IllegalStateException( "Could not find table group for: " + np );
}
);
}
@Override
public SqlTuple toSqlExpression(
TableGroup tableGroup,
@ -201,8 +225,10 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
@ -213,6 +239,7 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
@ -229,10 +256,11 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
assert lhs.getModelPart() instanceof PluralAttributeMapping;
return new CompositeTableGroup( navigablePath, this, lhs, fetched );
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
}
@Override

View File

@ -28,7 +28,6 @@ import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectableMappings;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause;
@ -316,6 +315,7 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
null,
SqlAstJoinType.INNER,
true,
false,
creationState.getSqlAstCreationState()
);
tableGroup.addTableGroupJoin( tableGroupJoin );

View File

@ -19,6 +19,7 @@ import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.Map;
import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.ToOne;
@ -33,12 +34,14 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
@ -49,6 +52,7 @@ import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.OneToManyTableGroup;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
@ -95,7 +99,10 @@ public class EntityCollectionPart
this.entityMappingType = entityMappingType;
final String referencedPropertyName;
if ( bootModelValue instanceof OneToMany ) {
referencedPropertyName = null;
final String mappedByProperty = collectionDescriptor.getMappedByProperty();
referencedPropertyName = mappedByProperty == null || mappedByProperty.isEmpty()
? null
: mappedByProperty;
}
else {
referencedPropertyName = ( (ToOne) bootModelValue ).getReferencedPropertyName();
@ -139,7 +146,20 @@ public class EntityCollectionPart
this.targetKeyPropertyNames = targetKeyPropertyNames;
}
else if ( bootModelValue instanceof OneToMany ) {
this.targetKeyPropertyNames = Collections.singleton( referencedPropertyName );
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
int dotIndex = -1;
while ( ( dotIndex = referencedPropertyName.indexOf( '.', dotIndex + 1 ) ) != -1 ) {
targetKeyPropertyNames.add( referencedPropertyName.substring( 0, dotIndex ) );
}
final Type propertyType = ( (PropertyMapping) entityMappingType.getEntityPersister() )
.toType( referencedPropertyName );
ToOneAttributeMapping.addPrefixedPropertyNames(
targetKeyPropertyNames,
referencedPropertyName,
propertyType,
creationProcess.getCreationContext().getSessionFactory()
);
this.targetKeyPropertyNames = targetKeyPropertyNames;
}
else {
final EntityMetamodel entityMetamodel = entityMappingType.getEntityPersister().getEntityMetamodel();
@ -152,7 +172,7 @@ public class EntityCollectionPart
}
else {
final String mapsIdAttributeName;
if ( ( mapsIdAttributeName = ToOneAttributeMapping.mapsId( entityMappingType, referencedPropertyName ) ) != null ) {
if ( ( mapsIdAttributeName = ToOneAttributeMapping.findMapsIdPropertyName( entityMappingType, referencedPropertyName ) ) != null ) {
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
targetKeyPropertyNames.add( referencedPropertyName );
ToOneAttributeMapping.addPrefixedPropertyNames(
@ -177,8 +197,33 @@ public class EntityCollectionPart
String fkTargetModelPartName,
MappingModelCreationProcess creationProcess) {
if ( fkTargetModelPartName == null ) {
if ( nature == Nature.INDEX ) {
final String mapKeyPropertyName = ( (Map) bootValueMapping ).getMapKeyPropertyName();
if ( mapKeyPropertyName == null ) {
fkTargetModelPart = entityMappingType.getIdentifierMapping();
}
else {
final EntityPersister elementPersister = ( (EntityType) collectionDescriptor.getElementType() )
.getAssociatedEntityPersister( creationProcess.getCreationContext().getSessionFactory() );
fkTargetModelPart = elementPersister.findByPath( mapKeyPropertyName );
if ( fkTargetModelPart == null ) {
throw new RuntimeException( "Couldn't find model part for path [" + mapKeyPropertyName + "] on entity: " + elementPersister.getEntityName() );
}
}
}
else {
final String mappedByProperty = bootValueMapping.getMappedByProperty();
if ( collectionDescriptor.isOneToMany() && mappedByProperty != null && !mappedByProperty.isEmpty() ) {
fkTargetModelPart = entityMappingType.findByPath( mappedByProperty );
if ( fkTargetModelPart == null ) {
throw new RuntimeException( "Couldn't find model part for path [" + mappedByProperty + "] on entity: " + entityMappingType.getEntityName() );
}
}
else {
fkTargetModelPart = entityMappingType.getIdentifierMapping();
}
}
}
else {
fkTargetModelPart = entityMappingType.findSubPart( fkTargetModelPartName, null );
}
@ -206,15 +251,35 @@ public class EntityCollectionPart
MappingModelCreationProcess creationProcess,
Dialect dialect) {
final EntityPersister associatedEntityDescriptor = creationProcess.getEntityPersister( entityType.getAssociatedEntityName() );
final ModelPart fkTargetPart = entityType.isReferenceToPrimaryKey()
? associatedEntityDescriptor.getIdentifierMapping()
: associatedEntityDescriptor.findSubPart( entityType.getRHSUniqueKeyPropertyName() );
// If this is mapped by a to-one attribute, we can use the FK of that attribute
if ( fkTargetModelPart instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) fkTargetModelPart;
if ( toOneAttributeMapping.getForeignKeyDescriptor() == null ) {
throw new RuntimeException( "Not yet ready: " + toOneAttributeMapping );
}
return toOneAttributeMapping.getForeignKeyDescriptor();
}
final ModelPart fkTargetPart = fkTargetModelPart;
final String fkKeyTableName;
if ( nature == Nature.INDEX ) {
final String indexPropertyName = collectionDescriptor.getAttributeMapping()
.getIndexMetadata()
.getIndexPropertyName();
if ( indexPropertyName == null ) {
fkKeyTableName = ( (Joinable) collectionDescriptor ).getTableName();
}
else {
fkKeyTableName = fkBootDescriptorSource.getTable().getQuotedName( dialect );
}
}
else {
fkKeyTableName = ( (Joinable) collectionDescriptor ).getTableName();
}
if ( fkTargetPart instanceof BasicValuedModelPart ) {
final BasicValuedModelPart basicFkTargetPart = (BasicValuedModelPart) fkTargetPart;
final Joinable collectionDescriptorAsJoinable = (Joinable) collectionDescriptor;
final SelectableMapping keySelectableMapping = SelectableMappingImpl.from(
collectionDescriptorAsJoinable.getTableName(),
fkKeyTableName,
fkBootDescriptorSource.getColumnIterator().next(),
basicFkTargetPart.getJdbcMapping(),
dialect,
@ -284,7 +349,9 @@ public class EntityCollectionPart
@Override
public ModelPart getKeyTargetMatchPart() {
return fkTargetModelPart;
return collectionDescriptor.isOneToMany()
? entityMappingType.getIdentifierMapping()
: fkTargetModelPart;
}
@Override
@ -308,35 +375,26 @@ public class EntityCollectionPart
}
@Override
public EntityFetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
// find or create the TableGroup associated with this `fetchablePath`
final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess();
creationState.registerVisitedAssociationKey( getForeignKeyDescriptor().getAssociationKey() );
final TableGroup partTableGroup = fromClauseAccess.resolveTableGroup(
fetchablePath,
np -> {
final TableGroup parentTableGroup = fromClauseAccess.getTableGroup( np.getParent() );
if ( collectionDescriptor.isOneToMany() && nature == Nature.ELEMENT ) {
return ( (OneToManyTableGroup) parentTableGroup ).getElementTableGroup();
}
for ( TableGroupJoin nestedTableGroupJoin : parentTableGroup.getNestedTableGroupJoins() ) {
if ( nestedTableGroupJoin.getNavigablePath().equals( np ) ) {
return nestedTableGroupJoin.getJoinedGroup();
}
public ModelPart findSubPart(String name) {
return findSubPart( name, null );
}
throw new IllegalStateException( "Could not find table group for: " + np );
@Override
public ModelPart findSubPart(String name, EntityMappingType targetType) {
// Prefer resolving the key part of the foreign key rather than the target part if possible
// to allow deferring the initialization of the target table group, omitting it if possible.
// This is not possible for one-to-many associations because we need to create the target table group eagerly,
// to preserve the cardinality. Also, the OneToManyTableGroup has no reference to the parent table group
if ( !collectionDescriptor.isOneToMany() && targetKeyPropertyNames.contains( name ) ) {
if ( fkDescriptor.getTargetPart() instanceof NonAggregatedIdentifierMappingImpl ) {
return ( (ModelPartContainer) fkDescriptor.getKeyPart() ).findSubPart( name, targetType );
}
);
return new EntityFetchJoinedImpl( fetchParent, this, partTableGroup, selected, fetchablePath, creationState );
if ( fkTargetModelPart instanceof ToOneAttributeMapping ) {
return fkTargetModelPart;
}
return fkDescriptor.getKeyPart();
}
return EntityValuedFetchable.super.findSubPart( name, targetType );
}
@Override
@ -355,30 +413,42 @@ public class EntityCollectionPart
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess();
final TableGroup partTableGroup = fromClauseAccess.resolveTableGroup(
navigablePath,
np -> {
final TableGroup parentTableGroup = fromClauseAccess.getTableGroup( np.getParent() );
if ( collectionDescriptor.isOneToMany() && nature == Nature.ELEMENT ) {
return ( (OneToManyTableGroup) parentTableGroup ).getElementTableGroup();
}
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
navigablePath,
parentTableGroup,
resultVariable,
SqlAstJoinType.INNER,
true,
creationState.getSqlAstCreationState()
);
parentTableGroup.addTableGroupJoin( tableGroupJoin );
return tableGroupJoin.getJoinedGroup();
}
);
final TableGroup partTableGroup = resolveTableGroup( navigablePath, creationState );
return entityMappingType.createDomainResult( navigablePath, partTableGroup, resultVariable, creationState );
}
@Override
public EntityFetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
creationState.registerVisitedAssociationKey( getForeignKeyDescriptor().getAssociationKey() );
final TableGroup partTableGroup = resolveTableGroup( fetchablePath, creationState );
return new EntityFetchJoinedImpl( fetchParent, this, partTableGroup, selected, fetchablePath, creationState );
}
private TableGroup resolveTableGroup(NavigablePath fetchablePath, DomainResultCreationState creationState) {
final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess();
return fromClauseAccess.resolveTableGroup(
fetchablePath,
np -> {
final PluralTableGroup parentTableGroup = (PluralTableGroup) fromClauseAccess.getTableGroup( np.getParent() );
switch ( nature ) {
case ELEMENT:
return parentTableGroup.getElementTableGroup();
case INDEX:
return parentTableGroup.getIndexTableGroup();
}
throw new IllegalStateException( "Could not find table group for: " + np );
}
);
}
@Override
public int forEachSelectable(int offset, SelectableConsumer consumer) {
return entityMappingType.forEachSelectable( offset, consumer );
@ -452,8 +522,10 @@ public class EntityCollectionPart
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
if ( collectionDescriptor.isOneToMany() && nature == Nature.ELEMENT ) {
// If this is a one-to-many, the element part is already available, so we return a TableGroupJoin "hull"
@ -474,6 +546,7 @@ public class EntityCollectionPart
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
final TableGroupJoin join = new TableGroupJoin(
@ -483,13 +556,11 @@ public class EntityCollectionPart
null
);
final TableReference keySideTableReference = collectionTableGroup.getPrimaryTableReference();
lazyTableGroup.setTableGroupInitializerCallback(
tableGroup -> join.applyPredicate(
fkDescriptor.generateJoinPredicate(
tableGroup.getPrimaryTableReference(),
keySideTableReference,
collectionTableGroup.resolveTableReference( fkDescriptor.getKeyTable() ),
sqlAstJoinType,
sqlExpressionResolver,
creationContext
@ -510,6 +581,7 @@ public class EntityCollectionPart
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final boolean canUseInnerJoin = sqlAstJoinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();

View File

@ -39,6 +39,7 @@ import org.hibernate.mapping.Component;
import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.Map;
import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.PersistentClass;
@ -56,6 +57,7 @@ import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
import org.hibernate.metamodel.mapping.CollectionMappingType;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.GeneratedValueResolver;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PropertyBasedMapping;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectableMappings;
@ -619,7 +621,8 @@ public class MappingModelCreationHelper {
final RuntimeModelCreationContext creationContext = creationProcess.getCreationContext();
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final Dialect dialect = sessionFactory.getJdbcServices().getJdbcEnvironment().getDialect();
final JdbcEnvironment jdbcEnvironment = sessionFactory.getJdbcServices().getJdbcEnvironment();
final Dialect dialect = jdbcEnvironment.getDialect();
final MappingMetamodel domainModel = creationContext.getDomainModel();
final CollectionPersister collectionDescriptor = domainModel.findCollectionDescriptor( bootValueMapping.getRole() );
@ -736,11 +739,20 @@ public class MappingModelCreationHelper {
jtdRegistry.getDescriptor( mapJavaType ),
collectionSemantics
);
final String mapKeyTableExpression;
if ( bootValueMapping instanceof Map && ( (Map) bootValueMapping ).getMapKeyPropertyName() != null ) {
mapKeyTableExpression = jdbcEnvironment.getQualifiedObjectNameFormatter().format(
( (Map) bootValueMapping ).getIndex().getTable().getQualifiedTableName(),
dialect
);
}
else {
mapKeyTableExpression = tableExpression;
}
indexDescriptor = interpretMapKey(
bootValueMapping,
collectionDescriptor,
tableExpression,
mapKeyTableExpression,
sqlAliasStem,
dialect,
creationProcess
@ -888,7 +900,8 @@ public class MappingModelCreationHelper {
MappingModelCreationProcess creationProcess) {
ModelPart attributeMappingSubPart = null;
if ( !StringHelper.isEmpty( collectionDescriptor.getMappedByProperty() ) ) {
attributeMappingSubPart = attributeMapping.findSubPart( collectionDescriptor.getMappedByProperty(), null );
attributeMappingSubPart = ( (ModelPartContainer) attributeMapping.getElementDescriptor().getPartMappingType() )
.findSubPart( collectionDescriptor.getMappedByProperty(), null );
}
if ( attributeMappingSubPart instanceof ToOneAttributeMapping ) {
@ -1300,6 +1313,7 @@ public class MappingModelCreationHelper {
final Type identifierOrUniqueKeyType = entityType.getIdentifierOrUniqueKeyType(
creationProcess.getCreationContext().getSessionFactory()
);
if ( identifierOrUniqueKeyType instanceof ComponentType ) {
componentType = (ComponentType) identifierOrUniqueKeyType;
if ( bootValueMapping instanceof ToOne ) {
sorted = ( (ToOne) bootValueMapping ).isSorted();
@ -1309,6 +1323,11 @@ public class MappingModelCreationHelper {
sorted = true;
}
}
else {
// This happens when we have a one-to-many with a mapped-by associations that has a basic FK
return new int[] { 0 };
}
}
// Consider the reordering if available
if ( !sorted && componentType.getOriginalPropertyOrder() != null ) {
return componentType.getOriginalPropertyOrder();

View File

@ -20,6 +20,7 @@ import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.mapping.List;
import org.hibernate.mapping.Map;
import org.hibernate.mapping.Property;
import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
import org.hibernate.metamodel.mapping.CollectionMappingType;
@ -51,6 +52,7 @@ import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.CollectionTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.OneToManyTableGroup;
import org.hibernate.sql.ast.tree.predicate.Predicate;
@ -85,6 +87,7 @@ public class PluralAttributeMappingImpl
private final PropertyAccess propertyAccess;
private final StateArrayContributorMetadataAccess stateArrayContributorMetadataAccess;
private final String referencedPropertyName;
private final String mapKeyPropertyName;
private final CollectionPart elementDescriptor;
private final CollectionPart indexDescriptor;
@ -135,6 +138,13 @@ public class PluralAttributeMappingImpl
this.collectionDescriptor = collectionDescriptor;
this.referencedPropertyName = bootDescriptor.getReferencedPropertyName();
if ( bootDescriptor instanceof Map ) {
this.mapKeyPropertyName = ( (Map) bootDescriptor ).getMapKeyPropertyName();
}
else {
this.mapKeyPropertyName = null;
}
this.bidirectionalAttributeName = StringHelper.subStringNullIfEmpty( bootDescriptor.getMappedByProperty(), '.');
this.sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromAttributeName( attributeName );
@ -146,18 +156,14 @@ public class PluralAttributeMappingImpl
separateCollectionTable = ( (Joinable) collectionDescriptor ).getTableName();
}
indexMetadata = new IndexMetadata() {
final int baseIndex;
{
if ( bootDescriptor instanceof List ) {
baseIndex = ( (List) bootDescriptor ).getBaseIndex();
}
else {
baseIndex = -1;
}
}
indexMetadata = new IndexMetadata() {
@Override
public CollectionPart getIndexDescriptor() {
return indexDescriptor;
@ -167,6 +173,11 @@ public class PluralAttributeMappingImpl
public int getListIndexBase() {
return baseIndex;
}
@Override
public String getIndexPropertyName() {
return mapKeyPropertyName;
}
};
if ( collectionDescriptor instanceof Aware ) {
@ -443,6 +454,7 @@ public class PluralAttributeMappingImpl
null,
SqlAstJoinType.LEFT,
true,
false,
creationState.getSqlAstCreationState()
);
lhsTableGroup.addTableGroupJoin( tableGroupJoin );
@ -496,8 +508,10 @@ public class PluralAttributeMappingImpl
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final java.util.List<Predicate> predicates = new ArrayList<>( 2 );
final TableGroup tableGroup = createRootTableGroupJoin(
@ -509,6 +523,7 @@ public class PluralAttributeMappingImpl
predicates::add,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
@ -531,6 +546,7 @@ public class PluralAttributeMappingImpl
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final CollectionPersister collectionDescriptor = getCollectionDescriptor();
final TableGroup tableGroup;
@ -542,6 +558,7 @@ public class PluralAttributeMappingImpl
explicitSourceAlias,
aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() ),
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
}
@ -553,6 +570,7 @@ public class PluralAttributeMappingImpl
explicitSourceAlias,
aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() ),
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
}
@ -582,6 +600,7 @@ public class PluralAttributeMappingImpl
String sourceAlias,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final TableGroup elementTableGroup = ( (EntityCollectionPart) elementDescriptor ).createTableGroupInternal(
canUseInnerJoins,
@ -598,15 +617,17 @@ public class PluralAttributeMappingImpl
creationContext.getSessionFactory()
);
if ( indexDescriptor instanceof EntityCollectionPart ) {
final TableGroupJoin tableGroupJoin = ( (EntityCollectionPart) indexDescriptor ).createTableGroupJoin(
if ( indexDescriptor instanceof TableGroupJoinProducer ) {
final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) indexDescriptor ).createTableGroupJoin(
navigablePath.append( CollectionPart.Nature.INDEX.getName() ),
elementTableGroup,
tableGroup,
null,
SqlAstJoinType.INNER,
fetched,
false,
stem -> sqlAliasBase,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
tableGroup.registerIndexTableGroup( tableGroupJoin );
@ -622,6 +643,7 @@ public class PluralAttributeMappingImpl
String sourceAlias,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
assert !getCollectionDescriptor().isOneToMany();
@ -647,33 +669,38 @@ public class PluralAttributeMappingImpl
creationContext.getSessionFactory()
);
if ( indexDescriptor instanceof EntityCollectionPart ) {
final TableGroupJoin tableGroupJoin = ( (EntityCollectionPart) indexDescriptor ).createTableGroupJoin(
navigablePath.append( CollectionPart.Nature.INDEX.getName() ),
tableGroup,
null,
SqlAstJoinType.INNER,
fetched,
stem -> sqlAliasBase,
sqlExpressionResolver,
creationContext
);
tableGroup.registerIndexTableGroup( tableGroupJoin );
}
if ( elementDescriptor instanceof EntityCollectionPart ) {
final TableGroupJoin tableGroupJoin = ( (EntityCollectionPart) elementDescriptor ).createTableGroupJoin(
if ( elementDescriptor instanceof TableGroupJoinProducer ) {
final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) elementDescriptor ).createTableGroupJoin(
navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ),
tableGroup,
null,
SqlAstJoinType.INNER,
fetched,
false,
stem -> sqlAliasBase,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
tableGroup.registerElementTableGroup( tableGroupJoin );
}
if ( indexDescriptor instanceof TableGroupJoinProducer ) {
final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) indexDescriptor ).createTableGroupJoin(
navigablePath.append( CollectionPart.Nature.INDEX.getName() ),
tableGroup,
null,
SqlAstJoinType.INNER,
fetched,
false,
stem -> sqlAliasBase,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
tableGroup.registerIndexTableGroup( tableGroupJoin );
}
return tableGroup;
}
@ -692,6 +719,7 @@ public class PluralAttributeMappingImpl
additionalPredicateCollectorAccess,
creationState.getSqlAliasBaseGenerator().createSqlAliasBase( getSqlAliasStem() ),
creationState.getSqlExpressionResolver(),
creationState.getFromClauseAccess(),
creationContext
);
}
@ -704,6 +732,7 @@ public class PluralAttributeMappingImpl
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver expressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
if ( getCollectionDescriptor().isOneToMany() ) {
return createOneToManyTableGroup(
@ -713,6 +742,7 @@ public class PluralAttributeMappingImpl
explicitSourceAlias,
sqlAliasBase,
expressionResolver,
fromClauseAccess,
creationContext
);
}
@ -724,6 +754,7 @@ public class PluralAttributeMappingImpl
explicitSourceAlias,
sqlAliasBase,
expressionResolver,
fromClauseAccess,
creationContext
);
}

View File

@ -33,6 +33,7 @@ import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.ToOne;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -40,6 +41,7 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
@ -62,6 +64,8 @@ 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.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.MappedByTableGroup;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
@ -108,7 +112,23 @@ public class ToOneAttributeMapping
private final NavigableRole navigableRole;
private final String sqlAliasStem;
// The nullability of the actual FK column
private final boolean isNullable;
/*
The nullability of the table on which the FK column is located
Note that this can be null although the FK column is not nullable e.g. in the case of a join table
@Entity
public class Entity1 {
@OneToOne
@JoinTable(name = "key_table")
Entity2 association;
}
Here the join to "key_table" is nullable, but the FK column is not null.
Choosing an inner join for the association would be wrong though, because of the nullability of the key table,
hence this flag is also controlling the default join type.
*/
private final boolean isKeyTableNullable;
private final boolean isConstrained;
private final boolean isIgnoreNotFound;
@ -422,7 +442,9 @@ public class ToOneAttributeMapping
else {
this.targetKeyPropertyName = referencedPropertyName;
final String mapsIdAttributeName;
if ( ( mapsIdAttributeName = mapsId( entityMappingType, referencedPropertyName ) ) != null ) {
// If there is a "virtual property" for a non-PK join mapping, we try to see if the columns match the
// primary key columns and if so, we add the primary key property name as target key property
if ( ( mapsIdAttributeName = findMapsIdPropertyName( entityMappingType, referencedPropertyName ) ) != null ) {
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
targetKeyPropertyNames.add( targetKeyPropertyName );
addPrefixedPropertyNames(
@ -487,7 +509,7 @@ public class ToOneAttributeMapping
return true;
}
static String mapsId(EntityMappingType entityMappingType, String referencedPropertyName) {
static String findMapsIdPropertyName(EntityMappingType entityMappingType, String referencedPropertyName) {
final AbstractEntityPersister persister = (AbstractEntityPersister) entityMappingType.getEntityPersister();
if ( Arrays.equals( persister.getKeyColumnNames(), persister.getPropertyColumnNames( referencedPropertyName ) ) ) {
return persister.getIdentifierPropertyName();
@ -616,7 +638,20 @@ public class ToOneAttributeMapping
// Prefer resolving the key part of the foreign key rather than the target part if possible
// This way, we don't have to register table groups the target entity type
if ( canUseParentTableGroup && targetKeyPropertyNames.contains( name ) ) {
return foreignKeyDescriptor.getKeyPart();
final ModelPart fkSideModelPart;
final ModelPart fkTargetModelPart;
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
fkTargetModelPart = foreignKeyDescriptor.getTargetPart();
fkSideModelPart = foreignKeyDescriptor.getKeyPart();
}
else {
fkTargetModelPart = foreignKeyDescriptor.getKeyPart();
fkSideModelPart = foreignKeyDescriptor.getTargetPart();
}
if ( fkTargetModelPart instanceof NonAggregatedIdentifierMappingImpl ) {
return ( (ModelPartContainer) fkSideModelPart ).findSubPart( name, targetType );
}
return fkSideModelPart;
}
return EntityValuedFetchable.super.findSubPart( name, targetType );
}
@ -953,6 +988,7 @@ public class ToOneAttributeMapping
resultVariable,
getJoinType( fetchablePath, parentTableGroup ),
true,
false,
creationState.getSqlAstCreationState()
);
parentTableGroup.addTableGroupJoin( tableGroupJoin );
@ -969,6 +1005,7 @@ public class ToOneAttributeMapping
resultVariable,
getDefaultSqlAstJoinType( parentTableGroup ),
true,
false,
creationState.getSqlAstCreationState()
);
parentTableGroup.addTableGroupJoin( tableGroupJoin );
@ -1063,7 +1100,7 @@ public class ToOneAttributeMapping
private boolean isSelectByUniqueKey(ForeignKeyDescriptor.Nature side) {
if ( side == ForeignKeyDescriptor.Nature.KEY ) {
// case 1.2
return !getKeyTargetMatchPart().getNavigableRole()
return !foreignKeyDescriptor.getNavigableRole()
.equals( entityMappingType.getIdentifierMapping().getNavigableRole() );
}
else {
@ -1093,6 +1130,7 @@ public class ToOneAttributeMapping
null,
getDefaultSqlAstJoinType( tableGroup ),
true,
false,
creationState.getSqlAstCreationState()
);
tableGroup.addTableGroupJoin( tableGroupJoin );
@ -1150,10 +1188,88 @@ public class ToOneAttributeMapping
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
// Make sure the lhs is never a plural table group directly, but always a table group for a part
// This is vital for the map key property check that comes next
assert !( lhs instanceof PluralTableGroup );
TableGroup parentTableGroup = lhs;
ModelPartContainer parentContainer = lhs.getModelPart();
StringBuilder embeddablePathSb = null;
// Traverse up embeddable table groups until we find a table group for a collection part
while ( !( parentContainer instanceof CollectionPart ) ) {
if ( parentContainer instanceof EmbeddableValuedModelPart ) {
if ( embeddablePathSb == null ) {
embeddablePathSb = new StringBuilder();
}
embeddablePathSb.insert( 0, parentContainer.getPartName() + "." );
parentTableGroup = fromClauseAccess.findTableGroup( parentTableGroup.getNavigablePath().getParent() );
parentContainer = parentTableGroup.getModelPart();
}
else {
break;
}
}
// If a parent is a collection part, there is no custom predicate and the join is INNER or LEFT
// we check if this attribute is the map key property to reuse the existing index table group
if ( CollectionPart.Nature.ELEMENT.getName().equals( parentTableGroup.getNavigablePath().getUnaliasedLocalName() )
&& !addsPredicate && ( sqlAstJoinType == SqlAstJoinType.INNER || sqlAstJoinType == SqlAstJoinType.LEFT ) ) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) fromClauseAccess.findTableGroup(
parentTableGroup.getNavigablePath().getParent()
);
final String indexPropertyName = pluralTableGroup.getModelPart()
.getIndexMetadata()
.getIndexPropertyName();
final String pathName;
if ( embeddablePathSb != null ) {
pathName = embeddablePathSb.append( getAttributeName() ).toString();
}
else {
pathName = getAttributeName();
}
if ( pathName.equals( indexPropertyName ) ) {
final TableGroup indexTableGroup = pluralTableGroup.getIndexTableGroup();
// If this is the map key property, we can reuse the index table group
initializeIfNeeded( lhs, sqlAstJoinType, indexTableGroup );
return new TableGroupJoin(
navigablePath,
sqlAstJoinType,
new MappedByTableGroup(
navigablePath,
this,
indexTableGroup,
fetched,
pluralTableGroup,
(np, tableExpression) -> {
if ( !canUseParentTableGroup ) {
return false;
}
NavigablePath path = np.getParent();
// Fast path
if ( path != null && navigablePath.equals( path ) ) {
return targetKeyPropertyNames.contains( np.getUnaliasedLocalName() )
&& identifyingColumnsTableExpression.equals( tableExpression );
}
final StringBuilder sb = new StringBuilder( np.getFullPath().length() );
sb.append( np.getUnaliasedLocalName() );
while ( path != null && !navigablePath.equals( path ) ) {
sb.insert( 0, '.' );
sb.insert( 0, path.getUnaliasedLocalName() );
path = path.getParent();
}
return path != null && navigablePath.equals( path )
&& targetKeyPropertyNames.contains( sb.toString() )
&& identifyingColumnsTableExpression.equals( tableExpression );
}
),
null
);
}
}
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
@ -1163,6 +1279,7 @@ public class ToOneAttributeMapping
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
final TableGroupJoin join = new TableGroupJoin(
@ -1186,10 +1303,7 @@ public class ToOneAttributeMapping
)
);
if ( sqlAstJoinType == SqlAstJoinType.INNER && isNullable ) {
// Force initialization of the underlying table group join to retain cardinality
lazyTableGroup.getPrimaryTableReference();
}
initializeIfNeeded( lhs, sqlAstJoinType, lazyTableGroup );
return join;
}
@ -1204,6 +1318,7 @@ public class ToOneAttributeMapping
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( sqlAliasStem );
final boolean canUseInnerJoin = sqlAstJoinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins() && !isNullable;
@ -1266,15 +1381,19 @@ public class ToOneAttributeMapping
)
);
if ( sqlAstJoinType == SqlAstJoinType.INNER && isNullable ) {
// Force initialization of the underlying table group join to retain cardinality
lazyTableGroup.getPrimaryTableReference();
}
initializeIfNeeded( lhs, sqlAstJoinType, lazyTableGroup );
}
return lazyTableGroup;
}
private void initializeIfNeeded(TableGroup lhs, SqlAstJoinType sqlAstJoinType, TableGroup tableGroup) {
if ( sqlAstJoinType == SqlAstJoinType.INNER && ( isNullable || !lhs.canUseInnerJoins() ) ) {
// Force initialization of the underlying table group join to retain cardinality
tableGroup.getPrimaryTableReference();
}
}
private SqlAstJoinType getJoinType(NavigablePath navigablePath, TableGroup tableGroup) {
for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) {
if ( tableGroupJoin.getNavigablePath().equals( navigablePath ) ) {
@ -1353,7 +1472,7 @@ public class ToOneAttributeMapping
@Override
public ModelPart getKeyTargetMatchPart() {
return foreignKeyDescriptor;
return foreignKeyDescriptor.getPart( sideNature );
}
@Override

View File

@ -82,6 +82,11 @@ public abstract class AbstractPluralAttribute<D, C, E>
return elementPathSource.findSubPathSource( name );
}
@Override
public SqmPathSource<?> getIntermediatePathSource(SqmPathSource<?> pathSource) {
return pathSource == elementPathSource ? null : elementPathSource;
}
@Override
public CollectionType getCollectionType() {
return getCollectionClassification().toJpaClassification();
@ -130,10 +135,15 @@ public abstract class AbstractPluralAttribute<D, C, E>
}
@Override
public SqmPath<E> createSqmPath(SqmPath<?> lhs) {
final NavigablePath navigablePath = lhs.getNavigablePath().append( getPathName() );
//noinspection unchecked
return new SqmPluralValuedSimplePath(
public SqmPath<E> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
return new SqmPluralValuedSimplePath<>(
navigablePath,
this,
lhs,

View File

@ -46,9 +46,15 @@ public class AnyMappingSqmPathSource<J> extends AbstractSqmPathSource<J> {
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs) {
final NavigablePath navigablePath = lhs.getNavigablePath().append( getPathName() );
return new SqmAnyValuedSimplePath( navigablePath, this, lhs, lhs.nodeBuilder() );
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
return new SqmAnyValuedSimplePath<>( navigablePath, this, lhs, lhs.nodeBuilder() );
}
}

View File

@ -40,8 +40,14 @@ public class BasicSqmPathSource<J>
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs) {
final NavigablePath navigablePath = lhs.getNavigablePath().append( getPathName() );
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
return new SqmBasicValuedSimplePath<>(
navigablePath,
this,

View File

@ -35,7 +35,7 @@ public class DiscriminatorSqmPathSource<D> extends AbstractSqmPathSource<D>
}
@Override
public SqmPath<D> createSqmPath(SqmPath<?> lhs) {
public SqmPath<D> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
return new DiscriminatorSqmPath( this, lhs, entityDomainType, entityMapping, lhs.nodeBuilder() );
}

View File

@ -8,6 +8,7 @@ package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
@ -48,9 +49,16 @@ public class EmbeddedSqmPathSource<J>
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs) {
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
return new SqmEmbeddedValuedSimplePath<>(
lhs.getNavigablePath().append( getPathName() ),
navigablePath,
this,
lhs,
lhs.nodeBuilder()

View File

@ -7,6 +7,7 @@
package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
@ -35,9 +36,16 @@ public class EntitySqmPathSource<J> extends AbstractSqmPathSource<J> {
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs) {
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
return new SqmEntityValuedSimplePath<>(
lhs.getNavigablePath().append( getPathName() ),
navigablePath,
this,
lhs,
lhs.nodeBuilder()

View File

@ -186,8 +186,7 @@ public class EntityTypeImpl<J>
}
@Override
public SqmPath<J> createSqmPath(
SqmPath<?> lhs) {
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
throw new UnsupportedOperationException(
"EntityType cannot be used to create an SqmPath - that would be an SqmFrom which are created directly"
);

View File

@ -59,6 +59,11 @@ class ListAttributeImpl<X, E> extends AbstractPluralAttribute<X, List<E>, E> imp
return getElementPathSource().findSubPathSource( name );
}
@Override
public SqmPathSource<?> getIntermediatePathSource(SqmPathSource<?> pathSource) {
return pathSource == getElementPathSource() || pathSource == indexPathSource ? null : getElementPathSource();
}
@Override
public SqmAttributeJoin createSqmJoin(
SqmFrom lhs,

View File

@ -69,6 +69,11 @@ class MapAttributeImpl<X, K, V> extends AbstractPluralAttribute<X, Map<K, V>, V>
return getElementPathSource().findSubPathSource( name );
}
@Override
public SqmPathSource<?> getIntermediatePathSource(SqmPathSource<?> pathSource) {
return pathSource == getElementPathSource() || pathSource == keyPathSource ? null : getElementPathSource();
}
@Override
public SimpleDomainType<K> getKeyType() {
return (SimpleDomainType<K>) keyPathSource.getSqmPathType();

View File

@ -7,6 +7,7 @@
package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
@ -35,9 +36,16 @@ public class NonAggregatedCompositeSqmPathSource<J> extends AbstractSqmPathSourc
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs) {
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
return new NonAggregatedCompositeSimplePath<>(
lhs.getNavigablePath().append( getPathName() ),
navigablePath,
this,
lhs,
lhs.nodeBuilder()

View File

@ -221,8 +221,8 @@ public class SingularAttributeImpl<D,J>
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs) {
return sqmPathSource.createSqmPath( lhs );
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
return sqmPathSource.createSqmPath( lhs, intermediatePathSource );
}
private class DelayedKeyTypeAccess implements Supplier<SimpleDomainType<J>>, Serializable {

View File

@ -465,6 +465,18 @@ public abstract class AbstractCollectionPersister
indexFormulas[i] = indexForm.getFormula();
hasFormula = true;
}
// Treat a mapped-by index like a formula to avoid trying to set it in insert/update
// Previously this was a sub-query formula, but was changed to represent the proper mapping
// which enables optimizations for queries. The old insert/update code wasn't adapted yet though.
// For now, this is good enough, because the formula is never used anymore,
// since all read paths go through the new code that can properly handle this case
else if ( indexedCollection instanceof org.hibernate.mapping.Map
&& ( (org.hibernate.mapping.Map) indexedCollection ).getMapKeyPropertyName() != null ) {
Column indexCol = (Column) s;
indexFormulaTemplates[i] = Template.TEMPLATE + indexCol.getQuotedName( dialect );
indexFormulas[i] = indexCol.getQuotedName( dialect );
hasFormula = true;
}
else {
Column indexCol = (Column) s;
indexColumnNames[i] = indexCol.getQuotedName( dialect );
@ -1172,6 +1184,7 @@ public abstract class AbstractCollectionPersister
() -> p -> {},
new SqlAliasBaseConstant( alias ),
sqlAstCreationState.getSqlExpressionResolver(),
sqlAstCreationState.getFromClauseAccess(),
getFactory()
);

View File

@ -210,6 +210,7 @@ import org.hibernate.sql.Template;
import org.hibernate.sql.Update;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseConstant;
@ -1321,6 +1322,7 @@ public abstract class AbstractEntityPersister
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final TableReference primaryTableReference = createPrimaryTableReference(
sqlAliasBase,
@ -1911,6 +1913,7 @@ public abstract class AbstractEntityPersister
() -> p -> {},
new SqlAliasBaseConstant( alias ),
sqlAstCreationState.getSqlExpressionResolver(),
sqlAstCreationState.getFromClauseAccess(),
getFactory()
);

View File

@ -1315,6 +1315,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
@Override
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
// If the base type is part of the treatedEntityNames this means we can't optimize this,
// as the table group is e.g. returned through a select
if ( treatedEntityNames.contains( getEntityName() ) ) {
return;
}
@ -1324,7 +1326,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
for ( String treatedEntityName : treatedEntityNames ) {
final JoinedSubclassEntityPersister subPersister = (JoinedSubclassEntityPersister) getSubclassMappingType( treatedEntityName );
final String[] subclassTableNames = subPersister.getSubclassTableNames();
if ( tableGroup.canUseInnerJoins() ) {
// For every treated entity name, we collect table names that are needed by all treated entity names
// In mathematical terms, sharedSuperclassTables will be the "intersection" of the table names of all treated entities
if ( sharedSuperclassTables.isEmpty() ) {
for ( int i = 0; i < subclassTableNames.length; i++ ) {
if ( subPersister.isClassOrSuperclassTable[i] ) {
@ -1335,18 +1338,21 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
else {
sharedSuperclassTables.retainAll( Arrays.asList( subclassTableNames ) );
}
}
// Add the table references for all table names of the treated entities as we have to retain these table references.
// Table references not appearing in this set can later be pruned away
// todo (6.0): no need to resolve all table references, only the ones needed for cardinality
for ( int i = 0; i < subclassTableNames.length; i++ ) {
retainedTableReferences.add( tableGroup.resolveTableReference( null, subclassTableNames[i], false ) );
}
}
final List<TableReferenceJoin> tableReferenceJoins = tableGroup.getTableReferenceJoins();
if ( sharedSuperclassTables.isEmpty() ) {
tableReferenceJoins
.removeIf( join -> !retainedTableReferences.contains( join.getJoinedTableReference() ) );
}
else {
// The optimization is to remove all table reference joins that are not contained in the retainedTableReferences
// In addition, we switch from a possible LEFT join, to an inner join for all sharedSuperclassTables
// For now, we can only do this if the table group reports canUseInnerJoins or isRealTableGroup,
// because the switch for table reference joins to INNER must be cardinality preserving.
// If canUseInnerJoins is true, this is trivially given, but also if the table group is real
// i.e. with parenthesis around, as that means the table reference joins will be isolated
if ( tableGroup.canUseInnerJoins() || tableGroup.isRealTableGroup() ) {
final TableReferenceJoin[] oldJoins = tableReferenceJoins.toArray( new TableReferenceJoin[0] );
tableReferenceJoins.clear();
for ( TableReferenceJoin oldJoin : oldJoins ) {
@ -1368,6 +1374,10 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
}
}
}
else {
tableReferenceJoins
.removeIf( join -> !retainedTableReferences.contains( join.getJoinedTableReference() ) );
}
}
@Override

View File

@ -45,6 +45,7 @@ import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
@ -844,6 +845,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver expressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final TableGroup tableGroup = super.createRootTableGroup(
canUseInnerJoins,
@ -852,6 +854,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
additionalPredicateCollectorAccess,
sqlAliasBase,
expressionResolver,
fromClauseAccess,
creationContext
);
@ -954,9 +957,12 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
@Override
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
// If the base type is part of the treatedEntityNames this means we can't optimize this,
// as the table group is e.g. returned through a select
if ( treatedEntityNames.contains( getEntityName() ) ) {
return;
}
// The optimization is to simply add the discriminator filter fragment for all treated entity names
final TableReference tableReference = tableGroup.getPrimaryTableReference();
tableReference.setPrunedTableExpression(
"(select * from " + getTableName() + " t where " + discriminatorFilterFragment( "t", treatedEntityNames ) + ")"

View File

@ -47,6 +47,7 @@ import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
@ -255,6 +256,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver expressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final TableReference tableReference = resolvePrimaryTableReference( sqlAliasBase );
@ -395,10 +397,13 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
@Override
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
// If the base type is part of the treatedEntityNames this means we can't optimize this,
// as the table group is e.g. returned through a select
if ( treatedEntityNames.contains( getEntityName() ) ) {
return;
}
final TableReference tableReference = tableGroup.resolveTableReference( getRootTableName() );
// Replace the default union sub-query with a specially created one that only selects the tables for the treated entity names
tableReference.setPrunedTableExpression( generateSubquery( treatedEntityNames ) );
}
@ -527,6 +532,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
final Dialect dialect = getFactory().getJdbcServices().getDialect();
// Collect all selectables of every entity subtype and group by selection expression as well as table name
final LinkedHashMap<String, Map<String, SelectableMapping>> selectables = new LinkedHashMap<>();
visitSubTypeAttributeMappings(
attributeMapping -> attributeMapping.forEachSelectable(
@ -534,6 +540,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
.put( selectable.getContainingTableExpression(), selectable )
)
);
// Collect the concrete subclass table names for the treated entity names
final Set<String> treatedTableNames = new HashSet<>( treated.size() );
for ( String subclassName : treated ) {
final UnionSubclassEntityPersister subPersister = (UnionSubclassEntityPersister) getSubclassMappingType( subclassName );
@ -544,6 +551,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
}
}
// Create a union sub-query for the table names, like generateSubquery(PersistentClass model, Mapping mapping)
final StringBuilder buf = new StringBuilder( subquery.length() )
.append( "( " );
@ -554,6 +562,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
for ( Map<String, SelectableMapping> selectableMappings : selectables.values() ) {
SelectableMapping selectableMapping = selectableMappings.get( subclassTableName );
if ( selectableMapping == null ) {
// If there is no selectable mapping for a table name, we render a null expression
selectableMapping = selectableMappings.values().iterator().next();
final int sqlType = selectableMapping.getJdbcMapping().getJdbcTypeDescriptor()
.getDefaultSqlTypeCode();

View File

@ -155,6 +155,16 @@ public class NavigablePath implements DotIdentifierSequence, Serializable {
return identifierForTableGroup;
}
public boolean isParent(NavigablePath navigablePath) {
while ( navigablePath != null ) {
if ( this.equals( navigablePath.getParent() ) ) {
return true;
}
navigablePath = navigablePath.getParent();
}
return false;
}
@Override
public String toString() {
return fullPath;

View File

@ -40,11 +40,8 @@ import org.hibernate.query.results.implicit.ImplicitFetchBuilderEmbeddable;
import org.hibernate.query.results.implicit.ImplicitFetchBuilderEntity;
import org.hibernate.query.results.implicit.ImplicitFetchBuilderPlural;
import org.hibernate.query.results.implicit.ImplicitModelPartResultBuilderEntity;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.collection.internal.EntityCollectionPartTableGroup;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaType;
@ -277,15 +274,7 @@ public class Builders {
if ( fetchable instanceof EntityCollectionPart ) {
final EntityCollectionPart entityCollectionPart = (EntityCollectionPart) fetchable;
return (parent, fetchablePath, jdbcResultsMetadata, legacyFetchResolver, domainResultCreationState) -> {
final FromClauseAccess fromClauseAccess = domainResultCreationState.getSqlAstCreationState()
.getFromClauseAccess();
final TableGroup collectionTableGroup = fromClauseAccess.findTableGroup( parent.getNavigablePath() );
fromClauseAccess.registerTableGroup(
fetchablePath,
new EntityCollectionPartTableGroup( fetchablePath, collectionTableGroup, entityCollectionPart )
);
return parent.generateFetchableFetch(
return (parent, fetchablePath, jdbcResultsMetadata, legacyFetchResolver, domainResultCreationState) -> parent.generateFetchableFetch(
entityCollectionPart,
fetchablePath,
FetchTiming.IMMEDIATE,
@ -293,7 +282,6 @@ public class Builders {
null,
domainResultCreationState
);
};
}
throw new UnsupportedOperationException();

View File

@ -118,8 +118,10 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue
tableAlias,
SqlAstJoinType.INNER,
true,
false,
s -> sqlAliasBase,
creationState.getSqlExpressionResolver(),
creationState.getFromClauseAccess(),
creationState.getCreationContext()
);
ownerTableGroup.addTableGroupJoin( tableGroupJoin );

View File

@ -114,6 +114,7 @@ public class DynamicResultBuilderEntityCalculated implements DynamicResultBuilde
null,
new SqlAliasBaseConstant( tableAlias ),
creationStateImpl,
creationStateImpl.getFromClauseAccess(),
creationStateImpl.getCreationContext()
);

View File

@ -89,6 +89,7 @@ public class ImplicitFetchBuilderEmbeddable implements ImplicitFetchBuilder {
null,
SqlAstJoinType.INNER,
true,
false,
creationStateImpl
);
parentTableGroup.addTableGroupJoin( tableGroupJoin );

View File

@ -70,6 +70,7 @@ public class ImplicitModelPartResultBuilderEmbeddable
null,
SqlAstJoinType.INNER,
true,
false,
creationStateImpl
);

View File

@ -41,10 +41,17 @@ public interface SqmPathSource<J> extends SqmExpressable<J>, Bindable<J>, SqmExp
*/
SqmPathSource<?> findSubPathSource(String name);
/**
* Returns the intermediate SqmPathSource for a path source previously acquired via {@link #findSubPathSource(String)}.
*/
default SqmPathSource<?> getIntermediatePathSource(SqmPathSource<?> pathSource) {
return null;
}
/**
* Create an SQM path for this source relative to the given left-hand side
*/
SqmPath<J> createSqmPath(SqmPath<?> lhs);
SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource);
@Override
default SqmExpressable<J> getExpressable() {

View File

@ -90,6 +90,8 @@ import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.domain.SqmTreatedRoot;
import org.hibernate.sql.ast.tree.from.CorrelatedPluralTableGroup;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.exec.internal.VersionTypeSeedParameterSpecification;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
@ -373,8 +375,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private Map<String, FilterPredicate> collectionFilterPredicates;
private OrderByFragmentConsumer orderByFragmentConsumer;
private final Map<String, NavigablePath> joinPathBySqmJoinFullPath = new HashMap<>();
private final SqlAliasBaseManager sqlAliasBaseManager = new SqlAliasBaseManager();
private final Stack<SqlAstProcessingState> processingStateStack = new StandardStack<>();
private final Stack<FromClauseIndex> fromClauseIndexStack = new StandardStack<>();
@ -1460,7 +1460,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
applyCollectionFilterPredicates( sqlQuerySpec );
}
joinPathBySqmJoinFullPath.clear();
return sqlQuerySpec;
}
finally {
@ -1954,6 +1953,59 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
from.getCorrelationParent().getNavigablePath()
);
final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() );
if ( parentTableGroup instanceof PluralTableGroup ) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) parentTableGroup;
final CorrelatedPluralTableGroup correlatedPluralTableGroup = new CorrelatedPluralTableGroup(
parentTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroup elementTableGroup = pluralTableGroup.getElementTableGroup();
if ( elementTableGroup != null ) {
final TableGroup correlatedElementTableGroup = new CorrelatedTableGroup(
elementTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
elementTableGroup.getNavigablePath(),
SqlAstJoinType.INNER,
correlatedElementTableGroup
);
correlatedPluralTableGroup.registerElementTableGroup( tableGroupJoin );
}
final TableGroup indexTableGroup = pluralTableGroup.getIndexTableGroup();
if ( indexTableGroup != null ) {
final TableGroup correlatedIndexTableGroup = new CorrelatedTableGroup(
indexTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
indexTableGroup.getNavigablePath(),
SqlAstJoinType.INNER,
correlatedIndexTableGroup
);
correlatedPluralTableGroup.registerIndexTableGroup( tableGroupJoin );
}
tableGroup = correlatedPluralTableGroup;
}
else {
tableGroup = new CorrelatedTableGroup(
parentTableGroup,
sqlAliasBase,
@ -1964,6 +2016,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
),
sessionFactory
);
}
fromClauseIndex.register( from, tableGroup );
registerPluralTableGroupParts( tableGroup );
log.tracef( "Resolved SqmRoot [%s] to correlated TableGroup [%s]", sqmRoot, tableGroup );
consumeExplicitJoins( from, tableGroup );
@ -2142,13 +2198,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
sqmFrom.visitSqmJoins(
sqmJoin -> {
registerUsage( (SqmFrom<?, ?>) sqmJoin.getLhs(), lhsTableGroup );
consumeExplicitJoin( sqmJoin, lhsTableGroup, lhsTableGroup, true );
final TableGroup actualTableGroup = findActualTableGroup( lhsTableGroup, sqmJoin );
registerUsage( (SqmFrom<?, ?>) sqmJoin.getLhs(), actualTableGroup );
consumeExplicitJoin( sqmJoin, actualTableGroup, actualTableGroup, true );
}
);
for ( SqmFrom<?, ?> sqmTreat : sqmFrom.getSqmTreats() ) {
registerUsage( sqmTreat, lhsTableGroup );
consumeExplicitJoins( sqmTreat, lhsTableGroup );
final TableGroup actualTableGroup = findActualTableGroup( lhsTableGroup, sqmTreat );
registerUsage( sqmTreat, actualTableGroup );
consumeExplicitJoins( sqmTreat, actualTableGroup );
}
}
@ -2172,6 +2230,26 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
private TableGroup findActualTableGroup(TableGroup lhsTableGroup, SqmPath<?> path) {
final SqmPathSource<?> intermediatePathSource;
final SqmPath<?> lhs;
if ( path instanceof SqmTreatedPath<?, ?> ) {
lhs = ( (SqmTreatedPath<?, ?>) path ).getWrappedPath().getLhs();
}
else {
lhs = path.getLhs();
}
intermediatePathSource = lhs == null ? null : lhs.getReferencedPathSource()
.getIntermediatePathSource( path.getReferencedPathSource() );
if ( intermediatePathSource == null ) {
return lhsTableGroup;
}
// The only possible intermediate path source for now is the element path source for plural attributes
assert intermediatePathSource.getPathName().equals( CollectionPart.Nature.ELEMENT.getName() );
final PluralTableGroup pluralTableGroup = (PluralTableGroup) lhsTableGroup;
return pluralTableGroup.getElementTableGroup();
}
private TableGroup consumeAttributeJoin(
SqmAttributeJoin<?, ?> sqmJoin,
TableGroup lhsTableGroup,
@ -2185,50 +2263,37 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final TableGroup joinedTableGroup;
final NavigablePath sqmJoinNavigablePath = sqmJoin.getNavigablePath();
final NavigablePath parentNavigablePath = sqmJoinNavigablePath.getParent();
final ModelPart modelPart = ownerTableGroup.getModelPart().findSubPart(
pathSource.getPathName(),
SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this )
);
final NavigablePath joinPath;
if ( pathSource instanceof PluralPersistentAttribute ) {
assert modelPart instanceof PluralAttributeMapping;
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
joinPath = getJoinNavigablePath(
sqmJoinNavigablePath,
parentNavigablePath,
pluralAttributeMapping.getPartName()
);
joinPathBySqmJoinFullPath.put(
sqmJoin.getNavigablePath().getFullPath(),
joinPath.append( CollectionPart.Nature.ELEMENT.getName() )
);
joinedTableGroupJoin = pluralAttributeMapping.createTableGroupJoin(
joinPath,
sqmJoinNavigablePath,
ownerTableGroup,
sqmJoin.getExplicitAlias(),
sqmJoinType.getCorrespondingSqlJoinType(),
sqmJoin.isFetched(),
sqmJoin.getJoinPredicate() != null,
this
);
}
else {
assert modelPart instanceof TableGroupJoinProducer;
joinPath = getJoinNavigablePath( sqmJoinNavigablePath, parentNavigablePath, modelPart.getPartName() );
joinedTableGroupJoin = ( (TableGroupJoinProducer) modelPart ).createTableGroupJoin(
joinPath,
sqmJoinNavigablePath,
ownerTableGroup,
sqmJoin.getExplicitAlias(),
sqmJoinType.getCorrespondingSqlJoinType(),
sqmJoin.isFetched(),
sqmJoin.getJoinPredicate() != null,
this
);
@ -2243,7 +2308,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
joinedTableGroup = joinedTableGroupJoin.getJoinedGroup();
lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin );
getFromClauseIndex().register( sqmJoin, joinedTableGroup, joinPath );
getFromClauseIndex().register( sqmJoin, joinedTableGroup );
registerPluralTableGroupParts( joinedTableGroup );
// add any additional join restrictions
if ( sqmJoin.getJoinPredicate() != null ) {
@ -2251,10 +2317,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
QueryLogging.QUERY_MESSAGE_LOGGER.debugf( "Join fetch [" + sqmJoinNavigablePath + "] is restricted" );
}
if ( joinedTableGroupJoin == null ) {
throw new IllegalStateException();
}
final SqmJoin<?, ?> oldJoin = currentlyProcessingJoin;
currentlyProcessingJoin = sqmJoin;
joinedTableGroupJoin.applyPredicate( (Predicate) sqmJoin.getJoinPredicate().accept( this ) );
@ -2267,24 +2329,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return joinedTableGroup;
}
private NavigablePath getJoinNavigablePath(
NavigablePath sqmJoinNavigablePath,
NavigablePath parentNavigablePath,
String partName) {
if ( parentNavigablePath == null ) {
return sqmJoinNavigablePath;
}
else {
final NavigablePath elementNavigablePath = joinPathBySqmJoinFullPath.get( parentNavigablePath.getFullPath() );
if ( elementNavigablePath == null ) {
return sqmJoinNavigablePath;
}
else {
return elementNavigablePath.append( partName );
}
}
}
private TableGroup consumeCrossJoin(SqmCrossJoin<?> sqmJoin, TableGroup lhsTableGroup, boolean transitive) {
final EntityPersister entityDescriptor = resolveEntityPersister( sqmJoin.getReferencedPathSource() );
@ -2450,9 +2494,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
private TableGroup createTableGroup(TableGroup parentTableGroup, SqmPath<?> joinedPath, boolean useInnerJoin) {
final TableGroup actualParentTableGroup = findActualTableGroup( parentTableGroup, joinedPath );
final SqmPath<?> lhsPath = joinedPath.getLhs();
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
final ModelPart subPart = parentTableGroup.getModelPart().findSubPart(
final ModelPart subPart = actualParentTableGroup.getModelPart().findSubPart(
joinedPath.getReferencedPathSource().getPathName(),
lhsPath instanceof SqmTreatedPath
? resolveEntityPersister( ( (SqmTreatedPath<?, ?>) lhsPath ).getTreatTarget() )
@ -2471,14 +2516,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
defaultSqlAstJoinType = SqlAstJoinType.INNER;
}
else {
defaultSqlAstJoinType = joinProducer.getDefaultSqlAstJoinType( parentTableGroup );
defaultSqlAstJoinType = joinProducer.getDefaultSqlAstJoinType( actualParentTableGroup );
}
if ( fromClauseIndex.findTableGroupOnLeaf( parentTableGroup.getNavigablePath() ) == null ) {
if ( fromClauseIndex.findTableGroupOnLeaf( actualParentTableGroup.getNavigablePath() ) == null ) {
final QuerySpec querySpec = currentQuerySpec();
// The parent table group is on a parent query, so we need a root table group
tableGroup = joinProducer.createRootTableGroupJoin(
joinedPath.getNavigablePath(),
parentTableGroup,
actualParentTableGroup,
null,
defaultSqlAstJoinType,
false,
@ -2492,10 +2537,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else {
final TableGroupJoin tableGroupJoin = joinProducer.createTableGroupJoin(
joinedPath.getNavigablePath(),
parentTableGroup,
actualParentTableGroup,
null,
defaultSqlAstJoinType,
false,
false,
this
);
// Implicit joins in the ON clause of attribute joins need to be added as nested table group joins
@ -2505,15 +2551,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final boolean nested = currentClauseStack.getCurrent() == Clause.FROM
&& currentlyProcessingJoin instanceof SqmAttributeJoin<?, ?>;
if ( nested ) {
parentTableGroup.addNestedTableGroupJoin( tableGroupJoin );
actualParentTableGroup.addNestedTableGroupJoin( tableGroupJoin );
}
else {
parentTableGroup.addTableGroupJoin( tableGroupJoin );
actualParentTableGroup.addTableGroupJoin( tableGroupJoin );
}
tableGroup = tableGroupJoin.getJoinedGroup();
}
fromClauseIndex.register( joinedPath, tableGroup );
registerPluralTableGroupParts( tableGroup );
}
else {
tableGroup = null;
@ -2521,6 +2568,24 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return tableGroup;
}
private void registerPluralTableGroupParts(TableGroup tableGroup) {
if ( tableGroup instanceof PluralTableGroup ) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup;
if ( pluralTableGroup.getElementTableGroup() != null ) {
getFromClauseAccess().registerTableGroup(
pluralTableGroup.getElementTableGroup().getNavigablePath(),
pluralTableGroup.getElementTableGroup()
);
}
if ( pluralTableGroup.getIndexTableGroup() != null ) {
getFromClauseAccess().registerTableGroup(
pluralTableGroup.getIndexTableGroup().getNavigablePath(),
pluralTableGroup.getIndexTableGroup()
);
}
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqmPath handling
// - Note that SqmFrom references defined in the FROM-clause are already
@ -2885,11 +2950,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final TableGroup parentTableGroup = getFromClauseAccess().getTableGroup( pluralPath.getNavigablePath().getParent() );
assert parentTableGroup != null;
final PluralAttributeMapping collectionPart = (PluralAttributeMapping) parentTableGroup.getModelPart().findSubPart(
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) parentTableGroup.getModelPart().findSubPart(
pluralPath.getNavigablePath().getUnaliasedLocalName(),
null
);
assert collectionPart != null;
assert pluralAttributeMapping != null;
final QuerySpec subQuerySpec = new QuerySpec( false );
pushProcessingState(
@ -2901,7 +2966,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
)
);
try {
final TableGroup tableGroup = collectionPart.createRootTableGroup(
final TableGroup tableGroup = pluralAttributeMapping.createRootTableGroup(
true,
pluralPath.getNavigablePath(),
null,
@ -2911,6 +2976,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
getFromClauseAccess().registerTableGroup( pluralPath.getNavigablePath(), tableGroup );
registerPluralTableGroupParts( tableGroup );
subQuerySpec.getFromClause().addRoot( tableGroup );
final AbstractSqmSelfRenderingFunctionDescriptor functionDescriptor = (AbstractSqmSelfRenderingFunctionDescriptor) creationContext
@ -2932,7 +2998,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 1, 0, expression ) );
subQuerySpec.applyPredicate(
collectionPart.getKeyDescriptor().generateJoinPredicate(
pluralAttributeMapping.getKeyDescriptor().generateJoinPredicate(
parentTableGroup,
tableGroup,
SqlAstJoinType.INNER,
@ -2971,8 +3037,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
null,
SqlAstJoinType.INNER,
false,
false,
sqlAliasBaseManager,
getSqlExpressionResolver(),
this,
creationContext
);
@ -3018,9 +3086,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
AbstractSqmSpecificPluralPartPath<?> pluralPartPath,
boolean index,
boolean max) {
prepareReusablePath( pluralPartPath, () -> null );
prepareReusablePath( pluralPartPath.getLhs(), () -> null );
final PluralAttributeMapping mappingModelExpressable = (PluralAttributeMapping) determineValueMapping(
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) determineValueMapping(
pluralPartPath.getPluralDomainPath() );
final FromClauseAccess parentFromClauseAccess = getFromClauseAccess();
final QuerySpec subQuerySpec = new QuerySpec( false );
@ -3033,7 +3101,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
)
);
try {
final TableGroup tableGroup = mappingModelExpressable.createRootTableGroup(
final TableGroup tableGroup = pluralAttributeMapping.createRootTableGroup(
true,
pluralPartPath.getNavigablePath(),
null,
@ -3043,6 +3111,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
getFromClauseAccess().registerTableGroup( pluralPartPath.getNavigablePath(), tableGroup );
registerPluralTableGroupParts( tableGroup );
subQuerySpec.getFromClause().addRoot( tableGroup );
final AbstractSqmSelfRenderingFunctionDescriptor functionDescriptor = (AbstractSqmSelfRenderingFunctionDescriptor) creationContext
@ -3051,8 +3120,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
.getSqmFunctionRegistry()
.findFunctionDescriptor( max ? "max" : "min" );
final CollectionPart collectionPart = index
? mappingModelExpressable.getIndexDescriptor()
: mappingModelExpressable.getElementDescriptor();
? pluralAttributeMapping.getIndexDescriptor()
: pluralAttributeMapping.getElementDescriptor();
final ModelPart modelPart;
if ( collectionPart instanceof EntityAssociationMapping ) {
modelPart = ( (EntityAssociationMapping) collectionPart ).getKeyTargetMatchPart();
@ -3098,7 +3167,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 1, 0, expression ) );
subQuerySpec.applyPredicate(
mappingModelExpressable.getKeyDescriptor().generateJoinPredicate(
pluralAttributeMapping.getKeyDescriptor().generateJoinPredicate(
parentFromClauseAccess.findTableGroup( pluralPartPath.getPluralDomainPath().getNavigablePath().getParent() ),
tableGroup,
SqlAstJoinType.INNER,
@ -4668,19 +4737,19 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final SqmPath<?> pluralPath = predicate.getPluralPath();
prepareReusablePath( pluralPath, () -> null );
final PluralAttributeMapping mappingModelExpressable = (PluralAttributeMapping) determineValueMapping(
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) determineValueMapping(
pluralPath );
if ( mappingModelExpressable.getElementDescriptor() instanceof EntityCollectionPart ) {
if ( pluralAttributeMapping.getElementDescriptor() instanceof EntityCollectionPart ) {
inferrableTypeAccessStack.push(
() -> ( (EntityCollectionPart) mappingModelExpressable.getElementDescriptor() ).getKeyTargetMatchPart() );
() -> ( (EntityCollectionPart) pluralAttributeMapping.getElementDescriptor() ).getKeyTargetMatchPart() );
}
else if ( mappingModelExpressable.getElementDescriptor() instanceof EmbeddedCollectionPart ) {
else if ( pluralAttributeMapping.getElementDescriptor() instanceof EmbeddedCollectionPart ) {
inferrableTypeAccessStack.push(
() -> mappingModelExpressable.getElementDescriptor() );
() -> pluralAttributeMapping.getElementDescriptor() );
}
else {
inferrableTypeAccessStack.push( () -> mappingModelExpressable );
inferrableTypeAccessStack.push( () -> pluralAttributeMapping );
}
final Expression lhs;
@ -4702,7 +4771,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
)
);
try {
final TableGroup tableGroup = mappingModelExpressable.createRootTableGroup(
final TableGroup tableGroup = pluralAttributeMapping.createRootTableGroup(
true,
pluralPath.getNavigablePath(),
null,
@ -4712,9 +4781,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
getFromClauseAccess().registerTableGroup( pluralPath.getNavigablePath(), tableGroup );
registerPluralTableGroupParts( tableGroup );
subQuerySpec.getFromClause().addRoot( tableGroup );
final CollectionPart elementDescriptor = mappingModelExpressable.getElementDescriptor();
final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor();
if ( elementDescriptor instanceof EntityCollectionPart ) {
( (EntityCollectionPart) elementDescriptor ).getKeyTargetMatchPart()
.createDomainResult(
@ -4734,7 +4804,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
subQuerySpec.applyPredicate(
mappingModelExpressable.getKeyDescriptor().generateJoinPredicate(
pluralAttributeMapping.getKeyDescriptor().generateJoinPredicate(
parentFromClauseAccess.findTableGroup( pluralPath.getNavigablePath().getParent() ),
tableGroup,
SqlAstJoinType.INNER,
@ -4821,6 +4891,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
parentNavPath,
tableGroup
);
registerPluralTableGroupParts( tableGroup );
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) visitPluralValuedPath(
sqmPluralPath
@ -4834,8 +4905,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmPluralPath.getExplicitAlias(),
SqlAstJoinType.INNER,
false,
false,
sqlAliasBaseManager,
subQueryState,
this,
creationContext
)
);
@ -5213,6 +5286,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
alias,
tableGroupJoinProducer.getDefaultSqlAstJoinType( lhs ),
true,
false,
this
);
lhs.addTableGroupJoin( tableGroupJoin );
@ -5328,18 +5402,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
tableGroup
);
if ( manyToManyFilterPredicate != null ) {
TableGroupJoin elementTableGroupJoin = null;
for ( TableGroupJoin nestedTableGroupJoin : tableGroup.getNestedTableGroupJoins() ) {
final NavigablePath navigablePath = nestedTableGroupJoin.getNavigablePath();
if ( navigablePath.getParent() == tableGroup.getNavigablePath()
&& CollectionPart.Nature.ELEMENT.getName().equals( navigablePath.getUnaliasedLocalName() ) ) {
elementTableGroupJoin = nestedTableGroupJoin;
final TableGroup parentTableGroup = getFromClauseIndex().getTableGroup( fetchParent.getNavigablePath() );
TableGroupJoin pluralTableGroupJoin = null;
for ( TableGroupJoin nestedTableGroupJoin : parentTableGroup.getTableGroupJoins() ) {
if ( nestedTableGroupJoin.getNavigablePath() == fetchablePath ) {
pluralTableGroupJoin = nestedTableGroupJoin;
break;
}
}
assert elementTableGroupJoin != null;
elementTableGroupJoin.applyPredicate( manyToManyFilterPredicate );
assert pluralTableGroupJoin != null;
pluralTableGroupJoin.applyPredicate( manyToManyFilterPredicate );
}
}

View File

@ -17,10 +17,9 @@ import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
@ -104,26 +103,21 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
}
else if ( mapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
final ForeignKeyDescriptor fkDescriptor = associationMapping.getForeignKeyDescriptor();
final String lhsTable;
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
final ModelPart lhsPart;
boolean useKeyPart = associationMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY;
if ( useKeyPart ) {
lhsTable = fkDescriptor.getKeyTable();
lhsPart = fkDescriptor.getKeyPart();
if ( keyTargetMatchPart instanceof ToOneAttributeMapping ) {
lhsPart = ( (ToOneAttributeMapping) keyTargetMatchPart ).getKeyTargetMatchPart();
}
else {
lhsTable = fkDescriptor.getTargetTable();
lhsPart = fkDescriptor.getTargetPart();
lhsPart = keyTargetMatchPart;
}
if ( lhsPart instanceof BasicValuedModelPart ) {
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) lhsPart;
final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath,
lhsTable
basicValuedModelPart.getContainingTableExpression()
);
if ( fkDescriptor instanceof SimpleForeignKeyDescriptor ) {
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) lhsPart;
sqlExpression = sqlExprResolver.resolveSqlExpression(
createColumnReferenceKey( tableReference, basicValuedModelPart.getSelectionExpression() ),
processingState -> new ColumnReference(
@ -134,9 +128,14 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
);
}
else {
final List<Expression> expressions = new ArrayList<>( fkDescriptor.getJdbcTypeCount() );
final List<Expression> expressions = new ArrayList<>( lhsPart.getJdbcTypeCount() );
lhsPart.forEachSelectable(
(selectionIndex, selectableMapping) -> expressions.add(
(selectionIndex, selectableMapping) -> {
final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath,
selectableMapping.getContainingTableExpression()
);
expressions.add(
sqlExprResolver.resolveSqlExpression(
createColumnReferenceKey(
tableReference,
@ -148,7 +147,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
sessionFactory
)
)
)
);
}
);
sqlExpression = new SqlTuple( expressions, lhsPart );
}

View File

@ -138,7 +138,7 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
throw UnknownPathException.unknownSubPath( this, name );
}
return subSource.createSqmPath( this );
return subSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subSource ) );
}
);
}

View File

@ -155,7 +155,7 @@ public abstract class AbstractSqmPath<T> extends AbstractSqmExpression<T> implem
protected <X> SqmPath<X> resolvePath(String attributeName, SqmPathSource<X> pathSource) {
if ( reusablePaths == null ) {
reusablePaths = new HashMap<>();
final SqmPath<X> path = pathSource.createSqmPath( this );
final SqmPath<X> path = pathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( pathSource ) );
reusablePaths.put( attributeName, path );
return path;
}
@ -163,7 +163,7 @@ public abstract class AbstractSqmPath<T> extends AbstractSqmExpression<T> implem
//noinspection unchecked
return (SqmPath<X>) reusablePaths.computeIfAbsent(
attributeName,
name -> pathSource.createSqmPath( this )
name -> pathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( pathSource ) )
);
}
}

View File

@ -43,7 +43,7 @@ public class NonAggregatedCompositeSimplePath<T> extends SqmEntityValuedSimplePa
throw UnknownPathException.unknownSubPath( this, name );
}
return subPathSource.createSqmPath( this );
return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) );
}
@Override

View File

@ -64,7 +64,7 @@ public class SqmAnyValuedSimplePath<T> extends AbstractSqmSimplePath<T> {
throw UnknownPathException.unknownSubPath( this, name );
}
return subPathSource.createSqmPath( this );
return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) );
}

View File

@ -55,7 +55,7 @@ public class SqmEmbeddedValuedSimplePath<T> extends AbstractSqmSimplePath<T> imp
throw UnknownPathException.unknownSubPath( this, name );
}
return subPathSource.createSqmPath( this );
return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) );
}
@Override

View File

@ -40,7 +40,7 @@ public class SqmEntityValuedSimplePath<T> extends AbstractSqmSimplePath<T> {
.getPathRegistry()
.findPath( getLhs().getNavigablePath() ) != null;
return subPathSource.createSqmPath( this );
return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) );
}
@Override

View File

@ -52,7 +52,7 @@ public class SqmIndexedCollectionAccessPath<T> extends AbstractSqmPath<T> implem
boolean isTerminal,
SqmCreationState creationState) {
final SqmPathSource<?> subPathSource = getReferencedPathSource().getElementPathSource().findSubPathSource( name );
return subPathSource.createSqmPath( this );
return subPathSource.createSqmPath( this, getReferencedPathSource().getElementPathSource() );
}
@Override

View File

@ -35,7 +35,7 @@ public class SqmMaxElementPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
boolean isTerminal,
SqmCreationState creationState) {
if ( getPluralAttribute().getElementPathSource().getSqmPathType() instanceof ManagedDomainType ) {
return getPluralAttribute().getElementPathSource().createSqmPath( this );
return getPluralAttribute().getElementPathSource().createSqmPath( this, null );
}
throw new SemanticException( "Collection element cannot be de-referenced : " + getPluralDomainPath().getNavigablePath() );

View File

@ -48,7 +48,7 @@ public class SqmMaxIndexPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
String name,
boolean isTerminal,
SqmCreationState creationState) {
return indexPathSource.createSqmPath( this );
return indexPathSource.createSqmPath( this, null );
}
@Override

View File

@ -35,7 +35,7 @@ public class SqmMinElementPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
boolean isTerminal,
SqmCreationState creationState) {
if ( getPluralAttribute().getElementPathSource().getSqmPathType() instanceof ManagedDomainType ) {
return getPluralAttribute().getElementPathSource().createSqmPath( this );
return getPluralAttribute().getElementPathSource().createSqmPath( this, null );
}
throw new SemanticException( "Collection element cannot be de-referenced : " + getPluralDomainPath().getNavigablePath() );

View File

@ -48,7 +48,7 @@ public class SqmMinIndexPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
String name,
boolean isTerminal,
SqmCreationState creationState) {
return indexPathSource.createSqmPath( this );
return indexPathSource.createSqmPath( this, null );
}
@Override

View File

@ -17,7 +17,6 @@ import jakarta.persistence.metamodel.SingularAttribute;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.PathException;
import org.hibernate.query.SemanticException;
import org.hibernate.query.criteria.JpaPath;
import org.hibernate.query.hql.spi.SemanticPathPart;

View File

@ -373,7 +373,7 @@ public class SqmPolymorphicRootDescriptor<T> implements EntityDomainType<T> {
}
@Override
public SqmPath<T> createSqmPath(SqmPath<?> lhs) {
public SqmPath<T> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
throw new UnsupportedOperationException();
}

View File

@ -8,10 +8,7 @@ package org.hibernate.query.sqm.tree.domain;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.TreatedNavigablePath;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.UnknownPathException;
@ -104,7 +101,7 @@ public class SqmTreatedRoot<T, S extends T> extends SqmRoot<S> implements SqmTre
throw UnknownPathException.unknownSubPath( this, name );
}
return subSource.createSqmPath( this );
return subSource.createSqmPath( this, null );
}
);
}

View File

@ -111,12 +111,12 @@ public class SqmCrossJoin<T> extends AbstractSqmFrom<T, T> implements SqmJoin<T,
@Override
public <S extends T> SqmFrom<?, S> treatAs(Class<S> treatJavaType, String alias) {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException( "Cross join treats can not be aliased" );
}
@Override
public <S extends T> SqmFrom<?, S> treatAs(EntityDomainType<S> treatTarget, String alias) {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException( "Cross join treats can not be aliased" );
}
public SqmCrossJoin<T> makeCopy(SqmCreationProcessingState creationProcessingState) {

View File

@ -124,12 +124,12 @@ public class SqmEntityJoin<T> extends AbstractSqmJoin<T, T> implements SqmQualif
@Override
public <S extends T> SqmFrom<?, S> treatAs(Class<S> treatJavaType, String alias) {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException( "Entity join treats can not be aliased" );
}
@Override
public <S extends T> SqmFrom<?, S> treatAs(EntityDomainType<S> treatTarget, String alias) {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException( "Entity join treats can not be aliased" );
}
@Override

View File

@ -172,12 +172,12 @@ public class SqmRoot<E> extends AbstractSqmFrom<E,E> implements JpaRoot<E>, Doma
@Override
public <S extends E> SqmFrom<?, S> treatAs(Class<S> treatJavaType, String alias) {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException( "Root treats can not be aliased" );
}
@Override
public <S extends E> SqmFrom<?, S> treatAs(EntityDomainType<S> treatTarget, String alias) {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException( "Root treats can not be aliased" );
}
@Override

View File

@ -137,6 +137,14 @@ public class SqlTreePrinter {
);
}
final List<TableGroupJoin> nestedTableGroupJoins = tableGroup.getNestedTableGroupJoins();
if ( ! nestedTableGroupJoins.isEmpty() ) {
logNode(
"NestedTableGroupJoins",
() -> tableGroup.visitNestedTableGroupJoins( this::visitTableGroupJoin )
);
}
final List<TableGroupJoin> tableGroupJoins = tableGroup.getTableGroupJoins();
if ( ! tableGroupJoins.isEmpty() ) {
logNode(

View File

@ -34,7 +34,6 @@ import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.query.FetchClauseType;
@ -3358,7 +3357,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
String separator = NO_SEPARATOR;
for ( TableGroup root : fromClause.getRoots() ) {
appendSql( separator );
renderTableGroup( root );
renderTableGroup( root, null );
separator = COMA_SEPARATOR;
}
}
@ -3369,26 +3368,18 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
@SuppressWarnings("WeakerAccess")
protected void renderTableGroup(TableGroup tableGroup) {
// NOTE : commented out blocks render the TableGroup as a CTE
// if ( tableGroup.getGroupAlias() != null ) {
// sqlAppender.appendSql( OPEN_PARENTHESIS );
// }
protected void renderTableGroup(TableGroup tableGroup, List<TableGroupJoin> tableGroupJoinCollector) {
final LockMode effectiveLockMode = getEffectiveLockMode( tableGroup.getSourceAlias() );
final boolean usesLockHint = renderTableReference( tableGroup.getPrimaryTableReference(), effectiveLockMode );
renderTableReferenceJoins( tableGroup );
// if ( tableGroup.getGroupAlias() != null ) {
// sqlAppender.appendSql( CLOSE_PARENTHESIS );
// sqlAppender.appendSql( AS_KEYWORD );
// sqlAppender.appendSql( tableGroup.getGroupAlias() );
// }
processNestedTableGroupJoins( tableGroup );
processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector );
if ( tableGroupJoinCollector != null ) {
tableGroupJoinCollector.addAll( tableGroup.getTableGroupJoins() );
}
else {
processTableGroupJoins( tableGroup );
}
ModelPartContainer modelPart = tableGroup.getModelPart();
if ( modelPart instanceof AbstractEntityPersister ) {
String[] querySpaces = (String[]) ( (AbstractEntityPersister) modelPart ).getQuerySpaces();
@ -3407,32 +3398,56 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
protected void renderTableGroup(TableGroup tableGroup, Predicate predicate) {
protected void renderTableGroup(TableGroup tableGroup, Predicate predicate, List<TableGroupJoin> tableGroupJoinCollector) {
// Without reference joins or nested join groups, even a real table group does not need parenthesis
final boolean realTableGroup = tableGroup.isRealTableGroup()
&& ( CollectionHelper.isNotEmpty( tableGroup.getTableReferenceJoins() )
|| hasTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
if ( realTableGroup ) {
appendSql( OPEN_PARENTHESIS );
}
final LockMode effectiveLockMode = getEffectiveLockMode( tableGroup.getSourceAlias() );
final boolean usesLockHint = renderTableReference( tableGroup.getPrimaryTableReference(), effectiveLockMode );
final List<TableGroupJoin> tableGroupJoins;
if ( realTableGroup ) {
// For real table groups, we collect all normal table group joins within that table group
// The purpose of that is to render them in-order outside of the group/parenthesis
// This is necessary for at least Derby but is also a lot easier to read
renderTableReferenceJoins( tableGroup );
processNestedTableGroupJoins( tableGroup );
if ( tableGroupJoinCollector == null ) {
tableGroupJoins = new ArrayList<>();
processNestedTableGroupJoins( tableGroup, tableGroupJoins );
}
else {
tableGroupJoins = null;
processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector );
}
appendSql( CLOSE_PARENTHESIS );
}
else {
tableGroupJoins = null;
}
appendSql( " on " );
predicate.accept( this );
if ( !realTableGroup ) {
renderTableReferenceJoins( tableGroup );
processNestedTableGroupJoins( tableGroup );
processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector );
}
if ( tableGroupJoinCollector != null ) {
tableGroupJoinCollector.addAll( tableGroup.getTableGroupJoins() );
}
else {
if ( tableGroupJoins != null ) {
for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
processTableGroupJoin( tableGroupJoin, null );
}
}
processTableGroupJoins( tableGroup );
}
ModelPartContainer modelPart = tableGroup.getModelPart();
if ( modelPart instanceof AbstractEntityPersister ) {
@ -3452,13 +3467,22 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
private boolean hasTableGroupsToRender(List<TableGroupJoin> nestedTableGroupJoins) {
private boolean hasNestedTableGroupsToRender(List<TableGroupJoin> nestedTableGroupJoins) {
for ( TableGroupJoin nestedTableGroupJoin : nestedTableGroupJoins ) {
final TableGroup joinedGroup = nestedTableGroupJoin.getJoinedGroup();
if ( joinedGroup instanceof VirtualTableGroup ) {
return !joinedGroup.getTableGroupJoins().isEmpty() || !joinedGroup.getNestedTableGroupJoins().isEmpty();
final TableGroup realTableGroup;
if ( joinedGroup instanceof LazyTableGroup ) {
realTableGroup = ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup();
}
if ( !( joinedGroup instanceof LazyTableGroup ) || ( (LazyTableGroup) joinedGroup ).isInitialized() ) {
else {
realTableGroup = joinedGroup;
}
if ( realTableGroup instanceof VirtualTableGroup ) {
if ( hasNestedTableGroupsToRender( realTableGroup.getNestedTableGroupJoins() ) ) {
return true;
}
}
else if ( realTableGroup != null ) {
return true;
}
}
@ -3522,31 +3546,43 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
@SuppressWarnings("WeakerAccess")
protected void processTableGroupJoins(TableGroup source) {
source.visitTableGroupJoins( this::processTableGroupJoin );
source.visitTableGroupJoins( tableGroupJoin -> processTableGroupJoin( tableGroupJoin, null ) );
}
@SuppressWarnings("WeakerAccess")
protected void processNestedTableGroupJoins(TableGroup source) {
source.visitNestedTableGroupJoins( this::processTableGroupJoin );
protected void processNestedTableGroupJoins(TableGroup source, List<TableGroupJoin> tableGroupJoinCollector) {
source.visitNestedTableGroupJoins( tableGroupJoin -> processTableGroupJoin( tableGroupJoin, tableGroupJoinCollector ) );
}
@SuppressWarnings("WeakerAccess")
protected void processTableGroupJoin(TableGroupJoin tableGroupJoin) {
protected void processTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
if ( joinedGroup instanceof VirtualTableGroup ) {
processTableGroupJoins( tableGroupJoin.getJoinedGroup() );
processNestedTableGroupJoins( tableGroupJoin.getJoinedGroup() );
final TableGroup realTableGroup;
if ( joinedGroup instanceof LazyTableGroup ) {
realTableGroup = ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup();
}
else if ( !( joinedGroup instanceof LazyTableGroup ) || ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup() != null ) {
else {
realTableGroup = joinedGroup;
}
if ( realTableGroup instanceof VirtualTableGroup ) {
processNestedTableGroupJoins( realTableGroup, tableGroupJoinCollector );
if ( tableGroupJoinCollector != null ) {
tableGroupJoinCollector.addAll( realTableGroup.getTableGroupJoins() );
}
else {
processTableGroupJoins( realTableGroup );
}
}
else if ( realTableGroup != null ) {
appendSql( WHITESPACE );
renderJoinType( tableGroupJoin.getJoinType() );
if ( tableGroupJoin.getPredicate() != null && !tableGroupJoin.getPredicate().isEmpty() ) {
renderTableGroup( joinedGroup, tableGroupJoin.getPredicate() );
renderTableGroup( realTableGroup, tableGroupJoin.getPredicate(), tableGroupJoinCollector );
}
else {
renderTableGroup( joinedGroup );
renderTableGroup( realTableGroup, tableGroupJoinCollector );
}
}
}

View File

@ -19,7 +19,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBase;
*
* @author Christian Beikov
*/
public class CollectionTableGroup extends StandardTableGroup {
public class CollectionTableGroup extends StandardTableGroup implements PluralTableGroup {
private TableGroup indexTableGroup;
private TableGroup elementTableGroup;
@ -51,6 +51,21 @@ public class CollectionTableGroup extends StandardTableGroup {
);
}
@Override
public PluralAttributeMapping getModelPart() {
return (PluralAttributeMapping) super.getModelPart();
}
@Override
public TableGroup getElementTableGroup() {
return elementTableGroup;
}
@Override
public TableGroup getIndexTableGroup() {
return indexTableGroup;
}
public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin) {
assert this.indexTableGroup == null;
this.indexTableGroup = indexTableGroupJoin.getJoinedGroup();
@ -78,7 +93,7 @@ public class CollectionTableGroup extends StandardTableGroup {
if ( tableReference != null ) {
return tableReference;
}
if ( indexTableGroup != null ) {
if ( indexTableGroup != null && ( navigablePath == null || indexTableGroup.getNavigablePath().isParent( navigablePath ) ) ) {
final TableReference indexTableReference = indexTableGroup.getTableReference(
navigablePath,
tableExpression,
@ -89,7 +104,7 @@ public class CollectionTableGroup extends StandardTableGroup {
return indexTableReference;
}
}
if ( elementTableGroup != null ) {
if ( elementTableGroup != null && ( navigablePath == null || elementTableGroup.getNavigablePath().isParent( navigablePath ) ) ) {
final TableReference elementTableReference = elementTableGroup.getTableReference(
navigablePath,
tableExpression,

View File

@ -20,6 +20,14 @@ public interface ColumnReferenceQualifier {
return resolveTableReference( null, tableExpression, true );
}
/**
* Like {@link #getTableReference(NavigablePath, String, boolean, boolean)}, but will throw an exception if no
* table reference can be found, even after resolving possible table reference joins.
*
* @param navigablePath The path for which to look up the table reference, may be null
* @param tableExpression The table expression for which to look up the table reference
* @param allowFkOptimization Whether a foreign key optimization is allowed i.e. use the FK column on the key-side
*/
TableReference resolveTableReference(
NavigablePath navigablePath,
String tableExpression,
@ -33,6 +41,14 @@ public interface ColumnReferenceQualifier {
return getTableReference( null, tableExpression, true, false );
}
/**
* Returns the table reference for the table expression, or null if not found.
*
* @param navigablePath The path for which to look up the table reference, may be null
* @param tableExpression The table expression for which to look up the table reference
* @param allowFkOptimization Whether a foreign key optimization is allowed i.e. use the FK column on the key-side
* @param resolve Whether to potentially create table reference joins for this table group
*/
TableReference getTableReference(
NavigablePath navigablePath,
String tableExpression,

View File

@ -0,0 +1,102 @@
/*
* 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.sql.ast.tree.from;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
/**
* A table group for correlated plural attributes.
*
* @author Christian Beikov
*/
public class CorrelatedPluralTableGroup extends CorrelatedTableGroup implements PluralTableGroup {
private TableGroup indexTableGroup;
private TableGroup elementTableGroup;
public CorrelatedPluralTableGroup(
TableGroup correlatedTableGroup,
SqlAliasBase sqlAliasBase,
QuerySpec querySpec,
Consumer<Predicate> joinPredicateConsumer,
SessionFactoryImplementor sessionFactory) {
super( correlatedTableGroup, sqlAliasBase, querySpec, joinPredicateConsumer, sessionFactory );
}
@Override
public PluralAttributeMapping getModelPart() {
return (PluralAttributeMapping) super.getModelPart();
}
@Override
public TableGroup getElementTableGroup() {
return elementTableGroup;
}
@Override
public TableGroup getIndexTableGroup() {
return indexTableGroup;
}
public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin) {
assert this.indexTableGroup == null;
this.indexTableGroup = indexTableGroupJoin.getJoinedGroup();
}
public void registerElementTableGroup(TableGroupJoin elementTableGroupJoin) {
assert this.elementTableGroup == null;
this.elementTableGroup = elementTableGroupJoin.getJoinedGroup();
}
@Override
protected TableReference getTableReferenceInternal(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization,
boolean resolve) {
final TableReference tableReference = super.getTableReferenceInternal(
navigablePath,
tableExpression,
allowFkOptimization,
resolve
);
if ( tableReference != null ) {
return tableReference;
}
if ( indexTableGroup != null && ( navigablePath == null || indexTableGroup.getNavigablePath().isParent( navigablePath ) ) ) {
final TableReference indexTableReference = indexTableGroup.getTableReference(
navigablePath,
tableExpression,
allowFkOptimization,
resolve
);
if ( indexTableReference != null ) {
return indexTableReference;
}
}
if ( elementTableGroup != null && ( navigablePath == null || elementTableGroup.getNavigablePath().isParent( navigablePath ) ) ) {
final TableReference elementTableReference = elementTableGroup.getTableReference(
navigablePath,
tableExpression,
allowFkOptimization,
resolve
);
if ( elementTableReference != null ) {
return elementTableReference;
}
}
return null;
}
}

View File

@ -56,6 +56,15 @@ public class CorrelatedTableGroup extends AbstractTableGroup {
super.addTableGroupJoin( join );
}
@Override
public void addNestedTableGroupJoin(TableGroupJoin join) {
assert !getTableGroupJoins().contains( join );
assert join.getJoinType() == SqlAstJoinType.INNER;
querySpec.getFromClause().addRoot( join.getJoinedGroup() );
joinPredicateConsumer.accept( join.getPredicate() );
super.addNestedTableGroupJoin( join );
}
@Override
protected TableReference getTableReferenceInternal(
NavigablePath navigablePath,

View File

@ -18,6 +18,11 @@ import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAliasBase;
/**
* The purpose of this table group is to defer creating the actual table group until it is really needed.
* If it is not needed, we can safely skip rendering it. This is useful for ToOneAttributeMapping and EntityCollectionPart,
* where we need a table group for the association, but aren't sure which columns are needed yet.
* Deferring initialization enables getting away with fewer joins in case only foreign key columns are used.
*
* @author Christian Beikov
*/
public class LazyTableGroup extends AbstractColumnReferenceQualifier implements TableGroup {

View File

@ -0,0 +1,173 @@
/*
* 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.sql.ast.tree.from;
import java.util.Collections;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.query.NavigablePath;
/**
* @author Christian Beikov
*/
public class MappedByTableGroup implements VirtualTableGroup {
private final NavigablePath navigablePath;
private final ModelPartContainer modelPart;
private final TableGroup underlyingTableGroup;
private final boolean fetched;
private final TableGroup parentTableGroup;
private final BiPredicate<NavigablePath, String> navigablePathChecker;
public MappedByTableGroup(
NavigablePath navigablePath,
ModelPartContainer modelPart,
TableGroup underlyingTableGroup,
boolean fetched,
TableGroup parentTableGroup,
BiPredicate<NavigablePath, String> navigablePathChecker) {
this.navigablePath = navigablePath;
this.modelPart = modelPart;
this.underlyingTableGroup = underlyingTableGroup;
this.fetched = fetched;
this.parentTableGroup = parentTableGroup;
this.navigablePathChecker = navigablePathChecker;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public ModelPartContainer getExpressionType() {
return getModelPart();
}
@Override
public String getGroupAlias() {
// none, although we could also delegate to the underlyingTableGroup's group-alias
return null;
}
@Override
public boolean isFetched() {
return fetched;
}
@Override
public ModelPartContainer getModelPart() {
return modelPart;
}
@Override
public String getSourceAlias() {
return underlyingTableGroup.getSourceAlias();
}
@Override
public List<TableGroupJoin> getTableGroupJoins() {
return Collections.emptyList();
}
@Override
public List<TableGroupJoin> getNestedTableGroupJoins() {
return Collections.emptyList();
}
@Override
public boolean isRealTableGroup() {
return false;
}
@Override
public boolean canUseInnerJoins() {
return underlyingTableGroup.canUseInnerJoins();
}
@Override
public void addTableGroupJoin(TableGroupJoin join) {
underlyingTableGroup.addTableGroupJoin( join );
}
@Override
public void addNestedTableGroupJoin(TableGroupJoin join) {
underlyingTableGroup.addNestedTableGroupJoin( join );
}
@Override
public void visitTableGroupJoins(Consumer<TableGroupJoin> consumer) {
// No-op
}
@Override
public void visitNestedTableGroupJoins(Consumer<TableGroupJoin> consumer) {
// No-op
}
@Override
public void applyAffectedTableNames(Consumer<String> nameCollector) {
underlyingTableGroup.applyAffectedTableNames( nameCollector );
}
@Override
public TableReference getPrimaryTableReference() {
return underlyingTableGroup.getPrimaryTableReference();
}
@Override
public List<TableReferenceJoin> getTableReferenceJoins() {
return underlyingTableGroup.getTableReferenceJoins();
}
@Override
public TableReference resolveTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization) {
final TableReference tableReference = getTableReference(
navigablePath,
tableExpression,
allowFkOptimization,
true
);
if ( tableReference == null ) {
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" );
}
return tableReference;
}
@Override
public TableReference getTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization,
boolean resolve) {
if ( allowFkOptimization && ( navigablePath == null || navigablePathChecker.test( navigablePath, tableExpression ) ) ) {
final TableReference reference = parentTableGroup.getTableReference(
navigablePath,
tableExpression,
allowFkOptimization,
resolve
);
if ( reference != null ) {
return reference;
}
}
return underlyingTableGroup.getTableReference(
navigablePath,
tableExpression,
allowFkOptimization,
resolve
);
}
}

View File

@ -22,7 +22,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState;
*
* @author Christian Beikov
*/
public class OneToManyTableGroup extends AbstractColumnReferenceQualifier implements TableGroup {
public class OneToManyTableGroup extends AbstractColumnReferenceQualifier implements TableGroup, PluralTableGroup {
private final SessionFactoryImplementor sessionFactory;
private final PluralAttributeMapping pluralAttributeMapping;
private final TableGroup elementTableGroup;
@ -52,10 +52,16 @@ public class OneToManyTableGroup extends AbstractColumnReferenceQualifier implem
return sessionFactory;
}
@Override
public TableGroup getElementTableGroup() {
return elementTableGroup;
}
@Override
public TableGroup getIndexTableGroup() {
return indexTableGroup;
}
public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin) {
assert this.indexTableGroup == null;
this.indexTableGroup = indexTableGroupJoin.getJoinedGroup();
@ -163,7 +169,8 @@ public class OneToManyTableGroup extends AbstractColumnReferenceQualifier implem
allowFkOptimization,
resolve
);
if ( tableReference != null || indexTableGroup == null ) {
if ( tableReference != null || indexTableGroup == null
|| navigablePath != null && indexTableGroup.getNavigablePath().isParent( navigablePath ) ) {
return tableReference;
}

View File

@ -0,0 +1,21 @@
/*
* 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.sql.ast.tree.from;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
/**
* @author Christian Beikov
*/
public interface PluralTableGroup extends TableGroup {
PluralAttributeMapping getModelPart();
TableGroup getElementTableGroup();
TableGroup getIndexTableGroup();
}

View File

@ -12,6 +12,7 @@ import java.util.function.Supplier;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -33,7 +34,8 @@ public interface RootTableGroupProducer extends TableGroupProducer, ModelPartCon
NavigablePath navigablePath,
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState, SqlAstCreationContext creationContext);
SqlAstCreationState creationState,
SqlAstCreationContext creationContext);
TableGroup createRootTableGroup(
boolean canUseInnerJoins,
@ -42,5 +44,6 @@ public interface RootTableGroupProducer extends TableGroupProducer, ModelPartCon
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlExpressionResolver expressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext);
}

View File

@ -11,29 +11,28 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.query.NavigablePath;
/**
* @author Steve Ebersole
* @author Christian Beikov
*/
public class CompositeTableGroup implements VirtualTableGroup {
public class StandardVirtualTableGroup implements VirtualTableGroup {
private final NavigablePath navigablePath;
private final EmbeddableValuedModelPart compositionMapping;
private final ModelPartContainer modelPart;
private final TableGroup underlyingTableGroup;
private final boolean fetched;
private List<TableGroupJoin> tableGroupJoins;
private List<TableGroupJoin> nestedTableGroupJoins;
public CompositeTableGroup(
public StandardVirtualTableGroup(
NavigablePath navigablePath,
EmbeddableValuedModelPart compositionMapping,
ModelPartContainer modelPart,
TableGroup underlyingTableGroup,
boolean fetched) {
this.navigablePath = navigablePath;
this.compositionMapping = compositionMapping;
this.modelPart = modelPart;
this.underlyingTableGroup = underlyingTableGroup;
this.fetched = fetched;
}
@ -44,7 +43,7 @@ public class CompositeTableGroup implements VirtualTableGroup {
}
@Override
public EmbeddableValuedModelPart getExpressionType() {
public ModelPartContainer getExpressionType() {
return getModelPart();
}
@ -60,8 +59,8 @@ public class CompositeTableGroup implements VirtualTableGroup {
}
@Override
public EmbeddableValuedModelPart getModelPart() {
return compositionMapping;
public ModelPartContainer getModelPart() {
return modelPart;
}
@Override
@ -136,25 +135,35 @@ public class CompositeTableGroup implements VirtualTableGroup {
return underlyingTableGroup.getTableReferenceJoins();
}
@Override
public TableReference resolveTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization) {
final TableReference tableReference = getTableReference(
navigablePath,
tableExpression,
allowFkOptimization,
true
);
if ( tableReference == null ) {
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" );
}
return tableReference;
}
@Override
public TableReference getTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization,
boolean resolve) {
return underlyingTableGroup.getTableReference( navigablePath, tableExpression, allowFkOptimization, resolve );
}
@Override
public TableReference resolveTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization) {
final TableReference tableReference = underlyingTableGroup.getTableReference(
navigablePath,
tableExpression,
allowFkOptimization,
true
resolve
);
if ( tableReference != null ) {
return tableReference;
@ -162,7 +171,7 @@ public class CompositeTableGroup implements VirtualTableGroup {
for ( TableGroupJoin tableGroupJoin : getNestedTableGroupJoins() ) {
final TableReference primaryTableReference = tableGroupJoin.getJoinedGroup()
.getPrimaryTableReference()
.getTableReference( navigablePath, tableExpression, allowFkOptimization, true );
.getTableReference( navigablePath, tableExpression, allowFkOptimization, resolve );
if ( primaryTableReference != null ) {
return primaryTableReference;
}
@ -170,12 +179,12 @@ public class CompositeTableGroup implements VirtualTableGroup {
for ( TableGroupJoin tableGroupJoin : getTableGroupJoins() ) {
final TableReference primaryTableReference = tableGroupJoin.getJoinedGroup()
.getPrimaryTableReference()
.getTableReference( navigablePath, tableExpression, allowFkOptimization, true );
.getTableReference( navigablePath, tableExpression, allowFkOptimization, resolve );
if ( primaryTableReference != null ) {
return primaryTableReference;
}
}
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" );
return null;
}
}

View File

@ -48,6 +48,48 @@ public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPat
void addTableGroupJoin(TableGroupJoin join);
/**
* A nested table group join is a join against a table group,
* that is ensured to be joined against the primary table reference and table reference joins in isolation,
* prior to doing other table group joins e.g.
*
* <code>
* select *
* from entity1 e
* left join (
* collection_table c1
* join association a on a.id = c1.target_id
* ) on c1.entity_id = e.id and c1.key = 1
* </code>
*
* is modeled as
*
* <code>
* TableGroup(
* primaryTableReference = TableReference(entity1, e),
* tableGroupJoins = [
* TableGroupJoin(
* TableGroup(
* primaryTableReference = TableReference(collection_table, c1),
* nestedTableGroupJoins = [
* TableGroupJoin(
* TableGroup(
* primaryTableReference = TableReference(association, a)
* )
* )
* ]
* )
* )
* ]
* )
* </code>
*
* This is necessary to correctly retain the cardinality of an HQL join like e.g.
*
* <code>
* from Entity1 e left join e.collectionAssociation c on key(c) = 1
* </code>
*/
void addNestedTableGroupJoin(TableGroupJoin join);
void visitTableGroupJoins(Consumer<TableGroupJoin> consumer);

View File

@ -10,6 +10,7 @@ import java.util.function.Consumer;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -32,6 +33,7 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
return createTableGroupJoin(
navigablePath,
@ -39,8 +41,10 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
explicitSourceAlias,
sqlAstJoinType,
fetched,
addsPredicate,
creationState.getSqlAliasBaseGenerator(),
creationState.getSqlExpressionResolver(),
creationState.getFromClauseAccess(),
creationState.getCreationContext()
);
}
@ -54,8 +58,10 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext);
/**
@ -78,6 +84,7 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
predicateConsumer,
creationState.getSqlAliasBaseGenerator(),
creationState.getSqlExpressionResolver(),
creationState.getFromClauseAccess(),
creationState.getCreationContext()
);
}
@ -94,5 +101,6 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext);
}

View File

@ -1,134 +0,0 @@
/*
* 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.sql.results.graph.collection.internal;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
/**
* @author Steve Ebersole
*/
public class EntityCollectionPartTableGroup implements TableGroup {
private final NavigablePath collectionPartPath;
private final TableGroup collectionTableGroup;
private final EntityCollectionPart collectionPart;
public EntityCollectionPartTableGroup(
NavigablePath collectionPartPath,
TableGroup collectionTableGroup,
EntityCollectionPart collectionPart) {
this.collectionPartPath = collectionPartPath;
this.collectionTableGroup = collectionTableGroup;
this.collectionPart = collectionPart;
}
@Override
public NavigablePath getNavigablePath() {
return collectionPartPath;
}
@Override
public ModelPart getExpressionType() {
return getModelPart();
}
@Override
public String getGroupAlias() {
return null;
}
@Override
public EntityCollectionPart getModelPart() {
return collectionPart;
}
@Override
public String getSourceAlias() {
return collectionTableGroup.getSourceAlias();
}
@Override
public List<TableGroupJoin> getTableGroupJoins() {
return collectionTableGroup.getTableGroupJoins();
}
@Override
public List<TableGroupJoin> getNestedTableGroupJoins() {
return collectionTableGroup.getNestedTableGroupJoins();
}
@Override
public boolean canUseInnerJoins() {
return collectionTableGroup.canUseInnerJoins();
}
@Override
public void addTableGroupJoin(TableGroupJoin join) {
collectionTableGroup.addTableGroupJoin( join );
}
@Override
public void addNestedTableGroupJoin(TableGroupJoin join) {
collectionTableGroup.addNestedTableGroupJoin( join );
}
@Override
public void visitTableGroupJoins(Consumer<TableGroupJoin> consumer) {
collectionTableGroup.visitTableGroupJoins( consumer );
}
@Override
public void visitNestedTableGroupJoins(Consumer<TableGroupJoin> consumer) {
collectionTableGroup.visitNestedTableGroupJoins( consumer );
}
@Override
public void applyAffectedTableNames(Consumer<String> nameCollector) {
collectionTableGroup.applyAffectedTableNames( nameCollector );
}
@Override
public TableReference getPrimaryTableReference() {
return collectionTableGroup.getPrimaryTableReference();
}
@Override
public List<TableReferenceJoin> getTableReferenceJoins() {
return collectionTableGroup.getTableReferenceJoins();
}
@Override
public TableReference getTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization,
boolean resolve) {
return collectionTableGroup.getTableReference( navigablePath, tableExpression, allowFkOptimization, resolve );
}
@Override
public TableReference resolveTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization) {
return collectionTableGroup.resolveTableReference( navigablePath, tableExpression, allowFkOptimization );
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
// do nothing
}
}

View File

@ -65,6 +65,7 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab
null,
SqlAstJoinType.INNER,
true,
false,
creationState.getSqlAstCreationState()
);
lhsTableGroup.addTableGroupJoin( tableGroupJoin );

View File

@ -54,6 +54,7 @@ public class EmbeddableResultImpl<T> extends AbstractFetchParent implements Embe
resultVariable,
SqlAstJoinType.INNER,
true,
false,
creationState.getSqlAstCreationState()
);
tableGroup.addTableGroupJoin( tableGroupJoin );

View File

@ -57,7 +57,7 @@ public class EntityDelayedFetchImpl extends AbstractNonJoinedEntityFetch {
() -> new EntityDelayedFetchInitializer(
parentAccess,
navigablePath,
getEntityValuedModelPart(),
(ToOneAttributeMapping) getEntityValuedModelPart(),
selectByUniqueKey,
keyResult.createResultAssembler( creationState )
)

View File

@ -39,9 +39,9 @@ import org.hibernate.query.sqm.internal.QuerySqmImpl;
import org.hibernate.query.sqm.sql.SqmTranslation;
import org.hibernate.query.sqm.sql.internal.StandardSqmTranslator;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.select.SelectStatement;
@ -168,7 +168,7 @@ public class CriteriaEntityGraphTest implements SessionFactoryScopeAware {
TableGroup::getClass
) );
Map<String, Class<? extends TableGroup> > expectedTableGroupByName = new HashMap<>();
expectedTableGroupByName.put( "homeAddress", CompositeTableGroup.class );
expectedTableGroupByName.put( "homeAddress", StandardVirtualTableGroup.class );
expectedTableGroupByName.put( "company", LazyTableGroup.class );
assertThat( tableGroupByName, is( expectedTableGroupByName ) );
} );
@ -250,14 +250,16 @@ public class CriteriaEntityGraphTest implements SessionFactoryScopeAware {
// Check the from-clause
assertPluralAttributeJoinedGroup( sqlAst, "shipAddresses", tableGroup -> {
if ( graphSemantic == GraphSemantic.LOAD ) {
assertThat( tableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( tableGroup.getTableGroupJoins(), isEmpty() );
assertThat( tableGroup.getNestedTableGroupJoins(), hasSize( 1 ) );
final TableGroup compositeTableGroup = tableGroup.getTableGroupJoins()
final TableGroup compositeTableGroup = tableGroup.getNestedTableGroupJoins()
.iterator()
.next()
.getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( CompositeTableGroup.class ) );
assertThat( compositeTableGroup, instanceOf( StandardVirtualTableGroup.class ) );
assertThat( compositeTableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( compositeTableGroup.getNestedTableGroupJoins(), isEmpty() );
final TableGroup countryTableGroup = compositeTableGroup.getTableGroupJoins()
.iterator()
@ -266,13 +268,16 @@ public class CriteriaEntityGraphTest implements SessionFactoryScopeAware {
assertThat( countryTableGroup.getModelPart().getPartName(), is( "country" ) );
assertThat( countryTableGroup.getTableGroupJoins(), isEmpty() );
assertThat( countryTableGroup.getNestedTableGroupJoins(), isEmpty() );
}
else {
assertThat( tableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( tableGroup.getTableGroupJoins(), isEmpty() );
assertThat( tableGroup.getNestedTableGroupJoins(), hasSize( 1 ) );
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getTableGroupJoins() ).getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( CompositeTableGroup.class ) );
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getNestedTableGroupJoins() ).getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( StandardVirtualTableGroup.class ) );
assertThat( compositeTableGroup.getTableGroupJoins(), isEmpty() );
assertThat( compositeTableGroup.getNestedTableGroupJoins(), isEmpty() );
}
} );
@ -324,7 +329,7 @@ public class CriteriaEntityGraphTest implements SessionFactoryScopeAware {
final TableGroup joinedGroup = tableGroup.getTableGroupJoins().iterator().next().getJoinedGroup();
assertThat( joinedGroup.getModelPart().getPartName(), is( "homeAddress" ) );
assertThat( joinedGroup.getModelPart(), instanceOf( EmbeddedAttributeMapping.class ) );
assertThat( joinedGroup, instanceOf( CompositeTableGroup.class ) );
assertThat( joinedGroup, instanceOf( StandardVirtualTableGroup.class ) );
}
// util methods for verifying 'domain-result' graph ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -33,9 +33,9 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.select.SelectStatement;
@ -164,7 +164,7 @@ public class EntityGraphLoadPlanBuilderTest implements SessionFactoryScopeAware
TableGroup::getClass
) );
Map<String, Class<? extends TableGroup> > expectedTableGroupByName = new HashMap<>();
expectedTableGroupByName.put( "homeAddress", CompositeTableGroup.class );
expectedTableGroupByName.put( "homeAddress", StandardVirtualTableGroup.class );
expectedTableGroupByName.put( "company", LazyTableGroup.class );
assertThat( tableGroupByName, is( expectedTableGroupByName ) );
} );
@ -245,25 +245,30 @@ public class EntityGraphLoadPlanBuilderTest implements SessionFactoryScopeAware
// Check the from-clause
assertPluralAttributeJoinedGroup( sqlAst, "shipAddresses", tableGroup -> {
if ( graphSemantic == GraphSemantic.LOAD ) {
assertThat( tableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( tableGroup.getTableGroupJoins(), isEmpty() );
assertThat( tableGroup.getNestedTableGroupJoins(), hasSize( 1 ) );
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getTableGroupJoins() )
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getNestedTableGroupJoins() )
.getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( CompositeTableGroup.class ) );
assertThat( compositeTableGroup, instanceOf( StandardVirtualTableGroup.class ) );
assertThat( compositeTableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( compositeTableGroup.getNestedTableGroupJoins(), isEmpty() );
final TableGroup countryTableGroup = CollectionUtils.getOnlyElement( compositeTableGroup.getTableGroupJoins() )
.getJoinedGroup();
assertThat( countryTableGroup.getModelPart().getPartName(), is( "country" ) );
assertThat( countryTableGroup.getTableGroupJoins(), isEmpty() );
assertThat( countryTableGroup.getNestedTableGroupJoins(), isEmpty() );
}
else {
assertThat( tableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( tableGroup.getTableGroupJoins(), isEmpty() );
assertThat( tableGroup.getNestedTableGroupJoins(), hasSize( 1 ) );
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getTableGroupJoins() ).getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( CompositeTableGroup.class ) );
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getNestedTableGroupJoins() ).getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( StandardVirtualTableGroup.class ) );
assertThat( compositeTableGroup.getTableGroupJoins(), isEmpty() );
assertThat( compositeTableGroup.getNestedTableGroupJoins(), isEmpty() );
}
} );
@ -315,7 +320,7 @@ public class EntityGraphLoadPlanBuilderTest implements SessionFactoryScopeAware
final TableGroup joinedGroup = CollectionUtils.getOnlyElement( tableGroup.getTableGroupJoins() ).getJoinedGroup();
assertThat( joinedGroup.getModelPart().getPartName(), is( "homeAddress" ) );
assertThat( joinedGroup.getModelPart(), instanceOf( EmbeddedAttributeMapping.class ) );
assertThat( joinedGroup, instanceOf( CompositeTableGroup.class ) );
assertThat( joinedGroup, instanceOf( StandardVirtualTableGroup.class ) );
}
// util methods for verifying 'domain-result' graph ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -37,9 +37,9 @@ import org.hibernate.query.sqm.internal.QuerySqmImpl;
import org.hibernate.query.sqm.sql.SqmTranslation;
import org.hibernate.query.sqm.sql.internal.StandardSqmTranslator;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.select.SelectStatement;
@ -166,7 +166,7 @@ public class HqlEntityGraphTest implements SessionFactoryScopeAware {
TableGroup::getClass
) );
Map<String, Class<? extends TableGroup> > expectedTableGroupByName = new HashMap<>();
expectedTableGroupByName.put( "homeAddress", CompositeTableGroup.class );
expectedTableGroupByName.put( "homeAddress", StandardVirtualTableGroup.class );
expectedTableGroupByName.put( "company", LazyTableGroup.class );
assertThat( tableGroupByName, is( expectedTableGroupByName ) );
} );
@ -248,14 +248,16 @@ public class HqlEntityGraphTest implements SessionFactoryScopeAware {
// Check the from-clause
assertPluralAttributeJoinedGroup( sqlAst, "shipAddresses", tableGroup -> {
if ( graphSemantic == GraphSemantic.LOAD ) {
assertThat( tableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( tableGroup.getTableGroupJoins(), isEmpty() );
assertThat( tableGroup.getNestedTableGroupJoins(), hasSize( 1 ) );
final TableGroup compositeTableGroup = tableGroup.getTableGroupJoins()
final TableGroup compositeTableGroup = tableGroup.getNestedTableGroupJoins()
.iterator()
.next()
.getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( CompositeTableGroup.class ) );
assertThat( compositeTableGroup, instanceOf( StandardVirtualTableGroup.class ) );
assertThat( compositeTableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( compositeTableGroup.getNestedTableGroupJoins(), isEmpty() );
final TableGroup countryTableGroup = compositeTableGroup.getTableGroupJoins()
.iterator()
@ -264,13 +266,16 @@ public class HqlEntityGraphTest implements SessionFactoryScopeAware {
assertThat( countryTableGroup.getModelPart().getPartName(), is( "country" ) );
assertThat( countryTableGroup.getTableGroupJoins(), isEmpty() );
assertThat( countryTableGroup.getNestedTableGroupJoins(), isEmpty() );
}
else {
assertThat( tableGroup.getTableGroupJoins(), hasSize( 1 ) );
assertThat( tableGroup.getTableGroupJoins(), isEmpty() );
assertThat( tableGroup.getNestedTableGroupJoins(), hasSize( 1 ) );
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getTableGroupJoins() ).getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( CompositeTableGroup.class ) );
final TableGroup compositeTableGroup = CollectionUtils.getOnlyElement( tableGroup.getNestedTableGroupJoins() ).getJoinedGroup();
assertThat( compositeTableGroup, instanceOf( StandardVirtualTableGroup.class ) );
assertThat( compositeTableGroup.getTableGroupJoins(), isEmpty() );
assertThat( compositeTableGroup.getNestedTableGroupJoins(), isEmpty() );
}
} );
}
@ -321,7 +326,7 @@ public class HqlEntityGraphTest implements SessionFactoryScopeAware {
final TableGroup joinedGroup = tableGroup.getTableGroupJoins().iterator().next().getJoinedGroup();
assertThat( joinedGroup.getModelPart().getPartName(), is( "homeAddress" ) );
assertThat( joinedGroup.getModelPart(), instanceOf( EmbeddedAttributeMapping.class ) );
assertThat( joinedGroup, instanceOf( CompositeTableGroup.class ) );
assertThat( joinedGroup, instanceOf( StandardVirtualTableGroup.class ) );
}
// util methods for verifying 'domain-result' graph ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -6,45 +6,104 @@
*/
package org.hibernate.orm.test.jpa.ql;
import org.hibernate.Session;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.test.jpa.AbstractJPATest;
import org.hibernate.test.jpa.MapContent;
import org.hibernate.test.jpa.MapOwner;
import org.hibernate.test.jpa.Relationship;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
@TestForIssue(jiraKey = "HHH-14279")
public class MapIssueTest extends AbstractJPATest {
@Override
public String[] getMappings() {
return new String[] {};
}
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { MapOwner.class, MapContent.class, Relationship.class};
}
@DomainModel(
annotatedClasses = {
MapOwner.class, MapContent.class, Relationship.class
})
@SessionFactory(statementInspectorClass = SQLStatementInspector.class)
public class MapIssueTest {
@Test
@RequiresDialect(value = PostgreSQLDialect.class, comment = "Requires support for using a correlated column in a join condition which H2 apparently does not support. For simplicity just run this on PostgreSQL")
public void testWhereSubqueryMapKeyIsEntityWhereWithKey() {
Session s = openSession();
s.beginTransaction();
public void testWhereSubqueryMapKeyIsEntityWhereWithKey(SessionFactoryScope scope) {
scope.inTransaction(
s -> {
s.createQuery( "select r from Relationship r where exists (select 1 from MapOwner as o left join o.contents c with key(c) = r)" ).list();
s.getTransaction().commit();
s.close();
}
);
}
@Test
public void testMapKeyIsEntityWhereWithKey() {
Session s = openSession();
s.beginTransaction();
public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select 1 from MapOwner as o left join o.contents c where c.id is not null" ).list();
s.getTransaction().commit();
s.close();
statementInspector.assertExecutedCount( 1 );
// Assert only the collection table is joined
statementInspector.assertNumberOfJoins( 0, 1 );
}
);
}
@Test
public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select c from MapOwner as o left join o.contents c join c.relationship r where r.id is not null" ).list();
statementInspector.assertExecutedCount( 1 );
// Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable
statementInspector.assertNumberOfJoins( 0, 2 );
}
);
}
@Test
public void testMapKeyJoinIsReused(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select key(c), c from MapOwner as o left join o.contents c join c.relationship r where r.name is not null" ).list();
statementInspector.assertExecutedCount( 1 );
// Assert 3 joins, collection table, collection element and relationship
statementInspector.assertNumberOfJoins( 0, 3 );
}
);
}
@Test
public void testMapKeyJoinIsReusedForFurtherJoin(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select key(c), c from MapOwner as o left join o.contents c join c.relationship r join r.self s where s.name is not null" ).list();
statementInspector.assertExecutedCount( 1 );
// Assert 3 joins, collection table, collection element, relationship and self
statementInspector.assertNumberOfJoins( 0, 4 );
}
);
}
@Test
public void testMapKeyJoinIsReusedForFurtherJoinAndElementJoinIsProperlyOrdered(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select key(c), c from MapOwner as o left join o.contents c join c.relationship r join r.self s join c.relationship2 where s.name is not null" ).list();
statementInspector.assertExecutedCount( 1 );
// Assert 3 joins, collection table, collection element, relationship, relationship2 and self
statementInspector.assertNumberOfJoins( 0, 5 );
}
);
}
}

View File

@ -147,7 +147,6 @@ public class TreatKeywordTest extends BaseCoreFunctionalTestCase {
@Test
@TestForIssue( jiraKey = "HHH-9862" )
// @FailureExpected( jiraKey = "HHH-9862" )
public void testRestrictionsOnJoinedSubclasses() {
Session s = openSession();
s.beginTransaction();

View File

@ -11,6 +11,8 @@ public class MapContent {
private Long id;
@ManyToOne(optional = false)
private Relationship relationship;
@ManyToOne
private Relationship relationship2;
public Long getId() {
return id;

View File

@ -2,6 +2,7 @@ package org.hibernate.test.jpa;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
@Entity
public class Relationship {
@ -9,6 +10,8 @@ public class Relationship {
@Id
private Long id;
private String name;
@ManyToOne
private Relationship self;
public Long getId() {
return id;

View File

@ -43,7 +43,7 @@ public final class DialectContext {
}
}
catch (ClassNotFoundException cnfe) {
throw new HibernateException( "Dialect class not found: " + dialectName );
throw new HibernateException( "Dialect class not found: " + dialectName, cnfe );
}
catch (Exception e) {
throw new HibernateException( "Could not instantiate given dialect class: " + dialectName, e );