HHH-17646 Optimize away real table group rendering if possible
This commit is contained in:
parent
5fd74adcbf
commit
a956fda688
|
@ -76,7 +76,7 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void renderTableReferenceJoins(TableGroup tableGroup) {
|
||||
protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) {
|
||||
// When we are in a recursive CTE, we can't render joins on DB2...
|
||||
// See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
|
||||
if ( isInRecursiveQueryPart() ) {
|
||||
|
@ -103,7 +103,7 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
|
|||
}
|
||||
}
|
||||
else {
|
||||
super.renderTableReferenceJoins( tableGroup );
|
||||
super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -340,14 +340,10 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
|
|||
final TableReference tableRef = tableGroup.getPrimaryTableReference();
|
||||
// The H2 parser can't handle a sub-query as first element in a nested join
|
||||
// i.e. `join ( (select ...) alias join ... )`, so we have to introduce a dummy table reference
|
||||
if ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) {
|
||||
final boolean realTableGroup = tableGroup.isRealTableGroup()
|
||||
&& ( CollectionHelper.isNotEmpty( tableGroup.getTableReferenceJoins() )
|
||||
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
|
||||
if ( realTableGroup ) {
|
||||
if ( getSqlBuffer().charAt( getSqlBuffer().length() - 1 ) == '('
|
||||
&& ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) ) {
|
||||
appendSql( "dual cross join " );
|
||||
}
|
||||
}
|
||||
return super.renderPrimaryTableReference( tableGroup, lockMode );
|
||||
}
|
||||
|
||||
|
|
|
@ -12,20 +12,14 @@ import java.util.function.Consumer;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.sqm.BinaryArithmeticOperator;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
||||
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.MutationStatement;
|
||||
import org.hibernate.sql.ast.tree.Statement;
|
||||
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.Literal;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
|
@ -147,8 +141,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
|||
protected void visitAnsiCaseSearchedExpression(
|
||||
CaseSearchedExpression expression,
|
||||
Consumer<Expression> resultRenderer) {
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|
||||
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
|
||||
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
|
||||
final List<CaseSearchedExpression.WhenFragment> whenFragments = expression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
super.visitAnsiCaseSearchedExpression(
|
||||
|
@ -172,8 +165,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
|||
protected void visitAnsiCaseSimpleExpression(
|
||||
CaseSimpleExpression expression,
|
||||
Consumer<Expression> resultRenderer) {
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|
||||
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
|
||||
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
|
||||
final List<CaseSimpleExpression.WhenFragment> whenFragments = expression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
super.visitAnsiCaseSimpleExpression(
|
||||
|
@ -193,11 +185,11 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression caseSearchedExpression) {
|
||||
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSearchedExpression caseSearchedExpression) {
|
||||
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|
||||
|| isLiteral( firstResult ) ) {
|
||||
|| isStringLiteral( firstResult ) ) {
|
||||
for ( int i = 1; i < whenFragments.size(); i++ ) {
|
||||
final Expression result = whenFragments.get( i ).getResult();
|
||||
if ( isParameter( result ) ) {
|
||||
|
@ -205,7 +197,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
|||
return false;
|
||||
}
|
||||
}
|
||||
else if ( !isLiteral( result ) ) {
|
||||
else if ( !isStringLiteral( result ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -214,11 +206,11 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
|||
return false;
|
||||
}
|
||||
|
||||
protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression caseSimpleExpression) {
|
||||
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSimpleExpression caseSimpleExpression) {
|
||||
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|
||||
|| isLiteral( firstResult ) ) {
|
||||
|| isStringLiteral( firstResult ) ) {
|
||||
for ( int i = 1; i < whenFragments.size(); i++ ) {
|
||||
final Expression result = whenFragments.get( i ).getResult();
|
||||
if ( isParameter( result ) ) {
|
||||
|
@ -226,7 +218,7 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
|||
return false;
|
||||
}
|
||||
}
|
||||
else if ( !isLiteral( result ) ) {
|
||||
else if ( !isStringLiteral( result ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -235,6 +227,13 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean isStringLiteral( Expression expression ) {
|
||||
if ( expression instanceof Literal ) {
|
||||
return ( (Literal) expression ).getJdbcMapping().getJdbcType().isStringLike();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFilterClause() {
|
||||
return true;
|
||||
|
|
|
@ -75,7 +75,7 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void renderTableReferenceJoins(TableGroup tableGroup) {
|
||||
protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) {
|
||||
// When we are in a recursive CTE, we can't render joins on DB2...
|
||||
// See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
|
||||
if ( isInRecursiveQueryPart() ) {
|
||||
|
@ -102,7 +102,7 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
|
|||
}
|
||||
}
|
||||
else {
|
||||
super.renderTableReferenceJoins( tableGroup );
|
||||
super.renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -314,14 +314,10 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslato
|
|||
final TableReference tableRef = tableGroup.getPrimaryTableReference();
|
||||
// The H2 parser can't handle a sub-query as first element in a nested join
|
||||
// i.e. `join ( (select ...) alias join ... )`, so we have to introduce a dummy table reference
|
||||
if ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) {
|
||||
final boolean realTableGroup = tableGroup.isRealTableGroup()
|
||||
&& ( isNotEmpty( tableGroup.getTableReferenceJoins() )
|
||||
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
|
||||
if ( realTableGroup ) {
|
||||
if ( getSqlBuffer().charAt( getSqlBuffer().length() - 1 ) == '('
|
||||
&& ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) ) {
|
||||
appendSql( "dual cross join " );
|
||||
}
|
||||
}
|
||||
return super.renderPrimaryTableReference( tableGroup, lockMode );
|
||||
}
|
||||
|
||||
|
|
|
@ -12,14 +12,12 @@ import java.util.function.Consumer;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.sqm.BinaryArithmeticOperator;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
||||
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.Statement;
|
||||
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
|
@ -152,8 +150,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
protected void visitAnsiCaseSearchedExpression(
|
||||
CaseSearchedExpression expression,
|
||||
Consumer<Expression> resultRenderer) {
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|
||||
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
|
||||
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
|
||||
final List<CaseSearchedExpression.WhenFragment> whenFragments = expression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
super.visitAnsiCaseSearchedExpression(
|
||||
|
@ -177,8 +174,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
protected void visitAnsiCaseSimpleExpression(
|
||||
CaseSimpleExpression expression,
|
||||
Consumer<Expression> resultRenderer) {
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|
||||
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
|
||||
if ( areAllResultsPlainParametersOrStringLiterals( expression ) ) {
|
||||
final List<CaseSimpleExpression.WhenFragment> whenFragments = expression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
super.visitAnsiCaseSimpleExpression(
|
||||
|
@ -198,11 +194,11 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression caseSearchedExpression) {
|
||||
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSearchedExpression caseSearchedExpression) {
|
||||
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|
||||
|| isLiteral( firstResult ) ) {
|
||||
|| isStringLiteral( firstResult ) ) {
|
||||
for ( int i = 1; i < whenFragments.size(); i++ ) {
|
||||
final Expression result = whenFragments.get( i ).getResult();
|
||||
if ( isParameter( result ) ) {
|
||||
|
@ -210,7 +206,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
return false;
|
||||
}
|
||||
}
|
||||
else if ( !isLiteral( result ) ) {
|
||||
else if ( !isStringLiteral( result ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -219,11 +215,11 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
return false;
|
||||
}
|
||||
|
||||
protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression caseSimpleExpression) {
|
||||
protected boolean areAllResultsPlainParametersOrStringLiterals(CaseSimpleExpression caseSimpleExpression) {
|
||||
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|
||||
|| isLiteral( firstResult ) ) {
|
||||
|| isStringLiteral( firstResult ) ) {
|
||||
for ( int i = 1; i < whenFragments.size(); i++ ) {
|
||||
final Expression result = whenFragments.get( i ).getResult();
|
||||
if ( isParameter( result ) ) {
|
||||
|
@ -231,7 +227,7 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
return false;
|
||||
}
|
||||
}
|
||||
else if ( !isLiteral( result ) ) {
|
||||
else if ( !isStringLiteral( result ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -240,6 +236,13 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean isStringLiteral( Expression expression ) {
|
||||
if ( expression instanceof Literal ) {
|
||||
return ( (Literal) expression ).getJdbcMapping().getJdbcType().isStringLike();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFilterClause() {
|
||||
return true;
|
||||
|
|
|
@ -48,6 +48,8 @@ import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
|
|||
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
|
||||
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Base implementation for composite identifier mappings
|
||||
*
|
||||
|
@ -129,9 +131,9 @@ public abstract class AbstractCompositeIdentifierMapping
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
|
@ -154,11 +156,11 @@ public abstract class AbstractCompositeIdentifierMapping
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
|
||||
}
|
||||
|
|
|
@ -292,6 +292,14 @@ public interface EntityMappingType
|
|||
*/
|
||||
EntityIdentifierMapping getIdentifierMapping();
|
||||
|
||||
/**
|
||||
* Mapping details for the entity's identifier. This is shared across all
|
||||
* entity mappings within an inheritance hierarchy.
|
||||
*/
|
||||
default EntityIdentifierMapping getIdentifierMappingForJoin() {
|
||||
return getIdentifierMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping details for the entity's discriminator. This is shared across all
|
||||
* entity mappings within an inheritance hierarchy.
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
*/
|
||||
package org.hibernate.metamodel.mapping.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.engine.FetchTiming;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
@ -255,6 +257,24 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
|
|||
this.entityTableGroup = entityTableGroup;
|
||||
}
|
||||
|
||||
public List<TableReference> getUsedTableReferences() {
|
||||
final ArrayList<TableReference> usedTableReferences = new ArrayList<>( tableDiscriminatorDetailsMap.size() );
|
||||
tableDiscriminatorDetailsMap.forEach(
|
||||
(tableName, tableDiscriminatorDetails) -> {
|
||||
final TableReference tableReference = entityTableGroup.getTableReference(
|
||||
entityTableGroup.getNavigablePath(),
|
||||
tableName,
|
||||
false
|
||||
);
|
||||
|
||||
if ( tableReference != null ) {
|
||||
usedTableReferences.add( tableReference );
|
||||
}
|
||||
}
|
||||
);
|
||||
return usedTableReferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderToSql(
|
||||
SqlAppender sqlAppender,
|
||||
|
|
|
@ -52,6 +52,8 @@ import org.hibernate.type.AnyType;
|
|||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.MutabilityPlan;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Singular, any-valued attribute
|
||||
*
|
||||
|
@ -487,9 +489,9 @@ public class DiscriminatedAssociationAttributeMapping
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
|
@ -512,11 +514,11 @@ public class DiscriminatedAssociationAttributeMapping
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ import org.hibernate.sql.results.graph.Fetchable;
|
|||
import org.hibernate.type.AnyType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -343,19 +347,13 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAstJoinType joinType;
|
||||
if ( requestedJoinType == null ) {
|
||||
joinType = SqlAstJoinType.INNER;
|
||||
}
|
||||
else {
|
||||
joinType = requestedJoinType;
|
||||
}
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
|
@ -374,11 +372,11 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@ import org.hibernate.sql.results.graph.embeddable.internal.AggregateEmbeddableRe
|
|||
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
|
||||
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -318,13 +322,13 @@ public class EmbeddedAttributeMapping
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAstJoinType joinType = requestedJoinType == null ? SqlAstJoinType.INNER : requestedJoinType;
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
|
@ -343,11 +347,11 @@ public class EmbeddedAttributeMapping
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.util.function.Consumer;
|
|||
|
||||
import org.hibernate.engine.FetchStyle;
|
||||
import org.hibernate.engine.FetchTiming;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.CollectionPart;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
|
@ -21,7 +20,6 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
|
|||
import org.hibernate.metamodel.mapping.MappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
|
||||
|
@ -41,7 +39,6 @@ import org.hibernate.sql.ast.tree.from.PluralTableGroup;
|
|||
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
|
@ -49,12 +46,15 @@ import org.hibernate.sql.results.graph.DomainResultCreationState;
|
|||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.FetchOptions;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.Fetchable;
|
||||
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
|
||||
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
|
||||
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -231,19 +231,13 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAstJoinType joinType;
|
||||
if ( requestedJoinType == null ) {
|
||||
joinType = SqlAstJoinType.INNER;
|
||||
}
|
||||
else {
|
||||
joinType = requestedJoinType;
|
||||
}
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
|
@ -262,11 +256,11 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
assert lhs.getModelPart() instanceof PluralAttributeMapping;
|
||||
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
|
||||
|
|
|
@ -56,6 +56,8 @@ import org.hibernate.sql.ast.tree.from.TableReference;
|
|||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.EntityType;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
import static org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper.createInverseModelPart;
|
||||
import static org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper.getPropertyOrder;
|
||||
|
@ -258,14 +260,13 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
|
||||
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
|
@ -302,11 +303,11 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
|||
public LazyTableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
|
||||
|
@ -393,7 +394,8 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
|||
fkTargetModelPart = resolveNamedTargetPart( mapKeyPropertyName, entityMappingType, collectionDescriptor );
|
||||
}
|
||||
else {
|
||||
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
|
||||
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMappingForJoin();
|
||||
// fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
|
||||
}
|
||||
}
|
||||
else if ( StringHelper.isNotEmpty( bootCollectionDescriptor.getMappedByProperty() ) ) {
|
||||
|
@ -449,7 +451,8 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
|||
}
|
||||
else {
|
||||
// non-inverse @ManyToMany
|
||||
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
|
||||
fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMappingForJoin();
|
||||
// fkTargetModelPart = getAssociatedEntityMappingType().getIdentifierMapping();
|
||||
}
|
||||
|
||||
if ( getNature() == Nature.ELEMENT ) {
|
||||
|
|
|
@ -116,6 +116,8 @@ import org.hibernate.type.descriptor.java.MutabilityPlan;
|
|||
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
|
||||
import static org.hibernate.metamodel.mapping.MappingModelCreationLogging.MAPPING_MODEL_CREATION_MESSAGE_LOGGER;
|
||||
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
|
||||
|
@ -775,7 +777,8 @@ public class MappingModelCreationHelper {
|
|||
}
|
||||
|
||||
if ( isReferenceToPrimaryKey ) {
|
||||
fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMapping();
|
||||
fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMappingForJoin();
|
||||
// fkTargetPart = collectionDescriptor.getOwnerEntityPersister().getIdentifierMapping();
|
||||
}
|
||||
else {
|
||||
fkTargetPart = declaringType.findContainingEntityMapping().findSubPart( lhsPropertyName );
|
||||
|
@ -928,7 +931,8 @@ public class MappingModelCreationHelper {
|
|||
|
||||
final ModelPart fkTarget;
|
||||
if ( bootValueMapping.isReferenceToPrimaryKey() ) {
|
||||
fkTarget = referencedEntityDescriptor.getIdentifierMapping();
|
||||
fkTarget = referencedEntityDescriptor.getIdentifierMappingForJoin();
|
||||
// fkTarget = referencedEntityDescriptor.getIdentifierMapping();
|
||||
}
|
||||
else {
|
||||
fkTarget = referencedEntityDescriptor.findByPath( bootValueMapping.getReferencedPropertyName() );
|
||||
|
@ -1677,9 +1681,9 @@ public class MappingModelCreationHelper {
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
|
@ -1690,11 +1694,11 @@ public class MappingModelCreationHelper {
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
|||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
/**
|
||||
|
@ -154,9 +156,9 @@ public class OneToManyCollectionPart extends AbstractEntityCollectionPart implem
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
|
@ -193,11 +195,11 @@ public class OneToManyCollectionPart extends AbstractEntityCollectionPart implem
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return createTableGroupInternal(
|
||||
true,
|
||||
|
|
|
@ -75,6 +75,8 @@ import org.hibernate.sql.results.graph.collection.internal.SelectEagerCollection
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
|
||||
import static org.hibernate.boot.model.internal.SoftDeleteHelper.resolveSoftDeleteMapping;
|
||||
|
||||
|
@ -683,9 +685,9 @@ public class PluralAttributeMappingImpl
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
|
@ -802,7 +804,7 @@ public class PluralAttributeMappingImpl
|
|||
}
|
||||
}
|
||||
|
||||
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
if ( hasSoftDelete() ) {
|
||||
return SqlAstJoinType.LEFT;
|
||||
}
|
||||
|
@ -824,11 +826,11 @@ public class PluralAttributeMappingImpl
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
|
@ -861,8 +863,10 @@ public class PluralAttributeMappingImpl
|
|||
if ( collectionDescriptor.isOneToMany() ) {
|
||||
tableGroup = createOneToManyTableGroup(
|
||||
lhs.canUseInnerJoins() && joinType == SqlAstJoinType.INNER,
|
||||
joinType,
|
||||
navigablePath,
|
||||
fetched,
|
||||
addsPredicate,
|
||||
explicitSourceAlias,
|
||||
sqlAliasBase,
|
||||
creationState
|
||||
|
@ -896,8 +900,10 @@ public class PluralAttributeMappingImpl
|
|||
|
||||
private TableGroup createOneToManyTableGroup(
|
||||
boolean canUseInnerJoins,
|
||||
SqlAstJoinType joinType,
|
||||
NavigablePath navigablePath,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
String sourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstCreationState creationState) {
|
||||
|
@ -920,6 +926,12 @@ public class PluralAttributeMappingImpl
|
|||
elementTableGroup,
|
||||
creationState.getCreationContext().getSessionFactory()
|
||||
);
|
||||
// For inner joins we never need join nesting
|
||||
final boolean nestedJoin = joinType != SqlAstJoinType.INNER
|
||||
// For outer joins we need nesting if there might be an on-condition that refers to the element table
|
||||
&& ( addsPredicate
|
||||
|| isAffectedByEnabledFilters( creationState.getLoadQueryInfluencers(), creationState.applyOnlyLoadByKeyFilters() )
|
||||
|| collectionDescriptor.hasWhereRestrictions() );
|
||||
|
||||
if ( indexDescriptor instanceof TableGroupJoinProducer ) {
|
||||
final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) indexDescriptor ).createTableGroupJoin(
|
||||
|
@ -932,7 +944,7 @@ public class PluralAttributeMappingImpl
|
|||
false,
|
||||
creationState
|
||||
);
|
||||
tableGroup.registerIndexTableGroup( tableGroupJoin );
|
||||
tableGroup.registerIndexTableGroup( tableGroupJoin, nestedJoin );
|
||||
}
|
||||
|
||||
return tableGroup;
|
||||
|
@ -1022,8 +1034,10 @@ public class PluralAttributeMappingImpl
|
|||
if ( getCollectionDescriptor().isOneToMany() ) {
|
||||
return createOneToManyTableGroup(
|
||||
canUseInnerJoins,
|
||||
SqlAstJoinType.INNER,
|
||||
navigablePath,
|
||||
false,
|
||||
false,
|
||||
explicitSourceAlias,
|
||||
explicitSqlAliasBase,
|
||||
creationState
|
||||
|
|
|
@ -66,6 +66,7 @@ import org.hibernate.persister.entity.JoinedSubclassEntityPersister;
|
|||
import org.hibernate.property.access.spi.PropertyAccess;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
import org.hibernate.spi.EntityIdentifierNavigablePath;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.spi.TreatedNavigablePath;
|
||||
|
@ -106,13 +107,14 @@ import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
|
|||
import org.hibernate.sql.results.internal.NullValueAssembler;
|
||||
import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl;
|
||||
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
|
||||
import org.hibernate.type.ComponentType;
|
||||
import org.hibernate.type.CompositeType;
|
||||
import org.hibernate.type.EmbeddedComponentType;
|
||||
import org.hibernate.type.EntityType;
|
||||
import org.hibernate.type.Type;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
|
||||
|
||||
/**
|
||||
|
@ -855,6 +857,7 @@ public class ToOneAttributeMapping
|
|||
// * the association does not force a join (`@NotFound`, nullable 1-1, ...)
|
||||
// Otherwise we need to join to the associated entity table(s)
|
||||
final boolean forceJoin = hasNotFoundAction()
|
||||
|| entityMappingType.getSoftDeleteMapping() != null
|
||||
|| ( cardinality == Cardinality.ONE_TO_ONE && isNullable() );
|
||||
this.canUseParentTableGroup = ! forceJoin
|
||||
&& sideNature == ForeignKeyDescriptor.Nature.KEY
|
||||
|
@ -1978,9 +1981,9 @@ public class ToOneAttributeMapping
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
|
@ -2072,7 +2075,11 @@ public class ToOneAttributeMapping
|
|||
|
||||
final TableGroupJoin join = new TableGroupJoin(
|
||||
navigablePath,
|
||||
joinType,
|
||||
// Avoid checking for nested joins in here again, since this is already done in createRootTableGroupJoin
|
||||
// and simply rely on the canUseInnerJoins flag instead for override the join type to LEFT
|
||||
requestedJoinType == null && !lazyTableGroup.canUseInnerJoins()
|
||||
? SqlAstJoinType.LEFT
|
||||
: joinType,
|
||||
lazyTableGroup,
|
||||
null
|
||||
);
|
||||
|
@ -2144,7 +2151,7 @@ public class ToOneAttributeMapping
|
|||
}
|
||||
|
||||
@Override
|
||||
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
if ( requestedJoinType != null ) {
|
||||
return requestedJoinType;
|
||||
}
|
||||
|
@ -2160,11 +2167,11 @@ public class ToOneAttributeMapping
|
|||
public LazyTableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAliasBase sqlAliasBase = SqlAliasBase.from(
|
||||
explicitSqlAliasBase,
|
||||
|
@ -2174,18 +2181,16 @@ public class ToOneAttributeMapping
|
|||
);
|
||||
|
||||
final SoftDeleteMapping softDeleteMapping = getAssociatedEntityMappingType().getSoftDeleteMapping();
|
||||
|
||||
final boolean canUseInnerJoin;
|
||||
if ( ! lhs.canUseInnerJoins() ) {
|
||||
canUseInnerJoin = false;
|
||||
}
|
||||
else if ( isNullable
|
||||
|| hasNotFoundAction()
|
||||
|| softDeleteMapping != null ) {
|
||||
final SqlAstJoinType currentlyProcessingJoinType = creationState instanceof SqmToSqlAstConverter
|
||||
? ( (SqmToSqlAstConverter) creationState ).getCurrentlyProcessingJoinType()
|
||||
: null;
|
||||
if ( currentlyProcessingJoinType != null && currentlyProcessingJoinType != SqlAstJoinType.INNER ) {
|
||||
// Don't change the join type though, as that has implications for eager initialization of a LazyTableGroup
|
||||
canUseInnerJoin = false;
|
||||
}
|
||||
else {
|
||||
canUseInnerJoin = requestedJoinType == SqlAstJoinType.INNER;
|
||||
canUseInnerJoin = determineSqlJoinType( lhs, requestedJoinType, fetched ) == SqlAstJoinType.INNER;
|
||||
}
|
||||
|
||||
TableGroup realParentTableGroup = lhs;
|
||||
|
|
|
@ -5814,6 +5814,7 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
|
||||
private ModelPart getIdentifierModelPart(String name, EntityMappingType treatTargetType) {
|
||||
final EntityIdentifierMapping identifierMapping = getIdentifierMappingForJoin();
|
||||
if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) {
|
||||
NonAggregatedIdentifierMapping mapping = (NonAggregatedIdentifierMapping) identifierMapping;
|
||||
final ModelPart subPart = mapping.findSubPart( name, treatTargetType );
|
||||
|
|
|
@ -1331,6 +1331,15 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityIdentifierMapping getIdentifierMappingForJoin() {
|
||||
// If the joined subclass has a physical discriminator and has subtypes
|
||||
// we must use the root table identifier mapping for joining to allow table group elimination to work
|
||||
return isPhysicalDiscriminator() && !getSubMappingTypes().isEmpty()
|
||||
? getRootEntityDescriptor().getIdentifierMapping()
|
||||
: super.getIdentifierMappingForJoin();
|
||||
}
|
||||
|
||||
private boolean applyDiscriminatorPredicate(
|
||||
TableReferenceJoin join,
|
||||
NamedTableReference tableReference,
|
||||
|
@ -1341,9 +1350,11 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
|||
final String discriminatorPredicate = getPrunedDiscriminatorPredicate(
|
||||
entityNameUses,
|
||||
metamodel,
|
||||
tableReference.getIdentificationVariable()
|
||||
"t"
|
||||
// tableReference.getIdentificationVariable()
|
||||
);
|
||||
join.applyPredicate( new SqlFragmentPredicate( discriminatorPredicate ) );
|
||||
tableReference.setPrunedTableExpression( "(select * from " + getRootTableName() + " t where " + discriminatorPredicate + ")" );
|
||||
// join.applyPredicate( new SqlFragmentPredicate( discriminatorPredicate ) );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -63,6 +63,9 @@ import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
|
|||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import jakarta.persistence.metamodel.Attribute;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
|
@ -341,13 +344,13 @@ public class AnonymousTupleEmbeddableValuedModelPart implements EmbeddableValued
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAstJoinType joinType = requestedJoinType == null ? SqlAstJoinType.INNER : requestedJoinType;
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
|
@ -366,18 +369,13 @@ public class AnonymousTupleEmbeddableValuedModelPart implements EmbeddableValued
|
|||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
return new StandardVirtualTableGroup(
|
||||
navigablePath,
|
||||
this,
|
||||
lhs,
|
||||
fetched
|
||||
);
|
||||
return new StandardVirtualTableGroup( navigablePath, this, lhs, fetched );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -70,6 +70,8 @@ import org.hibernate.sql.results.graph.DomainResult;
|
|||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
|
||||
|
||||
|
@ -245,14 +247,13 @@ public class AnonymousTupleEntityValuedModelPart
|
|||
public TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType requestedJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType requestedJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
|
||||
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
|
@ -440,11 +441,11 @@ public class AnonymousTupleEntityValuedModelPart
|
|||
public LazyTableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlAliasBase sqlAliasBase = SqlAliasBase.from(
|
||||
explicitSqlAliasBase,
|
||||
|
|
|
@ -41,8 +41,11 @@ import org.hibernate.metamodel.model.domain.BasicDomainType;
|
|||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
|
||||
import org.hibernate.metamodel.model.domain.ManagedDomainType;
|
||||
import org.hibernate.metamodel.model.domain.SimpleDomainType;
|
||||
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.IllegalSelectQueryException;
|
||||
import org.hibernate.query.Order;
|
||||
|
@ -63,6 +66,7 @@ import org.hibernate.query.sqm.tree.SqmDmlStatement;
|
|||
import org.hibernate.query.sqm.tree.SqmJoinType;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmSingularJoin;
|
||||
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
@ -101,6 +105,7 @@ import org.hibernate.type.internal.ConvertedBasicTypeImpl;
|
|||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.metamodel.Type;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
@ -188,7 +193,7 @@ public class SqmUtil {
|
|||
// we need to render the target side if in group/order by
|
||||
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
|
||||
&& ( clause == Clause.GROUP || clause == Clause.ORDER
|
||||
|| !isFkOptimizationAllowed( sqmPath.getLhs() )
|
||||
|| !isFkOptimizationAllowed( sqmPath.getLhs(), association )
|
||||
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
|
||||
|| queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
|
||||
return association.getAssociatedEntityMappingType();
|
||||
|
@ -217,17 +222,106 @@ public class SqmUtil {
|
|||
* a join that cannot be dereferenced through the foreign key on the associated table,
|
||||
* i.e. a join that's neither {@linkplain SqmJoinType#INNER} nor {@linkplain SqmJoinType#LEFT}
|
||||
* or one that has an explicit on clause predicate.
|
||||
*
|
||||
* @deprecated Use {@link #isFkOptimizationAllowed(SqmPath, EntityAssociationMapping)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "6.6.1")
|
||||
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
|
||||
if ( sqmPath instanceof SqmJoin<?, ?> sqmJoin ) {
|
||||
return switch ( sqmJoin.getSqmJoinType() ) {
|
||||
case INNER, LEFT -> sqmJoin.getJoinPredicate() == null;
|
||||
default -> false;
|
||||
};
|
||||
if ( sqmPath instanceof SqmJoin<?, ?> ) {
|
||||
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
|
||||
switch ( sqmJoin.getSqmJoinType() ) {
|
||||
case LEFT:
|
||||
final EntityAssociationMapping associationMapping = resolveAssociationMapping( sqmJoin );
|
||||
if ( associationMapping != null && isFiltered( associationMapping ) ) {
|
||||
return false;
|
||||
}
|
||||
// FallThrough intended
|
||||
case INNER:
|
||||
return sqmJoin.getJoinPredicate() == null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility that returns {@code false} when the provided {@link SqmPath sqmPath} is
|
||||
* a join that cannot be dereferenced through the foreign key on the associated table,
|
||||
* i.e. a join that's neither {@linkplain SqmJoinType#INNER} nor {@linkplain SqmJoinType#LEFT}
|
||||
* or one that has an explicit on clause predicate.
|
||||
*/
|
||||
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath, EntityAssociationMapping associationMapping) {
|
||||
if ( sqmPath instanceof SqmJoin<?, ?> ) {
|
||||
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
|
||||
switch ( sqmJoin.getSqmJoinType() ) {
|
||||
case LEFT:
|
||||
if ( isFiltered( associationMapping ) ) {
|
||||
return false;
|
||||
}
|
||||
// FallThrough intended
|
||||
case INNER:
|
||||
return sqmJoin.getJoinPredicate() == null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isFiltered(EntityAssociationMapping associationMapping) {
|
||||
final EntityMappingType entityMappingType = associationMapping.getAssociatedEntityMappingType();
|
||||
return !associationMapping.isFkOptimizationAllowed()
|
||||
// When the identifier mappings are different we have a joined subclass entity
|
||||
// which will filter rows based on a discriminator predicate
|
||||
|| entityMappingType.getIdentifierMappingForJoin() != entityMappingType.getIdentifierMapping();
|
||||
}
|
||||
|
||||
private static @Nullable EntityAssociationMapping resolveAssociationMapping(SqmJoin<?, ?> sqmJoin) {
|
||||
if ( sqmJoin instanceof SqmSingularJoin<?, ?> ) {
|
||||
final SqmSingularJoin<?, ?> singularJoin = (SqmSingularJoin<?, ?>) sqmJoin;
|
||||
if ( singularJoin.getAttribute().getSqmPathType() instanceof EntityDomainType<?> ) {
|
||||
return resolveAssociationMapping( singularJoin );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable EntityAssociationMapping resolveAssociationMapping(SqmSingularJoin<?, ?> sqmJoin) {
|
||||
SingularPersistentAttribute<?, ?> attribute = sqmJoin.getAttribute();
|
||||
ManagedDomainType<?> declaringType = attribute.getDeclaringType();
|
||||
if ( declaringType.getPersistenceType() != Type.PersistenceType.ENTITY ) {
|
||||
final StringBuilder pathBuilder = new StringBuilder();
|
||||
do {
|
||||
if ( pathBuilder.length() > 0 ) {
|
||||
pathBuilder.insert(0, '.');
|
||||
}
|
||||
pathBuilder.insert( 0, attribute.getName() );
|
||||
final SqmFrom<?, ?> lhs = sqmJoin.getLhs();
|
||||
if ( !(lhs instanceof SqmSingularJoin<?, ?> ) ) {
|
||||
return null;
|
||||
}
|
||||
sqmJoin = (SqmSingularJoin<?, ?>) lhs;
|
||||
attribute = sqmJoin.getAttribute();
|
||||
declaringType = attribute.getDeclaringType();
|
||||
} while (declaringType.getPersistenceType() != Type.PersistenceType.ENTITY );
|
||||
pathBuilder.insert(0, '.');
|
||||
pathBuilder.insert( 0, attribute.getName() );
|
||||
final EntityPersister entityDescriptor = sqmJoin.nodeBuilder()
|
||||
.getSessionFactory()
|
||||
.getMappingMetamodel()
|
||||
.getEntityDescriptor( ( (EntityDomainType<?>) declaringType ).getHibernateEntityName() );
|
||||
return (EntityAssociationMapping) entityDescriptor.findByPath( pathBuilder.toString() );
|
||||
}
|
||||
else {
|
||||
final EntityPersister entityDescriptor = sqmJoin.nodeBuilder()
|
||||
.getSessionFactory()
|
||||
.getMappingMetamodel()
|
||||
.getEntityDescriptor( ( (EntityDomainType<?>) declaringType ).getHibernateEntityName() );
|
||||
return (EntityAssociationMapping) entityDescriptor.findAttributeMapping( attribute.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
public static List<NavigablePath> getWhereClauseNavigablePaths(SqmQuerySpec<?> querySpec) {
|
||||
final SqmWhereClause where = querySpec.getWhereClause();
|
||||
if ( where == null || where.getPredicate() == null ) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.sql;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.PreparedStatement;
|
||||
|
@ -28,7 +29,6 @@ import java.util.function.BiConsumer;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.LockMode;
|
||||
|
@ -3922,7 +3922,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
this
|
||||
);
|
||||
// Implicit joins in the ON clause need to be added as nested table group joins
|
||||
final boolean nested = currentClauseStack.getCurrent() == Clause.FROM;
|
||||
final boolean nested = currentlyProcessingJoin != null;
|
||||
if ( nested ) {
|
||||
parentTableGroup.addNestedTableGroupJoin( tableGroupJoin );
|
||||
}
|
||||
|
@ -3996,6 +3996,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SqlAstJoinType getCurrentlyProcessingJoinType() {
|
||||
return currentlyProcessingJoin == null
|
||||
? null
|
||||
: currentlyProcessingJoin.getSqmJoinType().getCorrespondingSqlJoinType();
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// SqmPath handling
|
||||
// - Note that SqmFrom references defined in the FROM-clause are already
|
||||
|
@ -4125,8 +4132,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
// When the inferred mapping is null, we try to resolve to the FK by default, which is fine because
|
||||
// expansion to all target columns for select and group by clauses is handled in EntityValuedPathInterpretation
|
||||
if ( entityValuedModelPart instanceof EntityAssociationMapping
|
||||
&& ( (EntityAssociationMapping) entityValuedModelPart ).isFkOptimizationAllowed()
|
||||
&& isFkOptimizationAllowed( path ) ) {
|
||||
&& isFkOptimizationAllowed( path, (EntityAssociationMapping) entityValuedModelPart ) ) {
|
||||
// If the table group uses an association mapping that is not a one-to-many,
|
||||
// we make use of the FK model part - unless the path is a non-optimizable join,
|
||||
// for which we should always use the target's identifier to preserve semantics
|
||||
|
@ -8401,7 +8407,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) fetchable;
|
||||
final TableGroup compatibleTableGroup = lhs.findCompatibleJoinedGroup(
|
||||
joinProducer,
|
||||
joinProducer.determineSqlJoinType( lhs, null, true )
|
||||
joinProducer.getDefaultSqlAstJoinType( lhs )
|
||||
);
|
||||
final SqmQueryPart<?> queryPart = getCurrentSqmQueryPart();
|
||||
if ( compatibleTableGroup == null
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
|||
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
import org.hibernate.sql.ast.spi.FromClauseAccess;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
|
@ -31,6 +32,8 @@ import org.hibernate.sql.ast.tree.expression.Expression;
|
|||
import org.hibernate.sql.ast.tree.expression.QueryTransformer;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -101,6 +104,11 @@ public class FakeSqmToSqlAstConverter extends BaseSemanticQueryWalker implements
|
|||
public void registerQueryTransformer(QueryTransformer transformer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SqlAstJoinType getCurrentlyProcessingJoinType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInTypeInference() {
|
||||
return false;
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
|
@ -38,6 +39,12 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
|
|||
|
||||
void registerQueryTransformer(QueryTransformer transformer);
|
||||
|
||||
/**
|
||||
* Returns the {@link SqlAstJoinType} of the currently processing join if there is one, or {@code null}.
|
||||
* This is used to determine the join type for implicit joins happening in the {@code ON} clause.
|
||||
*/
|
||||
@Nullable SqlAstJoinType getCurrentlyProcessingJoinType();
|
||||
|
||||
/**
|
||||
* Returns whether the state of the translation is currently in type inference mode.
|
||||
* This is useful to avoid type inference based on other incomplete inference information.
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@ import org.hibernate.sql.ast.SqlAstJoinType;
|
|||
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.SqlTreeCreationException;
|
||||
import org.hibernate.sql.ast.internal.TableGroupHelper;
|
||||
import org.hibernate.sql.ast.internal.ParameterMarkerStrategyStandard;
|
||||
import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement;
|
||||
import org.hibernate.sql.ast.tree.MutationStatement;
|
||||
|
@ -6034,10 +6035,60 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
|
||||
protected void renderTableGroup(TableGroup tableGroup, Predicate predicate, List<TableGroupJoin> tableGroupJoinCollector) {
|
||||
// Without reference joins or nested join groups, even a real table group does not need parenthesis
|
||||
final boolean realTableGroup = tableGroup.isRealTableGroup()
|
||||
&& ( CollectionHelper.isNotEmpty( tableGroup.getTableReferenceJoins() )
|
||||
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
|
||||
final boolean realTableGroup;
|
||||
int swappedJoinIndex = -1;
|
||||
boolean forceLeftJoin = false;
|
||||
if ( tableGroup.isRealTableGroup() ) {
|
||||
if ( hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) ) {
|
||||
// If there are nested table groups, we need to render a real table group
|
||||
realTableGroup = true;
|
||||
}
|
||||
else {
|
||||
// Determine the reference join indexes of the table reference used in the predicate
|
||||
final int referenceJoinIndexForPredicateSwap = TableGroupHelper.findReferenceJoinForPredicateSwap(
|
||||
tableGroup,
|
||||
predicate
|
||||
);
|
||||
if ( referenceJoinIndexForPredicateSwap == TableGroupHelper.REAL_TABLE_GROUP_REQUIRED ) {
|
||||
// Means that real table group rendering is necessary
|
||||
realTableGroup = true;
|
||||
}
|
||||
else if ( referenceJoinIndexForPredicateSwap == TableGroupHelper.NO_TABLE_GROUP_REQUIRED ) {
|
||||
// Means that no swap is necessary to avoid the table group rendering
|
||||
realTableGroup = false;
|
||||
forceLeftJoin = !tableGroup.canUseInnerJoins();
|
||||
}
|
||||
else {
|
||||
// Means that real table group rendering can be avoided if the primary table reference is swapped
|
||||
// with the table reference join at the given index
|
||||
realTableGroup = false;
|
||||
forceLeftJoin = !tableGroup.canUseInnerJoins();
|
||||
swappedJoinIndex = referenceJoinIndexForPredicateSwap;
|
||||
|
||||
// Render the table reference of the table reference join first
|
||||
final TableReferenceJoin tableReferenceJoin = tableGroup.getTableReferenceJoins().get( swappedJoinIndex );
|
||||
renderNamedTableReference( tableReferenceJoin.getJoinedTableReference(), LockMode.NONE );
|
||||
// along with the predicate for the table group
|
||||
if ( predicate != null ) {
|
||||
appendSql( " on " );
|
||||
predicate.accept( this );
|
||||
}
|
||||
|
||||
// Then render the join syntax and fall through to rendering the primary table reference
|
||||
appendSql( WHITESPACE );
|
||||
if ( tableGroup.canUseInnerJoins() ) {
|
||||
appendSql( tableReferenceJoin.getJoinType().getText() );
|
||||
}
|
||||
else {
|
||||
append( "left " );
|
||||
}
|
||||
appendSql( "join " );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
realTableGroup = false;
|
||||
}
|
||||
if ( realTableGroup ) {
|
||||
appendSql( OPEN_PARENTHESIS );
|
||||
}
|
||||
|
@ -6065,7 +6116,8 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
tableGroupJoins = null;
|
||||
}
|
||||
|
||||
if ( predicate != null ) {
|
||||
// Predicate was already rendered when swappedJoinIndex is not equal to -1
|
||||
if ( predicate != null && swappedJoinIndex == -1 ) {
|
||||
appendSql( " on " );
|
||||
predicate.accept( this );
|
||||
}
|
||||
|
@ -6083,7 +6135,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
|
||||
if ( !realTableGroup ) {
|
||||
renderTableReferenceJoins( tableGroup );
|
||||
renderTableReferenceJoins( tableGroup, swappedJoinIndex, forceLeftJoin );
|
||||
processNestedTableGroupJoins( tableGroup, tableGroupJoinCollector );
|
||||
}
|
||||
if ( tableGroupJoinCollector != null ) {
|
||||
|
@ -6362,14 +6414,35 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
|
||||
protected void renderTableReferenceJoins(TableGroup tableGroup) {
|
||||
renderTableReferenceJoins( tableGroup, -1, false );
|
||||
}
|
||||
|
||||
protected void renderTableReferenceJoins(TableGroup tableGroup, int swappedJoinIndex, boolean forceLeftJoin) {
|
||||
final List<TableReferenceJoin> joins = tableGroup.getTableReferenceJoins();
|
||||
if ( joins == null || joins.isEmpty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ( TableReferenceJoin tableJoin : joins ) {
|
||||
if ( swappedJoinIndex != -1 ) {
|
||||
// Finish the join against the primary table reference after the swap
|
||||
final TableReferenceJoin swappedJoin = joins.get( swappedJoinIndex );
|
||||
if ( swappedJoin.getPredicate() != null && !swappedJoin.getPredicate().isEmpty() ) {
|
||||
appendSql( " on " );
|
||||
swappedJoin.getPredicate().accept( this );
|
||||
}
|
||||
}
|
||||
|
||||
for ( int i = 0; i < joins.size(); i++ ) {
|
||||
// Skip the swapped join since it was already rendered
|
||||
if ( swappedJoinIndex != i ) {
|
||||
final TableReferenceJoin tableJoin = joins.get( i );
|
||||
appendSql( WHITESPACE );
|
||||
if ( forceLeftJoin ) {
|
||||
append( "left " );
|
||||
}
|
||||
else {
|
||||
appendSql( tableJoin.getJoinType().getText() );
|
||||
}
|
||||
appendSql( "join " );
|
||||
|
||||
renderNamedTableReference( tableJoin.getJoinedTableReference(), LockMode.NONE );
|
||||
|
@ -6380,6 +6453,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processTableGroupJoins(TableGroup source) {
|
||||
source.visitTableGroupJoins( tableGroupJoin -> processTableGroupJoin( tableGroupJoin, null ) );
|
||||
|
|
|
@ -63,10 +63,19 @@ public class OneToManyTableGroup extends AbstractColumnReferenceQualifier implem
|
|||
}
|
||||
|
||||
public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin) {
|
||||
registerIndexTableGroup( indexTableGroupJoin, true );
|
||||
}
|
||||
|
||||
public void registerIndexTableGroup(TableGroupJoin indexTableGroupJoin, boolean nested) {
|
||||
assert this.indexTableGroup == null;
|
||||
this.indexTableGroup = indexTableGroupJoin.getJoinedGroup();
|
||||
if ( nested ) {
|
||||
addNestedTableGroupJoin( indexTableGroupJoin );
|
||||
}
|
||||
else {
|
||||
addTableGroupJoin( indexTableGroupJoin );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupAlias() {
|
||||
|
|
|
@ -12,10 +12,11 @@ import org.hibernate.spi.NavigablePath;
|
|||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -41,9 +42,9 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
|
|||
TableGroupJoin createTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState);
|
||||
|
@ -70,14 +71,14 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
|
|||
TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup lhs,
|
||||
String explicitSourceAlias,
|
||||
SqlAliasBase explicitSqlAliasBase,
|
||||
SqlAstJoinType sqlAstJoinType,
|
||||
@Nullable String explicitSourceAlias,
|
||||
@Nullable SqlAliasBase explicitSqlAliasBase,
|
||||
@Nullable SqlAstJoinType sqlAstJoinType,
|
||||
boolean fetched,
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
@Nullable Consumer<Predicate> predicateConsumer,
|
||||
SqlAstCreationState creationState);
|
||||
|
||||
default SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
default SqlAstJoinType determineSqlJoinType(TableGroup lhs, @Nullable SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
if ( requestedJoinType != null ) {
|
||||
return requestedJoinType;
|
||||
}
|
||||
|
|
|
@ -458,7 +458,10 @@ public final class DateTimeUtils {
|
|||
/**
|
||||
* Do the same conversion that databases do when they encounter a timestamp with a higher precision
|
||||
* than what is supported by a column, which is to round the excess fractions.
|
||||
*
|
||||
* @deprecated Use {@link #adjustToDefaultPrecision(Temporal, Dialect)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "6.6.1")
|
||||
public static <T extends Temporal> T roundToDefaultPrecision(T temporal, Dialect d) {
|
||||
final int defaultTimestampPrecision = d.getDefaultTimestampPrecision();
|
||||
if ( defaultTimestampPrecision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) {
|
||||
|
@ -472,6 +475,9 @@ public final class DateTimeUtils {
|
|||
}
|
||||
|
||||
public static <T extends Temporal> T roundToSecondPrecision(T temporal, int precision) {
|
||||
if ( precision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) {
|
||||
return temporal;
|
||||
}
|
||||
if ( precision == 0 ) {
|
||||
//noinspection unchecked
|
||||
return temporal.get( ChronoField.NANO_OF_SECOND ) >= 500_000_000L
|
||||
|
|
|
@ -5,10 +5,8 @@ import org.hibernate.testing.orm.junit.DomainModel;
|
|||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
//@Disabled("test produces broken SQL and issue needs to be fixed")
|
||||
@TestForIssue(jiraKey = "HHH-15933")
|
||||
@SessionFactory
|
||||
@DomainModel(annotatedClasses = { Split.class, Reference.class })
|
||||
|
|
|
@ -57,6 +57,33 @@ public class AttributeJoinWithJoinedInheritanceTest {
|
|||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-17646" )
|
||||
public void testLeftJoinSelectFk(SessionFactoryScope scope) {
|
||||
scope.inTransaction( s -> {
|
||||
final ChildEntityA childEntityA = new SubChildEntityA1( 11 );
|
||||
s.persist( childEntityA );
|
||||
final ChildEntityB childEntityB = new ChildEntityB( 21 );
|
||||
s.persist( childEntityB );
|
||||
s.persist( new RootOne( 1, childEntityA ) );
|
||||
s.persist( new RootOne( 2, null ) );
|
||||
} );
|
||||
scope.inTransaction( s -> {
|
||||
// simulate association with ChildEntityB
|
||||
s.createNativeMutationQuery( "update root_one set child_id = 21 where id = 2" ).executeUpdate();
|
||||
} );
|
||||
scope.inTransaction( s -> {
|
||||
final List<Integer> resultList = s.createQuery(
|
||||
"select ce.id " +
|
||||
"from RootOne r left join r.child ce ",
|
||||
Integer.class
|
||||
).getResultList();
|
||||
assertEquals( 2, resultList.size() );
|
||||
assertEquals( 11, resultList.get( 0 ) );
|
||||
assertNull( resultList.get( 1 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeftJoin(SessionFactoryScope scope) {
|
||||
scope.inTransaction( s -> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
|||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -411,7 +410,6 @@ public class OuterJoinTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Hibernate doesn't support implicit joins")
|
||||
public void testJoinOrderWithRightNormalJoinWithInnerImplicitJoins(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction( em -> {
|
||||
List<Tuple> resultList = em.createQuery(
|
||||
|
|
|
@ -35,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
)
|
||||
public class LocalTimeTest {
|
||||
|
||||
private static final LocalTime LOCAL_TIME = DateTimeUtils.roundToDefaultPrecision(
|
||||
private static final LocalTime LOCAL_TIME = DateTimeUtils.adjustToDefaultPrecision(
|
||||
LocalTime.now(),
|
||||
DialectContext.getDialect()
|
||||
);
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.hibernate.orm.test.jpa.ql;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
|
@ -21,19 +20,28 @@ import jakarta.persistence.CollectionTable;
|
|||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@TestForIssue(jiraKey = "HHH-16691")
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
JoinTableOptimizationTest.Document.class, JoinTableOptimizationTest.Person.class
|
||||
JoinTableOptimizationTest.Document.class,
|
||||
JoinTableOptimizationTest.Person.class,
|
||||
JoinTableOptimizationTest.File.class,
|
||||
JoinTableOptimizationTest.Picture.class
|
||||
})
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class JoinTableOptimizationTest {
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-16691")
|
||||
public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -48,6 +56,7 @@ public class JoinTableOptimizationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-16691")
|
||||
public void testInnerJoin(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -68,6 +77,7 @@ public class JoinTableOptimizationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-16691")
|
||||
public void testLeftJoin(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -88,6 +98,7 @@ public class JoinTableOptimizationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-16691")
|
||||
public void testInnerJoinCustomOnClause(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -108,6 +119,7 @@ public class JoinTableOptimizationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-16691")
|
||||
public void testLeftJoinCustomOnClause(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -147,6 +159,199 @@ public class JoinTableOptimizationTest {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testLeftJoinFetch(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select d from Document d left join fetch d.people" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select d1_0.id,d1_0.name,p1_0.Document_id,p1_1.id,p1_1.name " +
|
||||
"from Document d1_0 " +
|
||||
"left join people p1_0 on d1_0.id=p1_0.Document_id " +
|
||||
"left join Person p1_1 on p1_1.id=p1_0.people_id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinTablePolymorphicJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d join d.manyFiles f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select mf1_1.id,case when mf1_2.id is not null then 1 when mf1_1.id is not null then 0 end,mf1_1.document_id,mf1_1.name,mf1_2.extension " +
|
||||
"from Document d1_0 " +
|
||||
"join many_files mf1_0 on d1_0.id=mf1_0.Document_id " +
|
||||
"join file_tbl mf1_1 on mf1_1.id=mf1_0.manyFiles_id " +
|
||||
"left join Picture mf1_2 on mf1_1.id=mf1_2.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinTablePolymorphicLeftJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d left join d.manyFiles f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select mf1_1.id,case when mf1_2.id is not null then 1 when mf1_1.id is not null then 0 end,mf1_1.document_id,mf1_1.name,mf1_2.extension " +
|
||||
"from Document d1_0 " +
|
||||
"left join many_files mf1_0 on d1_0.id=mf1_0.Document_id " +
|
||||
"left join file_tbl mf1_1 on mf1_1.id=mf1_0.manyFiles_id " +
|
||||
"left join Picture mf1_2 on mf1_1.id=mf1_2.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinPolymorphicJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d join d.files f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select f1_0.id,case when f1_1.id is not null then 1 when f1_0.id is not null then 0 end,d1_0.id,d1_0.name,f1_0.name,f1_1.extension " +
|
||||
"from Document d1_0 " +
|
||||
"join file_tbl f1_0 on d1_0.id=f1_0.document_id " +
|
||||
"left join Picture f1_1 on f1_0.id=f1_1.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinPolymorphicLeftJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d left join d.files f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select f1_0.id,case when f1_1.id is not null then 1 when f1_0.id is not null then 0 end,d1_0.id,d1_0.name,f1_0.name,f1_1.extension " +
|
||||
"from Document d1_0 " +
|
||||
"left join file_tbl f1_0 on d1_0.id=f1_0.document_id " +
|
||||
"left join Picture f1_1 on f1_0.id=f1_1.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinTablePolymorphicSubtypeJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d join d.manyPictures f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select mp1_1.id,mp1_2.document_id,mp1_2.name,mp1_1.extension " +
|
||||
"from Document d1_0 " +
|
||||
"join many_pictures mp1_0 on d1_0.id=mp1_0.Document_id " +
|
||||
"join Picture mp1_1 on mp1_1.id=mp1_0.manyPictures_id " +
|
||||
"join file_tbl mp1_2 on mp1_1.id=mp1_2.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinTablePolymorphicSubtypeLeftJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d left join d.manyPictures f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select mp1_1.id,mp1_2.document_id,mp1_2.name,mp1_1.extension " +
|
||||
"from Document d1_0 " +
|
||||
"left join many_pictures mp1_0 on d1_0.id=mp1_0.Document_id " +
|
||||
"left join Picture mp1_1 on mp1_1.id=mp1_0.manyPictures_id " +
|
||||
"left join file_tbl mp1_2 on mp1_1.id=mp1_2.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinPolymorphicSubtypeJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d join d.pictures f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select p1_0.id,p1_1.document_id,p1_1.name,p1_0.extension " +
|
||||
"from Document d1_0 " +
|
||||
"join file_tbl p1_1 on d1_0.id=p1_1.document_id " +
|
||||
"join Picture p1_0 on p1_0.id=p1_1.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17646")
|
||||
public void testJoinPolymorphicSubtypeLeftJoined(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select f from Document d left join d.pictures f" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
Assertions.assertEquals(
|
||||
"select p1_0.id,p1_1.document_id,p1_1.name,p1_0.extension " +
|
||||
"from Document d1_0 " +
|
||||
"left join file_tbl p1_1 on d1_0.id=p1_1.document_id " +
|
||||
"left join Picture p1_0 on p1_0.id=p1_1.id",
|
||||
statementInspector.getSqlQueries().get( 0 ),
|
||||
"Nested join was not optimized away"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "Document")
|
||||
public static class Document {
|
||||
@Id
|
||||
|
@ -158,6 +363,16 @@ public class JoinTableOptimizationTest {
|
|||
@ElementCollection
|
||||
@CollectionTable(name = "document_pages")
|
||||
Set<Page> pages;
|
||||
@OneToMany(mappedBy = "document")
|
||||
Set<File> files;
|
||||
@ManyToMany
|
||||
@JoinTable(name = "many_files")
|
||||
Set<File> manyFiles;
|
||||
@OneToMany(mappedBy = "document")
|
||||
Set<Picture> pictures;
|
||||
@ManyToMany
|
||||
@JoinTable(name = "many_pictures")
|
||||
Set<Picture> manyPictures;
|
||||
}
|
||||
@Entity(name = "Person")
|
||||
public static class Person {
|
||||
|
@ -165,6 +380,20 @@ public class JoinTableOptimizationTest {
|
|||
Long id;
|
||||
String name;
|
||||
}
|
||||
@Entity(name = "File")
|
||||
@Table(name = "file_tbl")
|
||||
@Inheritance(strategy = InheritanceType.JOINED)
|
||||
public static class File {
|
||||
@Id
|
||||
Long id;
|
||||
String name;
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
Document document;
|
||||
}
|
||||
@Entity(name = "Picture")
|
||||
public static class Picture extends File {
|
||||
String extension;
|
||||
}
|
||||
@Embeddable
|
||||
public static class Page {
|
||||
String text;
|
||||
|
|
|
@ -44,7 +44,7 @@ import jakarta.persistence.Id;
|
|||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
|
||||
import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
|
||||
|
||||
/**
|
||||
* Tests for "direct" JDBC handling of {@linkplain java.time Java Time} types.
|
||||
|
@ -84,7 +84,7 @@ public class GlobalJavaTimeJdbcTypeTests {
|
|||
@Test
|
||||
void testInstant(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final Instant start = roundToDefaultPrecision( Instant.EPOCH, dialect );
|
||||
final Instant start = adjustToDefaultPrecision( Instant.EPOCH, dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
@ -114,7 +114,7 @@ public class GlobalJavaTimeJdbcTypeTests {
|
|||
@Test
|
||||
void testLocalDateTime(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final LocalDateTime start = roundToDefaultPrecision( LocalDateTime.now(), dialect );
|
||||
final LocalDateTime start = adjustToDefaultPrecision( LocalDateTime.now(), dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
@ -144,7 +144,7 @@ public class GlobalJavaTimeJdbcTypeTests {
|
|||
@Test
|
||||
void testLocalDate(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final LocalDate startTime = roundToDefaultPrecision( LocalDate.now(), dialect );
|
||||
final LocalDate startTime = adjustToDefaultPrecision( LocalDate.now(), dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
@ -177,7 +177,7 @@ public class GlobalJavaTimeJdbcTypeTests {
|
|||
@SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase drivers truncate fractional seconds from the LocalTime")
|
||||
void testLocalTime(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect );
|
||||
final LocalTime startTime = adjustToDefaultPrecision( LocalTime.now(), dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
|
|
@ -45,7 +45,7 @@ import jakarta.persistence.Id;
|
|||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
|
||||
import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
|
||||
|
||||
/**
|
||||
* Tests for "direct" JDBC handling of {@linkplain java.time Java Time} types.
|
||||
|
@ -85,7 +85,7 @@ public class JavaTimeJdbcTypeTests {
|
|||
@Test
|
||||
void testInstant(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final Instant start = roundToDefaultPrecision( Instant.EPOCH, dialect );
|
||||
final Instant start = adjustToDefaultPrecision( Instant.EPOCH, dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
@ -115,7 +115,7 @@ public class JavaTimeJdbcTypeTests {
|
|||
@Test
|
||||
void testLocalDateTime(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final LocalDateTime start = roundToDefaultPrecision( LocalDateTime.now(), dialect );
|
||||
final LocalDateTime start = adjustToDefaultPrecision( LocalDateTime.now(), dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
@ -145,7 +145,7 @@ public class JavaTimeJdbcTypeTests {
|
|||
@Test
|
||||
void testLocalDate(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final LocalDate startTime = roundToDefaultPrecision( LocalDate.now(), dialect );
|
||||
final LocalDate startTime = adjustToDefaultPrecision( LocalDate.now(), dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
@ -178,7 +178,7 @@ public class JavaTimeJdbcTypeTests {
|
|||
@SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase drivers truncate fractional seconds from the LocalTime")
|
||||
void testLocalTime(SessionFactoryScope scope) {
|
||||
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
|
||||
final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect );
|
||||
final LocalTime startTime = adjustToDefaultPrecision( LocalTime.now(), dialect );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final EntityWithJavaTimeValues entity = new EntityWithJavaTimeValues();
|
||||
|
|
|
@ -111,6 +111,23 @@ public class ToOneTests {
|
|||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void fkAccessTest(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final Integer issue2Reporter = session.createQuery( "select i.reporter.id from Issue i where i.id = 2", Integer.class ).getSingleResultOrNull();
|
||||
assertThat( issue2Reporter ).isNull();
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".reporter_fk" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsAnyOf( ".active='Y'", ".active=N'Y'" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsOnlyOnce( "active" );
|
||||
} );
|
||||
}
|
||||
|
||||
@Entity(name="Issue")
|
||||
@Table(name="issues")
|
||||
public static class Issue {
|
||||
|
|
Loading…
Reference in New Issue