diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcServices.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcServices.java index 2c27c3f527..e2b8f2671f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcServices.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcServices.java @@ -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; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java index 26ae68f998..278585092b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 4840f26d71..b9eacba798 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java index 37a887bbf0..54bc7dbde8 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java @@ -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; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index e9f902e8c2..4c86036cfd 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -1227,13 +1227,7 @@ public abstract class AbstractEntityPersister SqlExpressionResolver sqlExpressionResolver, Supplier> 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, diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index a8385e35a5..01a2083048 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -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; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java index bfe1403bb0..2e1181641d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java @@ -90,7 +90,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { executionContext.getSession() ); - return jdbcServices.getJdbcDeleteExecutor().execute( + return jdbcServices.getJdbcMutationExecutor().execute( jdbcDelete, jdbcParameterBindings, sql -> executionContext.getSession() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java index 959d9614c7..adc467d321 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java @@ -90,7 +90,7 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan { executionContext.getSession() ); - return jdbcServices.getJdbcUpdateExecutor().execute( + return jdbcServices.getJdbcMutationExecutor().execute( jdbcUpdate, jdbcParameterBindings, sql -> executionContext.getSession() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index 18503d1be9..162c302bfe 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -102,6 +102,20 @@ public class SqmTreePrinter implements SemanticQueryWalker { 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 { 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(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java index bf678bb903..9eaa6a6d72 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmIdSelectGenerator.java @@ -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 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( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java index 4875ec5fff..1eb4826aee 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java @@ -245,7 +245,6 @@ public class SqmMutationStrategyHelper { final SqmQuerySpec sqmIdSelectQuerySpec = SqmIdSelectGenerator.generateSqmEntityIdSelect( sqmDeleteStatement, - executionContext, factory ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java index c206ccdad9..c779de53b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java @@ -71,7 +71,12 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele @Override public int execute(ExecutionContext executionContext) { - final List ids = selectMatchingIds( executionContext ); + final List 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 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 -> { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/AbstractTableBasedHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java similarity index 53% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/AbstractTableBasedHandler.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java index 25ea422ec7..59f025da9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/AbstractTableBasedHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java @@ -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 sessionUidAccess; - - private final Supplier exporterSupplier; - - - public AbstractTableBasedHandler( - SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement, + public static int saveMatchingIdsIntoIdTable( + SqmUpdateStatement sqmMutation, + MultiTableSqmMutationConverter sqmConverter, + TableGroup mutatingTableGroup, + Predicate suppliedPredicate, IdTable idTable, - TempTableDdlTransactionHandling ddlTransactionHandling, + Function sessionUidAccess, DomainParameterXref domainParameterXref, - BeforeUseAction beforeUseAction, - AfterUseAction afterUseAction, - Function sessionUidAccess, - Supplier 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 getSessionUidAccess() { - return sessionUidAccess; - } - - public Supplier 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 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 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 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 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 idTableExporterAccess, + TempTableDdlTransactionHandling ddlTransactionHandling, + Function 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() + ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithoutIdTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithoutIdTableHelper.java new file mode 100644 index 0000000000..55445c2e06 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithoutIdTableHelper.java @@ -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; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java index 7b03d39c3e..6cc000bf2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/LocalTemporaryTableStrategy.java @@ -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 ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/MultiTableSqmMutationConverter.java new file mode 100644 index 0000000000..9e83eb6442 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/MultiTableSqmMutationConverter.java @@ -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> 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 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 assignmentConsumer, + BiConsumer> parameterResolutionConsumer) { + this.parameterResolutionConsumer = parameterResolutionConsumer; + + for ( SqmAssignment assignment : setClause.getAssignments() ) { + visitAssignment( assignment, assignmentConsumer ); + } + } + + public List visitSetClause(SqmSetClause setClause) { + throw new UnsupportedOperationException(); + } + private void visitAssignment( + SqmAssignment sqmAssignment, + Consumer 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 restrictionColumnReferenceConsumer, + BiConsumer> 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 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 jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter ); + parameterResolutionConsumer.accept( sqmParameter, jdbcParameters ); + + return expression; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public SqlAstCreationState getSqlAstCreationState() { + return this; + } + + public void visitSelectClause( + SqmSelectClause sqmSelectClause, + QuerySpec sqlQuerySpec, + Consumer columnReferenceConsumer, + BiConsumer> 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 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; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java new file mode 100644 index 0000000000..b6a54b75fc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java @@ -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 idTableExporterAccess; + + private final Function sessionUidAccess; + + public RestrictedDeleteExecutionDelegate( + EntityMappingType entityDescriptor, + IdTable idTable, + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + BeforeUseAction beforeUseAction, + AfterUseAction afterUseAction, + TempTableDdlTransactionHandling ddlTransactionHandling, + Supplier idTableExporterAccess, + Function 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 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> restrictionSqmParameterResolutions, + SqlExpressionResolver sqlExpressionResolver, + ExecutionContext executionContext) { + final EntityPersister rootEntityPersister; + final String rootEntityName = entityDescriptor.getEntityPersister().getRootEntityName(); + if ( rootEntityName.equals( entityDescriptor.getEntityName() ) ) { + rootEntityPersister = entityDescriptor.getEntityPersister(); + } + else { + rootEntityPersister = sessionFactory.getDomainModel().findEntityDescriptor( rootEntityName ); + } + + final AtomicInteger rows = new AtomicInteger(); + + final String rootTableName = ( (Joinable) rootEntityPersister ).getTableName(); + final TableReference rootTableReference = tableGroup.resolveTableReference( rootTableName ); + + final QuerySpec matchingIdSubQuerySpec = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( + tableGroup.getNavigablePath(), + rootTableReference, + suppliedPredicate, + rootEntityPersister, + sqlExpressionResolver, + sessionFactory + ); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + SqmUtil.generateJdbcParamsXref( + domainParameterXref, + () -> restrictionSqmParameterResolutions + ), + sessionFactory.getDomainModel(), + navigablePath -> tableGroup, + executionContext.getSession() + ); + + entityDescriptor.visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> { + if ( tableExpression.equals( rootTableName ) ) { + rows.set( + deleteFromRootTableWithoutIdTable( + rootTableReference, + suppliedPredicate, + jdbcParameterBindings, + executionContext + ) + ); + } + else { + deleteFromNonRootTableWithoutIdTable( + tableGroup.resolveTableReference( tableExpression ), + tableKeyColumnVisitationSupplier, + sqlExpressionResolver, + tableGroup, + matchingIdSubQuerySpec, + jdbcParameterBindings, + executionContext + ); + } + } + ); + + return rows.get(); + } + + private int deleteFromRootTableWithoutIdTable( + TableReference rootTableReference, + Predicate predicate, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + return executeSqlDelete( + new DeleteStatement( rootTableReference, predicate ), + jdbcParameterBindings, + executionContext + ); + } + + private void deleteFromNonRootTableWithoutIdTable( + TableReference targetTableReference, + Supplier> tableKeyColumnVisitationSupplier, + SqlExpressionResolver sqlExpressionResolver, + TableGroup rootTableGroup, + QuerySpec matchingIdSubQuerySpec, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + assert targetTableReference != null; + log.trace( "deleteFromNonRootTable - " + targetTableReference.getTableExpression() ); + + /* + * delete from sub_table + * where sub_id in ( + * select root_id from root_table + * where {predicate} + * ) + */ + + /* + * Create the `sub_id` reference as the LHS of the in-subquery predicate + */ + final List deletingTableColumnRefs = new ArrayList<>(); + tableKeyColumnVisitationSupplier.get().accept( + (columnExpression, containingTableExpression, jdbcMapping) -> { + assert targetTableReference.getTableExpression().equals( containingTableExpression ); + + final Expression expression = sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( targetTableReference, columnExpression ), + sqlAstProcessingState -> new ColumnReference( + rootTableGroup.getPrimaryTableReference(), + columnExpression, + jdbcMapping, + sessionFactory + ) + ); + + deletingTableColumnRefs.add( (ColumnReference) expression ); + } + ); + + final Expression deletingTableColumnRefsExpression; + if ( deletingTableColumnRefs.size() == 1 ) { + deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 ); + } + else { + deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() ); + } + + final InSubQueryPredicate idMatchPredicate = new InSubQueryPredicate( + deletingTableColumnRefsExpression, + matchingIdSubQuerySpec, + false + ); + + final DeleteStatement sqlAstDelete = new DeleteStatement( targetTableReference, idMatchPredicate ); + final int rows = executeSqlDelete( + sqlAstDelete, + jdbcParameterBindings, + executionContext + ); + log.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows ); + } + + + private static int executeSqlDelete( + DeleteStatement sqlAst, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final JdbcServices jdbcServices = factory.getJdbcServices(); + + final SqlAstDeleteTranslator sqlAstTranslator = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildDeleteTranslator( factory ); + final JdbcDelete jdbcDelete = sqlAstTranslator.translate( sqlAst ); + + return jdbcServices.getJdbcMutationExecutor().execute( + jdbcDelete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + + private int executeWithIdTable( + Predicate predicate, + TableGroup deletingTableGroup, + Map> restrictionSqmParameterResolutions, + ExecutionContext executionContext) { + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + SqmUtil.generateJdbcParamsXref( + domainParameterXref, + () -> restrictionSqmParameterResolutions + ), + sessionFactory.getDomainModel(), + navigablePath -> deletingTableGroup, + executionContext.getSession() + ); + + ExecuteWithIdTableHelper.performBeforeIdTableUseActions( + beforeUseAction, + idTable, + idTableExporterAccess, + ddlTransactionHandling, + executionContext + ); + + try { + return executeUsingIdTable( predicate, executionContext, jdbcParameterBindings ); + } + finally { + ExecuteWithIdTableHelper.performAfterIdTableUseActions( + afterUseAction, + idTable, + idTableExporterAccess, + ddlTransactionHandling, + sessionUidAccess, + executionContext + ); + } + } + + private int executeUsingIdTable( + Predicate predicate, + ExecutionContext executionContext, + JdbcParameterBindings jdbcParameterBindings) { + final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable( + 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> 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> restrictionSqmParameterResolutions; + + private BiConsumer> sqmParamResolutionConsumer; + + Converter( + SqlAstCreationContext creationContext, + QueryOptions queryOptions, + DomainParameterXref domainParameterXref, + QueryParameterBindings domainParameterBindings) { + super( creationContext, queryOptions, domainParameterXref, domainParameterBindings ); + } + + Map> getRestrictionSqmParameterResolutions() { + return restrictionSqmParameterResolutions; + } + + @Override + public Stack getProcessingStateStack() { + return super.getProcessingStateStack(); + } + + public Predicate visitWhereClause(SqmWhereClause sqmWhereClause, Consumer 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 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 jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter ); + sqmParamResolutionConsumer.accept( sqmParameter, jdbcParameters ); + return expression; + } + + @Override + public SqlExpressionResolver getSqlExpressionResolver() { + return super.getSqlExpressionResolver(); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java index ac5c667d25..46130edfdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedDeleteHandler.java @@ -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 sessionUidAccess; + private final Supplier exporterSupplier; + + private final DomainParameterXref domainParameterXref; + public TableBasedDeleteHandler( SqmDeleteStatement sqmDeleteStatement, - IdTable idTable, + DomainParameterXref domainParameterXref, IdTable idTable, + Function sessionUidAccess, Supplier 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 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> 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 ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java new file mode 100644 index 0000000000..7ed97763c8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java @@ -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 sessionUidAccess; + private final Supplier exporterSupplier; + + private final DomainParameterXref domainParameterXref; + + TableBasedUpdateHandler( + SqmUpdateStatement sqmDeleteStatement, + DomainParameterXref domainParameterXref, + IdTable idTable, + Function sessionUidAccess, + Supplier 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 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 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> 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 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 consumer) { + consumer.accept( tableReference.getIdentificationVariable(), tableReference ); + } + + private void collectTableReference( + TableReferenceJoin tableReferenceJoin, + BiConsumer consumer) { + collectTableReference( tableReferenceJoin.getJoinedTableReference(), consumer ); + } + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableKeyExpressionCollector.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableKeyExpressionCollector.java new file mode 100644 index 0000000000..a1a7fdf8fc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableKeyExpressionCollector.java @@ -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 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() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedTableBasedDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedDeleteExecutionDelegate.java similarity index 60% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedTableBasedDeleteHandler.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedDeleteExecutionDelegate.java index 6aeb5bb932..ec8e1a89fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedTableBasedDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedDeleteExecutionDelegate.java @@ -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 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() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java new file mode 100644 index 0000000000..87858d9f9f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java @@ -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 sessionUidAccess; + private final Supplier idTableExporterAccess; + private final DomainParameterXref domainParameterXref; + private final TableGroup updatingTableGroup; + private final Predicate suppliedPredicate; + + private final EntityMappingType entityDescriptor; + + private final JdbcParameterBindings jdbcParameterBindings; + + private final Map> assignmentsByTable; + private final SessionFactoryImplementor sessionFactory; + + public UpdateExecutionDelegate( + SqmUpdateStatement sqmUpdate, + MultiTableSqmMutationConverter sqmConverter, + IdTable idTable, + TempTableDdlTransactionHandling ddlTransactionHandling, + BeforeUseAction beforeUseAction, + AfterUseAction afterUseAction, + Function sessionUidAccess, + Supplier idTableExporterAccess, + DomainParameterXref domainParameterXref, + TableGroup updatingTableGroup, + TableReference hierarchyRootTableReference, + Map tableReferenceByAlias, + List assignments, + Predicate suppliedPredicate, + Map> 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 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 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 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> tableKeyColumnVisitationSupplier, + QuerySpec idTableSubQuery, + ExecutionContext executionContext) { + final TableReference updatingTableReference = updatingTableGroup.resolveTableReference( tableExpression ); + + final List 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 + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java index 1035905fce..1c3b312c00 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java @@ -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; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index a2d6d87a3a..ebacb996d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -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 jdbcParametersForSqm = new ArrayList<>(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AssignableSqmPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AssignableSqmPathInterpretation.java index 23a117709b..fd202004f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AssignableSqmPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AssignableSqmPathInterpretation.java @@ -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 extends SqmPathInterpretation { +public interface AssignableSqmPathInterpretation extends SqmPathInterpretation, Assignable { // need to be able to collect assignments per-table, including // SqmParameter -> JdbcParameter mapping diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java index cfb86ccb9e..cbba740f94 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java @@ -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 implements AssignableSqmPathInterp public String toString() { return "BasicValuedPathInterpretation(" + sqmPath.getNavigablePath().getFullPath() + ')'; } + + @Override + public void visitColumnReferences(Consumer columnReferenceConsumer) { + columnReferenceConsumer.accept( columnReference ); + } + + @Override + public List getColumnReferences() { + return Collections.singletonList( columnReference ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java index fea946dbe2..4cdfdcbd74 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java @@ -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 implements AssignableSqmPathI return new EmbeddableValuedPathInterpretation<>( mapping.toSqlExpression( tableGroup, - converter.getCurrentClauseStack().getCurrent(), converter, converter @@ -114,4 +117,30 @@ public class EmbeddableValuedPathInterpretation implements AssignableSqmPathI public String toString() { return "EmbeddableValuedPathInterpretation(" + sqmPath.getNavigablePath().getFullPath() + ')'; } + + @Override + public void visitColumnReferences(Consumer 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 getColumnReferences() { + final List results = new ArrayList<>(); + visitColumnReferences( results::add ); + return results; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertSelectTranslator.java index bbde3184ea..59de034c76 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertSelectTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertSelectTranslator.java @@ -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); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java index 8a968e4252..90ebe97155 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java new file mode 100644 index 0000000000..6069d353a6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java @@ -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 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); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java index f3ddf282de..94633097f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstToJdbcOperationConverter.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java similarity index 86% rename from hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstToJdbcOperationConverter.java rename to hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 25261ba29a..6869d66b1f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstToJdbcOperationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -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 affectedTableNames = new HashSet<>(); - protected AbstractSqlAstToJdbcOperationConverter(SessionFactoryImplementor sessionFactory) { + protected AbstractSqlAstTranslator(SessionFactoryImplementor sessionFactory) { super( sessionFactory ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java index ca37aaf9c4..d3ffeda180 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java @@ -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 ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertSelectTranslator.java index 87a7ef3b38..e18fc4c8f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertSelectTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertSelectTranslator.java @@ -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 ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java index c4bf3a7e19..4ebdc549f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java @@ -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) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java index 3871de29a6..1fc2446c82 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java @@ -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 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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java index 0c2fad94da..cf6b06d8f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java @@ -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 columnReferenceConsumer) { + columnReferenceConsumer.accept( this ); + } + + @Override + public List getColumnReferences() { + return Collections.singletonList( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java index bc287f8a19..d46fcddb01 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java @@ -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; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java index 1f686e5ee2..3d291f0104 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java @@ -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; /** diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java index d60c025cf3..d59d4668b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableAliasResolver.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableAliasResolver.java new file mode 100644 index 0000000000..d91c7703a5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableAliasResolver.java @@ -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); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java index 733882ab2d..933c1fed39 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/Assignable.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/Assignable.java new file mode 100644 index 0000000000..103a54a76b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/Assignable.java @@ -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 getColumnReferences(); + + void visitColumnReferences(Consumer columnReferenceConsumer); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/Assignment.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/Assignment.java index 6846e061d7..83e2f0e0c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/Assignment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/Assignment.java @@ -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() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/HqlUpdateExecutionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/HqlUpdateExecutionTests.java index 5d9a9421ed..8e54ef88aa 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/HqlUpdateExecutionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/HqlUpdateExecutionTests.java @@ -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() + ); + } }