From 032fdb5d2eebecf56de8d35f640c2c30ee714cf4 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 20 Nov 2019 10:27:50 -0600 Subject: [PATCH] HHH-13715 - working support for "multi-table" HQL/Criteria UPDATE and DELETE queries CTE, id-table and in-line strategies are all implemented (though only id-table is tested); refactoring for performance (direct creation of SQL AST object directly, rather than SQM -> SQL AST) and as part of initial impls for remaining strategies (global temp and persistent id tables, and the "inline" strategy; fixed concurrency bug (thanks Luis!) --- .../internal/StrategySelectorBuilder.java | 6 +- .../dialect/AbstractTransactSQLDialect.java | 3 +- .../org/hibernate/dialect/DerbyDialect.java | 3 +- .../java/org/hibernate/dialect/H2Dialect.java | 3 +- .../hibernate/dialect/InformixDialect.java | 3 +- .../org/hibernate/dialect/MySQLDialect.java | 3 +- .../entity/AbstractEntityPersister.java | 22 +- .../entity/JoinedSubclassEntityPersister.java | 4 +- .../entity/SingleTableEntityPersister.java | 4 +- .../internal/MultiTableDeleteQueryPlan.java | 18 +- .../internal/MultiTableUpdateQueryPlan.java | 18 +- .../query/sqm/internal/QuerySqmImpl.java | 22 +- .../sqm/internal/UpdateQueryPlanImpl.java | 2 +- .../{spi => internal}/DeleteHandler.java | 2 +- .../mutation/{spi => internal}/Handler.java | 8 +- .../internal/MatchingIdSelectionHelper.java | 232 +++++++++++++++++ .../MultiTableSqmMutationConverter.java | 4 +- .../internal/SqmIdSelectGenerator.java | 128 --------- .../internal/SqmMutationStrategyHelper.java | 242 +----------------- .../{spi => internal}/UpdateHandler.java | 2 +- .../cte/AbstractCteMutationHandler.java | 12 +- .../internal/cte/CteDeleteHandler.java | 56 ++-- ...MutationStrategy.java => CteStrategy.java} | 79 ++---- .../internal/cte/CteUpdateHandler.java | 11 +- .../idtable/ExecuteWithIdTableHelper.java | 241 +++++++---------- .../idtable/GlobalTemporaryTableStrategy.java | 183 ++++++++++++- .../internal/idtable/IdTableHelper.java | 78 +----- .../idtable/LocalTemporaryTableStrategy.java | 70 ++--- .../idtable/PersistentTableStrategy.java | 178 ++++++++++++- .../RestrictedDeleteExecutionDelegate.java | 163 +++--------- .../idtable/TableBasedDeleteHandler.java | 10 +- .../idtable/TableBasedUpdateHandler.java | 35 +-- .../idtable/UpdateExecutionDelegate.java | 5 +- .../DisjunctionRestrictionProducer.java | 121 +++++++++ .../InPredicateRestrictionProducer.java | 113 ++++++++ .../internal/inline/InlineDeleteHandler.java | 176 +++++++++++++ .../internal/inline/InlineStrategy.java | 72 ++++++ .../internal/inline/InlineUpdateHandler.java | 53 ++++ .../inline/MatchingIdRestrictionProducer.java | 39 +++ ...leValueConstructorRestrictionProducer.java | 57 +++++ .../mutation/spi/AbstractMutationHandler.java | 5 +- .../mutation/spi/HandlerCreationContext.java | 21 -- .../spi/SqmMultiTableMutationStrategy.java | 49 ++-- .../sqm/sql/BaseSqmToSqlAstConverter.java | 9 +- .../org/hibernate/sql/ast/SqlTreePrinter.java | 9 +- .../sql/ast/spi/AbstractSqlAstWalker.java | 37 +-- .../ast/spi/DerbyCaseExpressionWalker.java | 2 +- .../hibernate/sql/ast/spi/SqlAstWalker.java | 3 + .../hibernate/sql/ast/tree/cte/CteTable.java | 13 +- .../sql/ast/tree/cte/CteTableGroup.java | 5 +- .../sql/ast/tree/expression/JdbcLiteral.java | 176 +++++++++++++ .../sql/ast/tree/expression/Literal.java | 18 ++ .../sql/ast/tree/expression/QueryLiteral.java | 105 +++++++- .../sql/exec/spi/StatementCreatorHelper.java | 26 ++ .../mutation/multitable/IdSelectionTests.java | 208 +++++++++++++++ 55 files changed, 2118 insertions(+), 1049 deletions(-) rename hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/{spi => internal}/DeleteHandler.java (87%) rename hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/{spi => internal}/Handler.java (64%) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java rename hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/{idtable => }/MultiTableSqmMutationConverter.java (99%) delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java rename hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/{spi => internal}/UpdateHandler.java (87%) rename hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/{CteBasedMutationStrategy.java => CteStrategy.java} (65%) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/DisjunctionRestrictionProducer.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/MatchingIdRestrictionProducer.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/TableValueConstructorRestrictionProducer.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/HandlerCreationContext.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Literal.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/mutation/multitable/IdSelectionTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index 14fc0b66c2..93f1cde1e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -89,7 +89,7 @@ import org.hibernate.engine.transaction.jta.platform.internal.WebSphereJtaPlatfo import org.hibernate.engine.transaction.jta.platform.internal.WebSphereLibertyJtaPlatform; import org.hibernate.engine.transaction.jta.platform.internal.WeblogicJtaPlatform; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; -import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy; +import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; import org.hibernate.query.sqm.mutation.internal.idtable.GlobalTemporaryTableStrategy; import org.hibernate.query.sqm.mutation.internal.idtable.LocalTemporaryTableStrategy; import org.hibernate.query.sqm.mutation.internal.idtable.PersistentTableStrategy; @@ -410,8 +410,8 @@ public class StrategySelectorBuilder { private void addSqmMultiTableMutationStrategies(StrategySelectorImpl strategySelector) { strategySelector.registerStrategyImplementor( SqmMultiTableMutationStrategy.class, - CteBasedMutationStrategy.SHORT_NAME, - CteBasedMutationStrategy.class + CteStrategy.SHORT_NAME, + CteStrategy.class ); strategySelector.registerStrategyImplementor( SqmMultiTableMutationStrategy.class, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java index 16fefb1446..e5eb98e1e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -223,7 +223,8 @@ abstract class AbstractTransactSQLDialect extends Dialect { // // sql-server, at least needed this dropped after use; strange! this::getTypeName, AfterUseAction.DROP, - TempTableDdlTransactionHandling.NONE + TempTableDdlTransactionHandling.NONE, + runtimeModelCreationContext.getSessionFactory() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index 69e91963fa..fc92944cc4 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -618,7 +618,8 @@ public class DerbyDialect extends DB2Dialect { } }, AfterUseAction.CLEAN, - TempTableDdlTransactionHandling.NONE + TempTableDdlTransactionHandling.NONE, + runtimeModelCreationContext.getSessionFactory() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 5e9957df3d..ddab75f3d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -379,7 +379,8 @@ public class H2Dialect extends Dialect { new IdTable( entityDescriptor, basename -> "HT_" + basename ), this::getTypeName, AfterUseAction.CLEAN, - TempTableDdlTransactionHandling.NONE + TempTableDdlTransactionHandling.NONE, + runtimeModelCreationContext.getSessionFactory() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java index b64781d5e9..7d437f4990 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java @@ -301,7 +301,8 @@ public class InformixDialect extends Dialect { } }, AfterUseAction.NONE, - TempTableDdlTransactionHandling.NONE + TempTableDdlTransactionHandling.NONE, + runtimeModelCreationContext.getSessionFactory() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index 2c661b9959..78b140ca21 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -366,7 +366,8 @@ public class MySQLDialect extends Dialect { } }, AfterUseAction.DROP, - TempTableDdlTransactionHandling.NONE + TempTableDdlTransactionHandling.NONE, + runtimeModelCreationContext.getSessionFactory() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 4c86036cfd..685d75aaec 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -144,7 +144,6 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.JdbcMapping; -import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.Queryable; @@ -169,7 +168,6 @@ import org.hibernate.property.access.spi.Setter; import org.hibernate.query.ComparisonOperator; import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; -import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.Alias; @@ -6201,6 +6199,18 @@ public abstract class AbstractEntityPersister else { sqmMultiTableMutationStrategy = null; } + + // register a callback for after all `#prepareMappingModel` calls have finished. here we want to delay the + // generation of `staticFetchableList` because we need to wait until after all sub-classes have had their + // `#prepareMappingModel` called (and their declared attribute mappings resolved) + creationProcess.registerInitializationCallback( + () -> { + staticFetchableList = new ArrayList<>( attributeMappings.size() ); + visitAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) ); + visitSubTypeAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) ); + return true; + } + ); } protected static SqmMultiTableMutationStrategy interpretSqmMultiTableStrategy( @@ -6220,7 +6230,7 @@ public abstract class AbstractEntityPersister return SqmMutationStrategyHelper.resolveStrategy( entityBootDescriptor, entityMappingDescriptor, - creationProcess.getCreationContext() + creationProcess ); } @@ -6607,12 +6617,6 @@ public abstract class AbstractEntityPersister } protected List getStaticFetchableList() { - if ( staticFetchableList == null ) { - staticFetchableList = new ArrayList<>( attributeMappings.size() ); - visitAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) ); - - visitSubTypeAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) ); - } return staticFetchableList; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index 4dea660c11..30d8722aba 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -59,7 +59,6 @@ import org.hibernate.sql.CaseFragment; import org.hibernate.sql.InFragment; import org.hibernate.sql.Insert; import org.hibernate.sql.SelectFragment; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; @@ -1320,8 +1319,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { final Expression expression = new QueryLiteral<>( discriminatorValuesByTableName.get( table.getTableExpression() ), - resultType, - Clause.SELECT + resultType ); caseSearchedExpression.when( predicate, expression ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index 01a2083048..f0d81db2ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -46,7 +46,6 @@ import org.hibernate.query.NavigablePath; import org.hibernate.sql.InFragment; import org.hibernate.sql.Insert; import org.hibernate.sql.SelectFragment; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; @@ -915,8 +914,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { ComparisonOperator.EQUAL, new QueryLiteral<>( getDiscriminatorValue(), - ( (BasicType) getDiscriminatorType() ), - Clause.WHERE + ( (BasicType) getDiscriminatorType() ) ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java index 1eebf9aa0b..126d3cd5cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java @@ -7,21 +7,29 @@ package org.hibernate.query.sqm.internal; import org.hibernate.query.spi.NonSelectQueryPlan; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.sql.exec.spi.ExecutionContext; /** * @author Steve Ebersole */ public class MultiTableDeleteQueryPlan implements NonSelectQueryPlan { - private final DeleteHandler deleteHandler; + private final SqmDeleteStatement sqmDelete; + private final DomainParameterXref domainParameterXref; + private final SqmMultiTableMutationStrategy deleteStrategy; - public MultiTableDeleteQueryPlan(DeleteHandler deleteHandler) { - this.deleteHandler = deleteHandler; + public MultiTableDeleteQueryPlan( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy deleteStrategy) { + this.sqmDelete = sqmDelete; + this.domainParameterXref = domainParameterXref; + this.deleteStrategy = deleteStrategy; } @Override public int executeUpdate(ExecutionContext executionContext) { - return deleteHandler.execute( executionContext ); + return deleteStrategy.executeDelete( sqmDelete, domainParameterXref, executionContext ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java index e041cdbd89..c33a1da8d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java @@ -7,21 +7,29 @@ package org.hibernate.query.sqm.internal; import org.hibernate.query.spi.NonSelectQueryPlan; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.exec.spi.ExecutionContext; /** * @author Steve Ebersole */ public class MultiTableUpdateQueryPlan implements NonSelectQueryPlan { - private final UpdateHandler updateHandler; + private final SqmUpdateStatement sqmUpdate; + private final DomainParameterXref domainParameterXref; + private final SqmMultiTableMutationStrategy mutationStrategy; - public MultiTableUpdateQueryPlan(UpdateHandler updateHandler) { - this.updateHandler = updateHandler; + public MultiTableUpdateQueryPlan( + SqmUpdateStatement sqmUpdate, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy mutationStrategy) { + this.sqmUpdate = sqmUpdate; + this.domainParameterXref = domainParameterXref; + this.mutationStrategy = mutationStrategy; } @Override public int executeUpdate(ExecutionContext executionContext) { - return updateHandler.execute( executionContext ); + return mutationStrategy.executeUpdate( sqmUpdate, domainParameterXref, executionContext ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index e730a6a209..4acc8f3bb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -545,34 +545,22 @@ public class QuerySqmImpl return new SimpleDeleteQueryPlan( sqmDelete, domainParameterXref ); } else { - return new MultiTableDeleteQueryPlan( - multiTableStrategy.buildDeleteHandler( - sqmDelete, - domainParameterXref, - this::getSessionFactory - ) - ); + return new MultiTableDeleteQueryPlan( sqmDelete, domainParameterXref, multiTableStrategy ); } } private NonSelectQueryPlan buildUpdateQueryPlan() { - final SqmUpdateStatement sqmStatement = (SqmUpdateStatement) getSqmStatement(); + final SqmUpdateStatement sqmUpdate = (SqmUpdateStatement) getSqmStatement(); - final String entityNameToUpdate = sqmStatement.getTarget().getReferencedPathSource().getHibernateEntityName(); + final String entityNameToUpdate = sqmUpdate.getTarget().getReferencedPathSource().getHibernateEntityName(); final EntityPersister entityDescriptor = getSessionFactory().getDomainModel().findEntityDescriptor( entityNameToUpdate ); final SqmMultiTableMutationStrategy multiTableStrategy = entityDescriptor.getSqmMultiTableMutationStrategy(); if ( multiTableStrategy == null ) { - return new SimpleUpdateQueryPlan( sqmStatement, domainParameterXref ); + return new SimpleUpdateQueryPlan( sqmUpdate, domainParameterXref ); } else { - return new MultiTableUpdateQueryPlan( - multiTableStrategy.buildUpdateHandler( - sqmStatement, - domainParameterXref, - this::getSessionFactory - ) - ); + return new MultiTableUpdateQueryPlan( sqmUpdate, domainParameterXref, multiTableStrategy ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/UpdateQueryPlanImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/UpdateQueryPlanImpl.java index 2d9d580d19..91338ca90a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/UpdateQueryPlanImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/UpdateQueryPlanImpl.java @@ -7,7 +7,7 @@ package org.hibernate.query.sqm.internal; import org.hibernate.query.spi.NonSelectQueryPlan; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; +import org.hibernate.query.sqm.mutation.internal.UpdateHandler; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.exec.spi.ExecutionContext; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/DeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/DeleteHandler.java similarity index 87% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/DeleteHandler.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/DeleteHandler.java index dc21167534..06bb963dcd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/DeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/DeleteHandler.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.query.sqm.mutation.spi; +package org.hibernate.query.sqm.mutation.internal; /** * Handler for dealing with multi-table SQM DELETE queries. diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/Handler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/Handler.java similarity index 64% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/Handler.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/Handler.java index 84848f4b07..b5bd0302e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/Handler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/Handler.java @@ -4,11 +4,17 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.query.sqm.mutation.spi; +package org.hibernate.query.sqm.mutation.internal; import org.hibernate.sql.exec.spi.ExecutionContext; /** + * Simply as a matter of code structuring it is often worthwhile to put all of the execution code into a separate + * handler (executor) class. This contract helps unify those helpers. + * + * Hiding this "behind the strategy" also allows mixing approaches based on the nature of specific + * queries + * * @author Steve Ebersole */ public interface Handler { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java new file mode 100644 index 0000000000..a62cd601df --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java @@ -0,0 +1,232 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.sql.ast.SqlAstSelectTranslator; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.sql.results.internal.domain.basic.BasicResult; +import org.hibernate.sql.results.spi.DomainResult; + +import org.jboss.logging.Logger; + +/** + * Helper used to generate the SELECT for selection of an entity's identifier, here specifically intended to be used + * as the SELECT portion of a multi-table SQM mutation + * + * @author Steve Ebersole + */ +public class MatchingIdSelectionHelper { + private static final Logger log = Logger.getLogger( MatchingIdSelectionHelper.class ); + + /** + * @asciidoc + * + * Generates a query-spec for selecting all ids matching the restriction defined as part + * of the user's update/delete query. This query-spec is generally used: + * + * * to select all the matching ids via JDBC - see {@link MatchingIdSelectionHelper#selectMatchingIds} + * * as a sub-query restriction to insert rows into an "id table" + */ + public static SelectStatement generateMatchingIdSelectStatement( + EntityMappingType targetEntityDescriptor, + SqmDeleteOrUpdateStatement sqmStatement, + Predicate restriction, + MultiTableSqmMutationConverter sqmConverter, + SessionFactoryImplementor sessionFactory) { + final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel(); + log.tracef( "Starting generation of entity-id SQM selection - %s", entityDomainType.getHibernateEntityName() ); + + final QuerySpec idSelectionQuery = new QuerySpec( true, 1 ); + + final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); + idSelectionQuery.getFromClause().addRoot( mutatingTableGroup ); + + final List domainResults = new ArrayList<>(); + final AtomicInteger i = new AtomicInteger(); + targetEntityDescriptor.getIdentifierMapping().visitColumns( + (columnExpression, containingTableExpression, jdbcMapping) -> { + final int position = i.getAndIncrement(); + final TableReference tableReference = mutatingTableGroup.resolveTableReference( containingTableExpression ); + final Expression expression = sqmConverter.getSqlExpressionResolver().resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ), + sqlAstProcessingState -> new ColumnReference( + tableReference, + columnExpression, + jdbcMapping, + sessionFactory + ) + ); + idSelectionQuery.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + position, + position + 1, + expression, + jdbcMapping + ) + ); + + //noinspection unchecked + domainResults.add( new BasicResult( position, null, jdbcMapping.getJavaTypeDescriptor() ) ); + + } + ); + + idSelectionQuery.applyPredicate( restriction ); + + return new SelectStatement( idSelectionQuery, domainResults ); + } + /** + * @asciidoc + * + * Generates a query-spec for selecting all ids matching the restriction defined as part + * of the user's update/delete query. This query-spec is generally used: + * + * * to select all the matching ids via JDBC - see {@link MatchingIdSelectionHelper#selectMatchingIds} + * * as a sub-query restriction to insert rows into an "id table" + */ + public static QuerySpec generateMatchingIdSelectQuery( + EntityMappingType targetEntityDescriptor, + SqmDeleteOrUpdateStatement sqmStatement, + DomainParameterXref domainParameterXref, + Predicate restriction, + MultiTableSqmMutationConverter sqmConverter, + SessionFactoryImplementor sessionFactory) { + final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel(); + log.tracef( "Starting generation of entity-id SQM selection - %s", entityDomainType.getHibernateEntityName() ); + + + final QuerySpec idSelectionQuery = new QuerySpec( true, 1 ); + + final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); + idSelectionQuery.getFromClause().addRoot( mutatingTableGroup ); + + final AtomicInteger i = new AtomicInteger(); + targetEntityDescriptor.getIdentifierMapping().visitColumns( + (columnExpression, containingTableExpression, jdbcMapping) -> { + final int position = i.getAndIncrement(); + final TableReference tableReference = mutatingTableGroup.resolveTableReference( containingTableExpression ); + final Expression expression = sqmConverter.getSqlExpressionResolver().resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ), + sqlAstProcessingState -> new ColumnReference( + tableReference, + columnExpression, + jdbcMapping, + sessionFactory + ) + ); + idSelectionQuery.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + position, + position + 1, + expression, + jdbcMapping + ) + ); + } + ); + + idSelectionQuery.applyPredicate( restriction ); + + return idSelectionQuery; + } + + /** + * Centralized selection of ids matching the restriction of the DELETE + * or UPDATE SQM query + */ + public static List selectMatchingIds( + SqmDeleteOrUpdateStatement sqmMutationStatement, + DomainParameterXref domainParameterXref, + ExecutionContext executionContext) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final EntityMappingType entityDescriptor = factory.getDomainModel() + .getEntityDescriptor( sqmMutationStatement.getTarget().getModel().getHibernateEntityName() ); + + final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( + entityDescriptor, + domainParameterXref, + executionContext.getQueryOptions(), + executionContext.getQueryParameterBindings(), + factory + ); + + + final Map> parameterResolutions; + if ( domainParameterXref.getSqmParameterCount() == 0 ) { + parameterResolutions = Collections.emptyMap(); + } + else { + parameterResolutions = new IdentityHashMap<>(); + } + + final Predicate restriction = sqmConverter.visitWhereClause( + sqmMutationStatement.getWhereClause(), + columnReference -> {}, + parameterResolutions::put + ); + + final SelectStatement matchingIdSelection = generateMatchingIdSelectStatement( + entityDescriptor, + sqmMutationStatement, + restriction, + sqmConverter, + factory + ); + + + final JdbcServices jdbcServices = factory.getJdbcServices(); + final SqlAstSelectTranslator sqlAstSelectTranslator = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildSelectTranslator( factory ); + + final JdbcSelect idSelectJdbcOperation = sqlAstSelectTranslator.translate( matchingIdSelection ); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ), + factory.getDomainModel(), + navigablePath -> sqmConverter.getMutatingTableGroup(), + executionContext.getSession() + ); + + return jdbcServices.getJdbcSelectExecutor().list( + idSelectJdbcOperation, + jdbcParameterBindings, + executionContext, + row -> row + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java similarity index 99% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/MultiTableSqmMutationConverter.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index 9e83eb6442..7990d94493 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.query.sqm.mutation.internal.idtable; +package org.hibernate.query.sqm.mutation.internal; import java.util.List; import java.util.function.BiConsumer; @@ -109,7 +109,6 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter imp return super.getProcessingStateStack(); } - /** * Specialized hook to visit the assignments defined by the update SQM allow * "listening" for each SQL assignment. @@ -128,6 +127,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter imp public List visitSetClause(SqmSetClause setClause) { throw new UnsupportedOperationException(); } + private void visitAssignment( SqmAssignment sqmAssignment, Consumer assignmentConsumer) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java deleted file mode 100644 index 9eaa6a6d72..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.mutation.internal; - -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.internal.util.collections.StandardStack; -import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.query.hql.spi.SqmCreationOptions; -import org.hibernate.query.hql.spi.SqmCreationProcessingState; -import org.hibernate.query.hql.spi.SqmCreationState; -import org.hibernate.query.sqm.internal.SqmQuerySpecCreationProcessingStateStandardImpl; -import org.hibernate.query.sqm.spi.SqmCreationContext; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.domain.SqmPath; -import org.hibernate.query.sqm.tree.from.SqmFromClause; -import org.hibernate.query.sqm.tree.from.SqmRoot; -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.SqmSelection; - -import org.jboss.logging.Logger; - -/** - * Helper used to generate the SELECT for selection of an entity's - * identifier, here specifically intended to be used as the SELECT - * portion of a multi-table SQM mutation - * - * @author Steve Ebersole - */ -public class SqmIdSelectGenerator { - private static final Logger log = Logger.getLogger( SqmIdSelectGenerator.class ); - - /** - * @asciidoc - * - * Generates a query-spec for selecting all ids matching the restriction defined as part - * of the user's update/delete query. This query-spec is generally used: - * - * * to select all the matching ids via JDBC - see {@link SqmMutationStrategyHelper#selectMatchingIds} - * * as a sub-query restriction to insert rows into an "id table" - */ - public static SqmQuerySpec generateSqmEntityIdSelect( - SqmDeleteOrUpdateStatement sqmStatement, - SqmCreationContext sqmCreationContext) { - final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel(); - - log.tracef( "Starting generation of entity-id SQM selection - %s", entityDomainType.getHibernateEntityName() ); - - final SqmQuerySpec sqmQuerySpec = new SqmQuerySpec( sqmCreationContext.getNodeBuilder() ); - - final Stack processingStateStack = new StandardStack<>(); - - final SqmCreationState creationState = new SqmCreationState() { - @Override - public SqmCreationContext getCreationContext() { - return sqmCreationContext; - } - - @Override - public SqmCreationOptions getCreationOptions() { - return () -> false; - } - - @Override - public Stack getProcessingStateStack() { - return processingStateStack; - } - }; - - - // temporary - used just for creating processingState - final SqmSelectStatement sqmSelectStatement = new SqmSelectStatement( sqmCreationContext.getNodeBuilder() ); - //noinspection unchecked - sqmSelectStatement.setQuerySpec( sqmQuerySpec ); - - final SqmCreationProcessingState processingState = new SqmQuerySpecCreationProcessingStateStandardImpl( - null, - sqmSelectStatement, - creationState - ); - - processingStateStack.push( processingState ); - - final SqmFromClause sqmFromClause = new SqmFromClause(); - sqmQuerySpec.setFromClause( sqmFromClause ); - - - //noinspection unchecked -// final SqmRoot sqmRoot = new SqmRoot( entityDomainType, null, sqmCreationContext.getNodeBuilder() ); - final SqmRoot sqmRoot = sqmStatement.getTarget(); - - log.debugf( "Using SqmRoot [%s] as root for entity id-select", sqmRoot ); - sqmFromClause.addRoot( sqmRoot ); - - final SqmSelectClause sqmSelectClause = new SqmSelectClause( true, sqmCreationContext.getNodeBuilder() ); - sqmQuerySpec.setSelectClause( sqmSelectClause ); - applySelections( sqmQuerySpec, sqmRoot, processingState ); - - if ( sqmStatement.getWhereClause() != null ) { - sqmQuerySpec.applyPredicate( sqmStatement.getWhereClause().getPredicate() ); - } - - return sqmQuerySpec; - } - - private static void applySelections( - SqmQuerySpec sqmQuerySpec, - SqmRoot sqmRoot, - SqmCreationProcessingState processingState) { - //noinspection unchecked - final SqmPath idPath = sqmRoot.getModel().getIdentifierDescriptor().createSqmPath( sqmRoot, processingState.getCreationState() ); - - //noinspection unchecked - sqmQuerySpec.getSelectClause().add( - new SqmSelection( - idPath, - null, - processingState.getCreationState().getCreationContext().getNodeBuilder() - ) - ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java index 1eb4826aee..811a46381a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java @@ -6,46 +6,14 @@ */ package org.hibernate.query.sqm.mutation.internal; -import java.util.List; -import java.util.Map; - import org.hibernate.boot.spi.SessionFactoryOptions; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.mapping.RootClass; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; -import org.hibernate.query.sqm.sql.SqmSelectTranslation; -import org.hibernate.query.sqm.sql.SqmSelectTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.domain.SqmSimplePath; -import org.hibernate.query.sqm.tree.expression.SqmExpression; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate; -import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; -import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate; -import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate; -import org.hibernate.query.sqm.tree.predicate.SqmJunctivePredicate; -import org.hibernate.query.sqm.tree.predicate.SqmPredicate; -import org.hibernate.query.sqm.tree.select.SqmQuerySpec; -import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcParameter; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcSelect; /** * @author Steve Ebersole @@ -66,220 +34,22 @@ public class SqmMutationStrategyHelper { public static SqmMultiTableMutationStrategy resolveStrategy( RootClass entityBootDescriptor, EntityMappingType rootEntityDescriptor, - RuntimeModelCreationContext creationContext) { + MappingModelCreationProcess creationProcess) { + final RuntimeModelCreationContext creationContext = creationProcess.getCreationContext(); final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); final SessionFactoryOptions options = sessionFactory.getSessionFactoryOptions(); + final SqmMultiTableMutationStrategy specifiedStrategy = options.getSqmMultiTableMutationStrategy(); if ( specifiedStrategy != null ) { return specifiedStrategy; } + // todo (6.0) : add capability define strategy per-hierarchy + return sessionFactory.getServiceRegistry().getService( JdbcServices.class ) .getJdbcEnvironment() .getDialect() .getFallbackSqmMutationStrategy( rootEntityDescriptor, creationContext ); } - /** - * Specialized "Supplier" or "tri Function" for creating the - * fallback handler if the query matches no "special cases" - */ - public interface FallbackDeleteHandlerCreator { - DeleteHandler create( - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext); - } - - /** - * Standard DeleteHandler resolution applying "special case" resolution - */ - public static DeleteHandler resolveDeleteHandler( - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext, - FallbackDeleteHandlerCreator fallbackCreator) { - if ( sqmDelete.getWhereClause() == null ) { - // special case : unrestricted - // -> delete all rows, no need to use the id table - } - else { - // if the predicate contains refers to any non-id Navigable, we will need to use the id table - if ( ! hasNonIdReferences( sqmDelete.getWhereClause().getPredicate() ) ) { - // special case : not restricted on non-id Navigable reference - // -> we can apply the original restriction to the individual - // - // todo (6.0) : technically non-id references where the reference is mapped to the primary table - // can also be handled by this special case. Really the special case condition is "has - // any non-id references to Navigables not mapped to the primary table of the container" - } - } - - // otherwise, use the fallback.... - return fallbackCreator.create( sqmDelete, domainParameterXref, creationContext ); - } - - /** - * Specialized "Supplier" or "tri Function" for creating the - * fallback handler if the query mmatches no "special cases" - */ - public interface FallbackUpdateHandlerCreator { - UpdateHandler create( - SqmUpdateStatement sqmUpdate, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext); - } - - /** - * Standard UpdateHandler resolution applying "special case" resolution - */ - public static UpdateHandler resolveUpdateHandler( - SqmUpdateStatement sqmUpdate, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext, - FallbackUpdateHandlerCreator fallbackCreator) { - if ( sqmUpdate.getWhereClause() == null ) { - // special case : unrestricted - // -> delete all rows, no need to use the id table - } - else { - // see if the predicate contains any non-id Navigable references - if ( ! hasNonIdReferences( sqmUpdate.getWhereClause().getPredicate() ) ) { - // special case : not restricted on non-id Navigable reference - // -> we can apply the original restriction to the individual updates without needing to use the id-table - // - // todo (6.0) : technically non-id references where the reference is mapped to the primary table - // can also be handled by this special case. Really the special case condition is "has - // any non-id references to Navigables not mapped to the primary table of the container" - } - } - - // todo (6.0) : implement the above special cases - - // otherwise, use the fallback.... - return fallbackCreator.create( sqmUpdate, domainParameterXref, creationContext ); - } - - /** - * Does the given `predicate` "non-identifier Navigable references"? - * - * @see #isNonIdentifierReference - */ - @SuppressWarnings("WeakerAccess") - public static boolean hasNonIdReferences(SqmPredicate predicate) { - if ( predicate instanceof SqmGroupedPredicate ) { - return hasNonIdReferences( ( (SqmGroupedPredicate) predicate ).getSubPredicate() ); - } - - if ( predicate instanceof SqmJunctivePredicate ) { - return hasNonIdReferences( ( (SqmJunctivePredicate) predicate ).getLeftHandPredicate() ) - && hasNonIdReferences( ( (SqmJunctivePredicate) predicate ).getRightHandPredicate() ); - } - - if ( predicate instanceof SqmComparisonPredicate ) { - final SqmExpression lhs = ( (SqmComparisonPredicate) predicate ).getLeftHandExpression(); - final SqmExpression rhs = ( (SqmComparisonPredicate) predicate ).getRightHandExpression(); - - return isNonIdentifierReference( lhs ) || isNonIdentifierReference( rhs ); - } - - if ( predicate instanceof SqmInListPredicate ) { - final SqmInListPredicate inPredicate = (SqmInListPredicate) predicate; - if ( isNonIdentifierReference( inPredicate.getTestExpression() ) ) { - return true; - } - - for ( SqmExpression listExpression : inPredicate.getListExpressions() ) { - if ( isNonIdentifierReference( listExpression ) ) { - return true; - } - } - - return false; - } - - if ( predicate instanceof SqmBetweenPredicate ) { - final SqmBetweenPredicate betweenPredicate = (SqmBetweenPredicate) predicate; - return isNonIdentifierReference( betweenPredicate.getExpression() ) - || isNonIdentifierReference( betweenPredicate.getLowerBound() ) - || isNonIdentifierReference( betweenPredicate.getUpperBound() ); - } - - return false; - } - - /** - * Is the given `expression` a `SqmNavigableReference` that is also a reference - * to a non-`EntityIdentifier` `Navigable`? - * - * @see SqmSimplePath - */ - @SuppressWarnings("WeakerAccess") - public static boolean isNonIdentifierReference(SqmExpression expression) { -// if ( expression instanceof SqmNavigableReference ) { -// return ! EntityIdentifier.class.isInstance( expression ); -// } - - return false; - } - - /** - * Centralized selection of ids matching the restriction of the DELETE - * or UPDATE SQM query - */ - public static List selectMatchingIds( - SqmDeleteOrUpdateStatement sqmDeleteStatement, - DomainParameterXref domainParameterXref, - ExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryEngine queryEngine = factory.getQueryEngine(); - final SqmTranslatorFactory sqmTranslatorFactory = queryEngine.getSqmTranslatorFactory(); - - final SqmSelectTranslator selectConverter = sqmTranslatorFactory.createSelectTranslator( - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getLoadQueryInfluencers(), - factory - ); - - final SqmQuerySpec sqmIdSelectQuerySpec = SqmIdSelectGenerator.generateSqmEntityIdSelect( - sqmDeleteStatement, - factory - ); - - final SqmSelectStatement sqmIdSelect = new SqmSelectStatement( factory.getNodeBuilder() ); - //noinspection unchecked - sqmIdSelect.setQuerySpec( sqmIdSelectQuerySpec ); - - final SqmSelectTranslation sqmInterpretation = selectConverter.translate( sqmIdSelect ); - - final JdbcServices jdbcServices = factory.getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( factory ).translate( sqmInterpretation.getSqlAst() ); - - final Map, Map>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - factory.getDomainModel(), - selectConverter.getFromClauseAccess()::findTableGroup, - executionContext.getSession() - ); - - - return factory.getJdbcServices().getJdbcSelectExecutor().list( - jdbcSelect, - jdbcParameterBindings, - executionContext, - row -> row - ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/UpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/UpdateHandler.java similarity index 87% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/UpdateHandler.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/UpdateHandler.java index 0627e37825..5d24873169 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/UpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/UpdateHandler.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.query.sqm.mutation.spi; +package org.hibernate.query.sqm.mutation.internal; /** * Handler for dealing with multi-table SQM UPDATE queries. diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java index e5a8ef71da..1bc6799a08 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java @@ -6,9 +6,9 @@ */ package org.hibernate.query.sqm.mutation.internal.cte; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.sql.ast.tree.cte.CteTable; @@ -22,15 +22,15 @@ import org.hibernate.sql.ast.tree.cte.CteTable; public abstract class AbstractCteMutationHandler extends AbstractMutationHandler { private final CteTable cteTable; private final DomainParameterXref domainParameterXref; - private final CteBasedMutationStrategy strategy; + private final CteStrategy strategy; public AbstractCteMutationHandler( CteTable cteTable, SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, - CteBasedMutationStrategy strategy, - HandlerCreationContext creationContext) { - super( sqmStatement, creationContext ); + CteStrategy strategy, + SessionFactoryImplementor sessionFactory) { + super( sqmStatement, sessionFactory ); this.cteTable = cteTable; this.domainParameterXref = domainParameterXref; @@ -45,7 +45,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler return domainParameterXref; } - public CteBasedMutationStrategy getStrategy() { + public CteStrategy getStrategy() { return strategy; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java index c779de53b9..7bfc7ce901 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java @@ -19,9 +19,8 @@ import org.hibernate.metamodel.mapping.ColumnConsumer; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; +import org.hibernate.query.sqm.mutation.internal.DeleteHandler; +import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.sql.ast.SqlAstTranslatorFactory; @@ -54,12 +53,11 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele CteTable cteTable, SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, - CteBasedMutationStrategy strategy, - HandlerCreationContext creationContext) { - super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, creationContext ); + CteStrategy strategy, + SessionFactoryImplementor sessionFactory) { + super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory ); - final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); + final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); } @@ -71,7 +69,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele @Override public int execute(ExecutionContext executionContext) { - final List ids = SqmMutationStrategyHelper.selectMatchingIds( + final List ids = MatchingIdSelectionHelper.selectMatchingIds( getSqmDeleteOrUpdateStatement(), getDomainParameterXref(), executionContext @@ -122,17 +120,15 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele ); getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableColumnsVisitationSupplier) -> { - executeDelete( - cteDefinitionQuerySpec, - tableExpression, - tableColumnsVisitationSupplier, - getEntityDescriptor().getIdentifierMapping(), - cteQuerySpec, - jdbcParameterBindings, - executionContext - ); - } + (tableExpression, tableColumnsVisitationSupplier) -> executeDelete( + cteDefinitionQuerySpec, + tableExpression, + tableColumnsVisitationSupplier, + getEntityDescriptor().getIdentifierMapping(), + cteQuerySpec, + jdbcParameterBindings, + executionContext + ) ); return ids.size(); @@ -201,7 +197,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele ); return new CteStatement( cteDefinition, - CteBasedMutationStrategy.TABLE_NAME, + CteStrategy.TABLE_NAME, getCteTable(), deleteStatement ); @@ -225,16 +221,14 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele final List columnsToMatchReferences = new ArrayList<>(); columnsToMatchVisitationSupplier.get().accept( - (columnExpression, containingTableExpression, jdbcMapping) -> { - columnsToMatchReferences.add( - new ColumnReference( - targetTableReference, - columnExpression, - jdbcMapping, - sessionFactory - ) - ); - } + (columnExpression, containingTableExpression, jdbcMapping) -> columnsToMatchReferences.add( + new ColumnReference( + targetTableReference, + columnExpression, + jdbcMapping, + sessionFactory + ) + ) ); final Expression columnsToMatchExpression; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteBasedMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java similarity index 65% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteBasedMutationStrategy.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java index 1a6cfe8b10..af95992f35 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteBasedMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java @@ -10,17 +10,16 @@ import java.util.Locale; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.exec.spi.ExecutionContext; /** * @asciidoc @@ -50,51 +49,25 @@ import org.hibernate.sql.ast.tree.cte.CteTable; * ) * ```` * - * todo (6.0) : why not: - * - * ```` - * with cte_id (id) as ( - * select id - * from Person p - * where ... - * ) - * delete from Contact - * where (id) in ( - * select id - * from cte_id - * ) - * - * with cte_id (id) as ( - * select id - * from Person p - * where ... - * ) - * delete from Person - * where (id) in ( - * select id - * from cte_id - * ) - * ```` - * * @author Evandro Pires da Silva * @author Vlad Mihalcea * @author Steve Ebersole */ -public class CteBasedMutationStrategy implements SqmMultiTableMutationStrategy { +public class CteStrategy implements SqmMultiTableMutationStrategy { public static final String SHORT_NAME = "cte"; public static final String TABLE_NAME = "id_cte"; private final EntityPersister rootDescriptor; + private final SessionFactoryImplementor sessionFactory; private final CteTable cteTable; - public CteBasedMutationStrategy( + public CteStrategy( EntityPersister rootDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { this.rootDescriptor = rootDescriptor; + this.sessionFactory = runtimeModelCreationContext.getSessionFactory(); - final Dialect dialect = runtimeModelCreationContext.getTypeConfiguration() - .getSessionFactory() - .getServiceRegistry() + final Dialect dialect = sessionFactory.getServiceRegistry() .getService( JdbcServices.class ) .getJdbcEnvironment() .getDialect(); @@ -120,24 +93,30 @@ public class CteBasedMutationStrategy implements SqmMultiTableMutationStrategy { ); } - this.cteTable = new CteTable( rootDescriptor, runtimeModelCreationContext.getTypeConfiguration() ); + this.cteTable = new CteTable( rootDescriptor ); } @Override - public UpdateHandler buildUpdateHandler( - SqmUpdateStatement sqmUpdateStatement, + public int executeDelete( + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - checkMatch( sqmUpdateStatement, creationContext ); - - return new CteUpdateHandler( cteTable, sqmUpdateStatement, domainParameterXref, this, creationContext ); + ExecutionContext context) { + checkMatch( sqmDelete ); + return new CteDeleteHandler( cteTable, sqmDelete, domainParameterXref, this, sessionFactory ).execute( context ); } - private void checkMatch(SqmDeleteOrUpdateStatement sqmStatement, HandlerCreationContext creationContext) { + @Override + public int executeUpdate( + SqmUpdateStatement sqmUpdate, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + checkMatch( sqmUpdate ); + return new CteUpdateHandler( cteTable, sqmUpdate, domainParameterXref, this, sessionFactory ).execute( context ); + } + + private void checkMatch(SqmDeleteOrUpdateStatement sqmStatement) { final String targetEntityName = sqmStatement.getTarget().getEntityName(); - final EntityPersister targetEntityDescriptor = creationContext.getSessionFactory() - .getDomainModel() - .getEntityDescriptor( targetEntityName ); + final EntityPersister targetEntityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( targetEntityName ); if ( targetEntityDescriptor != rootDescriptor && ! rootDescriptor.isSubclassEntityName( targetEntityDescriptor.getEntityName() ) ) { throw new IllegalArgumentException( @@ -151,14 +130,4 @@ public class CteBasedMutationStrategy implements SqmMultiTableMutationStrategy { } } - - @Override - public DeleteHandler buildDeleteHandler( - SqmDeleteStatement sqmDeleteStatement, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - checkMatch( sqmDeleteStatement, creationContext ); - - return new CteDeleteHandler( cteTable, sqmDeleteStatement, domainParameterXref, this, creationContext ); - } } 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 f6d8df79f0..cc6bbfb11b 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 @@ -7,9 +7,9 @@ package org.hibernate.query.sqm.mutation.internal.cte; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; +import org.hibernate.query.sqm.mutation.internal.UpdateHandler; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.tree.cte.CteTable; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -24,13 +24,14 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements UpdateHandler { + @SuppressWarnings("WeakerAccess") public CteUpdateHandler( CteTable cteTable, SqmUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, - CteBasedMutationStrategy strategy, - HandlerCreationContext creationContext) { - super( cteTable, sqmStatement, domainParameterXref, strategy, creationContext ); + CteStrategy strategy, + SessionFactoryImplementor sessionFactory) { + super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java index 59f025da9b..f9c2056d27 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java @@ -7,6 +7,7 @@ package org.hibernate.query.sqm.mutation.internal.idtable; import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Supplier; @@ -15,25 +16,16 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.ComparisonOperator; import org.hibernate.query.NavigablePath; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmTreePrinter; -import org.hibernate.query.sqm.mutation.internal.SqmIdSelectGenerator; -import org.hibernate.query.sqm.sql.SqmQuerySpecTranslation; -import org.hibernate.query.sqm.sql.SqmSelectTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.expression.SqmLiteral; -import org.hibernate.query.sqm.tree.select.SqmQuerySpec; -import org.hibernate.query.sqm.tree.select.SqmSelection; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.sql.ast.Clause; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.sql.ast.SqlAstInsertSelectTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.from.StandardTableGroup; @@ -47,6 +39,7 @@ import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcInsert; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.UUIDCharType; import org.jboss.logging.Logger; @@ -63,158 +56,83 @@ public final class ExecuteWithIdTableHelper { } public static int saveMatchingIdsIntoIdTable( - SqmUpdateStatement sqmMutation, MultiTableSqmMutationConverter sqmConverter, - TableGroup mutatingTableGroup, Predicate suppliedPredicate, IdTable idTable, Function sessionUidAccess, - DomainParameterXref domainParameterXref, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final SqmQuerySpec sqmIdSelect = SqmIdSelectGenerator.generateSqmEntityIdSelect( - sqmMutation, - factory + final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); + + assert mutatingTableGroup.getModelPart() instanceof EntityMappingType; + final EntityMappingType mutatingEntityDescriptor = (EntityMappingType) mutatingTableGroup.getModelPart(); + + final InsertSelectStatement idTableInsert = new InsertSelectStatement(); + + final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory ); + idTableInsert.setTargetTable( idTableReference ); + + for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) { + final IdTableColumn column = idTable.getIdTableColumns().get( i ); + idTableInsert.addTargetColumnReferences( + new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory ) + ); + } + + final QuerySpec matchingIdSelection = new QuerySpec( true, 1 ); + idTableInsert.setSourceSelectStatement( matchingIdSelection ); + + matchingIdSelection.getFromClause().addRoot( mutatingTableGroup ); + + final AtomicInteger positionWrapper = new AtomicInteger(); + + mutatingEntityDescriptor.getIdentifierMapping().visitColumns( + (columnExpression, containingTableExpression, jdbcMapping) -> { + final int jdbcPosition = positionWrapper.getAndIncrement(); + final TableReference tableReference = mutatingTableGroup.resolveTableReference( containingTableExpression ); + matchingIdSelection.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + jdbcPosition, + jdbcPosition + 1, + sqmConverter.getSqlExpressionResolver().resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ), + sqlAstProcessingState -> new ColumnReference( + tableReference, + columnExpression, + jdbcMapping, + factory + ) + ), + jdbcMapping + ) + ); + } ); if ( idTable.getSessionUidColumn() != null ) { - //noinspection unchecked - sqmIdSelect.getSelectClause().add( - new SqmSelection( - new SqmLiteral( + final int jdbcPosition = positionWrapper.getAndIncrement(); + matchingIdSelection.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + jdbcPosition, + jdbcPosition + 1, + new QueryLiteral( sessionUidAccess.apply( executionContext.getSession() ), - UUIDCharType.INSTANCE, - executionContext.getSession().getFactory().getNodeBuilder() + StandardBasicTypes.STRING ), - null, - executionContext.getSession().getFactory().getNodeBuilder() + idTable.getSessionUidColumn().getJdbcMapping() ) ); } - SqmTreePrinter.logTree( sqmIdSelect, "Entity-identifier Selection SqmQuerySpec" ); - - final InsertSelectStatement insertSelectStatement = new InsertSelectStatement(); - - final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory ); - insertSelectStatement.setTargetTable( idTableReference ); - - final QuerySpec matchingIdRestrictionQuerySpec = generateTempTableInsertValuesQuerySpec( - sqmConverter, - mutatingTableGroup, - suppliedPredicate, - sqmIdSelect - ); - insertSelectStatement.setSourceSelectStatement( matchingIdRestrictionQuerySpec ); - - for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) { - final IdTableColumn column = idTable.getIdTableColumns().get( i ); - insertSelectStatement.addTargetColumnReferences( - new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory ) - ); - } + matchingIdSelection.applyPredicate( suppliedPredicate ); final JdbcServices jdbcServices = factory.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); final SqlAstInsertSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory ); - final JdbcInsert jdbcInsert = sqlAstTranslator.translate( insertSelectStatement ); - - return jdbcServices.getJdbcMutationExecutor().execute( - jdbcInsert, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } - - private static QuerySpec generateTempTableInsertValuesQuerySpec( - MultiTableSqmMutationConverter sqmConverter, - TableGroup mutatingTableGroup, Predicate suppliedPredicate, SqmQuerySpec sqmIdSelect) { - final QuerySpec matchingIdRestrictionQuerySpec = new QuerySpec( false, 1 ); - sqmConverter.visitSelectClause( - sqmIdSelect.getSelectClause(), - matchingIdRestrictionQuerySpec, - columnReference -> {}, - (sqmParameter, jdbcParameters) -> {} - ); - matchingIdRestrictionQuerySpec.getFromClause().addRoot( mutatingTableGroup ); - matchingIdRestrictionQuerySpec.applyPredicate( suppliedPredicate ); - return matchingIdRestrictionQuerySpec; - } - - public static int saveMatchingIdsIntoIdTable( - SqmDeleteOrUpdateStatement sqmMutation, - Predicate predicate, - IdTable idTable, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final SqmQuerySpec sqmIdSelect = SqmIdSelectGenerator.generateSqmEntityIdSelect( - sqmMutation, - factory - ); - - if ( idTable.getSessionUidColumn() != null ) { - //noinspection unchecked - sqmIdSelect.getSelectClause().add( - new SqmSelection( - new SqmLiteral( - sessionUidAccess.apply( executionContext.getSession() ), - UUIDCharType.INSTANCE, - executionContext.getSession().getFactory().getNodeBuilder() - ), - null, - executionContext.getSession().getFactory().getNodeBuilder() - ) - ); - } - - SqmTreePrinter.logTree( sqmIdSelect, "Entity-identifier Selection SqmQuerySpec" ); - - final SqmTranslatorFactory sqmTranslatorFactory = factory.getQueryEngine().getSqmTranslatorFactory(); - final SqmSelectTranslator sqmTranslator = sqmTranslatorFactory.createSelectTranslator( - QueryOptions.NONE, - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory - ); - - final SqmQuerySpecTranslation sqmIdSelectTranslation = sqmTranslator.translate( sqmIdSelect ); - - - - final InsertSelectStatement insertSelectStatement = new InsertSelectStatement(); - - final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory ); - insertSelectStatement.setTargetTable( idTableReference ); - - final QuerySpec matchingIdRestrictionQuerySpec = sqmIdSelectTranslation.getSqlAst(); - insertSelectStatement.setSourceSelectStatement( matchingIdRestrictionQuerySpec ); - matchingIdRestrictionQuerySpec.applyPredicate( predicate ); - - for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) { - final IdTableColumn column = idTable.getIdTableColumns().get( i ); - insertSelectStatement.addTargetColumnReferences( - new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory ) - ); - } - - final JdbcServices jdbcServices = factory.getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final SqlAstInsertSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory ); - final JdbcInsert jdbcInsert = sqlAstTranslator.translate( insertSelectStatement ); + final JdbcInsert jdbcInsert = sqlAstTranslator.translate( idTableInsert ); return jdbcServices.getJdbcMutationExecutor().execute( jdbcInsert, @@ -302,8 +220,7 @@ public final class ExecuteWithIdTableHelper { ComparisonOperator.EQUAL, new QueryLiteral( sessionUidAccess.apply( executionContext.getSession() ), - UUIDCharType.INSTANCE, - Clause.WHERE + UUIDCharType.INSTANCE ) ) ); @@ -317,12 +234,23 @@ public final class ExecuteWithIdTableHelper { TempTableDdlTransactionHandling ddlTransactionHandling, ExecutionContext executionContext) { if ( beforeUseAction == BeforeUseAction.CREATE ) { - IdTableHelper.createIdTable( + final IdTableHelper.IdTableCreationWork idTableCreationWork = new IdTableHelper.IdTableCreationWork( idTable, idTableExporterAccess.get(), - ddlTransactionHandling, - executionContext.getSession() + executionContext.getSession().getFactory() ); + + if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { + ( (SessionImplementor) executionContext.getSession() ).doWork( idTableCreationWork ); + } + else { + final IsolationDelegate isolationDelegate = executionContext.getSession() + .getJdbcCoordinator() + .getJdbcSessionOwner() + .getTransactionCoordinator() + .createIsolationDelegate(); + isolationDelegate.delegateWork( idTableCreationWork, ddlTransactionHandling == TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT ); + } } } @@ -342,12 +270,23 @@ public final class ExecuteWithIdTableHelper { ); } else if ( afterUseAction == AfterUseAction.DROP ) { - IdTableHelper.dropIdTable( + final IdTableHelper.IdTableDropWork idTableDropWork = new IdTableHelper.IdTableDropWork( idTable, idTableExporterAccess.get(), - ddlTransactionHandling, - executionContext.getSession() + executionContext.getSession().getFactory() ); + + if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { + ( (SessionImplementor) executionContext.getSession() ).doWork( idTableDropWork ); + } + else { + final IsolationDelegate isolationDelegate = executionContext.getSession() + .getJdbcCoordinator() + .getJdbcSessionOwner() + .getTransactionCoordinator() + .createIsolationDelegate(); + isolationDelegate.delegateWork( idTableDropWork, ddlTransactionHandling == TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/GlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/GlobalTemporaryTableStrategy.java index f8c5cf8998..08618fa4ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/GlobalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/GlobalTemporaryTableStrategy.java @@ -6,14 +6,23 @@ */ package org.hibernate.query.sqm.mutation.internal.idtable; -import org.hibernate.NotYetImplementedFor6Exception; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.function.Supplier; + +import org.hibernate.SessionFactory; +import org.hibernate.SessionFactoryObserver; +import org.hibernate.boot.TempTableDdlTransactionHandling; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; + +import org.jboss.logging.Logger; /** * Strategy based on ANSI SQL's definition of a "global temporary table". @@ -21,22 +30,168 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; * @author Steve Ebersole */ public class GlobalTemporaryTableStrategy implements SqmMultiTableMutationStrategy { + private static final Logger log = Logger.getLogger( GlobalTemporaryTableStrategy.class ); + public static final String SHORT_NAME = "global_temporary"; public static final String DROP_ID_TABLES = "hibernate.hql.bulk_id_strategy.global_temporary.drop_tables"; - @Override - public UpdateHandler buildUpdateHandler( - SqmUpdateStatement sqmUpdateStatement, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - throw new NotYetImplementedFor6Exception( getClass() ); + private final IdTable idTable; + private final AfterUseAction afterUseAction; + private final Supplier idTableExporterAccess; + + private final SessionFactoryImplementor sessionFactory; + + private boolean prepared; + private boolean created; + private boolean released; + + public GlobalTemporaryTableStrategy( + IdTable idTable, + Supplier idTableExporterAccess, + AfterUseAction afterUseAction, + SessionFactoryImplementor sessionFactory) { + this.idTable = idTable; + this.idTableExporterAccess = idTableExporterAccess; + this.afterUseAction = afterUseAction; + this.sessionFactory = sessionFactory; + + if ( afterUseAction == AfterUseAction.DROP ) { + throw new IllegalArgumentException( "Global-temp ID tables cannot use AfterUseAction.DROP : " + idTable.getTableExpression() ); + } } @Override - public DeleteHandler buildDeleteHandler( - SqmDeleteStatement sqmDeleteStatement, + public int executeUpdate( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - throw new NotYetImplementedFor6Exception( getClass() ); + ExecutionContext context) { + return new TableBasedUpdateHandler( + sqmUpdate, + domainParameterXref, + idTable, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + idTableExporterAccess, + BeforeUseAction.CREATE, + afterUseAction, + TempTableDdlTransactionHandling.NONE, + sessionFactory + ).execute( context ); + } + + @Override + public int executeDelete( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + return new TableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + idTable, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + idTableExporterAccess, + BeforeUseAction.CREATE, + afterUseAction, + TempTableDdlTransactionHandling.NONE, + sessionFactory + ).execute( context ); + } + + @Override + public void prepare( + MappingModelCreationProcess mappingModelCreationProcess, + JdbcConnectionAccess connectionAccess) { + if ( prepared ) { + return; + } + + prepared = true; + + log.debugf( "Creating global-temp ID table : %s", idTable.getTableExpression() ); + + final IdTableHelper.IdTableCreationWork idTableCreationWork = new IdTableHelper.IdTableCreationWork( + idTable, + idTableExporterAccess.get(), + sessionFactory + ); + Connection connection; + try { + connection = connectionAccess.obtainConnection(); + } + catch (UnsupportedOperationException e) { + // assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl + log.debug( "Unable to obtain JDBC connection; assuming ID tables already exist or wont be needed" ); + return; + } + catch (SQLException e) { + log.error( "Unable obtain JDBC Connection", e ); + return; + } + + try { + idTableCreationWork.execute( connection ); + created = true; + } + finally { + try { + connectionAccess.releaseConnection( connection ); + } + catch (SQLException ignore) { + } + } + + if ( created ) { + // todo (6.0) : register strategy for dropping of the table if requested - DROP_ID_TABLES + } + } + + @Override + public void release( + SessionFactoryImplementor sessionFactory, + JdbcConnectionAccess connectionAccess) { + if ( released ) { + return; + } + + released = true; + + if ( ! created ) { + return; + } + + log.debugf( "Dropping global-temp ID table : %s", idTable.getTableExpression() ); + + final IdTableHelper.IdTableDropWork idTableDropWork = new IdTableHelper.IdTableDropWork( + idTable, + idTableExporterAccess.get(), + sessionFactory + ); + Connection connection; + try { + connection = connectionAccess.obtainConnection(); + } + catch (UnsupportedOperationException e) { + // assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl + log.debugf( "Unable to obtain JDBC connection; unable to drop global-temp ID table : %s", idTable.getTableExpression() ); + return; + } + catch (SQLException e) { + log.error( "Unable obtain JDBC Connection", e ); + return; + } + + try { + idTableDropWork.execute( connection ); + } + finally { + try { + connectionAccess.releaseConnection( connection ); + } + catch (SQLException ignore) { + } + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java index cb2d969442..8728cb5985 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java @@ -13,11 +13,11 @@ import java.sql.SQLWarning; import java.sql.Statement; import java.util.function.Function; -import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -26,6 +26,7 @@ import org.hibernate.jdbc.AbstractWork; /** * @author Steve Ebersole */ +@SuppressWarnings("WeakerAccess") public class IdTableHelper { private final static CoreMessageLogger log = CoreLogging.messageLogger( IdTableHelper.class ); @@ -34,35 +35,23 @@ public class IdTableHelper { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Creation - public static void createIdTable( - IdTable idTable, - IdTableExporter exporter, - TempTableDdlTransactionHandling ddlTransactionHandling, - SharedSessionContractImplementor session) { - executeWork( - new IdTableCreationWork( idTable, exporter, session ), - ddlTransactionHandling, - session - ); - } - - private static class IdTableCreationWork extends AbstractWork { + public static class IdTableCreationWork extends AbstractWork { private final IdTable idTable; private final IdTableExporter exporter; - private final SharedSessionContractImplementor session; + private final SessionFactoryImplementor sessionFactory; - IdTableCreationWork( + public IdTableCreationWork( IdTable idTable, IdTableExporter exporter, - SharedSessionContractImplementor session) { + SessionFactoryImplementor sessionFactory) { this.idTable = idTable; this.exporter = exporter; - this.session = session; + this.sessionFactory = sessionFactory; } @Override public void execute(Connection connection) { - final JdbcServices jdbcServices = session.getFactory().getJdbcServices(); + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); try { final String creationCommand = exporter.getSqlCreateCommand( idTable ); @@ -91,35 +80,23 @@ public class IdTableHelper { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Drop - public static void dropIdTable( - IdTable idTable, - IdTableExporter exporter, - TempTableDdlTransactionHandling ddlTransactionHandling, - SharedSessionContractImplementor session) { - executeWork( - new IdTableDropWork( idTable, exporter, session ), - ddlTransactionHandling, - session - ); - } - - private static class IdTableDropWork extends AbstractWork { + public static class IdTableDropWork extends AbstractWork { private final IdTable idTable; private final IdTableExporter exporter; - private final SharedSessionContractImplementor session; + private final SessionFactoryImplementor sessionFactory; IdTableDropWork( IdTable idTable, IdTableExporter exporter, - SharedSessionContractImplementor session) { + SessionFactoryImplementor sessionFactory) { this.idTable = idTable; this.exporter = exporter; - this.session = session; + this.sessionFactory = sessionFactory; } @Override public void execute(Connection connection) { - final JdbcServices jdbcServices = session.getFactory().getJdbcServices(); + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); try { final String dropCommand = exporter.getSqlCreateCommand( idTable ); @@ -182,35 +159,6 @@ public class IdTableHelper { } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Misc - - private static void executeWork( - AbstractWork work, - TempTableDdlTransactionHandling ddlTransactionHandling, - SharedSessionContractImplementor session) { - if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { - // simply execute the work using a Connection obtained from JdbcConnectionAccess - // - // NOTE : we do not (potentially) release the Connection here - // via LogicalConnectionImplementor#afterStatement because - // for sure we will be immediately using it again to - // populate the id table and use it... - - try { - work.execute( session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() ); - } - catch (SQLException e) { - log.error( "Unable to use JDBC Connection to create perform id table management", e ); - } - } - else { - session.getTransactionCoordinator() - .createIsolationDelegate() - .delegateWork( work, ddlTransactionHandling == TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT ); - } - } - private static SqlExceptionHelper.WarningHandler WARNING_HANDLER = new SqlExceptionHelper.WarningHandlerLoggingSupport() { public boolean doProcess() { return log.isDebugEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java index 6cc000bf2c..9e20969745 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java @@ -10,14 +10,12 @@ import java.util.function.Function; import java.util.function.Supplier; import org.hibernate.boot.TempTableDdlTransactionHandling; -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; /** * Strategy based on ANSI SQL's definition of a "local temporary table" (local to each db session). @@ -31,63 +29,73 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg private final Supplier idTableExporterAccess; private final AfterUseAction afterUseAction; private final TempTableDdlTransactionHandling ddlTransactionHandling; + private final SessionFactoryImplementor sessionFactory; public LocalTemporaryTableStrategy( IdTable idTable, Supplier idTableExporterAccess, AfterUseAction afterUseAction, - TempTableDdlTransactionHandling ddlTransactionHandling) { + TempTableDdlTransactionHandling ddlTransactionHandling, + SessionFactoryImplementor sessionFactory) { this.idTable = idTable; this.idTableExporterAccess = idTableExporterAccess; this.afterUseAction = afterUseAction; this.ddlTransactionHandling = ddlTransactionHandling; + this.sessionFactory = sessionFactory; } public LocalTemporaryTableStrategy( IdTable idTable, Function databaseTypeNameResolver, AfterUseAction afterUseAction, - TempTableDdlTransactionHandling ddlTransactionHandling) { + TempTableDdlTransactionHandling ddlTransactionHandling, + SessionFactoryImplementor sessionFactory) { this( idTable, () -> new TempIdTableExporter( true, databaseTypeNameResolver ), afterUseAction, - ddlTransactionHandling - ); - } - - @Override - public UpdateHandler buildUpdateHandler( - SqmUpdateStatement sqmUpdateStatement, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - return new TableBasedUpdateHandler( - sqmUpdateStatement, - domainParameterXref, idTable, - SharedSessionContractImplementor::getTenantIdentifier, - idTableExporterAccess, - BeforeUseAction.CREATE, - afterUseAction, ddlTransactionHandling, - creationContext + sessionFactory ); } @Override - public DeleteHandler buildDeleteHandler( - SqmDeleteStatement sqmDeleteStatement, + public int executeUpdate( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - return new TableBasedDeleteHandler( - sqmDeleteStatement, + ExecutionContext context) { + return new TableBasedUpdateHandler( + sqmUpdate, domainParameterXref, idTable, - SharedSessionContractImplementor::getTenantIdentifier, + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, idTableExporterAccess, BeforeUseAction.CREATE, afterUseAction, ddlTransactionHandling, - creationContext - ); + sessionFactory + ).execute( context ); + } + + @Override + public int executeDelete( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + return new TableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + idTable, + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + idTableExporterAccess, + BeforeUseAction.CREATE, + afterUseAction, + ddlTransactionHandling, + sessionFactory + ).execute( context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/PersistentTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/PersistentTableStrategy.java index e2a1d4992d..95bc0578dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/PersistentTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/PersistentTableStrategy.java @@ -6,14 +6,21 @@ */ package org.hibernate.query.sqm.mutation.internal.idtable; -import org.hibernate.NotYetImplementedFor6Exception; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.function.Supplier; + +import org.hibernate.boot.TempTableDdlTransactionHandling; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; + +import org.jboss.logging.Logger; /** * This is a strategy that mimics temporary tables for databases which do not support @@ -23,6 +30,8 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; * @author Steve Ebersole */ public class PersistentTableStrategy implements SqmMultiTableMutationStrategy { + private static final Logger log = Logger.getLogger( PersistentTableStrategy.class ); + public static final String SHORT_NAME = "persistent"; public static final String DROP_ID_TABLES = "hibernate.hql.bulk_id_strategy.persistent.drop_tables"; @@ -30,19 +39,160 @@ public class PersistentTableStrategy implements SqmMultiTableMutationStrategy { public static final String SCHEMA = "hibernate.hql.bulk_id_strategy.persistent.schema"; public static final String CATALOG = "hibernate.hql.bulk_id_strategy.persistent.catalog"; - @Override - public UpdateHandler buildUpdateHandler( - SqmUpdateStatement sqmUpdateStatement, - DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - throw new NotYetImplementedFor6Exception( getClass() ); + private final IdTable idTable; + private final AfterUseAction afterUseAction; + private final Supplier idTableExporterAccess; + + private final SessionFactoryImplementor sessionFactory; + + private boolean prepared; + private boolean created; + private boolean released; + + public PersistentTableStrategy( + IdTable idTable, + AfterUseAction afterUseAction, + Supplier idTableExporterAccess, + SessionFactoryImplementor sessionFactory) { + this.idTable = idTable; + this.afterUseAction = afterUseAction; + this.idTableExporterAccess = idTableExporterAccess; + this.sessionFactory = sessionFactory; + + if ( afterUseAction == AfterUseAction.DROP ) { + throw new IllegalArgumentException( "Persistent ID tables cannot use AfterUseAction.DROP : " + idTable.getTableExpression() ); + } } @Override - public DeleteHandler buildDeleteHandler( - SqmDeleteStatement sqmDeleteStatement, + public int executeUpdate( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext) { - throw new NotYetImplementedFor6Exception( getClass() ); + ExecutionContext context) { + return new TableBasedUpdateHandler( + sqmUpdate, + domainParameterXref, + idTable, + session -> session.getSessionIdentifier().toString(), + idTableExporterAccess, + BeforeUseAction.CREATE, + afterUseAction, + TempTableDdlTransactionHandling.NONE, + sessionFactory + ).execute( context ); + } + + @Override + public int executeDelete( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + return new TableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + idTable, + session -> session.getSessionIdentifier().toString(), + idTableExporterAccess, + BeforeUseAction.CREATE, + afterUseAction, + TempTableDdlTransactionHandling.NONE, + sessionFactory + ).execute( context ); + } + + @Override + public void prepare( + MappingModelCreationProcess mappingModelCreationProcess, + JdbcConnectionAccess connectionAccess) { + if ( prepared ) { + return; + } + + prepared = true; + + log.debugf( "Creating persistent ID table : %s", idTable.getTableExpression() ); + + final IdTableHelper.IdTableCreationWork idTableCreationWork = new IdTableHelper.IdTableCreationWork( + idTable, + idTableExporterAccess.get(), + sessionFactory + ); + Connection connection; + try { + connection = connectionAccess.obtainConnection(); + } + catch (UnsupportedOperationException e) { + // assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl + log.debug( "Unable to obtain JDBC connection; assuming ID tables already exist or wont be needed" ); + return; + } + catch (SQLException e) { + log.error( "Unable obtain JDBC Connection", e ); + return; + } + + try { + idTableCreationWork.execute( connection ); + created = true; + } + finally { + try { + connectionAccess.releaseConnection( connection ); + } + catch (SQLException ignore) { + } + } + + if ( created ) { + // todo (6.0) : register strategy for dropping of the table if requested - DROP_ID_TABLES + } + } + + @Override + public void release( + SessionFactoryImplementor sessionFactory, + JdbcConnectionAccess connectionAccess) { + if ( released ) { + return; + } + + released = true; + + if ( created ) { + return; + } + + + log.debugf( "Dropping persistent ID table : %s", idTable.getTableExpression() ); + + final IdTableHelper.IdTableDropWork idTableDropWork = new IdTableHelper.IdTableDropWork( + idTable, + idTableExporterAccess.get(), + sessionFactory + ); + Connection connection; + try { + connection = connectionAccess.obtainConnection(); + } + catch (UnsupportedOperationException e) { + // assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl + log.debugf( "Unable to obtain JDBC connection; unable to drop persistent ID table : %s", idTable.getTableExpression() ); + return; + } + catch (SQLException e) { + log.error( "Unable obtain JDBC Connection", e ); + return; + } + + try { + idTableDropWork.execute( connection ); + } + finally { + try { + connectionAccess.releaseConnection( connection ); + } + catch (SQLException ignore) { + } + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java index b6a54b75fc..2c5bae2e3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java @@ -7,40 +7,32 @@ package org.hibernate.query.sqm.mutation.internal.idtable; import java.util.ArrayList; +import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import org.hibernate.LockMode; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.collections.Stack; import org.hibernate.metamodel.mapping.ColumnConsumer; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; -import org.hibernate.query.NavigablePath; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; -import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; -import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.SqlAstDeleteTranslator; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.ColumnReference; @@ -76,7 +68,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle private final Supplier idTableExporterAccess; private final Function sessionUidAccess; + private final MultiTableSqmMutationConverter converter; + @SuppressWarnings("WeakerAccess") public RestrictedDeleteExecutionDelegate( EntityMappingType entityDescriptor, IdTable idTable, @@ -86,7 +80,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle AfterUseAction afterUseAction, TempTableDdlTransactionHandling ddlTransactionHandling, Supplier idTableExporterAccess, - Function sessionUidAccess, + Function sessionUidAccess, + QueryOptions queryOptions, + QueryParameterBindings queryParameterBindings, SessionFactoryImplementor sessionFactory) { this.entityDescriptor = entityDescriptor; this.idTable = idTable; @@ -98,54 +94,35 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle this.idTableExporterAccess = idTableExporterAccess; this.sessionUidAccess = sessionUidAccess; this.sessionFactory = sessionFactory; + + converter = new MultiTableSqmMutationConverter( + entityDescriptor, + domainParameterXref, + queryOptions, + queryParameterBindings, + sessionFactory + ); } @Override public int execute(ExecutionContext executionContext) { - final Converter converter = new Converter( - sessionFactory, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings() - ); - - final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl( - null, - converter, - converter.getCurrentClauseStack()::getCurrent - ) { - @Override - public Expression resolveSqlExpression( - String key, Function creator) { - return super.resolveSqlExpression( key, creator ); - } - }; - - converter.getProcessingStateStack().push( rootProcessingState ); final EntityPersister entityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( sqmDelete.getTarget().getEntityName() ); final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName(); - final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getEntityName() ); - final TableGroup deletingTableGroup = entityDescriptor.createRootTableGroup( - navigablePath, - null, - JoinType.LEFT, - LockMode.PESSIMISTIC_WRITE, - converter.getSqlAliasBaseGenerator(), - converter.getSqlExpressionResolver(), - () -> predicate -> {}, - sessionFactory - ); - // because this is a multi-table update, here we expect multiple TableReferences - assert !deletingTableGroup.getTableReferenceJoins().isEmpty(); - - // Register this TableGroup with the "registry" in preparation for - converter.getFromClauseAccess().registerTableGroup( navigablePath, deletingTableGroup ); + final TableGroup deletingTableGroup = converter.getMutatingTableGroup(); final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( hierarchyRootTableName ); assert hierarchyRootTableReference != null; + final Map> parameterResolutions; + if ( domainParameterXref.getSqmParameterCount() == 0 ) { + parameterResolutions = Collections.emptyMap(); + } + else { + parameterResolutions = new IdentityHashMap<>(); + } + // Use the converter to interpret the where-clause. We do this for 2 reasons: // 1) the resolved Predicate is ultimately the base for applying restriction to the deletes // 2) we also inspect each ColumnReference that is part of the where-clause to see which @@ -158,7 +135,8 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) { needsIdTableWrapper.set( true ); } - } + }, + parameterResolutions::put ); boolean needsIdTable = needsIdTableWrapper.get(); @@ -167,7 +145,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle return executeWithIdTable( predicate, deletingTableGroup, - converter.getRestrictionSqmParameterResolutions(), + parameterResolutions, executionContext ); } @@ -175,7 +153,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle return executeWithoutIdTable( predicate, deletingTableGroup, - converter.getRestrictionSqmParameterResolutions(), + parameterResolutions, converter.getSqlExpressionResolver(), executionContext ); @@ -399,11 +377,10 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle ExecutionContext executionContext, JdbcParameterBindings jdbcParameterBindings) { final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable( - sqmDelete, + converter, predicate, idTable, sessionUidAccess, - domainParameterXref, jdbcParameterBindings, executionContext ); @@ -468,86 +445,4 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle ); } - - - - static class Converter extends BaseSqmToSqlAstConverter { - private Map> restrictionSqmParameterResolutions; - - private BiConsumer> sqmParamResolutionConsumer; - - Converter( - SqlAstCreationContext creationContext, - QueryOptions queryOptions, - DomainParameterXref domainParameterXref, - QueryParameterBindings domainParameterBindings) { - super( creationContext, queryOptions, domainParameterXref, domainParameterBindings ); - } - - Map> getRestrictionSqmParameterResolutions() { - return restrictionSqmParameterResolutions; - } - - @Override - public Stack getProcessingStateStack() { - return super.getProcessingStateStack(); - } - - public Predicate visitWhereClause(SqmWhereClause sqmWhereClause, Consumer restrictionColumnReferenceConsumer) { - if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) { - return null; - } - - sqmParamResolutionConsumer = (sqm, jdbcs) -> { - if ( restrictionSqmParameterResolutions == null ) { - restrictionSqmParameterResolutions = new IdentityHashMap<>(); - } - restrictionSqmParameterResolutions.put( sqm, jdbcs ); - }; - - final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent(); - final SqlAstProcessingStateImpl restrictionProcessingState = new SqlAstProcessingStateImpl( - rootProcessingState, - this, - getCurrentClauseStack()::getCurrent - ) { - @Override - public SqlExpressionResolver getSqlExpressionResolver() { - return this; - } - - @Override - public Expression resolveSqlExpression( - String key, Function creator) { - final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression( key, creator ); - if ( expression instanceof ColumnReference ) { - restrictionColumnReferenceConsumer.accept( (ColumnReference) expression ); - } - return expression; - } - }; - - getProcessingStateStack().push( restrictionProcessingState ); - try { - return (Predicate) sqmWhereClause.getPredicate().accept( this ); - } - finally { - getProcessingStateStack().pop(); - } - } - - @Override - protected Expression consumeSqmParameter(SqmParameter sqmParameter) { - final Expression expression = super.consumeSqmParameter( sqmParameter ); - final List jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter ); - sqmParamResolutionConsumer.accept( sqmParameter, jdbcParameters ); - return expression; - } - - @Override - public SqlExpressionResolver getSqlExpressionResolver() { - return super.getSqlExpressionResolver(); - } - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java index 46130edfdf..14c8e9ef73 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java @@ -10,11 +10,11 @@ import java.util.function.Function; import java.util.function.Supplier; import org.hibernate.boot.TempTableDdlTransactionHandling; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.DeleteHandler; import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; -import org.hibernate.query.sqm.mutation.spi.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -50,8 +50,8 @@ public class TableBasedDeleteHandler BeforeUseAction beforeUseAction, AfterUseAction afterUseAction, TempTableDdlTransactionHandling ddlTransactionHandling, - HandlerCreationContext creationContext) { - super( sqmDeleteStatement, creationContext ); + SessionFactoryImplementor sessionFactory) { + super( sqmDeleteStatement, sessionFactory ); this.idTable = idTable; this.ddlTransactionHandling = ddlTransactionHandling; this.beforeUseAction = beforeUseAction; @@ -85,6 +85,8 @@ public class TableBasedDeleteHandler ddlTransactionHandling, exporterSupplier, sessionUidAccess, + executionContext.getQueryOptions(), + executionContext.getQueryParameterBindings(), getSessionFactory() ); } 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 7ed97763c8..2af4706041 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 @@ -16,7 +16,6 @@ import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; -import org.hibernate.LockMode; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -24,16 +23,14 @@ import org.hibernate.internal.util.collections.Stack; import org.hibernate.metamodel.spi.DomainMetamodel; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; -import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.internal.UpdateHandler; import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; -import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext; -import org.hibernate.query.sqm.mutation.spi.UpdateHandler; import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; @@ -63,11 +60,12 @@ public class TableBasedUpdateHandler private final AfterUseAction afterUseAction; private final Function sessionUidAccess; private final Supplier exporterSupplier; - private final DomainParameterXref domainParameterXref; + private final EntityPersister entityDescriptor; + TableBasedUpdateHandler( - SqmUpdateStatement sqmDeleteStatement, + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, IdTable idTable, Function sessionUidAccess, @@ -75,8 +73,8 @@ public class TableBasedUpdateHandler BeforeUseAction beforeUseAction, AfterUseAction afterUseAction, TempTableDdlTransactionHandling ddlTransactionHandling, - HandlerCreationContext creationContext) { - super( sqmDeleteStatement, creationContext ); + SessionFactoryImplementor sessionFactory) { + super( sqmUpdate, sessionFactory ); this.idTable = idTable; this.exporterSupplier = exporterSupplier; this.beforeUseAction = beforeUseAction; @@ -84,6 +82,9 @@ public class TableBasedUpdateHandler this.ddlTransactionHandling = ddlTransactionHandling; this.sessionUidAccess = sessionUidAccess; this.domainParameterXref = domainParameterXref; + + final String targetEntityName = sqmUpdate.getTarget().getEntityName(); + this.entityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( targetEntityName ); } protected SqmUpdateStatement getSqmUpdate() { @@ -131,21 +132,7 @@ public class TableBasedUpdateHandler converterProcessingStateStack.push( rootProcessingState ); - final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getEntityName() ); - final TableGroup updatingTableGroup = entityDescriptor.createRootTableGroup( - navigablePath, - null, - JoinType.LEFT, - LockMode.PESSIMISTIC_WRITE, - converterDelegate.getSqlAliasBaseGenerator(), - converterDelegate.getSqlExpressionResolver(), - () -> predicate -> {}, - sessionFactory - ); - - // because this is a multi-table update, here we expect multiple TableReferences - assert !updatingTableGroup.getTableReferenceJoins().isEmpty(); - converterDelegate.getFromClauseAccess().registerTableGroup( navigablePath, updatingTableGroup ); + final TableGroup updatingTableGroup = converterDelegate.getMutatingTableGroup(); final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( hierarchyRootTableName ); assert hierarchyRootTableReference != null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java index 87858d9f9f..4e0174ab93 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java @@ -23,6 +23,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.SqlAstUpdateTranslator; @@ -157,15 +158,11 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio ); try { - final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable( - sqmUpdate, sqmConverter, - updatingTableGroup, suppliedPredicate, idTable, sessionUidAccess, - domainParameterXref, jdbcParameterBindings, executionContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/DisjunctionRestrictionProducer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/DisjunctionRestrictionProducer.java new file mode 100644 index 0000000000..5699529275 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/DisjunctionRestrictionProducer.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.ColumnConsumer; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.query.ComparisonOperator; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.JdbcLiteral; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.Junction; +import org.hibernate.sql.exec.spi.ExecutionContext; + +/** + * MatchingIdRestrictionProducer producing a restriction based on a disjunction (OR) predicate. E.g.: + * + * ```` + * delete + * from + * entity-table + * where + * ( id = 1 ) + * or ( id = 2 ) + * or ( id = 3 ) + * or ( id = 4 ) + * ```` + * + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class DisjunctionRestrictionProducer implements MatchingIdRestrictionProducer { + @Override + public Junction produceRestriction( + List matchingIdValues, + EntityMappingType entityDescriptor, + TableReference mutatingTableReference, + Supplier> columnsToMatchVisitationSupplier, + ExecutionContext executionContext) { + assert matchingIdValues != null; + assert ! matchingIdValues.isEmpty(); + + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final int idColumnCount = identifierMapping.getJdbcTypeCount( sessionFactory.getTypeConfiguration() ); + assert idColumnCount > 0; + + final Junction predicate = new Junction( Junction.Nature.DISJUNCTION ); + + if ( idColumnCount == 1 ) { + final BasicValuedModelPart basicIdMapping = (BasicValuedModelPart) identifierMapping; + final String idColumn = basicIdMapping.getMappedColumnExpression(); + final ColumnReference idColumnReference = new ColumnReference( + mutatingTableReference, + idColumn, + basicIdMapping.getJdbcMapping(), + sessionFactory + ); + + for ( int i = 0; i < matchingIdValues.size(); i++ ) { + final Object matchingId = matchingIdValues.get( i ); + + predicate.add( + new ComparisonPredicate( + idColumnReference, + ComparisonOperator.EQUAL, + new JdbcLiteral<>( matchingId, basicIdMapping.getJdbcMapping() ) + ) + ); + + } + } + else { + final List columnReferences = new ArrayList<>( idColumnCount ); + final List jdbcMappings = new ArrayList<>( idColumnCount ); + identifierMapping.visitColumns( + (columnExpression, containingTableExpression, jdbcMapping) -> { + columnReferences.add( new ColumnReference( mutatingTableReference, columnExpression, jdbcMapping, sessionFactory ) ); + jdbcMappings.add( jdbcMapping ); + } + ); + + for ( int i = 0; i < matchingIdValues.size(); i++ ) { + final Junction idMatch = new Junction( Junction.Nature.CONJUNCTION ); + + final Object matchingId = matchingIdValues.get( i ); + assert matchingId instanceof Object[]; + + final Object[] matchingIdParts = (Object[]) matchingId; + + for ( int p = 0; p < matchingIdParts.length; p++ ) { + idMatch.add( + new ComparisonPredicate( + columnReferences.get( p ), + ComparisonOperator.EQUAL, + new JdbcLiteral<>( matchingIdParts[ p ], jdbcMappings.get( p ) ) + ) + ); + } + + predicate.add( idMatch ); + } + } + + return predicate; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java new file mode 100644 index 0000000000..d00d2a037f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.ColumnConsumer; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcLiteral; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.InListPredicate; +import org.hibernate.sql.exec.spi.ExecutionContext; + +/** + * MatchingIdRestrictionProducer producing a restriction based on an in-values-list predicate. E.g.: + * + * ```` + * delete + * from + * entity-table + * where + * ( id ) in ( + * ( 1 ), + * ( 2 ), + * ( 3 ), + * ( 4 ) + * ) + * ```` + * + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class InPredicateRestrictionProducer implements MatchingIdRestrictionProducer { + @Override + public InListPredicate produceRestriction( + List matchingIdValues, + EntityMappingType entityDescriptor, + TableReference mutatingTableReference, + Supplier> columnsToMatchVisitationSupplier, + ExecutionContext executionContext) { + assert matchingIdValues != null; + assert ! matchingIdValues.isEmpty(); + + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final int idColumnCount = identifierMapping.getJdbcTypeCount( sessionFactory.getTypeConfiguration() ); + assert idColumnCount > 0; + + final InListPredicate predicate; + + if ( idColumnCount == 1 ) { + final BasicValuedModelPart basicIdMapping = (BasicValuedModelPart) identifierMapping; + final String idColumn = basicIdMapping.getMappedColumnExpression(); + final Expression inFixture = new ColumnReference( + mutatingTableReference, + idColumn, + basicIdMapping.getJdbcMapping(), + sessionFactory + ); + predicate = new InListPredicate( inFixture ); + + for ( int i = 0; i < matchingIdValues.size(); i++ ) { + final Object matchingId = matchingIdValues.get( i ); + predicate.addExpression( new JdbcLiteral<>( matchingId, basicIdMapping.getJdbcMapping() ) ); + } + } + else { + final List columnReferences = new ArrayList<>( idColumnCount ); + final List jdbcMappings = new ArrayList<>( idColumnCount ); + identifierMapping.visitColumns( + (columnExpression, containingTableExpression, jdbcMapping) -> { + columnReferences.add( new ColumnReference( mutatingTableReference, columnExpression, jdbcMapping, sessionFactory ) ); + jdbcMappings.add( jdbcMapping ); + } + ); + + final Expression inFixture = new SqlTuple( columnReferences, identifierMapping ); + predicate = new InListPredicate( inFixture ); + + for ( int i = 0; i < matchingIdValues.size(); i++ ) { + final Object matchingId = matchingIdValues.get( i ); + assert matchingId instanceof Object[]; + final Object[] matchingIdParts = (Object[]) matchingId; + + final List tupleParts = new ArrayList<>( idColumnCount ); + for ( int p = 0; p < matchingIdParts.length; p++ ) { + tupleParts.add( + new JdbcLiteral<>( matchingIdParts[p],jdbcMappings.get( p ) ) + ); + } + + predicate.addExpression( new SqlTuple( tupleParts, identifierMapping ) ); + } + } + + return predicate; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java new file mode 100644 index 0000000000..74f78f6579 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java @@ -0,0 +1,176 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import java.sql.PreparedStatement; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.ColumnConsumer; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.DeleteHandler; +import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.sql.ast.SqlAstDeleteTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcDelete; +import org.hibernate.sql.exec.spi.JdbcMutationExecutor; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.StatementCreatorHelper; + +/** + * DeleteHandler for the in-line strategy + * + * @author Evandro Pires da Silva + * @author Vlad Mihalcea + * @author Steve Ebersole + */ +@SuppressWarnings("WeakerAccess") +public class InlineDeleteHandler implements DeleteHandler { + private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; + private final SqmDeleteStatement sqmDeleteStatement; + private final DomainParameterXref domainParameterXref; + + private final ExecutionContext executionContext; + + private final SessionFactoryImplementor sessionFactory; + private final SqlAstTranslatorFactory sqlAstTranslatorFactory; + private final JdbcMutationExecutor jdbcMutationExecutor; + + protected InlineDeleteHandler( + MatchingIdRestrictionProducer matchingIdsPredicateProducer, + SqmDeleteStatement sqmDeleteStatement, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + this.sqmDeleteStatement = sqmDeleteStatement; + + this.domainParameterXref = domainParameterXref; + this.matchingIdsPredicateProducer = matchingIdsPredicateProducer; + + this.executionContext = context; + + this.sessionFactory = executionContext.getSession().getFactory(); + this.sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); + this.jdbcMutationExecutor = sessionFactory.getJdbcServices().getJdbcMutationExecutor(); + } + + @Override + public int execute(ExecutionContext executionContext) { + final List ids = MatchingIdSelectionHelper.selectMatchingIds( + sqmDeleteStatement, + domainParameterXref, + executionContext + ); + + if ( ids == null || ids.isEmpty() ) { + return 0; + } + + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final String mutatingEntityName = sqmDeleteStatement.getTarget().getModel().getHibernateEntityName(); + final EntityMappingType entityDescriptor = factory.getDomainModel().getEntityDescriptor( mutatingEntityName ); + + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( domainParameterXref.getQueryParameterCount() ); + + // delete from the tables + + entityDescriptor.visitAttributeMappings( + attribute -> { + if ( attribute instanceof PluralAttributeMapping ) { + final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) attribute; + + if ( pluralAttribute.getSeparateCollectionTable() != null ) { + // this collection has a separate collection table, meaning it is one of: + // 1) element-collection + // 2) many-to-many + // 3) one-to many using a dedicated join-table + // + // in all of these cases, we should clean up the matching rows in the + // collection table + +// todo (6.0) : implement this +// executeDelete( +// pluralAttribute.getSeparateCollectionTable(), +// matchingIdsPredicateProducer.produceRestriction( +// ids, +// () -> columnConsumer -> , +// executionContext +// ), +// jdbcParameterBindings, +// executionContext +// ); + } + } + } + ); + + entityDescriptor.visitConstraintOrderedTables( + (tableExpression, tableKeyColumnsVisitationSupplier) -> { + executeDelete( + tableExpression, + entityDescriptor, + tableKeyColumnsVisitationSupplier, + ids, + jdbcParameterBindings, + executionContext + ); + } + ); + + return ids.size(); + } + + private void executeDelete( + String targetTableExpression, + EntityMappingType entityDescriptor, + Supplier> tableKeyColumnsVisitationSupplier, + List ids, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + final TableReference targetTableReference = new TableReference( + targetTableExpression, + null, + false, + sessionFactory + ); + + final Predicate matchingIdsPredicate = matchingIdsPredicateProducer.produceRestriction( + ids, + entityDescriptor, + targetTableReference, + tableKeyColumnsVisitationSupplier, + executionContext + ); + + final DeleteStatement deleteStatement = new DeleteStatement( targetTableReference, matchingIdsPredicate ); + + final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( sessionFactory ); + final JdbcDelete jdbcOperation = sqlAstTranslator.translate( deleteStatement ); + + jdbcMutationExecutor.execute( + jdbcOperation, + jdbcParameterBindings, + this::prepareQueryStatement, + (integer, preparedStatement) -> {}, + executionContext + ); + } + + private PreparedStatement prepareQueryStatement(String sql) { + return StatementCreatorHelper.prepareQueryStatement( sql, executionContext.getSession() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineStrategy.java new file mode 100644 index 0000000000..92c22132a6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineStrategy.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import java.util.function.Function; + +import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.dialect.Dialect; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; + +/** + * Support for multi-table SQM mutation operations which select the matching id values from the database back into + * the VM and uses that list of values to produce a restriction for the mutations. The exact form of that + * restriction is based on the {@link MatchingIdRestrictionProducer} implementation used + * + * @author Vlad Mihalcea + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class InlineStrategy implements SqmMultiTableMutationStrategy { + private final Function matchingIdsStrategy; + + public InlineStrategy(Dialect dialect) { + this( determinePredicateProducer( dialect ) ); + } + + private static Function determinePredicateProducer(Dialect dialect) { + throw new NotYetImplementedFor6Exception(); + } + + public InlineStrategy(Function matchingIdsStrategy) { + this.matchingIdsStrategy = matchingIdsStrategy; + } + + @Override + public int executeUpdate( + SqmUpdateStatement sqmUpdate, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + final InlineUpdateHandler handler = new InlineUpdateHandler( + matchingIdsStrategy.apply( sqmUpdate ), + sqmUpdate, + domainParameterXref, + context + ); + return handler.execute( context ); + } + + @Override + public int executeDelete( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + final InlineDeleteHandler deleteHandler = new InlineDeleteHandler( + matchingIdsStrategy.apply( sqmDelete ), + sqmDelete, + domainParameterXref, + context + ); + + return deleteHandler.execute( context ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java new file mode 100644 index 0000000000..52a7a09793 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.UpdateHandler; +import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcMutationExecutor; + +/** + * @author Steve Ebersole + */ +public class InlineUpdateHandler implements UpdateHandler { + private final SqmUpdateStatement sqmUpdate; + private final DomainParameterXref domainParameterXref; + private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; + + private final ExecutionContext executionContext; + + private final SessionFactoryImplementor sessionFactory; + private final SqlAstTranslatorFactory sqlAstTranslatorFactory; + private final JdbcMutationExecutor jdbcMutationExecutor; + + public InlineUpdateHandler( + MatchingIdRestrictionProducer matchingIdsPredicateProducer, + SqmUpdateStatement sqmUpdate, + DomainParameterXref domainParameterXref, + ExecutionContext context) { + this.matchingIdsPredicateProducer = matchingIdsPredicateProducer; + this.domainParameterXref = domainParameterXref; + this.sqmUpdate = sqmUpdate; + + this.executionContext = context; + + this.sessionFactory = executionContext.getSession().getFactory(); + this.sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); + this.jdbcMutationExecutor = sessionFactory.getJdbcServices().getJdbcMutationExecutor(); + } + + @Override + public int execute(ExecutionContext executionContext) { + throw new NotYetImplementedFor6Exception( getClass() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/MatchingIdRestrictionProducer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/MatchingIdRestrictionProducer.java new file mode 100644 index 0000000000..6545be1fa6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/MatchingIdRestrictionProducer.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.metamodel.mapping.ColumnConsumer; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.exec.spi.ExecutionContext; + +/** + * Strategy (pattern) for producing the restriction used when mutating a + * particular table in the group of tables + * + * @author Steve Ebersole + */ +public interface MatchingIdRestrictionProducer { + /** + * Produce the restriction predicate + * + * @param matchingIdValues The matching id values. + * @param mutatingTableReference The TableReference for the table being mutated + * @param columnsToMatchVisitationSupplier The columns against which to restrict the mutations + */ + Predicate produceRestriction( + List matchingIdValues, + EntityMappingType entityDescriptor, + TableReference mutatingTableReference, + Supplier> columnsToMatchVisitationSupplier, + ExecutionContext executionContext); +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/TableValueConstructorRestrictionProducer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/TableValueConstructorRestrictionProducer.java new file mode 100644 index 0000000000..c8c27c2737 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/TableValueConstructorRestrictionProducer.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.metamodel.mapping.ColumnConsumer; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; +import org.hibernate.sql.exec.spi.ExecutionContext; + +/** + * MatchingIdRestrictionProducer producing a restriction based on a SQL table-value-constructor. E.g.: + * + * ```` + * delete + * from + * entity-table + * where + * ( id ) in ( + * select + * id + * from ( + * values + * ( 1 ), + * ( 2 ), + * ( 3 ), + * ( 4 ) + * ) as HT (id) + * ) + * ```` + * + * @author Vlad Mihalcea + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class TableValueConstructorRestrictionProducer implements MatchingIdRestrictionProducer { + @Override + public InSubQueryPredicate produceRestriction( + List matchingIdValues, + EntityMappingType entityDescriptor, + TableReference mutatingTableReference, + Supplier> columnsToMatchVisitationSupplier, + ExecutionContext executionContext) { + // Not "yet" implemented. Not sure we will. This requires the ability to define + // "in-line views" with a table-ctor which the SQL AST does not yet define support for + throw new NotYetImplementedFor6Exception( getClass() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java index 1c3b312c00..837248dafe 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.mutation.spi; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.query.sqm.mutation.internal.Handler; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; /** @@ -21,9 +22,9 @@ public abstract class AbstractMutationHandler implements Handler { public AbstractMutationHandler( SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement, - HandlerCreationContext creationContext) { + SessionFactoryImplementor sessionFactory) { this.sqmDeleteOrUpdateStatement = sqmDeleteOrUpdateStatement; - this.sessionFactory = creationContext.getSessionFactory(); + this.sessionFactory = sessionFactory; final String entityName = sqmDeleteOrUpdateStatement.getTarget() .getReferencedPathSource() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/HandlerCreationContext.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/HandlerCreationContext.java deleted file mode 100644 index 48b1a30518..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/HandlerCreationContext.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.mutation.spi; - -import org.hibernate.engine.spi.SessionFactoryImplementor; - -/** - * Parameter object (pattern) for contextual information for - * {@link SqmMultiTableMutationStrategy#buildUpdateHandler} and - * {@link SqmMultiTableMutationStrategy#buildDeleteHandler} - */ -public interface HandlerCreationContext { - /** - * Access to the SessionFactory - */ - SessionFactoryImplementor getSessionFactory(); -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java index e00b71c68a..eb8bfe9516 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java @@ -9,27 +9,24 @@ package org.hibernate.query.sqm.mutation.spi; import org.hibernate.Metamodel; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; /** - * Pluggable strategy for defining how mutation (`UPDATE` or `DELETE`) - * queries should be handled when the target entity is mapped to multiple - * tables (generally via secondary tables or joined-inheritance). + * Pluggable strategy for defining how mutation (`UPDATE` or `DELETE`) queries should be handled when the target + * entity is mapped to multiple tables via secondary tables or certain inheritance strategies. * - * {@link #prepare} and {@link #release} allow the strategy to perform - * any one time preparation and cleanup. + * The main contracts here are {@link #executeUpdate} and {@link #executeDelete}. * - * The heavy lifting is handled by the {@link UpdateHandler} and {@link DeleteHandler} - * delegates obtained via {@link #buildUpdateHandler} and {@link #buildDeleteHandler} - * methods. + * {@link #prepare} and {@link #release} allow the strategy to perform any one time preparation and cleanup. * - * @apiNote See {@link SqmMutationStrategyHelper#resolveStrategy} for standard resolution - * of the strategy to use. See also {@link SqmMutationStrategyHelper#resolveDeleteHandler} - * and {@link SqmMutationStrategyHelper#resolveUpdateHandler} for standard resolution of - * the delete and update handler to use applying standard special-case handling + * @apiNote See {@link SqmMutationStrategyHelper#resolveStrategy} for standard resolution of the strategy to use + * for each hierarchy * * @author Steve Ebersole */ @@ -40,8 +37,7 @@ public interface SqmMultiTableMutationStrategy { * is being built. */ default void prepare( - Metamodel runtimeMetadata, - SessionFactoryOptions sessionFactoryOptions, + MappingModelCreationProcess mappingModelCreationProcess, JdbcConnectionAccess connectionAccess) { // by default, nothing to do... } @@ -49,35 +45,28 @@ public interface SqmMultiTableMutationStrategy { /** * Release the strategy. Called one time as the SessionFactory is * being shut down. - * - * @param runtimeMetadata Access to the runtime mappings - * @param connectionAccess Access to the JDBC Connection */ - default void release(Metamodel runtimeMetadata, JdbcConnectionAccess connectionAccess) { + default void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAccess connectionAccess) { // by default, nothing to do... } /** - * Build a handler capable of handling the update query indicated by the given SQM tree. + * Execute the multi-table update indicated by the passed SqmUpdateStatement * - * @param sqmUpdateStatement The SQM AST representing the update query - * @param domainParameterXref cross references between SqmParameters and QueryParameters - * @param creationContext Context info for the creation + * @return The number of rows affected */ - UpdateHandler buildUpdateHandler( + int executeUpdate( SqmUpdateStatement sqmUpdateStatement, DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext); + ExecutionContext context); /** - * Build a handler capable of handling the delete query indicated by the given SQM tree. + * Execute the multi-table update indicated by the passed SqmUpdateStatement * - * @param sqmDeleteStatement The SQM AST representing the delete query - * @param domainParameterXref cross references between SqmParameters - * @param creationContext Context info for the creation + * @return The number of rows affected */ - DeleteHandler buildDeleteHandler( + int executeDelete( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, - HandlerCreationContext creationContext); + ExecutionContext context); } 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 ebacb996d5..0e3e3571fa 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 @@ -764,8 +764,7 @@ public abstract class BaseSqmToSqlAstConverter literal, getCreationContext().getDomainModel(), getFromClauseAccess()::findTableGroup - ), - getCurrentClauseStack().getCurrent() + ) ); } @@ -1450,8 +1449,7 @@ public abstract class BaseSqmToSqlAstConverter public Object visitEnumLiteral(SqmEnumLiteral sqmEnumLiteral) { return new QueryLiteral( sqmEnumLiteral.getEnumValue(), - (BasicValuedMapping) determineValueMapping( sqmEnumLiteral ), - getCurrentClauseStack().getCurrent() + (BasicValuedMapping) determineValueMapping( sqmEnumLiteral ) ); } @@ -1459,8 +1457,7 @@ public abstract class BaseSqmToSqlAstConverter public Object visitFieldLiteral(SqmFieldLiteral sqmFieldLiteral) { return new QueryLiteral( sqmFieldLiteral.getValue(), - (BasicValuedMapping) determineValueMapping( sqmFieldLiteral ), - getCurrentClauseStack().getCurrent() + (BasicValuedMapping) determineValueMapping( sqmFieldLiteral ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java index 5302c10635..34625fb5fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlTreePrinter.java @@ -21,6 +21,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; +import org.hibernate.sql.ast.tree.expression.JdbcLiteral; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; @@ -445,9 +446,15 @@ public class SqlTreePrinter implements SqlAstWalker { // logNode( "positional-param (%s)", parameter.getPosition() ); // } + + @Override + public void visitJdbcLiteral(JdbcLiteral jdbcLiteral) { + logNode( "literal (" + jdbcLiteral.getLiteralValue() + ')' ); + } + @Override public void visitQueryLiteral(QueryLiteral queryLiteral) { - logNode( "literal (" + queryLiteral.getValue() + ')' ); + logNode( "literal (" + queryLiteral.getLiteralValue() + ')' ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java index 6b2a6a16e1..59b2689264 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java @@ -16,6 +16,7 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.QueryLiteralRendering; import org.hibernate.query.UnaryArithmeticOperator; import org.hibernate.sql.ast.Clause; @@ -25,6 +26,8 @@ import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcLiteral; +import org.hibernate.sql.ast.tree.expression.Literal; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; @@ -792,6 +795,12 @@ public abstract class AbstractSqlAstWalker // visitJdbcParameterBinder( positionalParameter ); // } + + @Override + public void visitJdbcLiteral(JdbcLiteral jdbcLiteral) { + renderAsLiteral( jdbcLiteral ); + } + @Override public void visitQueryLiteral(QueryLiteral queryLiteral) { final QueryLiteralRendering queryLiteralRendering = getSessionFactory().getSessionFactoryOptions().getQueryLiteralRenderingMode(); @@ -807,7 +816,7 @@ public abstract class AbstractSqlAstWalker } case AUTO: case AS_PARAM_OUTSIDE_SELECT: { - if ( queryLiteral.isInSelect() ) { + if ( clauseStack.getCurrent() == Clause.SELECT ) { renderAsLiteral( queryLiteral ); } else { @@ -824,25 +833,21 @@ public abstract class AbstractSqlAstWalker } @SuppressWarnings("unchecked") - private void renderAsLiteral(QueryLiteral queryLiteral) { - if ( queryLiteral.getValue() == null ) { + private void renderAsLiteral(Literal literal) { + if ( literal.getLiteralValue() == null ) { // todo : not sure we allow this "higher up" appendSql( SqlAppender.NULL_KEYWORD ); } else { - assert queryLiteral.getExpressionType().getJdbcTypeCount( getTypeConfiguration() ) == 1; - queryLiteral.visitJdbcTypes( - jdbcMapping -> { - final JdbcLiteralFormatter literalFormatter = jdbcMapping.getSqlTypeDescriptor().getJdbcLiteralFormatter( jdbcMapping.getJavaTypeDescriptor() ); - appendSql( - literalFormatter.toJdbcLiteral( - queryLiteral.getValue(), - dialect, - null - ) - ); - }, - getTypeConfiguration() + assert literal.getExpressionType().getJdbcTypeCount( getTypeConfiguration() ) == 1; + final JdbcMapping jdbcMapping = literal.getJdbcMapping(); + final JdbcLiteralFormatter literalFormatter = jdbcMapping.getSqlTypeDescriptor().getJdbcLiteralFormatter( jdbcMapping.getJavaTypeDescriptor() ); + appendSql( + literalFormatter.toJdbcLiteral( + literal.getLiteralValue(), + dialect, + null + ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/DerbyCaseExpressionWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/DerbyCaseExpressionWalker.java index 9053c23507..f3f44f2d8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/DerbyCaseExpressionWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/DerbyCaseExpressionWalker.java @@ -37,7 +37,7 @@ public class DerbyCaseExpressionWalker implements CaseExpressionWalker { if ( otherwise != null ) { sqlBuffer.append( " else " ); if ( otherwise instanceof QueryLiteral ) { - Object value = ( (QueryLiteral) otherwise ).getValue(); + Object value = ( (QueryLiteral) otherwise ).getLiteralValue(); if ( value == null ) { // null is not considered the same type as Integer. sqlBuffer.append( "-1" ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstWalker.java index ef5cdf50ef..d148fc1cbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstWalker.java @@ -12,6 +12,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; +import org.hibernate.sql.ast.tree.expression.JdbcLiteral; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; @@ -99,6 +100,8 @@ public interface SqlAstWalker { void visitParameter(JdbcParameter jdbcParameter); + void visitJdbcLiteral(JdbcLiteral jdbcLiteral); + void visitQueryLiteral(QueryLiteral queryLiteral); void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java index e405bc16b8..5a62cbc0b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java @@ -17,7 +17,7 @@ import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.NavigablePath; -import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy; +import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.from.StandardTableGroup; @@ -29,7 +29,6 @@ import org.hibernate.sql.exec.spi.JdbcParameter; import org.hibernate.sql.exec.spi.JdbcParameterBinding; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.internal.SqlSelectionImpl; -import org.hibernate.type.spi.TypeConfiguration; /** * Describes the table definition for the CTE - its name amd its columns @@ -41,10 +40,10 @@ public class CteTable { private final List cteColumns; - public CteTable(EntityMappingType entityDescriptor, TypeConfiguration typeConfiguration) { + public CteTable(EntityMappingType entityDescriptor) { this.sessionFactory = entityDescriptor.getEntityPersister().getFactory(); - final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount( typeConfiguration ); + final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount( sessionFactory.getTypeConfiguration() ); cteColumns = new ArrayList<>( numberOfColumns ); entityDescriptor.getIdentifierMapping().visitColumns( (columnExpression, containingTableExpression, jdbcMapping) -> cteColumns.add( @@ -62,7 +61,7 @@ public class CteTable { } public String getTableExpression() { - return CteBasedMutationStrategy.TABLE_NAME; + return CteStrategy.TABLE_NAME; } public List getCteColumns() { @@ -150,13 +149,13 @@ public class CteTable { return new TableReference( tableValueCtorExpressionBuffer.toString(), - CteBasedMutationStrategy.TABLE_NAME, + CteStrategy.TABLE_NAME, false, sessionFactory ); } - public QuerySpec createCteSubQuery(ExecutionContext executionContext) { + public QuerySpec createCteSubQuery(@SuppressWarnings("unused") ExecutionContext executionContext) { final QuerySpec querySpec = new QuerySpec( false ); final TableReference cteTableReference = new TableReference( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java index 0e012d9593..a8eb5ae05b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java @@ -15,7 +15,7 @@ import java.util.function.Supplier; import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.NavigablePath; -import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy; +import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableReference; @@ -31,8 +31,9 @@ public class CteTableGroup implements TableGroup { private final NavigablePath navigablePath; private final TableReference cteTableReference; + @SuppressWarnings("WeakerAccess") public CteTableGroup(TableReference cteTableReference) { - this.navigablePath = new NavigablePath( CteBasedMutationStrategy.TABLE_NAME ); + this.navigablePath = new NavigablePath( CteStrategy.TABLE_NAME ); this.cteTableReference = cteTableReference; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java new file mode 100644 index 0000000000..b5300b2486 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java @@ -0,0 +1,176 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.tree.expression; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.sql.results.internal.domain.basic.BasicResult; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Represents a literal in the SQL AST. This form accepts a {@link JdbcMapping} and acts + * as its own MappingModelExpressable. + * + * @see QueryLiteral + * + * @author Steve Ebersole + */ +public class JdbcLiteral implements Literal, MappingModelExpressable, DomainResultProducer { + private final T literalValue; + private final JdbcMapping jdbcMapping; + + public JdbcLiteral(T literalValue, JdbcMapping jdbcMapping) { + this.literalValue = literalValue; + this.jdbcMapping = jdbcMapping; + } + + @Override + public Object getLiteralValue() { + return literalValue; + } + + @Override + public JdbcMapping getJdbcMapping() { + return jdbcMapping; + } + + @Override + public void accept(SqlAstWalker sqlTreeWalker) { + sqlTreeWalker.visitJdbcLiteral( this ); + } + + @Override + public void bindParameterValue( + PreparedStatement statement, + int startPosition, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) throws SQLException { + //noinspection unchecked + jdbcMapping.getJdbcValueBinder().bind( + statement, + literalValue, + startPosition, + executionContext.getSession() + ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // MappingModelExpressable + + @Override + public MappingModelExpressable getExpressionType() { + return this; + } + + @Override + public int getJdbcTypeCount(TypeConfiguration typeConfiguration) { + return 1; + } + + @Override + public List getJdbcMappings(TypeConfiguration typeConfiguration) { + return Collections.singletonList( jdbcMapping ); + } + + @Override + public void visitJdbcTypes(Consumer action, Clause clause, TypeConfiguration typeConfiguration) { + action.accept( jdbcMapping ); + } + + @Override + public Object disassemble(Object value, SharedSessionContractImplementor session) { + return value; + } + + @Override + public void visitDisassembledJdbcValues( + Object value, + Clause clause, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + valuesConsumer.consume( value, jdbcMapping ); + } + + @Override + public void visitJdbcValues( + Object value, + Clause clause, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + valuesConsumer.consume( value, jdbcMapping ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // DomainResultProducer + + @Override + public void visitJdbcTypes(Consumer action, TypeConfiguration typeConfiguration) { + action.accept( jdbcMapping ); + } + + @Override + public DomainResult createDomainResult(String resultVariable, DomainResultCreationState creationState) { + final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); + final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); + + final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( + this, + jdbcMapping.getJavaTypeDescriptor(), + sqlAstCreationState.getCreationContext().getDomainModel().getTypeConfiguration() + ); + + //noinspection unchecked + return new BasicResult( sqlSelection.getValuesArrayPosition(), resultVariable, jdbcMapping.getJavaTypeDescriptor() ); + } + + @Override + public void applySqlSelections(DomainResultCreationState creationState) { + final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); + final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); + + sqlExpressionResolver.resolveSqlSelection( + this, + jdbcMapping.getJavaTypeDescriptor(), + sqlAstCreationState.getCreationContext().getDomainModel().getTypeConfiguration() + ); + } + + @Override + public SqlSelection createSqlSelection( + int jdbcPosition, + int valuesArrayPosition, + JavaTypeDescriptor javaTypeDescriptor, + TypeConfiguration typeConfiguration) { + return new SqlSelectionImpl( + jdbcPosition, + valuesArrayPosition, + this, + jdbcMapping + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Literal.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Literal.java new file mode 100644 index 0000000000..039859aab2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Literal.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.tree.expression; + +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; + +/** + * @author Steve Ebersole + */ +public interface Literal extends JdbcParameterBinder, Expression { + Object getLiteralValue(); + JdbcMapping getJdbcMapping(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/QueryLiteral.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/QueryLiteral.java index 3883307b00..7f6de80609 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/QueryLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/QueryLiteral.java @@ -6,22 +6,117 @@ */ package org.hibernate.sql.ast.tree.expression; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.function.Consumer; + import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.sql.ast.Clause; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.sql.ast.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.sql.results.internal.domain.basic.BasicResult; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; /** - * A literal specified in the source query. + * Represents a literal in the SQL AST. This form accepts a {@link BasicValuedMapping} is its MappingModelExpressable. + * + * @see JdbcLiteral * * @author Steve Ebersole */ -public class QueryLiteral extends AbstractLiteral { - public QueryLiteral(Object value, BasicValuedMapping expressableType, Clause clause) { - super( value, expressableType, clause ); +public class QueryLiteral implements Literal, DomainResultProducer { + private final Object value; + private final BasicValuedMapping type; + + public QueryLiteral(Object value, BasicValuedMapping type) { + this.value = value; + this.type = type; + } + + @Override + public Object getLiteralValue() { + return value; + } + + @Override + public JdbcMapping getJdbcMapping() { + return type.getJdbcMapping(); } @Override public void accept(SqlAstWalker walker) { walker.visitQueryLiteral( this ); } + + @Override + public BasicValuedMapping getExpressionType() { + return type; + } + + @Override + public DomainResult createDomainResult( + String resultVariable, + DomainResultCreationState creationState) { + final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver(); + final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( + this, + type.getMappedTypeDescriptor().getMappedJavaTypeDescriptor(), + creationState.getSqlAstCreationState() + .getCreationContext() + .getSessionFactory() + .getTypeConfiguration() + ); + + //noinspection unchecked + return new BasicResult<>( + sqlSelection.getValuesArrayPosition(), + resultVariable, + type.getMappedTypeDescriptor().getMappedJavaTypeDescriptor() + ); + } + + @Override + public SqlSelection createSqlSelection( + int jdbcPosition, + int valuesArrayPosition, + JavaTypeDescriptor javaTypeDescriptor, + TypeConfiguration typeConfiguration) { + return new SqlSelectionImpl( + jdbcPosition, + valuesArrayPosition, + this, + type.getJdbcMapping() + ); + } + + @Override + public void visitJdbcTypes( + Consumer action, + TypeConfiguration typeConfiguration) { + action.accept( type.getJdbcMapping() ); + } + + @Override + public void bindParameterValue( + PreparedStatement statement, + int startPosition, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) throws SQLException { + //noinspection unchecked + ( (BasicType) getExpressionType() ).getJdbcValueBinder().bind( + statement, + getLiteralValue(), + startPosition, + executionContext.getSession() + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java new file mode 100644 index 0000000000..4477729a81 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.exec.spi; + +import java.sql.PreparedStatement; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.resource.jdbc.LogicalConnection; + +/** + * Helper for creating various types of + * NOTE : + * @author Steve Ebersole + */ +public class StatementCreatorHelper { + public static PreparedStatement prepareQueryStatement( + String sql, + SharedSessionContractImplementor session) { + return session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/mutation/multitable/IdSelectionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/mutation/multitable/IdSelectionTests.java new file mode 100644 index 0000000000..12a68327a3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/mutation/multitable/IdSelectionTests.java @@ -0,0 +1,208 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.query.sqm.mutation.multitable; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.orm.test.metamodel.mapping.SecondaryTableTests; +import org.hibernate.orm.test.metamodel.mapping.inheritance.joined.JoinedInheritanceTest; +import org.hibernate.query.internal.ParameterMetadataImpl; +import org.hibernate.query.internal.QueryParameterBindingsImpl; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.sql.exec.spi.Callback; +import org.hibernate.sql.exec.spi.ExecutionContext; + +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +/** + * Tests for selecting matching ids related to SQM update/select statements. + * + * Matching-id-selection is used in CTE- and inline-based strategies. + * + * A "functional correctness" test for {@link MatchingIdSelectionHelper#selectMatchingIds} + * + * @author Steve Ebersole + */ +@SuppressWarnings("WeakerAccess") +@DomainModel( + standardModels = StandardDomainModel.GAMBIT, + annotatedClasses = { + SecondaryTableTests.SimpleEntityWithSecondaryTables.class, + JoinedInheritanceTest.Customer.class, + JoinedInheritanceTest.DomesticCustomer.class, + JoinedInheritanceTest.ForeignCustomer.class + } +) +@ServiceRegistry +@SessionFactory( exportSchema = true ) +public class IdSelectionTests { + + @Test + public void testSecondaryTableRestrictedOnRootTable(SessionFactoryScope scope) { + final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory() + .getQueryEngine() + .getHqlTranslator() + .translate( "delete SimpleEntityWithSecondaryTables where name = :n" ); + + final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm ); + final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); + + final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + scope.getSessionFactory() + ); + domainParamBindings.getBinding( "n" ).setBindValue( "abc" ); + + scope.inTransaction( + session -> { + final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings ); + + MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext ); + } + ); + } + + @Test + public void testSecondaryTableRestrictedOnNonRootTable(SessionFactoryScope scope) { + final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory() + .getQueryEngine() + .getHqlTranslator() + .translate( "delete SimpleEntityWithSecondaryTables where data = :d" ); + + final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm ); + final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); + + final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + scope.getSessionFactory() + ); + domainParamBindings.getBinding( "d" ).setBindValue( "123" ); + + scope.inTransaction( + session -> { + final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings ); + + MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext ); + } + ); + } + + @Test + public void testJoinedSubclassRestrictedOnRootTable(SessionFactoryScope scope) { + final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory() + .getQueryEngine() + .getHqlTranslator() + .translate( "delete Customer where name = :n" ); + + final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm ); + final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); + + final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + scope.getSessionFactory() + ); + domainParamBindings.getBinding( "n" ).setBindValue( "Acme" ); + + scope.inTransaction( + session -> { + final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings ); + + MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext ); + } + ); + } + + @Test + public void testJoinedSubclassRestrictedOnNonPrimaryRootTable(SessionFactoryScope scope) { + final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory() + .getQueryEngine() + .getHqlTranslator() + .translate( "delete ForeignCustomer where name = :n" ); + + final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm ); + final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); + + final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + scope.getSessionFactory() + ); + domainParamBindings.getBinding( "n" ).setBindValue( "Acme" ); + + scope.inTransaction( + session -> { + final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings ); + + MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext ); + } + ); + } + + @Test + public void testJoinedSubclassRestrictedOnPrimaryNonRootTable(SessionFactoryScope scope) { + final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory() + .getQueryEngine() + .getHqlTranslator() + .translate( "delete ForeignCustomer where vat = :v" ); + + final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm ); + final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); + + final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + scope.getSessionFactory() + ); + domainParamBindings.getBinding( "v" ).setBindValue( "123" ); + + scope.inTransaction( + session -> { + final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings ); + + MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext ); + } + ); + } + + private static class TestExecutionContext implements ExecutionContext { + private final SessionImplementor session; + private final QueryParameterBindingsImpl domainParamBindings; + + public TestExecutionContext(SessionImplementor session, QueryParameterBindingsImpl domainParamBindings) { + this.session = session; + this.domainParamBindings = domainParamBindings; + } + + @Override + public SharedSessionContractImplementor getSession() { + return session; + } + + @Override + public QueryOptions getQueryOptions() { + return QueryOptions.NONE; + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return domainParamBindings; + } + + @Override + public Callback getCallback() { + return afterLoadAction -> { + }; + } + } +}