HHH-17646 Optimize away real table group rendering if possible

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

View File

@ -76,7 +76,7 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
} }
@Override @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... // 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 // See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
if ( isInRecursiveQueryPart() ) { if ( isInRecursiveQueryPart() ) {
@ -103,7 +103,7 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
} }
} }
else { else {
super.renderTableReferenceJoins( tableGroup ); super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
} }
} }

View File

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

View File

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

View File

@ -75,7 +75,7 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
} }
@Override @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... // 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 // See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
if ( isInRecursiveQueryPart() ) { if ( isInRecursiveQueryPart() ) {
@ -102,7 +102,7 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
} }
} }
else { else {
super.renderTableReferenceJoins( tableGroup ); super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
} }
} }

View File

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

View File

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

View File

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

View File

@ -292,6 +292,14 @@ public interface EntityMappingType
*/ */
EntityIdentifierMapping getIdentifierMapping(); 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 * Mapping details for the entity's discriminator. This is shared across all
* entity mappings within an inheritance hierarchy. * entity mappings within an inheritance hierarchy.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -75,6 +75,8 @@ import org.hibernate.sql.results.graph.collection.internal.SelectEagerCollection
import org.jboss.logging.Logger; 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.createNonSoftDeletedRestriction;
import static org.hibernate.boot.model.internal.SoftDeleteHelper.resolveSoftDeleteMapping; import static org.hibernate.boot.model.internal.SoftDeleteHelper.resolveSoftDeleteMapping;
@ -683,9 +685,9 @@ public class PluralAttributeMappingImpl
public TableGroupJoin createTableGroupJoin( public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath, NavigablePath navigablePath,
TableGroup lhs, TableGroup lhs,
String explicitSourceAlias, @Nullable String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase, @Nullable SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType, @Nullable SqlAstJoinType requestedJoinType,
boolean fetched, boolean fetched,
boolean addsPredicate, boolean addsPredicate,
SqlAstCreationState creationState) { SqlAstCreationState creationState) {
@ -802,7 +804,7 @@ public class PluralAttributeMappingImpl
} }
} }
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) { public SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) {
if ( hasSoftDelete() ) { if ( hasSoftDelete() ) {
return SqlAstJoinType.LEFT; return SqlAstJoinType.LEFT;
} }
@ -824,11 +826,11 @@ public class PluralAttributeMappingImpl
public TableGroup createRootTableGroupJoin( public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath, NavigablePath navigablePath,
TableGroup lhs, TableGroup lhs,
String explicitSourceAlias, @Nullable String explicitSourceAlias,
SqlAliasBase explicitSqlAliasBase, @Nullable SqlAliasBase explicitSqlAliasBase,
SqlAstJoinType requestedJoinType, @Nullable SqlAstJoinType requestedJoinType,
boolean fetched, boolean fetched,
Consumer<Predicate> predicateConsumer, @Nullable Consumer<Predicate> predicateConsumer,
SqlAstCreationState creationState) { SqlAstCreationState creationState) {
return createRootTableGroupJoin( return createRootTableGroupJoin(
navigablePath, navigablePath,
@ -861,8 +863,10 @@ public class PluralAttributeMappingImpl
if ( collectionDescriptor.isOneToMany() ) { if ( collectionDescriptor.isOneToMany() ) {
tableGroup = createOneToManyTableGroup( tableGroup = createOneToManyTableGroup(
lhs.canUseInnerJoins() && joinType == SqlAstJoinType.INNER, lhs.canUseInnerJoins() && joinType == SqlAstJoinType.INNER,
joinType,
navigablePath, navigablePath,
fetched, fetched,
addsPredicate,
explicitSourceAlias, explicitSourceAlias,
sqlAliasBase, sqlAliasBase,
creationState creationState
@ -896,8 +900,10 @@ public class PluralAttributeMappingImpl
private TableGroup createOneToManyTableGroup( private TableGroup createOneToManyTableGroup(
boolean canUseInnerJoins, boolean canUseInnerJoins,
SqlAstJoinType joinType,
NavigablePath navigablePath, NavigablePath navigablePath,
boolean fetched, boolean fetched,
boolean addsPredicate,
String sourceAlias, String sourceAlias,
SqlAliasBase explicitSqlAliasBase, SqlAliasBase explicitSqlAliasBase,
SqlAstCreationState creationState) { SqlAstCreationState creationState) {
@ -920,6 +926,12 @@ public class PluralAttributeMappingImpl
elementTableGroup, elementTableGroup,
creationState.getCreationContext().getSessionFactory() 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 ) { if ( indexDescriptor instanceof TableGroupJoinProducer ) {
final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) indexDescriptor ).createTableGroupJoin( final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) indexDescriptor ).createTableGroupJoin(
@ -932,7 +944,7 @@ public class PluralAttributeMappingImpl
false, false,
creationState creationState
); );
tableGroup.registerIndexTableGroup( tableGroupJoin ); tableGroup.registerIndexTableGroup( tableGroupJoin, nestedJoin );
} }
return tableGroup; return tableGroup;
@ -1022,8 +1034,10 @@ public class PluralAttributeMappingImpl
if ( getCollectionDescriptor().isOneToMany() ) { if ( getCollectionDescriptor().isOneToMany() ) {
return createOneToManyTableGroup( return createOneToManyTableGroup(
canUseInnerJoins, canUseInnerJoins,
SqlAstJoinType.INNER,
navigablePath, navigablePath,
false, false,
false,
explicitSourceAlias, explicitSourceAlias,
explicitSqlAliasBase, explicitSqlAliasBase,
creationState creationState

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmQueryPart; 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.spi.SqlAstCreationState;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
@ -38,6 +39,12 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
void registerQueryTransformer(QueryTransformer transformer); 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. * 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. * This is useful to avoid type inference based on other incomplete inference information.

View File

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

View File

@ -88,6 +88,7 @@ import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlTreeCreationException; 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.internal.ParameterMarkerStrategyStandard;
import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement;
import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.MutationStatement;
@ -6034,10 +6035,60 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
protected void renderTableGroup(TableGroup tableGroup, Predicate predicate, List<TableGroupJoin> tableGroupJoinCollector) { protected void renderTableGroup(TableGroup tableGroup, Predicate predicate, List<TableGroupJoin> tableGroupJoinCollector) {
// Without reference joins or nested join groups, even a real table group does not need parenthesis final boolean realTableGroup;
final boolean realTableGroup = tableGroup.isRealTableGroup() int swappedJoinIndex = -1;
&& ( CollectionHelper.isNotEmpty( tableGroup.getTableReferenceJoins() ) boolean forceLeftJoin = false;
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) ); 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 ) { if ( realTableGroup ) {
appendSql( OPEN_PARENTHESIS ); appendSql( OPEN_PARENTHESIS );
} }
@ -6065,7 +6116,8 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
tableGroupJoins = null; tableGroupJoins = null;
} }
if ( predicate != null ) { // Predicate was already rendered when swappedJoinIndex is not equal to -1
if ( predicate != null && swappedJoinIndex == -1 ) {
appendSql( " on " ); appendSql( " on " );
predicate.accept( this ); predicate.accept( this );
} }
@ -6083,7 +6135,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
if ( !realTableGroup ) { if ( !realTableGroup ) {
renderTableReferenceJoins( tableGroup ); renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector ); processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector );
} }
if ( tableGroupJoinCollector != null ) { if ( tableGroupJoinCollector != null ) {
@ -6362,14 +6414,35 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
protected void renderTableReferenceJoins(TableGroup tableGroup) { protected void renderTableReferenceJoins(TableGroup tableGroup) {
renderTableReferenceJoins( tableGroup, -1, false );
}
protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) {
final List<TableReferenceJoin> joins = tableGroup.getTableReferenceJoins(); final List<TableReferenceJoin> joins = tableGroup.getTableReferenceJoins();
if ( joins == null || joins.isEmpty() ) { if ( joins == null || joins.isEmpty() ) {
return; return;
} }
for ( TableReferenceJoin tableJoin : joins ) { if ( swappedJoinIndex != -1 ) {
// Finish the join against the primary table reference after the swap
final TableReferenceJoin swappedJoin = joins.get( swappedJoinIndex );
if ( swappedJoin.getPredicate() != null && !swappedJoin.getPredicate().isEmpty() ) {
appendSql( " on " );
swappedJoin.getPredicate().accept( this );
}
}
for ( int i = 0; i < joins.size(); i++ ) {
// Skip the swapped join since it was already rendered
if ( swappedJoinIndex != i ) {
final TableReferenceJoin tableJoin = joins.get( i );
appendSql( WHITESPACE ); appendSql( WHITESPACE );
if ( forceLeftJoin ) {
append( "left " );
}
else {
appendSql( tableJoin.getJoinType().getText() ); appendSql( tableJoin.getJoinType().getText() );
}
appendSql( "join " ); appendSql( "join " );
renderNamedTableReference( tableJoin.getJoinedTableReference(), LockMode.NONE ); renderNamedTableReference( tableJoin.getJoinedTableReference(), LockMode.NONE );
@ -6380,6 +6453,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
} }
} }
}
protected void processTableGroupJoins(TableGroup source) { protected void processTableGroupJoins(TableGroup source) {
source.visitTableGroupJoins( tableGroupJoin -> processTableGroupJoin( tableGroupJoin, null ) ); source.visitTableGroupJoins( tableGroupJoin -> processTableGroupJoin( tableGroupJoin, null ) );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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