From 4334b463764466cc9fb48b7564ff1f23130c4cb8 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 30 Mar 2022 17:17:19 +0200 Subject: [PATCH] Update the bulk section of the batching documentation chapter for the new mutation strategies. Also implement the missing InlineUpdateHandler --- .../userguide/appendices/Configurations.adoc | 4 +- .../userguide/chapters/batch/Batching.adoc | 186 ++---- .../internal/MatchingIdSelectionHelper.java | 89 +-- .../TableKeyExpressionCollector.java | 18 +- .../internal/cte/CteDeleteHandler.java | 2 +- .../internal/inline/InlineDeleteHandler.java | 4 +- .../inline/InlineMutationStrategy.java | 6 +- .../internal/inline/InlineUpdateHandler.java | 593 +++++++++++++++++- .../RestrictedDeleteExecutionDelegate.java | 1 + .../temptable/UpdateExecutionDelegate.java | 1 + .../hibernate/sql/ast/tree/insert/Values.java | 10 +- ...tractMutationStrategyCompositeIdTest.java} | 12 +- .../AbstractMutationStrategyIdTest.java} | 17 +- ...efaultMutationStrategyCompositeIdTest.java | 14 + .../bulkid/DefaultMutationStrategyIdTest.java | 14 + .../GlobalQuotedIdentifiersBulkIdTest.java | 15 +- ...InlineMutationStrategyCompositeIdTest.java | 15 + .../bulkid/InlineMutationStrategyIdTest.java | 15 + .../OracleInlineMutationStrategyIdTest.java | 15 + .../CteValuesListBulkCompositeIdTest.java | 19 - .../test/bulkid/CteValuesListBulkIdTest.java | 19 - ...obalTemporaryTableBulkCompositeIdTest.java | 21 - .../InlineIdsInClauseBulkCompositeIdTest.java | 20 - .../bulkid/InlineIdsInClauseBulkIdTest.java | 19 - .../InlineIdsOrClauseBulkCompositeIdTest.java | 13 - .../bulkid/InlineIdsOrClauseBulkIdTest.java | 15 - ...ubSelectValuesListBulkCompositeIdTest.java | 19 - ...nlineIdsSubSelectValuesListBulkIdTest.java | 19 - .../OracleInlineIdsInClauseBulkIdTest.java | 17 - migration-guide.adoc | 3 + 30 files changed, 809 insertions(+), 406 deletions(-) rename hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/{temptable => }/TableKeyExpressionCollector.java (77%) rename hibernate-core/src/{test_legacy/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java => test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java} (90%) rename hibernate-core/src/{test_legacy/org/hibernate/test/bulkid/AbstractBulkIdTest.java => test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java} (84%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java rename hibernate-core/src/{test_legacy/org/hibernate => test/java/org/hibernate/orm}/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java (85%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/OracleInlineMutationStrategyIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkCompositeIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/GlobalTemporaryTableBulkCompositeIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkCompositeIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkCompositeIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkCompositeIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkIdTest.java delete mode 100644 hibernate-core/src/test_legacy/org/hibernate/test/bulkid/OracleInlineIdsInClauseBulkIdTest.java diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index b6d92ddbf3..42410de5d0 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -551,8 +551,8 @@ therefore reusing its execution plan. ==== Multi-table bulk HQL operations -`*hibernate.hql.bulk_id_strategy*` (e.g. A fully-qualified class name, an instance, or a `Class` object reference):: -Provide a custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/hql/spi/id/MultiTableBulkIdStrategy.html[`org.hibernate.hql.spi.id.MultiTableBulkIdStrategy`] implementation for handling multi-table bulk HQL operations. +`*hibernate.query.mutation_strategy*` (e.g. A fully-qualified class name, an instance, or a `Class` object reference):: +Provide a custom https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.html[`org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy`] implementation for handling multi-table bulk HQL operations. `*hibernate.hql.bulk_id_strategy.global_temporary.drop_tables*` (e.g. `true` or `false` (default value)):: For databases that don't support local tables, but just global ones, this configuration property allows you to DROP the global tables used for multi-table bulk HQL operations when the `SessionFactory` or the `EntityManagerFactory` is closed. diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc index e3b48dfd09..bfcdd1d354 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc @@ -1,7 +1,7 @@ [[batch]] == Batching :sourcedir: ../../../../../test/java/org/hibernate/userguide/batch -:bulkid-sourcedir: ../../../../../../../hibernate-core/src/test_legacy/java/org/hibernate/test/bulkid +:bulkid-sourcedir: ../../../../../../../hibernate-core/src/test/java/org/hibernate/orm/test/bulkid :extrasdir: extras [[batch-jdbcbatch]] @@ -165,7 +165,7 @@ Both the Hibernate native Query Language and JPQL (Java Persistence Query Langua [[batch-bulk-hql-update-delete-example]] .Pseudo-syntax for UPDATE and DELETE statements using HQL ==== -[source, JAVA, indent=0] +[source, SQL, indent=0] ---- UPDATE FROM EntityName e WHERE e.name = ? @@ -250,24 +250,32 @@ In the example of joined-subclass, a `DELETE` against one of the subclasses may ==== HQL syntax for INSERT -.Pseudo-syntax for INSERT statements +.Pseudo-syntax for INSERT-SELECT statements ==== -[source, JAVA, indent=0] +[source, SQL, indent=0] ---- INSERT INTO EntityName properties_list -SELECT properties_list +SELECT select_list FROM ... ---- ==== -Only the `INSERT INTO ... SELECT ...` form is supported. -You cannot specify explicit values to insert. +Alternatively one can also declare individual values + +.Pseudo-syntax for INSERT-VALUES statements +==== +[source, SQL, indent=0] +---- +INSERT INTO EntityName + properties_list +VALUES values_list +---- +==== The `properties_list` is analogous to the column specification in the `SQL` `INSERT` statement. -For entities involved in mapped inheritance, you can only use properties directly defined on that given class-level in the `properties_list`. -Superclass properties are not allowed and subclass properties are irrelevant. -In other words, `INSERT` statements are inherently non-polymorphic. +Note that `INSERT` statements are inherently non-polymorphic, so it is not possible to use an `EntityName` +which is abstract or refer to subclass properties. The SELECT statement can be any valid HQL select query, but the return types must match the types expected by the INSERT. Hibernate verifies the return types during query compilation, instead of expecting the database to check it. @@ -282,7 +290,7 @@ Otherwise, Hibernate throws an exception during parsing. Available in-database For properties mapped as either version or timestamp, the insert statement gives you two options. You can either specify the property in the properties_list, in which case its value is taken from the corresponding select expressions or omit it from the properties_list, -in which case the seed value defined by the org.hibernate.type.VersionType is used. +in which case the seed value defined by the `org.hibernate.type.descriptor.java.VersionJavaType` is used. [[batch-bulk-hql-insert-example]] .HQL INSERT statement @@ -296,10 +304,19 @@ include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-insert-example] This section is only a brief overview of HQL. For more information, see <>. [[batch-bulk-hql-strategies]] -==== Bulk-id strategies +==== Bulk mutation strategies -This article is about the https://hibernate.atlassian.net/browse/HHH-11262[HHH-11262] JIRA issue which now allows the bulk-id -strategies to work even when you cannot create temporary tables. +When a bulk mutation involves multiple tables, Hibernate has to issue individual DML statements to the respective tables. +Since the mutation itself could have an effect on the conditions used in the statement, it's generally not possible +to simply execute parts of the DML statement against the respective tables. Instead, Hibernate has to temporarily remember +which rows will be affected, and execute the DML statements based on these rows. + +Usually, Hibernate will make use of local or global temporary tables to remember the primary keys of the rows. +For some databases, currently only PostgreSQL and DB2, a more advanced strategy (`CteMutationStrategy`) is used, +which makes use of DML in CTE support to execute the whole operation in one SQL statement. + +The chosen strategy, unless overridden through the `hibernate.query.mutation_strategy` setting, is based on the +Dialect support through `org.hibernate.dialect.Dialect.getFallbackSqmMutationStrategy`. [[batch-bulk-hql-strategies-class-diagram]] ===== Class diagram @@ -311,22 +328,22 @@ image:images/domain/bulkid/temp_table_class_diagram.png[Entity class diagram] The `Person` entity is the base class of this entity inheritance model, and is mapped as follows: [[batch-bulk-hql-temp-table-base-class-example]] -.Bulk-id base class entity +.Bulk mutation base class entity ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractBulkCompositeIdTest.java[tags=batch-bulk-hql-temp-table-base-class-example] +include::{bulkid-sourcedir}/AbstractMutationStrategyCompositeIdTest.java[tags=batch-bulk-hql-temp-table-base-class-example] ---- ==== Both the `Doctor` and `Engineer` entity classes extend the `Person` base class: [[batch-bulk-hql-temp-table-sub-classes-example]] -.Bulk-id subclass entities +.Bulk mutation subclass entities ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractBulkCompositeIdTest.java[tags=batch-bulk-hql-temp-table-sub-classes-example] +include::{bulkid-sourcedir}/AbstractMutationStrategyIdTest.java[tags=batch-bulk-hql-temp-table-sub-classes-example] ---- ==== @@ -336,11 +353,11 @@ include::{bulkid-sourcedir}/AbstractBulkCompositeIdTest.java[tags=batch-bulk-hql Now, when you try to execute a bulk entity delete query: [[batch-bulk-hql-temp-table-delete-query-example]] -.Bulk-id delete query example +.Bulk mutation delete query example ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractBulkCompositeIdTest.java[tags=batch-bulk-hql-temp-table-delete-query-example] +include::{bulkid-sourcedir}/AbstractMutationStrategyCompositeIdTest.java[tags=batch-bulk-hql-temp-table-delete-query-example] ---- [source, SQL, indent=0] @@ -349,31 +366,21 @@ include::{extrasdir}/batch-bulk-hql-temp-table-delete-query-example.sql[] ---- ==== -`HT_Person` is a temporary table that Hibernate creates to hold all the entity identifiers that are to be updated or deleted by the bulk id operation. +`HT_Person` is a temporary table that Hibernate creates to hold all the entity identifiers that are to be updated or deleted by the bulk operation. The temporary table can be either global or local, depending on the underlying database capabilities. [[batch-bulk-hql-strategies-non-temporary-table]] -===== Non-temporary table bulk-id strategies +===== Non-temporary table bulk mutation strategies -As the https://hibernate.atlassian.net/browse/HHH-11262[HHH-11262] issue describes, there are use cases when the application developer cannot use temporary tables because -the database user lacks this privilege. - -In this case, we defined several options which you can choose depending on your database capabilities: - -- `InlineIdsInClauseBulkIdStrategy` -- `InlineIdsSubSelectValueListBulkIdStrategy` -- `InlineIdsOrClauseBulkIdStrategy` -- `CteValuesListBulkIdStrategy` - -[[batch-bulk-hql-strategies-InlineIdsInClauseBulkIdStrategy]] -====== `InlineIdsInClauseBulkIdStrategy` +When the temporary table strategy can not be used because the database user lacks privilege to create temporary tables, +the `InlineMutationStrategy` must be used. To use this strategy, you need to configure the following configuration property: [source,xml] ---- - ---- @@ -390,110 +397,3 @@ include::{extrasdir}/batch-bulk-hql-InlineIdsInClauseBulkIdStrategy-delete-query So, the entity identifiers are selected first and used for each particular update or delete statement. -[TIP] -==== -The IN clause row value expression has long been supported by Oracle, PostgreSQL, and nowadays by MySQL 5.7. -However, SQL Server 2014 does not support it, so you'll have to use a different strategy. -==== - -[[batch-bulk-hql-strategies-InlineIdsSubSelectValueListBulkIdStrategy]] -====== `InlineIdsSubSelectValueListBulkIdStrategy` - -To use this strategy, you need to configure the following configuration property: - -[source,xml] ----- - ----- - -Now, when running the previous test case, Hibernate generates the following SQL statements: - -[[batch-bulk-hql-InlineIdsSubSelectValueListBulkIdStrategy-delete-query-example]] -.`InlineIdsSubSelectValueListBulkIdStrategy` delete entity query example -==== -[source, SQL, indent=0] ----- -include::{extrasdir}/batch-bulk-hql-InlineIdsSubSelectValueListBulkIdStrategy-delete-query-example.sql[] ----- -==== - -[TIP] -==== -The underlying database must support the `VALUES` list clause, like PostgreSQL or SQL Server 2008. -However, this strategy requires the IN-clause row value expression for composite identifiers, and for this reason, you can only use the `InlineIdsSubSelectValueListBulkIdStrategy` strategy with PostgreSQL. -==== - -[[batch-bulk-hql-strategies-InlineIdsOrClauseBulkIdStrategy]] -====== `InlineIdsOrClauseBulkIdStrategy` - -To use this strategy, you need to configure the following configuration property: - -[source,xml] ----- - ----- - -Now, when running the previous test case, Hibernate generates the following SQL statements: - -[[batch-bulk-hql-InlineIdsOrClauseBulkIdStrategy-delete-query-example]] -.`InlineIdsOrClauseBulkIdStrategy` delete entity query example -==== -[source, SQL, indent=0] ----- -include::{extrasdir}/batch-bulk-hql-InlineIdsOrClauseBulkIdStrategy-delete-query-example.sql[] ----- -==== - -[TIP] -==== -The `InlineIdsOrClauseBulkIdStrategy` strategy has the advantage of being supported by all the major relational database systems (e.g. Oracle, SQL Server, MySQL, and PostgreSQL). -==== - -[[batch-bulk-hql-strategies-CteValuesListBulkIdStrategy]] -====== `CteValuesListBulkIdStrategy` - -To use this strategy, you need to configure the following configuration property: - -[source,xml] ----- - ----- - -Now, when running the previous test case, Hibernate generates the following SQL statements: - -[[batch-bulk-hql-CteValuesListBulkIdStrategy-delete-query-example]] -.`CteValuesListBulkIdStrategy` delete entity query example -==== -[source, SQL, indent=0] ----- -include::{extrasdir}/batch-bulk-hql-CteValuesListBulkIdStrategy-delete-query-example.sql[] ----- -==== - -[TIP] -==== -The underlying database must support CTE (Common Table Expressions) that can be referenced from non-query statements as well. For instance, PostgreSQL supports this feature since version 9.1 and SQL Server offers support for it since version 2005. - -The underlying database must also support the VALUES list clause, like PostgreSQL or SQL Server 2008. - -However, this strategy requires the IN-clause row value expression for composite identifiers, so you can only use this strategy with PostgreSQL. -==== - -If you can use temporary tables, that's probably the best choice. -However, if you are not allowed to create temporary tables, you must pick one of these four strategies that works with your underlying database. -Before making up your mind, you should benchmark which one works best for your current workload. -For instance, https://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/[CTE are optimization fences in PostgreSQL], so make sure you measure before making a decision. - -If you're using Oracle or MySQL 5.7, you can choose either `InlineIdsOrClauseBulkIdStrategy` or `InlineIdsInClauseBulkIdStrategy`. -For older version of MySQL, then you can only use `InlineIdsOrClauseBulkIdStrategy`. - -If you're using SQL Server, `InlineIdsOrClauseBulkIdStrategy` is the only option for you. - -If you're using PostgreSQL, then you have the luxury of choosing any of these four strategies. - 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 index e3a6cd6fdb..30c581e4b9 100644 --- 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 @@ -29,6 +29,7 @@ import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.internal.SqlAstQueryPartProcessingStateImpl; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstTranslator; @@ -47,6 +48,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; +import org.hibernate.sql.results.spi.RowTransformer; import org.jboss.logging.Logger; @@ -190,7 +192,7 @@ public class MatchingIdSelectionHelper { * or UPDATE SQM query */ public static List selectMatchingIds( - SqmDeleteOrUpdateStatement sqmMutationStatement, + SqmDeleteOrUpdateStatement sqmMutationStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); @@ -236,46 +238,50 @@ public class MatchingIdSelectionHelper { factory ); - sqmConverter.getProcessingStateStack().push( - new SqlAstQueryPartProcessingStateImpl( - matchingIdSelection.getQuerySpec(), - sqmConverter.getCurrentProcessingState(), - sqmConverter.getSqlAstCreationState(), - sqmConverter.getCurrentClauseStack()::getCurrent, - true - ) - ); - entityDescriptor.visitSubTypeAttributeMappings( - attribute -> { - if ( attribute instanceof PluralAttributeMapping ) { - final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) attribute; + if ( sqmMutationStatement instanceof SqmDeleteStatement ) { + // For delete statements we also want to collect FK values to execute collection table cleanups - if ( pluralAttribute.getSeparateCollectionTable() != null ) { - // Ensure that the FK target columns are available - final boolean useFkTarget = !( pluralAttribute.getKeyDescriptor() - .getTargetPart() instanceof EntityIdentifierMapping ); - if ( useFkTarget ) { - final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); - pluralAttribute.getKeyDescriptor().getTargetPart().applySqlSelections( - mutatingTableGroup.getNavigablePath(), - mutatingTableGroup, - sqmConverter, - (selection, jdbcMapping) -> { - matchingIdSelection.getDomainResultDescriptors().add( - new BasicResult<>( - selection.getValuesArrayPosition(), - null, - jdbcMapping.getJavaTypeDescriptor() - ) - ); - } - ); + sqmConverter.getProcessingStateStack().push( + new SqlAstQueryPartProcessingStateImpl( + matchingIdSelection.getQuerySpec(), + sqmConverter.getCurrentProcessingState(), + sqmConverter.getSqlAstCreationState(), + sqmConverter.getCurrentClauseStack()::getCurrent, + true + ) + ); + entityDescriptor.visitSubTypeAttributeMappings( + attribute -> { + if ( attribute instanceof PluralAttributeMapping ) { + final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) attribute; + + if ( pluralAttribute.getSeparateCollectionTable() != null ) { + // Ensure that the FK target columns are available + final boolean useFkTarget = !( pluralAttribute.getKeyDescriptor() + .getTargetPart() instanceof EntityIdentifierMapping ); + if ( useFkTarget ) { + final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); + pluralAttribute.getKeyDescriptor().getTargetPart().applySqlSelections( + mutatingTableGroup.getNavigablePath(), + mutatingTableGroup, + sqmConverter, + (selection, jdbcMapping) -> { + matchingIdSelection.getDomainResultDescriptors().add( + new BasicResult<>( + selection.getValuesArrayPosition(), + null, + jdbcMapping.getJavaTypeDescriptor() + ) + ); + } + ); + } } } } - } - ); - sqmConverter.getProcessingStateStack().pop(); + ); + sqmConverter.getProcessingStateStack().pop(); + } final JdbcServices jdbcServices = factory.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); @@ -318,11 +324,18 @@ public class MatchingIdSelectionHelper { ); lockOptions.setLockMode( lockMode ); + final RowTransformer rowTransformer; + if ( matchingIdSelection.getDomainResultDescriptors().size() == 1 ) { + rowTransformer = row -> row[0]; + } + else { + rowTransformer = row -> row; + } return jdbcServices.getJdbcSelectExecutor().list( idSelectJdbcOperation, jdbcParameterBindings, SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), - row -> row[0], + rowTransformer, ListResultsConsumer.UniqueSemantic.FILTER ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableKeyExpressionCollector.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/TableKeyExpressionCollector.java similarity index 77% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableKeyExpressionCollector.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/TableKeyExpressionCollector.java index eb92514b03..2f9ce22f28 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableKeyExpressionCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/TableKeyExpressionCollector.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.temptable; +package org.hibernate.query.sqm.mutation.internal; import java.util.ArrayList; import java.util.List; @@ -17,17 +17,17 @@ import org.hibernate.sql.ast.tree.expression.SqlTuple; /** * @author Steve Ebersole */ -class TableKeyExpressionCollector { - private final EntityMappingType entityMappingType; +public class TableKeyExpressionCollector { - TableKeyExpressionCollector(EntityMappingType entityMappingType) { + private final EntityMappingType entityMappingType; + private Expression firstColumnExpression; + private List collectedColumnExpressions; + + public TableKeyExpressionCollector(EntityMappingType entityMappingType) { this.entityMappingType = entityMappingType; } - Expression firstColumnExpression; - List collectedColumnExpressions; - - void apply(ColumnReference columnReference) { + public void apply(ColumnReference columnReference) { if ( firstColumnExpression == null ) { firstColumnExpression = columnReference; } @@ -41,7 +41,7 @@ class TableKeyExpressionCollector { } } - Expression buildKeyExpression() { + public Expression buildKeyExpression() { if ( collectedColumnExpressions == null ) { return firstColumnExpression; } 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 46ffd83378..5c26ef1d22 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 @@ -36,7 +36,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.results.graph.basic.BasicResult; /** - * Bulk-id delete handler that uses CTE and VALUES lists. + * Bulk mutation delete handler that uses CTE and VALUES lists. * * @author Christian Beikov */ 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 index 03f0cd9dc7..75071a01f0 100644 --- 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 @@ -43,7 +43,7 @@ import org.hibernate.sql.exec.spi.StatementCreatorHelper; */ public class InlineDeleteHandler implements DeleteHandler { private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; - private final SqmDeleteStatement sqmDeleteStatement; + private final SqmDeleteStatement sqmDeleteStatement; private final DomainParameterXref domainParameterXref; private final DomainQueryExecutionContext executionContext; @@ -54,7 +54,7 @@ public class InlineDeleteHandler implements DeleteHandler { protected InlineDeleteHandler( MatchingIdRestrictionProducer matchingIdsPredicateProducer, - SqmDeleteStatement sqmDeleteStatement, + SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { this.sqmDeleteStatement = sqmDeleteStatement; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java index 01fa8ba973..736588814e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java @@ -26,17 +26,17 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; */ @SuppressWarnings("unused") public class InlineMutationStrategy implements SqmMultiTableMutationStrategy { - private final Function matchingIdsStrategy; + private final Function,MatchingIdRestrictionProducer> matchingIdsStrategy; public InlineMutationStrategy(Dialect dialect) { this( determinePredicateProducer( dialect ) ); } - private static Function determinePredicateProducer(Dialect dialect) { + private static Function,MatchingIdRestrictionProducer> determinePredicateProducer(Dialect dialect) { return statement -> new InPredicateRestrictionProducer(); } - public InlineMutationStrategy(Function matchingIdsStrategy) { + public InlineMutationStrategy(Function,MatchingIdRestrictionProducer> matchingIdsStrategy) { this.matchingIdsStrategy = matchingIdsStrategy; } 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 index ae67b18a1d..880c277fb7 100644 --- 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 @@ -6,21 +6,85 @@ */ package org.hibernate.query.sqm.mutation.internal.inline; -import org.hibernate.NotYetImplementedFor6Exception; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.MappingMetamodel; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.SelectableConsumer; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Joinable; +import org.hibernate.query.SemanticException; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.UpdateHandler; +import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +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.spi.NavigablePath; +import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.SqlAliasBaseImpl; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.expression.QueryLiteral; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.from.NamedTableReference; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupJoin; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.from.TableReferenceJoin; +import org.hibernate.sql.ast.tree.from.UnionTableReference; +import org.hibernate.sql.ast.tree.from.ValuesTableGroup; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.insert.Values; +import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.InListPredicate; +import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.predicate.PredicateCollector; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcInsert; import org.hibernate.sql.exec.spi.JdbcMutationExecutor; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcUpdate; +import org.hibernate.sql.results.internal.SqlSelectionImpl; /** * @author Steve Ebersole */ public class InlineUpdateHandler implements UpdateHandler { - private final SqmUpdateStatement sqmUpdate; + private final SqmUpdateStatement sqmUpdate; private final DomainParameterXref domainParameterXref; private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; @@ -32,7 +96,7 @@ public class InlineUpdateHandler implements UpdateHandler { public InlineUpdateHandler( MatchingIdRestrictionProducer matchingIdsPredicateProducer, - SqmUpdateStatement sqmUpdate, + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { this.matchingIdsPredicateProducer = matchingIdsPredicateProducer; @@ -48,6 +112,527 @@ public class InlineUpdateHandler implements UpdateHandler { @Override public int execute(DomainQueryExecutionContext executionContext) { - throw new NotYetImplementedFor6Exception( getClass() ); + final List ids = MatchingIdSelectionHelper.selectMatchingIds( + sqmUpdate, + domainParameterXref, + executionContext + ); + + if ( ids == null || ids.isEmpty() ) { + return 0; + } + + domainParameterXref.clearExpansions(); + final MappingMetamodel domainModel = sessionFactory.getRuntimeMetamodels().getMappingMetamodel(); + + final String mutatingEntityName = sqmUpdate.getTarget().getModel().getHibernateEntityName(); + final EntityPersister entityDescriptor = domainModel.getEntityDescriptor( mutatingEntityName ); + + final String rootEntityName = entityDescriptor.getEntityPersister().getRootEntityName(); + final EntityPersister rootEntityDescriptor = domainModel.getEntityDescriptor( rootEntityName ); + + final String hierarchyRootTableName = ( (Joinable) rootEntityDescriptor ).getTableName(); + + final List inListExpressions = new ArrayList<>( ids.size() ); + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + if ( identifierMapping instanceof BasicValuedModelPart ) { + final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) identifierMapping; + for ( int i = 0; i < ids.size(); i++ ) { + inListExpressions.add( new QueryLiteral<>( ids.get( i ), basicValuedModelPart ) ); + } + } + else { + final int jdbcTypeCount = identifierMapping.getJdbcTypeCount(); + for ( int i = 0; i < ids.size(); i++ ) { + final Object[] id = (Object[]) ids.get( i ); + final List tupleElements = new ArrayList<>( jdbcTypeCount ); + inListExpressions.add( new SqlTuple( tupleElements, identifierMapping ) ); + identifierMapping.forEachJdbcType( + (index, jdbcMapping) -> { + tupleElements.add( + new QueryLiteral<>( id[index], (BasicValuedMapping) jdbcMapping ) + ); + } + ); + } + } + + final MultiTableSqmMutationConverter converterDelegate = new MultiTableSqmMutationConverter( + entityDescriptor, + sqmUpdate, + sqmUpdate.getTarget(), + domainParameterXref, + executionContext.getQueryOptions(), + executionContext.getSession().getLoadQueryInfluencers(), + executionContext.getQueryParameterBindings(), + sessionFactory + ); + + final TableGroup updatingTableGroup = converterDelegate.getMutatingTableGroup(); + + final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( + updatingTableGroup.getNavigablePath(), + hierarchyRootTableName + ); + assert hierarchyRootTableReference != null; + + final Map, List>> parameterResolutions; + if ( domainParameterXref.getSqmParameterCount() == 0 ) { + parameterResolutions = Collections.emptyMap(); + } + else { + parameterResolutions = new IdentityHashMap<>(); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // visit the set-clause using our special converter, collecting + // information about the assignments + + final List assignments = new ArrayList<>(); + final Map, MappingModelExpressible> paramTypeResolutions = new LinkedHashMap<>(); + + converterDelegate.visitSetClause( + sqmUpdate.getSetClause(), + assignments::add, + (sqmParameter, mappingType, jdbcParameters) -> { + parameterResolutions.computeIfAbsent( + sqmParameter, + k -> new ArrayList<>( 1 ) + ).add( jdbcParameters ); + paramTypeResolutions.put( sqmParameter, mappingType ); + } + ); + converterDelegate.addVersionedAssignment( assignments::add, sqmUpdate ); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // visit the where-clause using our special converter, collecting information + // about the restrictions + + final Predicate providedPredicate; + final SqmWhereClause whereClause = sqmUpdate.getWhereClause(); + if ( whereClause == null || whereClause.getPredicate() == null ) { + providedPredicate = null; + } + else { + providedPredicate = converterDelegate.visitWhereClause( + whereClause, + columnReference -> {}, + (sqmParameter, mappingType, jdbcParameters) -> { + parameterResolutions.computeIfAbsent( + sqmParameter, + k -> new ArrayList<>( 1 ) + ).add( jdbcParameters ); + paramTypeResolutions.put( sqmParameter, mappingType ); + } + + ); + assert providedPredicate != null; + } + + final PredicateCollector predicateCollector = new PredicateCollector( providedPredicate ); + + entityDescriptor.applyBaseRestrictions( + predicateCollector::applyPredicate, + updatingTableGroup, + true, + executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), + null, + converterDelegate + ); + + converterDelegate.pruneTableGroupJoins(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // cross-reference the TableReference by alias. The TableGroup already + // cross-references it by name, bu the ColumnReference only has the alias + + final Map tableReferenceByAlias = CollectionHelper.mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 ); + collectTableReference( updatingTableGroup.getPrimaryTableReference(), tableReferenceByAlias::put ); + for ( int i = 0; i < updatingTableGroup.getTableReferenceJoins().size(); i++ ) { + collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put ); + } + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + SqmUtil.generateJdbcParamsXref( + domainParameterXref, + () -> parameterResolutions + ), + sessionFactory.getRuntimeMetamodels().getMappingMetamodel(), + navigablePath -> updatingTableGroup, + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) paramTypeResolutions.get( parameter ); + } + }, + executionContext.getSession() + ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // segment the assignments by table-reference + final Map> assignmentsByTable = new HashMap<>(); + for ( int i = 0; i < assignments.size(); i++ ) { + final Assignment assignment = assignments.get( i ); + final List assignmentColumnRefs = assignment.getAssignable().getColumnReferences(); + + TableReference assignmentTableReference = null; + + for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) { + final ColumnReference columnReference = assignmentColumnRefs.get( c ); + final TableReference tableReference = resolveTableReference( + columnReference, + tableReferenceByAlias + ); + + if ( assignmentTableReference != null && assignmentTableReference != tableReference ) { + throw new SemanticException( "Assignment referred to columns from multiple tables: " + assignment.getAssignable() ); + } + + assignmentTableReference = tableReference; + } + + List assignmentsForTable = assignmentsByTable.get( assignmentTableReference ); + if ( assignmentsForTable == null ) { + assignmentsForTable = new ArrayList<>(); + assignmentsByTable.put( assignmentTableReference, assignmentsForTable ); + } + assignmentsForTable.add( assignment ); + } + + final int rows = ids.size(); + + final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); + entityDescriptor.visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> updateTable( + tableExpression, + tableKeyColumnVisitationSupplier, + entityDescriptor, + updatingTableGroup, + assignmentsByTable, + inListExpressions, + rows, + jdbcParameterBindings, + executionContextAdapter + ) + ); + + return rows; + } + + private void updateTable( + String tableExpression, + Supplier> tableKeyColumnVisitationSupplier, + EntityPersister entityDescriptor, + TableGroup updatingTableGroup, + Map> assignmentsByTable, + List inListExpressions, + int expectedUpdateCount, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + final TableReference updatingTableReference = updatingTableGroup.getTableReference( + updatingTableGroup.getNavigablePath(), + tableExpression, + true, + true + ); + + final List assignments = assignmentsByTable.get( updatingTableReference ); + if ( assignments == null || assignments.isEmpty() ) { + // no assignments for this table - skip it + return; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // create the in-subquery predicate to restrict the updates to just + // matching ids + + final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor ); + + tableKeyColumnVisitationSupplier.get().accept( + (columnIndex, selection) -> { + assert selection.getContainingTableExpression().equals( tableExpression ); + keyColumnCollector.apply( + new ColumnReference( + (String) null, + selection, + sessionFactory + ) + ); + } + ); + + final Expression keyExpression = keyColumnCollector.buildKeyExpression(); + final InListPredicate idListPredicate = new InListPredicate( + keyExpression, + inListExpressions + ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Create the SQL AST and convert it into a JdbcOperation + final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); + final UpdateStatement sqlAst = new UpdateStatement( + dmlTableReference, + assignments, + idListPredicate + ); + + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); + final JdbcUpdate jdbcUpdate = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildUpdateTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + + final int updateCount = jdbcServices.getJdbcMutationExecutor().execute( + jdbcUpdate, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + + if ( updateCount == expectedUpdateCount ) { + // We are done when the update count matches + return; + } + // Otherwise we have to check if the table is nullable, and if so, insert into that table + final AbstractEntityPersister entityPersister = (AbstractEntityPersister) entityDescriptor.getEntityPersister(); + boolean isNullable = false; + for (int i = 0; i < entityPersister.getTableSpan(); i++) { + if ( tableExpression.equals( entityPersister.getTableName( i ) ) && entityPersister.isNullableTable( i ) ) { + isNullable = true; + break; + } + } + if ( isNullable ) { + // Copy the subquery contents into a root query + final QuerySpec querySpec = new QuerySpec( true ); + final NavigablePath valuesPath = new NavigablePath( "id" ); + final List valuesList = new ArrayList<>( inListExpressions.size() ); + for ( Expression inListExpression : inListExpressions ) { + if ( inListExpression instanceof SqlTuple ) { + //noinspection unchecked + valuesList.add( new Values( (List) ( (SqlTuple) inListExpression ).getExpressions() ) ); + } + else { + valuesList.add( new Values( Collections.singletonList( inListExpression ) ) ); + } + } + final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( + true, + updatingTableGroup.getNavigablePath(), + updatingTableGroup.getSourceAlias(), + () -> predicate -> {}, + new SqlAliasBaseImpl( updatingTableGroup.getGroupAlias() ), + null, + null, + sessionFactory + ); + final List columnNames; + final Predicate joinPredicate; + if ( keyExpression instanceof SqlTuple ) { + final List expressions = ( (SqlTuple) keyExpression ).getExpressions(); + final List lhs = new ArrayList<>( expressions.size() ); + final List rhs = new ArrayList<>( expressions.size() ); + columnNames = new ArrayList<>( expressions.size() ); + entityDescriptor.getIdentifierMapping().forEachSelectable( + (i, selectableMapping) -> { + final Expression expression = expressions.get( i ); + final ColumnReference columnReference = expression.getColumnReference(); + final ColumnReference valuesColumnReference = new ColumnReference( + valuesPath.getLocalName(), + columnReference.getColumnExpression(), + false, + null, + null, + columnReference.getJdbcMapping(), + null + ); + columnNames.add( columnReference.getColumnExpression() ); + lhs.add( valuesColumnReference ); + rhs.add( + new ColumnReference( + rootTableGroup.getPrimaryTableReference(), + selectableMapping.getSelectionExpression(), + false, + null, + null, + columnReference.getJdbcMapping(), + null + ) + ); + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( 1, 0, valuesColumnReference ) + ); + } + ); + joinPredicate = new ComparisonPredicate( + new SqlTuple( lhs, entityDescriptor.getIdentifierMapping() ), + ComparisonOperator.EQUAL, + new SqlTuple( rhs, entityDescriptor.getIdentifierMapping() ) + ); + } + else { + final ColumnReference columnReference = keyExpression.getColumnReference(); + final ColumnReference valuesColumnReference = new ColumnReference( + valuesPath.getLocalName(), + columnReference.getColumnExpression(), + false, + null, + null, + columnReference.getJdbcMapping(), + null + ); + columnNames = Collections.singletonList( columnReference.getColumnExpression() ); + joinPredicate = new ComparisonPredicate( + valuesColumnReference, + ComparisonOperator.EQUAL, + new ColumnReference( + rootTableGroup.getPrimaryTableReference(), + ( (BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping() ).getSelectionExpression(), + false, + null, + null, + columnReference.getJdbcMapping(), + null + ) + ); + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( 1, 0, valuesColumnReference ) + ); + } + final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( + valuesPath, + null, + valuesList, + valuesPath.getLocalName(), + columnNames, + true, + sessionFactory + ); + valuesTableGroup.addNestedTableGroupJoin( + new TableGroupJoin( + rootTableGroup.getNavigablePath(), + SqlAstJoinType.LEFT, + rootTableGroup, + joinPredicate + ) + ); + querySpec.getFromClause().addRoot( valuesTableGroup ); + // Only when the target row does not exist + querySpec.applyPredicate( + new NullnessPredicate( + new ColumnReference( + rootTableGroup.resolveTableReference( tableExpression ), + columnNames.get( 0 ), + entityDescriptor.getIdentifierMapping().getJdbcMappings().get( 0 ), + null + ) + ) + ); + + // Collect the target column references from the key expressions + final List targetColumnReferences = new ArrayList<>(); + if ( keyExpression instanceof SqlTuple ) { + //noinspection unchecked + targetColumnReferences.addAll( (Collection) ( (SqlTuple) keyExpression ).getExpressions() ); + } + else { + targetColumnReferences.add( (ColumnReference) keyExpression ); + } + // And transform assignments to target column references and selections + for ( Assignment assignment : assignments ) { + targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() ); + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + 0, + -1, + assignment.getAssignedValue() + ) + ); + } + + final InsertStatement insertSqlAst = new InsertStatement( + dmlTableReference + ); + insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) ); + insertSqlAst.setSourceSelectStatement( querySpec ); + + final JdbcInsert jdbcInsert = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildInsertTranslator( sessionFactory, insertSqlAst ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + + final int insertCount = jdbcServices.getJdbcMutationExecutor().execute( + jdbcInsert, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + assert insertCount + updateCount == expectedUpdateCount; + } + } + + private Expression asExpression(SelectClause selectClause) { + final List sqlSelections = selectClause.getSqlSelections(); + if ( sqlSelections.size() == 1 ) { + return sqlSelections.get( 0 ).getExpression(); + } + final List expressions = new ArrayList<>( sqlSelections.size() ); + for ( SqlSelection sqlSelection : sqlSelections ) { + expressions.add( sqlSelection.getExpression() ); + } + return new SqlTuple( expressions, null ); + } + + private void collectTableReference( + TableReference tableReference, + BiConsumer consumer) { + consumer.accept( tableReference.getIdentificationVariable(), tableReference ); + } + + private void collectTableReference( + TableReferenceJoin tableReferenceJoin, + BiConsumer consumer) { + collectTableReference( tableReferenceJoin.getJoinedTableReference(), consumer ); + } + + private TableReference resolveTableReference( + ColumnReference columnReference, + Map tableReferenceByAlias) { + final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( columnReference.getQualifier() ); + if ( tableReferenceByQualifier != null ) { + return tableReferenceByQualifier; + } + + throw new SemanticException( "Assignment referred to column of a joined association: " + columnReference ); + } + + private NamedTableReference resolveUnionTableReference( + TableReference tableReference, + String tableExpression) { + if ( tableReference instanceof UnionTableReference ) { + return new NamedTableReference( + tableExpression, + tableReference.getIdentificationVariable(), + tableReference.isOptional(), + sessionFactory + ); + } + return (NamedTableReference) tableReference; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java index ad57101f51..65a37a35ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java @@ -39,6 +39,7 @@ import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; +import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java index 06d68cee76..73e3fbb338 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java @@ -31,6 +31,7 @@ import org.hibernate.query.sqm.ComparisonOperator; 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.mutation.internal.TableKeyExpressionCollector; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/Values.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/Values.java index c818893b35..7dc691404c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/Values.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/Values.java @@ -12,7 +12,15 @@ import java.util.ArrayList; import java.util.List; public class Values { - private final List expressions = new ArrayList<>(); + private final List expressions; + + public Values() { + this.expressions = new ArrayList<>(); + } + + public Values(List expressions) { + this.expressions = expressions; + } public List getExpressions() { return expressions; diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java similarity index 90% rename from hibernate-core/src/test_legacy/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java index a7d5c2e788..0d84d5df8a 100644 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/AbstractBulkCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java @@ -1,4 +1,4 @@ -package org.hibernate.test.bulkid; +package org.hibernate.orm.test.bulkid; import java.io.Serializable; import java.util.Objects; @@ -9,7 +9,7 @@ import jakarta.persistence.InheritanceType; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; @@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals; /** * @author Vlad Mihalcea */ -public abstract class AbstractBulkCompositeIdTest extends BaseCoreFunctionalTestCase { +public abstract class AbstractMutationStrategyCompositeIdTest extends BaseCoreFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { @@ -35,13 +35,13 @@ public abstract class AbstractBulkCompositeIdTest extends BaseCoreFunctionalTest @Override protected void configure(Configuration configuration) { super.configure( configuration ); - Class strategyClass = getMultiTableBulkIdStrategyClass(); + Class strategyClass = getMultiTableBulkIdStrategyClass(); if ( strategyClass != null ) { - configuration.setProperty( AvailableSettings.HQL_BULK_ID_STRATEGY, strategyClass.getName() ); + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, strategyClass.getName() ); } } - protected abstract Class getMultiTableBulkIdStrategyClass(); + protected abstract Class getMultiTableBulkIdStrategyClass(); @Override protected boolean isCleanupTestDataRequired() { diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/AbstractBulkIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java similarity index 84% rename from hibernate-core/src/test_legacy/org/hibernate/test/bulkid/AbstractBulkIdTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java index cfc914d560..f5a092d66c 100644 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/AbstractBulkIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java @@ -1,4 +1,4 @@ -package org.hibernate.test.bulkid; +package org.hibernate.orm.test.bulkid; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -8,7 +8,7 @@ import jakarta.persistence.InheritanceType; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; @@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals; /** * @author Vlad Mihalcea */ -public abstract class AbstractBulkIdTest extends BaseCoreFunctionalTestCase { +public abstract class AbstractMutationStrategyIdTest extends BaseCoreFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { @@ -31,15 +31,20 @@ public abstract class AbstractBulkIdTest extends BaseCoreFunctionalTestCase { }; } - @Override @Override protected void configure(Configuration configuration) { super.configure( configuration ); - configuration.setProperty( AvailableSettings.HQL_BULK_ID_STRATEGY, getMultiTableBulkIdStrategyClass().getName() ); + final Class multiTableBulkIdStrategyClass = getMultiTableBulkIdStrategyClass(); + if ( multiTableBulkIdStrategyClass != null ) { + configuration.setProperty( + AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, + multiTableBulkIdStrategyClass.getName() + ); + } } - protected abstract Class getMultiTableBulkIdStrategyClass(); + protected abstract Class getMultiTableBulkIdStrategyClass(); @Override protected boolean isCleanupTestDataRequired() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java new file mode 100644 index 0000000000..aeea77045f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java @@ -0,0 +1,14 @@ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; + +/** + * @author Vlad Mihalcea + */ +public class DefaultMutationStrategyCompositeIdTest extends AbstractMutationStrategyCompositeIdTest { + + @Override + protected Class getMultiTableBulkIdStrategyClass() { + return null; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java new file mode 100644 index 0000000000..d3fe1940bc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java @@ -0,0 +1,14 @@ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; + +/** + * @author Vlad Mihalcea + */ +public class DefaultMutationStrategyIdTest extends AbstractMutationStrategyIdTest { + + @Override + protected Class getMultiTableBulkIdStrategyClass() { + return null; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java similarity index 85% rename from hibernate-core/src/test_legacy/org/hibernate/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java index d923df63d2..b1675c2a8d 100644 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalQuotedIdentifiersBulkIdTest.java @@ -1,4 +1,4 @@ -package org.hibernate.test.bulkid; +package org.hibernate.orm.test.bulkid; import java.sql.Timestamp; import java.util.Arrays; @@ -13,17 +13,13 @@ import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.inline.InlineIdsOrClauseBulkIdStrategy; -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.sqm.mutation.internal.inline.InlineMutationStrategy; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; @@ -31,8 +27,7 @@ import static org.junit.Assert.assertEquals; * @author Vlad Mihalcea */ @TestForIssue( jiraKey = "HHH-12561" ) -public class GlobalQuotedIdentifiersBulkIdTest - extends BaseEntityManagerFunctionalTestCase { +public class GlobalQuotedIdentifiersBulkIdTest extends BaseEntityManagerFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { @@ -46,7 +41,7 @@ public class GlobalQuotedIdentifiersBulkIdTest @Override protected void addConfigOptions(Map options) { options.put( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE ); - options.put( AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, InlineIdsOrClauseBulkIdStrategy.class.getName() ); + options.put( AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, InlineMutationStrategy.class.getName() ); } @Before diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java new file mode 100644 index 0000000000..77cdf6ccb9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java @@ -0,0 +1,15 @@ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.inline.InlineMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; + +/** + * @author Vlad Mihalcea + */ +public class InlineMutationStrategyCompositeIdTest extends AbstractMutationStrategyCompositeIdTest { + + @Override + protected Class getMultiTableBulkIdStrategyClass() { + return InlineMutationStrategy.class; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java new file mode 100644 index 0000000000..7c71dd8483 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java @@ -0,0 +1,15 @@ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.inline.InlineMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; + +/** + * @author Vlad Mihalcea + */ +public class InlineMutationStrategyIdTest extends AbstractMutationStrategyIdTest { + + @Override + protected Class getMultiTableBulkIdStrategyClass() { + return InlineMutationStrategy.class; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/OracleInlineMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/OracleInlineMutationStrategyIdTest.java new file mode 100644 index 0000000000..95ee232dee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/OracleInlineMutationStrategyIdTest.java @@ -0,0 +1,15 @@ +package org.hibernate.orm.test.bulkid; + +/** + * Special test that tries to update 1100 rows. Oracle only supports up to 1000 parameters per in-predicate, + * so we want to test if this scenario works. + * + * @author Vlad Mihalcea + */ +public class OracleInlineMutationStrategyIdTest extends InlineMutationStrategyIdTest { + + @Override + protected int entityCount() { + return 1100; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkCompositeIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkCompositeIdTest.java deleted file mode 100644 index f5266634d8..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkCompositeIdTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.cte.CteValuesListBulkIdStrategy; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialectFeature(DialectChecks.SupportNonQueryValuesListWithCTE.class) -public class CteValuesListBulkCompositeIdTest extends AbstractBulkCompositeIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return CteValuesListBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkIdTest.java deleted file mode 100644 index 3bf87faca5..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/CteValuesListBulkIdTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.cte.CteValuesListBulkIdStrategy; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialectFeature(DialectChecks.SupportNonQueryValuesListWithCTE.class) -public class CteValuesListBulkIdTest extends AbstractBulkIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return CteValuesListBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/GlobalTemporaryTableBulkCompositeIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/GlobalTemporaryTableBulkCompositeIdTest.java deleted file mode 100644 index d6b8dd104e..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/GlobalTemporaryTableBulkCompositeIdTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialectFeature(DialectChecks.SupportsGlobalTemporaryTables.class) -public class GlobalTemporaryTableBulkCompositeIdTest extends AbstractBulkCompositeIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - // Since we only allow dialects that support global temporary tables, we avoid overriding the strategy - // This is important because otherwise we would loose id table configurations that are made in the dialects - return null; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkCompositeIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkCompositeIdTest.java deleted file mode 100644 index 3d905e16ac..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkCompositeIdTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialectFeature(DialectChecks.SupportRowValueConstructorSyntaxInInList.class) -public class InlineIdsInClauseBulkCompositeIdTest extends - AbstractBulkCompositeIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return InlineIdsInClauseBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkIdTest.java deleted file mode 100644 index 68c1f907e6..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsInClauseBulkIdTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialectFeature(DialectChecks.SupportRowValueConstructorSyntaxInInList.class) -public class InlineIdsInClauseBulkIdTest extends AbstractBulkIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return InlineIdsInClauseBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkCompositeIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkCompositeIdTest.java deleted file mode 100644 index 4454efa83d..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkCompositeIdTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.hibernate.test.bulkid; - -/** - * @author Vlad Mihalcea - */ -public class InlineIdsOrClauseBulkCompositeIdTest extends - AbstractBulkCompositeIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return InlineIdsOrClauseBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkIdTest.java deleted file mode 100644 index 99e395eecb..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsOrClauseBulkIdTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.inline.InlineIdsOrClauseBulkIdStrategy; - -/** - * @author Vlad Mihalcea - */ -public class InlineIdsOrClauseBulkIdTest extends AbstractBulkIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return InlineIdsOrClauseBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkCompositeIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkCompositeIdTest.java deleted file mode 100644 index 2bce45b4ac..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkCompositeIdTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.inline.InlineIdsSubSelectValueListBulkIdStrategy; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialectFeature(DialectChecks.SupportValuesListAndRowValueConstructorSyntaxInInList.class) -public class InlineIdsSubSelectValuesListBulkCompositeIdTest extends AbstractBulkCompositeIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return InlineIdsSubSelectValueListBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkIdTest.java deleted file mode 100644 index e104ecfcef..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/InlineIdsSubSelectValuesListBulkIdTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; -import org.hibernate.hql.spi.id.inline.InlineIdsSubSelectValueListBulkIdStrategy; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialectFeature(DialectChecks.SupportValuesListAndRowValueConstructorSyntaxInInList.class) -public class InlineIdsSubSelectValuesListBulkIdTest extends AbstractBulkIdTest { - - @Override - protected Class getMultiTableBulkIdStrategyClass() { - return InlineIdsSubSelectValueListBulkIdStrategy.class; - } -} \ No newline at end of file diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/OracleInlineIdsInClauseBulkIdTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/OracleInlineIdsInClauseBulkIdTest.java deleted file mode 100644 index 3594031189..0000000000 --- a/hibernate-core/src/test_legacy/org/hibernate/test/bulkid/OracleInlineIdsInClauseBulkIdTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.hibernate.test.bulkid; - -import org.hibernate.dialect.Oracle8iDialect; - -import org.hibernate.testing.RequiresDialect; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialect(Oracle8iDialect.class) -public class OracleInlineIdsInClauseBulkIdTest extends InlineIdsInClauseBulkIdTest { - - @Override - protected int entityCount() { - return 1100; - } -} \ No newline at end of file diff --git a/migration-guide.adoc b/migration-guide.adoc index 2589af770d..840154b71a 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -82,6 +82,9 @@ containing the various individual DML statements that would normally be executed This allows to run SQM DML statements in a single JDBC operation that does not move any data between the database and the application, which should provide a significant boost for statements that involve many rows. +Note that the configuration property `hibernate.hql.bulk_id_strategy` was changed to `hibernate.query.mutation_strategy` +which will now refer to classes or objects implementing `org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy`. + [[identifier-object]] == Identifier as Object