HHH-13715 - working support for "multi-table" HQL/Criteria UPDATE and DELETE queries

complete support for "local temp table"-based strategy
This commit is contained in:
Steve Ebersole 2019-11-18 12:20:48 -06:00
parent 0dae701c93
commit b04599cbe5
45 changed files with 2065 additions and 679 deletions

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -1227,13 +1227,7 @@ public abstract class AbstractEntityPersister
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationContext creationContext) {
final SqlAliasBase sqlAliasBase;
if ( aliasBaseGenerator == null ) {
sqlAliasBase = null;
}
else {
sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
}
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final TableGroupBuilder builder = TableGroupBuilder.builder(
navigablePath,

View File

@ -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;

View File

@ -90,7 +90,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan {
executionContext.getSession()
);
return jdbcServices.getJdbcDeleteExecutor().execute(
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcDelete,
jdbcParameterBindings,
sql -> executionContext.getSession()

View File

@ -90,7 +90,7 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan {
executionContext.getSession()
);
return jdbcServices.getJdbcUpdateExecutor().execute(
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcUpdate,
jdbcParameterBindings,
sql -> executionContext.getSession()

View File

@ -102,6 +102,20 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
private static final Logger LOGGER = QueryLogger.subLogger( "sqm.ast" );
private static final boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();
public static void logTree(SqmQuerySpec sqmQuerySpec, String header) {
if ( ! DEBUG_ENABLED ) {
return;
}
final SqmTreePrinter treePrinter = new SqmTreePrinter();
treePrinter.visitQuerySpec( sqmQuerySpec );
final String title = header != null ? header : "SqmQuerySpec Tree";
LOGGER.debugf( "%s :\n%s", title, treePrinter.buffer.toString() );
}
public static void logTree(SqmStatement sqmStatement) {
if ( ! DEBUG_ENABLED ) {
return;
@ -122,7 +136,7 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
printer.visitInsertSelectStatement( (SqmInsertSelectStatement) sqmStatement );
}
LOGGER.debugf( "Semantic Query (SQM) Tree :\n%s", printer.buffer.toString() );
LOGGER.debugf( "SqmStatement Tree :\n%s", printer.buffer.toString() );
}
private final StringBuffer buffer = new StringBuffer();

View File

@ -6,28 +6,22 @@
*/
package org.hibernate.query.sqm.mutation.internal;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.hql.spi.SqmCreationOptions;
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.internal.SqmQuerySpecCreationProcessingStateStandardImpl;
import org.hibernate.query.sqm.mutation.internal.idtable.IdTableSessionUidColumn;
import org.hibernate.query.sqm.spi.SqmCreationContext;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.type.StringType;
import org.jboss.logging.Logger;
@ -41,41 +35,30 @@ import org.jboss.logging.Logger;
public class SqmIdSelectGenerator {
private static final Logger log = Logger.getLogger( SqmIdSelectGenerator.class );
/**
* @asciidoc
*
* Generates a query-spec for selecting all ids matching the restriction defined as part
* of the user's update/delete query. This query-spec is generally used:
*
* * to select all the matching ids via JDBC - see {@link SqmMutationStrategyHelper#selectMatchingIds}
* * as a sub-query restriction to insert rows into an "id table"
*/
public static SqmQuerySpec generateSqmEntityIdSelect(
SqmDeleteOrUpdateStatement sqmStatement,
ExecutionContext executionContext,
SessionFactoryImplementor sessionFactory) {
final SqmIdSelectGenerator generator = new SqmIdSelectGenerator( sqmStatement, executionContext, sessionFactory );
return generator.process();
}
SqmCreationContext sqmCreationContext) {
final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel();
private final SqmDeleteOrUpdateStatement sourceSqmStatement;
private final ExecutionContext executionContext;
private final SqmCreationContext creationContext;
private final EntityDomainType entityType;
public SqmIdSelectGenerator(
SqmDeleteOrUpdateStatement sourceSqmStatement,
ExecutionContext executionContext,
SqmCreationContext creationContext) {
this.sourceSqmStatement = sourceSqmStatement;
this.executionContext = executionContext;
this.creationContext = creationContext;
final String targetEntityName = sourceSqmStatement.getTarget().getEntityName();
this.entityType = creationContext.getJpaMetamodel().entity( targetEntityName );
}
private SqmQuerySpec process() {
final SqmQuerySpec sqmQuerySpec = new SqmQuerySpec( creationContext.getNodeBuilder() );
log.tracef( "Starting generation of entity-id SQM selection - %s", entityDomainType.getHibernateEntityName() );
final SqmQuerySpec sqmQuerySpec = new SqmQuerySpec( sqmCreationContext.getNodeBuilder() );
final Stack<SqmCreationProcessingState> processingStateStack = new StandardStack<>();
final SqmCreationState creationState = new SqmCreationState() {
@Override
public SqmCreationContext getCreationContext() {
return creationContext;
return sqmCreationContext;
}
@Override
@ -89,8 +72,9 @@ public class SqmIdSelectGenerator {
}
};
// temporary - used just for creating processingState
final SqmSelectStatement sqmSelectStatement = new SqmSelectStatement( creationContext.getNodeBuilder() );
final SqmSelectStatement sqmSelectStatement = new SqmSelectStatement( sqmCreationContext.getNodeBuilder() );
//noinspection unchecked
sqmSelectStatement.setQuerySpec( sqmQuerySpec );
@ -105,27 +89,31 @@ public class SqmIdSelectGenerator {
final SqmFromClause sqmFromClause = new SqmFromClause();
sqmQuerySpec.setFromClause( sqmFromClause );
//noinspection unchecked
final SqmRoot<?> sqmRoot = new SqmRoot( entityType, null, sourceSqmStatement.nodeBuilder() );
// final SqmRoot<?> sqmRoot = new SqmRoot( entityDomainType, null, sqmCreationContext.getNodeBuilder() );
final SqmRoot<?> sqmRoot = sqmStatement.getTarget();
log.debugf( "Using SqmRoot [%s] as root for entity id-select", sqmRoot );
sqmFromClause.addRoot( sqmRoot );
final SqmSelectClause sqmSelectClause = new SqmSelectClause( true, creationContext.getNodeBuilder() );
final SqmSelectClause sqmSelectClause = new SqmSelectClause( true, sqmCreationContext.getNodeBuilder() );
sqmQuerySpec.setSelectClause( sqmSelectClause );
applySelections( sqmQuerySpec, sqmRoot, processingState );
if ( sourceSqmStatement.getWhereClause() != null ) {
sqmQuerySpec.applyPredicate( sourceSqmStatement.getWhereClause().getPredicate() );
if ( sqmStatement.getWhereClause() != null ) {
sqmQuerySpec.applyPredicate( sqmStatement.getWhereClause().getPredicate() );
}
return sqmQuerySpec;
}
private void applySelections(
private static void applySelections(
SqmQuerySpec sqmQuerySpec,
SqmRoot<?> sqmRoot,
SqmCreationProcessingState processingState) {
//noinspection unchecked
final SqmPath idPath = entityType.getIdentifierDescriptor().createSqmPath( sqmRoot, processingState.getCreationState() );
final SqmPath idPath = sqmRoot.getModel().getIdentifierDescriptor().createSqmPath( sqmRoot, processingState.getCreationState() );
//noinspection unchecked
sqmQuerySpec.getSelectClause().add(

View File

@ -245,7 +245,6 @@ public class SqmMutationStrategyHelper {
final SqmQuerySpec sqmIdSelectQuerySpec = SqmIdSelectGenerator.generateSqmEntityIdSelect(
sqmDeleteStatement,
executionContext,
factory
);

View File

@ -71,7 +71,12 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
@Override
public int execute(ExecutionContext executionContext) {
final List<Object> ids = selectMatchingIds( executionContext );
final List<Object> ids = SqmMutationStrategyHelper.selectMatchingIds(
getSqmDeleteOrUpdateStatement(),
getDomainParameterXref(),
executionContext
);
if ( ids == null || ids.isEmpty() ) {
return 0;
}
@ -133,14 +138,6 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
return ids.size();
}
private List<Object> selectMatchingIds(ExecutionContext executionContext) {
return SqmMutationStrategyHelper.selectMatchingIds(
getSqmDeleteOrUpdateStatement(),
getDomainParameterXref(),
executionContext
);
}
protected void executeDelete(
QuerySpec cteDefinition,
String targetTable,
@ -158,7 +155,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
executionContext
);
final SessionFactoryImplementor sessionFactory = getCreationContext().getSessionFactory();
final SessionFactoryImplementor sessionFactory = getSessionFactory();
final JdbcDelete jdbcDelete = sqlAstTranslatorFactory.buildDeleteTranslator( sessionFactory )
.translate( cteStatement );
@ -168,7 +165,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
.getJdbcCoordinator()
.getLogicalConnection();
sessionFactory.getJdbcServices().getJdbcDeleteExecutor().execute(
sessionFactory.getJdbcServices().getJdbcMutationExecutor().execute(
jdbcDelete,
jdbcParameterBindings,
sql -> {

View File

@ -16,15 +16,13 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.internal.SqmTreePrinter;
import org.hibernate.query.sqm.mutation.internal.SqmIdSelectGenerator;
import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler;
import org.hibernate.query.sqm.mutation.spi.Handler;
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
import org.hibernate.query.sqm.sql.SqmQuerySpecTranslation;
import org.hibernate.query.sqm.sql.SqmSelectTranslator;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
@ -32,6 +30,7 @@ import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstInsertSelectTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@ -42,6 +41,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcInsert;
@ -52,162 +52,39 @@ import org.hibernate.type.UUIDCharType;
import org.jboss.logging.Logger;
/**
* Support for {@link Handler} implementations
*
* @author Steve Ebersole
*/
public abstract class AbstractTableBasedHandler extends AbstractMutationHandler {
private static final Logger log = Logger.getLogger( AbstractTableBasedHandler.class );
@SuppressWarnings("WeakerAccess")
public final class ExecuteWithIdTableHelper {
private static final Logger log = Logger.getLogger( ExecuteWithIdTableHelper.class );
public static final boolean debugging = log.isDebugEnabled();
private final IdTable idTable;
private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final BeforeUseAction beforeUseAction;
private final AfterUseAction afterUseAction;
private ExecuteWithIdTableHelper() {
}
private final DomainParameterXref domainParameterXref;
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
private final Supplier<IdTableExporter> exporterSupplier;
public AbstractTableBasedHandler(
SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement,
public static int saveMatchingIdsIntoIdTable(
SqmUpdateStatement sqmMutation,
MultiTableSqmMutationConverter sqmConverter,
TableGroup mutatingTableGroup,
Predicate suppliedPredicate,
IdTable idTable,
TempTableDdlTransactionHandling ddlTransactionHandling,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
DomainParameterXref domainParameterXref,
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
Function<SharedSessionContractImplementor,String> sessionUidAccess,
Supplier<IdTableExporter> exporterSupplier,
HandlerCreationContext creationContext) {
super( sqmDeleteOrUpdateStatement, creationContext );
this.idTable = idTable;
this.ddlTransactionHandling = ddlTransactionHandling;
this.beforeUseAction = beforeUseAction;
this.afterUseAction = afterUseAction;
this.domainParameterXref = domainParameterXref;
this.sessionUidAccess = sessionUidAccess;
this.exporterSupplier = exporterSupplier;
}
public IdTable getIdTable() {
return idTable;
}
public DomainParameterXref getDomainParameterXref() {
return domainParameterXref;
}
public BeforeUseAction getBeforeUseAction() {
return beforeUseAction;
}
public AfterUseAction getAfterUseAction() {
return afterUseAction;
}
public Function<SharedSessionContractImplementor, String> getSessionUidAccess() {
return sessionUidAccess;
}
public Supplier<IdTableExporter> getExporterSupplier() {
return exporterSupplier;
}
@Override
public int execute(ExecutionContext executionContext) {
// In general:
// 1) prepare for use - this is completely a subclass hook
// 2) perform execution
// 3) release after use - again, completely a subclass hook
beforeExecution( executionContext );
try {
return performExecution( executionContext );
}
finally {
afterExecution( executionContext );
}
}
/**
* Allow subclasses a chance to perform any preliminary work they need
* to perform prior to execution
*/
protected void beforeExecution(ExecutionContext executionContext) {
}
/**
* Allow subclasses a chance to perform any clean-up work they need
* to perform after execution
*/
protected void afterExecution(ExecutionContext executionContext) {
}
protected int performExecution(ExecutionContext executionContext) {
performBeforeUseActions( executionContext );
try {
// 1) save the matching ids into the id table
final int affectedRowCount = saveMatchingIdsIntoIdTable( executionContext );
log.debugf( "insert for matching ids resulted in %s rows", affectedRowCount );
// 2) perform the actual individual update or deletes, using
// inclusion in the id-table as restriction
performMutations( executionContext );
return affectedRowCount;
}
finally {
performAfterUseActions( executionContext );
}
}
private void performBeforeUseActions(ExecutionContext executionContext) {
if ( getBeforeUseAction() == BeforeUseAction.CREATE ) {
IdTableHelper.createIdTable( idTable, getExporterSupplier().get(), ddlTransactionHandling, executionContext.getSession() );
}
}
private void performAfterUseActions(ExecutionContext executionContext) {
if ( getAfterUseAction() == AfterUseAction.CLEAN ) {
IdTableHelper.cleanIdTableRows(
idTable,
getExporterSupplier().get(),
sessionUidAccess,
executionContext.getSession()
);
}
else if ( getAfterUseAction() == AfterUseAction.DROP ) {
IdTableHelper.dropIdTable(
idTable,
getExporterSupplier().get(),
ddlTransactionHandling,
executionContext.getSession()
);
}
}
protected int saveMatchingIdsIntoIdTable(ExecutionContext executionContext) {
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final SqmQuerySpec sqmIdSelect = SqmIdSelectGenerator.generateSqmEntityIdSelect(
getSqmDeleteOrUpdateStatement(),
executionContext,
sqmMutation,
factory
);
if ( getIdTable().getSessionUidColumn() != null ) {
if ( idTable.getSessionUidColumn() != null ) {
//noinspection unchecked
sqmIdSelect.getSelectClause().add(
new SqmSelection(
new SqmLiteral(
executionContext.getSession().getSessionIdentifier().toString(),
sessionUidAccess.apply( executionContext.getSession() ),
UUIDCharType.INSTANCE,
executionContext.getSession().getFactory().getNodeBuilder()
),
@ -217,23 +94,20 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
);
}
final SqmTranslatorFactory sqmTranslatorFactory = factory.getQueryEngine().getSqmTranslatorFactory();
final SqmSelectTranslator sqmTranslator = sqmTranslatorFactory.createSelectTranslator(
QueryOptions.NONE,
domainParameterXref,
executionContext.getQueryParameterBindings(),
executionContext.getSession().getLoadQueryInfluencers(),
factory
);
final SqmQuerySpecTranslation sqmIdSelectTranslation = sqmTranslator.translate( sqmIdSelect );
SqmTreePrinter.logTree( sqmIdSelect, "Entity-identifier Selection SqmQuerySpec" );
final InsertSelectStatement insertSelectStatement = new InsertSelectStatement();
final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory );
insertSelectStatement.setTargetTable( idTableReference );
insertSelectStatement.setSourceSelectStatement( sqmIdSelectTranslation.getSqlAst() );
final QuerySpec matchingIdRestrictionQuerySpec = generateTempTableInsertValuesQuerySpec(
sqmConverter,
mutatingTableGroup,
suppliedPredicate,
sqmIdSelect
);
insertSelectStatement.setSourceSelectStatement( matchingIdRestrictionQuerySpec );
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
final IdTableColumn column = idTable.getIdTableColumns().get( i );
@ -248,34 +122,117 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
final SqlAstInsertSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory );
final JdbcInsert jdbcInsert = sqlAstTranslator.translate( insertSelectStatement );
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
SqmUtil.generateJdbcParamsXref(
domainParameterXref,
sqmIdSelectTranslation::getJdbcParamsBySqmParam
),
factory.getDomainModel(),
sqmTranslator.getFromClauseAccess()::findTableGroup,
executionContext.getSession()
);
return jdbcServices.getJdbcInsertExecutor().execute(
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcInsert,
jdbcParameterBindings,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {
},
(integer, preparedStatement) -> {},
executionContext
);
}
private static QuerySpec generateTempTableInsertValuesQuerySpec(
MultiTableSqmMutationConverter sqmConverter,
TableGroup mutatingTableGroup, Predicate suppliedPredicate, SqmQuerySpec sqmIdSelect) {
final QuerySpec matchingIdRestrictionQuerySpec = new QuerySpec( false, 1 );
sqmConverter.visitSelectClause(
sqmIdSelect.getSelectClause(),
matchingIdRestrictionQuerySpec,
columnReference -> {},
(sqmParameter, jdbcParameters) -> {}
);
matchingIdRestrictionQuerySpec.getFromClause().addRoot( mutatingTableGroup );
matchingIdRestrictionQuerySpec.applyPredicate( suppliedPredicate );
return matchingIdRestrictionQuerySpec;
}
public QuerySpec createIdTableSubQuery(ExecutionContext executionContext) {
public static int saveMatchingIdsIntoIdTable(
SqmDeleteOrUpdateStatement sqmMutation,
Predicate predicate,
IdTable idTable,
Function<SharedSessionContractImplementor,String> sessionUidAccess,
DomainParameterXref domainParameterXref,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final SqmQuerySpec sqmIdSelect = SqmIdSelectGenerator.generateSqmEntityIdSelect(
sqmMutation,
factory
);
if ( idTable.getSessionUidColumn() != null ) {
//noinspection unchecked
sqmIdSelect.getSelectClause().add(
new SqmSelection(
new SqmLiteral(
sessionUidAccess.apply( executionContext.getSession() ),
UUIDCharType.INSTANCE,
executionContext.getSession().getFactory().getNodeBuilder()
),
null,
executionContext.getSession().getFactory().getNodeBuilder()
)
);
}
SqmTreePrinter.logTree( sqmIdSelect, "Entity-identifier Selection SqmQuerySpec" );
final SqmTranslatorFactory sqmTranslatorFactory = factory.getQueryEngine().getSqmTranslatorFactory();
final SqmSelectTranslator sqmTranslator = sqmTranslatorFactory.createSelectTranslator(
QueryOptions.NONE,
domainParameterXref,
executionContext.getQueryParameterBindings(),
executionContext.getSession().getLoadQueryInfluencers(),
factory
);
final SqmQuerySpecTranslation sqmIdSelectTranslation = sqmTranslator.translate( sqmIdSelect );
final InsertSelectStatement insertSelectStatement = new InsertSelectStatement();
final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory );
insertSelectStatement.setTargetTable( idTableReference );
final QuerySpec matchingIdRestrictionQuerySpec = sqmIdSelectTranslation.getSqlAst();
insertSelectStatement.setSourceSelectStatement( matchingIdRestrictionQuerySpec );
matchingIdRestrictionQuerySpec.applyPredicate( predicate );
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
final IdTableColumn column = idTable.getIdTableColumns().get( i );
insertSelectStatement.addTargetColumnReferences(
new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory )
);
}
final JdbcServices jdbcServices = factory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final SqlAstInsertSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory );
final JdbcInsert jdbcInsert = sqlAstTranslator.translate( insertSelectStatement );
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcInsert,
jdbcParameterBindings,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {},
executionContext
);
}
public static QuerySpec createIdTableSelectQuerySpec(
IdTable idTable,
Function<SharedSessionContractImplementor,String> sessionUidAccess,
EntityMappingType entityDescriptor,
ExecutionContext executionContext) {
final QuerySpec querySpec = new QuerySpec( false );
final TableReference idTableReference = new TableReference(
@ -286,7 +243,7 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
);
final TableGroup idTableGroup = new StandardTableGroup(
new NavigablePath( idTableReference.getTableExpression() ),
getEntityDescriptor(),
entityDescriptor,
LockMode.NONE,
idTableReference,
Collections.emptyList(),
@ -296,15 +253,16 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
querySpec.getFromClause().addRoot( idTableGroup );
applySelections( querySpec, idTableReference, executionContext );
applyRestrictions( querySpec, idTableReference, executionContext );
applyIdTableSelections( querySpec, idTableReference, idTable, executionContext );
applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext );
return querySpec;
}
private void applySelections(
private static void applyIdTableSelections(
QuerySpec querySpec,
TableReference tableReference,
IdTable idTable,
ExecutionContext executionContext) {
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
final IdTableColumn idTableColumn = idTable.getIdTableColumns().get( i );
@ -326,9 +284,11 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
}
}
private void applyRestrictions(
private static void applyIdTableRestrictions(
QuerySpec querySpec,
TableReference idTableReference,
IdTable idTable,
Function<SharedSessionContractImplementor,String> sessionUidAccess,
ExecutionContext executionContext) {
if ( idTable.getSessionUidColumn() != null ) {
querySpec.applyPredicate(
@ -350,5 +310,44 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
}
}
protected abstract void performMutations(ExecutionContext executionContext);
public static void performBeforeIdTableUseActions(
BeforeUseAction beforeUseAction,
IdTable idTable,
Supplier<IdTableExporter> idTableExporterAccess,
TempTableDdlTransactionHandling ddlTransactionHandling,
ExecutionContext executionContext) {
if ( beforeUseAction == BeforeUseAction.CREATE ) {
IdTableHelper.createIdTable(
idTable,
idTableExporterAccess.get(),
ddlTransactionHandling,
executionContext.getSession()
);
}
}
public static void performAfterIdTableUseActions(
AfterUseAction afterUseAction,
IdTable idTable,
Supplier<IdTableExporter> idTableExporterAccess,
TempTableDdlTransactionHandling ddlTransactionHandling,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
ExecutionContext executionContext) {
if ( afterUseAction == AfterUseAction.CLEAN ) {
IdTableHelper.cleanIdTableRows(
idTable,
idTableExporterAccess.get(),
sessionUidAccess,
executionContext.getSession()
);
}
else if ( afterUseAction == AfterUseAction.DROP ) {
IdTableHelper.dropIdTable(
idTable,
idTableExporterAccess.get(),
ddlTransactionHandling,
executionContext.getSession()
);
}
}
}

View File

@ -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;
}
}

View File

@ -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
);
}

View File

@ -0,0 +1,267 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.LockMode;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl;
import org.hibernate.query.sqm.sql.internal.SqlAstQuerySpecProcessingStateImpl;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
import org.hibernate.query.sqm.tree.update.SqmAssignment;
import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.sql.ast.JoinType;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.Assignable;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.results.spi.DomainResultCreationState;
/**
* Specialized BaseSqmToSqlAstConverter implementation used during conversion
* of an SQM mutation query tree representing into the various SQL AST trees
* needed to perform that operation.
*
* @see #visitSetClause(SqmSetClause, Consumer, BiConsumer)
* @see #visitWhereClause(SqmWhereClause, Consumer, BiConsumer)
* @see #visitSelectClause(SqmSelectClause, QuerySpec, Consumer, BiConsumer)
*
* @author Steve Ebersole
*/
public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter implements DomainResultCreationState {
private final EntityMappingType mutatingEntityDescriptor;
private final TableGroup mutatingTableGroup;
private BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer;
public MultiTableSqmMutationConverter(
EntityMappingType mutatingEntityDescriptor,
DomainParameterXref domainParameterXref,
QueryOptions queryOptions,
QueryParameterBindings domainParameterBindings,
SqlAstCreationContext creationContext) {
super( creationContext, queryOptions, domainParameterXref, domainParameterBindings );
this.mutatingEntityDescriptor = mutatingEntityDescriptor;
final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl(
null,
this,
getCurrentClauseStack()::getCurrent
);
getProcessingStateStack().push( rootProcessingState );
final NavigablePath navigablePath = new NavigablePath( mutatingEntityDescriptor.getEntityName() );
this.mutatingTableGroup = mutatingEntityDescriptor.createRootTableGroup(
navigablePath,
null,
JoinType.LEFT,
LockMode.PESSIMISTIC_WRITE,
getSqlAliasBaseGenerator(),
getSqlExpressionResolver(),
() -> predicate -> {
},
creationContext.getSessionFactory()
);
// because this is a multi-table update, here we expect multiple TableReferences
assert !mutatingTableGroup.getTableReferenceJoins().isEmpty();
getFromClauseAccess().registerTableGroup( navigablePath, mutatingTableGroup );
}
@SuppressWarnings("unused")
public EntityMappingType getMutatingEntityDescriptor() {
return mutatingEntityDescriptor;
}
public TableGroup getMutatingTableGroup() {
return mutatingTableGroup;
}
@Override
public Stack<SqlAstProcessingState> getProcessingStateStack() {
return super.getProcessingStateStack();
}
/**
* Specialized hook to visit the assignments defined by the update SQM allow
* "listening" for each SQL assignment.
*/
public void visitSetClause(
SqmSetClause setClause,
Consumer<Assignment> assignmentConsumer,
BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer) {
this.parameterResolutionConsumer = parameterResolutionConsumer;
for ( SqmAssignment assignment : setClause.getAssignments() ) {
visitAssignment( assignment, assignmentConsumer );
}
}
public List<Assignment> visitSetClause(SqmSetClause setClause) {
throw new UnsupportedOperationException();
}
private void visitAssignment(
SqmAssignment sqmAssignment,
Consumer<Assignment> assignmentConsumer) {
final Assignable assignable = (Assignable) sqmAssignment.getTargetPath().accept( this );
final Expression value = (Expression) sqmAssignment.getValue().accept( this );
assignmentConsumer.accept( new Assignment( assignable, value ) );
}
@Override
public Assignment visitAssignment(SqmAssignment sqmAssignment) {
return new Assignment(
(Assignable) sqmAssignment.getTargetPath().accept( this ),
(Expression) sqmAssignment.getValue().accept( this )
);
}
public Predicate visitWhereClause(
SqmWhereClause sqmWhereClause,
Consumer<ColumnReference> restrictionColumnReferenceConsumer,
BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer) {
this.parameterResolutionConsumer = parameterResolutionConsumer;
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
return null;
}
final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent();
final SqlAstProcessingStateImpl restrictionProcessingState = new SqlAstProcessingStateImpl(
rootProcessingState,
this,
getCurrentClauseStack()::getCurrent
) {
@Override
public SqlExpressionResolver getSqlExpressionResolver() {
return this;
}
@Override
public Expression resolveSqlExpression(
String key, Function<SqlAstProcessingState, Expression> creator) {
final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression(
key,
creator
);
if ( expression instanceof ColumnReference ) {
restrictionColumnReferenceConsumer.accept( (ColumnReference) expression );
}
return expression;
}
};
getProcessingStateStack().push( restrictionProcessingState );
try {
return (Predicate) sqmWhereClause.getPredicate().accept( this );
}
finally {
getProcessingStateStack().pop();
this.parameterResolutionConsumer = null;
}
}
@Override
public Predicate visitWhereClause(SqmWhereClause whereClause) {
return (Predicate) super.visitWhereClause( whereClause );
}
@Override
protected Expression consumeSqmParameter(SqmParameter sqmParameter) {
assert parameterResolutionConsumer != null;
final Expression expression = super.consumeSqmParameter( sqmParameter );
final List<JdbcParameter> jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter );
parameterResolutionConsumer.accept( sqmParameter, jdbcParameters );
return expression;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public SqlAstCreationState getSqlAstCreationState() {
return this;
}
public void visitSelectClause(
SqmSelectClause sqmSelectClause,
QuerySpec sqlQuerySpec,
Consumer<ColumnReference> columnReferenceConsumer,
BiConsumer<SqmParameter, List<JdbcParameter>> parameterResolutionConsumer) {
assert sqmSelectClause != null;
this.parameterResolutionConsumer = parameterResolutionConsumer;
final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent();
final SqlAstProcessingStateImpl processingState = new SqlAstQuerySpecProcessingStateImpl(
sqlQuerySpec,
rootProcessingState,
this,
getCurrentClauseStack()::getCurrent
) {
@Override
public SqlExpressionResolver getSqlExpressionResolver() {
return this;
}
@Override
public Expression resolveSqlExpression(
String key, Function<SqlAstProcessingState, Expression> creator) {
final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression(
key,
creator
);
if ( expression instanceof ColumnReference ) {
columnReferenceConsumer.accept( (ColumnReference) expression );
}
return expression;
}
};
getProcessingStateStack().push( processingState );
try {
for ( int i = 0; i < sqmSelectClause.getSelectionItems().size(); i++ ) {
final DomainResultProducer domainResultProducer = (DomainResultProducer) sqmSelectClause.getSelectionItems()
.get( i )
.accept( this );
domainResultProducer.applySqlSelections( this );
}
}
finally {
getProcessingStateStack().pop();
this.parameterResolutionConsumer = null;
}
}
}

View File

@ -0,0 +1,553 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.LockMode;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter;
import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
import org.hibernate.sql.ast.JoinType;
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcDelete;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
private static final Logger log = Logger.getLogger( RestrictedDeleteExecutionDelegate.class );
private final EntityMappingType entityDescriptor;
private final IdTable idTable;
private final SqmDeleteStatement sqmDelete;
private final DomainParameterXref domainParameterXref;
private final SessionFactoryImplementor sessionFactory;
private final BeforeUseAction beforeUseAction;
private final AfterUseAction afterUseAction;
private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final Supplier<IdTableExporter> idTableExporterAccess;
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
public RestrictedDeleteExecutionDelegate(
EntityMappingType entityDescriptor,
IdTable idTable,
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling,
Supplier<IdTableExporter> idTableExporterAccess,
Function<SharedSessionContractImplementor,String> sessionUidAccess,
SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
this.idTable = idTable;
this.sqmDelete = sqmDelete;
this.domainParameterXref = domainParameterXref;
this.beforeUseAction = beforeUseAction;
this.afterUseAction = afterUseAction;
this.ddlTransactionHandling = ddlTransactionHandling;
this.idTableExporterAccess = idTableExporterAccess;
this.sessionUidAccess = sessionUidAccess;
this.sessionFactory = sessionFactory;
}
@Override
public int execute(ExecutionContext executionContext) {
final Converter converter = new Converter(
sessionFactory,
executionContext.getQueryOptions(),
domainParameterXref,
executionContext.getQueryParameterBindings()
);
final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl(
null,
converter,
converter.getCurrentClauseStack()::getCurrent
) {
@Override
public Expression resolveSqlExpression(
String key, Function<SqlAstProcessingState, Expression> creator) {
return super.resolveSqlExpression( key, creator );
}
};
converter.getProcessingStateStack().push( rootProcessingState );
final EntityPersister entityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( sqmDelete.getTarget().getEntityName() );
final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName();
final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getEntityName() );
final TableGroup deletingTableGroup = entityDescriptor.createRootTableGroup(
navigablePath,
null,
JoinType.LEFT,
LockMode.PESSIMISTIC_WRITE,
converter.getSqlAliasBaseGenerator(),
converter.getSqlExpressionResolver(),
() -> predicate -> {},
sessionFactory
);
// because this is a multi-table update, here we expect multiple TableReferences
assert !deletingTableGroup.getTableReferenceJoins().isEmpty();
// Register this TableGroup with the "registry" in preparation for
converter.getFromClauseAccess().registerTableGroup( navigablePath, deletingTableGroup );
final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( hierarchyRootTableName );
assert hierarchyRootTableReference != null;
// Use the converter to interpret the where-clause. We do this for 2 reasons:
// 1) the resolved Predicate is ultimately the base for applying restriction to the deletes
// 2) we also inspect each ColumnReference that is part of the where-clause to see which
// table it comes from. if all of the referenced columns (if any at all) are from the root table
// we can perform all of the deletes without using an id-table
final AtomicBoolean needsIdTableWrapper = new AtomicBoolean( false );
final Predicate predicate = converter.visitWhereClause(
sqmDelete.getWhereClause(),
columnReference -> {
if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
needsIdTableWrapper.set( true );
}
}
);
boolean needsIdTable = needsIdTableWrapper.get();
if ( needsIdTable ) {
return executeWithIdTable(
predicate,
deletingTableGroup,
converter.getRestrictionSqmParameterResolutions(),
executionContext
);
}
else {
return executeWithoutIdTable(
predicate,
deletingTableGroup,
converter.getRestrictionSqmParameterResolutions(),
converter.getSqlExpressionResolver(),
executionContext
);
}
}
private int executeWithoutIdTable(
Predicate suppliedPredicate,
TableGroup tableGroup,
Map<SqmParameter, List<JdbcParameter>> restrictionSqmParameterResolutions,
SqlExpressionResolver sqlExpressionResolver,
ExecutionContext executionContext) {
final EntityPersister rootEntityPersister;
final String rootEntityName = entityDescriptor.getEntityPersister().getRootEntityName();
if ( rootEntityName.equals( entityDescriptor.getEntityName() ) ) {
rootEntityPersister = entityDescriptor.getEntityPersister();
}
else {
rootEntityPersister = sessionFactory.getDomainModel().findEntityDescriptor( rootEntityName );
}
final AtomicInteger rows = new AtomicInteger();
final String rootTableName = ( (Joinable) rootEntityPersister ).getTableName();
final TableReference rootTableReference = tableGroup.resolveTableReference( rootTableName );
final QuerySpec matchingIdSubQuerySpec = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec(
tableGroup.getNavigablePath(),
rootTableReference,
suppliedPredicate,
rootEntityPersister,
sqlExpressionResolver,
sessionFactory
);
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
SqmUtil.generateJdbcParamsXref(
domainParameterXref,
() -> restrictionSqmParameterResolutions
),
sessionFactory.getDomainModel(),
navigablePath -> tableGroup,
executionContext.getSession()
);
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnVisitationSupplier) -> {
if ( tableExpression.equals( rootTableName ) ) {
rows.set(
deleteFromRootTableWithoutIdTable(
rootTableReference,
suppliedPredicate,
jdbcParameterBindings,
executionContext
)
);
}
else {
deleteFromNonRootTableWithoutIdTable(
tableGroup.resolveTableReference( tableExpression ),
tableKeyColumnVisitationSupplier,
sqlExpressionResolver,
tableGroup,
matchingIdSubQuerySpec,
jdbcParameterBindings,
executionContext
);
}
}
);
return rows.get();
}
private int deleteFromRootTableWithoutIdTable(
TableReference rootTableReference,
Predicate predicate,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
return executeSqlDelete(
new DeleteStatement( rootTableReference, predicate ),
jdbcParameterBindings,
executionContext
);
}
private void deleteFromNonRootTableWithoutIdTable(
TableReference targetTableReference,
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
SqlExpressionResolver sqlExpressionResolver,
TableGroup rootTableGroup,
QuerySpec matchingIdSubQuerySpec,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
assert targetTableReference != null;
log.trace( "deleteFromNonRootTable - " + targetTableReference.getTableExpression() );
/*
* delete from sub_table
* where sub_id in (
* select root_id from root_table
* where {predicate}
* )
*/
/*
* Create the `sub_id` reference as the LHS of the in-subquery predicate
*/
final List<ColumnReference> deletingTableColumnRefs = new ArrayList<>();
tableKeyColumnVisitationSupplier.get().accept(
(columnExpression, containingTableExpression, jdbcMapping) -> {
assert targetTableReference.getTableExpression().equals( containingTableExpression );
final Expression expression = sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( targetTableReference, columnExpression ),
sqlAstProcessingState -> new ColumnReference(
rootTableGroup.getPrimaryTableReference(),
columnExpression,
jdbcMapping,
sessionFactory
)
);
deletingTableColumnRefs.add( (ColumnReference) expression );
}
);
final Expression deletingTableColumnRefsExpression;
if ( deletingTableColumnRefs.size() == 1 ) {
deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 );
}
else {
deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() );
}
final InSubQueryPredicate idMatchPredicate = new InSubQueryPredicate(
deletingTableColumnRefsExpression,
matchingIdSubQuerySpec,
false
);
final DeleteStatement sqlAstDelete = new DeleteStatement( targetTableReference, idMatchPredicate );
final int rows = executeSqlDelete(
sqlAstDelete,
jdbcParameterBindings,
executionContext
);
log.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows );
}
private static int executeSqlDelete(
DeleteStatement sqlAst,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final JdbcServices jdbcServices = factory.getJdbcServices();
final SqlAstDeleteTranslator sqlAstTranslator = jdbcServices.getJdbcEnvironment()
.getSqlAstTranslatorFactory()
.buildDeleteTranslator( factory );
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( sqlAst );
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcDelete,
jdbcParameterBindings,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {},
executionContext
);
}
private int executeWithIdTable(
Predicate predicate,
TableGroup deletingTableGroup,
Map<SqmParameter, List<JdbcParameter>> restrictionSqmParameterResolutions,
ExecutionContext executionContext) {
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
SqmUtil.generateJdbcParamsXref(
domainParameterXref,
() -> restrictionSqmParameterResolutions
),
sessionFactory.getDomainModel(),
navigablePath -> deletingTableGroup,
executionContext.getSession()
);
ExecuteWithIdTableHelper.performBeforeIdTableUseActions(
beforeUseAction,
idTable,
idTableExporterAccess,
ddlTransactionHandling,
executionContext
);
try {
return executeUsingIdTable( predicate, executionContext, jdbcParameterBindings );
}
finally {
ExecuteWithIdTableHelper.performAfterIdTableUseActions(
afterUseAction,
idTable,
idTableExporterAccess,
ddlTransactionHandling,
sessionUidAccess,
executionContext
);
}
}
private int executeUsingIdTable(
Predicate predicate,
ExecutionContext executionContext,
JdbcParameterBindings jdbcParameterBindings) {
final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable(
sqmDelete,
predicate,
idTable,
sessionUidAccess,
domainParameterXref,
jdbcParameterBindings,
executionContext
);
final QuerySpec idTableSubQuery = ExecuteWithIdTableHelper.createIdTableSelectQuerySpec(
idTable,
sessionUidAccess,
entityDescriptor,
executionContext
);
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable(
tableExpression,
tableKeyColumnVisitationSupplier,
idTableSubQuery,
executionContext
)
);
return rows;
}
private void deleteFromTableUsingIdTable(
String tableExpression,
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
QuerySpec idTableSubQuery,
ExecutionContext executionContext) {
log.trace( "deleteFromTableUsingIdTable - " + tableExpression );
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor );
tableKeyColumnVisitationSupplier.get().accept(
(columnExpression, containingTableExpression, jdbcMapping) -> {
assert containingTableExpression.equals( tableExpression );
keyColumnCollector.apply(
new ColumnReference(
(String) null,
columnExpression,
jdbcMapping,
factory
)
);
}
);
final InSubQueryPredicate predicate = new InSubQueryPredicate(
keyColumnCollector.buildKeyExpression(),
idTableSubQuery,
false
);
executeSqlDelete(
new DeleteStatement(
new TableReference( tableExpression, null, true, factory ),
predicate
),
JdbcParameterBindings.NO_BINDINGS,
executionContext
);
}
static class Converter extends BaseSqmToSqlAstConverter {
private Map<SqmParameter,List<JdbcParameter>> restrictionSqmParameterResolutions;
private BiConsumer<SqmParameter,List<JdbcParameter>> sqmParamResolutionConsumer;
Converter(
SqlAstCreationContext creationContext,
QueryOptions queryOptions,
DomainParameterXref domainParameterXref,
QueryParameterBindings domainParameterBindings) {
super( creationContext, queryOptions, domainParameterXref, domainParameterBindings );
}
Map<SqmParameter, List<JdbcParameter>> getRestrictionSqmParameterResolutions() {
return restrictionSqmParameterResolutions;
}
@Override
public Stack<SqlAstProcessingState> getProcessingStateStack() {
return super.getProcessingStateStack();
}
public Predicate visitWhereClause(SqmWhereClause sqmWhereClause, Consumer<ColumnReference> restrictionColumnReferenceConsumer) {
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
return null;
}
sqmParamResolutionConsumer = (sqm, jdbcs) -> {
if ( restrictionSqmParameterResolutions == null ) {
restrictionSqmParameterResolutions = new IdentityHashMap<>();
}
restrictionSqmParameterResolutions.put( sqm, jdbcs );
};
final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent();
final SqlAstProcessingStateImpl restrictionProcessingState = new SqlAstProcessingStateImpl(
rootProcessingState,
this,
getCurrentClauseStack()::getCurrent
) {
@Override
public SqlExpressionResolver getSqlExpressionResolver() {
return this;
}
@Override
public Expression resolveSqlExpression(
String key, Function<SqlAstProcessingState, Expression> creator) {
final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression( key, creator );
if ( expression instanceof ColumnReference ) {
restrictionColumnReferenceConsumer.accept( (ColumnReference) expression );
}
return expression;
}
};
getProcessingStateStack().push( restrictionProcessingState );
try {
return (Predicate) sqmWhereClause.getPredicate().accept( this );
}
finally {
getProcessingStateStack().pop();
}
}
@Override
protected Expression consumeSqmParameter(SqmParameter sqmParameter) {
final Expression expression = super.consumeSqmParameter( sqmParameter );
final List<JdbcParameter> jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter );
sqmParamResolutionConsumer.accept( sqmParameter, jdbcParameters );
return expression;
}
@Override
public SqlExpressionResolver getSqlExpressionResolver() {
return super.getSqlExpressionResolver();
}
}
}

View File

@ -6,33 +6,17 @@
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler;
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcDelete;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.jboss.logging.Logger;
@ -40,31 +24,68 @@ import org.jboss.logging.Logger;
* @author Steve Ebersole
*/
public class TableBasedDeleteHandler
extends AbstractTableBasedHandler
extends AbstractMutationHandler
implements DeleteHandler {
private static final Logger log = Logger.getLogger( TableBasedDeleteHandler.class );
public interface ExecutionDelegate {
int execute(ExecutionContext executionContext);
}
private final IdTable idTable;
private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final BeforeUseAction beforeUseAction;
private final AfterUseAction afterUseAction;
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
private final Supplier<IdTableExporter> exporterSupplier;
private final DomainParameterXref domainParameterXref;
public TableBasedDeleteHandler(
SqmDeleteStatement sqmDeleteStatement,
IdTable idTable,
DomainParameterXref domainParameterXref, IdTable idTable,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
Supplier<IdTableExporter> exporterSupplier,
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling transactionality,
DomainParameterXref domainParameterXref,
TempTableDdlTransactionHandling ddlTransactionHandling,
HandlerCreationContext creationContext) {
super(
sqmDeleteStatement,
super( sqmDeleteStatement, creationContext );
this.idTable = idTable;
this.ddlTransactionHandling = ddlTransactionHandling;
this.beforeUseAction = beforeUseAction;
this.afterUseAction = afterUseAction;
this.domainParameterXref = domainParameterXref;
this.sessionUidAccess = sessionUidAccess;
this.exporterSupplier = exporterSupplier;
}
@Override
public int execute(ExecutionContext executionContext) {
log.tracef( "Starting multi-table delete execution - %s", getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() );
return resolveDelegate( executionContext ).execute( executionContext );
}
private ExecutionDelegate resolveDelegate(ExecutionContext executionContext) {
if ( getSqmDeleteOrUpdateStatement().getWhereClause() == null
|| getSqmDeleteOrUpdateStatement().getWhereClause().getPredicate() == null ) {
return new UnrestrictedDeleteExecutionDelegate( getEntityDescriptor() );
}
return new RestrictedDeleteExecutionDelegate(
getEntityDescriptor(),
idTable,
transactionality,
getSqmDeleteOrUpdateStatement(),
domainParameterXref,
beforeUseAction,
afterUseAction,
session -> session.getSessionIdentifier().toString(),
ddlTransactionHandling,
exporterSupplier,
creationContext
sessionUidAccess,
getSessionFactory()
);
}
@ -72,110 +93,4 @@ public class TableBasedDeleteHandler
public SqmDeleteStatement getSqmDeleteOrUpdateStatement() {
return (SqmDeleteStatement) super.getSqmDeleteOrUpdateStatement();
}
@Override
protected void performMutations(ExecutionContext executionContext) {
log.trace( "performMutations - " + getEntityDescriptor().getEntityName() );
// create the selection of "matching ids" from the id-table. this is used as the subquery in
// used to restrict the deletions from each table
final QuerySpec idTableSelectSubQuerySpec = createIdTableSubQuery( executionContext );
getEntityDescriptor().visitConstraintOrderedTables(
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
deleteFrom( tableExpression, tableKeyColumnsVisitationSupplier, idTableSelectSubQuerySpec, executionContext );
}
);
}
private static class TableKeyExpressionCollector {
private final EntityMappingType entityMappingType;
public TableKeyExpressionCollector(EntityMappingType entityMappingType) {
this.entityMappingType = entityMappingType;
}
Expression firstColumnExpression;
List<Expression> collectedColumnExpressions;
void apply(ColumnReference columnReference) {
if ( firstColumnExpression == null ) {
firstColumnExpression = columnReference;
}
else if ( collectedColumnExpressions == null ) {
collectedColumnExpressions = new ArrayList<>();
collectedColumnExpressions.add( firstColumnExpression );
collectedColumnExpressions.add( columnReference );
}
else {
collectedColumnExpressions.add( columnReference );
}
}
Expression buildKeyExpression() {
if ( collectedColumnExpressions == null ) {
return firstColumnExpression;
}
return new SqlTuple( collectedColumnExpressions, entityMappingType.getIdentifierMapping() );
}
}
private void deleteFrom(
String tableExpression,
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
QuerySpec idTableSelectSubQuery,
ExecutionContext executionContext) {
log.trace( "deleteFrom - " + tableExpression );
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() );
tableKeyColumnVisitationSupplier.get().accept(
(columnExpression, containingTableExpression, jdbcMapping) -> {
assert containingTableExpression.equals( tableExpression );
keyColumnCollector.apply(
new ColumnReference(
(String) null,
columnExpression,
jdbcMapping,
factory
)
);
}
);
final InSubQueryPredicate predicate = new InSubQueryPredicate(
keyColumnCollector.buildKeyExpression(),
idTableSelectSubQuery,
false
);
final DeleteStatement deleteStatement = new DeleteStatement(
new TableReference( tableExpression, null, true, factory ),
predicate
);
final JdbcServices jdbcServices = factory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory );
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement );
final int rows = jdbcServices.getJdbcDeleteExecutor().execute(
jdbcDelete,
JdbcParameterBindings.NO_BINDINGS,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {
},
executionContext
);
log.debugf( "delete-from `%s` : %s rows", tableExpression, rows );
}
}

View File

@ -0,0 +1,238 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.LockMode;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.spi.DomainMetamodel;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler;
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.JoinType;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class TableBasedUpdateHandler
extends AbstractMutationHandler
implements UpdateHandler {
private static final Logger log = Logger.getLogger( TableBasedUpdateHandler.class );
public interface ExecutionDelegate {
int execute(ExecutionContext executionContext);
}
private final IdTable idTable;
private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final BeforeUseAction beforeUseAction;
private final AfterUseAction afterUseAction;
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
private final Supplier<IdTableExporter> exporterSupplier;
private final DomainParameterXref domainParameterXref;
TableBasedUpdateHandler(
SqmUpdateStatement sqmDeleteStatement,
DomainParameterXref domainParameterXref,
IdTable idTable,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
Supplier<IdTableExporter> exporterSupplier,
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling,
HandlerCreationContext creationContext) {
super( sqmDeleteStatement, creationContext );
this.idTable = idTable;
this.exporterSupplier = exporterSupplier;
this.beforeUseAction = beforeUseAction;
this.afterUseAction = afterUseAction;
this.ddlTransactionHandling = ddlTransactionHandling;
this.sessionUidAccess = sessionUidAccess;
this.domainParameterXref = domainParameterXref;
}
protected SqmUpdateStatement getSqmUpdate() {
return getSqmDeleteOrUpdateStatement();
}
@Override
public SqmUpdateStatement getSqmDeleteOrUpdateStatement() {
return (SqmUpdateStatement) super.getSqmDeleteOrUpdateStatement();
}
@Override
public int execute(ExecutionContext executionContext) {
log.tracef( "Starting multi-table update execution - %s", getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() );
return resolveDelegate( executionContext ).execute( executionContext );
}
private ExecutionDelegate resolveDelegate(ExecutionContext executionContext) {
final SessionFactoryImplementor sessionFactory = getSessionFactory();
final DomainMetamodel domainModel = sessionFactory.getDomainModel();
final EntityPersister entityDescriptor = domainModel.getEntityDescriptor( getSqmDeleteOrUpdateStatement().getTarget().getEntityName() );
final String rootEntityName = entityDescriptor.getRootEntityName();
final EntityPersister rootEntityDescriptor = domainModel.getEntityDescriptor( rootEntityName );
final String hierarchyRootTableName = ( (Joinable) rootEntityDescriptor ).getTableName();
final MultiTableSqmMutationConverter converterDelegate = new MultiTableSqmMutationConverter(
entityDescriptor,
domainParameterXref,
executionContext.getQueryOptions(),
executionContext.getQueryParameterBindings(),
sessionFactory
);
final Stack<SqlAstProcessingState> converterProcessingStateStack = converterDelegate.getProcessingStateStack();
final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl(
null,
converterDelegate,
converterDelegate.getCurrentClauseStack()::getCurrent
);
converterProcessingStateStack.push( rootProcessingState );
final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getEntityName() );
final TableGroup updatingTableGroup = entityDescriptor.createRootTableGroup(
navigablePath,
null,
JoinType.LEFT,
LockMode.PESSIMISTIC_WRITE,
converterDelegate.getSqlAliasBaseGenerator(),
converterDelegate.getSqlExpressionResolver(),
() -> predicate -> {},
sessionFactory
);
// because this is a multi-table update, here we expect multiple TableReferences
assert !updatingTableGroup.getTableReferenceJoins().isEmpty();
converterDelegate.getFromClauseAccess().registerTableGroup( navigablePath, updatingTableGroup );
final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( hierarchyRootTableName );
assert hierarchyRootTableReference != null;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// cross-reference the TableReference by alias. The TableGroup already
// cross-references it by name, bu the ColumnReference only has the alias
final Map<String, TableReference> tableReferenceByAlias = new HashMap<>( updatingTableGroup.getTableReferenceJoins().size() + 1 );
collectTableReference( updatingTableGroup.getPrimaryTableReference(), tableReferenceByAlias::put );
for ( int i = 0; i < updatingTableGroup.getTableReferenceJoins().size(); i++ ) {
collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put );
}
final Map<SqmParameter,List<JdbcParameter>> parameterResolutions;
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
parameterResolutions = Collections.emptyMap();
}
else {
parameterResolutions = new IdentityHashMap<>();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// visit the set-clause using our special converter, collecting
// information about the assignments
final List<Assignment> assignments = new ArrayList<>();
converterDelegate.visitSetClause(
getSqmDeleteOrUpdateStatement().getSetClause(),
assignments::add,
parameterResolutions::put
);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// visit the where-clause using our special converter, collecting information
// about the restrictions
final Predicate predicate;
final SqmWhereClause whereClause = getSqmUpdate().getWhereClause();
if ( whereClause == null || whereClause.getPredicate() == null ) {
predicate = null;
}
else {
predicate = converterDelegate.visitWhereClause(
whereClause,
columnReference -> {},
parameterResolutions::put
);
assert predicate != null;
}
return new UpdateExecutionDelegate(
getSqmUpdate(),
converterDelegate,
idTable,
ddlTransactionHandling,
beforeUseAction,
afterUseAction,
sessionUidAccess,
exporterSupplier,
domainParameterXref,
updatingTableGroup,
hierarchyRootTableReference,
tableReferenceByAlias,
assignments,
predicate,
parameterResolutions,
executionContext
);
}
private void collectTableReference(
TableReference tableReference,
BiConsumer<String, TableReference> consumer) {
consumer.accept( tableReference.getIdentificationVariable(), tableReference );
}
private void collectTableReference(
TableReferenceJoin tableReferenceJoin,
BiConsumer<String, TableReference> consumer) {
collectTableReference( tableReferenceJoin.getJoinedTableReference(), consumer );
}
}

View File

@ -0,0 +1,51 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
/**
* @author Steve Ebersole
*/
class TableKeyExpressionCollector {
private final EntityMappingType entityMappingType;
TableKeyExpressionCollector(EntityMappingType entityMappingType) {
this.entityMappingType = entityMappingType;
}
Expression firstColumnExpression;
List<Expression> collectedColumnExpressions;
void apply(ColumnReference columnReference) {
if ( firstColumnExpression == null ) {
firstColumnExpression = columnReference;
}
else if ( collectedColumnExpressions == null ) {
collectedColumnExpressions = new ArrayList<>();
collectedColumnExpressions.add( firstColumnExpression );
collectedColumnExpressions.add( columnReference );
}
else {
collectedColumnExpressions.add( columnReference );
}
}
Expression buildKeyExpression() {
if ( collectedColumnExpressions == null ) {
return firstColumnExpression;
}
return new SqlTuple( collectedColumnExpressions, entityMappingType.getIdentifierMapping() );
}
}

View File

@ -7,17 +7,11 @@
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
@ -29,39 +23,28 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings;
/**
* @author Steve Ebersole
*/
public class UnrestrictedTableBasedDeleteHandler extends TableBasedDeleteHandler implements DeleteHandler {
public UnrestrictedTableBasedDeleteHandler(
SqmDeleteStatement sqmDeleteStatement,
IdTable idTable,
TempTableDdlTransactionHandling ddlTransactionHandling,
DomainParameterXref domainParameterXref,
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
HandlerCreationContext creationContext) {
super(
sqmDeleteStatement,
idTable,
() -> null,
beforeUseAction,
afterUseAction,
ddlTransactionHandling,
domainParameterXref,
creationContext
);
public class UnrestrictedDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
private final EntityMappingType entityDescriptor;
public UnrestrictedDeleteExecutionDelegate(EntityMappingType entityDescriptor) {
this.entityDescriptor = entityDescriptor;
}
@Override
public int execute(ExecutionContext executionContext) {
final AtomicInteger rows = new AtomicInteger();
// NOTE : we want the number of rows returned from this method to be the number of rows deleted
// from the root table of the entity hierarchy, which happens to be the last table we
// will visit
final AtomicInteger result = new AtomicInteger();
getEntityDescriptor().visitConstraintOrderedTables(
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
rows.set( deleteFrom( tableExpression, executionContext ) );
final int rows = deleteFrom( tableExpression, executionContext );
result.set( rows );
}
);
return rows.get();
return result.get();
}
private int deleteFrom(
@ -81,7 +64,7 @@ public class UnrestrictedTableBasedDeleteHandler extends TableBasedDeleteHandler
final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory );
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement );
return jdbcServices.getJdbcDeleteExecutor().execute(
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcDelete,
JdbcParameterBindings.NO_BINDINGS,
sql -> executionContext.getSession()

View File

@ -0,0 +1,283 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcUpdate;
/**
* @author Steve Ebersole
*/
public class UpdateExecutionDelegate implements TableBasedUpdateHandler.ExecutionDelegate {
private final SqmUpdateStatement sqmUpdate;
private final MultiTableSqmMutationConverter sqmConverter;
private final IdTable idTable;
private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final BeforeUseAction beforeUseAction;
private final AfterUseAction afterUseAction;
private final Function<SharedSessionContractImplementor, String> sessionUidAccess;
private final Supplier<IdTableExporter> idTableExporterAccess;
private final DomainParameterXref domainParameterXref;
private final TableGroup updatingTableGroup;
private final Predicate suppliedPredicate;
private final EntityMappingType entityDescriptor;
private final JdbcParameterBindings jdbcParameterBindings;
private final Map<TableReference, List<Assignment>> assignmentsByTable;
private final SessionFactoryImplementor sessionFactory;
public UpdateExecutionDelegate(
SqmUpdateStatement sqmUpdate,
MultiTableSqmMutationConverter sqmConverter,
IdTable idTable,
TempTableDdlTransactionHandling ddlTransactionHandling,
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
Supplier<IdTableExporter> idTableExporterAccess,
DomainParameterXref domainParameterXref,
TableGroup updatingTableGroup,
TableReference hierarchyRootTableReference,
Map<String, TableReference> tableReferenceByAlias,
List<Assignment> assignments,
Predicate suppliedPredicate,
Map<SqmParameter, List<JdbcParameter>> parameterResolutions,
ExecutionContext executionContext) {
this.sqmUpdate = sqmUpdate;
this.sqmConverter = sqmConverter;
this.idTable = idTable;
this.ddlTransactionHandling = ddlTransactionHandling;
this.beforeUseAction = beforeUseAction;
this.afterUseAction = afterUseAction;
this.sessionUidAccess = sessionUidAccess;
this.idTableExporterAccess = idTableExporterAccess;
this.domainParameterXref = domainParameterXref;
this.updatingTableGroup = updatingTableGroup;
this.suppliedPredicate = suppliedPredicate;
this.sessionFactory = executionContext.getSession().getFactory();
final ModelPartContainer updatingModelPart = updatingTableGroup.getModelPart();
assert updatingModelPart instanceof EntityMappingType;
this.entityDescriptor = (EntityMappingType) updatingModelPart;
this.assignmentsByTable = new HashMap<>( updatingTableGroup.getTableReferenceJoins().size() + 1 );
jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
SqmUtil.generateJdbcParamsXref(
domainParameterXref,
() -> parameterResolutions
),
sessionFactory.getDomainModel(),
navigablePath -> updatingTableGroup,
executionContext.getSession()
);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// segment the assignments by table-reference
for ( int i = 0; i < assignments.size(); i++ ) {
final Assignment assignment = assignments.get( i );
final List<ColumnReference> assignmentColumnRefs = assignment.getAssignable().getColumnReferences();
TableReference assignmentTableReference = null;
for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) {
final ColumnReference columnReference = assignmentColumnRefs.get( c );
final TableReference tableReference = resolveTableReference(
columnReference,
updatingTableGroup,
tableReferenceByAlias
);
if ( assignmentTableReference != null && assignmentTableReference != tableReference ) {
throw new IllegalStateException( "Assignment referred to columns from multiple tables" );
}
assignmentTableReference = tableReference;
}
List<Assignment> assignmentsForTable = assignmentsByTable.get( assignmentTableReference );
if ( assignmentsForTable == null ) {
assignmentsForTable = new ArrayList<>();
assignmentsByTable.put( assignmentTableReference, assignmentsForTable );
}
assignmentsForTable.add( assignment );
}
}
@Override
public int execute(ExecutionContext executionContext) {
ExecuteWithIdTableHelper.performBeforeIdTableUseActions(
beforeUseAction,
idTable,
idTableExporterAccess,
ddlTransactionHandling,
executionContext
);
try {
final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable(
sqmUpdate,
sqmConverter,
updatingTableGroup,
suppliedPredicate,
idTable,
sessionUidAccess,
domainParameterXref,
jdbcParameterBindings,
executionContext
);
final QuerySpec idTableSubQuery = ExecuteWithIdTableHelper.createIdTableSelectQuerySpec(
idTable,
sessionUidAccess,
entityDescriptor,
executionContext
);
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnVisitationSupplier) -> updateTable(
tableExpression,
tableKeyColumnVisitationSupplier,
idTableSubQuery,
executionContext
)
);
return rows;
}
finally {
ExecuteWithIdTableHelper.performAfterIdTableUseActions(
afterUseAction,
idTable,
idTableExporterAccess,
ddlTransactionHandling,
sessionUidAccess,
executionContext
);
}
}
private TableReference resolveTableReference(
ColumnReference columnReference,
TableGroup updatingTableGroup,
Map<String, TableReference> tableReferenceByAlias) {
final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( columnReference.getQualifier() );
if ( tableReferenceByQualifier != null ) {
return tableReferenceByQualifier;
}
final TableReference tableReferenceByName = updatingTableGroup.resolveTableReference( columnReference.getQualifier() );
if ( tableReferenceByName != null ) {
return tableReferenceByName;
}
throw new IllegalStateException( "Could not resolve restricted column's table-reference" );
}
private void updateTable(
String tableExpression,
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
QuerySpec idTableSubQuery,
ExecutionContext executionContext) {
final TableReference updatingTableReference = updatingTableGroup.resolveTableReference( tableExpression );
final List<Assignment> assignments = assignmentsByTable.get( updatingTableReference );
if ( assignments == null || assignments.isEmpty() ) {
// no assignments for this table - skip it
return;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create the in-subquery predicate to restrict the updates to just
// matching ids
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor );
tableKeyColumnVisitationSupplier.get().accept(
(columnExpression, containingTableExpression, jdbcMapping) -> {
assert containingTableExpression.equals( tableExpression );
keyColumnCollector.apply(
new ColumnReference(
(String) null,
columnExpression,
jdbcMapping,
sessionFactory
)
);
}
);
final InSubQueryPredicate idTableSubQueryPredicate = new InSubQueryPredicate(
keyColumnCollector.buildKeyExpression(),
idTableSubQuery,
false
);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create the SQL AST and convert it into a JdbcOperation
final UpdateStatement sqlAst = new UpdateStatement( updatingTableReference, assignments, idTableSubQueryPredicate );
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final SqlAstUpdateTranslator sqlAstTranslator = jdbcServices.getJdbcEnvironment()
.getSqlAstTranslatorFactory()
.buildUpdateTranslator( sessionFactory );
final JdbcUpdate jdbcUpdate = sqlAstTranslator.translate( sqlAst );
jdbcServices.getJdbcMutationExecutor().execute(
jdbcUpdate,
jdbcParameterBindings,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {},
executionContext
);
}
}

View File

@ -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;
}
}

View File

@ -783,7 +783,7 @@ public abstract class BaseSqmToSqlAstConverter
}
private Expression consumeSqmParameter(SqmParameter sqmParameter) {
protected Expression consumeSqmParameter(SqmParameter sqmParameter) {
final MappingModelExpressable valueMapping = determineValueMapping( sqmParameter );
final List<JdbcParameter> jdbcParametersForSqm = new ArrayList<>();

View File

@ -10,12 +10,13 @@ import java.util.function.Consumer;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.update.Assignable;
import org.hibernate.sql.ast.tree.update.Assignment;
/**
* @author Steve Ebersole
*/
public interface AssignableSqmPathInterpretation<T> extends SqmPathInterpretation<T> {
public interface AssignableSqmPathInterpretation<T> extends SqmPathInterpretation<T>, Assignable {
// need to be able to collect assignments per-table, including
// SqmParameter -> JdbcParameter mapping

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.query.sqm.sql.internal;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.NotYetImplementedFor6Exception;
@ -132,4 +134,14 @@ public class BasicValuedPathInterpretation<T> implements AssignableSqmPathInterp
public String toString() {
return "BasicValuedPathInterpretation(" + sqmPath.getNavigablePath().getFullPath() + ')';
}
@Override
public void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer) {
columnReferenceConsumer.accept( columnReference );
}
@Override
public List<ColumnReference> getColumnReferences() {
return Collections.singletonList( columnReference );
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.query.sqm.sql.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.NotYetImplementedFor6Exception;
@ -17,7 +19,9 @@ import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.results.spi.DomainResult;
@ -45,7 +49,6 @@ public class EmbeddableValuedPathInterpretation<T> implements AssignableSqmPathI
return new EmbeddableValuedPathInterpretation<>(
mapping.toSqlExpression(
tableGroup,
converter.getCurrentClauseStack().getCurrent(),
converter,
converter
@ -114,4 +117,30 @@ public class EmbeddableValuedPathInterpretation<T> implements AssignableSqmPathI
public String toString() {
return "EmbeddableValuedPathInterpretation(" + sqmPath.getNavigablePath().getFullPath() + ')';
}
@Override
public void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer) {
if ( sqlExpression instanceof ColumnReference ) {
columnReferenceConsumer.accept( (ColumnReference) sqlExpression );
}
else if ( sqlExpression instanceof SqlTuple ) {
final SqlTuple sqlTuple = (SqlTuple) sqlExpression;
for ( Expression expression : sqlTuple.getExpressions() ) {
if ( ! ( expression instanceof ColumnReference ) ) {
throw new IllegalArgumentException( "Expecting ColumnReference, found : " + expression );
}
columnReferenceConsumer.accept( (ColumnReference) expression );
}
}
else {
// error or warning...
}
}
@Override
public List<ColumnReference> getColumnReferences() {
final List<ColumnReference> results = new ArrayList<>();
visitColumnReferences( results::add );
return results;
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -0,0 +1,36 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.ast;
import java.util.Set;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators;
/**
* @author Steve Ebersole
*/
public interface SqlAstTranslator extends SqlAstWalker, SqlTypeDescriptorIndicators {
/**
* Not the best spot for this. Its the table names collected while walking the SQL AST.
* Its ok here because the translator is consider a one-time-use. It just needs to be called
* after translation.
*
* A better option is probably to have "translation" objects that expose the affected table-names.
*/
Set<String> getAffectedTableNames();
/**
* Generalized support for translating a CTE statement. The underlying
* {@link CteStatement#getCteConsumer()} could be a SELECT, UPDATE, DELETE, etc.
*
* Implementors may throw an exception is the CTE-consumer is of the incorrect type
*/
JdbcOperation translate(CteStatement cteStatement);
}

View File

@ -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

View File

@ -10,6 +10,7 @@ import java.util.HashSet;
import java.util.Set;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlTreeCreationException;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.update.Assignment;
@ -17,13 +18,13 @@ import org.hibernate.sql.ast.tree.update.Assignment;
/**
* @author Steve Ebersole
*/
public abstract class AbstractSqlAstToJdbcOperationConverter
public abstract class AbstractSqlAstTranslator
extends AbstractSqlAstWalker
implements SqlAstToJdbcOperationConverter {
implements SqlAstTranslator {
private final Set<String> affectedTableNames = new HashSet<>();
protected AbstractSqlAstToJdbcOperationConverter(SessionFactoryImplementor sessionFactory) {
protected AbstractSqlAstTranslator(SessionFactoryImplementor sessionFactory) {
super( sessionFactory );
}

View File

@ -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 );
}

View File

@ -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 );

View File

@ -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) {

View File

@ -10,6 +10,7 @@ import java.util.List;
import java.util.Set;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
import org.hibernate.sql.ast.tree.cte.CteStatement;
@ -23,14 +24,25 @@ import org.hibernate.sql.exec.spi.JdbcUpdate;
* @author Steve Ebersole
*/
public class StandardSqlAstUpdateTranslator
extends AbstractSqlAstToJdbcOperationConverter
extends AbstractSqlAstTranslator
implements SqlAstUpdateTranslator {
private final Dialect dialect;
public StandardSqlAstUpdateTranslator(SessionFactoryImplementor sessionFactory) {
super( sessionFactory );
// todo (6.0) : use the Dialect to determine how to handle column references
// - specifically should they use the table-alias, the table-expression
// or neither for its qualifier
dialect = getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect();
}
private String updatingTableAlias;
@Override
public JdbcUpdate translate(UpdateStatement sqlAst) {
updatingTableAlias = sqlAst.getTargetTable().getIdentificationVariable();
appendSql( "update " );
appendSql( sqlAst.getTargetTable().getTableExpression() );
@ -45,7 +57,17 @@ public class StandardSqlAstUpdateTranslator
}
final Assignment assignment = sqlAst.getAssignments().get( i );
assignment.getColumnReference().accept( this );
final List<ColumnReference> columnReferences = assignment.getAssignable().getColumnReferences();
if ( columnReferences.size() == 1 ) {
columnReferences.get( 0 ).accept( this );
}
else {
appendSql( " (" );
for ( int cri = 0; cri < columnReferences.size(); cri++ ) {
columnReferences.get( cri ).accept( this );
}
appendSql( ") " );
}
appendSql( " = " );
assignment.getAssignedValue().accept( this );
}
@ -75,8 +97,18 @@ public class StandardSqlAstUpdateTranslator
@Override
public void visitColumnReference(ColumnReference 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
public JdbcUpdate translate(CteStatement cteStatement) {

View File

@ -7,8 +7,11 @@
package org.hibernate.sql.ast.tree.expression;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.StringHelper;
@ -17,6 +20,7 @@ import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.update.Assignable;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
@ -26,7 +30,7 @@ import org.hibernate.type.spi.TypeConfiguration;
*
* @author Steve Ebersole
*/
public class ColumnReference implements Expression {
public class ColumnReference implements Expression, Assignable {
private final String qualifier;
private final String columnExpression;
private final String referenceExpression;
@ -123,4 +127,14 @@ public class ColumnReference implements Expression {
public int hashCode() {
return referenceExpression.hashCode();
}
@Override
public void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer) {
columnReferenceConsumer.accept( this );
}
@Override
public List<ColumnReference> getColumnReferences() {
return Collections.singletonList( this );
}
}

View File

@ -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;

View File

@ -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;
/**

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -0,0 +1,22 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.ast.tree.update;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
/**
* @author Steve Ebersole
*/
public interface Assignable {
List<ColumnReference> getColumnReferences();
void visitColumnReferences(Consumer<ColumnReference> columnReferenceConsumer);
}

View File

@ -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() {

View File

@ -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()
);
}
}