HHH-17170 Support custom sql mutations for associated collections

This commit is contained in:
Marco Belladelli 2023-09-08 12:07:53 +02:00
parent 800f8f6f31
commit db2f2d8a9f
6 changed files with 180 additions and 150 deletions

View File

@ -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<JdbcMutationOperation> sqlAst = generateUpdateRowAst( tableReference );
final SqlAstTranslator<JdbcMutationOperation> 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<JdbcMutationOperation> sqlAst = generateDeleteRowAst( tableReference );
final SqlAstTranslator<JdbcMutationOperation> 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(),

View File

@ -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<JdbcMutationOperation> sqlAst = generateDeleteRowAst( tableReference );
final SqlAstTranslator<JdbcMutationOperation> translator = getFactory().getJdbcServices()
@ -568,7 +531,8 @@ public class OneToManyPersister extends AbstractCollectionPersister {
}
public RestrictedTableMutation<JdbcMutationOperation> generateDeleteRowAst(MutatingTableReference tableReference) {
final TableUpdateBuilderStandard<MutationOperation> updateBuilder = new TableUpdateBuilderStandard<>(
// note that custom sql delete row details are handled by CollectionRowUpdateBuilder
final CollectionRowDeleteByUpdateSetNullBuilder<MutationOperation> 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<JdbcMutationOperation> updateBuilder = new TableUpdateBuilderStandard<>(
this,
tableReference,

View File

@ -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();
}
};
}
}

View File

@ -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<O extends MutationOperation> extends TableUpdateBuilderStandard<O> {
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<O> buildMutation() {
final CollectionTableMapping tableMapping = (CollectionTableMapping) getMutatingTable().getTableMapping();
final List<ColumnValueBinding> valueBindings = combine(
getValueBindings(),
getKeyBindings(),
getLobValueBindings()
);
if ( tableMapping.getDeleteRowDetails().getCustomSql() != null ) {
return (RestrictedTableMutation<O>) 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<O>) new TableUpdateStandard(
getMutatingTable(),
getMutationTarget(),
getSqlComment(),
valueBindings,
getKeyRestrictionBindings(),
getOptimisticLockBindings(),
getWhereFragment(),
null
) {
@Override
public Expectation getExpectation() {
return tableMapping.getDeleteRowDetails().getExpectation();
}
};
}
}

View File

@ -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 ) {

View File

@ -54,6 +54,10 @@ public class TableUpdateBuilderStandard<O extends MutationOperation> extends Abs
this.whereFragment = whereFragment;
}
public String getWhereFragment() {
return whereFragment;
}
@SuppressWarnings("unchecked")
@Override
public RestrictedTableMutation<O> buildMutation() {