From 46a16c605a71b26f5d68b5075b411b3424caad8b Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 23 Mar 2021 16:07:26 +0100 Subject: [PATCH] Add support for DML with versioned entities --- .../org/hibernate/grammars/hql/HqlLexer.g4 | 1 + .../org/hibernate/grammars/hql/HqlParser.g4 | 3 +- ...VersionTypeSeedParameterSpecification.java | 33 +-- .../query/criteria/JpaCriteriaUpdate.java | 6 + .../hql/internal/SemanticQueryBuilder.java | 100 +++++--- .../query/sqm/internal/SqmTreePrinter.java | 2 +- .../internal/cte/CteUpdateHandler.java | 1 + .../idtable/TableBasedUpdateHandler.java | 1 + .../sqm/sql/BaseSqmToSqlAstConverter.java | 222 +++++++++++++----- .../sqm/tree/update/SqmUpdateStatement.java | 18 ++ .../ast/tree/predicate/ExistsPredicate.java | 2 +- .../sql/ast/tree/select/QueryGroup.java | 8 + .../sql/ast/tree/select/QueryPart.java | 2 + .../sql/ast/tree/select/QuerySpec.java | 6 + .../descriptor/jdbc/JdbcTypeDescriptor.java | 11 + 15 files changed, 298 insertions(+), 118 deletions(-) diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index 5a62f1a83d..3b373b39cb 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -136,6 +136,7 @@ ARROW : '->'; ID : [iI][dD]; VERSION : [vV] [eE] [rR] [sS] [iI] [oO] [nN]; +VERSIONED : [vV] [eE] [rR] [sS] [iI] [oO] [nN] [eE] [dD]; NATURALID : [nN] [aA] [tT] [uU] [rR] [aA] [lL] [iI] [dD]; ABS : [aA] [bB] [sS]; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index b7a8be55cd..94ddd33934 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -44,7 +44,7 @@ deleteStatement ; updateStatement - : UPDATE dmlTarget setClause whereClause? + : UPDATE VERSIONED? dmlTarget setClause whereClause? ; setClause @@ -1224,6 +1224,7 @@ identifier | UPPER | VALUE | VERSION + | VERSIONED | WEEK | WHERE | WITH diff --git a/hibernate-core/src/main/java/org/hibernate/param/VersionTypeSeedParameterSpecification.java b/hibernate-core/src/main/java/org/hibernate/param/VersionTypeSeedParameterSpecification.java index 9693c9d293..8bbd07e979 100644 --- a/hibernate-core/src/main/java/org/hibernate/param/VersionTypeSeedParameterSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/param/VersionTypeSeedParameterSpecification.java @@ -11,6 +11,10 @@ import java.sql.SQLException; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.sql.exec.internal.JdbcParameterImpl; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.type.Type; import org.hibernate.type.VersionType; @@ -19,7 +23,7 @@ import org.hibernate.type.VersionType; * * @author Steve Ebersole */ -public class VersionTypeSeedParameterSpecification implements ParameterSpecification { +public class VersionTypeSeedParameterSpecification extends JdbcParameterImpl { private final VersionType type; /** @@ -28,31 +32,16 @@ public class VersionTypeSeedParameterSpecification implements ParameterSpecifica * @param type The version type. */ public VersionTypeSeedParameterSpecification(VersionType type) { + super( (JdbcMapping) type ); this.type = type; } @Override - public int bind( + public void bindParameterValue( PreparedStatement statement, - QueryParameters qp, - SharedSessionContractImplementor session, - int position) throws SQLException { - type.nullSafeSet( statement, type.seed( session ), position, session ); - return 1; - } - - @Override - public Type getExpectedType() { - return type; - } - - @Override - public void setExpectedType(Type expectedType) { - // expected type is intrinsic here... - } - - @Override - public String renderDisplayInfo() { - return "version-seed, type=" + type; + int startPosition, + JdbcParameterBindings jdbcParamBindings, + ExecutionContext executionContext) throws SQLException { + type.nullSafeSet( statement, type.seed( executionContext.getSession() ), startPosition, executionContext.getSession() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaUpdate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaUpdate.java index f499d96235..66d399d273 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaUpdate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaUpdate.java @@ -12,4 +12,10 @@ import javax.persistence.criteria.CriteriaUpdate; * @author Steve Ebersole */ public interface JpaCriteriaUpdate extends JpaManipulationCriteria, CriteriaUpdate { + + boolean isVersioned(); + + JpaCriteriaUpdate versioned(); + + JpaCriteriaUpdate versioned(boolean versioned); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 2f598701a2..cb03cea98e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -93,6 +93,7 @@ import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom; import org.hibernate.query.sqm.tree.domain.SqmCorrelation; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; +import org.hibernate.query.sqm.tree.domain.SqmListJoin; import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference; import org.hibernate.query.sqm.tree.domain.SqmMapJoin; import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath; @@ -427,6 +428,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem processingState.getPathRegistry().register( root ); try { + updateStatement.versioned( ctx.VERSIONED() != null ); for ( HqlParser.AssignmentContext assignmentContext : ctx.setClause().assignment() ) { updateStatement.applyAssignment( consumeDomainPath( assignmentContext.dotIdentifierSequence() ), @@ -3105,7 +3107,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return getFunctionDescriptor("sqrt").generateSqmExpression( arg, - (AllowableFunctionReturnType) arg.getNodeType(), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); @@ -3567,7 +3569,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return getFunctionDescriptor("sum").generateSqmExpression( argument, - (AllowableFunctionReturnType) arg.getNodeType(), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); @@ -3985,44 +3987,74 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return visitMapKeyNavigablePath( ctx.mapKeyNavigablePath() ); } else if ( ctx.dotIdentifierSequence() != null && ctx.indexedPathAccessFragment() != null ) { - final SqmAttributeJoin indexedJoinPath = (SqmAttributeJoin) ctx.dotIdentifierSequence().accept( this ); - //noinspection unchecked - return new SqmIndexedCollectionAccessPath( - indexedJoinPath, - (SqmExpression) ctx.indexedPathAccessFragment().accept( this ) + dotIdentifierConsumerStack.push( + new QualifiedJoinPathConsumer( + (SqmRoot) dotIdentifierConsumerStack.getCurrent().getConsumedPart(), + SqmJoinType.INNER, + false, + null, + this + ) ); + + final SqmAttributeJoin indexedJoinPath; + try { + indexedJoinPath = (SqmAttributeJoin) ctx.dotIdentifierSequence().accept( this ); + } + finally { + dotIdentifierConsumerStack.pop(); + } + dotIdentifierConsumerStack.push( + new BasicDotIdentifierConsumer( indexedJoinPath, this ) { + @Override + protected void reset() { + } + } + ); + try { + return (SemanticPathPart) ctx.indexedPathAccessFragment().accept( this ); + } + finally { + dotIdentifierConsumerStack.pop(); + } } throw new ParsingException( "Unsure how to process `syntacticDomainPath` over : " + ctx.getText() ); } - -// @Override -// public SemanticPathPart visitDotIdentifierSequence(HqlParser.DotIdentifierSequenceContext ctx) { -// final int numberOfContinuations = ctx.dotIdentifierSequenceContinuation().size(); -// final boolean hasContinuations = numberOfContinuations != 0; -// -// final SemanticPathPart currentPathPart = semanticPathPartStack.getCurrent(); -// -// SemanticPathPart result = currentPathPart.resolvePathPart( -// ctx.identifier().getText(), -// !hasContinuations, -// this -// ); -// -// if ( hasContinuations ) { -// int i = 1; -// for ( HqlParser.DotIdentifierSequenceContinuationContext continuation : ctx.dotIdentifierSequenceContinuation() ) { -// result = result.resolvePathPart( -// continuation.identifier().getText(), -// i++ >= numberOfContinuations, -// this -// ); -// } -// } -// -// return result; -// } + @Override + public SemanticPathPart visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext ctx) { + final DotIdentifierConsumer consumer = dotIdentifierConsumerStack.pop(); + final SqmExpression indexExpression = (SqmExpression) ctx.expression().accept( this ); + final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) consumer.getConsumedPart(); + final SqmExpression index; + if ( attributeJoin instanceof SqmListJoin ) { + index = ( (SqmListJoin) attributeJoin ).index(); + } + else if ( attributeJoin instanceof SqmMapJoin ) { + index = ( (SqmMapJoin) attributeJoin ).key(); + } + else { + throw new SemanticException( "Index access is only supported on list or map attributes: " + attributeJoin.getNavigablePath() ); + } + attributeJoin.setJoinPredicate( creationContext.getNodeBuilder().equal( index, indexExpression ) ); + final SqmIndexedCollectionAccessPath path = new SqmIndexedCollectionAccessPath<>( + attributeJoin, + indexExpression + ); + dotIdentifierConsumerStack.push( + new BasicDotIdentifierConsumer( path, this ) { + @Override + protected void reset() { + } + } + ); + HqlParser.GeneralPathFragmentContext generalPathFragmentContext = ctx.generalPathFragment(); + if ( generalPathFragmentContext == null ) { + return path; + } + return (SemanticPathPart) generalPathFragmentContext.accept( this ); + } @Override public SemanticPathPart visitDotIdentifierSequence(HqlParser.DotIdentifierSequenceContext ctx) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index 0fbf081368..9235b74903 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -352,7 +352,7 @@ public class SqmTreePrinter implements SemanticQueryWalker { public Object visitUpdateStatement(SqmUpdateStatement statement) { if ( DEBUG_ENABLED ) { processStanza( - "update", + statement.isVersioned() ? "update versioned" : "update", () -> { logWithIndentation( "[target = %s]", statement.getTarget().getNavigablePath().getFullPath() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java index 8082044f2d..2275ad035e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java @@ -81,6 +81,7 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda assignments::add, parameterResolutions::put ); + sqmConverter.addVersionedAssignment( assignments::add, updateStatement ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // cross-reference the TableReference by alias. The TableGroup already diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java index 110f0adbf0..dad96bf279 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java @@ -156,6 +156,7 @@ public class TableBasedUpdateHandler k -> new ArrayList<>( 1 ) ).add( jdbcParameters ) ); + converterDelegate.addVersionedAssignment( assignments::add, getSqmDeleteOrUpdateStatement() ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // visit the where-clause using our special converter, collecting information 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 dea22f93c7..183a2c5566 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 @@ -7,6 +7,7 @@ package org.hibernate.query.sqm.sql; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -50,6 +51,7 @@ import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.mapping.ModelPart; @@ -64,6 +66,7 @@ import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource; +import org.hibernate.param.VersionTypeSeedParameterSpecification; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; @@ -173,12 +176,14 @@ import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationTarget; +import org.hibernate.query.sqm.tree.select.SqmJpaCompoundSelection; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; import org.hibernate.query.sqm.tree.select.SqmQueryGroup; import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.query.sqm.tree.select.SqmSelectableNode; import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.query.sqm.tree.select.SqmSortSpecification; import org.hibernate.query.sqm.tree.select.SqmSubQuery; @@ -275,13 +280,16 @@ import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl; import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.VersionType; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptorIndicators; import org.hibernate.type.spi.TypeConfiguration; +import org.hibernate.usertype.UserVersionType; import org.jboss.logging.Logger; import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; +import static org.hibernate.query.BinaryArithmeticOperator.ADD; import static org.hibernate.query.BinaryArithmeticOperator.MULTIPLY; import static org.hibernate.query.BinaryArithmeticOperator.SUBTRACT; import static org.hibernate.query.TemporalUnit.DAY; @@ -572,6 +580,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); final List assignments = visitSetClause( sqmStatement.getSetClause() ); + addVersionedAssignment( assignments::add, sqmStatement ); final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( getLoadQueryInfluencers(), @@ -625,6 +634,57 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } + public void addVersionedAssignment(Consumer assignmentConsumer, SqmUpdateStatement sqmStatement) { + if ( !sqmStatement.isVersioned() ) { + return; + } + final EntityPersister persister = creationContext.getDomainModel() + .findEntityDescriptor( sqmStatement.getTarget().getEntityName() ); + if ( !persister.isVersioned() ) { + throw new SemanticException( "increment option specified for update of non-versioned entity" ); + } + + final VersionType versionType = persister.getVersionType(); + if ( versionType instanceof UserVersionType ) { + throw new SemanticException( "user-defined version types not supported for increment option" ); + } + + final EntityVersionMapping versionMapping = persister.getVersionMapping(); + final List targetColumnReferences = BasicValuedPathInterpretation.from( + (SqmBasicValuedSimplePath) sqmStatement + .getRoot() + .get( versionMapping.getPartName() ), + this, + this + ).getColumnReferences(); + assert targetColumnReferences.size() == 1; + + final ColumnReference versionColumn = targetColumnReferences.get( 0 ); + final Expression value; + if ( isTimestampBasedVersion( versionType ) ) { + value = new VersionTypeSeedParameterSpecification( versionType ); + } + else { + final BasicValuedMapping basicValuedMapping = (BasicValuedMapping) versionType; + value = new BinaryArithmeticExpression( + versionColumn, + ADD, + new QueryLiteral<>( 1, basicValuedMapping ), + basicValuedMapping + ); + } + assignmentConsumer.accept( new Assignment( versionColumn, value ) ); + } + + private boolean isTimestampBasedVersion(VersionType versionType) { + if ( versionType instanceof BasicType ) { + return ( (BasicType) versionType ).getJdbcTypeDescriptor().isTemporal(); + } + final Class javaType = versionType.getReturnedClass(); + return java.util.Date.class.isAssignableFrom( javaType ) + || Calendar.class.isAssignableFrom( javaType ); + } + @Override public List visitSetClause(SqmSetClause setClause) { final List assignments = new ArrayList<>( setClause.getAssignments().size() ); @@ -830,6 +890,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); currentClauseStack.push( Clause.INSERT ); final InsertStatement insertStatement; + boolean needsVersionInsert = entityDescriptor.isVersioned(); try { final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath(); @@ -857,10 +918,34 @@ public abstract class BaseSqmToSqlAstConverter extends Base Collections.emptyList() ); - List targetPaths = sqmStatement.getInsertionTargetPaths(); - for ( SqmPath target : targetPaths ) { - Assignable assignable = (Assignable) target.accept( this ); - insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); + final List targetPaths = sqmStatement.getInsertionTargetPaths(); + if ( needsVersionInsert ) { + final String versionAttributeName = entityDescriptor.getVersionMapping().getVersionAttribute().getAttributeName(); + for ( int i = 0; i < targetPaths.size(); i++ ) { + final SqmPath path = targetPaths.get( i ); + if ( versionAttributeName.equals( path.getNavigablePath().getLocalName() ) ) { + needsVersionInsert = false; + } + final Assignable assignable = (Assignable) path.accept( this ); + insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); + } + if ( needsVersionInsert ) { + final List targetColumnReferences = BasicValuedPathInterpretation.from( + (SqmBasicValuedSimplePath) sqmStatement.getTarget() + .get( versionAttributeName ), + this, + this + ).getColumnReferences(); + assert targetColumnReferences.size() == 1; + + insertStatement.addTargetColumnReferences( targetColumnReferences ); + } + } + else { + for ( int i = 0; i < targetPaths.size(); i++ ) { + final Assignable assignable = (Assignable) targetPaths.get( i ).accept( this ); + insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); + } } } finally { @@ -872,6 +957,19 @@ public abstract class BaseSqmToSqlAstConverter extends Base visitQueryPart( selectQueryPart ) ); + if ( needsVersionInsert ) { + final VersionType versionType = entityDescriptor.getVersionType(); + final Expression expression = new VersionTypeSeedParameterSpecification( versionType ); + insertStatement.getSourceSelectStatement().forEachQuerySpec( + querySpec -> { + querySpec.getSelectClause().addSqlSelection( + // The position is irrelevant as this is only needed for insert + new SqlSelectionImpl( 1, 0, expression ) + ); + } + ); + } + return insertStatement; } @@ -1360,9 +1458,26 @@ public abstract class BaseSqmToSqlAstConverter extends Base } @Override - public Void visitSelection(SqmSelection sqmSelection) { + public Object visitSelection(SqmSelection sqmSelection) { currentSqlSelectionCollector().next(); - final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection ); + final Map> resultProducers; + if ( sqmSelection.getSelectableNode() instanceof SqmJpaCompoundSelection ) { + SqmJpaCompoundSelection selectableNode = (SqmJpaCompoundSelection) sqmSelection.getSelectableNode(); + resultProducers = new HashMap<>( selectableNode.getSelectionItems().size() ); + for ( SqmSelectableNode selectionItem : selectableNode.getSelectionItems() ) { + resultProducers.put( + selectionItem.getAlias(), + (DomainResultProducer) selectionItem.accept( this ) + ); + } + + } + else { + resultProducers = Collections.singletonMap( + sqmSelection.getAlias(), + (DomainResultProducer) sqmSelection.getSelectableNode().accept( this ) + ); + } if ( domainResults != null ) { final Stack processingStateStack = getProcessingStateStack(); @@ -1394,24 +1509,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base ) == null; } if ( collectDomainResults ) { - final DomainResult domainResult = resultProducer.createDomainResult( - sqmSelection.getAlias(), - this - ); - - domainResults.add( domainResult ); + resultProducers.forEach( (alias, r) -> domainResults.add( r.createDomainResult( alias, this ) ) ); } else { - resultProducer.applySqlSelections( this ); + resultProducers.forEach( (alias, r) -> r.applySqlSelections( this ) ); } } return null; } - private DomainResultProducer resolveDomainResultProducer(SqmSelection sqmSelection) { - return (DomainResultProducer) sqmSelection.getSelectableNode().accept( this ); - } - protected Expression resolveGroupOrOrderByExpression(SqmExpression groupByClauseExpression) { if ( groupByClauseExpression instanceof SqmLiteral ) { Object literal = ( (SqmLiteral) groupByClauseExpression ).getLiteralValue(); @@ -3539,7 +3645,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } @Override - public InSubQueryPredicate visitMemberOfPredicate(SqmMemberOfPredicate predicate) { + public Predicate visitMemberOfPredicate(SqmMemberOfPredicate predicate) { final SqmPath pluralPath = predicate.getPluralPath(); final PluralAttributeMapping mappingModelExpressable = (PluralAttributeMapping) determineValueMapping( pluralPath ); @@ -3564,63 +3670,58 @@ public abstract class BaseSqmToSqlAstConverter extends Base inferrableTypeAccessStack.pop(); } - return new InSubQueryPredicate( - lhs, - createMemberOfSubQuery( pluralPath, mappingModelExpressable ), - predicate.isNegated() - ); - } - - private QueryPart createMemberOfSubQuery(SqmPath pluralPath, PluralAttributeMapping mappingModelExpressable) { final FromClauseAccess parentFromClauseAccess = getFromClauseAccess(); - final QuerySpec querySpec = new QuerySpec( false ); + final QuerySpec subQuerySpec = new QuerySpec( false ); pushProcessingState( new SqlAstQueryPartProcessingStateImpl( - querySpec, + subQuerySpec, getCurrentProcessingState(), this, currentClauseStack::getCurrent ) ); try { - - final TableGroup rootTableGroup = mappingModelExpressable.createRootTableGroup( + final TableGroup tableGroup = mappingModelExpressable.createRootTableGroup( pluralPath.getNavigablePath(), null, true, LockOptions.NONE.getLockMode(), - () -> querySpec::applyPredicate, + () -> subQuerySpec::applyPredicate, this, creationContext ); - getFromClauseAccess().registerTableGroup( pluralPath.getNavigablePath(), rootTableGroup ); - - querySpec.getFromClause().addRoot( rootTableGroup ); + getFromClauseAccess().registerTableGroup( pluralPath.getNavigablePath(), tableGroup ); + subQuerySpec.getFromClause().addRoot( tableGroup ); final CollectionPart elementDescriptor = mappingModelExpressable.getElementDescriptor(); elementDescriptor.createDomainResult( pluralPath.getNavigablePath(), - rootTableGroup, + tableGroup, null, this ); - final Predicate predicate = mappingModelExpressable.getKeyDescriptor().generateJoinPredicate( - parentFromClauseAccess.findTableGroup( pluralPath.getNavigablePath().getParent() ), - rootTableGroup, - null, - getSqlExpressionResolver(), - creationContext + subQuerySpec.applyPredicate( + mappingModelExpressable.getKeyDescriptor().generateJoinPredicate( + parentFromClauseAccess.findTableGroup( pluralPath.getNavigablePath().getParent() ), + tableGroup, + SqlAstJoinType.INNER, + getSqlExpressionResolver(), + creationContext + ) ); - querySpec.applyPredicate( predicate ); } finally { popProcessingStateStack(); } - return querySpec; + return new InSubQueryPredicate( + lhs, + subQuerySpec, + predicate.isNegated() + ); } @Override @@ -3675,35 +3776,34 @@ public abstract class BaseSqmToSqlAstConverter extends Base assert parentNavPath != null; final TableGroup parentTableGroup = parentFromClauseAccess.getTableGroup( parentNavPath ); - + final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() ); + final TableGroup tableGroup = new CorrelatedTableGroup( + parentTableGroup, + sqlAliasBase, + subQuerySpec, + subQuerySpec::applyPredicate, + creationContext.getSessionFactory() + ); subQueryState.getSqlAstCreationState().getFromClauseAccess().registerTableGroup( parentNavPath, - parentTableGroup + tableGroup ); final SqmPathInterpretation sqmPathInterpretation = visitPluralValuedPath( sqmPluralPath ); - - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) sqmPathInterpretation.getExpressionType(); - - // note : do not add to `parentTableGroup` as a join - - final TableGroupJoin tableGroupJoin = pluralAttributeMapping.createTableGroupJoin( + // The creation of the table group join against the correlated table group + // has the side effect that the from and where clause of the sub-query are set + pluralAttributeMapping.createTableGroupJoin( pluralPathNavPath, - parentTableGroup, + tableGroup, sqmPluralPath.getExplicitAlias(), - SqlAstJoinType.LEFT, + SqlAstJoinType.INNER, LockMode.NONE, sqlAliasBaseManager, subQueryState, creationContext ); - final TableGroup collectionTableGroup = tableGroupJoin.getJoinedGroup(); - - subQuerySpec.getFromClause().addRoot( collectionTableGroup ); - subQuerySpec.applyPredicate( tableGroupJoin.getPredicate() ); - final ForeignKeyDescriptor collectionKeyDescriptor = pluralAttributeMapping.getKeyDescriptor(); final int jdbcTypeCount = collectionKeyDescriptor.getJdbcTypeCount(); assert jdbcTypeCount > 0; @@ -3713,7 +3813,11 @@ public abstract class BaseSqmToSqlAstConverter extends Base new SqlSelectionImpl( 1, 0, jdbcLiteral ) ); - return new ExistsPredicate( subQuerySpec ); + final ExistsPredicate existsPredicate = new ExistsPredicate( subQuerySpec ); + if ( predicate.isNegated() ) { + return existsPredicate; + } + return new NegatedPredicate( existsPredicate ); } finally { popProcessingStateStack(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java index cdd382eb8c..696daaef0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java @@ -35,6 +35,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; public class SqmUpdateStatement extends AbstractSqmDmlStatement implements SqmDeleteOrUpdateStatement, JpaCriteriaUpdate { + private boolean versioned; private SqmSetClause setClause; private SqmWhereClause whereClause; @@ -133,6 +134,23 @@ public class SqmUpdateStatement throw new NotYetImplementedFor6Exception(); } + @Override + public boolean isVersioned() { + return versioned; + } + + @Override + public SqmUpdateStatement versioned() { + this.versioned = true; + return this; + } + + @Override + public SqmUpdateStatement versioned(boolean versioned) { + this.versioned = versioned; + return this; + } + @Override public SqmUpdateStatement where(Expression restriction) { getWhereClause().setPredicate( (SqmPredicate) restriction ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java index 072478cf9c..45e9210d70 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java @@ -15,7 +15,7 @@ import org.hibernate.sql.ast.tree.select.QueryPart; */ public class ExistsPredicate implements Predicate { - private QueryPart expression; + private final QueryPart expression; public ExistsPredicate(QueryPart expression) { this.expression = expression; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java index ab721bc8f6..596c662119 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java @@ -7,6 +7,7 @@ package org.hibernate.sql.ast.tree.select; import java.util.List; +import java.util.function.Consumer; import org.hibernate.query.SetOperator; import org.hibernate.metamodel.mapping.MappingModelExpressable; @@ -37,6 +38,13 @@ public class QueryGroup extends QueryPart { return queryParts.get( queryParts.size() - 1 ).getLastQuerySpec(); } + @Override + public void forEachQuerySpec(Consumer querySpecConsumer) { + for ( int i = 0; i < queryParts.size(); i++ ) { + queryParts.get( i ).forEachQuerySpec( querySpecConsumer ); + } + } + public SetOperator getSetOperator() { return setOperator; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java index a61bd8a4a1..e331211eae 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java @@ -34,6 +34,8 @@ public abstract class QueryPart implements SqlAstNode, Expression, DomainResultP public abstract QuerySpec getLastQuerySpec(); + public abstract void forEachQuerySpec(Consumer querySpecConsumer); + /** * Does this QueryPart map to the statement's root query (as * opposed to one of its sub-queries)? diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java index 9a943dec80..229f3ac2af 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java @@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.select; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; @@ -59,6 +60,11 @@ public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContain return this; } + @Override + public void forEachQuerySpec(Consumer querySpecConsumer) { + querySpecConsumer.accept( this ); + } + public FromClause getFromClause() { return fromClause; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java index 2166fa39cc..7cf6a421ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java @@ -137,6 +137,17 @@ public interface JdbcTypeDescriptor extends Serializable { return false; } + default boolean isTemporal() { + switch ( getSqlType() ) { + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: + return true; + } + return false; + } + default CastType getCastType() { switch ( getJdbcType() ) { case Types.INTEGER: