org.hibernate.query.sqm.mutation.internal.idtable.RestrictedDeleteExecutionDelegate Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* 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.query.sqm.mutation.internal.idtable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
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.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcDelete;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
private static final Logger log = Logger.getLogger( RestrictedDeleteExecutionDelegate.class );
private final EntityMappingType entityDescriptor;
private final IdTable idTable;
private final SqmDeleteStatement sqmDelete;
private final DomainParameterXref domainParameterXref;
private final SessionFactoryImplementor sessionFactory;
private final BeforeUseAction beforeUseAction;
private final AfterUseAction afterUseAction;
private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final Supplier idTableExporterAccess;
private final Function sessionUidAccess;
private final MultiTableSqmMutationConverter converter;
@SuppressWarnings("WeakerAccess")
public RestrictedDeleteExecutionDelegate(
EntityMappingType entityDescriptor,
IdTable idTable,
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling,
Supplier idTableExporterAccess,
Function sessionUidAccess,
QueryOptions queryOptions,
QueryParameterBindings queryParameterBindings,
SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
this.idTable = idTable;
this.sqmDelete = sqmDelete;
this.domainParameterXref = domainParameterXref;
this.beforeUseAction = beforeUseAction;
this.afterUseAction = afterUseAction;
this.ddlTransactionHandling = ddlTransactionHandling;
this.idTableExporterAccess = idTableExporterAccess;
this.sessionUidAccess = sessionUidAccess;
this.sessionFactory = sessionFactory;
converter = new MultiTableSqmMutationConverter(
entityDescriptor,
domainParameterXref,
queryOptions,
queryParameterBindings,
sessionFactory
);
}
@Override
public int execute(ExecutionContext executionContext) {
final EntityPersister entityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( sqmDelete.getTarget().getEntityName() );
final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName();
final TableGroup deletingTableGroup = converter.getMutatingTableGroup();
final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( hierarchyRootTableName );
assert hierarchyRootTableReference != null;
final Map> parameterResolutions;
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
parameterResolutions = Collections.emptyMap();
}
else {
parameterResolutions = new IdentityHashMap<>();
}
// Use the converter to interpret the where-clause. We do this for 2 reasons:
// 1) the resolved Predicate is ultimately the base for applying restriction to the deletes
// 2) we also inspect each ColumnReference that is part of the where-clause to see which
// table it comes from. if all of the referenced columns (if any at all) are from the root table
// we can perform all of the deletes without using an id-table
final AtomicBoolean needsIdTableWrapper = new AtomicBoolean( false );
final Predicate predicate = converter.visitWhereClause(
sqmDelete.getWhereClause(),
columnReference -> {
if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
needsIdTableWrapper.set( true );
}
},
parameterResolutions::put
);
boolean needsIdTable = needsIdTableWrapper.get();
if ( needsIdTable ) {
return executeWithIdTable(
predicate,
deletingTableGroup,
parameterResolutions,
executionContext
);
}
else {
return executeWithoutIdTable(
predicate,
deletingTableGroup,
parameterResolutions,
converter.getSqlExpressionResolver(),
executionContext
);
}
}
private int executeWithoutIdTable(
Predicate suppliedPredicate,
TableGroup tableGroup,
Map> restrictionSqmParameterResolutions,
SqlExpressionResolver sqlExpressionResolver,
ExecutionContext executionContext) {
final EntityPersister rootEntityPersister;
final String rootEntityName = entityDescriptor.getEntityPersister().getRootEntityName();
if ( rootEntityName.equals( entityDescriptor.getEntityName() ) ) {
rootEntityPersister = entityDescriptor.getEntityPersister();
}
else {
rootEntityPersister = sessionFactory.getDomainModel().findEntityDescriptor( rootEntityName );
}
final AtomicInteger rows = new AtomicInteger();
final String rootTableName = ( (Joinable) rootEntityPersister ).getTableName();
final TableReference rootTableReference = tableGroup.resolveTableReference( rootTableName );
final QuerySpec matchingIdSubQuerySpec = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec(
tableGroup.getNavigablePath(),
rootTableReference,
suppliedPredicate,
rootEntityPersister,
sqlExpressionResolver,
sessionFactory
);
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
SqmUtil.generateJdbcParamsXref(
domainParameterXref,
() -> restrictionSqmParameterResolutions
),
sessionFactory.getDomainModel(),
navigablePath -> tableGroup,
executionContext.getSession()
);
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnVisitationSupplier) -> {
if ( tableExpression.equals( rootTableName ) ) {
rows.set(
deleteFromRootTableWithoutIdTable(
rootTableReference,
suppliedPredicate,
jdbcParameterBindings,
executionContext
)
);
}
else {
deleteFromNonRootTableWithoutIdTable(
tableGroup.resolveTableReference( tableExpression ),
tableKeyColumnVisitationSupplier,
sqlExpressionResolver,
tableGroup,
matchingIdSubQuerySpec,
jdbcParameterBindings,
executionContext
);
}
}
);
return rows.get();
}
private int deleteFromRootTableWithoutIdTable(
TableReference rootTableReference,
Predicate predicate,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
return executeSqlDelete(
new DeleteStatement( rootTableReference, predicate ),
jdbcParameterBindings,
executionContext
);
}
private void deleteFromNonRootTableWithoutIdTable(
TableReference targetTableReference,
Supplier> tableKeyColumnVisitationSupplier,
SqlExpressionResolver sqlExpressionResolver,
TableGroup rootTableGroup,
QuerySpec matchingIdSubQuerySpec,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
assert targetTableReference != null;
log.trace( "deleteFromNonRootTable - " + targetTableReference.getTableExpression() );
/*
* delete from sub_table
* where sub_id in (
* select root_id from root_table
* where {predicate}
* )
*/
/*
* Create the `sub_id` reference as the LHS of the in-subquery predicate
*/
final List deletingTableColumnRefs = new ArrayList<>();
tableKeyColumnVisitationSupplier.get().accept(
(columnExpression, containingTableExpression, jdbcMapping) -> {
assert targetTableReference.getTableExpression().equals( containingTableExpression );
final Expression expression = sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( targetTableReference, columnExpression ),
sqlAstProcessingState -> new ColumnReference(
rootTableGroup.getPrimaryTableReference(),
columnExpression,
jdbcMapping,
sessionFactory
)
);
deletingTableColumnRefs.add( (ColumnReference) expression );
}
);
final Expression deletingTableColumnRefsExpression;
if ( deletingTableColumnRefs.size() == 1 ) {
deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 );
}
else {
deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() );
}
final InSubQueryPredicate idMatchPredicate = new InSubQueryPredicate(
deletingTableColumnRefsExpression,
matchingIdSubQuerySpec,
false
);
final DeleteStatement sqlAstDelete = new DeleteStatement( targetTableReference, idMatchPredicate );
final int rows = executeSqlDelete(
sqlAstDelete,
jdbcParameterBindings,
executionContext
);
log.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows );
}
private static int executeSqlDelete(
DeleteStatement sqlAst,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final JdbcServices jdbcServices = factory.getJdbcServices();
final SqlAstDeleteTranslator sqlAstTranslator = jdbcServices.getJdbcEnvironment()
.getSqlAstTranslatorFactory()
.buildDeleteTranslator( factory );
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( sqlAst );
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcDelete,
jdbcParameterBindings,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {},
executionContext
);
}
private int executeWithIdTable(
Predicate predicate,
TableGroup deletingTableGroup,
Map> restrictionSqmParameterResolutions,
ExecutionContext executionContext) {
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
SqmUtil.generateJdbcParamsXref(
domainParameterXref,
() -> restrictionSqmParameterResolutions
),
sessionFactory.getDomainModel(),
navigablePath -> deletingTableGroup,
executionContext.getSession()
);
ExecuteWithIdTableHelper.performBeforeIdTableUseActions(
beforeUseAction,
idTable,
idTableExporterAccess,
ddlTransactionHandling,
executionContext
);
try {
return executeUsingIdTable( predicate, executionContext, jdbcParameterBindings );
}
finally {
ExecuteWithIdTableHelper.performAfterIdTableUseActions(
afterUseAction,
idTable,
idTableExporterAccess,
ddlTransactionHandling,
sessionUidAccess,
executionContext
);
}
}
private int executeUsingIdTable(
Predicate predicate,
ExecutionContext executionContext,
JdbcParameterBindings jdbcParameterBindings) {
final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable(
converter,
predicate,
idTable,
sessionUidAccess,
jdbcParameterBindings,
executionContext
);
final QuerySpec idTableSubQuery = ExecuteWithIdTableHelper.createIdTableSelectQuerySpec(
idTable,
sessionUidAccess,
entityDescriptor,
executionContext
);
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable(
tableExpression,
tableKeyColumnVisitationSupplier,
idTableSubQuery,
executionContext
)
);
return rows;
}
private void deleteFromTableUsingIdTable(
String tableExpression,
Supplier> tableKeyColumnVisitationSupplier,
QuerySpec idTableSubQuery,
ExecutionContext executionContext) {
log.trace( "deleteFromTableUsingIdTable - " + tableExpression );
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor );
tableKeyColumnVisitationSupplier.get().accept(
(columnExpression, containingTableExpression, jdbcMapping) -> {
assert containingTableExpression.equals( tableExpression );
keyColumnCollector.apply(
new ColumnReference(
(String) null,
columnExpression,
jdbcMapping,
factory
)
);
}
);
final InSubQueryPredicate predicate = new InSubQueryPredicate(
keyColumnCollector.buildKeyExpression(),
idTableSubQuery,
false
);
executeSqlDelete(
new DeleteStatement(
new TableReference( tableExpression, null, true, factory ),
predicate
),
JdbcParameterBindings.NO_BINDINGS,
executionContext
);
}
}