From 3002fc31c06f8db293f238baef6a9631e403cf99 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 30 Jan 2024 17:47:32 +0100 Subject: [PATCH] HHH-17646 Optimize away real table group rendering if possible --- .../dialect/DB2LegacySqlAstTranslator.java | 4 +- .../dialect/H2LegacySqlAstTranslator.java | 10 +- .../dialect/HSQLLegacySqlAstTranslator.java | 31 +- .../dialect/DB2SqlAstTranslator.java | 4 +- .../hibernate/dialect/H2SqlAstTranslator.java | 10 +- .../dialect/HSQLSqlAstTranslator.java | 27 +- .../AbstractCompositeIdentifierMapping.java | 16 +- .../metamodel/mapping/EntityMappingType.java | 8 + ...CaseStatementDiscriminatorMappingImpl.java | 20 ++ ...criminatedAssociationAttributeMapping.java | 16 +- .../internal/DiscriminatedCollectionPart.java | 26 +- .../internal/EmbeddedAttributeMapping.java | 20 +- .../internal/EmbeddedCollectionPart.java | 30 +- .../internal/ManyToManyCollectionPart.java | 23 +- .../internal/MappingModelCreationHelper.java | 22 +- .../internal/OneToManyCollectionPart.java | 16 +- .../internal/PluralAttributeMappingImpl.java | 32 +- .../internal/ToOneAttributeMapping.java | 41 +-- .../entity/AbstractEntityPersister.java | 1 + .../entity/JoinedSubclassEntityPersister.java | 15 +- ...onymousTupleEmbeddableValuedModelPart.java | 26 +- .../AnonymousTupleEntityValuedModelPart.java | 17 +- .../hibernate/query/sqm/internal/SqmUtil.java | 95 +++++- .../sqm/sql/BaseSqmToSqlAstConverter.java | 15 +- .../sqm/sql/FakeSqmToSqlAstConverter.java | 8 + .../query/sqm/sql/SqmToSqlAstConverter.java | 7 + .../sql/ast/internal/TableGroupHelper.java | 282 ++++++++++++++++++ .../sql/ast/spi/AbstractSqlAstTranslator.java | 104 ++++++- .../ast/tree/from/OneToManyTableGroup.java | 11 +- .../ast/tree/from/TableGroupJoinProducer.java | 19 +- .../type/descriptor/DateTimeUtils.java | 6 + .../secondary/RefToSecondaryTableTest.java | 2 - ...ttributeJoinWithJoinedInheritanceTest.java | 27 ++ ...eJoinWithNaturalJoinedInheritanceTest.java | 225 ++++++++++++++ ...inWithRestrictedJoinedInheritanceTest.java | 215 +++++++++++++ .../orm/test/join/OuterJoinTest.java | 2 - .../test/jpa/compliance/LocalTimeTest.java | 2 +- .../jpa/ql/JoinTableOptimizationTest.java | 235 ++++++++++++++- .../javatime/GlobalJavaTimeJdbcTypeTests.java | 10 +- .../javatime/JavaTimeJdbcTypeTests.java | 10 +- .../orm/test/softdelete/ToOneTests.java | 17 ++ .../orm/test/timezones/AutoZonedTest.java | 8 +- .../orm/test/timezones/ColumnZonedTest.java | 8 +- .../orm/test/timezones/DefaultZonedTest.java | 8 +- .../test/timezones/JDBCTimeZoneZonedTest.java | 8 +- .../orm/test/timezones/PassThruZonedTest.java | 8 +- .../timezones/UTCNormalizedInstantTest.java | 16 +- .../timezones/UTCNormalizedZonedTest.java | 8 +- 48 files changed, 1513 insertions(+), 258 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/internal/TableGroupHelper.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacySqlAstTranslator.java index f8ef6b3fb2..7f516f7e69 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacySqlAstTranslator.java @@ -76,7 +76,7 @@ protected boolean supportsWithClauseInSubquery() { } @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 @@ protected void renderTableReferenceJoins(TableGroup tableGroup) { } } else { - super.renderTableReferenceJoins( tableGroup ); + super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin ); } } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacySqlAstTranslator.java index 7213cd1690..c95d269072 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacySqlAstTranslator.java @@ -340,13 +340,9 @@ protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lo 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 ) { - appendSql( "dual cross join " ); - } + if ( getSqlBuffer().charAt( getSqlBuffer().length() - 1 ) == '(' + && ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) ) { + appendSql( "dual cross join " ); } return super.renderPrimaryTableReference( tableGroup, lockMode ); } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacySqlAstTranslator.java index 8f7e958ee2..0c4fb51be2 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacySqlAstTranslator.java @@ -12,20 +12,14 @@ 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 @@ protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) { protected void visitAnsiCaseSearchedExpression( CaseSearchedExpression expression, Consumer resultRenderer) { - if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression ) - || areAllResultsPlainParametersOrLiterals( expression ) ) { + if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) { final List whenFragments = expression.getWhenFragments(); final Expression firstResult = whenFragments.get( 0 ).getResult(); super.visitAnsiCaseSearchedExpression( @@ -172,8 +165,7 @@ protected void visitAnsiCaseSearchedExpression( protected void visitAnsiCaseSimpleExpression( CaseSimpleExpression expression, Consumer resultRenderer) { - if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression ) - || areAllResultsPlainParametersOrLiterals( expression ) ) { + if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) { final List whenFragments = expression.getWhenFragments(); final Expression firstResult = whenFragments.get( 0 ).getResult(); super.visitAnsiCaseSimpleExpression( @@ -193,11 +185,11 @@ protected void visitAnsiCaseSimpleExpression( } } - protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression caseSearchedExpression) { + protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSearchedExpression caseSearchedExpression) { final List 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 @@ protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression return false; } } - else if ( !isLiteral( result ) ) { + else if ( !isStringLiteral( result ) ) { return false; } } @@ -214,11 +206,11 @@ else if ( !isLiteral( result ) ) { return false; } - protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression caseSimpleExpression) { + protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSimpleExpression caseSimpleExpression) { final List 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 @@ protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression ca return false; } } - else if ( !isLiteral( result ) ) { + else if ( !isStringLiteral( result ) ) { return false; } } @@ -235,6 +227,13 @@ else if ( !isLiteral( result ) ) { 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; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java index 4e057b1d74..7612266186 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java @@ -75,7 +75,7 @@ protected boolean supportsWithClauseInSubquery() { } @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 @@ protected void renderTableReferenceJoins(TableGroup tableGroup) { } } else { - super.renderTableReferenceJoins( tableGroup ); + super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java index 4554d948da..093530fd5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java @@ -314,13 +314,9 @@ protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lo 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 ) { - appendSql( "dual cross join " ); - } + if ( getSqlBuffer().charAt( getSqlBuffer().length() - 1 ) == '(' + && ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) ) { + appendSql( "dual cross join " ); } return super.renderPrimaryTableReference( tableGroup, lockMode ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java index f458720b9b..aac2ecdc23 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java @@ -12,14 +12,12 @@ 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 @@ protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) { protected void visitAnsiCaseSearchedExpression( CaseSearchedExpression expression, Consumer resultRenderer) { - if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression ) - || areAllResultsPlainParametersOrLiterals( expression ) ) { + if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) { final List whenFragments = expression.getWhenFragments(); final Expression firstResult = whenFragments.get( 0 ).getResult(); super.visitAnsiCaseSearchedExpression( @@ -177,8 +174,7 @@ protected void visitAnsiCaseSearchedExpression( protected void visitAnsiCaseSimpleExpression( CaseSimpleExpression expression, Consumer resultRenderer) { - if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression ) - || areAllResultsPlainParametersOrLiterals( expression ) ) { + if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) { final List whenFragments = expression.getWhenFragments(); final Expression firstResult = whenFragments.get( 0 ).getResult(); super.visitAnsiCaseSimpleExpression( @@ -198,11 +194,11 @@ protected void visitAnsiCaseSimpleExpression( } } - protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression caseSearchedExpression) { + protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSearchedExpression caseSearchedExpression) { final List 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 @@ protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression return false; } } - else if ( !isLiteral( result ) ) { + else if ( !isStringLiteral( result ) ) { return false; } } @@ -219,11 +215,11 @@ else if ( !isLiteral( result ) ) { return false; } - protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression caseSimpleExpression) { + protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSimpleExpression caseSimpleExpression) { final List 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 @@ protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression ca return false; } } - else if ( !isLiteral( result ) ) { + else if ( !isStringLiteral( result ) ) { return false; } } @@ -240,6 +236,13 @@ else if ( !isLiteral( result ) ) { 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; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java index 8f8d17ac31..3d99255d11 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java @@ -48,6 +48,8 @@ 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 Fetch generateFetch( 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 TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java index 7c7b0071b5..8de1d5886e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java @@ -321,6 +321,14 @@ default void pruneForSubclasses(TableGroup tableGroup, Set treatedEntity */ 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. diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java index 08b66db39c..892754ab17 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java @@ -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 CaseStatementDiscriminatorExpression(TableGroup entityTableGroup) { this.entityTableGroup = entityTableGroup; } + public List getUsedTableReferences() { + final ArrayList 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, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java index 3c40af99c0..c539daeaab 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java @@ -52,6 +52,8 @@ 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 Object assemble(Serializable cached, SharedSessionContract session) { 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 TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java index 280c782c9c..81d86e6f5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java @@ -45,6 +45,10 @@ 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 int forEachJdbcType(int offset, IndexedConsumer action) { 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 TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index 9308988f03..a47872909d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java @@ -53,6 +53,10 @@ 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 SqlTuple toSqlExpression( 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 TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java index 190b5fc138..aa49eb298c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java @@ -13,7 +13,6 @@ 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.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.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.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 SqlTuple toSqlExpression( 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 TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { assert lhs.getModelPart() instanceof PluralAttributeMapping; return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java index 4e262fc3fc..f0818f97df 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java @@ -56,6 +56,8 @@ 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 ModelPart getKeyTargetMatchPart() { 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 TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER ); final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins(); @@ -393,7 +394,8 @@ else if ( getNature() == Nature.INDEX ) { 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 @@ else if ( StringHelper.isNotEmpty( bootCollectionDescriptor.getMappedByProperty( } else { // non-inverse @ManyToMany - fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping(); + fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMappingForJoin(); +// fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping(); } if ( getNature() == Nature.ELEMENT ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index 0d9d96459a..aebca1558d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -120,6 +120,8 @@ 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; @@ -781,7 +783,8 @@ private static void interpretPluralAttributeMappingKeyDescriptor( } if ( isReferenceToPrimaryKey ) { - fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMapping(); + fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMappingForJoin(); +// fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMapping(); } else { fkTargetPart = declaringType.findContainingEntityMapping().findSubPart( lhsPropertyName ); @@ -934,7 +937,8 @@ else if ( modelPart == null ) { final ModelPart fkTarget; if ( bootValueMapping.isReferenceToPrimaryKey() ) { - fkTarget = referencedEntityDescriptor.getIdentifierMapping(); + fkTarget = referencedEntityDescriptor.getIdentifierMappingForJoin(); +// fkTarget = referencedEntityDescriptor.getIdentifierMapping(); } else { fkTarget = referencedEntityDescriptor.findByPath( bootValueMapping.getReferencedPropertyName() ); @@ -1683,9 +1687,9 @@ public boolean isSimpleJoinPredicate(Predicate predicate) { 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) { @@ -1696,11 +1700,11 @@ public TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java index d6a082027d..495641d6c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java @@ -31,6 +31,8 @@ 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 boolean isSimpleJoinPredicate(Predicate predicate) { 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 TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { return createTableGroupInternal( true, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java index 5e60be959f..32d4229769 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java @@ -76,6 +76,8 @@ 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; @@ -684,9 +686,9 @@ public boolean isSimpleJoinPredicate(Predicate predicate) { 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) { @@ -803,7 +805,7 @@ private void applySoftDeleteRestriction( } } - public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) { + public SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) { if ( hasSoftDelete() ) { return SqlAstJoinType.LEFT; } @@ -825,11 +827,11 @@ public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType reques 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { return createRootTableGroupJoin( navigablePath, @@ -862,8 +864,10 @@ private TableGroup createRootTableGroupJoin( if ( collectionDescriptor.isOneToMany() ) { tableGroup = createOneToManyTableGroup( lhs.canUseInnerJoins() && joinType == SqlAstJoinType.INNER, + joinType, navigablePath, fetched, + addsPredicate, explicitSourceAlias, sqlAliasBase, creationState @@ -897,8 +901,10 @@ public void setForeignKeyDescriptor(ForeignKeyDescriptor fkDescriptor) { private TableGroup createOneToManyTableGroup( boolean canUseInnerJoins, + SqlAstJoinType joinType, NavigablePath navigablePath, boolean fetched, + boolean addsPredicate, String sourceAlias, SqlAliasBase explicitSqlAliasBase, SqlAstCreationState creationState) { @@ -921,6 +927,12 @@ private TableGroup createOneToManyTableGroup( 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( @@ -933,7 +945,7 @@ private TableGroup createOneToManyTableGroup( false, creationState ); - tableGroup.registerIndexTableGroup( tableGroupJoin ); + tableGroup.registerIndexTableGroup( tableGroupJoin, nestedJoin ); } return tableGroup; @@ -1023,8 +1035,10 @@ public TableGroup createRootTableGroup( if ( getCollectionDescriptor().isOneToMany() ) { return createOneToManyTableGroup( canUseInnerJoins, + SqlAstJoinType.INNER, navigablePath, false, + false, explicitSourceAlias, explicitSqlAliasBase, creationState diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index 66eda808ae..e0f3c45b3e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -67,6 +67,7 @@ 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; @@ -107,13 +108,14 @@ 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; /** @@ -858,6 +860,7 @@ public void setForeignKeyDescriptor(ForeignKeyDescriptor foreignKeyDescriptor) { // * 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 @@ -1981,9 +1984,9 @@ public Fetchable getFetchable(int position) { 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) { @@ -2075,7 +2078,11 @@ public TableGroupJoin createTableGroupJoin( 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 ); @@ -2147,7 +2154,7 @@ public TableGroupJoin createTableGroupJoin( } @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; } @@ -2163,11 +2170,11 @@ public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType reques 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { final SqlAliasBase sqlAliasBase = SqlAliasBase.from( explicitSqlAliasBase, @@ -2177,18 +2184,16 @@ public LazyTableGroup createRootTableGroupJoin( ); 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; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index f06efdcaf3..854b305903 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -6176,6 +6176,7 @@ public ModelPart findSubTypesSubPart(String name, EntityMappingType treatTargetT } 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 ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index d7e88fe0c5..a5c17ca8a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -1421,6 +1421,15 @@ public void pruneForSubclasses(TableGroup tableGroup, Map } } + @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, @@ -1431,9 +1440,11 @@ private boolean applyDiscriminatorPredicate( 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; diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java index 932baaa717..454ee57492 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java @@ -63,6 +63,9 @@ 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 @@ -346,13 +349,13 @@ public boolean isSimpleJoinPredicate(Predicate predicate) { 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, @@ -371,18 +374,13 @@ public TableGroupJoin createTableGroupJoin( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { - return new StandardVirtualTableGroup( - navigablePath, - this, - lhs, - fetched - ); + return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java index dc73c84a40..c973837b40 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java @@ -70,6 +70,8 @@ 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 JavaType getMappedJavaType() { 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, @@ -441,11 +442,11 @@ public TableGroup createTableGroupInternal( 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 predicateConsumer, + @Nullable Consumer predicateConsumer, SqlAstCreationState creationState) { final SqlAliasBase sqlAliasBase = SqlAliasBase.from( explicitSqlAliasBase, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java index d6a63c5cb3..370e3839e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java @@ -41,8 +41,11 @@ 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.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; @@ -102,6 +106,7 @@ 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; @@ -189,7 +194,7 @@ public static ModelPartContainer getTargetMappingIfNeeded( // 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(); @@ -218,13 +223,21 @@ private static CollectionPart getCollectionPart(PluralAttributeMapping attribute * 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 ) { final SqmJoin sqmJoin = (SqmJoin) sqmPath; switch ( sqmJoin.getSqmJoinType() ) { - case INNER: case LEFT: + final EntityAssociationMapping associationMapping = resolveAssociationMapping( sqmJoin ); + if ( associationMapping != null && isFiltered( associationMapping ) ) { + return false; + } + // FallThrough intended + case INNER: return !( sqmJoin instanceof SqmQualifiedJoin) || ( (SqmQualifiedJoin) sqmJoin ).getJoinPredicate() == null; default: @@ -234,6 +247,84 @@ public static boolean isFkOptimizationAllowed(SqmPath sqmPath) { 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 instanceof SqmQualifiedJoin) + || ( (SqmQualifiedJoin) 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 getWhereClauseNavigablePaths(SqmQuerySpec querySpec) { final SqmWhereClause where = querySpec.getWhereClause(); if ( where == null || where.getPredicate() == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index d52065febb..e9cb36dbb7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.sql; +import jakarta.annotation.Nullable; import jakarta.persistence.TemporalType; import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.Type; @@ -3925,7 +3926,7 @@ private TableGroup createTableGroup(TableGroup parentTableGroup, SqmPath join 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 ); } @@ -3999,6 +4000,13 @@ private void registerPluralTableGroupParts(NavigablePath navigablePath, TableGro } } + @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 @@ -4128,8 +4136,7 @@ private Expression visitTableGroup(TableGroup tableGroup, SqmPath path) { // 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 @@ -8404,7 +8411,7 @@ else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { 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 diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java index 20065e26dc..02ffca6028 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java @@ -21,6 +21,7 @@ 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.QueryTransformer; import org.hibernate.sql.ast.tree.predicate.Predicate; +import jakarta.annotation.Nullable; + /** * */ @@ -101,6 +104,11 @@ public SqmQueryPart getCurrentSqmQueryPart() { public void registerQueryTransformer(QueryTransformer transformer) { } + @Override + public @Nullable SqlAstJoinType getCurrentlyProcessingJoinType() { + return null; + } + @Override public boolean isInTypeInference() { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java index f5fc0caea3..e932d9a4a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java @@ -18,6 +18,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.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, 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. diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/TableGroupHelper.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/TableGroupHelper.java new file mode 100644 index 0000000000..06567a2c02 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/TableGroupHelper.java @@ -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 qualifiers; + private final String[] qualifierFragments; + private Integer usedTableReferenceJoinIndex; + + private TableGroupHelper(String primaryQualifier, Map qualifiers) { + this.primaryQualifier = primaryQualifier; + this.qualifiers = qualifiers; + final String[] qualifierFragments = new String[qualifiers.size()]; + for ( Map.Entry 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 qualifiers = CollectionHelper.mapOfSize( tableGroup.getTableReferenceJoins().size() ); + final List 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 lhsExpressions = lhsTuple.getExpressions(); + final List 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() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 9a9c79266a..358cf5bac2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -89,6 +89,7 @@ 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; @@ -6035,10 +6036,60 @@ protected void renderRootTableGroup(TableGroup tableGroup, List } protected void renderTableGroup(TableGroup tableGroup, Predicate predicate, List 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 ); } @@ -6066,7 +6117,8 @@ protected void renderTableGroup(TableGroup tableGroup, Predicate predicate, List 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 ); } @@ -6084,7 +6136,7 @@ protected void renderTableGroup(TableGroup tableGroup, Predicate predicate, List } if ( !realTableGroup ) { - renderTableReferenceJoins( tableGroup ); + renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin ); processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector ); } if ( tableGroupJoinCollector != null ) { @@ -6363,21 +6415,43 @@ protected void registerAffectedTable(String tableExpression) { } protected void renderTableReferenceJoins(TableGroup tableGroup) { + renderTableReferenceJoins( tableGroup, -1, false ); + } + + protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) { final List joins = tableGroup.getTableReferenceJoins(); if ( joins == null || joins.isEmpty() ) { return; } - for ( TableReferenceJoin tableJoin : joins ) { - appendSql( WHITESPACE ); - appendSql( tableJoin.getJoinType().getText() ); - appendSql( "join " ); - - renderNamedTableReference( tableJoin.getJoinedTableReference(), LockMode.NONE ); - - if ( tableJoin.getPredicate() != null && !tableJoin.getPredicate().isEmpty() ) { + 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 " ); - tableJoin.getPredicate().accept( this ); + 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 ); + + if ( tableJoin.getPredicate() != null && !tableJoin.getPredicate().isEmpty() ) { + appendSql( " on " ); + tableJoin.getPredicate().accept( this ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/OneToManyTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/OneToManyTableGroup.java index 45b250520c..5d5b06ef84 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/OneToManyTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/OneToManyTableGroup.java @@ -63,9 +63,18 @@ public TableGroup getIndexTableGroup() { } public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin) { + registerIndexTableGroup( indexTableGroupJoin, true ); + } + + public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin, boolean nested) { assert this.indexTableGroup == null; this.indexTableGroup = indexTableGroupJoin.getJoinedGroup(); - addNestedTableGroupJoin( indexTableGroupJoin ); + if ( nested ) { + addNestedTableGroupJoin( indexTableGroupJoin ); + } + else { + addTableGroupJoin( indexTableGroupJoin ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java index 80c5a392f0..d2c795232d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java @@ -12,10 +12,11 @@ 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 @@ TableGroupJoin createTableGroupJoin( TableGroup createRootTableGroupJoin( NavigablePath navigablePath, TableGroup lhs, - String explicitSourceAlias, - SqlAliasBase explicitSqlAliasBase, - SqlAstJoinType sqlAstJoinType, + @Nullable String explicitSourceAlias, + @Nullable SqlAliasBase explicitSqlAliasBase, + @Nullable SqlAstJoinType sqlAstJoinType, boolean fetched, - Consumer predicateConsumer, + @Nullable Consumer 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; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java index 42487e0c4a..df7934e614 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java @@ -477,7 +477,10 @@ public static T truncateToPrecision(T temporal, int precisi /** * 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 roundToDefaultPrecision(T temporal, Dialect d) { final int defaultTimestampPrecision = d.getDefaultTimestampPrecision(); if ( defaultTimestampPrecision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) { @@ -491,6 +494,9 @@ public static T roundToDefaultPrecision(T temporal, Dialect } public static 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 diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/secondary/RefToSecondaryTableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/secondary/RefToSecondaryTableTest.java index 2ae3ccc0bc..405b3606b9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/secondary/RefToSecondaryTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/secondary/RefToSecondaryTableTest.java @@ -5,10 +5,8 @@ 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 }) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java index c5896e3423..c5ff0c9d37 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithJoinedInheritanceTest.java @@ -57,6 +57,33 @@ public void cleanup(SessionFactoryScope scope) { } ); } + @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 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 -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java new file mode 100644 index 0000000000..08ef6698e0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithNaturalJoinedInheritanceTest.java @@ -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 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 void assertResult( + Tuple result, + Integer rootId, + Integer rootChildId, + Integer childId, + String discValue, + Class 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; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java new file mode 100644 index 0000000000..4e64027d59 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/join/AttributeJoinWithRestrictedJoinedInheritanceTest.java @@ -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 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 void assertResult( + Tuple result, + Integer rootId, + Integer rootChildId, + Integer childId, + String discValue, + Class 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; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/join/OuterJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/join/OuterJoinTest.java index b2eabc30a6..6b2aaf126d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/join/OuterJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/join/OuterJoinTest.java @@ -17,7 +17,6 @@ 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 void testJoinOrderWithRightJoinWithInnerImplicitJoins(EntityManagerFactor } @Test - @Disabled("Hibernate doesn't support implicit joins") public void testJoinOrderWithRightNormalJoinWithInnerImplicitJoins(EntityManagerFactoryScope scope) { scope.inTransaction( em -> { List resultList = em.createQuery( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/LocalTimeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/LocalTimeTest.java index 985f9b0e9e..18fe576ca7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/LocalTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/LocalTimeTest.java @@ -35,7 +35,7 @@ ) public class LocalTimeTest { - private static final LocalTime LOCAL_TIME = DateTimeUtils.roundToDefaultPrecision( + private static final LocalTime LOCAL_TIME = DateTimeUtils.adjustToDefaultPrecision( LocalTime.now(), DialectContext.getDialect() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/JoinTableOptimizationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/JoinTableOptimizationTest.java index 9f79f5066e..eb6b87b3ab 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/JoinTableOptimizationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/JoinTableOptimizationTest.java @@ -8,7 +8,6 @@ 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.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 void testOnlyCollectionTableJoined(SessionFactoryScope scope) { } @Test + @JiraKey("HHH-16691") public void testInnerJoin(SessionFactoryScope scope) { SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -68,6 +77,7 @@ public void testInnerJoin(SessionFactoryScope scope) { } @Test + @JiraKey("HHH-16691") public void testLeftJoin(SessionFactoryScope scope) { SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -88,6 +98,7 @@ public void testLeftJoin(SessionFactoryScope scope) { } @Test + @JiraKey("HHH-16691") public void testInnerJoinCustomOnClause(SessionFactoryScope scope) { SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -108,6 +119,7 @@ public void testInnerJoinCustomOnClause(SessionFactoryScope scope) { } @Test + @JiraKey("HHH-16691") public void testLeftJoinCustomOnClause(SessionFactoryScope scope) { SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -147,6 +159,199 @@ public void testElementCollectionJoinCustomOnClause(SessionFactoryScope scope) { ); } + @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 static class Document { @ElementCollection @CollectionTable(name = "document_pages") Set pages; + @OneToMany(mappedBy = "document") + Set files; + @ManyToMany + @JoinTable(name = "many_files") + Set manyFiles; + @OneToMany(mappedBy = "document") + Set pictures; + @ManyToMany + @JoinTable(name = "many_pictures") + Set manyPictures; } @Entity(name = "Person") public static class Person { @@ -165,6 +380,20 @@ public static class Person { 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; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java index 26060e9de7..5e1b6e95e5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java @@ -44,7 +44,7 @@ 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 @@ private void checkAttribute( @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 @@ void testInstant(SessionFactoryScope scope) { @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 @@ void testLocalDateTime(SessionFactoryScope scope) { @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 @@ void testLocalDate(SessionFactoryScope scope) { @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(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java index 91085418ac..481d8f8f25 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java @@ -45,7 +45,7 @@ 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 @@ private void checkAttribute( @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 @@ void testInstant(SessionFactoryScope scope) { @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 @@ void testLocalDateTime(SessionFactoryScope scope) { @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 @@ void testLocalDate(SessionFactoryScope scope) { @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(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/softdelete/ToOneTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/softdelete/ToOneTests.java index a2367e71ff..280c81a6cb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/softdelete/ToOneTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/softdelete/ToOneTests.java @@ -111,6 +111,23 @@ void basicSelectedTest(SessionFactoryScope scope) { } ); } + @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 { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/AutoZonedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/AutoZonedTest.java index 421fd7deb3..9a88ffee93 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/AutoZonedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/AutoZonedTest.java @@ -54,12 +54,12 @@ public class AutoZonedTest { Zoned z = s.find(Zoned.class, id); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowZoned.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowZoned.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowOffset.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowOffset.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) ); assertEquals( nowZoned.toOffsetDateTime().getOffset(), z.zonedDateTime.toOffsetDateTime().getOffset() ); assertEquals( nowOffset.getOffset(), z.offsetDateTime.getOffset() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/ColumnZonedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/ColumnZonedTest.java index dbbcb3a62e..245b6f2d99 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/ColumnZonedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/ColumnZonedTest.java @@ -54,12 +54,12 @@ public class ColumnZonedTest { Zoned z = s.find(Zoned.class, id); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowZoned.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowZoned.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowOffset.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowOffset.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) ); assertEquals( nowZoned.toOffsetDateTime().getOffset(), z.zonedDateTime.toOffsetDateTime().getOffset() ); assertEquals( nowOffset.getOffset(), z.offsetDateTime.getOffset() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/DefaultZonedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/DefaultZonedTest.java index 0bb8a1f3b2..37361b7353 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/DefaultZonedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/DefaultZonedTest.java @@ -51,12 +51,12 @@ public class DefaultZonedTest { Zoned z = s.find(Zoned.class, id); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowZoned.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowZoned.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowOffset.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowOffset.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) ); if ( dialect.getTimeZoneSupport() == TimeZoneSupport.NATIVE ) { assertEquals( nowZoned.toOffsetDateTime().getOffset(), z.zonedDateTime.toOffsetDateTime().getOffset() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/JDBCTimeZoneZonedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/JDBCTimeZoneZonedTest.java index f77bc9ced6..aeb27acc9e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/JDBCTimeZoneZonedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/JDBCTimeZoneZonedTest.java @@ -58,12 +58,12 @@ public class JDBCTimeZoneZonedTest { ZoneOffset systemOffset = systemZone.getRules().getOffset( Instant.now() ); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowZoned.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowZoned.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowOffset.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowOffset.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) ); assertEquals( systemZone, z.zonedDateTime.getZone() ); assertEquals( systemOffset, z.offsetDateTime.getOffset() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/PassThruZonedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/PassThruZonedTest.java index 8404eb25e4..440e066af4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/PassThruZonedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/PassThruZonedTest.java @@ -57,12 +57,12 @@ public class PassThruZonedTest { ZoneOffset systemOffset = systemZone.getRules().getOffset( Instant.now() ); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowZoned.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowZoned.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowOffset.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowOffset.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) ); assertEquals( systemZone, z.zonedDateTime.getZone() ); assertEquals( systemOffset, z.offsetDateTime.getOffset() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedInstantTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedInstantTest.java index dae2242aa8..fb6000ad13 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedInstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedInstantTest.java @@ -46,12 +46,12 @@ public class UTCNormalizedInstantTest { final Zoned z = s.find(Zoned.class, id); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( z.utcInstant, dialect ), - DateTimeUtils.roundToDefaultPrecision( instant, dialect ) + DateTimeUtils.adjustToDefaultPrecision( z.utcInstant, dialect ), + DateTimeUtils.adjustToDefaultPrecision( instant, dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( z.localInstant, dialect ), - DateTimeUtils.roundToDefaultPrecision( instant, dialect ) + DateTimeUtils.adjustToDefaultPrecision( z.localInstant, dialect ), + DateTimeUtils.adjustToDefaultPrecision( instant, dialect ) ); }); } @@ -80,12 +80,12 @@ public class UTCNormalizedInstantTest { final Zoned z = s.find(Zoned.class, id); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( z.utcInstant, dialect ), - DateTimeUtils.roundToDefaultPrecision( instant, dialect ) + DateTimeUtils.adjustToDefaultPrecision( z.utcInstant, dialect ), + DateTimeUtils.adjustToDefaultPrecision( instant, dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( z.localInstant, dialect ), - DateTimeUtils.roundToDefaultPrecision( instant, dialect ) + DateTimeUtils.adjustToDefaultPrecision( z.localInstant, dialect ), + DateTimeUtils.adjustToDefaultPrecision( instant, dialect ) ); }); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedZonedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedZonedTest.java index cbab481e0f..92c3865749 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedZonedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timezones/UTCNormalizedZonedTest.java @@ -54,12 +54,12 @@ public class UTCNormalizedZonedTest { Zoned z = s.find(Zoned.class, id); final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowZoned.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowZoned.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.zonedDateTime.toInstant(), dialect ) ); assertEquals( - DateTimeUtils.roundToDefaultPrecision( nowOffset.toInstant(), dialect ), - DateTimeUtils.roundToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) + DateTimeUtils.adjustToDefaultPrecision( nowOffset.toInstant(), dialect ), + DateTimeUtils.adjustToDefaultPrecision( z.offsetDateTime.toInstant(), dialect ) ); assertEquals( ZoneId.of("Z"), z.zonedDateTime.getZone() ); assertEquals( ZoneOffset.ofHours(0), z.offsetDateTime.getOffset() );