All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.query.sqm.mutation.internal.idtable.RestrictedDeleteExecutionDelegate Maven / Gradle / Ivy

There is a newer version: 7.0.0.Beta1
Show newest version
/*
 * 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
		);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy