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 61d5ab4db7
commit 74cc07d251
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.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext; 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.DeleteRowsCoordinator;
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp;
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorStandard; 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.persister.spi.PersisterCreationContext;
import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.tree.from.TableGroup; 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.MutatingTableReference;
import org.hibernate.sql.model.ast.RestrictedTableMutation; import org.hibernate.sql.model.ast.RestrictedTableMutation;
import org.hibernate.sql.model.ast.TableInsert; import org.hibernate.sql.model.ast.TableInsert;
import org.hibernate.sql.model.ast.TableMutation; 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.TableInsertBuilderStandard;
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard; 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.JdbcMutationOperation;
import org.hibernate.sql.model.jdbc.JdbcUpdateMutation;
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
@ -410,40 +406,6 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
// Update handling // Update handling
private JdbcMutationOperation generateUpdateRowOperation(MutatingTableReference tableReference) { 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 RestrictedTableMutation<JdbcMutationOperation> sqlAst = generateUpdateRowAst( tableReference );
final SqlAstTranslator<JdbcMutationOperation> translator = getFactory().getJdbcServices() final SqlAstTranslator<JdbcMutationOperation> translator = getFactory().getJdbcServices()
@ -458,6 +420,7 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
final PluralAttributeMapping attribute = getAttributeMapping(); final PluralAttributeMapping attribute = getAttributeMapping();
assert attribute != null; assert attribute != null;
// note that custom sql update row details are handled by TableUpdateBuilderStandard
final TableUpdateBuilderStandard<?> updateBuilder = new TableUpdateBuilderStandard<>( final TableUpdateBuilderStandard<?> updateBuilder = new TableUpdateBuilderStandard<>(
this, this,
tableReference, tableReference,
@ -620,41 +583,6 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
// Delete handling // Delete handling
private JdbcMutationOperation generateDeleteRowOperation(MutatingTableReference tableReference) { 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 RestrictedTableMutation<JdbcMutationOperation> sqlAst = generateDeleteRowAst( tableReference );
final SqlAstTranslator<JdbcMutationOperation> translator = getFactory().getJdbcServices() final SqlAstTranslator<JdbcMutationOperation> translator = getFactory().getJdbcServices()
@ -672,7 +600,8 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
final ForeignKeyDescriptor fkDescriptor = pluralAttribute.getKeyDescriptor(); final ForeignKeyDescriptor fkDescriptor = pluralAttribute.getKeyDescriptor();
assert fkDescriptor != null; 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, this,
tableReference, tableReference,
getFactory(), 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.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.MutationExecutor; import org.hibernate.engine.jdbc.mutation.MutationExecutor;
import org.hibernate.engine.jdbc.mutation.ParameterUsage; 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.internal.MutationQueryOptions;
import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService;
import org.hibernate.engine.spi.SharedSessionContractImplementor; 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.EntityCollectionPart;
import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart; import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext; 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.DeleteRowsCoordinator;
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp; import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp;
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorStandard; 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.MutatingTableReference;
import org.hibernate.sql.model.ast.RestrictedTableMutation; import org.hibernate.sql.model.ast.RestrictedTableMutation;
import org.hibernate.sql.model.ast.TableUpdate; 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.ast.builder.TableUpdateBuilderStandard;
import org.hibernate.sql.model.internal.MutationOperationGroupFactory;
import org.hibernate.sql.model.internal.TableUpdateStandard; 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.JdbcMutationOperation;
import org.hibernate.sql.model.jdbc.JdbcUpdateMutation;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
@ -518,41 +516,6 @@ public class OneToManyPersister extends AbstractCollectionPersister {
} }
private JdbcMutationOperation generateDeleteRowOperation(MutatingTableReference tableReference) { 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 RestrictedTableMutation<JdbcMutationOperation> sqlAst = generateDeleteRowAst( tableReference );
final SqlAstTranslator<JdbcMutationOperation> translator = getFactory().getJdbcServices() final SqlAstTranslator<JdbcMutationOperation> translator = getFactory().getJdbcServices()
@ -564,7 +527,8 @@ public class OneToManyPersister extends AbstractCollectionPersister {
} }
public RestrictedTableMutation<JdbcMutationOperation> generateDeleteRowAst(MutatingTableReference tableReference) { 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, this,
tableReference, tableReference,
getFactory(), getFactory(),
@ -732,41 +696,7 @@ public class OneToManyPersister extends AbstractCollectionPersister {
private JdbcMutationOperation generateWriteIndexOperation(MutatingTableReference tableReference) { private JdbcMutationOperation generateWriteIndexOperation(MutatingTableReference tableReference) {
if ( getIdentifierTableMapping().getUpdateDetails().getCustomSql() != null ) { // note that custom sql update details are handled by TableUpdateBuilderStandard
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) {
final TableUpdateBuilderStandard<JdbcMutationOperation> updateBuilder = new TableUpdateBuilderStandard<>( final TableUpdateBuilderStandard<JdbcMutationOperation> updateBuilder = new TableUpdateBuilderStandard<>(
this, this,
tableReference, 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; this.sqlComment = sqlComment;
} }
public String getWhereFragment() {
return whereFragment;
}
@Override @Override
public void setWhere(String fragment) { public void setWhere(String fragment) {
if ( isCustomSql && fragment != null ) { if ( isCustomSql && fragment != null ) {

View File

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