From db2f2d8a9ff6fb42a5a3c96b50496a3eabbf68cc Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Fri, 8 Sep 2023 12:07:53 +0200 Subject: [PATCH] HHH-17170 Support custom sql mutations for associated collections --- .../collection/BasicCollectionPersister.java | 79 +---------------- .../collection/OneToManyPersister.java | 80 ++--------------- .../builder/CollectionRowDeleteBuilder.java | 76 ++++++++++++++++ ...ectionRowDeleteByUpdateSetNullBuilder.java | 87 +++++++++++++++++++ .../builder/TableDeleteBuilderStandard.java | 4 + .../builder/TableUpdateBuilderStandard.java | 4 + 6 files changed, 180 insertions(+), 150 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteBuilder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteByUpdateSetNullBuilder.java diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java index 115cb1e84d..8c7c729eec 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java @@ -26,7 +26,6 @@ import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.persister.collection.mutation.CollectionTableMapping; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinator; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorStandard; @@ -44,17 +43,14 @@ import org.hibernate.persister.collection.mutation.UpdateRowsCoordinatorStandard import org.hibernate.persister.spi.PersisterCreationContext; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.model.ast.ColumnValueParameterList; import org.hibernate.sql.model.ast.MutatingTableReference; import org.hibernate.sql.model.ast.RestrictedTableMutation; import org.hibernate.sql.model.ast.TableInsert; import org.hibernate.sql.model.ast.TableMutation; -import org.hibernate.sql.model.ast.builder.TableDeleteBuilderStandard; +import org.hibernate.sql.model.ast.builder.CollectionRowDeleteBuilder; import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard; import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard; -import org.hibernate.sql.model.jdbc.JdbcDeleteMutation; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; -import org.hibernate.sql.model.jdbc.JdbcUpdateMutation; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -410,40 +406,6 @@ public class BasicCollectionPersister extends AbstractCollectionPersister { // Update handling private JdbcMutationOperation generateUpdateRowOperation(MutatingTableReference tableReference) { - if ( getIdentifierTableMapping().getUpdateDetails().getCustomSql() != null ) { - return buildCustomSqlUpdateRowOperation( tableReference ); - } - - return buildGeneratedUpdateRowOperation( tableReference ); - - } - - private JdbcMutationOperation buildCustomSqlUpdateRowOperation(MutatingTableReference tableReference) { - final PluralAttributeMapping attribute = getAttributeMapping(); - assert attribute != null; - - final ForeignKeyDescriptor foreignKey = attribute.getKeyDescriptor(); - assert foreignKey != null; - - final int keyColumnCount = foreignKey.getJdbcTypeCount(); - final ColumnValueParameterList parameterBinders = new ColumnValueParameterList( - tableReference, - ParameterUsage.RESTRICT, - keyColumnCount - ); - foreignKey.getKeyPart().forEachSelectable( parameterBinders ); - - return new JdbcUpdateMutation( - getCollectionTableMapping(), - this, - getCollectionTableMapping().getUpdateDetails().getCustomSql(), - getCollectionTableMapping().getUpdateDetails().isCallable(), - getCollectionTableMapping().getUpdateDetails().getExpectation(), - parameterBinders - ); - } - - private JdbcMutationOperation buildGeneratedUpdateRowOperation(MutatingTableReference tableReference) { final RestrictedTableMutation sqlAst = generateUpdateRowAst( tableReference ); final SqlAstTranslator translator = getFactory().getJdbcServices() @@ -458,6 +420,7 @@ public class BasicCollectionPersister extends AbstractCollectionPersister { final PluralAttributeMapping attribute = getAttributeMapping(); assert attribute != null; + // note that custom sql update row details are handled by TableUpdateBuilderStandard final TableUpdateBuilderStandard updateBuilder = new TableUpdateBuilderStandard<>( this, tableReference, @@ -620,41 +583,6 @@ public class BasicCollectionPersister extends AbstractCollectionPersister { // Delete handling private JdbcMutationOperation generateDeleteRowOperation(MutatingTableReference tableReference) { - if ( getIdentifierTableMapping().getDeleteRowDetails().getCustomSql() != null ) { - return buildCustomSqlDeleteRowOperation( tableReference ); - } - - return buildGeneratedDeleteRowOperation( tableReference ); - } - - private JdbcMutationOperation buildCustomSqlDeleteRowOperation(MutatingTableReference tableReference) { - final PluralAttributeMapping attribute = getAttributeMapping(); - assert attribute != null; - - final ForeignKeyDescriptor foreignKey = attribute.getKeyDescriptor(); - assert foreignKey != null; - - final CollectionTableMapping tableMapping = (CollectionTableMapping) tableReference.getTableMapping(); - - final int keyColumnCount = foreignKey.getJdbcTypeCount(); - final ColumnValueParameterList parameterBinders = new ColumnValueParameterList( - tableReference, - ParameterUsage.RESTRICT, - keyColumnCount - ); - foreignKey.getKeyPart().forEachSelectable( parameterBinders ); - - return new JdbcDeleteMutation( - tableMapping, - this, - tableMapping.getDeleteDetails().getCustomSql(), - tableMapping.getDeleteDetails().isCallable(), - tableMapping.getDeleteDetails().getExpectation(), - parameterBinders - ); - } - - private JdbcMutationOperation buildGeneratedDeleteRowOperation(MutatingTableReference tableReference) { final RestrictedTableMutation sqlAst = generateDeleteRowAst( tableReference ); final SqlAstTranslator translator = getFactory().getJdbcServices() @@ -672,7 +600,8 @@ public class BasicCollectionPersister extends AbstractCollectionPersister { final ForeignKeyDescriptor fkDescriptor = pluralAttribute.getKeyDescriptor(); assert fkDescriptor != null; - final TableDeleteBuilderStandard deleteBuilder = new TableDeleteBuilderStandard( + // note that custom sql delete row details are handled by CollectionRowDeleteBuilder + final CollectionRowDeleteBuilder deleteBuilder = new CollectionRowDeleteBuilder( this, tableReference, getFactory(), diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java index 5a9fe9de8b..70957ab352 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java @@ -20,7 +20,6 @@ import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.MutationExecutor; import org.hibernate.engine.jdbc.mutation.ParameterUsage; -import org.hibernate.sql.model.internal.MutationOperationGroupFactory; import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions; import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -38,7 +37,6 @@ import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.persister.collection.mutation.CollectionTableMapping; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinator; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorStandard; @@ -74,11 +72,11 @@ import org.hibernate.sql.model.ast.ColumnWriteFragment; import org.hibernate.sql.model.ast.MutatingTableReference; import org.hibernate.sql.model.ast.RestrictedTableMutation; import org.hibernate.sql.model.ast.TableUpdate; +import org.hibernate.sql.model.ast.builder.CollectionRowDeleteByUpdateSetNullBuilder; import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard; +import org.hibernate.sql.model.internal.MutationOperationGroupFactory; import org.hibernate.sql.model.internal.TableUpdateStandard; -import org.hibernate.sql.model.jdbc.JdbcDeleteMutation; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; -import org.hibernate.sql.model.jdbc.JdbcUpdateMutation; import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -522,41 +520,6 @@ public class OneToManyPersister extends AbstractCollectionPersister { } private JdbcMutationOperation generateDeleteRowOperation(MutatingTableReference tableReference) { - if ( getIdentifierTableMapping().getDeleteRowDetails().getCustomSql() != null ) { - return buildCustomSqlDeleteRowOperation( tableReference ); - } - - return buildGeneratedDeleteRowOperation( tableReference ); - } - - private JdbcMutationOperation buildCustomSqlDeleteRowOperation(MutatingTableReference tableReference) { - final PluralAttributeMapping attribute = getAttributeMapping(); - assert attribute != null; - - final ForeignKeyDescriptor foreignKey = attribute.getKeyDescriptor(); - assert foreignKey != null; - - final CollectionTableMapping tableMapping = (CollectionTableMapping) tableReference.getTableMapping(); - - final int keyColumnCount = foreignKey.getJdbcTypeCount(); - final ColumnValueParameterList parameterBinders = new ColumnValueParameterList( - tableReference, - ParameterUsage.RESTRICT, - keyColumnCount - ); - foreignKey.getKeyPart().forEachSelectable( parameterBinders ); - - return new JdbcDeleteMutation( - tableMapping, - this, - tableMapping.getDeleteDetails().getCustomSql(), - tableMapping.getDeleteDetails().isCallable(), - tableMapping.getDeleteDetails().getExpectation(), - parameterBinders - ); - } - - private JdbcMutationOperation buildGeneratedDeleteRowOperation(MutatingTableReference tableReference) { final RestrictedTableMutation sqlAst = generateDeleteRowAst( tableReference ); final SqlAstTranslator translator = getFactory().getJdbcServices() @@ -568,7 +531,8 @@ public class OneToManyPersister extends AbstractCollectionPersister { } public RestrictedTableMutation generateDeleteRowAst(MutatingTableReference tableReference) { - final TableUpdateBuilderStandard updateBuilder = new TableUpdateBuilderStandard<>( + // note that custom sql delete row details are handled by CollectionRowUpdateBuilder + final CollectionRowDeleteByUpdateSetNullBuilder updateBuilder = new CollectionRowDeleteByUpdateSetNullBuilder<>( this, tableReference, getFactory(), @@ -736,41 +700,7 @@ public class OneToManyPersister extends AbstractCollectionPersister { private JdbcMutationOperation generateWriteIndexOperation(MutatingTableReference tableReference) { - if ( getIdentifierTableMapping().getUpdateDetails().getCustomSql() != null ) { - return buildCustomSqlWriteIndexOperation( tableReference ); - } - - return buildGeneratedWriteIndexOperation( tableReference ); - } - - private JdbcMutationOperation buildCustomSqlWriteIndexOperation(MutatingTableReference tableReference) { - final PluralAttributeMapping attribute = getAttributeMapping(); - assert attribute != null; - - final ForeignKeyDescriptor foreignKey = attribute.getKeyDescriptor(); - assert foreignKey != null; - - final CollectionTableMapping tableMapping = (CollectionTableMapping) tableReference.getTableMapping(); - - final int keyColumnCount = foreignKey.getJdbcTypeCount(); - final ColumnValueParameterList parameterBinders = new ColumnValueParameterList( - tableReference, - ParameterUsage.RESTRICT, - keyColumnCount - ); - foreignKey.getKeyPart().forEachSelectable( parameterBinders ); - - return new JdbcUpdateMutation( - tableMapping, - this, - tableMapping.getUpdateDetails().getCustomSql(), - tableMapping.getUpdateDetails().isCallable(), - tableMapping.getUpdateDetails().getExpectation(), - parameterBinders - ); - } - - private JdbcMutationOperation buildGeneratedWriteIndexOperation(MutatingTableReference tableReference) { + // note that custom sql update details are handled by TableUpdateBuilderStandard final TableUpdateBuilderStandard updateBuilder = new TableUpdateBuilderStandard<>( this, tableReference, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteBuilder.java b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteBuilder.java new file mode 100644 index 0000000000..6740de509c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteBuilder.java @@ -0,0 +1,76 @@ +/* + * 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.model.ast.builder; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jdbc.Expectation; +import org.hibernate.persister.collection.mutation.CollectionTableMapping; +import org.hibernate.sql.model.MutationTarget; +import org.hibernate.sql.model.ast.MutatingTableReference; +import org.hibernate.sql.model.ast.TableDelete; +import org.hibernate.sql.model.internal.TableDeleteCustomSql; +import org.hibernate.sql.model.internal.TableDeleteStandard; + +/** + * Custom table delete builder for many-to-many collection join tables that handles row deletes + * + * @author Marco Belladelli + */ +public class CollectionRowDeleteBuilder extends TableDeleteBuilderStandard { + public CollectionRowDeleteBuilder( + MutationTarget mutationTarget, + MutatingTableReference tableReference, + SessionFactoryImplementor sessionFactory, + String whereFragment) { + super( mutationTarget, tableReference, sessionFactory, whereFragment ); + assert tableReference.getTableMapping() instanceof CollectionTableMapping; + } + + @Override + public TableDelete buildMutation() { + final CollectionTableMapping tableMapping = (CollectionTableMapping) getMutatingTable().getTableMapping(); + if ( tableMapping.getDeleteRowDetails().getCustomSql() != null ) { + return new TableDeleteCustomSql( + getMutatingTable(), + getMutationTarget(), + getSqlComment(), + getKeyRestrictionBindings(), + getOptimisticLockBindings(), + getParameters() + ) { + @Override + public String getCustomSql() { + return tableMapping.getDeleteRowDetails().getCustomSql(); + } + + @Override + public boolean isCallable() { + return tableMapping.getDeleteRowDetails().isCallable(); + } + + @Override + public Expectation getExpectation() { + return tableMapping.getDeleteRowDetails().getExpectation(); + } + }; + } + return new TableDeleteStandard( + getMutatingTable(), + getMutationTarget(), + getSqlComment(), + getKeyRestrictionBindings(), + getOptimisticLockBindings(), + getParameters(), + getWhereFragment() + ) { + @Override + public Expectation getExpectation() { + return tableMapping.getDeleteRowDetails().getExpectation(); + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteByUpdateSetNullBuilder.java b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteByUpdateSetNullBuilder.java new file mode 100644 index 0000000000..07853bdbb0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/CollectionRowDeleteByUpdateSetNullBuilder.java @@ -0,0 +1,87 @@ +/* + * 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.model.ast.builder; + +import java.util.List; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.jdbc.Expectation; +import org.hibernate.persister.collection.mutation.CollectionTableMapping; +import org.hibernate.sql.model.MutationOperation; +import org.hibernate.sql.model.MutationTarget; +import org.hibernate.sql.model.ast.ColumnValueBinding; +import org.hibernate.sql.model.ast.MutatingTableReference; +import org.hibernate.sql.model.ast.RestrictedTableMutation; +import org.hibernate.sql.model.internal.TableUpdateCustomSql; +import org.hibernate.sql.model.internal.TableUpdateStandard; + +/** + * Custom table update builder for one-to-many collections that handles row deletes + * + * @author Marco Belladelli + */ +public class CollectionRowDeleteByUpdateSetNullBuilder extends TableUpdateBuilderStandard { + public CollectionRowDeleteByUpdateSetNullBuilder( + MutationTarget mutationTarget, + MutatingTableReference tableReference, + SessionFactoryImplementor sessionFactory, + String whereFragment) { + super( mutationTarget, tableReference, sessionFactory, whereFragment ); + assert tableReference.getTableMapping() instanceof CollectionTableMapping; + } + + @SuppressWarnings( "unchecked" ) + @Override + public RestrictedTableMutation buildMutation() { + final CollectionTableMapping tableMapping = (CollectionTableMapping) getMutatingTable().getTableMapping(); + final List valueBindings = combine( + getValueBindings(), + getKeyBindings(), + getLobValueBindings() + ); + if ( tableMapping.getDeleteRowDetails().getCustomSql() != null ) { + return (RestrictedTableMutation) new TableUpdateCustomSql( + getMutatingTable(), + getMutationTarget(), + getSqlComment(), + valueBindings, + getKeyRestrictionBindings(), + getOptimisticLockBindings() + ) { + @Override + public String getCustomSql() { + return tableMapping.getDeleteRowDetails().getCustomSql(); + } + + @Override + public boolean isCallable() { + return tableMapping.getDeleteRowDetails().isCallable(); + } + + @Override + public Expectation getExpectation() { + return tableMapping.getDeleteRowDetails().getExpectation(); + } + }; + } + return (RestrictedTableMutation) new TableUpdateStandard( + getMutatingTable(), + getMutationTarget(), + getSqlComment(), + valueBindings, + getKeyRestrictionBindings(), + getOptimisticLockBindings(), + getWhereFragment(), + null + ) { + @Override + public Expectation getExpectation() { + return tableMapping.getDeleteRowDetails().getExpectation(); + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableDeleteBuilderStandard.java b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableDeleteBuilderStandard.java index 10fcbe778f..2d9b6b3aba 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableDeleteBuilderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableDeleteBuilderStandard.java @@ -67,6 +67,10 @@ public class TableDeleteBuilderStandard this.sqlComment = sqlComment; } + public String getWhereFragment() { + return whereFragment; + } + @Override public void setWhere(String fragment) { if ( isCustomSql && fragment != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableUpdateBuilderStandard.java b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableUpdateBuilderStandard.java index fb481ee574..eb267ed873 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableUpdateBuilderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/builder/TableUpdateBuilderStandard.java @@ -54,6 +54,10 @@ public class TableUpdateBuilderStandard extends Abs this.whereFragment = whereFragment; } + public String getWhereFragment() { + return whereFragment; + } + @SuppressWarnings("unchecked") @Override public RestrictedTableMutation buildMutation() {