From 86db807e2283ab56100ea1ee1d5255e5cbde48a7 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 20 Oct 2024 15:01:10 +0200 Subject: [PATCH] major cleanup to BaseSqmToSqlAstConverter this class was full of EXTREMELY long methods and lots of typecasts Signed-off-by: Gavin King --- .../MultiTableSqmMutationConverter.java | 2 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 2042 ++++++++--------- ...actSqlAstQueryNodeProcessingStateImpl.java | 5 +- 3 files changed, 976 insertions(+), 1073 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index 42569f88f7..7fe38e72f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -115,7 +115,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter getProcessingStateStack() { return super.getProcessingStateStack(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 2442c8408f..037d2d1b1e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -26,10 +26,11 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; + +import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.Internal; import org.hibernate.LockMode; -import org.hibernate.boot.model.process.internal.InferredBasicValueResolver; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.TimestampaddFunction; import org.hibernate.dialect.function.TimestampdiffFunction; @@ -104,7 +105,6 @@ import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EntityDiscriminatorSqmPath; import org.hibernate.metamodel.model.domain.internal.EntityTypeImpl; -import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.entity.EntityNameUse; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.BindableType; @@ -238,6 +238,7 @@ import org.hibernate.query.sqm.tree.expression.SqmToDuration; import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification; import org.hibernate.query.sqm.tree.expression.SqmTuple; import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation; +import org.hibernate.query.sqm.tree.expression.SqmWindow; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmCteJoin; @@ -419,6 +420,7 @@ import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaTypeHelper; +import org.hibernate.type.descriptor.java.VersionJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -436,8 +438,10 @@ import jakarta.persistence.metamodel.Type; import org.checkerframework.checker.nullness.qual.Nullable; import static jakarta.persistence.metamodel.Type.PersistenceType.ENTITY; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction; +import static org.hibernate.boot.model.process.internal.InferredBasicValueResolver.resolveSqlTypeIndicators; import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; import static org.hibernate.query.sqm.BinaryArithmeticOperator.ADD; @@ -448,7 +452,9 @@ import static org.hibernate.query.common.TemporalUnit.NANOSECOND; import static org.hibernate.query.common.TemporalUnit.NATIVE; import static org.hibernate.query.common.TemporalUnit.SECOND; import static org.hibernate.query.sqm.UnaryArithmeticOperator.UNARY_MINUS; +import static org.hibernate.query.sqm.internal.SqmMappingModelHelper.resolveMappingModelExpressible; import static org.hibernate.query.sqm.internal.SqmUtil.isFkOptimizationAllowed; +import static org.hibernate.query.sqm.sql.AggregateColumnAssignmentHandler.forEntityDescriptor; import static org.hibernate.sql.ast.spi.SqlAstTreeHelper.combinePredicates; import static org.hibernate.type.spi.TypeConfiguration.isDuration; @@ -491,11 +497,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base private Map cteNameMapping; private boolean containsCollectionFetches; private boolean trackSelectionsForGroup; - /* - * Captures the list of SqlSelection for a navigable path. - * The map will only contain entries for order by elements of a QueryGroup, that refer to an attribute name - * i.e. `(select e from Entity e union all ...) order by name` where `name` is an attribute of the type `Entity` - */ + + // Captures the list of SqlSelection for a navigable path. + // The map will only contain entries for order by elements of a QueryGroup, that refer to an attribute name + // i.e. `(select e from Entity e union all ...) order by name` where `name` is an attribute of the type `Entity` private Map>> trackedFetchSelectionsForGroup = Collections.emptyMap(); private List> orderByFragments; @@ -503,14 +508,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base private final SqlAliasBaseManager sqlAliasBaseManager = new SqlAliasBaseManager(); private final Stack processingStateStack = new StandardStack<>( SqlAstProcessingState.class ); private final Stack fromClauseIndexStack = new StandardStack<>( FromClauseIndex.class ); - /* - * Captures all entity name uses under which a table group is being used within the current conjunct. - * Outside of a top level conjunct, it represents the "global uses" i.e. select, from, group and order by clauses. - * Top level conjunct contexts like visitWhereClause, visitHavingClause, visitOrPredicate and visitNestedTopLevelPredicate - * stash away the parent entity name uses, consumes the entity name uses of the conjunct by rendering a type restriction, - * and then restore the parent entity name uses again. - */ + + // Captures all entity name uses under which a table group is being used within the current conjunct. + // Outside a top level conjunct, it represents the "global uses" i.e. select, from, group and order by clauses. + // Top level conjunct contexts like visitWhereClause, visitHavingClause, visitOrPredicate and visitNestedTopLevelPredicate + // stash away the parent entity name uses, consumes the entity name uses of the conjunct by rendering a type restriction, + // and then restore the parent entity name uses again. private final Map> tableGroupEntityNameUses = new IdentityHashMap<>(); + private SqlAstProcessingState lastPoppedProcessingState; private FromClauseIndex lastPoppedFromClauseIndex; private SqmJoin currentlyProcessingJoin; @@ -544,18 +549,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base boolean deduplicateSelectionItems) { this.inferrableTypeAccessStack.push( () -> null ); this.creationContext = creationContext; - this.jpaQueryComplianceEnabled = creationContext - .getSessionFactory() - .getSessionFactoryOptions() - .getJpaCompliance() - .isJpaQueryComplianceEnabled(); + this.jpaQueryComplianceEnabled = + creationContext.getSessionFactory().getSessionFactoryOptions() + .getJpaCompliance().isJpaQueryComplianceEnabled(); this.statement = statement; this.currentSqmStatement = statement; this.deduplicateSelectionItems = deduplicateSelectionItems; - if ( statement instanceof SqmSelectStatement ) { - final SqmQueryPart queryPart = ( (SqmSelectStatement) statement ).getQueryPart(); + if ( statement instanceof SqmSelectStatement selectStatement ) { // NOTE: note the difference here between `JpaSelection#getSelectionItems` // and `SqmSelectClause#getSelections`. // @@ -566,7 +568,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base // - `#getSelections` returns top-level selections. These are ultimately the // domain-results of the query this.domainResults = new ArrayList<>( - queryPart.getFirstQuerySpec() + selectStatement.getQueryPart().getFirstQuerySpec() .getSelectClause() .getSelections() .size() @@ -578,19 +580,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base // select e1.id, e1.id from Entity1 e1 // union all // select e2.id, e2.parentId from Entity2 e2 - if ( queryPart instanceof SqmQueryGroup ) { + if ( selectStatement.getQueryPart() instanceof SqmQueryGroup ) { this.deduplicateSelectionItems = false; } - final AppliedGraph appliedGraph = queryOptions.getAppliedGraph(); - if ( appliedGraph != null && appliedGraph.getSemantic() != null && appliedGraph.getGraph() != null ) { - this.entityGraphTraversalState = new StandardEntityGraphTraversalStateImpl( - appliedGraph.getSemantic(), appliedGraph.getGraph(), - creationContext.getSessionFactory().getJpaMetamodel() - ); - } - else { - this.entityGraphTraversalState = null; - } + this.entityGraphTraversalState = entityGraphTraversalState( creationContext, queryOptions ); } else { this.domainResults = null; @@ -605,26 +598,38 @@ public abstract class BaseSqmToSqlAstConverter extends Base this.domainModel = creationContext.getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(); } - private static Boolean stackMatchHelper(SqlAstProcessingState processingState, SqlAstProcessingState c) { - if ( !( processingState instanceof SqlAstQueryPartProcessingState ) ) { - return Boolean.FALSE; + private static EntityGraphTraversalState entityGraphTraversalState(SqlAstCreationContext creationContext, QueryOptions queryOptions) { + final AppliedGraph appliedGraph = queryOptions.getAppliedGraph(); + if ( appliedGraph != null + && appliedGraph.getSemantic() != null + && appliedGraph.getGraph() != null ) { + return new StandardEntityGraphTraversalStateImpl( + appliedGraph.getSemantic(), appliedGraph.getGraph(), + creationContext.getSessionFactory().getJpaMetamodel() + ); } - if ( processingState == c ) { + else { return null; } - final QueryPart part = ( (SqlAstQueryPartProcessingState) processingState ).getInflightQueryPart(); - if ( part instanceof QueryGroup ) { - if ( ( (QueryGroup) part ).getQueryParts().isEmpty() ) { - return null; - } + } + + private static Boolean stackMatchHelper(SqlAstProcessingState processingState, SqlAstProcessingState state) { + if ( !( processingState instanceof SqlAstQueryPartProcessingState sqlAstQueryPartProcessingState ) ) { + return false; + } + else if ( processingState == state ) { + return null; + } + else { + final QueryPart part = sqlAstQueryPartProcessingState.getInflightQueryPart(); + return part instanceof QueryGroup group && group.getQueryParts().isEmpty() ? null : false; } - return Boolean.FALSE; } private static Boolean matchSqlAstWithQueryPart(SqlAstProcessingState state, QueryPart cteQueryPartLocal) { - if ( state instanceof SqlAstQueryPartProcessingState ) { - if ( ( (SqlAstQueryPartProcessingState) state ).getInflightQueryPart() == cteQueryPartLocal ) { - return Boolean.TRUE; + if ( state instanceof SqlAstQueryPartProcessingState sqlAstQueryPartProcessingState ) { + if ( sqlAstQueryPartProcessingState.getInflightQueryPart() == cteQueryPartLocal ) { + return true; } } return null; @@ -661,8 +666,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private QueryPart currentQueryPart() { - final SqlAstQueryPartProcessingState processingState = (SqlAstQueryPartProcessingState) getProcessingStateStack() - .getCurrent(); + final SqlAstQueryPartProcessingState processingState = + (SqlAstQueryPartProcessingState) getProcessingStateStack().getCurrent(); return processingState.getInflightQueryPart(); } @@ -779,12 +784,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public FromClauseAccess getFromClauseAccess() { final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); - if ( fromClauseIndex == null ) { - return lastPoppedFromClauseIndex; - } - else { - return fromClauseIndex; - } + return fromClauseIndex == null ? lastPoppedFromClauseIndex : fromClauseIndex; } @Override @@ -831,7 +831,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base final EntityPersister entityDescriptor = getCreationContext() .getSessionFactory() - .getRuntimeMetamodels() .getMappingMetamodel() .getEntityDescriptor( entityName ); assert entityDescriptor != null; @@ -865,12 +864,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base assignments, combinePredicates( suppliedPredicate, - combinePredicates( - queryNodeProcessingState.getPredicate(), - additionalRestrictions - ) + combinePredicates( queryNodeProcessingState.getPredicate(), additionalRestrictions ) ), - Collections.emptyList() + emptyList() ); } finally { @@ -884,10 +880,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base if ( !sqmStatement.isVersioned() ) { return; } - final EntityPersister persister = creationContext.getSessionFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .findEntityDescriptor( sqmStatement.getTarget().getEntityName() ); + final EntityPersister persister = + creationContext.getSessionFactory().getMappingMetamodel() + .findEntityDescriptor( sqmStatement.getTarget().getEntityName() ); if ( !persister.isVersioned() ) { throw new SemanticException( "Increment option specified for update of non-versioned entity" ); } @@ -900,8 +895,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base currentClauseStack.push( Clause.SET ); final EntityVersionMapping versionMapping = persister.getVersionMapping(); final List targetColumnReferences = BasicValuedPathInterpretation.from( - (SqmBasicValuedSimplePath) sqmStatement - .getRoot() + (SqmBasicValuedSimplePath) sqmStatement.getRoot() .get( versionMapping.getPartName() ), this, jpaQueryComplianceEnabled @@ -910,56 +904,55 @@ public abstract class BaseSqmToSqlAstConverter extends Base assert targetColumnReferences.size() == 1; final ColumnReference versionColumn = targetColumnReferences.get( 0 ); - final Expression value; - if ( versionMapping.getJdbcMapping().getJdbcType().isTemporal() ) { - value = new VersionTypeSeedParameterSpecification( versionMapping ); - } - else { - value = new BinaryArithmeticExpression( - versionColumn, - ADD, - new QueryLiteral<>( - persister.getVersionJavaType().next( - persister.getVersionJavaType().seed( - versionMapping.getLength(), - versionMapping.getTemporalPrecision() != null - ? versionMapping.getTemporalPrecision() - : versionMapping.getPrecision(), - versionMapping.getScale(), - null - ), - versionMapping.getLength(), - versionMapping.getTemporalPrecision() != null - ? versionMapping.getTemporalPrecision() - : versionMapping.getPrecision(), - versionMapping.getScale(), - null - ), - versionType - ), - versionType - ); - } + final Expression value = + versionMapping.getJdbcMapping().getJdbcType().isTemporal() + ? new VersionTypeSeedParameterSpecification( versionMapping ) + : versionIncrementExpression( persister, versionColumn, versionMapping, versionType ); assignmentConsumer.accept( new Assignment( versionColumn, value ) ); } + private static Expression versionIncrementExpression( + EntityPersister persister, ColumnReference versionColumn, EntityVersionMapping versionMapping, BasicType versionType) { + final VersionJavaType versionJavaType = persister.getVersionJavaType(); + return new BinaryArithmeticExpression( + versionColumn, + ADD, + new QueryLiteral<>( + versionJavaType.next( + versionJavaType.seed( + versionMapping.getLength(), + versionMapping.getTemporalPrecision() != null + ? versionMapping.getTemporalPrecision() + : versionMapping.getPrecision(), + versionMapping.getScale(), + null + ), + versionMapping.getLength(), + versionMapping.getTemporalPrecision() != null + ? versionMapping.getTemporalPrecision() + : versionMapping.getPrecision(), + versionMapping.getScale(), + null + ), + versionType + ), + versionType + ); + } + @Override public List visitSetClause(SqmSetClause setClause) { final ArrayList assignments = new ArrayList<>( setClause.getAssignments().size() ); final SqmRoot target = ( (SqmDmlStatement) currentSqmStatement ).getTarget(); final String entityName = target.getEntityName(); - final EntityPersister entityDescriptor = getCreationContext() - .getSessionFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entityName ); + final EntityPersister entityDescriptor = + getCreationContext().getSessionFactory().getMappingMetamodel() + .getEntityDescriptor( entityName ); // Returns a new instance for collecting assignments if needed - final AggregateColumnAssignmentHandler aggregateColumnAssignmentHandler = AggregateColumnAssignmentHandler.forEntityDescriptor( - entityDescriptor, - setClause.getAssignments().size() - ); + final AggregateColumnAssignmentHandler aggregateColumnAssignmentHandler = + forEntityDescriptor( entityDescriptor, setClause.getAssignments().size() ); for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) { final SqmPathInterpretation assignedPathInterpretation; @@ -978,14 +971,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base final SqmExpression assignmentValue = sqmAssignment.getValue(); final SqmParameter assignmentValueParameter = getSqmParameter( assignmentValue ); final Expression pathSqlExpression = assignedPathInterpretation.getSqlExpression(); - final List targetColumnReferences; - if ( pathSqlExpression instanceof SqlTuple ) { - //noinspection unchecked - targetColumnReferences = (List) ( (SqlTuple) pathSqlExpression ).getExpressions(); - } - else { - targetColumnReferences = pathSqlExpression.getColumnReference().getColumnReferences(); - } + final List targetColumnReferences = + pathSqlExpression instanceof SqlTuple sqlTuple + ? (List) sqlTuple.getExpressions() + : pathSqlExpression.getColumnReference().getColumnReferences(); if ( assignmentValueParameter != null ) { consumeSqmParameter( assignmentValueParameter, @@ -1009,42 +998,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } else { - final Expression valueExpression = (Expression) assignmentValue.accept( this ); - - final int valueExprJdbcCount = getKeyExpressible( valueExpression.getExpressionType() ).getJdbcTypeCount(); - final int assignedPathJdbcCount = getKeyExpressible( assignedPathInterpretation.getExpressionType() ) - .getJdbcTypeCount(); - - if ( valueExprJdbcCount != assignedPathJdbcCount ) { - SqlTreeCreationLogger.LOGGER.debug( - "JDBC type count does not match in UPDATE assignment between the assigned-path and the assigned-value; " + - "this will likely lead to problems executing the query" - ); - } - - assert assignedPathJdbcCount == valueExprJdbcCount; - - if ( valueExpression instanceof SqlTuple ) { - final List expressions = ( (SqlTuple) valueExpression ).getExpressions(); - assert targetColumnReferences.size() == expressions.size(); - for ( int i = 0; i < targetColumnReferences.size(); i++ ) { - final ColumnReference columnReference = targetColumnReferences.get( i ); - addAssignment( assignments, aggregateColumnAssignmentHandler, columnReference, expressions.get( i ) ); - } - } - else if ( valueExpression instanceof EmbeddableValuedPathInterpretation ) { - final List expressions = ( (EmbeddableValuedPathInterpretation) valueExpression ).getSqlTuple().getExpressions(); - assert targetColumnReferences.size() == expressions.size(); - for ( int i = 0; i < targetColumnReferences.size(); i++ ) { - final ColumnReference columnReference = targetColumnReferences.get( i ); - addAssignment( assignments, aggregateColumnAssignmentHandler, columnReference, expressions.get( i ) ); - } - } - else { - for ( ColumnReference columnReference : targetColumnReferences ) { - addAssignment( assignments, aggregateColumnAssignmentHandler, columnReference, valueExpression ); - } - } + addAssignments( + (Expression) assignmentValue.accept( this ), + assignedPathInterpretation.getExpressionType(), + targetColumnReferences, + assignments, + aggregateColumnAssignmentHandler + ); } } finally { @@ -1060,6 +1020,52 @@ public abstract class BaseSqmToSqlAstConverter extends Base return assignments; } + private void addAssignments( + Expression valueExpression, + ModelPart assignedPathType, + List targetColumnReferences, + ArrayList assignments, + AggregateColumnAssignmentHandler aggregateColumnAssignmentHandler) { + checkAssignment( valueExpression, assignedPathType ); + + if ( valueExpression instanceof SqlTuple sqlTuple ) { + final List expressions = sqlTuple.getExpressions(); + assert targetColumnReferences.size() == expressions.size(); + for ( int i = 0; i < targetColumnReferences.size(); i++ ) { + final ColumnReference columnReference = targetColumnReferences.get( i ); + addAssignment( assignments, aggregateColumnAssignmentHandler, columnReference, expressions.get( i ) ); + } + } + else if ( valueExpression instanceof EmbeddableValuedPathInterpretation embeddable ) { + final List expressions = embeddable.getSqlTuple().getExpressions(); + assert targetColumnReferences.size() == expressions.size(); + for ( int i = 0; i < targetColumnReferences.size(); i++ ) { + final ColumnReference columnReference = targetColumnReferences.get( i ); + addAssignment( assignments, aggregateColumnAssignmentHandler, columnReference, expressions.get( i ) ); + } + } + else { + for ( ColumnReference columnReference : targetColumnReferences ) { + addAssignment( assignments, aggregateColumnAssignmentHandler, columnReference, valueExpression ); + } + } + } + + private void checkAssignment(Expression valueExpression, ModelPart assignedPathType) { + final int valueExprJdbcCount = getKeyExpressible( valueExpression.getExpressionType() ).getJdbcTypeCount(); + final int assignedPathJdbcCount = getKeyExpressible( assignedPathType ).getJdbcTypeCount(); + + // TODO: why is this not an AssertionFailure instead of a DEBUG level log?! + if ( valueExprJdbcCount != assignedPathJdbcCount ) { + SqlTreeCreationLogger.LOGGER.debug( + "JDBC type count does not match in UPDATE assignment between the assigned-path and the assigned-value; " + + "this will likely lead to problems executing the query" + ); + } + + assert assignedPathJdbcCount == valueExprJdbcCount; + } + private void addAssignment( List assignments, AggregateColumnAssignmentHandler aggregateColumnAssignmentHandler, @@ -1083,10 +1089,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base this.currentSqmStatement = statement; final SqmRoot sqmTarget = statement.getTarget(); final String entityName = sqmTarget.getEntityName(); - final EntityPersister entityDescriptor = creationContext.getSessionFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entityName ); + final EntityPersister entityDescriptor = + creationContext.getSessionFactory().getMappingMetamodel() + .getEntityDescriptor( entityName ); assert entityDescriptor != null; final FromClause fromClause = new FromClause( 1 ); @@ -1119,7 +1124,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base additionalRestrictions ) ), - Collections.emptyList() + emptyList() ); } finally { @@ -1140,10 +1145,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base this.currentSqmStatement = sqmStatement; final String entityName = sqmStatement.getTarget().getEntityName(); - final EntityPersister entityDescriptor = creationContext.getSessionFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entityName ); + final EntityPersister entityDescriptor = + creationContext.getSessionFactory().getMappingMetamodel() + .getEntityDescriptor( entityName ); assert entityDescriptor != null; SqmQueryPart selectQueryPart = sqmStatement.getSelectQueryPart(); @@ -1179,7 +1183,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base insertStatement = new InsertSelectStatement( cteContainer, (NamedTableReference) rootTableGroup.getPrimaryTableReference(), - Collections.emptyList() + emptyList() ); additionalInsertValues = visitInsertionTargetPaths( (assigable, references) -> insertStatement.addTargetColumnReferences( references ), @@ -1203,10 +1207,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base insertStatement.getSourceSelectStatement().visitQuerySpecs( querySpec -> { - final boolean appliedRowNumber = additionalInsertValues.applySelections( - querySpec, - creationContext.getSessionFactory() - ); + final boolean appliedRowNumber = + additionalInsertValues.applySelections( querySpec, creationContext.getSessionFactory() ); // Just make sure that if we get here, a row number will never be applied // If this requires the special row number handling, it should use the mutation strategy assert !appliedRowNumber; @@ -1222,22 +1224,22 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private static boolean hasJoins(TableGroup rootTableGroup) { - if ( !rootTableGroup.getTableReferenceJoins().isEmpty() ) { - return true; - } - return hasJoins( rootTableGroup.getTableGroupJoins() ) || hasJoins( rootTableGroup.getNestedTableGroupJoins() ); + return !rootTableGroup.getTableReferenceJoins().isEmpty() + || hasJoins( rootTableGroup.getTableGroupJoins() ) + || hasJoins( rootTableGroup.getNestedTableGroupJoins() ); } private static boolean hasJoins(List tableGroupJoins) { for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) { final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup(); - if ( !joinedGroup.isInitialized() ) { - continue; + if ( joinedGroup.isInitialized() ) { + if ( joinedGroup.isVirtual() ) { + return hasJoins( joinedGroup ); + } + else { + return true; + } } - else if ( joinedGroup.isVirtual() ) { - return hasJoins( joinedGroup ); - } - return true; } return false; } @@ -1253,10 +1255,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base this.currentSqmStatement = sqmStatement; final String entityName = sqmStatement.getTarget().getEntityName(); - final EntityPersister entityDescriptor = creationContext.getSessionFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entityName ); + final EntityPersister entityDescriptor = + creationContext.getSessionFactory().getMappingMetamodel() + .getEntityDescriptor( entityName ); assert entityDescriptor != null; pushProcessingState( @@ -1288,7 +1289,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base final InsertSelectStatement insertStatement = new InsertSelectStatement( cteContainer, (NamedTableReference) rootTableGroup.getPrimaryTableReference(), - Collections.emptyList() + emptyList() ); final AdditionalInsertValues additionalInsertValues = visitInsertionTargetPaths( @@ -1337,7 +1338,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base final List assignments; final Predicate predicate; if ( updateAction == null ) { - assignments = Collections.emptyList(); + assignments = emptyList(); predicate = null; } else { @@ -1437,20 +1438,20 @@ public abstract class BaseSqmToSqlAstConverter extends Base // For an InsertValuesStatement, we can just list the column, as we can inject a parameter in the VALUES clause. addIdColumn = true; } - else if ( !( identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator ) ) { + else if ( !( identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator bulkInsertionCapableGenerator ) ) { // For non-identity generators that don't implement BulkInsertionCapableIdentifierGenerator, there is nothing we can do addIdColumn = false; } else { // Same condition as in AdditionalInsertValues#applySelections final Optimizer optimizer; - if ( identifierGenerator instanceof OptimizableGenerator - && ( optimizer = ( (OptimizableGenerator) identifierGenerator ).getOptimizer() ) != null + if ( identifierGenerator instanceof OptimizableGenerator optimizableGenerator + && ( optimizer = optimizableGenerator.getOptimizer() ) != null && optimizer.getIncrementSize() > 1 - || !( (BulkInsertionCapableIdentifierGenerator) identifierGenerator ).supportsBulkInsertionIdentifierGeneration() ) { + || !bulkInsertionCapableGenerator.supportsBulkInsertionIdentifierGeneration() ) { // If the dialect does not support window functions, we don't need the id column in the temporary table insert // because we will make use of the special "rn_" column that is auto-incremented and serves as temporary identifier for a row, - // which is needed to control the generation of proper identifier values with the generator afterwards + // which is needed to control the generation of proper identifier values with the generator afterward addIdColumn = creationContext.getSessionFactory().getJdbcServices().getDialect().supportsWindowFunctions(); } else { @@ -1542,14 +1543,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base } if ( identifierGenerator != null ) { if ( identifierSelection == null ) { - if ( !( identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator ) ) { + if ( !( identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator bulkInsertionCapableGenerator ) ) { throw new SemanticException( "SQM INSERT-SELECT without bulk insertion capable identifier generator: " + identifierGenerator ); } - if ( identifierGenerator instanceof OptimizableGenerator ) { - final Optimizer optimizer = ( (OptimizableGenerator) identifierGenerator ).getOptimizer(); + if ( identifierGenerator instanceof OptimizableGenerator optimizableGenerator ) { + final Optimizer optimizer = optimizableGenerator.getOptimizer(); if ( optimizer != null && optimizer.getIncrementSize() > 1 - || !( (BulkInsertionCapableIdentifierGenerator) identifierGenerator ).supportsBulkInsertionIdentifierGeneration() ) { + || !bulkInsertionCapableGenerator.supportsBulkInsertionIdentifierGeneration() ) { // This is a special case where we have a sequence with an optimizer // or a table based identifier generator if ( !sessionFactory.getJdbcServices().getDialect().supportsWindowFunctions() ) { @@ -1562,14 +1563,12 @@ public abstract class BaseSqmToSqlAstConverter extends Base return true; } } - final String fragment = ( (BulkInsertionCapableIdentifierGenerator) identifierGenerator ) - .determineBulkInsertionIdentifierGenerationSelectFragment( + final String fragment = + bulkInsertionCapableGenerator.determineBulkInsertionIdentifierGenerationSelectFragment( sessionFactory.getSqlStringGenerationContext() ); // The position is irrelevant as this is only needed for insert - identifierSelection = new SqlSelectionImpl( - new SelfRenderingSqlFragmentExpression( fragment ) - ); + identifierSelection = new SqlSelectionImpl( new SelfRenderingSqlFragmentExpression( fragment ) ); } selectClause.addSqlSelection( identifierSelection ); } @@ -1578,7 +1577,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base public boolean requiresRowNumberIntermediate() { return identifierSelection != null - && !( identifierSelection.getExpression() instanceof SelfRenderingSqlFragmentExpression ); + && !( identifierSelection.getExpression() instanceof SelfRenderingSqlFragmentExpression ); } } @@ -1608,13 +1607,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public Values visitValues(SqmValues sqmValues) { - final List> insertionTargetPaths; - if ( currentSqmStatement instanceof SqmInsertStatement ) { - insertionTargetPaths = ( (SqmInsertStatement) currentSqmStatement ).getInsertionTargetPaths(); - } - else { - insertionTargetPaths = null; - } + final List> insertionTargetPaths = + currentSqmStatement instanceof SqmInsertStatement insertStatement + ? insertStatement.getInsertionTargetPaths() : null; final List> expressions = sqmValues.getExpressions(); final ArrayList valuesExpressions = new ArrayList<>( expressions.size() ); for ( int i = 0; i < expressions.size(); i++ ) { @@ -1639,7 +1634,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base this.currentSqmStatement = statement; final QueryPart queryPart = visitQueryPart( statement.getQueryPart() ); - final List> domainResults = queryPart.isRoot() ? this.domainResults : Collections.emptyList(); + final List> domainResults = queryPart.isRoot() ? this.domainResults : emptyList(); try { return new SelectStatement( cteContainer, queryPart, domainResults ); } @@ -1655,15 +1650,12 @@ public abstract class BaseSqmToSqlAstConverter extends Base final DynamicInstantiationNature instantiationNature = instantiationTarget.getNature(); final JavaType targetTypeDescriptor = interpretInstantiationTarget( instantiationTarget ); - final DynamicInstantiation dynamicInstantiation = new DynamicInstantiation<>( - instantiationNature, - targetTypeDescriptor - ); + final DynamicInstantiation dynamicInstantiation = + new DynamicInstantiation<>( instantiationNature, targetTypeDescriptor ); for ( SqmDynamicInstantiationArgument sqmArgument : sqmDynamicInstantiation.getArguments() ) { - final SqmSelectableNode selectableNode = sqmArgument.getSelectableNode(); - if ( selectableNode instanceof SqmPath ) { - prepareForSelection( (SqmPath) selectableNode ); + if ( sqmArgument.getSelectableNode() instanceof SqmPath sqmPath ) { + prepareForSelection( sqmPath ); } final DomainResultProducer argumentResultProducer = (DomainResultProducer) sqmArgument.accept( this ); @@ -1675,24 +1667,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base return dynamicInstantiation; } - @SuppressWarnings("unchecked") private JavaType interpretInstantiationTarget(SqmDynamicInstantiationTarget instantiationTarget) { - final Class targetJavaType; - - if ( instantiationTarget.getNature() == DynamicInstantiationNature.LIST ) { - targetJavaType = (Class) List.class; - } - else if ( instantiationTarget.getNature() == DynamicInstantiationNature.MAP ) { - targetJavaType = (Class) Map.class; - } - else { - targetJavaType = (Class) instantiationTarget.getJavaType(); - } - return getCreationContext().getMappingMetamodel() .getTypeConfiguration() .getJavaTypeRegistry() - .getDescriptor( targetJavaType ); + .getDescriptor( switch ( instantiationTarget.getNature() ) { + case LIST -> List.class; + case MAP -> Map.class; + default -> instantiationTarget.getJavaType(); + } ); } @Override @@ -1704,12 +1687,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base final Literal cycleLiteral = getLiteral( sqmCteStatement.getCycleLiteral() ); final Literal noCycleLiteral = getLiteral( sqmCteStatement.getNoCycleLiteral() ); final JdbcMapping cycleMarkType = cycleLiteral == null ? null : cycleLiteral.getJdbcMapping(); - final BasicType stringType = creationContext.getSessionFactory() - .getTypeConfiguration() - .getBasicTypeForJavaType( String.class ); - if ( queryPart instanceof SqmQueryGroup && queryPart.getSortSpecifications().isEmpty() - && queryPart.getFetchExpression() == null && queryPart.getOffsetExpression() == null ) { - final SqmQueryGroup queryGroup = (SqmQueryGroup) queryPart; + final BasicType stringType = + creationContext.getSessionFactory().getTypeConfiguration() + .getBasicTypeForJavaType( String.class ); + if ( queryPart instanceof SqmQueryGroup queryGroup + && queryPart.getSortSpecifications().isEmpty() + && queryPart.getFetchExpression() == null + && queryPart.getOffsetExpression() == null ) { switch ( queryGroup.getSetOperator() ) { case UNION: case UNION_ALL: @@ -1740,8 +1724,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base currentClauseStack::getCurrent, deduplicateSelectionItems ); - final DelegatingSqmAliasedNodeCollector collector = (DelegatingSqmAliasedNodeCollector) processingState - .getSqlExpressionResolver(); + final DelegatingSqmAliasedNodeCollector collector = + (DelegatingSqmAliasedNodeCollector) processingState.getSqlExpressionResolver(); final SqmQueryPart oldSqmQueryPart = currentSqmQueryPart; currentSqmQueryPart = queryGroup; pushProcessingState( processingState ); @@ -1768,7 +1752,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base final CteStatement cteStatement = new CteStatement( cteTable, - new SelectStatement( subCteContainer, group, Collections.emptyList() ), + new SelectStatement( subCteContainer, group, emptyList() ), sqmCteStatement.getMaterialization(), sqmCteStatement.getSearchClauseKind(), visitSearchBySpecifications( cteTable, sqmCteStatement.getSearchBySpecifications() ), @@ -1798,11 +1782,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } final SelectStatement statement; - if ( selectStatement instanceof SqmSubQuery ) { - statement = visitSubQueryExpression( (SqmSubQuery) selectStatement ); + if ( selectStatement instanceof SqmSubQuery subquery ) { + statement = visitSubQueryExpression( subquery ); + } + else if ( selectStatement instanceof SqmSelectStatement select ) { + statement = visitSelectStatement( select ); } else { - statement = visitSelectStatement( (SqmSelectStatement) selectStatement ); + throw new AssertionFailure( "Unrecognized select statemengt type" ); } final CteTable cteTable = new CteTable( @@ -1897,10 +1884,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } protected CteColumn createCteColumn(String cteColumn, JdbcMapping jdbcMapping) { - if ( cteColumn == null ) { - return null; - } - return new CteColumn( cteColumn, jdbcMapping ); + return cteColumn == null ? null : new CteColumn( cteColumn, jdbcMapping ); } protected void forEachCteColumn(CteTable cteTable, SqmCteTableColumn cteColumn, Consumer consumer) { @@ -1922,16 +1906,18 @@ public abstract class BaseSqmToSqlAstConverter extends Base if ( cycleColumns == null || cycleColumns.isEmpty() ) { return null; } - final int size = cycleColumns.size(); - final List columns = new ArrayList<>( size ); - for ( int i = 0; i < size; i++ ) { - forEachCteColumn( - cteTable, - (SqmCteTableColumn) cycleColumns.get( i ), - columns::add - ); + else { + final int size = cycleColumns.size(); + final List columns = new ArrayList<>( size ); + for ( int i = 0; i < size; i++ ) { + forEachCteColumn( + cteTable, + (SqmCteTableColumn) cycleColumns.get( i ), + columns::add + ); + } + return columns; } - return columns; } @Override @@ -1974,8 +1960,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base // and register the navigable paths so that a list of SqlSelection is tracked for the fetch Map>> trackedFetchSelectionsForGroup = null; for ( SqmSortSpecification sortSpecification : queryGroup.getOrderByClause().getSortSpecifications() ) { - if ( sortSpecification.getExpression() instanceof SqmAliasedNodeRef ) { - final SqmAliasedNodeRef nodeRef = (SqmAliasedNodeRef) sortSpecification.getExpression(); + if ( sortSpecification.getExpression() instanceof SqmAliasedNodeRef nodeRef ) { if ( nodeRef.getNavigablePath() != null ) { if ( trackedFetchSelectionsForGroup == null ) { trackedFetchSelectionsForGroup = new HashMap<>(); @@ -2030,45 +2015,20 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public QuerySpec visitQuerySpec(SqmQuerySpec sqmQuerySpec) { final boolean topLevel = getProcessingStateStack().isEmpty(); - final QuerySpec sqlQuerySpec = new QuerySpec( - topLevel, - sqmQuerySpec.getFromClause().getNumberOfRoots() - ); - final SqmSelectClause selectClause = sqmQuerySpec.getSelectClause(); + final QuerySpec sqlQuerySpec = new QuerySpec( topLevel, sqmQuerySpec.getFromClause().getNumberOfRoots() ); final Predicate originalAdditionalRestrictions = additionalRestrictions; additionalRestrictions = null; final boolean oldInNestedContext = inNestedContext; inNestedContext = false; - final boolean trackAliasedNodePositions; - if ( trackSelectionsForGroup ) { - trackAliasedNodePositions = true; - } - else if ( sqmQuerySpec.getOrderByClause() != null && sqmQuerySpec.getOrderByClause().hasPositionalSortItem() ) { - trackAliasedNodePositions = true; - } - else if ( sqmQuerySpec.hasPositionalGroupItem() ) { - trackAliasedNodePositions = true; - } - else { - // Since JPA Criteria queries can use the same expression object in order or group by items, - // we need to track the positions to be able to replace the expression in the items with alias references - // Also see #resolveGroupOrOrderByExpression for more details - trackAliasedNodePositions = statement.getQuerySource() == SqmQuerySource.CRITERIA - && ( sqmQuerySpec.getOrderByClause() != null || !sqmQuerySpec.getGroupByClauseExpressions().isEmpty() ); - } - final SqlAstQueryPartProcessingStateImpl processingState; - if ( trackAliasedNodePositions ) { + if ( trackAliasedNodePositions( sqmQuerySpec ) ) { processingState = new SqlAstQueryPartProcessingStateImpl( sqlQuerySpec, getCurrentProcessingState(), this, - r -> new SqmAliasedNodePositionTracker( - r, - selectClause.getSelections() - ), + resolver -> new SqmAliasedNodePositionTracker( resolver, sqmQuerySpec.getSelectClause().getSelections() ), currentClauseStack::getCurrent, deduplicateSelectionItems ); @@ -2092,57 +2052,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base queryTransformers.push( new ArrayList<>() ); try { - // we want to visit the from-clause first - visitFromClause( sqmQuerySpec.getFromClause() ); - - visitSelectClause( selectClause ); - - final SqmWhereClause whereClause = sqmQuerySpec.getWhereClause(); - if ( whereClause != null ) { - sqlQuerySpec.applyPredicate( visitWhereClause( whereClause.getPredicate() ) ); - } - - sqlQuerySpec.setGroupByClauseExpressions( visitGroupByClause( sqmQuerySpec.getGroupByClauseExpressions() ) ); - if ( sqmQuerySpec.getHavingClausePredicate() != null ) { - sqlQuerySpec.setHavingClauseRestrictions( visitHavingClause( sqmQuerySpec.getHavingClausePredicate() ) ); - } - - visitOrderByOffsetAndFetch( sqmQuerySpec, sqlQuerySpec ); - - if ( topLevel && statement instanceof SqmSelectStatement ) { - if ( orderByFragments != null ) { - orderByFragments.forEach( - entry -> entry.getKey().apply( - sqlQuerySpec, - entry.getValue(), - this - ) - ); - orderByFragments = null; - } - } - - // Look for treated SqmFrom registrations that have uses of the untreated SqmFrom. - // These SqmFrom nodes are then not treat-joined but rather treated only in expressions - // Consider the following two queries. The latter also uses the untreated SqmFrom - // and hence has different semantics i.e. the treat is not filtering, but just applies where it is used - // - select a.id from Root r join treat(r.attribute as Subtype) a where a.id = 1 - // - select a.id from Root r join r.attribute a where treat(a as Subtype).id = 1 - for ( Map.Entry, Boolean> entry : processingState.getFromRegistrations().entrySet() ) { - if ( entry.getValue() == Boolean.TRUE ) { - downgradeTreatUses( getFromClauseIndex().getTableGroup( entry.getKey().getNavigablePath() ) ); - } - } - - QuerySpec finalQuerySpec = sqlQuerySpec; - for ( QueryTransformer transformer : (List) queryTransformers.getCurrent() ) { - finalQuerySpec = transformer.transform( - cteContainer, - finalQuerySpec, - this - ); - } - return finalQuerySpec; + return querySpec( sqmQuerySpec, sqlQuerySpec, topLevel, processingState ); } finally { if ( additionalRestrictions != null ) { @@ -2157,6 +2067,81 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } + private QuerySpec querySpec( + SqmQuerySpec sqmQuerySpec, + QuerySpec sqlQuerySpec, + boolean topLevel, + SqlAstQueryPartProcessingStateImpl processingState) { + // we want to visit the from-clause first + visitFromClause( sqmQuerySpec.getFromClause() ); + + visitSelectClause( sqmQuerySpec.getSelectClause() ); + + final SqmWhereClause whereClause = sqmQuerySpec.getWhereClause(); + if ( whereClause != null ) { + sqlQuerySpec.applyPredicate( visitWhereClause( whereClause.getPredicate() ) ); + } + + sqlQuerySpec.setGroupByClauseExpressions( visitGroupByClause( sqmQuerySpec.getGroupByClauseExpressions() ) ); + + final SqmPredicate havingClausePredicate = sqmQuerySpec.getHavingClausePredicate(); + if ( havingClausePredicate != null ) { + sqlQuerySpec.setHavingClauseRestrictions( visitHavingClause( havingClausePredicate ) ); + } + + visitOrderByOffsetAndFetch( sqmQuerySpec, sqlQuerySpec ); + + if ( topLevel && statement instanceof SqmSelectStatement ) { + if ( orderByFragments != null ) { + orderByFragments.forEach( entry -> entry.getKey().apply( sqlQuerySpec, entry.getValue(), this ) ); + orderByFragments = null; + } + } + + downgradeTreatUses( processingState ); + + return applyTransformers( sqlQuerySpec ); + } + + /** + * Look for treated {@code SqmFrom} registrations that have uses of the untreated {@code SqmFrom}. + * These {@code SqmFrom} nodes are then not treat-joined but rather treated only in expressions. + * Consider the following two queries. The latter also uses the untreated {@code SqmFrom}, and + * hence has different semantics i.e. the treat is not filtering, but just applies where it's used. + * + *
select a.id from Root r join treat(r.attribute as Subtype) a where a.id = 1
+ * + *
select a.id from Root r join r.attribute a where treat(a as Subtype).id = 1
+ */ + private void downgradeTreatUses(SqlAstQueryPartProcessingStateImpl processingState) { + processingState.getFromRegistrations().forEach( (key, value) -> { + if ( value != null && value ) { + downgradeTreatUses( getFromClauseIndex().getTableGroup( key.getNavigablePath() ) ); + } + } ); + } + + private QuerySpec applyTransformers(QuerySpec sqlQuerySpec) { + QuerySpec finalQuerySpec = sqlQuerySpec; + @SuppressWarnings("unchecked") + final List transformers = queryTransformers.getCurrent(); + for ( QueryTransformer transformer : transformers ) { + finalQuerySpec = transformer.transform( cteContainer, finalQuerySpec, this ); + } + return finalQuerySpec; + } + + private boolean trackAliasedNodePositions(SqmQuerySpec sqmQuerySpec) { + return trackSelectionsForGroup + || sqmQuerySpec.getOrderByClause() != null && sqmQuerySpec.getOrderByClause().hasPositionalSortItem() + || sqmQuerySpec.hasPositionalGroupItem() + // Since JPA Criteria queries can use the same expression object in order or group by items, + // we need to track the positions to be able to replace the expression in the items with alias references + // Also see #resolveGroupOrOrderByExpression for more details + || statement.getQuerySource() == SqmQuerySource.CRITERIA + && ( sqmQuerySpec.getOrderByClause() != null || !sqmQuerySpec.getGroupByClauseExpressions().isEmpty() ); + } + private void downgradeTreatUses(TableGroup tableGroup) { final Map entityNameUses = tableGroupEntityNameUses.get( tableGroup ); if ( entityNameUses != null ) { @@ -2173,8 +2158,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base currentClauseStack.push( Clause.ORDER ); inferrableTypeAccessStack.push( () -> null ); try { - for ( SqmSortSpecification sortSpecification : sqmQueryPart.getOrderByClause() - .getSortSpecifications() ) { + for ( SqmSortSpecification sortSpecification : + sqmQueryPart.getOrderByClause().getSortSpecifications() ) { final SortSpecification specification = visitSortSpecification( sortSpecification ); if ( specification != null ) { sqlQueryPart.addSortSpecification( specification ); @@ -2242,29 +2227,91 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public Void visitSelection(SqmSelection sqmSelection) { - return visitSelection( + visitSelection( currentSqmQueryPart.getFirstQuerySpec().getSelectClause().getSelections().indexOf( sqmSelection ), sqmSelection ); + return null; } - public Void visitSelection(int index, SqmSelection sqmSelection) { - final boolean contributesToTopLevelSelectClause = currentClauseStack.depth() == 1 && currentClauseStack.getCurrent() == Clause.SELECT; + private void visitSelection(int index, SqmSelection sqmSelection) { + inferTargetPath( index ); + callResultProducers( resultProducers( sqmSelection ) ); + if ( statement instanceof SqmInsertSelectStatement + && contributesToTopLevelSelectClause() ) { + inferrableTypeAccessStack.pop(); + } + } + + private void inferTargetPath(int index) { // Only infer the type on the "top level" select clauses - final boolean inferTargetPath = statement instanceof SqmInsertSelectStatement && contributesToTopLevelSelectClause; // todo: add WriteExpression handling - if ( inferTargetPath ) { - final SqmPath path = ( (SqmInsertSelectStatement) statement ).getInsertionTargetPaths().get( index ); + if ( statement instanceof SqmInsertSelectStatement insertSelectStatement + && contributesToTopLevelSelectClause() ) { + // we infer the target path + final SqmPath path = insertSelectStatement.getInsertionTargetPaths().get( index ); inferrableTypeAccessStack.push( () -> determineValueMapping( path ) ); } - final List>> resultProducers; + } + + private boolean contributesToTopLevelSelectClause() { + return currentClauseStack.depth() == 1 + && currentClauseStack.getCurrent() == Clause.SELECT; + } + + private void callResultProducers(List>> resultProducers) { + final boolean needsDomainResults = domainResults != null && contributesToTopLevelSelectClause(); + final boolean collectDomainResults = collectDomainResults( needsDomainResults ); + resultProducers.forEach( + entry -> { + // this currentSqlSelectionCollector().next() is meant solely for resolving + // literal reference to a selection-item in the order-by or group-by clause. + // in the case of `DynamicInstantiation`, that ordering should ignore that + // level here. Visiting the dynamic-instantiation will manage this for its + // arguments + final DomainResultProducer domainResultProducer = entry.getValue(); + if ( !( domainResultProducer instanceof DynamicInstantiation ) ) { + currentSqlSelectionCollector().next(); + } + + if ( collectDomainResults ) { + domainResults.add( domainResultProducer.createDomainResult( entry.getKey(), this ) ); + } + else if ( needsDomainResults ) { + // We just create domain results for the purpose of creating selections + // This is necessary for top-level query specs within query groups to avoid cycles + domainResultProducer.createDomainResult( entry.getKey(), this ); + } + else { + domainResultProducer.applySqlSelections( this ); + } + } + ); + } + + private boolean collectDomainResults(boolean needsDomainResults) { + final Stack processingStateStack = getProcessingStateStack(); + if ( processingStateStack.depth() == 1 ) { + return needsDomainResults; + } + else { + final SqlAstProcessingState current = processingStateStack.getCurrent(); + // Since we only want to create domain results for the first/left-most query spec within query groups, + // we have to check if the current query spec is the left-most. + // This is the case when all upper level in-flight query groups are still empty + return needsDomainResults + && processingStateStack.findCurrentFirstWithParameter( current, BaseSqmToSqlAstConverter::stackMatchHelper ) == null; + } + } + + private List>> resultProducers(SqmSelection sqmSelection) { final SqmSelectableNode selectionNode = sqmSelection.getSelectableNode(); - if ( selectionNode instanceof SqmJpaCompoundSelection ) { - final SqmJpaCompoundSelection selectableNode = (SqmJpaCompoundSelection) selectionNode; - resultProducers = new ArrayList<>( selectableNode.getSelectionItems().size() ); + if ( selectionNode instanceof SqmJpaCompoundSelection selectableNode ) { + final List>> resultProducers = + new ArrayList<>( selectableNode.getSelectionItems().size() ); for ( SqmSelectableNode selectionItem : selectableNode.getSelectionItems() ) { - if ( selectionItem instanceof SqmPath ) { - prepareForSelection( (SqmPath) selectionItem ); + if ( selectionItem instanceof SqmPath path ) { + prepareForSelection( path ); } resultProducers.add( new AbstractMap.SimpleEntry<>( @@ -2273,83 +2320,27 @@ public abstract class BaseSqmToSqlAstConverter extends Base ) ); } + return resultProducers; } else { - if ( selectionNode instanceof SqmPath ) { - prepareForSelection( (SqmPath) selectionNode ); + if ( selectionNode instanceof SqmPath path ) { + prepareForSelection( path ); } - resultProducers = singletonList( + return singletonList( new AbstractMap.SimpleEntry<>( sqmSelection.getAlias(), (DomainResultProducer) selectionNode.accept( this ) ) ); } - - final Stack processingStateStack = getProcessingStateStack(); - final boolean needsDomainResults = domainResults != null && contributesToTopLevelSelectClause; - final boolean collectDomainResults; - if ( processingStateStack.depth() == 1 ) { - collectDomainResults = needsDomainResults; - } - else { - final SqlAstProcessingState current = processingStateStack.getCurrent(); - // Since we only want to create domain results for the first/left-most query spec within query groups, - // we have to check if the current query spec is the left-most. - // This is the case when all upper level in-flight query groups are still empty - collectDomainResults = needsDomainResults && processingStateStack.findCurrentFirstWithParameter( current, BaseSqmToSqlAstConverter::stackMatchHelper ) == null; - } - // this `currentSqlSelectionCollector().next()` is meant solely for resolving - // literal reference to a selection-item in the order-by or group-by clause. - // in the case of `DynamicInstantiation`, that ordering should ignore that - // level here. visiting the dynamic-instantiation will manage this for its - // arguments - if ( collectDomainResults ) { - resultProducers.forEach( - entry -> { - if ( !( entry.getValue() instanceof DynamicInstantiation ) ) { - currentSqlSelectionCollector().next(); - } - domainResults.add( entry.getValue().createDomainResult( entry.getKey(), this ) ); - } - ); - } - else if ( needsDomainResults ) { - // We just create domain results for the purpose of creating selections - // This is necessary for top-level query specs within query groups to avoid cycles - resultProducers.forEach( - entry -> { - if ( !( entry.getValue() instanceof DynamicInstantiation ) ) { - currentSqlSelectionCollector().next(); - } - entry.getValue().createDomainResult( entry.getKey(), this ); - } - ); - } - else { - resultProducers.forEach( - entry -> { - if ( !( entry.getValue() instanceof DynamicInstantiation ) ) { - currentSqlSelectionCollector().next(); - } - entry.getValue().applySqlSelections( this ); - } - ); - } - if ( inferTargetPath ) { - inferrableTypeAccessStack.pop(); - } - return null; } protected Expression resolveGroupOrOrderByExpression(SqmExpression groupByClauseExpression) { final int sqmPosition; final NavigablePath path; - if ( groupByClauseExpression instanceof SqmAliasedNodeRef ) { - final SqmAliasedNodeRef aliasedNodeRef = (SqmAliasedNodeRef) groupByClauseExpression; - final int aliasedNodeOrdinal = aliasedNodeRef.getPosition(); - sqmPosition = aliasedNodeOrdinal - 1; + if ( groupByClauseExpression instanceof SqmAliasedNodeRef aliasedNodeRef ) { + sqmPosition = aliasedNodeRef.getPosition() - 1; path = aliasedNodeRef.getNavigablePath(); } else if ( statement.getQuerySource() == SqmQuerySource.CRITERIA && currentClauseStack.getCurrent() != Clause.OVER ) { @@ -2370,66 +2361,64 @@ public abstract class BaseSqmToSqlAstConverter extends Base sqmPosition = -1; path = null; } - if ( sqmPosition != -1 ) { - final List selections; - if ( path == null ) { - selections = currentSqlSelectionCollector().getSelections( sqmPosition ); - } - else { - selections = trackedFetchSelectionsForGroup.get( path ).getValue(); - } - assert selections != null : String.format( Locale.ROOT, "No SqlSelections for SQM position `%s`", sqmPosition ); - final List expressions = new ArrayList<>( selections.size() ); - OUTER: for ( int i = 0; i < selections.size(); i++ ) { - final SqlSelection selection = selections.get( i ); - // We skip duplicate selections which can occur when grouping/ordering by an entity alias. - // Duplication happens because the primary key of an entity usually acts as FK target of collections - // which is, just like the identifier itself, also registered as selection - for ( int j = 0; j < i; j++ ) { - if ( selections.get( j ) == selection ) { - continue OUTER; - } - } - if ( currentSqmQueryPart instanceof SqmQueryGroup ) { - // Reusing the SqlSelection for query groups would be wrong because the aliases do no exist - // So we have to use a literal expression in a new SqlSelection instance to refer to the position - expressions.add( - new SqlSelectionExpression( - new SqlSelectionImpl( - selection.getJdbcResultSetIndex(), - selection.getValuesArrayPosition(), - new QueryLiteral<>( - selection.getValuesArrayPosition(), - basicType( Integer.class ) - ), - false - ) - ) - ); - } - else { - expressions.add( new SqlSelectionExpression( selection ) ); - } - } + return sqmPosition != -1 + ? selectionExpressions( path, sqmPosition ) + : (Expression) groupByClauseExpression.accept( this ); + } - if ( expressions.size() == 1 ) { - return expressions.get( 0 ); + private Expression selectionExpressions(NavigablePath path, int sqmPosition) { + final List selections = + path == null + ? currentSqlSelectionCollector().getSelections( sqmPosition ) + : trackedFetchSelectionsForGroup.get( path ).getValue(); + assert selections != null + : String.format( Locale.ROOT, "No SqlSelections for SQM position `%s`", sqmPosition ); + final List expressions = new ArrayList<>( selections.size() ); + for ( int i = 0; i < selections.size(); i++ ) { + final SqlSelectionExpression selectionExpression = selectionExpression( selections, i ); + if ( selectionExpression != null ) { + expressions.add( selectionExpression ); } - - return new SqlTuple( expressions, null ); } + return expressions.size() == 1 + ? expressions.get( 0 ) + : new SqlTuple( expressions, null ); + } - return (Expression) groupByClauseExpression.accept( this ); + private SqlSelectionExpression selectionExpression(List selections, int i) { + final SqlSelection selection = selections.get( i ); + // We skip duplicate selections which can occur when grouping/ordering by an entity alias. + // Duplication happens because the primary key of an entity usually acts as FK target of collections + // which is, just like the identifier itself, also registered as selection + for ( int j = 0; j < i; j++ ) { + if ( selections.get( j ) == selection ) { + return null; + } + } + return currentSqmQueryPart instanceof SqmQueryGroup + // Reusing the SqlSelection for query groups would be wrong because the aliases do no exist + // So we have to use a literal expression in a new SqlSelection instance to refer to the position + ? sqlSelectionExpression( selection ) + : new SqlSelectionExpression( selection ); + } + + private SqlSelectionExpression sqlSelectionExpression(SqlSelection selection) { + return new SqlSelectionExpression( + new SqlSelectionImpl( + selection.getJdbcResultSetIndex(), + selection.getValuesArrayPosition(), + new QueryLiteral<>( + selection.getValuesArrayPosition(), + basicType( Integer.class ) + ), + false + ) + ); } private int indexOfExpression(List> selections, SqmExpression node) { final int result = indexOfExpression( 0, selections, node ); - if ( result < 0 ) { - return -1; - } - else { - return result; - } + return result < 0 ? -1 : result; } private int indexOfExpression(int offset, List> selections, SqmExpression node) { @@ -2438,10 +2427,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base // Encoding this into the integer allows us to avoid some kind of mutable state to handle size/offset for ( int i = 0; i < selections.size(); i++ ) { final SqmSelectableNode selectableNode = selections.get( i ).getSelectableNode(); - if ( selectableNode instanceof SqmDynamicInstantiation ) { + if ( selectableNode instanceof SqmDynamicInstantiation dynamicInstantiation ) { final int subResult = indexOfExpression( offset + i, - ( (SqmDynamicInstantiation) selectableNode ).getArguments(), + dynamicInstantiation.getArguments(), node ); if ( subResult >= 0 ) { @@ -2449,8 +2438,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base } offset = -subResult - i; } - else if ( selectableNode instanceof SqmJpaCompoundSelection ) { - final List> selectionItems = ( (SqmJpaCompoundSelection) selectableNode ).getSelectionItems(); + else if ( selectableNode instanceof SqmJpaCompoundSelection compoundSelection ) { + final List> selectionItems = compoundSelection.getSelectionItems(); for ( int j = 0; j < selectionItems.size(); j++ ) { if ( selectionItems.get( j ) == node ) { return offset + i + j; @@ -2484,7 +2473,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base currentClauseStack.pop(); } } - return Collections.emptyList(); + return emptyList(); } @Override @@ -2629,8 +2618,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base throw new InterpretationException( "Access to from node '" + from.getCorrelationParent() + "' is not possible in from-clause subqueries, unless the 'lateral' keyword is used for the subquery!" ); } final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() ); - if ( parentTableGroup instanceof PluralTableGroup ) { - final PluralTableGroup pluralTableGroup = (PluralTableGroup) parentTableGroup; + if ( parentTableGroup instanceof PluralTableGroup pluralTableGroup ) { final CorrelatedPluralTableGroup correlatedPluralTableGroup = new CorrelatedPluralTableGroup( parentTableGroup, sqlAliasBase, @@ -2777,8 +2765,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } final SqlAstQueryNodeProcessingState currentQueryNodeProcessingState = currentQueryNodeProcessingState(); final TableGroup tableGroup; - if ( sqmRoot instanceof SqmDerivedRoot ) { - final SqmDerivedRoot derivedRoot = (SqmDerivedRoot) sqmRoot; + if ( sqmRoot instanceof SqmDerivedRoot derivedRoot ) { // Temporarily push an empty FromClauseIndex to disallow access to aliases from the top query // Only lateral subqueries are allowed to see the aliases fromClauseIndexStack.push( new FromClauseIndex( null ) ); @@ -2808,7 +2795,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base creationContext.getSessionFactory() ); } - else if ( sqmRoot instanceof SqmFunctionRoot functionRoot ) { + else if ( sqmRoot instanceof SqmFunctionRoot functionRoot ) { tableGroup = createFunctionTableGroup( functionRoot.getFunction(), functionRoot.getNavigablePath(), @@ -2818,8 +2805,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base functionRoot.getReusablePath( CollectionPart.Nature.INDEX.getName() ) != null ); } - else if ( sqmRoot instanceof SqmCteRoot ) { - final SqmCteRoot cteRoot = (SqmCteRoot) sqmRoot; + else if ( sqmRoot instanceof SqmCteRoot cteRoot ) { tableGroup = createCteTableGroup( getCteName( cteRoot.getCte().getCteTable() ), cteRoot.getNavigablePath(), @@ -3023,14 +3009,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base } final SqmPathSource resolvedModel; if ( !( sqmPath instanceof SqmTreatedPath ) - && tableGroup.getModelPart().getPartMappingType() instanceof EntityMappingType + && tableGroup.getModelPart().getPartMappingType() instanceof EntityMappingType entityType && ( resolvedModel = sqmPath.getResolvedModel() ) instanceof PersistentAttribute ) { final String attributeName = resolvedModel.getPathName(); - final EntityMappingType entityType = (EntityMappingType) tableGroup.getModelPart().getPartMappingType(); final EntityMappingType parentType; - if ( parentPath instanceof SqmTreatedPath ) { + if ( parentPath instanceof SqmTreatedPath treatedPath ) { // A treated attribute usage i.e. `treat(alias as Subtype).attribute = 1` - final ManagedDomainType treatTarget = ( (SqmTreatedPath) parentPath ).getTreatTarget(); + final ManagedDomainType treatTarget = treatedPath.getTreatTarget(); if ( treatTarget.getPersistenceType() == ENTITY ) { parentType = creationContext.getMappingMetamodel().getEntityDescriptor( treatTarget.getTypeName() ); @@ -3145,7 +3130,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } else { - persister = (EntityPersister) creationContext.getSessionFactory() + persister = creationContext.getSessionFactory() .getMappingMetamodel() .findEntityDescriptor( treatTargetTypeName ); if ( persister == null || !persister.getEntityMetamodel().isPolymorphic() ) { @@ -3228,18 +3213,12 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private boolean contextAllowsTreatOrFilterEntityNameUse() { - final Clause currentClause = getCurrentClauseStack().getCurrent(); - switch ( currentClause ) { - case SET: - case FROM: - case GROUP: - case HAVING: - case WHERE: - // A TREAT or FILTER EntityNameUse is only allowed in these clauses, - // but only if it's not in a nested context - return !inNestedContext; - } - return false; + return switch ( getCurrentClauseStack().getCurrent() ) { + // A TREAT or FILTER EntityNameUse is only allowed in these clauses, + // but only if it's not in a nested context + case SET, FROM, GROUP, HAVING, WHERE -> !inNestedContext; + default -> false; + }; } protected void registerTypeUsage(DiscriminatorSqmPath path) { @@ -3259,8 +3238,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base // The OR junction allows to create a union of entity name lists of all sub-predicates // The AND junction allows to create an intersection of entity name lists of all sub-predicates final MappingType partMappingType = tableGroup.getModelPart().getPartMappingType(); - if ( partMappingType instanceof EntityMappingType ) { - final EntityMappingType mappingType = (EntityMappingType) partMappingType; + if ( partMappingType instanceof EntityMappingType mappingType ) { final EntityPersister persister = mappingType.getEntityPersister(); // Avoid resolving subclass tables for persisters with physical discriminators as we won't need them if ( persister.getDiscriminatorMapping().hasPhysicalColumn() ) { @@ -3338,26 +3316,26 @@ public abstract class BaseSqmToSqlAstConverter extends Base TableGroup lhsTableGroup, TableGroup ownerTableGroup, boolean transitive) { - if ( sqmJoin instanceof SqmAttributeJoin ) { - return consumeAttributeJoin( ( (SqmAttributeJoin) sqmJoin ), lhsTableGroup, ownerTableGroup, transitive ); + if ( sqmJoin instanceof SqmAttributeJoin attributeJoin ) { + return consumeAttributeJoin( attributeJoin, lhsTableGroup, ownerTableGroup, transitive ); } - else if ( sqmJoin instanceof SqmCrossJoin ) { - return consumeCrossJoin( ( (SqmCrossJoin) sqmJoin ), lhsTableGroup, transitive ); + else if ( sqmJoin instanceof SqmCrossJoin crossJoin ) { + return consumeCrossJoin( crossJoin, lhsTableGroup, transitive ); } - else if ( sqmJoin instanceof SqmEntityJoin ) { - return consumeEntityJoin( ( (SqmEntityJoin) sqmJoin ), lhsTableGroup, transitive ); + else if ( sqmJoin instanceof SqmEntityJoin entityJoin ) { + return consumeEntityJoin( entityJoin, lhsTableGroup, transitive ); } - else if ( sqmJoin instanceof SqmDerivedJoin ) { - return consumeDerivedJoin( ( (SqmDerivedJoin) sqmJoin ), lhsTableGroup, transitive ); + else if ( sqmJoin instanceof SqmDerivedJoin derivedJoin ) { + return consumeDerivedJoin( derivedJoin, lhsTableGroup, transitive ); } else if ( sqmJoin instanceof SqmFunctionJoin functionJoin ) { return consumeFunctionJoin( functionJoin, lhsTableGroup, transitive ); } - else if ( sqmJoin instanceof SqmCteJoin ) { - return consumeCteJoin( ( (SqmCteJoin) sqmJoin ), lhsTableGroup, transitive ); + else if ( sqmJoin instanceof SqmCteJoin cteJoin ) { + return consumeCteJoin( cteJoin, lhsTableGroup, transitive ); } - else if ( sqmJoin instanceof SqmPluralPartJoin ) { - return consumePluralPartJoin( ( (SqmPluralPartJoin) sqmJoin ), ownerTableGroup, transitive ); + else if ( sqmJoin instanceof SqmPluralPartJoin pluralPartJoin ) { + return consumePluralPartJoin( pluralPartJoin, ownerTableGroup, transitive ); } else { throw new InterpretationException( "Could not resolve SqmJoin [" + sqmJoin.getNavigablePath() + "] to TableGroupJoin" ); @@ -3369,10 +3347,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base // but if the SqmPath is a SqmPluralPartJoin e.g. `join key(mapAlias) k` // or the SqmPath is a simple path for the key e.g. `select key(mapAlias)`, // then we want to return the PluralTableGroup instead - if ( lhsTableGroup instanceof PluralTableGroup + if ( lhsTableGroup instanceof PluralTableGroup pluralTableGroup && !( path instanceof SqmPluralPartJoin ) && CollectionPart.Nature.fromNameExact( path.getNavigablePath().getLocalName() ) == null ) { - final TableGroup elementTableGroup = ( (PluralTableGroup) lhsTableGroup ).getElementTableGroup(); + final TableGroup elementTableGroup = pluralTableGroup.getElementTableGroup(); // The element table group could be null for basic collections if ( elementTableGroup != null ) { return elementTableGroup; @@ -3455,11 +3433,12 @@ public abstract class BaseSqmToSqlAstConverter extends Base registerEntityNameProjectionUsage( sqmJoin, getActualTableGroup( joinedTableGroup, sqmJoin ) ); } registerPathAttributeEntityNameUsage( sqmJoin, ownerTableGroup ); - if ( !sqmJoin.hasTreats() && sqmJoin.getNodeType().getSqmPathType() instanceof EntityDomainType ) { - final EntityDomainType entityDomainType = (EntityDomainType) sqmJoin.getNodeType().getSqmPathType(); - final TableGroup elementTableGroup = joinedTableGroup instanceof PluralTableGroup ? - ( (PluralTableGroup) joinedTableGroup ).getElementTableGroup() : - joinedTableGroup; + if ( !sqmJoin.hasTreats() + && sqmJoin.getNodeType().getSqmPathType() instanceof EntityDomainType entityDomainType ) { + final TableGroup elementTableGroup = + joinedTableGroup instanceof PluralTableGroup pluralTableGroup + ? pluralTableGroup.getElementTableGroup() + : joinedTableGroup; final EntityValuedModelPart entityModelPart = (EntityValuedModelPart) elementTableGroup.getModelPart(); final EntityPersister entityDescriptor = entityModelPart.getEntityMappingType().getEntityPersister(); if ( entityDescriptor.getSuperMappingType() != null ) { @@ -3501,8 +3480,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base joinForPredicate = TableGroupJoinHelper.determineJoinForPredicateApply( joinedTableGroupJoin ); } // Since joins on treated paths will never cause table pruning, we need to add a join condition for the treat - if ( sqmJoin.getLhs() instanceof SqmTreatedPath ) { - final SqmTreatedPath treatedPath = (SqmTreatedPath) sqmJoin.getLhs(); + if ( sqmJoin.getLhs() instanceof SqmTreatedPath treatedPath ) { final ManagedDomainType treatTarget = treatedPath.getTreatTarget(); if ( treatTarget.getPersistenceType() == ENTITY ) { joinForPredicate.applyPredicate( @@ -3824,8 +3802,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base treated = false; } if ( parentPath == null ) { - if ( sqmPath instanceof SqmFunctionPath ) { - final SqmFunctionPath functionPath = (SqmFunctionPath) sqmPath; + if ( sqmPath instanceof SqmFunctionPath functionPath ) { if ( functionPath.getReferencedPathSource() instanceof CompositeSqmPathSource ) { return (TableGroup) visitFunctionPath( functionPath ); } @@ -3892,13 +3869,12 @@ public abstract class BaseSqmToSqlAstConverter extends Base && parentPath instanceof SqmSimplePath && CollectionPart.Nature.fromName( parentPath.getNavigablePath().getLocalName() ) == null && parentPath.getParentPath() != null - && parentTableGroup.getModelPart() instanceof ToOneAttributeMapping ) { + && parentTableGroup.getModelPart() instanceof ToOneAttributeMapping toOneAttributeMapping ) { // we need to handle the case of an implicit path involving a to-one // association that path has been previously joined using left. // typically, this indicates that the to-one is being // fetched - the fetch would use a left-join. however, since the path is // used outside the select-clause also, we need to force the join to be inner - final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) parentTableGroup.getModelPart(); final String partName = sqmPath.getResolvedModel().getPathName(); final ModelPart pathPart; if ( !toOneAttributeMapping.isFkOptimizationAllowed() @@ -3972,8 +3948,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); final TableGroup tableGroup; - if ( subPart instanceof TableGroupJoinProducer ) { - final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) subPart; + if ( subPart instanceof TableGroupJoinProducer joinProducer ) { if ( fromClauseIndex.findTableGroupOnCurrentFromClause( parentTableGroup.getNavigablePath() ) == null && !isRecursiveCte( parentTableGroup ) ) { final SqlAstQueryNodeProcessingState queryNodeProcessingState = currentQueryNodeProcessingState(); @@ -4051,8 +4026,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private boolean isMappedByOrNotFoundToOne(TableGroupJoinProducer joinProducer) { - if ( joinProducer instanceof ToOneAttributeMapping ) { - final ToOneAttributeMapping toOne = (ToOneAttributeMapping) joinProducer; + if ( joinProducer instanceof ToOneAttributeMapping toOne ) { return toOne.hasNotFoundAction() || // ToOne( mappedBy = "..." ) always has a referenced property name and is the target side ( toOne.getReferencedPropertyName() != null && toOne.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ); @@ -4061,8 +4035,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private boolean isRecursiveCte(TableGroup tableGroup) { - if ( tableGroup instanceof CteTableGroup ) { - final CteTableGroup cteTableGroup = (CteTableGroup) tableGroup; + if ( tableGroup instanceof CteTableGroup cteTableGroup ) { return cteContainer.getCteStatement( cteTableGroup.getPrimaryTableReference().getTableId() ).isRecursive(); } return false; @@ -4073,8 +4046,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private void registerPluralTableGroupParts(NavigablePath navigablePath, TableGroup tableGroup) { - if ( tableGroup instanceof PluralTableGroup ) { - final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup; + if ( tableGroup instanceof PluralTableGroup pluralTableGroup ) { if ( pluralTableGroup.getElementTableGroup() != null ) { getFromClauseAccess().registerTableGroup( navigablePath == null || navigablePath == tableGroup.getNavigablePath() @@ -4129,7 +4101,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } @Override - public Object visitRootFunction(SqmFunctionRoot sqmRoot) { + public Object visitRootFunction(SqmFunctionRoot sqmRoot) { final TableGroup resolved = getFromClauseAccess().findTableGroup( sqmRoot.getNavigablePath() ); if ( resolved != null ) { log.tracef( "SqmFunctionRoot [%s] resolved to existing TableGroup [%s]", sqmRoot, resolved ); @@ -4249,8 +4221,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ModelPart actualModelPart, SqmPath path) { final Expression result; - if ( actualModelPart instanceof EntityValuedModelPart ) { - final EntityValuedModelPart entityValuedModelPart = (EntityValuedModelPart) actualModelPart; + if ( actualModelPart instanceof EntityValuedModelPart entityValuedModelPart ) { final EntityValuedModelPart inferredEntityMapping = (EntityValuedModelPart) getInferredValueMapping(); final ModelPart resultModelPart; final EntityValuedModelPart interpretationModelPart; @@ -4286,10 +4257,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base interpretationModelPart = entityValuedModelPart; tableGroupToUse = null; } - else if ( inferredEntityMapping instanceof ToOneAttributeMapping ) { + else if ( inferredEntityMapping instanceof ToOneAttributeMapping toOneAttributeMapping ) { // If the inferred mapping is a to-one association, // we use the FK target part, which must be located on the entity mapping - final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) inferredEntityMapping; final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart( toOneAttributeMapping.getSideNature().inverse() ); @@ -4304,9 +4274,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base interpretationModelPart = toOneAttributeMapping; tableGroupToUse = null; } - else if ( inferredEntityMapping instanceof EntityCollectionPart ) { + else if ( inferredEntityMapping instanceof EntityCollectionPart collectionPart ) { // If the inferred mapping is a collection part, we try to make use of the FK again to avoid joins - final EntityCollectionPart collectionPart = (EntityCollectionPart) inferredEntityMapping; // If the inferred mapping is a collection part, we try to make use of the FK again to avoid joins if ( tableGroup.getModelPart() instanceof CollectionPart ) { @@ -4327,9 +4296,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base // When we compare the same collection parts, we can just use the FK part resultModelPart = manyToManyPart.getForeignKeyDescriptor().getKeyPart(); } - else if ( entityValuedModelPart instanceof EntityAssociationMapping ) { + else if ( entityValuedModelPart instanceof EntityAssociationMapping tableGroupAssociation ) { // If the table group model part is an association, we check if the FK targets are compatible - final EntityAssociationMapping tableGroupAssociation = (EntityAssociationMapping) entityValuedModelPart; final ModelPart pathTargetPart = tableGroupAssociation .getForeignKeyDescriptor() .getPart( tableGroupAssociation.getSideNature().inverse() ); @@ -4406,8 +4374,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base this ); } - else if ( actualModelPart instanceof EmbeddableValuedModelPart ) { - final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) actualModelPart; + else if ( actualModelPart instanceof EmbeddableValuedModelPart mapping ) { result = new EmbeddableValuedPathInterpretation<>( mapping.toSqlExpression( findTableGroup( navigablePath.getParent() ), @@ -4915,8 +4882,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private void registerProjectionUsageFromDescriptor(TableGroup tableGroup, CollectionPart descriptor) { - if ( descriptor instanceof EntityCollectionPart ) { - final EntityCollectionPart entityCollectionPart = (EntityCollectionPart) descriptor; + if ( descriptor instanceof EntityCollectionPart entityCollectionPart ) { final EntityMappingType entityMappingType = entityCollectionPart.getEntityMappingType(); registerEntityNameUsage( tableGroup, EntityNameUse.PROJECTION, entityMappingType.getEntityName(), true ); } @@ -4972,8 +4938,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ? pluralAttributeMapping.getIndexDescriptor() : pluralAttributeMapping.getElementDescriptor(); final ModelPart modelPart; - if ( collectionPart instanceof OneToManyCollectionPart ) { - final OneToManyCollectionPart toManyPart = (OneToManyCollectionPart) collectionPart; + if ( collectionPart instanceof OneToManyCollectionPart toManyPart ) { modelPart = toManyPart.getAssociatedEntityMappingType().getIdentifierMapping(); // modelPart = pluralAttributeMapping.getKeyDescriptor().getTargetPart(); } @@ -5060,8 +5025,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ? pluralAttributeMapping.getIndexDescriptor() : pluralAttributeMapping.getElementDescriptor(); final ModelPart modelPart; - if ( collectionPart instanceof OneToManyCollectionPart ) { - final OneToManyCollectionPart toManyPart = (OneToManyCollectionPart) collectionPart; + if ( collectionPart instanceof OneToManyCollectionPart toManyPart ) { modelPart = toManyPart.getAssociatedEntityMappingType().getIdentifierMapping(); // modelPart = pluralAttributeMapping.getKeyDescriptor().getTargetPart(); } @@ -5336,8 +5300,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base else { lhs = path.getLhs(); } - if ( lhs instanceof SqmTreatedPath ) { - final SqmTreatedPath treatedPath = (SqmTreatedPath) lhs; + if ( lhs instanceof SqmTreatedPath treatedPath ) { final ManagedDomainType treatTarget = treatedPath.getTreatTarget(); final Class treatTargetJavaType = treatTarget.getJavaType(); final SqmPath wrappedPath = treatedPath.getWrappedPath(); @@ -5416,17 +5379,16 @@ public abstract class BaseSqmToSqlAstConverter extends Base final Set typeNames; final EntityMappingType entityMapping; final EmbeddableMappingType embeddableMapping; - if ( modelPart instanceof PluralAttributeMapping ) { - entityMapping = (EntityMappingType) ( (PluralAttributeMapping) modelPart ).getElementDescriptor() - .getPartMappingType(); + if ( modelPart instanceof PluralAttributeMapping pluralAttributeMapping ) { + entityMapping = (EntityMappingType) pluralAttributeMapping.getElementDescriptor().getPartMappingType(); embeddableMapping = null; } - else if ( modelPart instanceof EntityValuedModelPart ) { - entityMapping = ( (EntityValuedModelPart) modelPart ).getEntityMappingType(); + else if ( modelPart instanceof EntityValuedModelPart entityValuedModelPart ) { + entityMapping = entityValuedModelPart.getEntityMappingType(); embeddableMapping = null; } - else if ( modelPart instanceof EmbeddableValuedModelPart ) { - embeddableMapping = ( (EmbeddableValuedModelPart) modelPart ).getEmbeddableTypeDescriptor(); + else if ( modelPart instanceof EmbeddableValuedModelPart embeddableValuedModelPart ) { + embeddableMapping = embeddableValuedModelPart.getEmbeddableTypeDescriptor(); entityMapping = null; } else { @@ -5581,7 +5543,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base private Predicate createTreatTypeRestriction(SqmPath lhs, Set subclassEntityNames) { // Do what visitSelfInterpretingSqmPath does, except for calling preparingReusablePath // as that would register a type usage for the table group that we don't want here - final EntityDiscriminatorSqmPath discriminatorSqmPath = (EntityDiscriminatorSqmPath) lhs.type(); + final EntityDiscriminatorSqmPath discriminatorSqmPath = (EntityDiscriminatorSqmPath) lhs.type(); registerTypeUsage( discriminatorSqmPath ); return createTreatTypeRestriction( DiscriminatorPathInterpretation.from( discriminatorSqmPath, this ), @@ -5669,29 +5631,94 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public Expression visitLiteral(SqmLiteral literal) { - if ( literal instanceof SqmLiteralNull ) { - MappingModelExpressible mappingModelExpressible = resolveInferredType(); - if ( mappingModelExpressible == null ) { - mappingModelExpressible = determineCurrentExpressible( literal ); + if ( literal instanceof SqmLiteralNull sqmLiteralNull ) { + return nullLiteral( sqmLiteralNull ); + } + else { + final MappingModelExpressible inferableExpressible = getInferredValueMapping(); + + if ( inferableExpressible instanceof DiscriminatorMapping discriminatorMapping ) { + return entityTypeLiteral( literal, discriminatorMapping ); } - if ( mappingModelExpressible instanceof BasicValuedMapping ) { - return new QueryLiteral<>( null, (BasicValuedMapping) mappingModelExpressible ); - } - else if ( mappingModelExpressible instanceof EntityMappingType ) { - // When selecting a literal null entity type, we should simply return a single null object - mappingModelExpressible = null; - } - final MappingModelExpressible keyExpressible = getKeyExpressible( mappingModelExpressible ); - if ( keyExpressible == null ) { - // treat Void as the bottom type, the class of null - return new QueryLiteral<>( null, BottomType.INSTANCE ); + else if ( inferableExpressible instanceof BasicValuedMapping basicValuedMapping ) { + @SuppressWarnings("rawtypes") + final BasicValueConverter valueConverter = basicValuedMapping.getJdbcMapping().getValueConverter(); + if ( valueConverter != null ) { + return queryLiteral( literal, basicValuedMapping, valueConverter ); + } } + final MappingModelExpressible expressible = literalExpressible( literal, inferableExpressible ); + if ( expressible instanceof EntityIdentifierMapping identifierMapping + //TODO: remove test against impl class EntityTypeImpl + && literal.getNodeType() instanceof EntityTypeImpl ) { + return new QueryLiteral<>( identifierMapping.getIdentifier( literal.getLiteralValue() ), + (BasicValuedMapping) expressible ); + } + else if ( expressible instanceof BasicValuedMapping mapping ) { + return new QueryLiteral<>( literal.getLiteralValue(), mapping ); + } + // Handling other values might seem unnecessary, but with JPA Criteria it is totally possible to have such literals + else if ( expressible instanceof EmbeddableValuedModelPart ) { + return sqlTuple( expressible, literal.getLiteralValue() ); + } + else if ( expressible instanceof EntityValuedModelPart entityValuedModelPart ) { + return associationLiteral( literal, entityValuedModelPart ); + } + else { + final BasicSqmPathSource nodeType = (BasicSqmPathSource) literal.getNodeType(); + return new QueryLiteral<>( + literal.getLiteralValue(), + creationContext.getSessionFactory().getTypeConfiguration() + .getBasicTypeRegistry() + .getRegisteredType( nodeType.getSqmPathType().getJavaType().getName() ) + ); + } + } + } + + private MappingModelExpressible literalExpressible(SqmLiteral literal, MappingModelExpressible inferableExpressible) { + final MappingModelExpressible localExpressible = + resolveMappingModelExpressible( literal, + creationContext.getSessionFactory().getMappingMetamodel(), + getFromClauseAccess() == null ? null : getFromClauseAccess()::findTableGroup ); + if ( localExpressible == null ) { + return getElementExpressible( inferableExpressible ); + } + else { + final MappingModelExpressible elementExpressible = getElementExpressible( localExpressible ); + return elementExpressible instanceof BasicType basicType + ? resolveSqlTypeIndicators( this, basicType, literal.getJavaTypeDescriptor() ) + : elementExpressible; + } + } + + private Expression nullLiteral(SqmLiteralNull literal) { + final MappingModelExpressible inferredType = resolveInferredType(); + final MappingModelExpressible mappingModelExpressible = + inferredType == null + ? determineCurrentExpressible( literal ) + : inferredType; + if ( mappingModelExpressible instanceof BasicValuedMapping basicValuedMapping ) { + return new QueryLiteral<>( null, basicValuedMapping ); + } + else { + // When selecting a literal null entity type, we should simply return a single null object + return nullLiteral( mappingModelExpressible instanceof EntityMappingType ? null : mappingModelExpressible ); + } + } + + private Expression nullLiteral(MappingModelExpressible mappingModelExpressible) { + final MappingModelExpressible keyExpressible = getKeyExpressible( mappingModelExpressible ); + if ( keyExpressible == null ) { + // treat Void as the bottom type, the class of null + return new QueryLiteral<>( null, BottomType.INSTANCE ); + } + else { final List expressions = new ArrayList<>( keyExpressible.getJdbcTypeCount() ); - - if ( keyExpressible instanceof ModelPart ) { + if ( keyExpressible instanceof ModelPart modelPart ) { // Use the selectable mappings as BasicValuedMapping if possible to retain column definition info for possible casts - ( (ModelPart) keyExpressible ).forEachSelectable( + modelPart.forEachSelectable( (index, selectableMapping) -> expressions.add( new QueryLiteral<>( null, @@ -5709,241 +5736,84 @@ public abstract class BaseSqmToSqlAstConverter extends Base ) ); } - return new SqlTuple( expressions, mappingModelExpressible); + return new SqlTuple( expressions, mappingModelExpressible ); } + } - final MappingModelExpressible inferableExpressible = getInferredValueMapping(); - - if ( inferableExpressible instanceof DiscriminatorMapping ) { - final MappingMetamodelImplementor mappingMetamodel = creationContext.getSessionFactory().getMappingMetamodel(); - final Object literalValue = literal.getLiteralValue(); - final EntityPersister entityDescriptor; - if ( literalValue instanceof Class ) { - entityDescriptor = mappingMetamodel.findEntityDescriptor( (Class) literalValue ); + private static Expression associationLiteral(SqmLiteral literal, EntityValuedModelPart entityValuedModelPart) { + final Object associationKey; + final ModelPart associationKeyPart; + if ( entityValuedModelPart instanceof EntityAssociationMapping association ) { + final ForeignKeyDescriptor foreignKeyDescriptor = association.getForeignKeyDescriptor(); + if ( association.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) { + // If the association is the target, we must use the identifier of the EntityMappingType + associationKey = association.getAssociatedEntityMappingType().getIdentifierMapping() + .getIdentifier( literal.getLiteralValue() ); + associationKeyPart = association.getAssociatedEntityMappingType().getIdentifierMapping(); } else { - final DiscriminatorMapping discriminatorMapping = (DiscriminatorMapping) inferableExpressible; - //noinspection unchecked - final DiscriminatorConverter valueConverter = (DiscriminatorConverter) discriminatorMapping.getValueConverter(); - final DiscriminatorValueDetails discriminatorDetails; - if ( valueConverter.getDomainJavaType().isInstance( literalValue ) ) { - discriminatorDetails = valueConverter.getDetailsForDiscriminatorValue( literalValue ); - } - else if ( valueConverter.getRelationalJavaType().isInstance( literalValue ) ) { - discriminatorDetails = valueConverter.getDetailsForRelationalForm( literalValue ); - } - else { - // Special case when passing the discriminator value as e.g. string literal, - // but the expected relational type is Character. - // In this case, we use wrap to transform the value to the correct type - final Object relationalForm = valueConverter.getRelationalJavaType().wrap( - literalValue, - creationContext.getSessionFactory().getWrapperOptions() - ); - discriminatorDetails = valueConverter.getDetailsForRelationalForm( relationalForm ); - } - entityDescriptor = discriminatorDetails.getIndicatedEntity().getEntityPersister(); - } - return new EntityTypeLiteral( entityDescriptor ); - } - else if ( inferableExpressible instanceof BasicValuedMapping ) { - final BasicValuedMapping basicValuedMapping = (BasicValuedMapping) inferableExpressible; - final BasicValueConverter valueConverter = basicValuedMapping.getJdbcMapping().getValueConverter(); - if ( valueConverter != null ) { - final Object value = literal.getLiteralValue(); - final Object sqlLiteralValue; - // For converted query literals, we support both, the domain and relational java type - if ( value == null || valueConverter.getDomainJavaType().isInstance( value ) ) { - sqlLiteralValue = valueConverter.toRelationalValue( value ); - } - else if ( valueConverter.getRelationalJavaType().isInstance( value ) ) { - sqlLiteralValue = value; - } - else if ( Character.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) - && value instanceof CharSequence && ( (CharSequence) value ).length() == 1 ) { - sqlLiteralValue = ( (CharSequence) value ).charAt( 0 ); - } - // In HQL, number literals might not match the relational java type exactly, - // so we allow coercion between the number types - else if ( Number.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) - && value instanceof Number ) { - sqlLiteralValue = valueConverter.getRelationalJavaType().coerce( - value, - creationContext.getSessionFactory()::getTypeConfiguration - ); - } - else { - throw new SemanticException( - String.format( - Locale.ROOT, - "Literal type '%s' did not match domain type '%s' nor converted type '%s'", - value.getClass(), - valueConverter.getDomainJavaType().getJavaTypeClass().getName(), - valueConverter.getRelationalJavaType().getJavaTypeClass().getName() - ) - ); - } - return new QueryLiteral<>( sqlLiteralValue, basicValuedMapping ); - } - } - - - final MappingModelExpressible expressible; - final MappingModelExpressible localExpressible = SqmMappingModelHelper.resolveMappingModelExpressible( - literal, - creationContext.getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(), - getFromClauseAccess() == null ? null : getFromClauseAccess()::findTableGroup - ); - if ( localExpressible == null ) { - expressible = getElementExpressible( inferableExpressible ); - } - else { - final MappingModelExpressible elementExpressible = getElementExpressible( localExpressible ); - if ( elementExpressible instanceof BasicType ) { - expressible = InferredBasicValueResolver.resolveSqlTypeIndicators( - this, - (BasicType) elementExpressible, - literal.getJavaTypeDescriptor() - ); - } - else { - expressible = elementExpressible; - } - } - - if ( expressible instanceof EntityIdentifierMapping && literal.getNodeType() instanceof EntityTypeImpl ) { - return new QueryLiteral<>( - ( (EntityIdentifierMapping) expressible ).getIdentifier( literal.getLiteralValue() ), - (BasicValuedMapping) expressible - ); - } - - if ( expressible instanceof BasicValuedMapping ) { - return new QueryLiteral<>( - literal.getLiteralValue(), - (BasicValuedMapping) expressible - ); - } - // Handling other values might seem unnecessary, but with JPA Criteria it is totally possible to have such literals - if ( expressible instanceof EmbeddableValuedModelPart ) { - final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) expressible; - final List list = new ArrayList<>( embeddableValuedModelPart.getJdbcTypeCount() ); - embeddableValuedModelPart.forEachJdbcValue( - literal.getLiteralValue(), - list, - null, - (selectionIndex, expressions, noop, value, jdbcMapping) - -> expressions.add( new QueryLiteral<>( value, (BasicValuedMapping) jdbcMapping ) ), - null - ); - return new SqlTuple( list, expressible ); - } - else if ( expressible instanceof EntityValuedModelPart ) { - final EntityValuedModelPart entityValuedModelPart = (EntityValuedModelPart) expressible; - final Object associationKey; - final ModelPart associationKeyPart; - if ( entityValuedModelPart instanceof EntityAssociationMapping ) { - final EntityAssociationMapping association = (EntityAssociationMapping) entityValuedModelPart; - final ForeignKeyDescriptor foreignKeyDescriptor = association.getForeignKeyDescriptor(); - if ( association.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) { - // If the association is the target, we must use the identifier of the EntityMappingType - associationKey = association.getAssociatedEntityMappingType().getIdentifierMapping() - .getIdentifier( literal.getLiteralValue() ); - associationKeyPart = association.getAssociatedEntityMappingType().getIdentifierMapping(); - } - else { - associationKey = foreignKeyDescriptor.getAssociationKeyFromSide( - literal.getLiteralValue(), - association.getSideNature().inverse(), - null - ); - associationKeyPart = foreignKeyDescriptor.getPart( association.getSideNature() ); - } - } - else { - final EntityIdentifierMapping identifierMapping = entityValuedModelPart.getEntityMappingType() - .getIdentifierMapping(); - associationKeyPart = identifierMapping; - associationKey = identifierMapping.getIdentifier( literal.getLiteralValue() ); - } - if ( associationKeyPart instanceof BasicValuedMapping ) { - return new QueryLiteral<>( - associationKey, - (BasicValuedMapping) associationKeyPart - ); - } - else { - final List list = new ArrayList<>( associationKeyPart.getJdbcTypeCount() ); - associationKeyPart.forEachJdbcValue( - associationKey, - list, - null, - (selectionIndex, expressions, noop, value, jdbcMapping) - -> expressions.add( new QueryLiteral<>( value, (BasicValuedMapping) jdbcMapping ) ), + associationKey = foreignKeyDescriptor.getAssociationKeyFromSide( + literal.getLiteralValue(), + association.getSideNature().inverse(), null ); - return new SqlTuple( list, associationKeyPart ); + associationKeyPart = foreignKeyDescriptor.getPart( association.getSideNature() ); } } else { + final EntityIdentifierMapping identifierMapping = entityValuedModelPart.getEntityMappingType() + .getIdentifierMapping(); + associationKeyPart = identifierMapping; + associationKey = identifierMapping.getIdentifier( literal.getLiteralValue() ); + } + + if ( associationKeyPart instanceof BasicValuedMapping ) { return new QueryLiteral<>( - literal.getLiteralValue(), - creationContext.getSessionFactory() - .getTypeConfiguration() - .getBasicTypeRegistry() - .getRegisteredType( - ( (BasicSqmPathSource) literal.getNodeType() ).getSqmPathType() - .getJavaType() - .getName() - ) + associationKey, + (BasicValuedMapping) associationKeyPart ); } - } - - @Override - public Expression visitHqlNumericLiteral(SqmHqlNumericLiteral numericLiteral) { - final BasicValuedMapping inferredExpressible = (BasicValuedMapping) getInferredValueMapping(); - final BasicValuedMapping expressible; - if ( inferredExpressible == null ) { - expressible = (BasicValuedMapping) determineCurrentExpressible( numericLiteral ); - } else { - expressible = inferredExpressible; + return sqlTuple( associationKeyPart, associationKey ); } - - final JdbcMapping jdbcMapping = expressible.getJdbcMapping(); - if ( jdbcMapping.getValueConverter() != null ) { - // special case where we need to parse the value in order to apply the conversion - return handleConvertedUnparsedNumericLiteral( numericLiteral, expressible ); - } - - return new UnparsedNumericLiteral<>( - numericLiteral.getUnparsedLiteralValue(), - numericLiteral.getTypeCategory(), - jdbcMapping - ); } - private Expression handleConvertedUnparsedNumericLiteral( - SqmHqlNumericLiteral numericLiteral, - BasicValuedMapping expressible) { - //noinspection rawtypes - final BasicValueConverter valueConverter = expressible.getJdbcMapping().getValueConverter(); - assert valueConverter != null; + private static SqlTuple sqlTuple(MappingModelExpressible associationKeyPart, Object associationKey) { + final List list = new ArrayList<>( associationKeyPart.getJdbcTypeCount() ); + associationKeyPart.forEachJdbcValue( + associationKey, + list, + null, + (selectionIndex, expressions, noop, value, jdbcMapping) + -> expressions.add( new QueryLiteral<>( value, (BasicValuedMapping) jdbcMapping ) ), + null + ); + return new SqlTuple( list, associationKeyPart ); + } - final Number parsedValue = numericLiteral.getTypeCategory().parseLiteralValue( numericLiteral.getUnparsedLiteralValue() ); - final Object sqlLiteralValue; - if ( valueConverter.getDomainJavaType().isInstance( parsedValue ) ) { - //noinspection unchecked - sqlLiteralValue = valueConverter.toRelationalValue( parsedValue ); + private QueryLiteral queryLiteral( + SqmLiteral literal, BasicValuedMapping basicValuedMapping, BasicValueConverter valueConverter) { + return new QueryLiteral<>( sqlLiteralValue( valueConverter, literal.getLiteralValue() ), basicValuedMapping ); + } + + private Object sqlLiteralValue(BasicValueConverter valueConverter, D value) { + // For converted query literals, we support both, the domain and relational java type + if ( value == null || valueConverter.getDomainJavaType().isInstance( value ) ) { + return valueConverter.toRelationalValue( value ); } - else if ( valueConverter.getRelationalJavaType().isInstance( parsedValue ) ) { - sqlLiteralValue = parsedValue; + else if ( valueConverter.getRelationalJavaType().isInstance( value ) ) { + return value; } - else if ( Number.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) ) { - //noinspection unchecked - sqlLiteralValue = valueConverter.getRelationalJavaType().coerce( - parsedValue, + else if ( Character.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) + && value instanceof CharSequence charSequence && charSequence.length() == 1 ) { + return charSequence.charAt( 0 ); + } + // In HQL, number literals might not match the relational java type exactly, + // so we allow coercion between the number types + else if ( Number.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) + && value instanceof Number ) { + return valueConverter.getRelationalJavaType().coerce( + value, creationContext.getSessionFactory()::getTypeConfiguration ); } @@ -5952,32 +5822,95 @@ public abstract class BaseSqmToSqlAstConverter extends Base String.format( Locale.ROOT, "Literal type '%s' did not match domain type '%s' nor converted type '%s'", - parsedValue.getClass(), + value.getClass(), valueConverter.getDomainJavaType().getJavaTypeClass().getName(), valueConverter.getRelationalJavaType().getJavaTypeClass().getName() ) ); } + } - return new QueryLiteral<>( sqlLiteralValue, expressible ); + private EntityTypeLiteral entityTypeLiteral(SqmLiteral literal, DiscriminatorMapping inferableExpressible) { + final E literalValue = literal.getLiteralValue(); + final EntityPersister entityDescriptor; + if ( literalValue instanceof Class clazz ) { + entityDescriptor = + creationContext.getSessionFactory().getMappingMetamodel() + .findEntityDescriptor( clazz ); + } + else { + @SuppressWarnings("unchecked") + final DiscriminatorConverter valueConverter = + (DiscriminatorConverter) inferableExpressible.getValueConverter(); + entityDescriptor = + discriminatorValueDetails( valueConverter, literalValue ) + .getIndicatedEntity().getEntityPersister(); + } + return new EntityTypeLiteral( entityDescriptor ); + } + + private DiscriminatorValueDetails discriminatorValueDetails(DiscriminatorConverter valueConverter, E literalValue) { + if ( valueConverter.getDomainJavaType().isInstance( literalValue ) ) { + return valueConverter.getDetailsForDiscriminatorValue( literalValue ); + } + else if ( valueConverter.getRelationalJavaType().isInstance( literalValue ) ) { + return valueConverter.getDetailsForRelationalForm( literalValue ); + } + else { + // Special case when passing the discriminator value as e.g. string literal, + // but the expected relational type is Character. + // In this case, we use wrap to transform the value to the correct type + final E relationalForm = + valueConverter.getRelationalJavaType() + .wrap( literalValue, creationContext.getSessionFactory().getWrapperOptions() ); + return valueConverter.getDetailsForRelationalForm( relationalForm ); + } + } + + @Override + public Expression visitHqlNumericLiteral(SqmHqlNumericLiteral numericLiteral) { + final BasicValuedMapping inferredExpressible = (BasicValuedMapping) getInferredValueMapping(); + final BasicValuedMapping expressible = + inferredExpressible == null + ? (BasicValuedMapping) determineCurrentExpressible( numericLiteral ) + : inferredExpressible; + final JdbcMapping jdbcMapping = expressible.getJdbcMapping(); + return jdbcMapping.getValueConverter() != null + // special case where we need to parse the value in order to apply the conversion + ? convertedUnparsedNumericLiteral( numericLiteral, expressible ) + : unparsedNumericLiteral( numericLiteral, jdbcMapping ); + } + + private static UnparsedNumericLiteral unparsedNumericLiteral( + SqmHqlNumericLiteral numericLiteral, JdbcMapping jdbcMapping) { + return new UnparsedNumericLiteral<>( + numericLiteral.getUnparsedLiteralValue(), + numericLiteral.getTypeCategory(), + jdbcMapping + ); + } + + private Expression convertedUnparsedNumericLiteral( + SqmHqlNumericLiteral numericLiteral, + BasicValuedMapping expressible) { + final BasicValueConverter valueConverter = expressible.getJdbcMapping().getValueConverter(); + assert valueConverter != null; + final Number parsedValue = + numericLiteral.getTypeCategory() + .parseLiteralValue( numericLiteral.getUnparsedLiteralValue() ); + return new QueryLiteral<>( sqlLiteralValue( valueConverter, parsedValue ), expressible ); } private MappingModelExpressible getKeyExpressible(JdbcMappingContainer mappingModelExpressible) { - if ( mappingModelExpressible instanceof EntityAssociationMapping ) { - return ( (EntityAssociationMapping) mappingModelExpressible ).getKeyTargetMatchPart(); - } - else { - return (MappingModelExpressible) mappingModelExpressible; - } + return mappingModelExpressible instanceof EntityAssociationMapping entityAssociationMapping + ? entityAssociationMapping.getKeyTargetMatchPart() + : (MappingModelExpressible) mappingModelExpressible; } private MappingModelExpressible getElementExpressible(MappingModelExpressible mappingModelExpressible) { - if ( mappingModelExpressible instanceof PluralAttributeMapping ) { - return ( (PluralAttributeMapping) mappingModelExpressible).getElementDescriptor(); - } - else { - return mappingModelExpressible; - } + return mappingModelExpressible instanceof PluralAttributeMapping pluralAttributeMapping + ? pluralAttributeMapping.getElementDescriptor() + : mappingModelExpressible; } @Override @@ -6019,10 +5952,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base valueMapping ); } - return new SqmParameterInterpretation( - jdbcParametersForSqm, - valueMapping - ); + return new SqmParameterInterpretation( jdbcParametersForSqm, valueMapping ); } private void replaceJdbcParametersType( @@ -6031,20 +5961,19 @@ public abstract class BaseSqmToSqlAstConverter extends Base MappingModelExpressible valueMapping) { final JdbcMapping jdbcMapping = valueMapping.getSingleJdbcMapping(); for ( SqmParameter sqmParameter : sqmParameters ) { - if ( sqmParameter == sourceSqmParameter ) { - continue; - } - sqmParameterMappingModelTypes.put( sqmParameter, valueMapping ); - final List> jdbcParamsForSqmParameter = jdbcParamsBySqmParam.get( sqmParameter ); - if ( jdbcParamsForSqmParameter != null ) { - for ( List parameters : jdbcParamsForSqmParameter ) { - assert parameters.size() == 1; - final JdbcParameter jdbcParameter = parameters.get( 0 ); - if ( ( (SqlExpressible) jdbcParameter ).getJdbcMapping() != jdbcMapping ) { - final JdbcParameter newJdbcParameter = new JdbcParameterImpl( jdbcMapping ); - parameters.set( 0, newJdbcParameter ); - jdbcParameters.getJdbcParameters().remove( jdbcParameter ); - jdbcParameters.getJdbcParameters().add( newJdbcParameter ); + if ( sqmParameter != sourceSqmParameter ) { + sqmParameterMappingModelTypes.put( sqmParameter, valueMapping ); + final List> jdbcParamsForSqmParameter = jdbcParamsBySqmParam.get( sqmParameter ); + if ( jdbcParamsForSqmParameter != null ) { + for ( List parameters : jdbcParamsForSqmParameter ) { + assert parameters.size() == 1; + final JdbcParameter jdbcParameter = parameters.get( 0 ); + if ( ( (SqlExpressible) jdbcParameter ).getJdbcMapping() != jdbcMapping ) { + final JdbcParameter newJdbcParameter = new JdbcParameterImpl( jdbcMapping ); + parameters.set( 0, newJdbcParameter ); + jdbcParameters.getJdbcParameters().remove( jdbcParameter ); + jdbcParameters.getJdbcParameters().add( newJdbcParameter ); + } } } } @@ -6055,35 +5984,35 @@ public abstract class BaseSqmToSqlAstConverter extends Base if ( sqmParameter.allowMultiValuedBinding() ) { final QueryParameterImplementor domainParam = domainParameterXref.getQueryParameter( sqmParameter ); final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( domainParam ); - - if ( !domainParamBinding.isMultiValued() ) { - return consumeSingleSqmParameter( sqmParameter ); - } - - final Collection bindValues = domainParamBinding.getBindValues(); - final List expressions = new ArrayList<>( bindValues.size() ); - boolean first = true; - for ( Object bindValue : bindValues ) { - final SqmParameter sqmParamToConsume; - // for each bind value create an "expansion" - if ( first ) { - sqmParamToConsume = sqmParameter; - first = false; - } - else { - sqmParamToConsume = sqmParameter.copy(); - domainParameterXref.addExpansion( domainParam, sqmParameter, sqmParamToConsume ); - } - expressions.add( consumeSingleSqmParameter( sqmParamToConsume ) ); - } - - return new SqlTuple( expressions, null ); + return !domainParamBinding.isMultiValued() + ? consumeSingleSqmParameter( sqmParameter ) + : expandParameter( sqmParameter, domainParamBinding, domainParam ); } else { return consumeSingleSqmParameter( sqmParameter ); } } + private SqlTuple expandParameter(SqmParameter sqmParameter, QueryParameterBinding domainParamBinding, QueryParameterImplementor domainParam) { + final Collection bindValues = domainParamBinding.getBindValues(); + final List expressions = new ArrayList<>( bindValues.size() ); + boolean first = true; + for ( Object bindValue : bindValues ) { + final SqmParameter sqmParamToConsume; + // for each bind value create an "expansion" + if ( first ) { + sqmParamToConsume = sqmParameter; + first = false; + } + else { + sqmParamToConsume = sqmParameter.copy(); + domainParameterXref.addExpansion( domainParam, sqmParameter, sqmParamToConsume ); + } + expressions.add( consumeSingleSqmParameter( sqmParamToConsume ) ); + } + return new SqlTuple( expressions, null ); + } + protected Expression consumeSingleSqmParameter(SqmParameter sqmParameter) { return consumeSqmParameter( sqmParameter, determineValueMapping( sqmParameter ), (integer, jdbcParameter) -> {} ); } @@ -6100,19 +6029,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base if ( sqmExpression instanceof SqmPath ) { log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression ); - - final MappingModelExpressible mappingModelExpressible = SqmMappingModelHelper.resolveMappingModelExpressible( - sqmExpression, - domainModel, - fromClauseIndex::findTableGroup - ); + final MappingModelExpressible mappingModelExpressible = + resolveMappingModelExpressible( sqmExpression, domainModel, fromClauseIndex::findTableGroup ); if ( mappingModelExpressible != null ) { return mappingModelExpressible; } } - if ( sqmExpression instanceof SqmBooleanExpressionPredicate ) { - final SqmBooleanExpressionPredicate expressionPredicate = (SqmBooleanExpressionPredicate) sqmExpression; + if ( sqmExpression instanceof SqmBooleanExpressionPredicate expressionPredicate ) { return determineValueMapping( expressionPredicate.getBooleanExpression(), fromClauseIndex ); } @@ -6124,45 +6048,45 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } - if ( sqmExpression instanceof SqmSubQuery ) { - final SqmSubQuery subQuery = (SqmSubQuery) sqmExpression; - final SqmSelectClause selectClause = subQuery.getQuerySpec().getSelectClause(); + if ( sqmExpression instanceof SqmSubQuery subquery ) { + final SqmSelectClause selectClause = subquery.getQuerySpec().getSelectClause(); if ( selectClause.getSelections().size() == 1 ) { final SqmSelection subQuerySelection = selectClause.getSelections().get( 0 ); - final SqmSelectableNode selectableNode = subQuerySelection.getSelectableNode(); - if ( selectableNode instanceof SqmExpression ) { - return determineValueMapping( (SqmExpression) selectableNode, fromClauseIndex ); + if ( subQuerySelection.getSelectableNode() instanceof SqmExpression expression ) { + return determineValueMapping( expression, fromClauseIndex ); } - final SqmExpressible selectionNodeType = subQuerySelection.getNodeType(); - if ( selectionNodeType != null ) { - final MappingModelExpressible expressible = domainModel.resolveMappingExpressible(selectionNodeType, this::findTableGroupByPath ); - - if ( expressible != null ) { - return expressible; - } - - try { - final MappingModelExpressible mappingModelExpressible = resolveInferredType(); - if ( mappingModelExpressible != null ) { - return mappingModelExpressible; + else { + final SqmExpressible selectionNodeType = subQuerySelection.getNodeType(); + if ( selectionNodeType != null ) { + final MappingModelExpressible expressible = + domainModel.resolveMappingExpressible(selectionNodeType, this::findTableGroupByPath ); + if ( expressible != null ) { + return expressible; + } + else { + try { + final MappingModelExpressible mappingModelExpressible = resolveInferredType(); + if ( mappingModelExpressible != null ) { + return mappingModelExpressible; + } + } + catch (Exception ignore) { + return null; + } } - } - catch (Exception ignore) { - return null; } } } } - if ( sqmExpression instanceof SelfRenderingSqmFunction ) { + if ( sqmExpression instanceof SelfRenderingSqmFunction selfRenderingSqmFunction ) { return domainModel.resolveMappingExpressible( - ( (SelfRenderingSqmFunction) sqmExpression ).resolveResultType( this ), + selfRenderingSqmFunction.resolveResultType( this ), this::findTableGroupByPath ); } - if ( sqmExpression instanceof SqmBinaryArithmetic ) { - final SqmBinaryArithmetic binaryArithmetic = (SqmBinaryArithmetic) sqmExpression; + if ( sqmExpression instanceof SqmBinaryArithmetic binaryArithmetic ) { if ( binaryArithmetic.getNodeType() == null ) { final MappingModelExpressible lhs = determineValueMapping( binaryArithmetic.getLeftHandOperand() ); final MappingModelExpressible rhs = determineValueMapping( binaryArithmetic.getRightHandOperand() ); @@ -6184,8 +6108,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } if ( nodeType instanceof EmbeddedSqmPathSource ) { - if ( sqmExpression instanceof SqmBinaryArithmetic ) { - final SqmBinaryArithmetic binaryArithmetic = (SqmBinaryArithmetic) sqmExpression; + if ( sqmExpression instanceof SqmBinaryArithmetic binaryArithmetic ) { if ( binaryArithmetic.getLeftHandOperand().getNodeType() == nodeType ) { return determineValueMapping( binaryArithmetic.getLeftHandOperand(), fromClauseIndex ); } @@ -6196,10 +6119,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base } - final MappingModelExpressible valueMapping = domainModel.resolveMappingExpressible( - nodeType, - fromClauseIndex::getTableGroup - ); + final MappingModelExpressible valueMapping = + domainModel.resolveMappingExpressible( nodeType, fromClauseIndex::getTableGroup ); if ( valueMapping == null ) { final MappingModelExpressible mappingModelExpressible = resolveInferredType(); @@ -6261,8 +6182,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base // Default to the Object type return basicType( Object.class ); } - else if ( paramType instanceof MappingModelExpressible ) { - final MappingModelExpressible paramModelType = (MappingModelExpressible) paramType; + else if ( paramType instanceof MappingModelExpressible paramModelType ) { final MappingModelExpressible inferredValueMapping = getInferredValueMapping(); // Prefer the model part type instead of the bind type if possible as the model part type contains size information if ( inferredValueMapping instanceof ModelPart ) { @@ -6389,69 +6309,17 @@ public abstract class BaseSqmToSqlAstConverter extends Base MappingModelExpressible valueMapping, BiConsumer jdbcParameterConsumer) { sqmParameterMappingModelTypes.put( expression, valueMapping ); - final Bindable bindable; - if ( valueMapping instanceof EntityAssociationMapping ) { - final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping; - bindable = mapping.getForeignKeyDescriptor().getPart( mapping.getSideNature() ); - } - else if ( valueMapping instanceof EntityMappingType ) { - bindable = ( (EntityMappingType) valueMapping ).getIdentifierMapping(); - } - else { - bindable = valueMapping; - } - if ( bindable instanceof SelectableMappings ) { - ( (SelectableMappings) bindable ).forEachSelectable( + final Bindable bindable = bindable( valueMapping ); + if ( bindable instanceof SelectableMappings selectableMappings ) { + selectableMappings.forEachSelectable( (index, selectableMapping) -> jdbcParameterConsumer.accept( index, new SqlTypedMappingJdbcParameter( selectableMapping ) ) ); } - else if ( bindable instanceof SelectableMapping ) { - jdbcParameterConsumer.accept( 0, new SqlTypedMappingJdbcParameter( (SelectableMapping) bindable ) ); + else if ( bindable instanceof SelectableMapping selectableMapping ) { + jdbcParameterConsumer.accept( 0, new SqlTypedMappingJdbcParameter( selectableMapping ) ); } else { - SqlTypedMapping sqlTypedMapping = null; - if ( bindable instanceof BasicType ) { - final int sqlTypeCode = ( (BasicType) bindable ).getJdbcType().getDdlTypeCode(); - if ( sqlTypeCode == SqlTypes.NUMERIC || sqlTypeCode == SqlTypes.DECIMAL ) { - // For numeric and decimal parameter types we must determine the precision/scale of the value. - // When we need to cast the parameter later, it is necessary to know the size to avoid truncation. - final QueryParameterBinding binding = domainParameterBindings.getBinding( - domainParameterXref.getQueryParameter( expression ) - ); - final Object bindValue; - if ( binding.isMultiValued() ) { - final Collection bindValues = binding.getBindValues(); - bindValue = !bindValues.isEmpty() ? bindValues.iterator().next() : null; - } - else { - bindValue = binding.getBindValue(); - } - if ( bindValue != null ) { - if ( bindValue instanceof BigInteger ) { - int precision = bindValue.toString().length() - ( ( (BigInteger) bindValue ).signum() < 0 ? 1 : 0 ); - sqlTypedMapping = new SqlTypedMappingImpl( - null, - null, - precision, - 0, - null, - ( (BasicType) bindable ).getJdbcMapping() - ); - } - else if ( bindValue instanceof BigDecimal ) { - final BigDecimal bigDecimal = (BigDecimal) bindValue; - sqlTypedMapping = new SqlTypedMappingImpl( - null, - null, - bigDecimal.precision(), - bigDecimal.scale(), - null, - ( (BasicType) bindable ).getJdbcMapping() - ); - } - } - } - } + final SqlTypedMapping sqlTypedMapping = sqlTypedMapping( expression, bindable ); if ( sqlTypedMapping == null ) { if ( bindable == null ) { throw new ConversionException( @@ -6470,6 +6338,72 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } + private SqlTypedMapping sqlTypedMapping(SqmParameter expression, Bindable bindable) { + if ( bindable instanceof BasicType basicType) { + final int sqlTypeCode = basicType.getJdbcType().getDdlTypeCode(); + if ( sqlTypeCode == SqlTypes.NUMERIC || sqlTypeCode == SqlTypes.DECIMAL ) { + // For numeric and decimal parameter types we must determine the precision/scale of the value. + // When we need to cast the parameter later, it is necessary to know the size to avoid truncation. + final QueryParameterBinding binding = domainParameterBindings.getBinding( + domainParameterXref.getQueryParameter( expression ) + ); + return sqlTypedMapping( binding, basicType ); + } + } + return null; + } + + private static SqlTypedMapping sqlTypedMapping(QueryParameterBinding binding, BasicType bindable) { + final Object bindValue; + if ( binding.isMultiValued() ) { + final Collection bindValues = binding.getBindValues(); + bindValue = bindValues.isEmpty() ? null : bindValues.iterator().next(); + } + else { + bindValue = binding.getBindValue(); + } + return bindValue != null ? precision( bindable, bindValue ) : null; + } + + private static SqlTypedMappingImpl precision(BasicType bindable, Object bindValue) { + if ( bindValue instanceof BigInteger bigInteger ) { + int precision = bindValue.toString().length() - ( bigInteger.signum() < 0 ? 1 : 0 ); + return new SqlTypedMappingImpl( + null, + null, + precision, + 0, + null, + bindable.getJdbcMapping() + ); + } + else if ( bindValue instanceof BigDecimal bigDecimal ) { + return new SqlTypedMappingImpl( + null, + null, + bigDecimal.precision(), + bigDecimal.scale(), + null, + bindable.getJdbcMapping() + ); + } + else { + return null; + } + } + + private static Bindable bindable(MappingModelExpressible valueMapping) { + if ( valueMapping instanceof EntityAssociationMapping mapping ) { + return mapping.getForeignKeyDescriptor().getPart( mapping.getSideNature() ); + } + else if ( valueMapping instanceof EntityMappingType entityMappingType ) { + return entityMappingType.getIdentifierMapping(); + } + else { + return valueMapping; + } + } + @Override public Object visitPositionalParameterExpression(SqmPositionalParameter expression) { return consumeSqmParameter( expression ); @@ -6508,13 +6442,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base final int size = groupedExpressions.size(); final List expressions = new ArrayList<>( size ); final MappingModelExpressible mappingModelExpressible = resolveInferredType(); - final EmbeddableMappingType embeddableMappingType; - if ( mappingModelExpressible instanceof ValueMapping ) { - embeddableMappingType = (EmbeddableMappingType) ( (ValueMapping) mappingModelExpressible).getMappedType(); - } - else { - embeddableMappingType = null; - } + final EmbeddableMappingType embeddableMappingType = + mappingModelExpressible instanceof ValueMapping valueMapping + ? (EmbeddableMappingType) valueMapping.getMappedType() + : null; if ( embeddableMappingType == null ) { try { inferrableTypeAccessStack.push( () -> null ); @@ -6543,13 +6474,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base valueMapping = mappingModelExpressible; } else { - final SqmExpressible expressible = sqmTuple.getExpressible(); - if ( expressible instanceof MappingModelExpressible) { - valueMapping = (MappingModelExpressible) expressible; - } - else { - valueMapping = null; - } + valueMapping = + sqmTuple.getExpressible() instanceof MappingModelExpressible modelExpressible + ? modelExpressible + : null; } return new SqlTuple( expressions, valueMapping ); } @@ -6610,29 +6538,29 @@ public abstract class BaseSqmToSqlAstConverter extends Base public Object visitOver(SqmOver over) { currentClauseStack.push( Clause.OVER ); final Expression expression = (Expression) over.getExpression().accept( this ); - - final List partitions = new ArrayList<>(over.getWindow().getPartitions().size()); - for ( SqmExpression partition : over.getWindow().getPartitions() ) { + final SqmWindow window = over.getWindow(); + final List partitions = new ArrayList<>( window.getPartitions().size() ); + for ( SqmExpression partition : window.getPartitions() ) { partitions.add( (Expression) partition.accept( this ) ); } - final List orderList = new ArrayList<>( over.getWindow().getOrderList().size() ); - for ( SqmSortSpecification sortSpecification : over.getWindow().getOrderList() ) { + final List orderList = new ArrayList<>( window.getOrderList().size() ); + for ( SqmSortSpecification sortSpecification : window.getOrderList() ) { orderList.add( visitSortSpecification( sortSpecification ) ); } final Over overExpression = new Over<>( expression, partitions, orderList, - over.getWindow().getMode(), - over.getWindow().getStartKind(), - over.getWindow().getStartExpression() == null ? + window.getMode(), + window.getStartKind(), + window.getStartExpression() == null ? null : - (Expression) over.getWindow().getStartExpression().accept( this ), - over.getWindow().getEndKind(), - over.getWindow().getEndExpression() == null ? + (Expression) window.getStartExpression().accept( this ), + window.getEndKind(), + window.getEndExpression() == null ? null : - (Expression) over.getWindow().getEndExpression().accept( this ), - over.getWindow().getExclusion() + (Expression) window.getEndExpression().accept( this ), + window.getExclusion() ); currentClauseStack.pop(); return overExpression; @@ -6662,12 +6590,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public Object visitCastTarget(SqmCastTarget target) { BasicValuedMapping targetType = (BasicValuedMapping) target.getType(); - if ( targetType instanceof BasicType ) { - targetType = InferredBasicValueResolver.resolveSqlTypeIndicators( - this, - (BasicType) targetType, - target.getNodeJavaType() - ); + if ( targetType instanceof BasicType basicType ) { + targetType = resolveSqlTypeIndicators( this, basicType, target.getNodeJavaType() ); } return new CastTarget( targetType.getJdbcMapping(), @@ -6789,12 +6713,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base private BasicValuedMapping getExpressionType(SqmExpression expression) { final SqmExpressible nodeType = expression.getNodeType(); - if ( nodeType != null ) { - if ( nodeType instanceof BasicValuedMapping ) { - return (BasicValuedMapping) nodeType; + if ( nodeType == null ) { + return JavaObjectType.INSTANCE; + } + else { + if ( nodeType instanceof BasicValuedMapping basicValuedMapping ) { + return basicValuedMapping; } - else if ( nodeType.getSqmType() instanceof BasicValuedMapping ) { - return (BasicValuedMapping) nodeType.getSqmType(); + else if ( nodeType.getSqmType() instanceof BasicValuedMapping basicValuedMapping ) { + return basicValuedMapping; } else { return getTypeConfiguration().getBasicTypeForJavaType( @@ -6802,7 +6729,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); } } - return JavaObjectType.INSTANCE; } private Expression toSqlExpression(Object value) { @@ -6844,17 +6770,17 @@ public abstract class BaseSqmToSqlAstConverter extends Base // ts + x * (d1 - d2) => (ts + x * d1) - x * d2 // ts - x * (d1 - d2) => (ts - x * d1) + x * d2 - Expression timestamp = adjustedTimestamp; - SqmExpressible timestampType = adjustedTimestampType; + final Expression timestamp = adjustedTimestamp; + final SqmExpressible timestampType = adjustedTimestampType; inferrableTypeAccessStack.push( () -> determineValueMapping( rhs, fromClauseIndex ) ); adjustedTimestamp = toSqlExpression( lhs.accept( this ) ); inferrableTypeAccessStack.pop(); - JdbcMappingContainer type = adjustedTimestamp.getExpressionType(); - if ( type instanceof SqmExpressible) { - adjustedTimestampType = (SqmExpressible) type; + final JdbcMappingContainer type = adjustedTimestamp.getExpressionType(); + if ( type instanceof SqmExpressible sqmExpressible ) { + adjustedTimestampType = sqmExpressible; } - else if (type instanceof ValueMapping ) { - adjustedTimestampType = (SqmExpressible) ( (ValueMapping) type ).getMappedType(); + else if (type instanceof ValueMapping valueMapping ) { + adjustedTimestampType = (SqmExpressible) valueMapping.getMappedType(); } else { // else we know it has not been transformed @@ -6916,10 +6842,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base // -x * (d1 + d2) => - x * d1 - x * d2 // -x * (d1 - d2) => - x * d1 + x * d2 inferrableTypeAccessStack.push( () -> determineValueMapping( rhs, fromClauseIndex ) ); - Expression duration = toSqlExpression( lhs.accept( this ) ); + final Expression duration = toSqlExpression( lhs.accept( this ) ); inferrableTypeAccessStack.pop(); - Expression scale = adjustmentScale; - boolean negate = negativeAdjustment; + final Expression scale = adjustmentScale; + final boolean negate = negativeAdjustment; adjustmentScale = applyScale( duration ); negativeAdjustment = false; //was sucked into the scale try { @@ -6980,9 +6906,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base // temporal type, so we must use it for both // the diff, and then the subsequent add - DurationUnit unit = new DurationUnit( baseUnit, diffResultType ); - BasicValuedMapping durationType = (BasicValuedMapping) expression.getNodeType(); - Expression scaledMagnitude = applyScale( + final DurationUnit unit = new DurationUnit( baseUnit, diffResultType ); + final BasicValuedMapping durationType = (BasicValuedMapping) expression.getNodeType(); + final Expression scaledMagnitude = applyScale( timestampdiff().expression( (ReturnableType) expression.getNodeType(), durationType.getJdbcMapping().getJdbcType().isInterval() ? null : unit, @@ -7001,14 +6927,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base // we're immediately converting the resulting // duration to a scalar in the given unit - DurationUnit unit = (DurationUnit) appliedByUnit.getUnit().accept( this ); + final DurationUnit unit = (DurationUnit) appliedByUnit.getUnit().accept( this ); return applyScale( timestampdiff().expression( null, unit, right, left ) ); } else { // a plain "bare" Duration - DurationUnit unit = new DurationUnit( baseUnit, diffResultType ); - BasicValuedMapping durationType = (BasicValuedMapping) expression.getNodeType(); - Expression scaledMagnitude = applyScale( + final DurationUnit unit = new DurationUnit( baseUnit, diffResultType ); + final BasicValuedMapping durationType = (BasicValuedMapping) expression.getNodeType(); + final Expression scaledMagnitude = applyScale( timestampdiff().expression( (ReturnableType) expression.getNodeType(), durationType.getJdbcMapping().getJdbcType().isInterval() ? null : unit, @@ -7021,8 +6947,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private static Expression getActualExpression(Expression expression) { - if ( expression.getExpressionType() instanceof EmbeddableValuedModelPart ) { - final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) expression.getExpressionType(); + if ( expression.getExpressionType() instanceof EmbeddableValuedModelPart embeddableValuedModelPart ) { if ( JavaTypeHelper.isTemporal( embeddableValuedModelPart.getJavaType() ) ) { return ( (SqlTupleContainer) expression ).getSqlTuple().getExpressions().get( 0 ); } @@ -7074,8 +6999,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base Expression applyScale(Expression magnitude) { boolean negate = negativeAdjustment; - if ( magnitude instanceof UnaryOperation ) { - UnaryOperation unary = (UnaryOperation) magnitude; + if ( magnitude instanceof UnaryOperation unary ) { if ( unary.getOperator() == UNARY_MINUS ) { // if it's already negated, don't // wrap it in another unary minus, @@ -7131,7 +7055,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base @SuppressWarnings("unchecked") static boolean isOne(Expression scale) { return scale instanceof QueryLiteral - && ( (QueryLiteral) scale ).getLiteralValue().longValue() == 1L; + && ( (QueryLiteral) scale ).getLiteralValue().longValue() == 1L; } @Override @@ -7253,7 +7177,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base final QueryPart queryPart = visitQueryPart( sqmSubQuery.getQueryPart() ); currentlyProcessingJoin = oldJoin; this.cteContainer = oldCteContainer; - return new SelectStatement( cteContainer, queryPart, Collections.emptyList() ); + return new SelectStatement( cteContainer, queryPart, emptyList() ); } @Override @@ -7357,7 +7281,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base private MappingModelExpressible determineCurrentExpressible(SqmTypedNode expression) { return creationContext.getSessionFactory() - .getRuntimeMetamodels() .getMappingMetamodel() .resolveMappingExpressible( expression.getNodeType(), getFromClauseIndex()::findTableGroup ); } @@ -7417,20 +7340,16 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private Summarization.Kind getSummarizationKind(SqmSummarization.Kind kind) { - switch ( kind ) { - case CUBE: - return Summarization.Kind.CUBE; - case ROLLUP: - return Summarization.Kind.ROLLUP; - } - throw new UnsupportedOperationException( "Unsupported summarization: " + kind ); + return switch ( kind ) { + case CUBE -> Summarization.Kind.CUBE; + case ROLLUP -> Summarization.Kind.ROLLUP; + }; } @Override public Expression visitEntityTypeLiteralExpression(SqmLiteralEntityType sqmExpression) { final EntityDomainType nodeType = sqmExpression.getNodeType(); final EntityPersister mappingDescriptor = creationContext.getSessionFactory() - .getRuntimeMetamodels() .getMappingMetamodel() .getEntityDescriptor( nodeType.getHibernateEntityName() ); @@ -7606,14 +7525,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base // Create a copy of the filtered table groups from which we remove final List missingTableGroupFilters; if ( filteredTableGroups == null || i == 0 ) { - missingTableGroupFilters = Collections.emptyList(); + missingTableGroupFilters = emptyList(); } else { missingTableGroupFilters = new ArrayList<>( filteredTableGroups ); } final List missingTableGroupTreats; if ( treatedTableGroups == null || i == 0 ) { - missingTableGroupTreats = Collections.emptyList(); + missingTableGroupTreats = emptyList(); } else { missingTableGroupTreats = new ArrayList<>( treatedTableGroups ); @@ -7681,12 +7600,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base else { unionEntityNameUse = useEntry.getValue(); } - if ( currentUseKind == null ) { - entityNameUses.put( useEntry.getKey(), unionEntityNameUse ); - } - else { - entityNameUses.put( useEntry.getKey(), unionEntityNameUse.stronger( currentUseKind ) ); - } + entityNameUses.put( useEntry.getKey(), + currentUseKind == null + ? unionEntityNameUse + : unionEntityNameUse.stronger( currentUseKind ) ); } } // Downgrade entity name uses for table groups that haven't been filtered in this disjunct @@ -7970,10 +7887,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base boolean inclusive) { final TableGroup tableGroup = getFromClauseIndex().getTableGroup( typeExpression.getNavigablePath().getParent() ); final MappingType partMappingType = tableGroup.getModelPart().getPartMappingType(); - if ( !( partMappingType instanceof EntityMappingType ) ) { + if ( !(partMappingType instanceof EntityMappingType entityMappingType) ) { return; } - final EntityMappingType entityMappingType = (EntityMappingType) partMappingType; if ( entityMappingType.getDiscriminatorMapping().hasPhysicalColumn() ) { // If the entity has a physical discriminator column we don't need to register any FILTER usages. // Register only an EXPRESSION usage to prevent pruning of the root type's table reference which @@ -8164,8 +8080,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base public NullnessPredicate visitIsNullPredicate(SqmNullnessPredicate predicate) { final SqmExpression sqmExpression = predicate.getExpression(); final Expression expression; - if ( sqmExpression instanceof SqmEntityValuedSimplePath ) { - final SqmEntityValuedSimplePath entityValuedPath = (SqmEntityValuedSimplePath) sqmExpression; + if ( sqmExpression instanceof SqmEntityValuedSimplePath entityValuedPath ) { inferrableTypeAccessStack.push( () -> basicType( Object.class ) ); expression = withTreatRestriction( prepareReusablePath( entityValuedPath, @@ -8205,9 +8120,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base // handling for "expansion" if ( predicate.getListExpressions().size() == 1 ) { final SqmExpression sqmExpression = predicate.getListExpressions().get( 0 ); - if ( sqmExpression instanceof SqmParameter ) { - final SqmParameter sqmParameter = (SqmParameter) sqmExpression; - + if ( sqmExpression instanceof SqmParameter sqmParameter ) { if ( sqmParameter.allowMultiValuedBinding() ) { final InListPredicate specialCase = processInListWithSingleParameter( predicate, sqmParameter ); if ( specialCase != null ) { @@ -8261,8 +8174,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base private void handleTypeComparison(InListPredicate inPredicate) { final Expression testExpression = inPredicate.getTestExpression(); - if ( testExpression instanceof DiscriminatorPathInterpretation ) { - final DiscriminatorPathInterpretation typeExpression = (DiscriminatorPathInterpretation) testExpression; + if ( testExpression instanceof DiscriminatorPathInterpretation typeExpression ) { boolean containsNonLiteral = false; for ( Expression listExpression : inPredicate.getListExpressions() ) { if ( !( listExpression instanceof EntityTypeLiteral ) ) { @@ -8284,8 +8196,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base SqmParameter sqmParameter) { assert sqmParameter.allowMultiValuedBinding(); - if ( sqmParameter instanceof JpaCriteriaParameter ) { - return processInSingleCriteriaParameter( sqmPredicate, (JpaCriteriaParameter) sqmParameter ); + if ( sqmParameter instanceof JpaCriteriaParameter parameter ) { + return processInSingleCriteriaParameter( sqmPredicate, parameter ); } return processInSingleHqlParameter( sqmPredicate, sqmParameter ); @@ -8308,12 +8220,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base assert jpaCriteriaParameter.allowsMultiValuedBinding(); final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( jpaCriteriaParameter ); - if ( !domainParamBinding.isMultiValued() ) { + if ( domainParamBinding.isMultiValued() ) { + final SqmJpaCriteriaParameterWrapper sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter ); + return processInSingleParameter( sqmPredicate, sqmWrapper, jpaCriteriaParameter, domainParamBinding ); + } + else { return null; } - final SqmJpaCriteriaParameterWrapper sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter ); - - return processInSingleParameter( sqmPredicate, sqmWrapper, jpaCriteriaParameter, domainParamBinding ); } @SuppressWarnings( "rawtypes" ) @@ -8383,19 +8296,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base // Functions that are rendered as predicates are always wrapped, // so the following unwraps the predicate and returns it directly instead of wrapping once more final Predicate sqlPredicate = caseExpr.getWhenFragments().get( 0 ).getPredicate(); - if ( predicate.isNegated() ) { - return new NegatedPredicate( sqlPredicate ); - } - return sqlPredicate; + return predicate.isNegated() ? new NegatedPredicate( sqlPredicate ) : sqlPredicate; } inferrableTypeAccessStack.pop(); if ( booleanExpression instanceof SelfRenderingExpression ) { final Predicate sqlPredicate = new SelfRenderingPredicate( (SelfRenderingExpression) booleanExpression ); - if ( predicate.isNegated() ) { - return new NegatedPredicate( sqlPredicate ); - } - return sqlPredicate; + return predicate.isNegated() ? new NegatedPredicate( sqlPredicate ) : sqlPredicate; } else { final JdbcMapping jdbcMapping = booleanExpression.getExpressionType().getJdbcMapping( 0 ); @@ -8654,14 +8561,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base if ( fetch != null && fetch.getTiming() == FetchTiming.IMMEDIATE ) { if ( fetchable instanceof TableGroupJoinProducer ) { if ( joinedTableGroup != null ) { - final TableGroup actualTableGroup = joinedTableGroup instanceof PluralTableGroup ? - ( (PluralTableGroup) joinedTableGroup ).getElementTableGroup() : - joinedTableGroup; + final TableGroup actualTableGroup = + joinedTableGroup instanceof PluralTableGroup pluralTableGroup + ? pluralTableGroup.getElementTableGroup() + : joinedTableGroup; final MappingType mappingType = actualTableGroup == null ? null : actualTableGroup.getModelPart().getPartMappingType(); - if ( mappingType instanceof EntityMappingType ) { - final EntityMappingType entityMappingType = (EntityMappingType) mappingType; + if ( mappingType instanceof EntityMappingType entityMappingType ) { registerEntityNameUsage( actualTableGroup, EntityNameUse.PROJECTION, @@ -8677,8 +8584,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } } - if ( fetchable instanceof PluralAttributeMapping ) { - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; + if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { final CollectionClassification collectionClassification = pluralAttributeMapping.getMappedType() .getCollectionSemantics() .getCollectionClassification(); @@ -8740,24 +8646,22 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private boolean shouldExplicitFetch(Integer maxDepth, Fetchable fetchable) { - /* - Forcing the value of explicitFetch to true will disable the fetch circularity check and - for already visited association or collection this will cause a StackOverflow if maxFetchDepth is null, see HHH-15391. - */ + // Forcing the value of explicitFetch to true will disable the fetch circularity check and + // for already visited association or collection this will cause a StackOverflow if maxFetchDepth is null, see HHH-15391. if ( maxDepth == null ) { - if ( fetchable instanceof ToOneAttributeMapping ) { - return !this.isAssociationKeyVisited( - ( (ToOneAttributeMapping) fetchable ).getForeignKeyDescriptor().getAssociationKey() - ); + if ( fetchable instanceof ToOneAttributeMapping toOneAttributeMapping ) { + return !isAssociationKeyVisited( toOneAttributeMapping.getForeignKeyDescriptor().getAssociationKey() ); } - else if ( fetchable instanceof PluralAttributeMapping ) { - return !this.isAssociationKeyVisited( - ( (PluralAttributeMapping) fetchable ).getKeyDescriptor().getAssociationKey() - ); + else if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { + return !isAssociationKeyVisited( pluralAttributeMapping.getKeyDescriptor().getAssociationKey() ); + } + else { + return true; } } - - return true; + else { + return true; + } } private Fetch buildFetch( @@ -8795,16 +8699,16 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); } } - - private void applyOrdering(TableGroup tableGroup, PluralAttributeMapping pluralAttributeMapping) { - if ( pluralAttributeMapping.getOrderByFragment() != null ) { - applyOrdering( tableGroup, pluralAttributeMapping.getOrderByFragment() ); - } - - if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) { - applyOrdering( tableGroup, pluralAttributeMapping.getManyToManyOrderByFragment() ); - } - } +// +// private void applyOrdering(TableGroup tableGroup, PluralAttributeMapping pluralAttributeMapping) { +// if ( pluralAttributeMapping.getOrderByFragment() != null ) { +// applyOrdering( tableGroup, pluralAttributeMapping.getOrderByFragment() ); +// } +// +// if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) { +// applyOrdering( tableGroup, pluralAttributeMapping.getManyToManyOrderByFragment() ); +// } +// } @Override public void applyOrdering(TableGroup tableGroup, OrderByFragment orderByFragment) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AbstractSqlAstQueryNodeProcessingStateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AbstractSqlAstQueryNodeProcessingStateImpl.java index 55292af908..bc431d1be4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AbstractSqlAstQueryNodeProcessingStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AbstractSqlAstQueryNodeProcessingStateImpl.java @@ -48,9 +48,8 @@ public abstract class AbstractSqlAstQueryNodeProcessingStateImpl public void registerFromUsage(SqmFrom sqmFrom, boolean downgradeTreatUses) { if ( !( sqmFrom instanceof SqmTreatedPath ) ) { if ( !sqmFromRegistrations.containsKey( sqmFrom ) ) { - final SqlAstProcessingState parentState = getParentState(); - if ( parentState instanceof SqlAstQueryPartProcessingState ) { - ( (SqlAstQueryPartProcessingState) parentState ).registerFromUsage( sqmFrom, downgradeTreatUses ); + if ( getParentState() instanceof SqlAstQueryPartProcessingState parentState ) { + parentState.registerFromUsage( sqmFrom, downgradeTreatUses ); } } else {