HHH-17646 Optimize away real table group rendering if possible

This commit is contained in:
Christian Beikov 2024-01-30 17:47:32 +01:00
parent 5fd74adcbf
commit a956fda688
42 changed files with 1490 additions and 233 deletions

View File

@ -76,7 +76,7 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
}
@Override
protected void renderTableReferenceJoins(TableGroup tableGroup) {
protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) {
// When we are in a recursive CTE, we can't render joins on DB2...
// See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
if ( isInRecursiveQueryPart() ) {
@ -103,7 +103,7 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
else {
super.renderTableReferenceJoins( tableGroup );
super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
}
}

View File

@ -340,14 +340,10 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
final TableReference tableRef = tableGroup.getPrimaryTableReference();
// The H2 parser can't handle a sub-query as first element in a nested join
// i.e. `join ( (select ...) alias join ... )`, so we have to introduce a dummy table reference
if ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) {
final boolean realTableGroup = tableGroup.isRealTableGroup()
&& ( CollectionHelper.isNotEmpty( tableGroup.getTableReferenceJoins() )
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
if ( realTableGroup ) {
if ( getSqlBuffer().charAt( getSqlBuffer().length() - 1 ) == '('
&& ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) ) {
appendSql( "dual cross join " );
}
}
return super.renderPrimaryTableReference( tableGroup, lockMode );
}

View File

@ -12,20 +12,14 @@ import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
@ -147,8 +141,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
protected void visitAnsiCaseSearchedExpression(
CaseSearchedExpression expression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = expression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSearchedExpression(
@ -172,8 +165,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
protected void visitAnsiCaseSimpleExpression(
CaseSimpleExpression expression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = expression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSimpleExpression(
@ -193,11 +185,11 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
}
}
protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression caseSearchedExpression) {
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSearchedExpression caseSearchedExpression) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|| isLiteral( firstResult ) ) {
|| isStringLiteral( firstResult ) ) {
for ( int i = 1; i < whenFragments.size(); i++ ) {
final Expression result = whenFragments.get( i ).getResult();
if ( isParameter( result ) ) {
@ -205,7 +197,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
return false;
}
}
else if ( !isLiteral( result ) ) {
else if ( !isStringLiteral( result ) ) {
return false;
}
}
@ -214,11 +206,11 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
return false;
}
protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression caseSimpleExpression) {
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSimpleExpression caseSimpleExpression) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|| isLiteral( firstResult ) ) {
|| isStringLiteral( firstResult ) ) {
for ( int i = 1; i < whenFragments.size(); i++ ) {
final Expression result = whenFragments.get( i ).getResult();
if ( isParameter( result ) ) {
@ -226,7 +218,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
return false;
}
}
else if ( !isLiteral( result ) ) {
else if ( !isStringLiteral( result ) ) {
return false;
}
}
@ -235,6 +227,13 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
return false;
}
private boolean isStringLiteral( Expression expression ) {
if ( expression instanceof Literal ) {
return ( (Literal) expression ).getJdbcMapping().getJdbcType().isStringLike();
}
return false;
}
@Override
public boolean supportsFilterClause() {
return true;

View File

@ -75,7 +75,7 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
}
@Override
protected void renderTableReferenceJoins(TableGroup tableGroup) {
protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) {
// When we are in a recursive CTE, we can't render joins on DB2...
// See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
if ( isInRecursiveQueryPart() ) {
@ -102,7 +102,7 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
}
}
else {
super.renderTableReferenceJoins( tableGroup );
super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
}
}

View File

@ -314,14 +314,10 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslato
final TableReference tableRef = tableGroup.getPrimaryTableReference();
// The H2 parser can't handle a sub-query as first element in a nested join
// i.e. `join ( (select ...) alias join ... )`, so we have to introduce a dummy table reference
if ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) {
final boolean realTableGroup = tableGroup.isRealTableGroup()
&& ( isNotEmpty( tableGroup.getTableReferenceJoins() )
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
if ( realTableGroup ) {
if ( getSqlBuffer().charAt( getSqlBuffer().length() - 1 ) == '('
&& ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) ) {
appendSql( "dual cross join " );
}
}
return super.renderPrimaryTableReference( tableGroup, lockMode );
}

View File

@ -12,14 +12,12 @@ import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -152,8 +150,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
protected void visitAnsiCaseSearchedExpression(
CaseSearchedExpression expression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = expression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSearchedExpression(
@ -177,8 +174,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
protected void visitAnsiCaseSimpleExpression(
CaseSimpleExpression expression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = expression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSimpleExpression(
@ -198,11 +194,11 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
}
protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression caseSearchedExpression) {
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSearchedExpression caseSearchedExpression) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|| isLiteral( firstResult ) ) {
|| isStringLiteral( firstResult ) ) {
for ( int i = 1; i < whenFragments.size(); i++ ) {
final Expression result = whenFragments.get( i ).getResult();
if ( isParameter( result ) ) {
@ -210,7 +206,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
return false;
}
}
else if ( !isLiteral( result ) ) {
else if ( !isStringLiteral( result ) ) {
return false;
}
}
@ -219,11 +215,11 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
return false;
}
protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression caseSimpleExpression) {
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSimpleExpression caseSimpleExpression) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|| isLiteral( firstResult ) ) {
|| isStringLiteral( firstResult ) ) {
for ( int i = 1; i < whenFragments.size(); i++ ) {
final Expression result = whenFragments.get( i ).getResult();
if ( isParameter( result ) ) {
@ -231,7 +227,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
return false;
}
}
else if ( !isLiteral( result ) ) {
else if ( !isStringLiteral( result ) ) {
return false;
}
}
@ -240,6 +236,13 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
return false;
}
private boolean isStringLiteral( Expression expression ) {
if ( expression instanceof Literal ) {
return ( (Literal) expression ).getJdbcMapping().getJdbcType().isStringLike();
}
return false;
}
@Override
public boolean supportsFilterClause() {
return true;

View File

@ -48,6 +48,8 @@ import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Base implementation for composite identifier mappings
*
@ -129,9 +131,9 @@ public abstract class AbstractCompositeIdentifierMapping
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
@ -154,11 +156,11 @@ public abstract class AbstractCompositeIdentifierMapping
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
}

View File

@ -292,6 +292,14 @@ public interface EntityMappingType
*/
EntityIdentifierMapping getIdentifierMapping();
/**
* Mapping details for the entity's identifier. This is shared across all
* entity mappings within an inheritance hierarchy.
*/
default EntityIdentifierMapping getIdentifierMappingForJoin() {
return getIdentifierMapping();
}
/**
* Mapping details for the entity's discriminator. This is shared across all
* entity mappings within an inheritance hierarchy.

View File

@ -6,7 +6,9 @@
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -255,6 +257,24 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
this.entityTableGroup = entityTableGroup;
}
public List<TableReference> getUsedTableReferences() {
final ArrayList<TableReference> usedTableReferences = new ArrayList<>( tableDiscriminatorDetailsMap.size() );
tableDiscriminatorDetailsMap.forEach(
(tableName, tableDiscriminatorDetails) -> {
final TableReference tableReference = entityTableGroup.getTableReference(
entityTableGroup.getNavigablePath(),
tableName,
false
);
if ( tableReference != null ) {
usedTableReferences.add( tableReference );
}
}
);
return usedTableReferences;
}
@Override
public void renderToSql(
SqlAppender sqlAppender,

View File

@ -52,6 +52,8 @@ import org.hibernate.type.AnyType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Singular, any-valued attribute
*
@ -487,9 +489,9 @@ public class DiscriminatedAssociationAttributeMapping
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
@ -512,11 +514,11 @@ public class DiscriminatedAssociationAttributeMapping
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
}

View File

@ -45,6 +45,10 @@ import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.type.AnyType;
import org.hibernate.type.descriptor.java.JavaType;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNullElse;
/**
* @author Steve Ebersole
*/
@ -343,19 +347,13 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
final SqlAstJoinType joinType;
if ( requestedJoinType == null ) {
joinType = SqlAstJoinType.INNER;
}
else {
joinType = requestedJoinType;
}
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
@ -374,11 +372,11 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
}

View File

@ -53,6 +53,10 @@ import org.hibernate.sql.results.graph.embeddable.internal.AggregateEmbeddableRe
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNullElse;
/**
* @author Steve Ebersole
*/
@ -318,13 +322,13 @@ public class EmbeddedAttributeMapping
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
final SqlAstJoinType joinType = requestedJoinType == null ? SqlAstJoinType.INNER : requestedJoinType;
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
@ -343,11 +347,11 @@ public class EmbeddedAttributeMapping
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
}

View File

@ -13,7 +13,6 @@ import java.util.function.Consumer;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -21,7 +20,6 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
@ -41,7 +39,6 @@ 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.from.TableGroupProducer;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.graph.DomainResult;
@ -49,12 +46,15 @@ import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.hibernate.type.descriptor.java.JavaType;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNullElse;
/**
* @author Steve Ebersole
*/
@ -231,19 +231,13 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
final SqlAstJoinType joinType;
if ( requestedJoinType == null ) {
joinType = SqlAstJoinType.INNER;
}
else {
joinType = requestedJoinType;
}
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
@ -262,11 +256,11 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
assert lhs.getModelPart() instanceof PluralAttributeMapping;
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );

View File

@ -56,6 +56,8 @@ import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.type.EntityType;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNullElse;
import static org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper.createInverseModelPart;
import static org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper.getPropertyOrder;
@ -258,14 +260,13 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
@ -302,11 +303,11 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
public LazyTableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
@ -393,7 +394,8 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
fkTargetModelPart = resolveNamedTargetPart( mapKeyPropertyName, entityMappingType, collectionDescriptor );
}
else {
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMappingForJoin();
// fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
}
}
else if ( StringHelper.isNotEmpty( bootCollectionDescriptor.getMappedByProperty() ) ) {
@ -449,7 +451,8 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
}
else {
// non-inverse @ManyToMany
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMappingForJoin();
// fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
}
if ( getNature() == Nature.ELEMENT ) {

View File

@ -116,6 +116,8 @@ import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.metamodel.mapping.MappingModelCreationLogging.MAPPING_MODEL_CREATION_MESSAGE_LOGGER;
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
@ -775,7 +777,8 @@ public class MappingModelCreationHelper {
}
if ( isReferenceToPrimaryKey ) {
fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMapping();
fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMappingForJoin();
// fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMapping();
}
else {
fkTargetPart = declaringType.findContainingEntityMapping().findSubPart( lhsPropertyName );
@ -928,7 +931,8 @@ public class MappingModelCreationHelper {
final ModelPart fkTarget;
if ( bootValueMapping.isReferenceToPrimaryKey() ) {
fkTarget = referencedEntityDescriptor.getIdentifierMapping();
fkTarget = referencedEntityDescriptor.getIdentifierMappingForJoin();
// fkTarget = referencedEntityDescriptor.getIdentifierMapping();
}
else {
fkTarget = referencedEntityDescriptor.findByPath( bootValueMapping.getReferencedPropertyName() );
@ -1677,9 +1681,9 @@ public class MappingModelCreationHelper {
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
@ -1690,11 +1694,11 @@ public class MappingModelCreationHelper {
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return null;
}

View File

@ -31,6 +31,8 @@ import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNullElse;
/**
@ -154,9 +156,9 @@ public class OneToManyCollectionPart extends AbstractEntityCollectionPart implem
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
@ -193,11 +195,11 @@ public class OneToManyCollectionPart extends AbstractEntityCollectionPart implem
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return createTableGroupInternal(
true,

View File

@ -75,6 +75,8 @@ import org.hibernate.sql.results.graph.collection.internal.SelectEagerCollection
import org.jboss.logging.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
import static org.hibernate.boot.model.internal.SoftDeleteHelper.resolveSoftDeleteMapping;
@ -683,9 +685,9 @@ public class PluralAttributeMappingImpl
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
@ -802,7 +804,7 @@ public class PluralAttributeMappingImpl
}
}
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) {
if ( hasSoftDelete() ) {
return SqlAstJoinType.LEFT;
}
@ -824,11 +826,11 @@ public class PluralAttributeMappingImpl
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return createRootTableGroupJoin(
navigablePath,
@ -861,8 +863,10 @@ public class PluralAttributeMappingImpl
if ( collectionDescriptor.isOneToMany() ) {
tableGroup = createOneToManyTableGroup(
lhs.canUseInnerJoins() && joinType == SqlAstJoinType.INNER,
joinType,
navigablePath,
fetched,
addsPredicate,
explicitSourceAlias,
sqlAliasBase,
creationState
@ -896,8 +900,10 @@ public class PluralAttributeMappingImpl
private TableGroup createOneToManyTableGroup(
boolean canUseInnerJoins,
SqlAstJoinType joinType,
NavigablePath navigablePath,
boolean fetched,
boolean addsPredicate,
String sourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstCreationState creationState) {
@ -920,6 +926,12 @@ public class PluralAttributeMappingImpl
elementTableGroup,
creationState.getCreationContext().getSessionFactory()
);
// For inner joins we never need join nesting
final boolean nestedJoin = joinType != SqlAstJoinType.INNER
// For outer joins we need nesting if there might be an on-condition that refers to the element table
&& ( addsPredicate
|| isAffectedByEnabledFilters( creationState.getLoadQueryInfluencers(), creationState.applyOnlyLoadByKeyFilters() )
|| collectionDescriptor.hasWhereRestrictions() );
if ( indexDescriptor instanceof TableGroupJoinProducer ) {
final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) indexDescriptor ).createTableGroupJoin(
@ -932,7 +944,7 @@ public class PluralAttributeMappingImpl
false,
creationState
);
tableGroup.registerIndexTableGroup( tableGroupJoin );
tableGroup.registerIndexTableGroup( tableGroupJoin, nestedJoin );
}
return tableGroup;
@ -1022,8 +1034,10 @@ public class PluralAttributeMappingImpl
if ( getCollectionDescriptor().isOneToMany() ) {
return createOneToManyTableGroup(
canUseInnerJoins,
SqlAstJoinType.INNER,
navigablePath,
false,
false,
explicitSourceAlias,
explicitSqlAliasBase,
creationState

View File

@ -66,6 +66,7 @@ import org.hibernate.persister.entity.JoinedSubclassEntityPersister;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.spi.EntityIdentifierNavigablePath;
import org.hibernate.spi.NavigablePath;
import org.hibernate.spi.TreatedNavigablePath;
@ -106,13 +107,14 @@ import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
import org.hibernate.sql.results.internal.NullValueAssembler;
import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl;
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
import org.hibernate.type.ComponentType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.JavaType;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
/**
@ -855,6 +857,7 @@ public class ToOneAttributeMapping
// * the association does not force a join (`@NotFound`, nullable 1-1, ...)
// Otherwise we need to join to the associated entity table(s)
final boolean forceJoin = hasNotFoundAction()
|| entityMappingType.getSoftDeleteMapping() != null
|| ( cardinality == Cardinality.ONE_TO_ONE && isNullable() );
this.canUseParentTableGroup = ! forceJoin
&& sideNature == ForeignKeyDescriptor.Nature.KEY
@ -1978,9 +1981,9 @@ public class ToOneAttributeMapping
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
@ -2072,7 +2075,11 @@ public class ToOneAttributeMapping
final TableGroupJoin join = new TableGroupJoin(
navigablePath,
joinType,
// Avoid checking for nested joins in here again, since this is already done in createRootTableGroupJoin
// and simply rely on the canUseInnerJoins flag instead for override the join type to LEFT
requestedJoinType == null && !lazyTableGroup.canUseInnerJoins()
? SqlAstJoinType.LEFT
: joinType,
lazyTableGroup,
null
);
@ -2144,7 +2151,7 @@ public class ToOneAttributeMapping
}
@Override
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) {
if ( requestedJoinType != null ) {
return requestedJoinType;
}
@ -2160,11 +2167,11 @@ public class ToOneAttributeMapping
public LazyTableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
final SqlAliasBase sqlAliasBase = SqlAliasBase.from(
explicitSqlAliasBase,
@ -2174,18 +2181,16 @@ public class ToOneAttributeMapping
);
final SoftDeleteMapping softDeleteMapping = getAssociatedEntityMappingType().getSoftDeleteMapping();
final boolean canUseInnerJoin;
if ( ! lhs.canUseInnerJoins() ) {
canUseInnerJoin = false;
}
else if ( isNullable
|| hasNotFoundAction()
|| softDeleteMapping != null ) {
final SqlAstJoinType currentlyProcessingJoinType = creationState instanceof SqmToSqlAstConverter
? ( (SqmToSqlAstConverter) creationState ).getCurrentlyProcessingJoinType()
: null;
if ( currentlyProcessingJoinType != null && currentlyProcessingJoinType != SqlAstJoinType.INNER ) {
// Don't change the join type though, as that has implications for eager initialization of a LazyTableGroup
canUseInnerJoin = false;
}
else {
canUseInnerJoin = requestedJoinType == SqlAstJoinType.INNER;
canUseInnerJoin = determineSqlJoinType( lhs, requestedJoinType, fetched ) == SqlAstJoinType.INNER;
}
TableGroup realParentTableGroup = lhs;

View File

@ -5814,6 +5814,7 @@ public abstract class AbstractEntityPersister
}
private ModelPart getIdentifierModelPart(String name, EntityMappingType treatTargetType) {
final EntityIdentifierMapping identifierMapping = getIdentifierMappingForJoin();
if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) {
NonAggregatedIdentifierMapping mapping = (NonAggregatedIdentifierMapping) identifierMapping;
final ModelPart subPart = mapping.findSubPart( name, treatTargetType );

View File

@ -1331,6 +1331,15 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
}
}
@Override
public EntityIdentifierMapping getIdentifierMappingForJoin() {
// If the joined subclass has a physical discriminator and has subtypes
// we must use the root table identifier mapping for joining to allow table group elimination to work
return isPhysicalDiscriminator() && !getSubMappingTypes().isEmpty()
? getRootEntityDescriptor().getIdentifierMapping()
: super.getIdentifierMappingForJoin();
}
private boolean applyDiscriminatorPredicate(
TableReferenceJoin join,
NamedTableReference tableReference,
@ -1341,9 +1350,11 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
final String discriminatorPredicate = getPrunedDiscriminatorPredicate(
entityNameUses,
metamodel,
tableReference.getIdentificationVariable()
"t"
// tableReference.getIdentificationVariable()
);
join.applyPredicate( new SqlFragmentPredicate( discriminatorPredicate ) );
tableReference.setPrunedTableExpression( "(select * from " + getRootTableName() + " t where " + discriminatorPredicate + ")" );
// join.applyPredicate( new SqlFragmentPredicate( discriminatorPredicate ) );
return true;
}
return false;

View File

@ -63,6 +63,9 @@ import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.metamodel.Attribute;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNullElse;
/**
* @author Christian Beikov
@ -341,13 +344,13 @@ public class AnonymousTupleEmbeddableValuedModelPart implements EmbeddableValued
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
final SqlAstJoinType joinType = requestedJoinType == null ? SqlAstJoinType.INNER : requestedJoinType;
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
@ -366,18 +369,13 @@ public class AnonymousTupleEmbeddableValuedModelPart implements EmbeddableValued
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
return new StandardVirtualTableGroup(
navigablePath,
this,
lhs,
fetched
);
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
}
@Override

View File

@ -70,6 +70,8 @@ import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.type.descriptor.java.JavaType;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNullElse;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
@ -245,14 +247,13 @@ public class AnonymousTupleEntityValuedModelPart
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState) {
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
@ -440,11 +441,11 @@ public class AnonymousTupleEntityValuedModelPart
public LazyTableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) {
final SqlAliasBase sqlAliasBase = SqlAliasBase.from(
explicitSqlAliasBase,

View File

@ -41,8 +41,11 @@ import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.SimpleDomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.IllegalSelectQueryException;
import org.hibernate.query.Order;
@ -63,6 +66,7 @@ import org.hibernate.query.sqm.tree.SqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmSingularJoin;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
@ -101,6 +105,7 @@ import org.hibernate.type.internal.ConvertedBasicTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.Tuple;
import jakarta.persistence.metamodel.Type;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.stream.Collectors.toList;
@ -188,7 +193,7 @@ public class SqmUtil {
// we need to render the target side if in group/order by
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
&& ( clause == Clause.GROUP || clause == Clause.ORDER
|| !isFkOptimizationAllowed( sqmPath.getLhs() )
|| !isFkOptimizationAllowed( sqmPath.getLhs(), association )
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
|| queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
return association.getAssociatedEntityMappingType();
@ -217,17 +222,106 @@ public class SqmUtil {
* a join that cannot be dereferenced through the foreign key on the associated table,
* i.e. a join that's neither {@linkplain SqmJoinType#INNER} nor {@linkplain SqmJoinType#LEFT}
* or one that has an explicit on clause predicate.
*
* @deprecated Use {@link #isFkOptimizationAllowed(SqmPath, EntityAssociationMapping)} instead
*/
@Deprecated(forRemoval = true, since = "6.6.1")
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
if ( sqmPath instanceof SqmJoin<?, ?> sqmJoin ) {
return switch ( sqmJoin.getSqmJoinType() ) {
case INNER, LEFT -> sqmJoin.getJoinPredicate() == null;
default -> false;
};
if ( sqmPath instanceof SqmJoin<?, ?> ) {
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
switch ( sqmJoin.getSqmJoinType() ) {
case LEFT:
final EntityAssociationMapping associationMapping = resolveAssociationMapping( sqmJoin );
if ( associationMapping != null && isFiltered( associationMapping ) ) {
return false;
}
// FallThrough intended
case INNER:
return sqmJoin.getJoinPredicate() == null;
default:
return false;
}
}
return false;
}
/**
* Utility that returns {@code false} when the provided {@link SqmPath sqmPath} is
* a join that cannot be dereferenced through the foreign key on the associated table,
* i.e. a join that's neither {@linkplain SqmJoinType#INNER} nor {@linkplain SqmJoinType#LEFT}
* or one that has an explicit on clause predicate.
*/
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath, EntityAssociationMapping associationMapping) {
if ( sqmPath instanceof SqmJoin<?, ?> ) {
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
switch ( sqmJoin.getSqmJoinType() ) {
case LEFT:
if ( isFiltered( associationMapping ) ) {
return false;
}
// FallThrough intended
case INNER:
return sqmJoin.getJoinPredicate() == null;
default:
return false;
}
}
return false;
}
private static boolean isFiltered(EntityAssociationMapping associationMapping) {
final EntityMappingType entityMappingType = associationMapping.getAssociatedEntityMappingType();
return !associationMapping.isFkOptimizationAllowed()
// When the identifier mappings are different we have a joined subclass entity
// which will filter rows based on a discriminator predicate
|| entityMappingType.getIdentifierMappingForJoin() != entityMappingType.getIdentifierMapping();
}
private static @Nullable EntityAssociationMapping resolveAssociationMapping(SqmJoin<?, ?> sqmJoin) {
if ( sqmJoin instanceof SqmSingularJoin<?, ?> ) {
final SqmSingularJoin<?, ?> singularJoin = (SqmSingularJoin<?, ?>) sqmJoin;
if ( singularJoin.getAttribute().getSqmPathType() instanceof EntityDomainType<?> ) {
return resolveAssociationMapping( singularJoin );
}
}
return null;
}
private static @Nullable EntityAssociationMapping resolveAssociationMapping(SqmSingularJoin<?, ?> sqmJoin) {
SingularPersistentAttribute<?, ?> attribute = sqmJoin.getAttribute();
ManagedDomainType<?> declaringType = attribute.getDeclaringType();
if ( declaringType.getPersistenceType() != Type.PersistenceType.ENTITY ) {
final StringBuilder pathBuilder = new StringBuilder();
do {
if ( pathBuilder.length() > 0 ) {
pathBuilder.insert(0, '.');
}
pathBuilder.insert( 0, attribute.getName() );
final SqmFrom<?, ?> lhs = sqmJoin.getLhs();
if ( !(lhs instanceof SqmSingularJoin<?, ?> ) ) {
return null;
}
sqmJoin = (SqmSingularJoin<?, ?>) lhs;
attribute = sqmJoin.getAttribute();
declaringType = attribute.getDeclaringType();
} while (declaringType.getPersistenceType() != Type.PersistenceType.ENTITY );
pathBuilder.insert(0, '.');
pathBuilder.insert( 0, attribute.getName() );
final EntityPersister entityDescriptor = sqmJoin.nodeBuilder()
.getSessionFactory()
.getMappingMetamodel()
.getEntityDescriptor( ( (EntityDomainType<?>) declaringType ).getHibernateEntityName() );
return (EntityAssociationMapping) entityDescriptor.findByPath( pathBuilder.toString() );
}
else {
final EntityPersister entityDescriptor = sqmJoin.nodeBuilder()
.getSessionFactory()
.getMappingMetamodel()
.getEntityDescriptor( ( (EntityDomainType<?>) declaringType ).getHibernateEntityName() );
return (EntityAssociationMapping) entityDescriptor.findAttributeMapping( attribute.getName() );
}
}
public static List<NavigablePath> getWhereClauseNavigablePaths(SqmQuerySpec<?> querySpec) {
final SqmWhereClause where = querySpec.getWhereClause();
if ( where == null || where.getPredicate() == null ) {

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.sql;
import jakarta.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.PreparedStatement;
@ -28,7 +29,6 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.LockMode;
@ -3922,7 +3922,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
this
);
// Implicit joins in the ON clause need to be added as nested table group joins
final boolean nested = currentClauseStack.getCurrent() == Clause.FROM;
final boolean nested = currentlyProcessingJoin != null;
if ( nested ) {
parentTableGroup.addNestedTableGroupJoin( tableGroupJoin );
}
@ -3996,6 +3996,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
@Override
public @Nullable SqlAstJoinType getCurrentlyProcessingJoinType() {
return currentlyProcessingJoin == null
? null
: currentlyProcessingJoin.getSqmJoinType().getCorrespondingSqlJoinType();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqmPath handling
// - Note that SqmFrom references defined in the FROM-clause are already
@ -4125,8 +4132,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// When the inferred mapping is null, we try to resolve to the FK by default, which is fine because
// expansion to all target columns for select and group by clauses is handled in EntityValuedPathInterpretation
if ( entityValuedModelPart instanceof EntityAssociationMapping
&& ( (EntityAssociationMapping) entityValuedModelPart ).isFkOptimizationAllowed()
&& isFkOptimizationAllowed( path ) ) {
&& isFkOptimizationAllowed( path, (EntityAssociationMapping) entityValuedModelPart ) ) {
// If the table group uses an association mapping that is not a one-to-many,
// we make use of the FK model part - unless the path is a non-optimizable join,
// for which we should always use the target's identifier to preserve semantics
@ -8401,7 +8407,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) fetchable;
final TableGroup compatibleTableGroup = lhs.findCompatibleJoinedGroup(
joinProducer,
joinProducer.determineSqlJoinType( lhs, null, true )
joinProducer.getDefaultSqlAstJoinType( lhs )
);
final SqmQueryPart<?> queryPart = getCurrentSqmQueryPart();
if ( compatibleTableGroup == null

View File

@ -21,6 +21,7 @@ import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
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;
@ -31,6 +32,8 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.QueryTransformer;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import jakarta.annotation.Nullable;
/**
*
*/
@ -101,6 +104,11 @@ public class FakeSqmToSqlAstConverter extends BaseSemanticQueryWalker implements
public void registerQueryTransformer(QueryTransformer transformer) {
}
@Override
public @Nullable SqlAstJoinType getCurrentlyProcessingJoinType() {
return null;
}
@Override
public boolean isInTypeInference() {
return false;

View File

@ -18,6 +18,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -38,6 +39,12 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
void registerQueryTransformer(QueryTransformer transformer);
/**
* Returns the {@link SqlAstJoinType} of the currently processing join if there is one, or {@code null}.
* This is used to determine the join type for implicit joins happening in the {@code ON} clause.
*/
@Nullable SqlAstJoinType getCurrentlyProcessingJoinType();
/**
* Returns whether the state of the translation is currently in type inference mode.
* This is useful to avoid type inference based on other incomplete inference information.

View File

@ -0,0 +1,282 @@
/*
* 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.internal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.internal.CaseStatementDiscriminatorMappingImpl;
import org.hibernate.persister.internal.SqlFragmentPredicate;
import org.hibernate.sql.ast.spi.AbstractSqlAstWalker;
import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.FilterPredicate;
import org.hibernate.sql.ast.tree.predicate.Junction;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.model.ast.ColumnWriteFragment;
/**
* A simple walker that checks if a predicate contains qualifiers.
*
* @author Christian Beikov
*/
public class TableGroupHelper extends AbstractSqlAstWalker {
public static final int REAL_TABLE_GROUP_REQUIRED = Integer.MAX_VALUE;
public static final int NO_TABLE_GROUP_REQUIRED = -1;
private final String primaryQualifier;
private final Map<String, Integer> qualifiers;
private final String[] qualifierFragments;
private Integer usedTableReferenceJoinIndex;
private TableGroupHelper(String primaryQualifier, Map<String, Integer> qualifiers) {
this.primaryQualifier = primaryQualifier;
this.qualifiers = qualifiers;
final String[] qualifierFragments = new String[qualifiers.size()];
for ( Map.Entry<String, Integer> entry : qualifiers.entrySet() ) {
qualifierFragments[entry.getValue()] = entry.getKey() + ".";
}
this.qualifierFragments = qualifierFragments;
}
/**
* Returns the index of a table reference join which can be swapped with the primary table reference
* to avoid rendering a real nested table group.
* {@link #REAL_TABLE_GROUP_REQUIRED} is returned if swapping is not possible.
* {@code #NO_TABLE_GROUP_REQUIRED} is returned if no swapping is necessary.
*/
public static int findReferenceJoinForPredicateSwap(TableGroup tableGroup, Predicate predicate) {
if ( predicate != null && !tableGroup.getTableReferenceJoins().isEmpty() ) {
final TableReference primaryTableReference = tableGroup.getPrimaryTableReference();
final HashMap<String, Integer> qualifiers = CollectionHelper.mapOfSize( tableGroup.getTableReferenceJoins().size() );
final List<TableReferenceJoin> tableReferenceJoins = tableGroup.getTableReferenceJoins();
for ( int i = 0; i < tableReferenceJoins.size(); i++ ) {
final TableReferenceJoin tableReferenceJoin = tableReferenceJoins.get( i );
if ( !tableGroup.canUseInnerJoins() ) {
if ( !isSimplePredicate( tableGroup, i ) ){//|| isSimpleOrOuterJoin( tableReferenceJoin ) ) {
// Can't do avoid the real table group rendering in this case if it's not inner joined,
// because doing so might change the meaning of the SQL. Consider this example:
// `from tbl1 t1 left join (tbl2 t2 join tbl3 t3 on t2.id=t3.id and ...) on t1.fk=t2.id`
//
// To avoid the nested table group rendering, the join on `tbl3` has to switch to left join
// `from tbl1 t1 left join tbl2 t2 on t1.fk=t2.id left join tbl3 t3 on t2.id=t3.id and ...`
// The additional predicate in the `tbl3` join can make `t3` null even though `t2` is non-null
return REAL_TABLE_GROUP_REQUIRED;
}
}
qualifiers.put( tableReferenceJoin.getJoinedTableReference().getIdentificationVariable(), i );
}
final TableGroupHelper qualifierCollector = new TableGroupHelper(
primaryTableReference.getIdentificationVariable(),
qualifiers
);
try {
predicate.accept( qualifierCollector );
if ( qualifierCollector.usedTableReferenceJoinIndex == null ) {
return NO_TABLE_GROUP_REQUIRED;
}
if ( qualifierCollector.usedTableReferenceJoinIndex != NO_TABLE_GROUP_REQUIRED
&& !tableGroup.canUseInnerJoins() && !isSimpleTableReference( primaryTableReference ) ) {
// Can't reorder table reference join with primary table reference if the primary table reference
// might filter out elements, since that affects result count with outer joins
return REAL_TABLE_GROUP_REQUIRED;
}
return qualifierCollector.usedTableReferenceJoinIndex;
}
catch (MultipleUsesFoundException ex) {
return REAL_TABLE_GROUP_REQUIRED;
}
}
return NO_TABLE_GROUP_REQUIRED;
}
private static boolean isSimpleTableReference(TableReference tableReference) {
return tableReference instanceof NamedTableReference && !tableReference.getTableId().startsWith( "(select" );
}
/**
* Checks if the table reference join at the given index uses a simple equality join predicate.
* Predicates that contain anything but comparisons of the primary table reference with table reference join columns
* are non-simple.
*/
private static boolean isSimplePredicate(TableGroup tableGroup, int index) {
final TableReference primaryTableReference = tableGroup.getPrimaryTableReference();
final TableReferenceJoin tableReferenceJoin = tableGroup.getTableReferenceJoins().get( index );
final NamedTableReference joinedTableReference = tableReferenceJoin.getJoinedTableReference();
final Predicate predicate = tableReferenceJoin.getPredicate();
if ( predicate instanceof Junction ) {
final Junction junction = (Junction) predicate;
if ( junction.getNature() == Junction.Nature.CONJUNCTION ) {
for ( Predicate subPredicate : junction.getPredicates() ) {
if ( !isComparison( subPredicate, primaryTableReference, joinedTableReference ) ) {
return false;
}
}
return true;
}
}
else {
return isComparison( predicate, primaryTableReference, joinedTableReference );
}
return false;
}
private static boolean isComparison(Predicate predicate, TableReference table1, TableReference table2) {
if ( predicate instanceof ComparisonPredicate ) {
final ComparisonPredicate comparisonPredicate = (ComparisonPredicate) predicate;
final Expression lhs = comparisonPredicate.getLeftHandExpression();
final Expression rhs = comparisonPredicate.getRightHandExpression();
final SqlTuple lhsTuple;
if ( lhs instanceof SqlTupleContainer && ( lhsTuple = ( (SqlTupleContainer) lhs ).getSqlTuple() ) != null ) {
final SqlTuple rhsTuple = ( (SqlTupleContainer) rhs ).getSqlTuple();
final List<? extends Expression> lhsExpressions = lhsTuple.getExpressions();
final List<? extends Expression> rhsExpressions = rhsTuple.getExpressions();
for ( int i = 0; i < lhsExpressions.size(); i++ ) {
final ColumnReference lhsColumn = lhsExpressions.get( i ).getColumnReference();
final ColumnReference rhsColumn = rhsExpressions.get( i ).getColumnReference();
if ( !isComparison( table1, table2, lhsColumn, rhsColumn ) ) {
return false;
}
}
return true;
}
else {
return isComparison( table1, table2, lhs.getColumnReference(), rhs.getColumnReference() );
}
}
return false;
}
private static boolean isComparison(
TableReference table1,
TableReference table2,
ColumnReference column1,
ColumnReference column2) {
if ( column1 != null && column2 != null ) {
final String column1Qualifier = column1.getQualifier();
final String column2Qualifier = column2.getQualifier();
final String table1Qualifier = table1.getIdentificationVariable();
final String table2Qualifier = table2.getIdentificationVariable();
return column1Qualifier.equals( table1Qualifier ) && column2Qualifier.equals( table2Qualifier )
|| column1Qualifier.equals( table2Qualifier ) && column2Qualifier.equals( table1Qualifier );
}
return false;
}
private static class MultipleUsesFoundException extends RuntimeException {
public MultipleUsesFoundException() {
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
private void checkQualifier(String qualifier) {
if ( primaryQualifier.equals( qualifier ) ) {
if ( usedTableReferenceJoinIndex != null && usedTableReferenceJoinIndex != NO_TABLE_GROUP_REQUIRED ) {
throw new MultipleUsesFoundException();
}
usedTableReferenceJoinIndex = NO_TABLE_GROUP_REQUIRED;
}
else {
final Integer index = qualifiers.get( qualifier );
if ( index != null ) {
if ( usedTableReferenceJoinIndex != null && usedTableReferenceJoinIndex.intValue() != index ) {
throw new MultipleUsesFoundException();
}
usedTableReferenceJoinIndex = index;
}
}
}
private void checkSql(String sql) {
if ( sql.contains( primaryQualifier + "." ) ) {
if ( usedTableReferenceJoinIndex != null && usedTableReferenceJoinIndex != NO_TABLE_GROUP_REQUIRED ) {
throw new MultipleUsesFoundException();
}
usedTableReferenceJoinIndex = NO_TABLE_GROUP_REQUIRED;
}
else {
for ( int i = 0; i < qualifierFragments.length; i++ ) {
if ( sql.contains( qualifierFragments[i] ) ) {
if ( usedTableReferenceJoinIndex != null && usedTableReferenceJoinIndex != i ) {
throw new MultipleUsesFoundException();
}
usedTableReferenceJoinIndex = i;
}
}
}
}
@Override
public void visitSelfRenderingExpression(SelfRenderingExpression expression) {
if ( expression instanceof SelfRenderingSqlFragmentExpression ) {
checkSql( ( (SelfRenderingSqlFragmentExpression) expression ).getExpression() );
}
else if ( expression instanceof CaseStatementDiscriminatorMappingImpl.CaseStatementDiscriminatorExpression ) {
for ( TableReference usedTableReference : ( (CaseStatementDiscriminatorMappingImpl.CaseStatementDiscriminatorExpression) expression ).getUsedTableReferences() ) {
usedTableReference.accept( this );
}
}
else {
super.visitSelfRenderingExpression( expression );
}
}
@Override
public void visitNamedTableReference(NamedTableReference tableReference) {
checkQualifier( tableReference.getIdentificationVariable() );
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
checkQualifier( columnReference.getQualifier() );
}
@Override
public void visitAggregateColumnWriteExpression(AggregateColumnWriteExpression aggregateColumnWriteExpression) {
checkQualifier( aggregateColumnWriteExpression.getAggregateColumnReference().getQualifier() );
}
@Override
public void visitFilterPredicate(FilterPredicate filterPredicate) {
for ( FilterPredicate.FilterFragmentPredicate fragment : filterPredicate.getFragments() ) {
visitFilterFragmentPredicate( fragment );
}
}
@Override
public void visitFilterFragmentPredicate(FilterPredicate.FilterFragmentPredicate fragmentPredicate) {
checkSql( fragmentPredicate.getSqlFragment() );
}
@Override
public void visitSqlFragmentPredicate(SqlFragmentPredicate predicate) {
checkSql( predicate.getSqlFragment() );
}
@Override
public void visitColumnWriteFragment(ColumnWriteFragment columnWriteFragment) {
checkSql( columnWriteFragment.getFragment() );
}
}

View File

@ -88,6 +88,7 @@ import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlTreeCreationException;
import org.hibernate.sql.ast.internal.TableGroupHelper;
import org.hibernate.sql.ast.internal.ParameterMarkerStrategyStandard;
import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement;
import org.hibernate.sql.ast.tree.MutationStatement;
@ -6034,10 +6035,60 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
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() )
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
final boolean realTableGroup;
int swappedJoinIndex = -1;
boolean forceLeftJoin = false;
if ( tableGroup.isRealTableGroup() ) {
if ( hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) ) {
// If there are nested table groups, we need to render a real table group
realTableGroup = true;
}
else {
// Determine the reference join indexes of the table reference used in the predicate
final int referenceJoinIndexForPredicateSwap = TableGroupHelper.findReferenceJoinForPredicateSwap(
tableGroup,
predicate
);
if ( referenceJoinIndexForPredicateSwap == TableGroupHelper.REAL_TABLE_GROUP_REQUIRED ) {
// Means that real table group rendering is necessary
realTableGroup = true;
}
else if ( referenceJoinIndexForPredicateSwap == TableGroupHelper.NO_TABLE_GROUP_REQUIRED ) {
// Means that no swap is necessary to avoid the table group rendering
realTableGroup = false;
forceLeftJoin = !tableGroup.canUseInnerJoins();
}
else {
// Means that real table group rendering can be avoided if the primary table reference is swapped
// with the table reference join at the given index
realTableGroup = false;
forceLeftJoin = !tableGroup.canUseInnerJoins();
swappedJoinIndex = referenceJoinIndexForPredicateSwap;
// Render the table reference of the table reference join first
final TableReferenceJoin tableReferenceJoin = tableGroup.getTableReferenceJoins().get( swappedJoinIndex );
renderNamedTableReference( tableReferenceJoin.getJoinedTableReference(), LockMode.NONE );
// along with the predicate for the table group
if ( predicate != null ) {
appendSql( " on " );
predicate.accept( this );
}
// Then render the join syntax and fall through to rendering the primary table reference
appendSql( WHITESPACE );
if ( tableGroup.canUseInnerJoins() ) {
appendSql( tableReferenceJoin.getJoinType().getText() );
}
else {
append( "left " );
}
appendSql( "join " );
}
}
}
else {
realTableGroup = false;
}
if ( realTableGroup ) {
appendSql( OPEN_PARENTHESIS );
}
@ -6065,7 +6116,8 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
tableGroupJoins = null;
}
if ( predicate != null ) {
// Predicate was already rendered when swappedJoinIndex is not equal to -1
if ( predicate != null && swappedJoinIndex == -1 ) {
appendSql( " on " );
predicate.accept( this );
}
@ -6083,7 +6135,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
if ( !realTableGroup ) {
renderTableReferenceJoins( tableGroup );
renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector );
}
if ( tableGroupJoinCollector != null ) {
@ -6362,14 +6414,35 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
protected void renderTableReferenceJoins(TableGroup tableGroup) {
renderTableReferenceJoins( tableGroup, -1, false );
}
protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) {
final List<TableReferenceJoin> joins = tableGroup.getTableReferenceJoins();
if ( joins == null || joins.isEmpty() ) {
return;
}
for ( TableReferenceJoin tableJoin : joins ) {
if ( swappedJoinIndex != -1 ) {
// Finish the join against the primary table reference after the swap
final TableReferenceJoin swappedJoin = joins.get( swappedJoinIndex );
if ( swappedJoin.getPredicate() != null && !swappedJoin.getPredicate().isEmpty() ) {
appendSql( " on " );
swappedJoin.getPredicate().accept( this );
}
}
for ( int i = 0; i < joins.size(); i++ ) {
// Skip the swapped join since it was already rendered
if ( swappedJoinIndex != i ) {
final TableReferenceJoin tableJoin = joins.get( i );
appendSql( WHITESPACE );
if ( forceLeftJoin ) {
append( "left " );
}
else {
appendSql( tableJoin.getJoinType().getText() );
}
appendSql( "join " );
renderNamedTableReference( tableJoin.getJoinedTableReference(), LockMode.NONE );
@ -6380,6 +6453,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
}
}
protected void processTableGroupJoins(TableGroup source) {
source.visitTableGroupJoins( tableGroupJoin -> processTableGroupJoin( tableGroupJoin, null ) );

View File

@ -63,10 +63,19 @@ public class OneToManyTableGroup extends AbstractColumnReferenceQualifier implem
}
public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin) {
registerIndexTableGroup( indexTableGroupJoin, true );
}
public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin, boolean nested) {
assert this.indexTableGroup == null;
this.indexTableGroup = indexTableGroupJoin.getJoinedGroup();
if ( nested ) {
addNestedTableGroupJoin( indexTableGroupJoin );
}
else {
addTableGroupJoin( indexTableGroupJoin );
}
}
@Override
public String getGroupAlias() {

View File

@ -12,10 +12,11 @@ import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* @author Steve Ebersole
*/
@ -41,9 +42,9 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
boolean addsPredicate,
SqlAstCreationState creationState);
@ -70,14 +71,14 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType sqlAstJoinType,
@Nullable String explicitSourceAlias,
@Nullable SqlAliasBase explicitSqlAliasBase,
@Nullable SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
@Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState);
default SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
default SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) {
if ( requestedJoinType != null ) {
return requestedJoinType;
}

View File

@ -458,7 +458,10 @@ public final class DateTimeUtils {
/**
* Do the same conversion that databases do when they encounter a timestamp with a higher precision
* than what is supported by a column, which is to round the excess fractions.
*
* @deprecated Use {@link #adjustToDefaultPrecision(Temporal, Dialect)} instead
*/
@Deprecated(forRemoval = true, since = "6.6.1")
public static <T extends Temporal> T roundToDefaultPrecision(T temporal, Dialect d) {
final int defaultTimestampPrecision = d.getDefaultTimestampPrecision();
if ( defaultTimestampPrecision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) {
@ -472,6 +475,9 @@ public final class DateTimeUtils {
}
public static <T extends Temporal> T roundToSecondPrecision(T temporal, int precision) {
if ( precision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) {
return temporal;
}
if ( precision == 0 ) {
//noinspection unchecked
return temporal.get( ChronoField.NANO_OF_SECOND ) >= 500_000_000L

View File

@ -5,10 +5,8 @@ import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
//@Disabled("test produces broken SQL and issue needs to be fixed")
@TestForIssue(jiraKey = "HHH-15933")
@SessionFactory
@DomainModel(annotatedClasses = { Split.class, Reference.class })

View File

@ -57,6 +57,33 @@ public class AttributeJoinWithJoinedInheritanceTest {
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-17646" )
public void testLeftJoinSelectFk(SessionFactoryScope scope) {
scope.inTransaction( s -> {
final ChildEntityA childEntityA = new SubChildEntityA1( 11 );
s.persist( childEntityA );
final ChildEntityB childEntityB = new ChildEntityB( 21 );
s.persist( childEntityB );
s.persist( new RootOne( 1, childEntityA ) );
s.persist( new RootOne( 2, null ) );
} );
scope.inTransaction( s -> {
// simulate association with ChildEntityB
s.createNativeMutationQuery( "update root_one set child_id = 21 where id = 2" ).executeUpdate();
} );
scope.inTransaction( s -> {
final List<Integer> resultList = s.createQuery(
"select ce.id " +
"from RootOne r left join r.child ce ",
Integer.class
).getResultList();
assertEquals( 2, resultList.size() );
assertEquals( 11, resultList.get( 0 ) );
assertNull( resultList.get( 1 ) );
} );
}
@Test
public void testLeftJoin(SessionFactoryScope scope) {
scope.inTransaction( s -> {

View File

@ -0,0 +1,225 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.inheritance.join;
import java.util.List;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
@DomainModel( annotatedClasses = {
AttributeJoinWithNaturalJoinedInheritanceTest.BaseClass.class,
AttributeJoinWithNaturalJoinedInheritanceTest.ChildEntityA.class,
AttributeJoinWithNaturalJoinedInheritanceTest.SubChildEntityA1.class,
AttributeJoinWithNaturalJoinedInheritanceTest.SubChildEntityA2.class,
AttributeJoinWithNaturalJoinedInheritanceTest.ChildEntityB.class,
AttributeJoinWithNaturalJoinedInheritanceTest.RootOne.class
} )
@SessionFactory
@Jira( "https://hibernate.atlassian.net/browse/HHH-17646" )
public class AttributeJoinWithNaturalJoinedInheritanceTest {
@AfterEach
public void cleanup(SessionFactoryScope scope) {
scope.inTransaction( s -> {
s.createMutationQuery( "delete from RootOne" ).executeUpdate();
s.createMutationQuery( "delete from SubChildEntityA1" ).executeUpdate();
s.createMutationQuery( "delete from SubChildEntityA2" ).executeUpdate();
s.createMutationQuery( "delete from BaseClass" ).executeUpdate();
} );
}
@Test
public void testLeftJoinWithDiscriminatorFiltering(SessionFactoryScope scope) {
scope.inTransaction( s -> {
final ChildEntityA childEntityA1 = new SubChildEntityA1( 11 );
s.persist( childEntityA1 );
final ChildEntityA childEntityA2 = new SubChildEntityA2( 21 );
s.persist( childEntityA2 );
s.persist( new RootOne( 1, childEntityA1 ) );
s.persist( new RootOne( 2, childEntityA2 ) );
} );
scope.inTransaction( s -> {
final List<Tuple> resultList = s.createQuery(
"select r, ce, ce.uk " +
"from RootOne r left join treat(r.child as SubChildEntityA1) ce " +
"order by r.id",
Tuple.class
).getResultList();
assertEquals( 2, resultList.size() );
assertResult( resultList.get( 0 ), 1, 11, 11, "child_a_1", SubChildEntityA1.class, 11 );
assertResult( resultList.get( 1 ), 2, 21, null, null, null, null );
} );
}
private <T extends ChildEntityA> void assertResult(
Tuple result,
Integer rootId,
Integer rootChildId,
Integer childId,
String discValue,
Class<T> subClass,
Integer uk) {
if ( rootId != null ) {
final RootOne root = result.get( 0, RootOne.class );
assertEquals( rootId, root.getId() );
assertEquals( rootChildId, root.getChildId() );
}
else {
assertNull( result.get( 0 ) );
}
if ( subClass != null ) {
assertInstanceOf( subClass, result.get( 1 ) );
final ChildEntityA sub1 = result.get( 1, subClass );
assertEquals( childId, sub1.getId() );
assertEquals( discValue, sub1.getDiscCol() );
}
else {
assertNull( result.get( 1 ) );
}
if ( uk != null ) {
assertEquals( uk, result.get( 2 ) );
}
else {
assertNull( result.get( 2 ) );
}
}
/**
* NOTE: We define a {@link DiscriminatorColumn} to allow multiple subclasses
* to share the same table name. This will need additional care when pruning
* the table expression, since we'll have to add the discriminator condition
* before joining with the subclass tables
*/
@Entity( name = "BaseClass" )
@Inheritance( strategy = InheritanceType.JOINED )
@DiscriminatorColumn( name = "disc_col" )
public static class BaseClass {
@Id
private Integer id;
@Column( name = "disc_col", insertable = false, updatable = false )
private String discCol;
public BaseClass() {
}
public BaseClass(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public String getDiscCol() {
return discCol;
}
}
@Entity( name = "ChildEntityA" )
@Table( name = "child_entity" )
public static abstract class ChildEntityA extends BaseClass {
@Column(unique = true)
private Integer uk;
public ChildEntityA() {
}
public ChildEntityA(Integer id) {
super( id );
this.uk = id;
}
public Integer getUk() {
return uk;
}
}
@Entity( name = "SubChildEntityA1" )
@DiscriminatorValue( "child_a_1" )
public static class SubChildEntityA1 extends ChildEntityA {
public SubChildEntityA1() {
}
public SubChildEntityA1(Integer id) {
super( id );
}
}
@Entity( name = "SubChildEntityA2" )
@DiscriminatorValue( "child_a_2" )
public static class SubChildEntityA2 extends ChildEntityA {
public SubChildEntityA2() {
}
public SubChildEntityA2(Integer id) {
super( id );
}
}
@Entity( name = "ChildEntityB" )
@Table( name = "child_entity" )
public static class ChildEntityB extends BaseClass {
public ChildEntityB() {
}
public ChildEntityB(Integer id) {
super( id );
}
}
@Entity( name = "RootOne" )
@Table( name = "root_one" )
public static class RootOne {
@Id
private Integer id;
@Column( name = "child_id", insertable = false, updatable = false )
private Integer childId;
@ManyToOne
@JoinColumn( name = "child_id", referencedColumnName = "uk")
private ChildEntityA child;
public RootOne() {
}
public RootOne(Integer id, ChildEntityA child) {
this.id = id;
this.child = child;
}
public Integer getId() {
return id;
}
public Integer getChildId() {
return childId;
}
}
}

View File

@ -0,0 +1,215 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.inheritance.join;
import java.util.List;
import org.hibernate.annotations.SQLRestriction;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
@DomainModel( annotatedClasses = {
AttributeJoinWithRestrictedJoinedInheritanceTest.BaseClass.class,
AttributeJoinWithRestrictedJoinedInheritanceTest.ChildEntityA.class,
AttributeJoinWithRestrictedJoinedInheritanceTest.SubChildEntityA1.class,
AttributeJoinWithRestrictedJoinedInheritanceTest.SubChildEntityA2.class,
AttributeJoinWithRestrictedJoinedInheritanceTest.ChildEntityB.class,
AttributeJoinWithRestrictedJoinedInheritanceTest.RootOne.class
} )
@SessionFactory
@Jira( "https://hibernate.atlassian.net/browse/HHH-17646" )
public class AttributeJoinWithRestrictedJoinedInheritanceTest {
@AfterEach
public void cleanup(SessionFactoryScope scope) {
scope.inTransaction( s -> {
s.createMutationQuery( "delete from RootOne" ).executeUpdate();
s.createMutationQuery( "delete from SubChildEntityA1" ).executeUpdate();
s.createMutationQuery( "delete from SubChildEntityA2" ).executeUpdate();
s.createMutationQuery( "delete from BaseClass" ).executeUpdate();
} );
}
@Test
public void testLeftJoinWithDiscriminatorFiltering(SessionFactoryScope scope) {
scope.inTransaction( s -> {
final ChildEntityA childEntityA1 = new SubChildEntityA1( 11 );
s.persist( childEntityA1 );
final ChildEntityA childEntityA2 = new SubChildEntityA2( 21 );
s.persist( childEntityA2 );
s.persist( new RootOne( 1, childEntityA1 ) );
s.persist( new RootOne( 2, childEntityA2 ) );
} );
scope.inTransaction( s -> {
final List<Tuple> resultList = s.createQuery(
"select r, ce " +
"from RootOne r left join r.child ce " +
"order by r.id",
Tuple.class
).getResultList();
assertEquals( 2, resultList.size() );
assertResult( resultList.get( 0 ), 1, 11, 11, "child_a_1", SubChildEntityA1.class );
assertResult( resultList.get( 1 ), 2, 21, null, null, null );
} );
}
private <T extends ChildEntityA> void assertResult(
Tuple result,
Integer rootId,
Integer rootChildId,
Integer childId,
String discValue,
Class<T> subClass) {
if ( rootId != null ) {
final RootOne root = result.get( 0, RootOne.class );
assertEquals( rootId, root.getId() );
assertEquals( rootChildId, root.getChildId() );
}
else {
assertNull( result.get( 0 ) );
}
if ( subClass != null ) {
assertInstanceOf( subClass, result.get( 1 ) );
final ChildEntityA sub1 = result.get( 1, subClass );
assertEquals( childId, sub1.getId() );
assertEquals( discValue, sub1.getDiscCol() );
}
else {
assertNull( result.get( 1 ) );
}
}
/**
* NOTE: We define a {@link DiscriminatorColumn} to allow multiple subclasses
* to share the same table name. This will need additional care when pruning
* the table expression, since we'll have to add the discriminator condition
* before joining with the subclass tables
*/
@Entity( name = "BaseClass" )
@Inheritance( strategy = InheritanceType.JOINED )
@DiscriminatorColumn( name = "disc_col" )
@SQLRestriction( "ident < 20" )
public static class BaseClass {
@Id
@Column(name = "ident")
private Integer id;
@Column( name = "disc_col", insertable = false, updatable = false )
private String discCol;
public BaseClass() {
}
public BaseClass(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public String getDiscCol() {
return discCol;
}
}
@Entity( name = "ChildEntityA" )
@Table( name = "child_entity" )
public static abstract class ChildEntityA extends BaseClass {
public ChildEntityA() {
}
public ChildEntityA(Integer id) {
super( id );
}
}
@Entity( name = "SubChildEntityA1" )
@DiscriminatorValue( "child_a_1" )
public static class SubChildEntityA1 extends ChildEntityA {
public SubChildEntityA1() {
}
public SubChildEntityA1(Integer id) {
super( id );
}
}
@Entity( name = "SubChildEntityA2" )
@DiscriminatorValue( "child_a_2" )
public static class SubChildEntityA2 extends ChildEntityA {
public SubChildEntityA2() {
}
public SubChildEntityA2(Integer id) {
super( id );
}
}
@Entity( name = "ChildEntityB" )
@Table( name = "child_entity" )
public static class ChildEntityB extends BaseClass {
public ChildEntityB() {
}
public ChildEntityB(Integer id) {
super( id );
}
}
@Entity( name = "RootOne" )
@Table( name = "root_one" )
public static class RootOne {
@Id
private Integer id;
@Column( name = "child_id", insertable = false, updatable = false )
private Integer childId;
@ManyToOne
@JoinColumn( name = "child_id")
private ChildEntityA child;
public RootOne() {
}
public RootOne(Integer id, ChildEntityA child) {
this.id = id;
this.child = child;
}
public Integer getId() {
return id;
}
public Integer getChildId() {
return childId;
}
}
}

View File

@ -17,7 +17,6 @@ import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -411,7 +410,6 @@ public class OuterJoinTest {
}
@Test
@Disabled("Hibernate doesn't support implicit joins")
public void testJoinOrderWithRightNormalJoinWithInnerImplicitJoins(EntityManagerFactoryScope scope) {
scope.inTransaction( em -> {
List<Tuple> resultList = em.createQuery(

View File

@ -35,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
)
public class LocalTimeTest {
private static final LocalTime LOCAL_TIME = DateTimeUtils.roundToDefaultPrecision(
private static final LocalTime LOCAL_TIME = DateTimeUtils.adjustToDefaultPrecision(
LocalTime.now(),
DialectContext.getDialect()
);

View File

@ -8,7 +8,6 @@ package org.hibernate.orm.test.jpa.ql;
import java.util.Set;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
@ -21,19 +20,28 @@ import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@TestForIssue(jiraKey = "HHH-16691")
@DomainModel(
annotatedClasses = {
JoinTableOptimizationTest.Document.class, JoinTableOptimizationTest.Person.class
JoinTableOptimizationTest.Document.class,
JoinTableOptimizationTest.Person.class,
JoinTableOptimizationTest.File.class,
JoinTableOptimizationTest.Picture.class
})
@SessionFactory(useCollectingStatementInspector = true)
public class JoinTableOptimizationTest {
@Test
@JiraKey("HHH-16691")
public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
@ -48,6 +56,7 @@ public class JoinTableOptimizationTest {
}
@Test
@JiraKey("HHH-16691")
public void testInnerJoin(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
@ -68,6 +77,7 @@ public class JoinTableOptimizationTest {
}
@Test
@JiraKey("HHH-16691")
public void testLeftJoin(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
@ -88,6 +98,7 @@ public class JoinTableOptimizationTest {
}
@Test
@JiraKey("HHH-16691")
public void testInnerJoinCustomOnClause(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
@ -108,6 +119,7 @@ public class JoinTableOptimizationTest {
}
@Test
@JiraKey("HHH-16691")
public void testLeftJoinCustomOnClause(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
@ -147,6 +159,199 @@ public class JoinTableOptimizationTest {
);
}
@Test
@JiraKey("HHH-17646")
public void testLeftJoinFetch(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select d from Document d left join fetch d.people" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select d1_0.id,d1_0.name,p1_0.Document_id,p1_1.id,p1_1.name " +
"from Document d1_0 " +
"left join people p1_0 on d1_0.id=p1_0.Document_id " +
"left join Person p1_1 on p1_1.id=p1_0.people_id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinTablePolymorphicJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d join d.manyFiles f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select mf1_1.id,case when mf1_2.id is not null then 1 when mf1_1.id is not null then 0 end,mf1_1.document_id,mf1_1.name,mf1_2.extension " +
"from Document d1_0 " +
"join many_files mf1_0 on d1_0.id=mf1_0.Document_id " +
"join file_tbl mf1_1 on mf1_1.id=mf1_0.manyFiles_id " +
"left join Picture mf1_2 on mf1_1.id=mf1_2.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinTablePolymorphicLeftJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d left join d.manyFiles f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select mf1_1.id,case when mf1_2.id is not null then 1 when mf1_1.id is not null then 0 end,mf1_1.document_id,mf1_1.name,mf1_2.extension " +
"from Document d1_0 " +
"left join many_files mf1_0 on d1_0.id=mf1_0.Document_id " +
"left join file_tbl mf1_1 on mf1_1.id=mf1_0.manyFiles_id " +
"left join Picture mf1_2 on mf1_1.id=mf1_2.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinPolymorphicJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d join d.files f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select f1_0.id,case when f1_1.id is not null then 1 when f1_0.id is not null then 0 end,d1_0.id,d1_0.name,f1_0.name,f1_1.extension " +
"from Document d1_0 " +
"join file_tbl f1_0 on d1_0.id=f1_0.document_id " +
"left join Picture f1_1 on f1_0.id=f1_1.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinPolymorphicLeftJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d left join d.files f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select f1_0.id,case when f1_1.id is not null then 1 when f1_0.id is not null then 0 end,d1_0.id,d1_0.name,f1_0.name,f1_1.extension " +
"from Document d1_0 " +
"left join file_tbl f1_0 on d1_0.id=f1_0.document_id " +
"left join Picture f1_1 on f1_0.id=f1_1.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinTablePolymorphicSubtypeJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d join d.manyPictures f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select mp1_1.id,mp1_2.document_id,mp1_2.name,mp1_1.extension " +
"from Document d1_0 " +
"join many_pictures mp1_0 on d1_0.id=mp1_0.Document_id " +
"join Picture mp1_1 on mp1_1.id=mp1_0.manyPictures_id " +
"join file_tbl mp1_2 on mp1_1.id=mp1_2.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinTablePolymorphicSubtypeLeftJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d left join d.manyPictures f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select mp1_1.id,mp1_2.document_id,mp1_2.name,mp1_1.extension " +
"from Document d1_0 " +
"left join many_pictures mp1_0 on d1_0.id=mp1_0.Document_id " +
"left join Picture mp1_1 on mp1_1.id=mp1_0.manyPictures_id " +
"left join file_tbl mp1_2 on mp1_1.id=mp1_2.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinPolymorphicSubtypeJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d join d.pictures f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select p1_0.id,p1_1.document_id,p1_1.name,p1_0.extension " +
"from Document d1_0 " +
"join file_tbl p1_1 on d1_0.id=p1_1.document_id " +
"join Picture p1_0 on p1_0.id=p1_1.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Test
@JiraKey("HHH-17646")
public void testJoinPolymorphicSubtypeLeftJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select f from Document d left join d.pictures f" ).list();
statementInspector.assertExecutedCount( 1 );
Assertions.assertEquals(
"select p1_0.id,p1_1.document_id,p1_1.name,p1_0.extension " +
"from Document d1_0 " +
"left join file_tbl p1_1 on d1_0.id=p1_1.document_id " +
"left join Picture p1_0 on p1_0.id=p1_1.id",
statementInspector.getSqlQueries().get( 0 ),
"Nested join was not optimized away"
);
}
);
}
@Entity(name = "Document")
public static class Document {
@Id
@ -158,6 +363,16 @@ public class JoinTableOptimizationTest {
@ElementCollection
@CollectionTable(name = "document_pages")
Set<Page> pages;
@OneToMany(mappedBy = "document")
Set<File> files;
@ManyToMany
@JoinTable(name = "many_files")
Set<File> manyFiles;
@OneToMany(mappedBy = "document")
Set<Picture> pictures;
@ManyToMany
@JoinTable(name = "many_pictures")
Set<Picture> manyPictures;
}
@Entity(name = "Person")
public static class Person {
@ -165,6 +380,20 @@ public class JoinTableOptimizationTest {
Long id;
String name;
}
@Entity(name = "File")
@Table(name = "file_tbl")
@Inheritance(strategy = InheritanceType.JOINED)
public static class File {
@Id
Long id;
String name;
@ManyToOne(fetch = FetchType.LAZY)
Document document;
}
@Entity(name = "Picture")
public static class Picture extends File {
String extension;
}
@Embeddable
public static class Page {
String text;

View File

@ -44,7 +44,7 @@ import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
/**
* Tests for "direct" JDBC handling of {@linkplain java.time Java Time} types.
@ -84,7 +84,7 @@ public class GlobalJavaTimeJdbcTypeTests {
@Test
void testInstant(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final Instant start = roundToDefaultPrecision( Instant.EPOCH, dialect );
final Instant start = adjustToDefaultPrecision( Instant.EPOCH, dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
@ -114,7 +114,7 @@ public class GlobalJavaTimeJdbcTypeTests {
@Test
void testLocalDateTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDateTime start = roundToDefaultPrecision( LocalDateTime.now(), dialect );
final LocalDateTime start = adjustToDefaultPrecision( LocalDateTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
@ -144,7 +144,7 @@ public class GlobalJavaTimeJdbcTypeTests {
@Test
void testLocalDate(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDate startTime = roundToDefaultPrecision( LocalDate.now(), dialect );
final LocalDate startTime = adjustToDefaultPrecision( LocalDate.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
@ -177,7 +177,7 @@ public class GlobalJavaTimeJdbcTypeTests {
@SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase drivers truncate fractional seconds from the LocalTime")
void testLocalTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect );
final LocalTime startTime = adjustToDefaultPrecision( LocalTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();

View File

@ -45,7 +45,7 @@ import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
/**
* Tests for "direct" JDBC handling of {@linkplain java.time Java Time} types.
@ -85,7 +85,7 @@ public class JavaTimeJdbcTypeTests {
@Test
void testInstant(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final Instant start = roundToDefaultPrecision( Instant.EPOCH, dialect );
final Instant start = adjustToDefaultPrecision( Instant.EPOCH, dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
@ -115,7 +115,7 @@ public class JavaTimeJdbcTypeTests {
@Test
void testLocalDateTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDateTime start = roundToDefaultPrecision( LocalDateTime.now(), dialect );
final LocalDateTime start = adjustToDefaultPrecision( LocalDateTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
@ -145,7 +145,7 @@ public class JavaTimeJdbcTypeTests {
@Test
void testLocalDate(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalDate startTime = roundToDefaultPrecision( LocalDate.now(), dialect );
final LocalDate startTime = adjustToDefaultPrecision( LocalDate.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
@ -178,7 +178,7 @@ public class JavaTimeJdbcTypeTests {
@SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase drivers truncate fractional seconds from the LocalTime")
void testLocalTime(SessionFactoryScope scope) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect );
final LocalTime startTime = adjustToDefaultPrecision( LocalTime.now(), dialect );
scope.inTransaction( (session) -> {
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();

View File

@ -111,6 +111,23 @@ public class ToOneTests {
} );
}
@Test
void fkAccessTest(SessionFactoryScope scope) {
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
sqlInspector.clear();
scope.inTransaction( (session) -> {
final Integer issue2Reporter = session.createQuery( "select i.reporter.id from Issue i where i.id = 2", Integer.class ).getSingleResultOrNull();
assertThat( issue2Reporter ).isNull();
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( " join " );
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".reporter_fk" );
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsAnyOf( ".active='Y'", ".active=N'Y'" );
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsOnlyOnce( "active" );
} );
}
@Entity(name="Issue")
@Table(name="issues")
public static class Issue {