From 3e0f5a8a8e77936d6c4823ad3774c91ae3cff2c6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 22 Feb 2019 18:25:10 +0000 Subject: [PATCH] Implement OneToManyRowsDeletionExecutor --- .../OneToManyRowsDeletionExecutor.java | 325 +++++++++++++++++- 1 file changed, 323 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/collection/OneToManyRowsDeletionExecutor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/collection/OneToManyRowsDeletionExecutor.java index 9c3c15302a74..752e5545ab30 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/collection/OneToManyRowsDeletionExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/collection/OneToManyRowsDeletionExecutor.java @@ -6,27 +6,348 @@ */ package org.hibernate.metamodel.model.domain.internal.collection; -import org.hibernate.NotYetImplementedFor6Exception; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.model.domain.spi.Navigable; import org.hibernate.metamodel.model.domain.spi.PersistentCollectionDescriptor; +import org.hibernate.metamodel.model.relational.spi.Column; import org.hibernate.metamodel.model.relational.spi.Table; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.query.spi.ComparisonOperator; +import org.hibernate.sql.SqlExpressableType; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.consume.spi.UpdateToJdbcUpdateConverter; +import org.hibernate.sql.ast.tree.spi.UpdateStatement; +import org.hibernate.sql.ast.tree.spi.assign.Assignment; +import org.hibernate.sql.ast.tree.spi.expression.ColumnReference; +import org.hibernate.sql.ast.tree.spi.expression.LiteralParameter; +import org.hibernate.sql.ast.tree.spi.expression.PositionalParameter; +import org.hibernate.sql.ast.tree.spi.from.TableReference; +import org.hibernate.sql.ast.tree.spi.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.spi.predicate.Junction; +import org.hibernate.sql.ast.tree.spi.predicate.Predicate; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.BasicExecutionContext; +import org.hibernate.sql.exec.spi.JdbcMutationExecutor; +import org.hibernate.sql.exec.spi.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcUpdate; + +import org.jboss.logging.Logger; /** * @author Chris Cranford */ public class OneToManyRowsDeletionExecutor implements CollectionRowsDeletionExecutor { + private static final Logger log = Logger.getLogger( OneToManyRowsDeletionExecutor.class ); + + + private final PersistentCollectionDescriptor collectionDescriptor; + private boolean deleteByIndex; + private Map jdbcParameterMap; + private final JdbcUpdate removalOperation; + public OneToManyRowsDeletionExecutor( PersistentCollectionDescriptor collectionDescriptor, SessionFactoryImplementor sessionFactory, Table dmlTargetTable, boolean deleteByIndex) { + this.collectionDescriptor = collectionDescriptor; + this.deleteByIndex = deleteByIndex; + final TableReference collectionTableRef = new TableReference( + dmlTargetTable, + null, + false + ); + final Map jdbcParameterMap = new HashMap<>(); + + final List assignments = new ArrayList<>(); + final AtomicInteger parameterCount = new AtomicInteger(); + + + applyNavigable( + collectionDescriptor.getCollectionKeyDescriptor(), + collectionTableRef, + parameterCount, + jdbcParameterMap::put, + assignments, + sessionFactory + ); + + if ( deleteByIndex ) { + applyNavigable( + collectionDescriptor.getIndexDescriptor(), + collectionTableRef, + parameterCount, + jdbcParameterMap::put, + assignments, + sessionFactory + ); + } + + final Predicate predicate = resolvePredicate( + collectionTableRef, + parameterCount, + jdbcParameterMap::put, + sessionFactory + ); + + + final UpdateStatement updateStatement = new UpdateStatement( collectionTableRef, assignments, predicate ); + + removalOperation = UpdateToJdbcUpdateConverter.createJdbcUpdate( + updateStatement, + sessionFactory + ); + + this.jdbcParameterMap = jdbcParameterMap; + + } + + private Predicate resolvePredicate( + TableReference dmltableRef, + AtomicInteger parameterCount, + BiConsumer columnConsumer, + SessionFactoryImplementor sessionFactory) { + Junction junction = new Junction( Junction.Nature.CONJUNCTION ); + collectionDescriptor.getCollectionKeyDescriptor().visitColumns( + (BiConsumer) (sqlExpressableType, column) -> { + final PositionalParameter parameter = new PositionalParameter( + parameterCount.getAndIncrement(), + column.getExpressableType(), + Clause.UPDATE, + sessionFactory.getTypeConfiguration() + ); + + columnConsumer.accept( column, parameter ); + + junction.add( + new ComparisonPredicate( + new ColumnReference( column ), + ComparisonOperator.EQUAL, + parameter + ) + ); + }, + Clause.WHERE, + sessionFactory.getTypeConfiguration() + ); + collectionDescriptor.getElementDescriptor().visitColumns( + (BiConsumer) (sqlExpressableType, column) -> { + final PositionalParameter parameter = new PositionalParameter( + parameterCount.getAndIncrement(), + column.getExpressableType(), + Clause.UPDATE, + sessionFactory.getTypeConfiguration() + ); + + columnConsumer.accept( column, parameter ); + + junction.add( + new ComparisonPredicate( + new ColumnReference( column ), + ComparisonOperator.EQUAL, + parameter + ) + ); + }, + Clause.WHERE, + sessionFactory.getTypeConfiguration() + ); + return junction; } @Override public void execute( PersistentCollection collection, Object key, SharedSessionContractImplementor session) { - throw new NotYetImplementedFor6Exception( getClass() ); + Iterator deletes = collection.getDeletes( collectionDescriptor, !deleteByIndex ); + if ( log.isDebugEnabled() ) { + log.debugf( + "Deleting rows of collection: %s", + MessageHelper.collectionInfoString( collectionDescriptor, collection, key, session ) + ); + } + if ( deletes.hasNext() ) { + int passes = 0; + final JdbcParameterBindingsImpl jdbcParameterBindings = new JdbcParameterBindingsImpl(); + final BasicExecutionContext executionContext = new BasicExecutionContext( session, jdbcParameterBindings ); + + while ( deletes.hasNext() ) { + Object entry = deletes.next(); + if ( collectionDescriptor.getIdDescriptor() != null ) { + bindCollectionId( entry, passes, collection, jdbcParameterBindings, session ); + } + else { + bindCollectionKey( key, jdbcParameterBindings, session ); + if ( deleteByIndex ) { + bindCollectionIndex( entry, jdbcParameterBindings, session ); + } + else { + bindCollectionElement( entry, collection, jdbcParameterBindings, session ); + } + } + JdbcMutationExecutor.WITH_AFTER_STATEMENT_CALL.execute( removalOperation, executionContext ); + + passes++; + jdbcParameterBindings.clear(); + log.debugf( "Done deleting collection rows: %s deleted", passes ); + } + } + else { + log.debug( "No rows to delete" ); + } + } + + @SuppressWarnings("WeakerAccess") + protected void applyNavigable( + Navigable navigable, + TableReference dmlTableRef, + AtomicInteger parameterCount, + BiConsumer columnConsumer, + List assignments, + SessionFactoryImplementor sessionFactory) { + //noinspection RedundantCast + navigable.visitColumns( + (BiConsumer) (sqlExpressableType, column) -> { + + final ColumnReference columnReference = dmlTableRef.resolveColumnReference( column ); + + final PositionalParameter parameter = new PositionalParameter( + parameterCount.getAndIncrement(), + column.getExpressableType(), + Clause.INSERT, + sessionFactory.getTypeConfiguration() + ); + + final Assignment assignment = new Assignment( columnReference, parameter ); + assignments.add( assignment ); + + columnConsumer.accept( column, parameter ); + }, + Clause.UPDATE, + sessionFactory.getTypeConfiguration() + ); + } + + protected void bindCollectionKey( + Object key, + JdbcParameterBindingsImpl jdbcParameterBindings, + SharedSessionContractImplementor session) { + collectionDescriptor.getCollectionKeyDescriptor().dehydrate( + collectionDescriptor.getCollectionKeyDescriptor().unresolve( key, session ), + (jdbcValue, type, boundColumn) -> createBinding( + jdbcValue, + boundColumn, + type, + jdbcParameterBindings, + session + ), + Clause.DELETE, + session + ); + } + + protected void bindCollectionElement( + Object entry, + PersistentCollection collection, + JdbcParameterBindingsImpl jdbcParameterBindings, + SharedSessionContractImplementor session) { + collectionDescriptor.getElementDescriptor().dehydrate( + collectionDescriptor.getElementDescriptor().unresolve( + collection.getElement( entry, collectionDescriptor ), + session + ), + (jdbcValue, type, boundColumn) -> createBinding( + jdbcValue, + boundColumn, + type, + jdbcParameterBindings, + session + ), + Clause.DELETE, + session + ); + } + + protected void bindCollectionIndex( + Object index, + JdbcParameterBindingsImpl jdbcParameterBindings, + SharedSessionContractImplementor session) { + // todo (6.0) : probably not the correct `assumedIndex` + if ( collectionDescriptor.getIndexDescriptor() != null ) { + if ( collectionDescriptor.getIndexDescriptor().getBaseIndex() != 0 ) { + index = (Integer) index + collectionDescriptor.getIndexDescriptor().getBaseIndex(); + } + collectionDescriptor.getIndexDescriptor().dehydrate( + collectionDescriptor.getIndexDescriptor().unresolve( index, session ), + (jdbcValue, type, boundColumn) -> createBinding( + jdbcValue, + boundColumn, + type, + jdbcParameterBindings, + session + ), + Clause.DELETE, + session + ); + } + } + + protected void bindCollectionId( + Object entry, + int assumedIdentifier, + PersistentCollection collection, + JdbcParameterBindingsImpl jdbcParameterBindings, + SharedSessionContractImplementor session) { + // todo (6.0) : probably not the correct `assumedIdentifier` + final Object identifier = collection.getIdentifier( entry, assumedIdentifier, collectionDescriptor ); + + collectionDescriptor.getIdDescriptor().dehydrate( + collectionDescriptor.getIdDescriptor().unresolve( identifier, session ), + (jdbcValue, type, boundColumn) -> createBinding( + jdbcValue, + boundColumn, + type, + jdbcParameterBindings, + session + ), + Clause.DELETE, + session + ); + } + + protected void createBinding( + Object jdbcValue, + Column boundColumn, + SqlExpressableType type, + JdbcParameterBindingsImpl jdbcParameterBindings, + SharedSessionContractImplementor session) { + final JdbcParameter jdbcParameter = resolveJdbcParameter( boundColumn ); + + jdbcParameterBindings.addBinding( + jdbcParameter, + new LiteralParameter( + jdbcValue, + type, + Clause.DELETE, + session.getFactory().getTypeConfiguration() + ) + ); + } + + private JdbcParameter resolveJdbcParameter(Column boundColumn) { + final JdbcParameter jdbcParameter = jdbcParameterMap.get( boundColumn ); + if ( jdbcParameter == null ) { + throw new IllegalStateException( "JdbcParameter not found for Column [" + boundColumn + "]" ); + } + return jdbcParameter; } }