HHH-13715 - working support for "multi-table" HQL/Criteria UPDATE and DELETE queries
complete support for "local temp table"-based strategy
This commit is contained in:
parent
0dae701c93
commit
b04599cbe5
|
@ -87,15 +87,7 @@ public interface JdbcServices extends Service {
|
|||
return JdbcSelectExecutorStandardImpl.INSTANCE;
|
||||
}
|
||||
|
||||
default JdbcMutationExecutor getJdbcDeleteExecutor() {
|
||||
return StandardJdbcMutationExecutor.INSTANCE;
|
||||
}
|
||||
|
||||
default JdbcMutationExecutor getJdbcUpdateExecutor() {
|
||||
return StandardJdbcMutationExecutor.INSTANCE;
|
||||
}
|
||||
|
||||
default JdbcMutationExecutor getJdbcInsertExecutor() {
|
||||
default JdbcMutationExecutor getJdbcMutationExecutor() {
|
||||
return StandardJdbcMutationExecutor.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,7 +247,13 @@ public interface EntityMappingType extends ManagedMappingType, Loadable {
|
|||
TableReferenceCollector collector,
|
||||
SqlExpressionResolver sqlExpressionResolver,
|
||||
SqlAstCreationContext creationContext) {
|
||||
getEntityPersister().applyTableReferences( sqlAliasBase, baseJoinType, collector, sqlExpressionResolver, creationContext );
|
||||
getEntityPersister().applyTableReferences(
|
||||
sqlAliasBase,
|
||||
baseJoinType,
|
||||
collector,
|
||||
sqlExpressionResolver,
|
||||
creationContext
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -85,7 +85,6 @@ import org.hibernate.persister.walking.spi.CompositeCollectionElementDefinition;
|
|||
import org.hibernate.persister.walking.spi.CompositionDefinition;
|
||||
import org.hibernate.persister.walking.spi.EntityDefinition;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.Alias;
|
||||
import org.hibernate.sql.SelectFragment;
|
||||
import org.hibernate.sql.SimpleSelect;
|
||||
|
@ -93,6 +92,7 @@ import org.hibernate.sql.Template;
|
|||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableReferenceCollector;
|
||||
import org.hibernate.type.AnyType;
|
||||
|
@ -2397,7 +2397,12 @@ public abstract class AbstractCollectionPersister
|
|||
else {
|
||||
// we do have a "collection table" - apply it first
|
||||
collector.applySecondaryTableReferences(
|
||||
new TableReference( qualifiedTableName, sqlAliasBase.generateNewAlias(), false, getFactory() ),
|
||||
new TableReference(
|
||||
qualifiedTableName,
|
||||
sqlAliasBase.generateNewAlias(),
|
||||
false,
|
||||
getFactory()
|
||||
),
|
||||
baseJoinType,
|
||||
(lhs, rhs, joinType) -> {
|
||||
// create the join-predicate between the owner table and the collection table
|
||||
|
|
|
@ -28,10 +28,10 @@ import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
|
|||
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.walking.spi.CollectionDefinition;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.tree.from.TableReferenceCollector;
|
||||
import org.hibernate.sql.ast.tree.from.TableReferenceContributor;
|
||||
import org.hibernate.type.CollectionType;
|
||||
|
|
|
@ -1227,13 +1227,7 @@ public abstract class AbstractEntityPersister
|
|||
SqlExpressionResolver sqlExpressionResolver,
|
||||
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
|
||||
SqlAstCreationContext creationContext) {
|
||||
final SqlAliasBase sqlAliasBase;
|
||||
if ( aliasBaseGenerator == null ) {
|
||||
sqlAliasBase = null;
|
||||
}
|
||||
else {
|
||||
sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
|
||||
}
|
||||
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
|
||||
|
||||
final TableGroupBuilder builder = TableGroupBuilder.builder(
|
||||
navigablePath,
|
||||
|
|
|
@ -43,7 +43,6 @@ import org.hibernate.mapping.Value;
|
|||
import org.hibernate.persister.spi.PersisterCreationContext;
|
||||
import org.hibernate.query.ComparisonOperator;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.InFragment;
|
||||
import org.hibernate.sql.Insert;
|
||||
import org.hibernate.sql.SelectFragment;
|
||||
|
@ -51,6 +50,7 @@ import org.hibernate.sql.ast.Clause;
|
|||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
|
|
|
@ -90,7 +90,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan {
|
|||
executionContext.getSession()
|
||||
);
|
||||
|
||||
return jdbcServices.getJdbcDeleteExecutor().execute(
|
||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcDelete,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
|
|
|
@ -90,7 +90,7 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan {
|
|||
executionContext.getSession()
|
||||
);
|
||||
|
||||
return jdbcServices.getJdbcUpdateExecutor().execute(
|
||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcUpdate,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
|
|
|
@ -102,6 +102,20 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
|
|||
private static final Logger LOGGER = QueryLogger.subLogger( "sqm.ast" );
|
||||
private static final boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();
|
||||
|
||||
public static void logTree(SqmQuerySpec sqmQuerySpec, String header) {
|
||||
if ( ! DEBUG_ENABLED ) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SqmTreePrinter treePrinter = new SqmTreePrinter();
|
||||
|
||||
treePrinter.visitQuerySpec( sqmQuerySpec );
|
||||
|
||||
final String title = header != null ? header : "SqmQuerySpec Tree";
|
||||
|
||||
LOGGER.debugf( "%s :\n%s", title, treePrinter.buffer.toString() );
|
||||
}
|
||||
|
||||
public static void logTree(SqmStatement sqmStatement) {
|
||||
if ( ! DEBUG_ENABLED ) {
|
||||
return;
|
||||
|
@ -122,7 +136,7 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
|
|||
printer.visitInsertSelectStatement( (SqmInsertSelectStatement) sqmStatement );
|
||||
}
|
||||
|
||||
LOGGER.debugf( "Semantic Query (SQM) Tree :\n%s", printer.buffer.toString() );
|
||||
LOGGER.debugf( "SqmStatement Tree :\n%s", printer.buffer.toString() );
|
||||
}
|
||||
|
||||
private final StringBuffer buffer = new StringBuffer();
|
||||
|
|
|
@ -6,28 +6,22 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.mutation.internal;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.internal.util.collections.StandardStack;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.query.hql.spi.SqmCreationOptions;
|
||||
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
|
||||
import org.hibernate.query.hql.spi.SqmCreationState;
|
||||
import org.hibernate.query.sqm.SqmQuerySource;
|
||||
import org.hibernate.query.sqm.internal.SqmQuerySpecCreationProcessingStateStandardImpl;
|
||||
import org.hibernate.query.sqm.mutation.internal.idtable.IdTableSessionUidColumn;
|
||||
import org.hibernate.query.sqm.spi.SqmCreationContext;
|
||||
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
import org.hibernate.query.sqm.tree.from.SqmFromClause;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.type.StringType;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
|
@ -41,41 +35,30 @@ import org.jboss.logging.Logger;
|
|||
public class SqmIdSelectGenerator {
|
||||
private static final Logger log = Logger.getLogger( SqmIdSelectGenerator.class );
|
||||
|
||||
/**
|
||||
* @asciidoc
|
||||
*
|
||||
* Generates a query-spec for selecting all ids matching the restriction defined as part
|
||||
* of the user's update/delete query. This query-spec is generally used:
|
||||
*
|
||||
* * to select all the matching ids via JDBC - see {@link SqmMutationStrategyHelper#selectMatchingIds}
|
||||
* * as a sub-query restriction to insert rows into an "id table"
|
||||
*/
|
||||
public static SqmQuerySpec generateSqmEntityIdSelect(
|
||||
SqmDeleteOrUpdateStatement sqmStatement,
|
||||
ExecutionContext executionContext,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
final SqmIdSelectGenerator generator = new SqmIdSelectGenerator( sqmStatement, executionContext, sessionFactory );
|
||||
return generator.process();
|
||||
}
|
||||
SqmCreationContext sqmCreationContext) {
|
||||
final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel();
|
||||
|
||||
private final SqmDeleteOrUpdateStatement sourceSqmStatement;
|
||||
private final ExecutionContext executionContext;
|
||||
private final SqmCreationContext creationContext;
|
||||
private final EntityDomainType entityType;
|
||||
|
||||
public SqmIdSelectGenerator(
|
||||
SqmDeleteOrUpdateStatement sourceSqmStatement,
|
||||
ExecutionContext executionContext,
|
||||
SqmCreationContext creationContext) {
|
||||
this.sourceSqmStatement = sourceSqmStatement;
|
||||
this.executionContext = executionContext;
|
||||
this.creationContext = creationContext;
|
||||
|
||||
final String targetEntityName = sourceSqmStatement.getTarget().getEntityName();
|
||||
this.entityType = creationContext.getJpaMetamodel().entity( targetEntityName );
|
||||
}
|
||||
|
||||
private SqmQuerySpec process() {
|
||||
final SqmQuerySpec sqmQuerySpec = new SqmQuerySpec( creationContext.getNodeBuilder() );
|
||||
log.tracef( "Starting generation of entity-id SQM selection - %s", entityDomainType.getHibernateEntityName() );
|
||||
|
||||
final SqmQuerySpec sqmQuerySpec = new SqmQuerySpec( sqmCreationContext.getNodeBuilder() );
|
||||
|
||||
final Stack<SqmCreationProcessingState> processingStateStack = new StandardStack<>();
|
||||
|
||||
final SqmCreationState creationState = new SqmCreationState() {
|
||||
@Override
|
||||
public SqmCreationContext getCreationContext() {
|
||||
return creationContext;
|
||||
return sqmCreationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -89,8 +72,9 @@ public class SqmIdSelectGenerator {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
// temporary - used just for creating processingState
|
||||
final SqmSelectStatement sqmSelectStatement = new SqmSelectStatement( creationContext.getNodeBuilder() );
|
||||
final SqmSelectStatement sqmSelectStatement = new SqmSelectStatement( sqmCreationContext.getNodeBuilder() );
|
||||
//noinspection unchecked
|
||||
sqmSelectStatement.setQuerySpec( sqmQuerySpec );
|
||||
|
||||
|
@ -105,27 +89,31 @@ public class SqmIdSelectGenerator {
|
|||
final SqmFromClause sqmFromClause = new SqmFromClause();
|
||||
sqmQuerySpec.setFromClause( sqmFromClause );
|
||||
|
||||
|
||||
//noinspection unchecked
|
||||
final SqmRoot<?> sqmRoot = new SqmRoot( entityType, null, sourceSqmStatement.nodeBuilder() );
|
||||
// final SqmRoot<?> sqmRoot = new SqmRoot( entityDomainType, null, sqmCreationContext.getNodeBuilder() );
|
||||
final SqmRoot<?> sqmRoot = sqmStatement.getTarget();
|
||||
|
||||
log.debugf( "Using SqmRoot [%s] as root for entity id-select", sqmRoot );
|
||||
sqmFromClause.addRoot( sqmRoot );
|
||||
|
||||
final SqmSelectClause sqmSelectClause = new SqmSelectClause( true, creationContext.getNodeBuilder() );
|
||||
final SqmSelectClause sqmSelectClause = new SqmSelectClause( true, sqmCreationContext.getNodeBuilder() );
|
||||
sqmQuerySpec.setSelectClause( sqmSelectClause );
|
||||
applySelections( sqmQuerySpec, sqmRoot, processingState );
|
||||
|
||||
if ( sourceSqmStatement.getWhereClause() != null ) {
|
||||
sqmQuerySpec.applyPredicate( sourceSqmStatement.getWhereClause().getPredicate() );
|
||||
if ( sqmStatement.getWhereClause() != null ) {
|
||||
sqmQuerySpec.applyPredicate( sqmStatement.getWhereClause().getPredicate() );
|
||||
}
|
||||
|
||||
return sqmQuerySpec;
|
||||
}
|
||||
|
||||
private void applySelections(
|
||||
private static void applySelections(
|
||||
SqmQuerySpec sqmQuerySpec,
|
||||
SqmRoot<?> sqmRoot,
|
||||
SqmCreationProcessingState processingState) {
|
||||
//noinspection unchecked
|
||||
final SqmPath idPath = entityType.getIdentifierDescriptor().createSqmPath( sqmRoot, processingState.getCreationState() );
|
||||
final SqmPath idPath = sqmRoot.getModel().getIdentifierDescriptor().createSqmPath( sqmRoot, processingState.getCreationState() );
|
||||
|
||||
//noinspection unchecked
|
||||
sqmQuerySpec.getSelectClause().add(
|
||||
|
|
|
@ -245,7 +245,6 @@ public class SqmMutationStrategyHelper {
|
|||
|
||||
final SqmQuerySpec sqmIdSelectQuerySpec = SqmIdSelectGenerator.generateSqmEntityIdSelect(
|
||||
sqmDeleteStatement,
|
||||
executionContext,
|
||||
factory
|
||||
);
|
||||
|
||||
|
|
|
@ -71,7 +71,12 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
|
||||
@Override
|
||||
public int execute(ExecutionContext executionContext) {
|
||||
final List<Object> ids = selectMatchingIds( executionContext );
|
||||
final List<Object> ids = SqmMutationStrategyHelper.selectMatchingIds(
|
||||
getSqmDeleteOrUpdateStatement(),
|
||||
getDomainParameterXref(),
|
||||
executionContext
|
||||
);
|
||||
|
||||
if ( ids == null || ids.isEmpty() ) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -133,14 +138,6 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
return ids.size();
|
||||
}
|
||||
|
||||
private List<Object> selectMatchingIds(ExecutionContext executionContext) {
|
||||
return SqmMutationStrategyHelper.selectMatchingIds(
|
||||
getSqmDeleteOrUpdateStatement(),
|
||||
getDomainParameterXref(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
|
||||
protected void executeDelete(
|
||||
QuerySpec cteDefinition,
|
||||
String targetTable,
|
||||
|
@ -158,7 +155,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
executionContext
|
||||
);
|
||||
|
||||
final SessionFactoryImplementor sessionFactory = getCreationContext().getSessionFactory();
|
||||
final SessionFactoryImplementor sessionFactory = getSessionFactory();
|
||||
|
||||
final JdbcDelete jdbcDelete = sqlAstTranslatorFactory.buildDeleteTranslator( sessionFactory )
|
||||
.translate( cteStatement );
|
||||
|
@ -168,7 +165,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
.getJdbcCoordinator()
|
||||
.getLogicalConnection();
|
||||
|
||||
sessionFactory.getJdbcServices().getJdbcDeleteExecutor().execute(
|
||||
sessionFactory.getJdbcServices().getJdbcMutationExecutor().execute(
|
||||
jdbcDelete,
|
||||
jdbcParameterBindings,
|
||||
sql -> {
|
||||
|
|
|
@ -16,15 +16,13 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
|||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.query.ComparisonOperator;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||
import org.hibernate.query.sqm.internal.SqmTreePrinter;
|
||||
import org.hibernate.query.sqm.mutation.internal.SqmIdSelectGenerator;
|
||||
import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler;
|
||||
import org.hibernate.query.sqm.mutation.spi.Handler;
|
||||
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
|
||||
import org.hibernate.query.sqm.sql.SqmQuerySpecTranslation;
|
||||
import org.hibernate.query.sqm.sql.SqmSelectTranslator;
|
||||
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
|
||||
|
@ -32,6 +30,7 @@ import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
||||
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlAstInsertSelectTranslator;
|
||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||
|
@ -42,6 +41,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
|
|||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
|
||||
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
|
||||
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.JdbcInsert;
|
||||
|
@ -52,162 +52,39 @@ import org.hibernate.type.UUIDCharType;
|
|||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Support for {@link Handler} implementations
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class AbstractTableBasedHandler extends AbstractMutationHandler {
|
||||
private static final Logger log = Logger.getLogger( AbstractTableBasedHandler.class );
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final class ExecuteWithIdTableHelper {
|
||||
private static final Logger log = Logger.getLogger( ExecuteWithIdTableHelper.class );
|
||||
public static final boolean debugging = log.isDebugEnabled();
|
||||
|
||||
private final IdTable idTable;
|
||||
private final TempTableDdlTransactionHandling ddlTransactionHandling;
|
||||
private final BeforeUseAction beforeUseAction;
|
||||
private final AfterUseAction afterUseAction;
|
||||
private ExecuteWithIdTableHelper() {
|
||||
}
|
||||
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
|
||||
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
|
||||
|
||||
private final Supplier<IdTableExporter> exporterSupplier;
|
||||
|
||||
|
||||
public AbstractTableBasedHandler(
|
||||
SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement,
|
||||
public static int saveMatchingIdsIntoIdTable(
|
||||
SqmUpdateStatement sqmMutation,
|
||||
MultiTableSqmMutationConverter sqmConverter,
|
||||
TableGroup mutatingTableGroup,
|
||||
Predicate suppliedPredicate,
|
||||
IdTable idTable,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
DomainParameterXref domainParameterXref,
|
||||
BeforeUseAction beforeUseAction,
|
||||
AfterUseAction afterUseAction,
|
||||
Function<SharedSessionContractImplementor,String> sessionUidAccess,
|
||||
Supplier<IdTableExporter> exporterSupplier,
|
||||
HandlerCreationContext creationContext) {
|
||||
super( sqmDeleteOrUpdateStatement, creationContext );
|
||||
this.idTable = idTable;
|
||||
this.ddlTransactionHandling = ddlTransactionHandling;
|
||||
this.beforeUseAction = beforeUseAction;
|
||||
this.afterUseAction = afterUseAction;
|
||||
|
||||
this.domainParameterXref = domainParameterXref;
|
||||
|
||||
this.sessionUidAccess = sessionUidAccess;
|
||||
this.exporterSupplier = exporterSupplier;
|
||||
}
|
||||
|
||||
public IdTable getIdTable() {
|
||||
return idTable;
|
||||
}
|
||||
|
||||
public DomainParameterXref getDomainParameterXref() {
|
||||
return domainParameterXref;
|
||||
}
|
||||
|
||||
public BeforeUseAction getBeforeUseAction() {
|
||||
return beforeUseAction;
|
||||
}
|
||||
|
||||
public AfterUseAction getAfterUseAction() {
|
||||
return afterUseAction;
|
||||
}
|
||||
|
||||
public Function<SharedSessionContractImplementor, String> getSessionUidAccess() {
|
||||
return sessionUidAccess;
|
||||
}
|
||||
|
||||
public Supplier<IdTableExporter> getExporterSupplier() {
|
||||
return exporterSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(ExecutionContext executionContext) {
|
||||
|
||||
// In general:
|
||||
// 1) prepare for use - this is completely a subclass hook
|
||||
// 2) perform execution
|
||||
// 3) release after use - again, completely a subclass hook
|
||||
|
||||
beforeExecution( executionContext );
|
||||
|
||||
try {
|
||||
return performExecution( executionContext );
|
||||
}
|
||||
finally {
|
||||
afterExecution( executionContext );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow subclasses a chance to perform any preliminary work they need
|
||||
* to perform prior to execution
|
||||
*/
|
||||
protected void beforeExecution(ExecutionContext executionContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow subclasses a chance to perform any clean-up work they need
|
||||
* to perform after execution
|
||||
*/
|
||||
protected void afterExecution(ExecutionContext executionContext) {
|
||||
}
|
||||
|
||||
protected int performExecution(ExecutionContext executionContext) {
|
||||
performBeforeUseActions( executionContext );
|
||||
|
||||
try {
|
||||
// 1) save the matching ids into the id table
|
||||
final int affectedRowCount = saveMatchingIdsIntoIdTable( executionContext );
|
||||
log.debugf( "insert for matching ids resulted in %s rows", affectedRowCount );
|
||||
|
||||
// 2) perform the actual individual update or deletes, using
|
||||
// inclusion in the id-table as restriction
|
||||
performMutations( executionContext );
|
||||
|
||||
return affectedRowCount;
|
||||
}
|
||||
finally {
|
||||
performAfterUseActions( executionContext );
|
||||
}
|
||||
}
|
||||
|
||||
private void performBeforeUseActions(ExecutionContext executionContext) {
|
||||
if ( getBeforeUseAction() == BeforeUseAction.CREATE ) {
|
||||
IdTableHelper.createIdTable( idTable, getExporterSupplier().get(), ddlTransactionHandling, executionContext.getSession() );
|
||||
}
|
||||
}
|
||||
|
||||
private void performAfterUseActions(ExecutionContext executionContext) {
|
||||
if ( getAfterUseAction() == AfterUseAction.CLEAN ) {
|
||||
IdTableHelper.cleanIdTableRows(
|
||||
idTable,
|
||||
getExporterSupplier().get(),
|
||||
sessionUidAccess,
|
||||
executionContext.getSession()
|
||||
);
|
||||
}
|
||||
else if ( getAfterUseAction() == AfterUseAction.DROP ) {
|
||||
IdTableHelper.dropIdTable(
|
||||
idTable,
|
||||
getExporterSupplier().get(),
|
||||
ddlTransactionHandling,
|
||||
executionContext.getSession()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected int saveMatchingIdsIntoIdTable(ExecutionContext executionContext) {
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext) {
|
||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||
|
||||
final SqmQuerySpec sqmIdSelect = SqmIdSelectGenerator.generateSqmEntityIdSelect(
|
||||
getSqmDeleteOrUpdateStatement(),
|
||||
executionContext,
|
||||
sqmMutation,
|
||||
factory
|
||||
);
|
||||
|
||||
if ( getIdTable().getSessionUidColumn() != null ) {
|
||||
if ( idTable.getSessionUidColumn() != null ) {
|
||||
//noinspection unchecked
|
||||
sqmIdSelect.getSelectClause().add(
|
||||
new SqmSelection(
|
||||
new SqmLiteral(
|
||||
executionContext.getSession().getSessionIdentifier().toString(),
|
||||
sessionUidAccess.apply( executionContext.getSession() ),
|
||||
UUIDCharType.INSTANCE,
|
||||
executionContext.getSession().getFactory().getNodeBuilder()
|
||||
),
|
||||
|
@ -217,23 +94,20 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
);
|
||||
}
|
||||
|
||||
final SqmTranslatorFactory sqmTranslatorFactory = factory.getQueryEngine().getSqmTranslatorFactory();
|
||||
final SqmSelectTranslator sqmTranslator = sqmTranslatorFactory.createSelectTranslator(
|
||||
QueryOptions.NONE,
|
||||
domainParameterXref,
|
||||
executionContext.getQueryParameterBindings(),
|
||||
executionContext.getSession().getLoadQueryInfluencers(),
|
||||
factory
|
||||
);
|
||||
|
||||
final SqmQuerySpecTranslation sqmIdSelectTranslation = sqmTranslator.translate( sqmIdSelect );
|
||||
SqmTreePrinter.logTree( sqmIdSelect, "Entity-identifier Selection SqmQuerySpec" );
|
||||
|
||||
final InsertSelectStatement insertSelectStatement = new InsertSelectStatement();
|
||||
|
||||
final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory );
|
||||
|
||||
insertSelectStatement.setTargetTable( idTableReference );
|
||||
insertSelectStatement.setSourceSelectStatement( sqmIdSelectTranslation.getSqlAst() );
|
||||
|
||||
final QuerySpec matchingIdRestrictionQuerySpec = generateTempTableInsertValuesQuerySpec(
|
||||
sqmConverter,
|
||||
mutatingTableGroup,
|
||||
suppliedPredicate,
|
||||
sqmIdSelect
|
||||
);
|
||||
insertSelectStatement.setSourceSelectStatement( matchingIdRestrictionQuerySpec );
|
||||
|
||||
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
|
||||
final IdTableColumn column = idTable.getIdTableColumns().get( i );
|
||||
|
@ -248,34 +122,117 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
final SqlAstInsertSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory );
|
||||
final JdbcInsert jdbcInsert = sqlAstTranslator.translate( insertSelectStatement );
|
||||
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||
executionContext.getQueryParameterBindings(),
|
||||
domainParameterXref,
|
||||
SqmUtil.generateJdbcParamsXref(
|
||||
domainParameterXref,
|
||||
sqmIdSelectTranslation::getJdbcParamsBySqmParam
|
||||
),
|
||||
factory.getDomainModel(),
|
||||
sqmTranslator.getFromClauseAccess()::findTableGroup,
|
||||
executionContext.getSession()
|
||||
);
|
||||
|
||||
return jdbcServices.getJdbcInsertExecutor().execute(
|
||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcInsert,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {
|
||||
},
|
||||
(integer, preparedStatement) -> {},
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
|
||||
private static QuerySpec generateTempTableInsertValuesQuerySpec(
|
||||
MultiTableSqmMutationConverter sqmConverter,
|
||||
TableGroup mutatingTableGroup, Predicate suppliedPredicate, SqmQuerySpec sqmIdSelect) {
|
||||
final QuerySpec matchingIdRestrictionQuerySpec = new QuerySpec( false, 1 );
|
||||
sqmConverter.visitSelectClause(
|
||||
sqmIdSelect.getSelectClause(),
|
||||
matchingIdRestrictionQuerySpec,
|
||||
columnReference -> {},
|
||||
(sqmParameter, jdbcParameters) -> {}
|
||||
);
|
||||
matchingIdRestrictionQuerySpec.getFromClause().addRoot( mutatingTableGroup );
|
||||
matchingIdRestrictionQuerySpec.applyPredicate( suppliedPredicate );
|
||||
return matchingIdRestrictionQuerySpec;
|
||||
}
|
||||
|
||||
public QuerySpec createIdTableSubQuery(ExecutionContext executionContext) {
|
||||
public static int saveMatchingIdsIntoIdTable(
|
||||
SqmDeleteOrUpdateStatement sqmMutation,
|
||||
Predicate predicate,
|
||||
IdTable idTable,
|
||||
Function<SharedSessionContractImplementor,String> sessionUidAccess,
|
||||
DomainParameterXref domainParameterXref,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext) {
|
||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||
|
||||
final SqmQuerySpec sqmIdSelect = SqmIdSelectGenerator.generateSqmEntityIdSelect(
|
||||
sqmMutation,
|
||||
factory
|
||||
);
|
||||
|
||||
if ( idTable.getSessionUidColumn() != null ) {
|
||||
//noinspection unchecked
|
||||
sqmIdSelect.getSelectClause().add(
|
||||
new SqmSelection(
|
||||
new SqmLiteral(
|
||||
sessionUidAccess.apply( executionContext.getSession() ),
|
||||
UUIDCharType.INSTANCE,
|
||||
executionContext.getSession().getFactory().getNodeBuilder()
|
||||
),
|
||||
null,
|
||||
executionContext.getSession().getFactory().getNodeBuilder()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
SqmTreePrinter.logTree( sqmIdSelect, "Entity-identifier Selection SqmQuerySpec" );
|
||||
|
||||
final SqmTranslatorFactory sqmTranslatorFactory = factory.getQueryEngine().getSqmTranslatorFactory();
|
||||
final SqmSelectTranslator sqmTranslator = sqmTranslatorFactory.createSelectTranslator(
|
||||
QueryOptions.NONE,
|
||||
domainParameterXref,
|
||||
executionContext.getQueryParameterBindings(),
|
||||
executionContext.getSession().getLoadQueryInfluencers(),
|
||||
factory
|
||||
);
|
||||
|
||||
final SqmQuerySpecTranslation sqmIdSelectTranslation = sqmTranslator.translate( sqmIdSelect );
|
||||
|
||||
|
||||
|
||||
final InsertSelectStatement insertSelectStatement = new InsertSelectStatement();
|
||||
|
||||
final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory );
|
||||
insertSelectStatement.setTargetTable( idTableReference );
|
||||
|
||||
final QuerySpec matchingIdRestrictionQuerySpec = sqmIdSelectTranslation.getSqlAst();
|
||||
insertSelectStatement.setSourceSelectStatement( matchingIdRestrictionQuerySpec );
|
||||
matchingIdRestrictionQuerySpec.applyPredicate( predicate );
|
||||
|
||||
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
|
||||
final IdTableColumn column = idTable.getIdTableColumns().get( i );
|
||||
insertSelectStatement.addTargetColumnReferences(
|
||||
new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory )
|
||||
);
|
||||
}
|
||||
|
||||
final JdbcServices jdbcServices = factory.getJdbcServices();
|
||||
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
|
||||
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
|
||||
final SqlAstInsertSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory );
|
||||
final JdbcInsert jdbcInsert = sqlAstTranslator.translate( insertSelectStatement );
|
||||
|
||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcInsert,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {},
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
|
||||
public static QuerySpec createIdTableSelectQuerySpec(
|
||||
IdTable idTable,
|
||||
Function<SharedSessionContractImplementor,String> sessionUidAccess,
|
||||
EntityMappingType entityDescriptor,
|
||||
ExecutionContext executionContext) {
|
||||
final QuerySpec querySpec = new QuerySpec( false );
|
||||
|
||||
final TableReference idTableReference = new TableReference(
|
||||
|
@ -286,7 +243,7 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
);
|
||||
final TableGroup idTableGroup = new StandardTableGroup(
|
||||
new NavigablePath( idTableReference.getTableExpression() ),
|
||||
getEntityDescriptor(),
|
||||
entityDescriptor,
|
||||
LockMode.NONE,
|
||||
idTableReference,
|
||||
Collections.emptyList(),
|
||||
|
@ -296,15 +253,16 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
|
||||
querySpec.getFromClause().addRoot( idTableGroup );
|
||||
|
||||
applySelections( querySpec, idTableReference, executionContext );
|
||||
applyRestrictions( querySpec, idTableReference, executionContext );
|
||||
applyIdTableSelections( querySpec, idTableReference, idTable, executionContext );
|
||||
applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext );
|
||||
|
||||
return querySpec;
|
||||
}
|
||||
|
||||
private void applySelections(
|
||||
private static void applyIdTableSelections(
|
||||
QuerySpec querySpec,
|
||||
TableReference tableReference,
|
||||
IdTable idTable,
|
||||
ExecutionContext executionContext) {
|
||||
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
|
||||
final IdTableColumn idTableColumn = idTable.getIdTableColumns().get( i );
|
||||
|
@ -326,9 +284,11 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
}
|
||||
}
|
||||
|
||||
private void applyRestrictions(
|
||||
private static void applyIdTableRestrictions(
|
||||
QuerySpec querySpec,
|
||||
TableReference idTableReference,
|
||||
IdTable idTable,
|
||||
Function<SharedSessionContractImplementor,String> sessionUidAccess,
|
||||
ExecutionContext executionContext) {
|
||||
if ( idTable.getSessionUidColumn() != null ) {
|
||||
querySpec.applyPredicate(
|
||||
|
@ -350,5 +310,44 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract void performMutations(ExecutionContext executionContext);
|
||||
public static void performBeforeIdTableUseActions(
|
||||
BeforeUseAction beforeUseAction,
|
||||
IdTable idTable,
|
||||
Supplier<IdTableExporter> idTableExporterAccess,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
ExecutionContext executionContext) {
|
||||
if ( beforeUseAction == BeforeUseAction.CREATE ) {
|
||||
IdTableHelper.createIdTable(
|
||||
idTable,
|
||||
idTableExporterAccess.get(),
|
||||
ddlTransactionHandling,
|
||||
executionContext.getSession()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void performAfterIdTableUseActions(
|
||||
AfterUseAction afterUseAction,
|
||||
IdTable idTable,
|
||||
Supplier<IdTableExporter> idTableExporterAccess,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
ExecutionContext executionContext) {
|
||||
if ( afterUseAction == AfterUseAction.CLEAN ) {
|
||||
IdTableHelper.cleanIdTableRows(
|
||||
idTable,
|
||||
idTableExporterAccess.get(),
|
||||
sessionUidAccess,
|
||||
executionContext.getSession()
|
||||
);
|
||||
}
|
||||
else if ( afterUseAction == AfterUseAction.DROP ) {
|
||||
IdTableHelper.dropIdTable(
|
||||
idTable,
|
||||
idTableExporterAccess.get(),
|
||||
ddlTransactionHandling,
|
||||
executionContext.getSession()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.Collections;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public final class ExecuteWithoutIdTableHelper {
|
||||
private ExecuteWithoutIdTableHelper() {
|
||||
}
|
||||
|
||||
public static QuerySpec createIdMatchingSubQuerySpec(
|
||||
NavigablePath navigablePath,
|
||||
TableReference rootTableReference,
|
||||
Predicate predicate,
|
||||
EntityPersister rootEntityPersister,
|
||||
SqlExpressionResolver sqlExpressionResolver,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
/*
|
||||
* `select root_id from root_table where {predicate}
|
||||
*/
|
||||
final QuerySpec matchingIdSelect = new QuerySpec( false, 1 );
|
||||
|
||||
final StandardTableGroup matchingIdSelectTableGroup = new StandardTableGroup(
|
||||
navigablePath,
|
||||
rootEntityPersister,
|
||||
LockMode.PESSIMISTIC_WRITE,
|
||||
rootTableReference,
|
||||
Collections.emptyList(),
|
||||
null,
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
matchingIdSelect.getFromClause().addRoot( matchingIdSelectTableGroup );
|
||||
|
||||
rootEntityPersister.getIdentifierMapping().visitColumns(
|
||||
(columnExpression, containingTableExpression, jdbcMapping) -> {
|
||||
final ColumnReference columnReference = (ColumnReference) sqlExpressionResolver.resolveSqlExpression(
|
||||
SqlExpressionResolver.createColumnReferenceKey( rootTableReference, columnExpression ),
|
||||
sqlAstProcessingState -> new ColumnReference(
|
||||
rootTableReference,
|
||||
columnExpression,
|
||||
jdbcMapping,
|
||||
sessionFactory
|
||||
)
|
||||
);
|
||||
final SqlSelection sqlSelection = new SqlSelectionImpl(
|
||||
// irrelevant
|
||||
0,
|
||||
0,
|
||||
columnReference,
|
||||
jdbcMapping
|
||||
);
|
||||
matchingIdSelect.getSelectClause().addSqlSelection( sqlSelection );
|
||||
}
|
||||
);
|
||||
|
||||
matchingIdSelect.applyPredicate( predicate );
|
||||
|
||||
return matchingIdSelect;
|
||||
}
|
||||
}
|
|
@ -9,8 +9,8 @@ package org.hibernate.query.sqm.mutation.internal.idtable;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.NotYetImplementedFor6Exception;
|
||||
import org.hibernate.boot.TempTableDdlTransactionHandling;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
|
||||
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
|
||||
|
@ -61,7 +61,16 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg
|
|||
SqmUpdateStatement sqmUpdateStatement,
|
||||
DomainParameterXref domainParameterXref,
|
||||
HandlerCreationContext creationContext) {
|
||||
throw new NotYetImplementedFor6Exception( getClass() );
|
||||
return new TableBasedUpdateHandler(
|
||||
sqmUpdateStatement,
|
||||
domainParameterXref, idTable,
|
||||
SharedSessionContractImplementor::getTenantIdentifier,
|
||||
idTableExporterAccess,
|
||||
BeforeUseAction.CREATE,
|
||||
afterUseAction,
|
||||
ddlTransactionHandling,
|
||||
creationContext
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -69,29 +78,15 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg
|
|||
SqmDeleteStatement sqmDeleteStatement,
|
||||
DomainParameterXref domainParameterXref,
|
||||
HandlerCreationContext creationContext) {
|
||||
if ( sqmDeleteStatement.getWhereClause() == null
|
||||
|| sqmDeleteStatement.getWhereClause().getPredicate() == null ) {
|
||||
// optimization - special handler not needing the temp table
|
||||
return new UnrestrictedTableBasedDeleteHandler(
|
||||
sqmDeleteStatement,
|
||||
idTable,
|
||||
ddlTransactionHandling,
|
||||
domainParameterXref,
|
||||
BeforeUseAction.CREATE,
|
||||
afterUseAction,
|
||||
sessionContractImplementor -> null,
|
||||
creationContext
|
||||
);
|
||||
}
|
||||
|
||||
return new TableBasedDeleteHandler(
|
||||
sqmDeleteStatement,
|
||||
domainParameterXref,
|
||||
idTable,
|
||||
SharedSessionContractImplementor::getTenantIdentifier,
|
||||
idTableExporterAccess,
|
||||
BeforeUseAction.CREATE,
|
||||
afterUseAction,
|
||||
ddlTransactionHandling,
|
||||
domainParameterXref,
|
||||
creationContext
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* 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.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.query.spi.QueryParameterBindings;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
|
||||
import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl;
|
||||
import org.hibernate.query.sqm.sql.internal.SqlAstQuerySpecProcessingStateImpl;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
|
||||
import org.hibernate.query.sqm.tree.update.SqmAssignment;
|
||||
import org.hibernate.query.sqm.tree.update.SqmSetClause;
|
||||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.update.Assignable;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameter;
|
||||
import org.hibernate.sql.results.spi.DomainResultCreationState;
|
||||
|
||||
/**
|
||||
* Specialized BaseSqmToSqlAstConverter implementation used during conversion
|
||||
* of an SQM mutation query tree representing into the various SQL AST trees
|
||||
* needed to perform that operation.
|
||||
*
|
||||
* @see #visitSetClause(SqmSetClause, Consumer, BiConsumer)
|
||||
* @see #visitWhereClause(SqmWhereClause, Consumer, BiConsumer)
|
||||
* @see #visitSelectClause(SqmSelectClause, QuerySpec, Consumer, BiConsumer)
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter implements DomainResultCreationState {
|
||||
private final EntityMappingType mutatingEntityDescriptor;
|
||||
private final TableGroup mutatingTableGroup;
|
||||
|
||||
private BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer;
|
||||
|
||||
public MultiTableSqmMutationConverter(
|
||||
EntityMappingType mutatingEntityDescriptor,
|
||||
DomainParameterXref domainParameterXref,
|
||||
QueryOptions queryOptions,
|
||||
QueryParameterBindings domainParameterBindings,
|
||||
SqlAstCreationContext creationContext) {
|
||||
super( creationContext, queryOptions, domainParameterXref, domainParameterBindings );
|
||||
this.mutatingEntityDescriptor = mutatingEntityDescriptor;
|
||||
|
||||
final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl(
|
||||
null,
|
||||
this,
|
||||
getCurrentClauseStack()::getCurrent
|
||||
);
|
||||
|
||||
getProcessingStateStack().push( rootProcessingState );
|
||||
|
||||
final NavigablePath navigablePath = new NavigablePath( mutatingEntityDescriptor.getEntityName() );
|
||||
this.mutatingTableGroup = mutatingEntityDescriptor.createRootTableGroup(
|
||||
navigablePath,
|
||||
null,
|
||||
JoinType.LEFT,
|
||||
LockMode.PESSIMISTIC_WRITE,
|
||||
getSqlAliasBaseGenerator(),
|
||||
getSqlExpressionResolver(),
|
||||
() -> predicate -> {
|
||||
},
|
||||
creationContext.getSessionFactory()
|
||||
);
|
||||
|
||||
// because this is a multi-table update, here we expect multiple TableReferences
|
||||
assert !mutatingTableGroup.getTableReferenceJoins().isEmpty();
|
||||
|
||||
getFromClauseAccess().registerTableGroup( navigablePath, mutatingTableGroup );
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public EntityMappingType getMutatingEntityDescriptor() {
|
||||
return mutatingEntityDescriptor;
|
||||
}
|
||||
|
||||
public TableGroup getMutatingTableGroup() {
|
||||
return mutatingTableGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stack<SqlAstProcessingState> getProcessingStateStack() {
|
||||
return super.getProcessingStateStack();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specialized hook to visit the assignments defined by the update SQM allow
|
||||
* "listening" for each SQL assignment.
|
||||
*/
|
||||
public void visitSetClause(
|
||||
SqmSetClause setClause,
|
||||
Consumer<Assignment> assignmentConsumer,
|
||||
BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer) {
|
||||
this.parameterResolutionConsumer = parameterResolutionConsumer;
|
||||
|
||||
for ( SqmAssignment assignment : setClause.getAssignments() ) {
|
||||
visitAssignment( assignment, assignmentConsumer );
|
||||
}
|
||||
}
|
||||
|
||||
public List<Assignment> visitSetClause(SqmSetClause setClause) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
private void visitAssignment(
|
||||
SqmAssignment sqmAssignment,
|
||||
Consumer<Assignment> assignmentConsumer) {
|
||||
final Assignable assignable = (Assignable) sqmAssignment.getTargetPath().accept( this );
|
||||
|
||||
final Expression value = (Expression) sqmAssignment.getValue().accept( this );
|
||||
|
||||
assignmentConsumer.accept( new Assignment( assignable, value ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Assignment visitAssignment(SqmAssignment sqmAssignment) {
|
||||
return new Assignment(
|
||||
(Assignable) sqmAssignment.getTargetPath().accept( this ),
|
||||
(Expression) sqmAssignment.getValue().accept( this )
|
||||
);
|
||||
}
|
||||
|
||||
public Predicate visitWhereClause(
|
||||
SqmWhereClause sqmWhereClause,
|
||||
Consumer<ColumnReference> restrictionColumnReferenceConsumer,
|
||||
BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer) {
|
||||
this.parameterResolutionConsumer = parameterResolutionConsumer;
|
||||
|
||||
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent();
|
||||
final SqlAstProcessingStateImpl restrictionProcessingState = new SqlAstProcessingStateImpl(
|
||||
rootProcessingState,
|
||||
this,
|
||||
getCurrentClauseStack()::getCurrent
|
||||
) {
|
||||
@Override
|
||||
public SqlExpressionResolver getSqlExpressionResolver() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression resolveSqlExpression(
|
||||
String key, Function<SqlAstProcessingState, Expression> creator) {
|
||||
final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression(
|
||||
key,
|
||||
creator
|
||||
);
|
||||
if ( expression instanceof ColumnReference ) {
|
||||
restrictionColumnReferenceConsumer.accept( (ColumnReference) expression );
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
};
|
||||
|
||||
getProcessingStateStack().push( restrictionProcessingState );
|
||||
try {
|
||||
return (Predicate) sqmWhereClause.getPredicate().accept( this );
|
||||
}
|
||||
finally {
|
||||
getProcessingStateStack().pop();
|
||||
this.parameterResolutionConsumer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate visitWhereClause(SqmWhereClause whereClause) {
|
||||
return (Predicate) super.visitWhereClause( whereClause );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Expression consumeSqmParameter(SqmParameter sqmParameter) {
|
||||
assert parameterResolutionConsumer != null;
|
||||
|
||||
final Expression expression = super.consumeSqmParameter( sqmParameter );
|
||||
|
||||
final List<JdbcParameter> jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter );
|
||||
parameterResolutionConsumer.accept( sqmParameter, jdbcParameters );
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@Override
|
||||
public SqlAstCreationState getSqlAstCreationState() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void visitSelectClause(
|
||||
SqmSelectClause sqmSelectClause,
|
||||
QuerySpec sqlQuerySpec,
|
||||
Consumer<ColumnReference> columnReferenceConsumer,
|
||||
BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer) {
|
||||
assert sqmSelectClause != null;
|
||||
|
||||
this.parameterResolutionConsumer = parameterResolutionConsumer;
|
||||
|
||||
final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent();
|
||||
final SqlAstProcessingStateImpl processingState = new SqlAstQuerySpecProcessingStateImpl(
|
||||
sqlQuerySpec,
|
||||
rootProcessingState,
|
||||
this,
|
||||
getCurrentClauseStack()::getCurrent
|
||||
) {
|
||||
@Override
|
||||
public SqlExpressionResolver getSqlExpressionResolver() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression resolveSqlExpression(
|
||||
String key, Function<SqlAstProcessingState, Expression> creator) {
|
||||
final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression(
|
||||
key,
|
||||
creator
|
||||
);
|
||||
if ( expression instanceof ColumnReference ) {
|
||||
columnReferenceConsumer.accept( (ColumnReference) expression );
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
};
|
||||
|
||||
getProcessingStateStack().push( processingState );
|
||||
try {
|
||||
for ( int i = 0; i < sqmSelectClause.getSelectionItems().size(); i++ ) {
|
||||
final DomainResultProducer domainResultProducer = (DomainResultProducer) sqmSelectClause.getSelectionItems()
|
||||
.get( i )
|
||||
.accept( this );
|
||||
domainResultProducer.applySqlSelections( this );
|
||||
}
|
||||
}
|
||||
finally {
|
||||
getProcessingStateStack().pop();
|
||||
this.parameterResolutionConsumer = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,553 @@
|
|||
/*
|
||||
* 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.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.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
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.internal.util.collections.Stack;
|
||||
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.NavigablePath;
|
||||
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.sql.BaseSqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
|
||||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
||||
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<IdTableExporter> idTableExporterAccess;
|
||||
|
||||
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
|
||||
|
||||
public RestrictedDeleteExecutionDelegate(
|
||||
EntityMappingType entityDescriptor,
|
||||
IdTable idTable,
|
||||
SqmDeleteStatement sqmDelete,
|
||||
DomainParameterXref domainParameterXref,
|
||||
BeforeUseAction beforeUseAction,
|
||||
AfterUseAction afterUseAction,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
Supplier<IdTableExporter> idTableExporterAccess,
|
||||
Function<SharedSessionContractImplementor,String> sessionUidAccess,
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(ExecutionContext executionContext) {
|
||||
final Converter converter = new Converter(
|
||||
sessionFactory,
|
||||
executionContext.getQueryOptions(),
|
||||
domainParameterXref,
|
||||
executionContext.getQueryParameterBindings()
|
||||
);
|
||||
|
||||
final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl(
|
||||
null,
|
||||
converter,
|
||||
converter.getCurrentClauseStack()::getCurrent
|
||||
) {
|
||||
@Override
|
||||
public Expression resolveSqlExpression(
|
||||
String key, Function<SqlAstProcessingState, Expression> creator) {
|
||||
return super.resolveSqlExpression( key, creator );
|
||||
}
|
||||
};
|
||||
|
||||
converter.getProcessingStateStack().push( rootProcessingState );
|
||||
|
||||
final EntityPersister entityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( sqmDelete.getTarget().getEntityName() );
|
||||
final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName();
|
||||
final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getEntityName() );
|
||||
final TableGroup deletingTableGroup = entityDescriptor.createRootTableGroup(
|
||||
navigablePath,
|
||||
null,
|
||||
JoinType.LEFT,
|
||||
LockMode.PESSIMISTIC_WRITE,
|
||||
converter.getSqlAliasBaseGenerator(),
|
||||
converter.getSqlExpressionResolver(),
|
||||
() -> predicate -> {},
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
// because this is a multi-table update, here we expect multiple TableReferences
|
||||
assert !deletingTableGroup.getTableReferenceJoins().isEmpty();
|
||||
|
||||
// Register this TableGroup with the "registry" in preparation for
|
||||
converter.getFromClauseAccess().registerTableGroup( navigablePath, deletingTableGroup );
|
||||
|
||||
final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( hierarchyRootTableName );
|
||||
assert hierarchyRootTableReference != null;
|
||||
|
||||
// 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 );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
boolean needsIdTable = needsIdTableWrapper.get();
|
||||
|
||||
if ( needsIdTable ) {
|
||||
return executeWithIdTable(
|
||||
predicate,
|
||||
deletingTableGroup,
|
||||
converter.getRestrictionSqmParameterResolutions(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
else {
|
||||
return executeWithoutIdTable(
|
||||
predicate,
|
||||
deletingTableGroup,
|
||||
converter.getRestrictionSqmParameterResolutions(),
|
||||
converter.getSqlExpressionResolver(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private int executeWithoutIdTable(
|
||||
Predicate suppliedPredicate,
|
||||
TableGroup tableGroup,
|
||||
Map<SqmParameter, List<JdbcParameter>> 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<Consumer<ColumnConsumer>> 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<ColumnReference> 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<SqmParameter, List<JdbcParameter>> 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(
|
||||
sqmDelete,
|
||||
predicate,
|
||||
idTable,
|
||||
sessionUidAccess,
|
||||
domainParameterXref,
|
||||
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<Consumer<ColumnConsumer>> 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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static class Converter extends BaseSqmToSqlAstConverter {
|
||||
private Map<SqmParameter,List<JdbcParameter>> restrictionSqmParameterResolutions;
|
||||
|
||||
private BiConsumer<SqmParameter,List<JdbcParameter>> sqmParamResolutionConsumer;
|
||||
|
||||
Converter(
|
||||
SqlAstCreationContext creationContext,
|
||||
QueryOptions queryOptions,
|
||||
DomainParameterXref domainParameterXref,
|
||||
QueryParameterBindings domainParameterBindings) {
|
||||
super( creationContext, queryOptions, domainParameterXref, domainParameterBindings );
|
||||
}
|
||||
|
||||
Map<SqmParameter, List<JdbcParameter>> getRestrictionSqmParameterResolutions() {
|
||||
return restrictionSqmParameterResolutions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stack<SqlAstProcessingState> getProcessingStateStack() {
|
||||
return super.getProcessingStateStack();
|
||||
}
|
||||
|
||||
public Predicate visitWhereClause(SqmWhereClause sqmWhereClause, Consumer<ColumnReference> restrictionColumnReferenceConsumer) {
|
||||
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sqmParamResolutionConsumer = (sqm, jdbcs) -> {
|
||||
if ( restrictionSqmParameterResolutions == null ) {
|
||||
restrictionSqmParameterResolutions = new IdentityHashMap<>();
|
||||
}
|
||||
restrictionSqmParameterResolutions.put( sqm, jdbcs );
|
||||
};
|
||||
|
||||
final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent();
|
||||
final SqlAstProcessingStateImpl restrictionProcessingState = new SqlAstProcessingStateImpl(
|
||||
rootProcessingState,
|
||||
this,
|
||||
getCurrentClauseStack()::getCurrent
|
||||
) {
|
||||
@Override
|
||||
public SqlExpressionResolver getSqlExpressionResolver() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression resolveSqlExpression(
|
||||
String key, Function<SqlAstProcessingState, Expression> creator) {
|
||||
final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression( key, creator );
|
||||
if ( expression instanceof ColumnReference ) {
|
||||
restrictionColumnReferenceConsumer.accept( (ColumnReference) expression );
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
};
|
||||
|
||||
getProcessingStateStack().push( restrictionProcessingState );
|
||||
try {
|
||||
return (Predicate) sqmWhereClause.getPredicate().accept( this );
|
||||
}
|
||||
finally {
|
||||
getProcessingStateStack().pop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Expression consumeSqmParameter(SqmParameter sqmParameter) {
|
||||
final Expression expression = super.consumeSqmParameter( sqmParameter );
|
||||
final List<JdbcParameter> jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter );
|
||||
sqmParamResolutionConsumer.accept( sqmParameter, jdbcParameters );
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlExpressionResolver getSqlExpressionResolver() {
|
||||
return super.getSqlExpressionResolver();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6,33 +6,17 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.mutation.internal.idtable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.ColumnConsumer;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler;
|
||||
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
|
||||
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
|
||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||
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.TableReference;
|
||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||
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.JdbcParameterBindings;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
|
@ -40,31 +24,68 @@ import org.jboss.logging.Logger;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class TableBasedDeleteHandler
|
||||
extends AbstractTableBasedHandler
|
||||
extends AbstractMutationHandler
|
||||
implements DeleteHandler {
|
||||
|
||||
private static final Logger log = Logger.getLogger( TableBasedDeleteHandler.class );
|
||||
|
||||
public interface ExecutionDelegate {
|
||||
int execute(ExecutionContext executionContext);
|
||||
}
|
||||
|
||||
private final IdTable idTable;
|
||||
private final TempTableDdlTransactionHandling ddlTransactionHandling;
|
||||
private final BeforeUseAction beforeUseAction;
|
||||
private final AfterUseAction afterUseAction;
|
||||
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
|
||||
private final Supplier<IdTableExporter> exporterSupplier;
|
||||
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
|
||||
|
||||
public TableBasedDeleteHandler(
|
||||
SqmDeleteStatement sqmDeleteStatement,
|
||||
IdTable idTable,
|
||||
DomainParameterXref domainParameterXref, IdTable idTable,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
Supplier<IdTableExporter> exporterSupplier,
|
||||
BeforeUseAction beforeUseAction,
|
||||
AfterUseAction afterUseAction,
|
||||
TempTableDdlTransactionHandling transactionality,
|
||||
DomainParameterXref domainParameterXref,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
HandlerCreationContext creationContext) {
|
||||
super(
|
||||
sqmDeleteStatement,
|
||||
super( sqmDeleteStatement, creationContext );
|
||||
this.idTable = idTable;
|
||||
this.ddlTransactionHandling = ddlTransactionHandling;
|
||||
this.beforeUseAction = beforeUseAction;
|
||||
this.afterUseAction = afterUseAction;
|
||||
|
||||
this.domainParameterXref = domainParameterXref;
|
||||
|
||||
this.sessionUidAccess = sessionUidAccess;
|
||||
this.exporterSupplier = exporterSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(ExecutionContext executionContext) {
|
||||
log.tracef( "Starting multi-table delete execution - %s", getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() );
|
||||
return resolveDelegate( executionContext ).execute( executionContext );
|
||||
}
|
||||
|
||||
private ExecutionDelegate resolveDelegate(ExecutionContext executionContext) {
|
||||
if ( getSqmDeleteOrUpdateStatement().getWhereClause() == null
|
||||
|| getSqmDeleteOrUpdateStatement().getWhereClause().getPredicate() == null ) {
|
||||
return new UnrestrictedDeleteExecutionDelegate( getEntityDescriptor() );
|
||||
}
|
||||
|
||||
return new RestrictedDeleteExecutionDelegate(
|
||||
getEntityDescriptor(),
|
||||
idTable,
|
||||
transactionality,
|
||||
getSqmDeleteOrUpdateStatement(),
|
||||
domainParameterXref,
|
||||
beforeUseAction,
|
||||
afterUseAction,
|
||||
session -> session.getSessionIdentifier().toString(),
|
||||
ddlTransactionHandling,
|
||||
exporterSupplier,
|
||||
creationContext
|
||||
sessionUidAccess,
|
||||
getSessionFactory()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -72,110 +93,4 @@ public class TableBasedDeleteHandler
|
|||
public SqmDeleteStatement getSqmDeleteOrUpdateStatement() {
|
||||
return (SqmDeleteStatement) super.getSqmDeleteOrUpdateStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performMutations(ExecutionContext executionContext) {
|
||||
log.trace( "performMutations - " + getEntityDescriptor().getEntityName() );
|
||||
|
||||
// create the selection of "matching ids" from the id-table. this is used as the subquery in
|
||||
// used to restrict the deletions from each table
|
||||
final QuerySpec idTableSelectSubQuerySpec = createIdTableSubQuery( executionContext );
|
||||
|
||||
getEntityDescriptor().visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
|
||||
deleteFrom( tableExpression, tableKeyColumnsVisitationSupplier, idTableSelectSubQuerySpec, executionContext );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static class TableKeyExpressionCollector {
|
||||
private final EntityMappingType entityMappingType;
|
||||
|
||||
public TableKeyExpressionCollector(EntityMappingType entityMappingType) {
|
||||
this.entityMappingType = entityMappingType;
|
||||
}
|
||||
|
||||
Expression firstColumnExpression;
|
||||
List<Expression> collectedColumnExpressions;
|
||||
|
||||
void apply(ColumnReference columnReference) {
|
||||
if ( firstColumnExpression == null ) {
|
||||
firstColumnExpression = columnReference;
|
||||
}
|
||||
else if ( collectedColumnExpressions == null ) {
|
||||
collectedColumnExpressions = new ArrayList<>();
|
||||
collectedColumnExpressions.add( firstColumnExpression );
|
||||
collectedColumnExpressions.add( columnReference );
|
||||
}
|
||||
else {
|
||||
collectedColumnExpressions.add( columnReference );
|
||||
}
|
||||
}
|
||||
|
||||
Expression buildKeyExpression() {
|
||||
if ( collectedColumnExpressions == null ) {
|
||||
return firstColumnExpression;
|
||||
}
|
||||
|
||||
return new SqlTuple( collectedColumnExpressions, entityMappingType.getIdentifierMapping() );
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteFrom(
|
||||
String tableExpression,
|
||||
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
|
||||
QuerySpec idTableSelectSubQuery,
|
||||
ExecutionContext executionContext) {
|
||||
log.trace( "deleteFrom - " + tableExpression );
|
||||
|
||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||
|
||||
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() );
|
||||
|
||||
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(),
|
||||
idTableSelectSubQuery,
|
||||
false
|
||||
);
|
||||
|
||||
final DeleteStatement deleteStatement = new DeleteStatement(
|
||||
new TableReference( tableExpression, null, true, factory ),
|
||||
predicate
|
||||
);
|
||||
|
||||
final JdbcServices jdbcServices = factory.getJdbcServices();
|
||||
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
|
||||
|
||||
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
|
||||
final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory );
|
||||
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement );
|
||||
|
||||
final int rows = jdbcServices.getJdbcDeleteExecutor().execute(
|
||||
jdbcDelete,
|
||||
JdbcParameterBindings.NO_BINDINGS,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {
|
||||
},
|
||||
executionContext
|
||||
);
|
||||
|
||||
log.debugf( "delete-from `%s` : %s rows", tableExpression, rows );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.boot.TempTableDdlTransactionHandling;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.metamodel.spi.DomainMetamodel;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.Joinable;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler;
|
||||
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
|
||||
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
|
||||
import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
|
||||
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
||||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameter;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class TableBasedUpdateHandler
|
||||
extends AbstractMutationHandler
|
||||
implements UpdateHandler {
|
||||
private static final Logger log = Logger.getLogger( TableBasedUpdateHandler.class );
|
||||
|
||||
public interface ExecutionDelegate {
|
||||
int execute(ExecutionContext executionContext);
|
||||
}
|
||||
|
||||
private final IdTable idTable;
|
||||
private final TempTableDdlTransactionHandling ddlTransactionHandling;
|
||||
private final BeforeUseAction beforeUseAction;
|
||||
private final AfterUseAction afterUseAction;
|
||||
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
|
||||
private final Supplier<IdTableExporter> exporterSupplier;
|
||||
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
|
||||
TableBasedUpdateHandler(
|
||||
SqmUpdateStatement sqmDeleteStatement,
|
||||
DomainParameterXref domainParameterXref,
|
||||
IdTable idTable,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
Supplier<IdTableExporter> exporterSupplier,
|
||||
BeforeUseAction beforeUseAction,
|
||||
AfterUseAction afterUseAction,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
HandlerCreationContext creationContext) {
|
||||
super( sqmDeleteStatement, creationContext );
|
||||
this.idTable = idTable;
|
||||
this.exporterSupplier = exporterSupplier;
|
||||
this.beforeUseAction = beforeUseAction;
|
||||
this.afterUseAction = afterUseAction;
|
||||
this.ddlTransactionHandling = ddlTransactionHandling;
|
||||
this.sessionUidAccess = sessionUidAccess;
|
||||
this.domainParameterXref = domainParameterXref;
|
||||
}
|
||||
|
||||
protected SqmUpdateStatement getSqmUpdate() {
|
||||
return getSqmDeleteOrUpdateStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmUpdateStatement getSqmDeleteOrUpdateStatement() {
|
||||
return (SqmUpdateStatement) super.getSqmDeleteOrUpdateStatement();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int execute(ExecutionContext executionContext) {
|
||||
log.tracef( "Starting multi-table update execution - %s", getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() );
|
||||
|
||||
return resolveDelegate( executionContext ).execute( executionContext );
|
||||
}
|
||||
|
||||
private ExecutionDelegate resolveDelegate(ExecutionContext executionContext) {
|
||||
final SessionFactoryImplementor sessionFactory = getSessionFactory();
|
||||
final DomainMetamodel domainModel = sessionFactory.getDomainModel();
|
||||
final EntityPersister entityDescriptor = domainModel.getEntityDescriptor( getSqmDeleteOrUpdateStatement().getTarget().getEntityName() );
|
||||
|
||||
final String rootEntityName = entityDescriptor.getRootEntityName();
|
||||
final EntityPersister rootEntityDescriptor = domainModel.getEntityDescriptor( rootEntityName );
|
||||
|
||||
final String hierarchyRootTableName = ( (Joinable) rootEntityDescriptor ).getTableName();
|
||||
|
||||
final MultiTableSqmMutationConverter converterDelegate = new MultiTableSqmMutationConverter(
|
||||
entityDescriptor,
|
||||
domainParameterXref,
|
||||
executionContext.getQueryOptions(),
|
||||
executionContext.getQueryParameterBindings(),
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
final Stack<SqlAstProcessingState> converterProcessingStateStack = converterDelegate.getProcessingStateStack();
|
||||
|
||||
final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl(
|
||||
null,
|
||||
converterDelegate,
|
||||
converterDelegate.getCurrentClauseStack()::getCurrent
|
||||
);
|
||||
|
||||
converterProcessingStateStack.push( rootProcessingState );
|
||||
|
||||
final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getEntityName() );
|
||||
final TableGroup updatingTableGroup = entityDescriptor.createRootTableGroup(
|
||||
navigablePath,
|
||||
null,
|
||||
JoinType.LEFT,
|
||||
LockMode.PESSIMISTIC_WRITE,
|
||||
converterDelegate.getSqlAliasBaseGenerator(),
|
||||
converterDelegate.getSqlExpressionResolver(),
|
||||
() -> predicate -> {},
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
// because this is a multi-table update, here we expect multiple TableReferences
|
||||
assert !updatingTableGroup.getTableReferenceJoins().isEmpty();
|
||||
converterDelegate.getFromClauseAccess().registerTableGroup( navigablePath, updatingTableGroup );
|
||||
|
||||
final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( hierarchyRootTableName );
|
||||
assert hierarchyRootTableReference != null;
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// cross-reference the TableReference by alias. The TableGroup already
|
||||
// cross-references it by name, bu the ColumnReference only has the alias
|
||||
|
||||
final Map<String, TableReference> tableReferenceByAlias = new HashMap<>( updatingTableGroup.getTableReferenceJoins().size() + 1 );
|
||||
collectTableReference( updatingTableGroup.getPrimaryTableReference(), tableReferenceByAlias::put );
|
||||
for ( int i = 0; i < updatingTableGroup.getTableReferenceJoins().size(); i++ ) {
|
||||
collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put );
|
||||
}
|
||||
|
||||
|
||||
final Map<SqmParameter,List<JdbcParameter>> parameterResolutions;
|
||||
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
|
||||
parameterResolutions = Collections.emptyMap();
|
||||
}
|
||||
else {
|
||||
parameterResolutions = new IdentityHashMap<>();
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// visit the set-clause using our special converter, collecting
|
||||
// information about the assignments
|
||||
|
||||
final List<Assignment> assignments = new ArrayList<>();
|
||||
|
||||
converterDelegate.visitSetClause(
|
||||
getSqmDeleteOrUpdateStatement().getSetClause(),
|
||||
assignments::add,
|
||||
parameterResolutions::put
|
||||
);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// visit the where-clause using our special converter, collecting information
|
||||
// about the restrictions
|
||||
|
||||
final Predicate predicate;
|
||||
final SqmWhereClause whereClause = getSqmUpdate().getWhereClause();
|
||||
if ( whereClause == null || whereClause.getPredicate() == null ) {
|
||||
predicate = null;
|
||||
}
|
||||
else {
|
||||
predicate = converterDelegate.visitWhereClause(
|
||||
whereClause,
|
||||
columnReference -> {},
|
||||
parameterResolutions::put
|
||||
);
|
||||
assert predicate != null;
|
||||
}
|
||||
|
||||
|
||||
return new UpdateExecutionDelegate(
|
||||
getSqmUpdate(),
|
||||
converterDelegate,
|
||||
idTable,
|
||||
ddlTransactionHandling,
|
||||
beforeUseAction,
|
||||
afterUseAction,
|
||||
sessionUidAccess,
|
||||
exporterSupplier,
|
||||
domainParameterXref,
|
||||
updatingTableGroup,
|
||||
hierarchyRootTableReference,
|
||||
tableReferenceByAlias,
|
||||
assignments,
|
||||
predicate,
|
||||
parameterResolutions,
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private void collectTableReference(
|
||||
TableReference tableReference,
|
||||
BiConsumer<String, TableReference> consumer) {
|
||||
consumer.accept( tableReference.getIdentificationVariable(), tableReference );
|
||||
}
|
||||
|
||||
private void collectTableReference(
|
||||
TableReferenceJoin tableReferenceJoin,
|
||||
BiConsumer<String, TableReference> consumer) {
|
||||
collectTableReference( tableReferenceJoin.getJoinedTableReference(), consumer );
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.List;
|
||||
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
class TableKeyExpressionCollector {
|
||||
private final EntityMappingType entityMappingType;
|
||||
|
||||
TableKeyExpressionCollector(EntityMappingType entityMappingType) {
|
||||
this.entityMappingType = entityMappingType;
|
||||
}
|
||||
|
||||
Expression firstColumnExpression;
|
||||
List<Expression> collectedColumnExpressions;
|
||||
|
||||
void apply(ColumnReference columnReference) {
|
||||
if ( firstColumnExpression == null ) {
|
||||
firstColumnExpression = columnReference;
|
||||
}
|
||||
else if ( collectedColumnExpressions == null ) {
|
||||
collectedColumnExpressions = new ArrayList<>();
|
||||
collectedColumnExpressions.add( firstColumnExpression );
|
||||
collectedColumnExpressions.add( columnReference );
|
||||
}
|
||||
else {
|
||||
collectedColumnExpressions.add( columnReference );
|
||||
}
|
||||
}
|
||||
|
||||
Expression buildKeyExpression() {
|
||||
if ( collectedColumnExpressions == null ) {
|
||||
return firstColumnExpression;
|
||||
}
|
||||
|
||||
return new SqlTuple( collectedColumnExpressions, entityMappingType.getIdentifierMapping() );
|
||||
}
|
||||
}
|
|
@ -7,17 +7,11 @@
|
|||
package org.hibernate.query.sqm.mutation.internal.idtable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hibernate.boot.TempTableDdlTransactionHandling;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
|
||||
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
|
||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||
|
@ -29,39 +23,28 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
|||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class UnrestrictedTableBasedDeleteHandler extends TableBasedDeleteHandler implements DeleteHandler {
|
||||
public UnrestrictedTableBasedDeleteHandler(
|
||||
SqmDeleteStatement sqmDeleteStatement,
|
||||
IdTable idTable,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
DomainParameterXref domainParameterXref,
|
||||
BeforeUseAction beforeUseAction,
|
||||
AfterUseAction afterUseAction,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
HandlerCreationContext creationContext) {
|
||||
super(
|
||||
sqmDeleteStatement,
|
||||
idTable,
|
||||
() -> null,
|
||||
beforeUseAction,
|
||||
afterUseAction,
|
||||
ddlTransactionHandling,
|
||||
domainParameterXref,
|
||||
creationContext
|
||||
);
|
||||
public class UnrestrictedDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
|
||||
private final EntityMappingType entityDescriptor;
|
||||
|
||||
public UnrestrictedDeleteExecutionDelegate(EntityMappingType entityDescriptor) {
|
||||
this.entityDescriptor = entityDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(ExecutionContext executionContext) {
|
||||
final AtomicInteger rows = new AtomicInteger();
|
||||
// NOTE : we want the number of rows returned from this method to be the number of rows deleted
|
||||
// from the root table of the entity hierarchy, which happens to be the last table we
|
||||
// will visit
|
||||
final AtomicInteger result = new AtomicInteger();
|
||||
|
||||
getEntityDescriptor().visitConstraintOrderedTables(
|
||||
entityDescriptor.visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
|
||||
rows.set( deleteFrom( tableExpression, executionContext ) );
|
||||
final int rows = deleteFrom( tableExpression, executionContext );
|
||||
result.set( rows );
|
||||
}
|
||||
);
|
||||
|
||||
return rows.get();
|
||||
return result.get();
|
||||
}
|
||||
|
||||
private int deleteFrom(
|
||||
|
@ -81,7 +64,7 @@ public class UnrestrictedTableBasedDeleteHandler extends TableBasedDeleteHandler
|
|||
final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory );
|
||||
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement );
|
||||
|
||||
return jdbcServices.getJdbcDeleteExecutor().execute(
|
||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcDelete,
|
||||
JdbcParameterBindings.NO_BINDINGS,
|
||||
sql -> executionContext.getSession()
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
||||
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
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.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameter;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.exec.spi.JdbcUpdate;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class UpdateExecutionDelegate implements TableBasedUpdateHandler.ExecutionDelegate {
|
||||
private final SqmUpdateStatement sqmUpdate;
|
||||
private final MultiTableSqmMutationConverter sqmConverter;
|
||||
private final IdTable idTable;
|
||||
private final TempTableDdlTransactionHandling ddlTransactionHandling;
|
||||
private final BeforeUseAction beforeUseAction;
|
||||
private final AfterUseAction afterUseAction;
|
||||
private final Function<SharedSessionContractImplementor, String> sessionUidAccess;
|
||||
private final Supplier<IdTableExporter> idTableExporterAccess;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
private final TableGroup updatingTableGroup;
|
||||
private final Predicate suppliedPredicate;
|
||||
|
||||
private final EntityMappingType entityDescriptor;
|
||||
|
||||
private final JdbcParameterBindings jdbcParameterBindings;
|
||||
|
||||
private final Map<TableReference, List<Assignment>> assignmentsByTable;
|
||||
private final SessionFactoryImplementor sessionFactory;
|
||||
|
||||
public UpdateExecutionDelegate(
|
||||
SqmUpdateStatement sqmUpdate,
|
||||
MultiTableSqmMutationConverter sqmConverter,
|
||||
IdTable idTable,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling,
|
||||
BeforeUseAction beforeUseAction,
|
||||
AfterUseAction afterUseAction,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
Supplier<IdTableExporter> idTableExporterAccess,
|
||||
DomainParameterXref domainParameterXref,
|
||||
TableGroup updatingTableGroup,
|
||||
TableReference hierarchyRootTableReference,
|
||||
Map<String, TableReference> tableReferenceByAlias,
|
||||
List<Assignment> assignments,
|
||||
Predicate suppliedPredicate,
|
||||
Map<SqmParameter, List<JdbcParameter>> parameterResolutions,
|
||||
ExecutionContext executionContext) {
|
||||
this.sqmUpdate = sqmUpdate;
|
||||
this.sqmConverter = sqmConverter;
|
||||
this.idTable = idTable;
|
||||
this.ddlTransactionHandling = ddlTransactionHandling;
|
||||
this.beforeUseAction = beforeUseAction;
|
||||
this.afterUseAction = afterUseAction;
|
||||
this.sessionUidAccess = sessionUidAccess;
|
||||
this.idTableExporterAccess = idTableExporterAccess;
|
||||
this.domainParameterXref = domainParameterXref;
|
||||
this.updatingTableGroup = updatingTableGroup;
|
||||
this.suppliedPredicate = suppliedPredicate;
|
||||
|
||||
this.sessionFactory = executionContext.getSession().getFactory();
|
||||
|
||||
final ModelPartContainer updatingModelPart = updatingTableGroup.getModelPart();
|
||||
assert updatingModelPart instanceof EntityMappingType;
|
||||
|
||||
this.entityDescriptor = (EntityMappingType) updatingModelPart;
|
||||
|
||||
this.assignmentsByTable = new HashMap<>( updatingTableGroup.getTableReferenceJoins().size() + 1 );
|
||||
|
||||
jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||
executionContext.getQueryParameterBindings(),
|
||||
domainParameterXref,
|
||||
SqmUtil.generateJdbcParamsXref(
|
||||
domainParameterXref,
|
||||
() -> parameterResolutions
|
||||
),
|
||||
sessionFactory.getDomainModel(),
|
||||
navigablePath -> updatingTableGroup,
|
||||
executionContext.getSession()
|
||||
);
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// segment the assignments by table-reference
|
||||
|
||||
for ( int i = 0; i < assignments.size(); i++ ) {
|
||||
final Assignment assignment = assignments.get( i );
|
||||
final List<ColumnReference> assignmentColumnRefs = assignment.getAssignable().getColumnReferences();
|
||||
|
||||
TableReference assignmentTableReference = null;
|
||||
|
||||
for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) {
|
||||
final ColumnReference columnReference = assignmentColumnRefs.get( c );
|
||||
final TableReference tableReference = resolveTableReference(
|
||||
columnReference,
|
||||
updatingTableGroup,
|
||||
tableReferenceByAlias
|
||||
);
|
||||
|
||||
if ( assignmentTableReference != null && assignmentTableReference != tableReference ) {
|
||||
throw new IllegalStateException( "Assignment referred to columns from multiple tables" );
|
||||
}
|
||||
|
||||
assignmentTableReference = tableReference;
|
||||
}
|
||||
|
||||
List<Assignment> assignmentsForTable = assignmentsByTable.get( assignmentTableReference );
|
||||
if ( assignmentsForTable == null ) {
|
||||
assignmentsForTable = new ArrayList<>();
|
||||
assignmentsByTable.put( assignmentTableReference, assignmentsForTable );
|
||||
}
|
||||
assignmentsForTable.add( assignment );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(ExecutionContext executionContext) {
|
||||
ExecuteWithIdTableHelper.performBeforeIdTableUseActions(
|
||||
beforeUseAction,
|
||||
idTable,
|
||||
idTableExporterAccess,
|
||||
ddlTransactionHandling,
|
||||
executionContext
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable(
|
||||
sqmUpdate,
|
||||
sqmConverter,
|
||||
updatingTableGroup,
|
||||
suppliedPredicate,
|
||||
idTable,
|
||||
sessionUidAccess,
|
||||
domainParameterXref,
|
||||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
|
||||
final QuerySpec idTableSubQuery = ExecuteWithIdTableHelper.createIdTableSelectQuerySpec(
|
||||
idTable,
|
||||
sessionUidAccess,
|
||||
entityDescriptor,
|
||||
executionContext
|
||||
);
|
||||
|
||||
entityDescriptor.visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnVisitationSupplier) -> updateTable(
|
||||
tableExpression,
|
||||
tableKeyColumnVisitationSupplier,
|
||||
idTableSubQuery,
|
||||
executionContext
|
||||
)
|
||||
);
|
||||
|
||||
return rows;
|
||||
}
|
||||
finally {
|
||||
ExecuteWithIdTableHelper.performAfterIdTableUseActions(
|
||||
afterUseAction,
|
||||
idTable,
|
||||
idTableExporterAccess,
|
||||
ddlTransactionHandling,
|
||||
sessionUidAccess,
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private TableReference resolveTableReference(
|
||||
ColumnReference columnReference,
|
||||
TableGroup updatingTableGroup,
|
||||
Map<String, TableReference> tableReferenceByAlias) {
|
||||
final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( columnReference.getQualifier() );
|
||||
if ( tableReferenceByQualifier != null ) {
|
||||
return tableReferenceByQualifier;
|
||||
}
|
||||
|
||||
final TableReference tableReferenceByName = updatingTableGroup.resolveTableReference( columnReference.getQualifier() );
|
||||
if ( tableReferenceByName != null ) {
|
||||
return tableReferenceByName;
|
||||
}
|
||||
|
||||
throw new IllegalStateException( "Could not resolve restricted column's table-reference" );
|
||||
}
|
||||
|
||||
private void updateTable(
|
||||
String tableExpression,
|
||||
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
|
||||
QuerySpec idTableSubQuery,
|
||||
ExecutionContext executionContext) {
|
||||
final TableReference updatingTableReference = updatingTableGroup.resolveTableReference( tableExpression );
|
||||
|
||||
final List<Assignment> assignments = assignmentsByTable.get( updatingTableReference );
|
||||
if ( assignments == null || assignments.isEmpty() ) {
|
||||
// no assignments for this table - skip it
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// create the in-subquery predicate to restrict the updates to just
|
||||
// matching ids
|
||||
|
||||
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor );
|
||||
|
||||
tableKeyColumnVisitationSupplier.get().accept(
|
||||
(columnExpression, containingTableExpression, jdbcMapping) -> {
|
||||
assert containingTableExpression.equals( tableExpression );
|
||||
keyColumnCollector.apply(
|
||||
new ColumnReference(
|
||||
(String) null,
|
||||
columnExpression,
|
||||
jdbcMapping,
|
||||
sessionFactory
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
final InSubQueryPredicate idTableSubQueryPredicate = new InSubQueryPredicate(
|
||||
keyColumnCollector.buildKeyExpression(),
|
||||
idTableSubQuery,
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Create the SQL AST and convert it into a JdbcOperation
|
||||
final UpdateStatement sqlAst = new UpdateStatement( updatingTableReference, assignments, idTableSubQueryPredicate );
|
||||
|
||||
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
|
||||
final SqlAstUpdateTranslator sqlAstTranslator = jdbcServices.getJdbcEnvironment()
|
||||
.getSqlAstTranslatorFactory()
|
||||
.buildUpdateTranslator( sessionFactory );
|
||||
|
||||
final JdbcUpdate jdbcUpdate = sqlAstTranslator.translate( sqlAst );
|
||||
|
||||
jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcUpdate,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {},
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
}
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.mutation.spi;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
|
||||
|
||||
/**
|
||||
|
@ -15,13 +15,22 @@ import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
|
|||
*/
|
||||
public abstract class AbstractMutationHandler implements Handler {
|
||||
private final SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement;
|
||||
private final HandlerCreationContext creationContext;
|
||||
|
||||
private final SessionFactoryImplementor sessionFactory;
|
||||
private final EntityMappingType entityDescriptor;
|
||||
|
||||
public AbstractMutationHandler(
|
||||
SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement,
|
||||
HandlerCreationContext creationContext) {
|
||||
this.sqmDeleteOrUpdateStatement = sqmDeleteOrUpdateStatement;
|
||||
this.creationContext = creationContext;
|
||||
this.sessionFactory = creationContext.getSessionFactory();
|
||||
|
||||
final String entityName = sqmDeleteOrUpdateStatement.getTarget()
|
||||
.getReferencedPathSource()
|
||||
.getHibernateEntityName();
|
||||
|
||||
this.entityDescriptor = sessionFactory.getMetamodel().getEntityDescriptor( entityName );
|
||||
|
||||
}
|
||||
|
||||
public SqmDeleteOrUpdateStatement getSqmDeleteOrUpdateStatement() {
|
||||
|
@ -29,15 +38,10 @@ public abstract class AbstractMutationHandler implements Handler {
|
|||
}
|
||||
|
||||
public EntityMappingType getEntityDescriptor() {
|
||||
final String entityName = sqmDeleteOrUpdateStatement.getTarget()
|
||||
.getReferencedPathSource()
|
||||
.getHibernateEntityName();
|
||||
|
||||
return creationContext.getSessionFactory().getMetamodel().getEntityDescriptor( entityName );
|
||||
|
||||
return entityDescriptor;
|
||||
}
|
||||
|
||||
public HandlerCreationContext getCreationContext() {
|
||||
return creationContext;
|
||||
public SessionFactoryImplementor getSessionFactory() {
|
||||
return sessionFactory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -783,7 +783,7 @@ public abstract class BaseSqmToSqlAstConverter
|
|||
}
|
||||
|
||||
|
||||
private Expression consumeSqmParameter(SqmParameter sqmParameter) {
|
||||
protected Expression consumeSqmParameter(SqmParameter sqmParameter) {
|
||||
final MappingModelExpressable valueMapping = determineValueMapping( sqmParameter );
|
||||
final List<JdbcParameter> jdbcParametersForSqm = new ArrayList<>();
|
||||
|
||||
|
|
|
@ -10,12 +10,13 @@ import java.util.function.Consumer;
|
|||
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.update.Assignable;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface AssignableSqmPathInterpretation<T> extends SqmPathInterpretation<T> {
|
||||
public interface AssignableSqmPathInterpretation<T> extends SqmPathInterpretation<T>, Assignable {
|
||||
// need to be able to collect assignments per-table, including
|
||||
// SqmParameter -> JdbcParameter mapping
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.sql.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.NotYetImplementedFor6Exception;
|
||||
|
@ -132,4 +134,14 @@ public class BasicValuedPathInterpretation<T> implements AssignableSqmPathInterp
|
|||
public String toString() {
|
||||
return "BasicValuedPathInterpretation(" + sqmPath.getNavigablePath().getFullPath() + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer) {
|
||||
columnReferenceConsumer.accept( columnReference );
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ColumnReference> getColumnReferences() {
|
||||
return Collections.singletonList( columnReference );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.sql.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.NotYetImplementedFor6Exception;
|
||||
|
@ -17,7 +19,9 @@ import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlAstWalker;
|
||||
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.update.Assignment;
|
||||
import org.hibernate.sql.results.spi.DomainResult;
|
||||
|
@ -45,7 +49,6 @@ public class EmbeddableValuedPathInterpretation<T> implements AssignableSqmPathI
|
|||
return new EmbeddableValuedPathInterpretation<>(
|
||||
mapping.toSqlExpression(
|
||||
tableGroup,
|
||||
|
||||
converter.getCurrentClauseStack().getCurrent(),
|
||||
converter,
|
||||
converter
|
||||
|
@ -114,4 +117,30 @@ public class EmbeddableValuedPathInterpretation<T> implements AssignableSqmPathI
|
|||
public String toString() {
|
||||
return "EmbeddableValuedPathInterpretation(" + sqmPath.getNavigablePath().getFullPath() + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer) {
|
||||
if ( sqlExpression instanceof ColumnReference ) {
|
||||
columnReferenceConsumer.accept( (ColumnReference) sqlExpression );
|
||||
}
|
||||
else if ( sqlExpression instanceof SqlTuple ) {
|
||||
final SqlTuple sqlTuple = (SqlTuple) sqlExpression;
|
||||
for ( Expression expression : sqlTuple.getExpressions() ) {
|
||||
if ( ! ( expression instanceof ColumnReference ) ) {
|
||||
throw new IllegalArgumentException( "Expecting ColumnReference, found : " + expression );
|
||||
}
|
||||
columnReferenceConsumer.accept( (ColumnReference) expression );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// error or warning...
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ColumnReference> getColumnReferences() {
|
||||
final List<ColumnReference> results = new ArrayList<>();
|
||||
visitColumnReferences( results::add );
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,12 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast;
|
||||
|
||||
import org.hibernate.sql.ast.spi.SqlAstToJdbcOperationConverter;
|
||||
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
|
||||
import org.hibernate.sql.exec.spi.JdbcInsert;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SqlAstInsertSelectTranslator extends SqlAstToJdbcOperationConverter {
|
||||
public interface SqlAstInsertSelectTranslator extends SqlAstTranslator {
|
||||
JdbcInsert translate(InsertSelectStatement sqlAst);
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast;
|
||||
|
||||
import org.hibernate.sql.ast.spi.SqlAstToJdbcOperationConverter;
|
||||
import org.hibernate.sql.ast.spi.SqlSelectAstWalker;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||
import org.hibernate.sql.exec.spi.JdbcSelect;
|
||||
|
@ -15,7 +13,7 @@ import org.hibernate.sql.exec.spi.JdbcSelect;
|
|||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SqlAstSelectTranslator extends SqlSelectAstWalker, SqlAstToJdbcOperationConverter {
|
||||
public interface SqlAstSelectTranslator extends SqlAstTranslator {
|
||||
|
||||
/**
|
||||
* Translate the SelectStatement into the executable JdbcSelect
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.ast;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.sql.ast.spi.SqlAstWalker;
|
||||
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SqlAstTranslator extends SqlAstWalker, SqlTypeDescriptorIndicators {
|
||||
/**
|
||||
* Not the best spot for this. Its the table names collected while walking the SQL AST.
|
||||
* Its ok here because the translator is consider a one-time-use. It just needs to be called
|
||||
* after translation.
|
||||
*
|
||||
* A better option is probably to have "translation" objects that expose the affected table-names.
|
||||
*/
|
||||
Set<String> getAffectedTableNames();
|
||||
|
||||
/**
|
||||
* Generalized support for translating a CTE statement. The underlying
|
||||
* {@link CteStatement#getCteConsumer()} could be a SELECT, UPDATE, DELETE, etc.
|
||||
*
|
||||
* Implementors may throw an exception is the CTE-consumer is of the incorrect type
|
||||
*/
|
||||
JdbcOperation translate(CteStatement cteStatement);
|
||||
}
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast;
|
||||
|
||||
import org.hibernate.sql.ast.spi.SqlAstToJdbcOperationConverter;
|
||||
import org.hibernate.sql.ast.spi.SqlAstWalker;
|
||||
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||
import org.hibernate.sql.exec.spi.JdbcUpdate;
|
||||
|
@ -15,7 +13,7 @@ import org.hibernate.sql.exec.spi.JdbcUpdate;
|
|||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SqlAstUpdateTranslator extends SqlAstWalker, SqlAstToJdbcOperationConverter {
|
||||
public interface SqlAstUpdateTranslator extends SqlAstTranslator {
|
||||
JdbcUpdate translate(UpdateStatement sqlAst);
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.SqlTreeCreationException;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
|
@ -17,13 +18,13 @@ import org.hibernate.sql.ast.tree.update.Assignment;
|
|||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class AbstractSqlAstToJdbcOperationConverter
|
||||
public abstract class AbstractSqlAstTranslator
|
||||
extends AbstractSqlAstWalker
|
||||
implements SqlAstToJdbcOperationConverter {
|
||||
implements SqlAstTranslator {
|
||||
|
||||
private final Set<String> affectedTableNames = new HashSet<>();
|
||||
|
||||
protected AbstractSqlAstToJdbcOperationConverter(SessionFactoryImplementor sessionFactory) {
|
||||
protected AbstractSqlAstTranslator(SessionFactoryImplementor sessionFactory) {
|
||||
super( sessionFactory );
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinder;
|
|||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class StandardSqlAstDeleteTranslator extends AbstractSqlAstToJdbcOperationConverter implements SqlAstDeleteTranslator {
|
||||
public class StandardSqlAstDeleteTranslator extends AbstractSqlAstTranslator implements SqlAstDeleteTranslator {
|
||||
public StandardSqlAstDeleteTranslator(SessionFactoryImplementor sessionFactory) {
|
||||
super( sessionFactory );
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinder;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class StandardSqlAstInsertSelectTranslator
|
||||
extends AbstractSqlAstToJdbcOperationConverter
|
||||
extends AbstractSqlAstTranslator
|
||||
implements SqlAstInsertSelectTranslator {
|
||||
public StandardSqlAstInsertSelectTranslator(SessionFactoryImplementor sessionFactory) {
|
||||
super( sessionFactory );
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.hibernate.sql.results.internal.JdbcValuesMappingProducerStandard;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class StandardSqlAstSelectTranslator
|
||||
extends AbstractSqlAstToJdbcOperationConverter
|
||||
extends AbstractSqlAstTranslator
|
||||
implements SqlAstSelectTranslator {
|
||||
|
||||
public StandardSqlAstSelectTranslator(SessionFactoryImplementor sessionFactory) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
import org.hibernate.NotYetImplementedFor6Exception;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
|
||||
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||
|
@ -23,14 +24,25 @@ import org.hibernate.sql.exec.spi.JdbcUpdate;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class StandardSqlAstUpdateTranslator
|
||||
extends AbstractSqlAstToJdbcOperationConverter
|
||||
extends AbstractSqlAstTranslator
|
||||
implements SqlAstUpdateTranslator {
|
||||
private final Dialect dialect;
|
||||
|
||||
public StandardSqlAstUpdateTranslator(SessionFactoryImplementor sessionFactory) {
|
||||
super( sessionFactory );
|
||||
|
||||
// todo (6.0) : use the Dialect to determine how to handle column references
|
||||
// - specifically should they use the table-alias, the table-expression
|
||||
// or neither for its qualifier
|
||||
dialect = getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect();
|
||||
}
|
||||
|
||||
private String updatingTableAlias;
|
||||
|
||||
@Override
|
||||
public JdbcUpdate translate(UpdateStatement sqlAst) {
|
||||
updatingTableAlias = sqlAst.getTargetTable().getIdentificationVariable();
|
||||
|
||||
appendSql( "update " );
|
||||
appendSql( sqlAst.getTargetTable().getTableExpression() );
|
||||
|
||||
|
@ -45,7 +57,17 @@ public class StandardSqlAstUpdateTranslator
|
|||
}
|
||||
|
||||
final Assignment assignment = sqlAst.getAssignments().get( i );
|
||||
assignment.getColumnReference().accept( this );
|
||||
final List<ColumnReference> columnReferences = assignment.getAssignable().getColumnReferences();
|
||||
if ( columnReferences.size() == 1 ) {
|
||||
columnReferences.get( 0 ).accept( this );
|
||||
}
|
||||
else {
|
||||
appendSql( " (" );
|
||||
for ( int cri = 0; cri < columnReferences.size(); cri++ ) {
|
||||
columnReferences.get( cri ).accept( this );
|
||||
}
|
||||
appendSql( ") " );
|
||||
}
|
||||
appendSql( " = " );
|
||||
assignment.getAssignedValue().accept( this );
|
||||
}
|
||||
|
@ -75,7 +97,17 @@ public class StandardSqlAstUpdateTranslator
|
|||
|
||||
@Override
|
||||
public void visitColumnReference(ColumnReference columnReference) {
|
||||
super.visitColumnReference( columnReference );
|
||||
if ( updatingTableAlias != null && updatingTableAlias.equals( columnReference.getQualifier() ) ) {
|
||||
// todo (6.0) : use the Dialect to determine how to handle column references
|
||||
// - specifically should they use the table-alias, the table-expression
|
||||
// or neither for its qualifier
|
||||
|
||||
// for now, use the unqualified form
|
||||
appendSql( columnReference.getColumnExpression() );
|
||||
}
|
||||
else {
|
||||
super.visitColumnReference( columnReference );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
|
||||
package org.hibernate.sql.ast.tree.expression;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
|
@ -17,6 +20,7 @@ import org.hibernate.metamodel.mapping.MappingModelExpressable;
|
|||
import org.hibernate.sql.ast.spi.SqlAstWalker;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.update.Assignable;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
@ -26,7 +30,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ColumnReference implements Expression {
|
||||
public class ColumnReference implements Expression, Assignable {
|
||||
private final String qualifier;
|
||||
private final String columnExpression;
|
||||
private final String referenceExpression;
|
||||
|
@ -123,4 +127,14 @@ public class ColumnReference implements Expression {
|
|||
public int hashCode() {
|
||||
return referenceExpression.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer) {
|
||||
columnReferenceConsumer.accept( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ColumnReference> getColumnReferences() {
|
||||
return Collections.singletonList( this );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ import org.hibernate.LockMode;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.spi.SqlAstWalker;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -123,19 +121,6 @@ public abstract class AbstractTableGroup extends AbstractColumnReferenceQualifie
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected void renderTableReference(
|
||||
TableReference tableBinding,
|
||||
SqlAppender sqlAppender,
|
||||
@SuppressWarnings("unused") SqlAstWalker walker) {
|
||||
sqlAppender.appendSql( tableBinding.getTableExpression() );
|
||||
|
||||
final String identificationVariable = tableBinding.getIdentificationVariable();
|
||||
if ( identificationVariable != null ) {
|
||||
sqlAppender.appendSql( " as " + identificationVariable );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInnerJoinPossible() {
|
||||
return isInnerJoinPossible;
|
||||
|
|
|
@ -12,10 +12,10 @@ import java.util.function.Supplier;
|
|||
import org.hibernate.LockMode;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,18 +6,13 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast.tree.from;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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.ast.tree.from;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TableAliasResolver {
|
||||
String resolveAlias(String tableExpression, String aliasStem);
|
||||
}
|
|
@ -8,10 +8,10 @@ package org.hibernate.sql.ast.tree.from;
|
|||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.JoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.ast.tree.update;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface Assignable {
|
||||
List<ColumnReference> getColumnReferences();
|
||||
|
||||
void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer);
|
||||
|
||||
}
|
|
@ -8,26 +8,25 @@ package org.hibernate.sql.ast.tree.update;
|
|||
|
||||
import org.hibernate.sql.ast.spi.SqlAstWalker;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class Assignment implements SqlAstNode {
|
||||
private final ColumnReference columnReference;
|
||||
private final Assignable assignable;
|
||||
private final Expression assignedValue;
|
||||
|
||||
public Assignment(ColumnReference columnReference, Expression assignedValue) {
|
||||
this.columnReference = columnReference;
|
||||
public Assignment(Assignable assignable, Expression assignedValue) {
|
||||
this.assignable = assignable;
|
||||
this.assignedValue = assignedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* The column being updated.
|
||||
*/
|
||||
public ColumnReference getColumnReference() {
|
||||
return columnReference;
|
||||
public Assignable getAssignable() {
|
||||
return assignable;
|
||||
}
|
||||
|
||||
public Expression getAssignedValue() {
|
||||
|
|
|
@ -6,16 +6,12 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.sql.exec;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
|
||||
import org.hibernate.orm.test.metamodel.mapping.SecondaryTableTests;
|
||||
import org.hibernate.orm.test.metamodel.mapping.inheritance.joined.JoinedInheritanceTest;
|
||||
|
||||
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||
import org.hibernate.testing.orm.domain.gambit.BasicEntity;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
|
@ -29,18 +25,21 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@DomainModel(
|
||||
standardModels = StandardDomainModel.GAMBIT
|
||||
// standardModels = StandardDomainModel.GAMBIT,
|
||||
// annotatedClasses = {
|
||||
// SecondaryTableTests.SimpleEntityWithSecondaryTables.class,
|
||||
// JoinedInheritanceTest.Customer.class,
|
||||
// JoinedInheritanceTest.DomesticCustomer.class,
|
||||
// JoinedInheritanceTest.ForeignCustomer.class
|
||||
// }
|
||||
standardModels = StandardDomainModel.GAMBIT,
|
||||
annotatedClasses = {
|
||||
SecondaryTableTests.SimpleEntityWithSecondaryTables.class,
|
||||
JoinedInheritanceTest.Customer.class,
|
||||
JoinedInheritanceTest.DomesticCustomer.class,
|
||||
JoinedInheritanceTest.ForeignCustomer.class
|
||||
}
|
||||
)
|
||||
@ServiceRegistry
|
||||
@SessionFactory( exportSchema = true )
|
||||
public class HqlUpdateExecutionTests {
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// single table
|
||||
|
||||
@Test
|
||||
public void testSimpleUpdate(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -130,174 +129,61 @@ public class HqlUpdateExecutionTests {
|
|||
);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testSimpleMultiTableDelete(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete SimpleEntityWithSecondaryTables" )
|
||||
// .executeUpdate()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testSimpleMultiTableRestrictedDelete(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete SimpleEntityWithSecondaryTables where data = :filter" )
|
||||
// .setParameter( "filter", "abc" )
|
||||
// .executeUpdate()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// @FailureExpected( reason = "Saving of entities with secondary tables is broken atm" )
|
||||
// public void testSimpleMultiTableRestrictedDeleteResults(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// session.save(
|
||||
// new SecondaryTableTests.SimpleEntityWithSecondaryTables(
|
||||
// 1,
|
||||
// "first",
|
||||
// Date.from( Instant.now() ),
|
||||
// "1 - cfdjdjvokfobkofbvovoijjbvoijofjdbiof"
|
||||
// )
|
||||
// );
|
||||
// session.save(
|
||||
// new SecondaryTableTests.SimpleEntityWithSecondaryTables(
|
||||
// 2,
|
||||
// "second",
|
||||
// Date.from( Instant.now() ),
|
||||
// "2 - s3o2rj9 fcojv9j gj9jfv943jv29j9j4"
|
||||
// )
|
||||
// );
|
||||
// session.save(
|
||||
// new SecondaryTableTests.SimpleEntityWithSecondaryTables(
|
||||
// 3,
|
||||
// "third",
|
||||
// Date.from( Instant.now() ),
|
||||
// "abc"
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// final int rows = session.createQuery( "delete SimpleEntityWithSecondaryTables where data = :filter" )
|
||||
// .setParameter( "filter", "abc" )
|
||||
// .executeUpdate();
|
||||
// assertThat( rows, is ( 1 ) );
|
||||
// }
|
||||
// );
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete SimpleEntityWithSecondaryTables" ).executeUpdate()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
//
|
||||
// @Test
|
||||
// public void testJoinedSubclassRootDelete(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete Customer" ).executeUpdate()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testJoinedSubclassRootRestrictedDelete(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete Customer where name = 'abc'" ).executeUpdate()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testJoinedSubclassRootRestrictedDeleteResults(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// session.save(
|
||||
// new JoinedInheritanceTest.ForeignCustomer( 1, "Adventures Abroad", "123" )
|
||||
// );
|
||||
// session.save(
|
||||
// new JoinedInheritanceTest.DomesticCustomer( 2, "Domestic Wonders", "456" )
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// final int rows = session.createQuery( "delete Customer where name = 'Adventures Abroad'" ).executeUpdate();
|
||||
// assertThat( rows, is( 1 ) );
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// final int rows = session.createQuery( "delete from Customer" ).executeUpdate();
|
||||
// assertThat( rows, is( 1 ) );
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// final int rows = session.createQuery( "delete from Customer" ).executeUpdate();
|
||||
// assertThat( rows, is( 0 ) );
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
//
|
||||
//
|
||||
// @Test
|
||||
// public void testJoinedSubclassLeafDelete(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete ForeignCustomer" ).executeUpdate()
|
||||
// );
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete DomesticCustomer" ).executeUpdate()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testJoinedSubclassLeafRestrictedDelete(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete ForeignCustomer where name = 'abc'" ).executeUpdate()
|
||||
// );
|
||||
// scope.inTransaction(
|
||||
// session -> session.createQuery( "delete DomesticCustomer where name = 'abc'" ).executeUpdate()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testJoinedSubclassLeafRestrictedDeleteResult(SessionFactoryScope scope) {
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// session.save(
|
||||
// new JoinedInheritanceTest.ForeignCustomer( 1, "Adventures Abroad", "123" )
|
||||
// );
|
||||
// session.save(
|
||||
// new JoinedInheritanceTest.DomesticCustomer( 2, "Domestic Wonders", "456" )
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// final int rows = session.createQuery( "delete ForeignCustomer where name = 'Adventures Abroad'" )
|
||||
// .executeUpdate();
|
||||
// assertThat( rows, is( 1 ) );
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// final int rows = session.createQuery( "delete DomesticCustomer where name = 'Domestic Wonders'" )
|
||||
// .executeUpdate();
|
||||
// assertThat( rows, is( 1 ) );
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// scope.inTransaction(
|
||||
// session -> {
|
||||
// final int rows = session.createQuery( "delete Customer" )
|
||||
// .executeUpdate();
|
||||
// assertThat( rows, is( 0 ) );
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// secondary tables
|
||||
|
||||
@Test
|
||||
public void testSecondaryTableUpdate(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "update SimpleEntityWithSecondaryTables set name = :p" )
|
||||
.setParameter( "p", "xyz" )
|
||||
.executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondaryTableRestrictedUpdatePrimary(SessionFactoryScope scope) {
|
||||
// attempts to update the entity referring to just columns in the root table
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "update SimpleEntityWithSecondaryTables set name = :p where name = :x" )
|
||||
.setParameter( "p", "xyz" )
|
||||
.setParameter( "x", "abc" )
|
||||
.executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondaryTableRestrictedUpdate(SessionFactoryScope scope) {
|
||||
// attempts to update the entity referring to columns in non-root table
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "update SimpleEntityWithSecondaryTables set name = :p where data = :x" )
|
||||
.setParameter( "p", "xyz" )
|
||||
.setParameter( "x", "123" )
|
||||
.executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// joined subclassing
|
||||
|
||||
@Test
|
||||
public void testJoinedSubclassRootUpdateUnrestricted(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "update Customer set name = :n" )
|
||||
.setParameter( "n", "abc" )
|
||||
.executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJoinedSubclassRootUpdateRestricted(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "update Customer set name = :n where id = :d" )
|
||||
.setParameter( "n", "abc" )
|
||||
.setParameter( "d", 1 )
|
||||
.executeUpdate()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue