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

CTE, id-table and in-line strategies are all implemented (though only id-table is tested);
  refactoring for performance (direct creation of SQL AST object directly, rather than SQM -> SQL AST) and as part of initial impls for remaining strategies (global temp and persistent id tables, and the "inline" strategy;
  fixed concurrency bug (thanks Luis!)
This commit is contained in:
Steve Ebersole 2019-11-20 10:27:50 -06:00
parent b04599cbe5
commit 032fdb5d2e
55 changed files with 2118 additions and 1049 deletions

View File

@ -89,7 +89,7 @@ import org.hibernate.engine.transaction.jta.platform.internal.WebSphereJtaPlatfo
import org.hibernate.engine.transaction.jta.platform.internal.WebSphereLibertyJtaPlatform;
import org.hibernate.engine.transaction.jta.platform.internal.WeblogicJtaPlatform;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy;
import org.hibernate.query.sqm.mutation.internal.idtable.GlobalTemporaryTableStrategy;
import org.hibernate.query.sqm.mutation.internal.idtable.LocalTemporaryTableStrategy;
import org.hibernate.query.sqm.mutation.internal.idtable.PersistentTableStrategy;
@ -410,8 +410,8 @@ public class StrategySelectorBuilder {
private void addSqmMultiTableMutationStrategies(StrategySelectorImpl strategySelector) {
strategySelector.registerStrategyImplementor(
SqmMultiTableMutationStrategy.class,
CteBasedMutationStrategy.SHORT_NAME,
CteBasedMutationStrategy.class
CteStrategy.SHORT_NAME,
CteStrategy.class
);
strategySelector.registerStrategyImplementor(
SqmMultiTableMutationStrategy.class,

View File

@ -223,7 +223,8 @@ abstract class AbstractTransactSQLDialect extends Dialect {
// // sql-server, at least needed this dropped after use; strange!
this::getTypeName,
AfterUseAction.DROP,
TempTableDdlTransactionHandling.NONE
TempTableDdlTransactionHandling.NONE,
runtimeModelCreationContext.getSessionFactory()
);
}

View File

@ -618,7 +618,8 @@ public class DerbyDialect extends DB2Dialect {
}
},
AfterUseAction.CLEAN,
TempTableDdlTransactionHandling.NONE
TempTableDdlTransactionHandling.NONE,
runtimeModelCreationContext.getSessionFactory()
);
}
}

View File

@ -379,7 +379,8 @@ public class H2Dialect extends Dialect {
new IdTable( entityDescriptor, basename -> "HT_" + basename ),
this::getTypeName,
AfterUseAction.CLEAN,
TempTableDdlTransactionHandling.NONE
TempTableDdlTransactionHandling.NONE,
runtimeModelCreationContext.getSessionFactory()
);
}

View File

@ -301,7 +301,8 @@ public class InformixDialect extends Dialect {
}
},
AfterUseAction.NONE,
TempTableDdlTransactionHandling.NONE
TempTableDdlTransactionHandling.NONE,
runtimeModelCreationContext.getSessionFactory()
);
}

View File

@ -366,7 +366,8 @@ public class MySQLDialect extends Dialect {
}
},
AfterUseAction.DROP,
TempTableDdlTransactionHandling.NONE
TempTableDdlTransactionHandling.NONE,
runtimeModelCreationContext.getSessionFactory()
);
}

View File

@ -144,7 +144,6 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.Queryable;
@ -169,7 +168,6 @@ import org.hibernate.property.access.spi.Setter;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.Alias;
@ -6201,6 +6199,18 @@ public abstract class AbstractEntityPersister
else {
sqmMultiTableMutationStrategy = null;
}
// register a callback for after all `#prepareMappingModel` calls have finished. here we want to delay the
// generation of `staticFetchableList` because we need to wait until after all sub-classes have had their
// `#prepareMappingModel` called (and their declared attribute mappings resolved)
creationProcess.registerInitializationCallback(
() -> {
staticFetchableList = new ArrayList<>( attributeMappings.size() );
visitAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) );
visitSubTypeAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) );
return true;
}
);
}
protected static SqmMultiTableMutationStrategy interpretSqmMultiTableStrategy(
@ -6220,7 +6230,7 @@ public abstract class AbstractEntityPersister
return SqmMutationStrategyHelper.resolveStrategy(
entityBootDescriptor,
entityMappingDescriptor,
creationProcess.getCreationContext()
creationProcess
);
}
@ -6607,12 +6617,6 @@ public abstract class AbstractEntityPersister
}
protected List<Fetchable> getStaticFetchableList() {
if ( staticFetchableList == null ) {
staticFetchableList = new ArrayList<>( attributeMappings.size() );
visitAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) );
visitSubTypeAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) );
}
return staticFetchableList;
}

View File

@ -59,7 +59,6 @@ import org.hibernate.sql.CaseFragment;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.SelectFragment;
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;
@ -1320,8 +1319,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
final Expression expression =
new QueryLiteral<>(
discriminatorValuesByTableName.get( table.getTableExpression() ),
resultType,
Clause.SELECT
resultType
);
caseSearchedExpression.when( predicate, expression );

View File

@ -46,7 +46,6 @@ import org.hibernate.query.NavigablePath;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.SelectFragment;
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;
@ -915,8 +914,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
ComparisonOperator.EQUAL,
new QueryLiteral<>(
getDiscriminatorValue(),
( (BasicType) getDiscriminatorType() ),
Clause.WHERE
( (BasicType) getDiscriminatorType() )
)
);
}

View File

@ -7,21 +7,29 @@
package org.hibernate.query.sqm.internal;
import org.hibernate.query.spi.NonSelectQueryPlan;
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* @author Steve Ebersole
*/
public class MultiTableDeleteQueryPlan implements NonSelectQueryPlan {
private final DeleteHandler deleteHandler;
private final SqmDeleteStatement sqmDelete;
private final DomainParameterXref domainParameterXref;
private final SqmMultiTableMutationStrategy deleteStrategy;
public MultiTableDeleteQueryPlan(DeleteHandler deleteHandler) {
this.deleteHandler = deleteHandler;
public MultiTableDeleteQueryPlan(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
SqmMultiTableMutationStrategy deleteStrategy) {
this.sqmDelete = sqmDelete;
this.domainParameterXref = domainParameterXref;
this.deleteStrategy = deleteStrategy;
}
@Override
public int executeUpdate(ExecutionContext executionContext) {
return deleteHandler.execute( executionContext );
return deleteStrategy.executeDelete( sqmDelete, domainParameterXref, executionContext );
}
}

View File

@ -7,21 +7,29 @@
package org.hibernate.query.sqm.internal;
import org.hibernate.query.spi.NonSelectQueryPlan;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* @author Steve Ebersole
*/
public class MultiTableUpdateQueryPlan implements NonSelectQueryPlan {
private final UpdateHandler updateHandler;
private final SqmUpdateStatement sqmUpdate;
private final DomainParameterXref domainParameterXref;
private final SqmMultiTableMutationStrategy mutationStrategy;
public MultiTableUpdateQueryPlan(UpdateHandler updateHandler) {
this.updateHandler = updateHandler;
public MultiTableUpdateQueryPlan(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
SqmMultiTableMutationStrategy mutationStrategy) {
this.sqmUpdate = sqmUpdate;
this.domainParameterXref = domainParameterXref;
this.mutationStrategy = mutationStrategy;
}
@Override
public int executeUpdate(ExecutionContext executionContext) {
return updateHandler.execute( executionContext );
return mutationStrategy.executeUpdate( sqmUpdate, domainParameterXref, executionContext );
}
}

View File

@ -545,34 +545,22 @@ public class QuerySqmImpl<R>
return new SimpleDeleteQueryPlan( sqmDelete, domainParameterXref );
}
else {
return new MultiTableDeleteQueryPlan(
multiTableStrategy.buildDeleteHandler(
sqmDelete,
domainParameterXref,
this::getSessionFactory
)
);
return new MultiTableDeleteQueryPlan( sqmDelete, domainParameterXref, multiTableStrategy );
}
}
private NonSelectQueryPlan buildUpdateQueryPlan() {
final SqmUpdateStatement sqmStatement = (SqmUpdateStatement) getSqmStatement();
final SqmUpdateStatement sqmUpdate = (SqmUpdateStatement) getSqmStatement();
final String entityNameToUpdate = sqmStatement.getTarget().getReferencedPathSource().getHibernateEntityName();
final String entityNameToUpdate = sqmUpdate.getTarget().getReferencedPathSource().getHibernateEntityName();
final EntityPersister entityDescriptor = getSessionFactory().getDomainModel().findEntityDescriptor( entityNameToUpdate );
final SqmMultiTableMutationStrategy multiTableStrategy = entityDescriptor.getSqmMultiTableMutationStrategy();
if ( multiTableStrategy == null ) {
return new SimpleUpdateQueryPlan( sqmStatement, domainParameterXref );
return new SimpleUpdateQueryPlan( sqmUpdate, domainParameterXref );
}
else {
return new MultiTableUpdateQueryPlan(
multiTableStrategy.buildUpdateHandler(
sqmStatement,
domainParameterXref,
this::getSessionFactory
)
);
return new MultiTableUpdateQueryPlan( sqmUpdate, domainParameterXref, multiTableStrategy );
}
}

View File

@ -7,7 +7,7 @@
package org.hibernate.query.sqm.internal;
import org.hibernate.query.spi.NonSelectQueryPlan;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.mutation.internal.UpdateHandler;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;

View File

@ -4,7 +4,7 @@
* 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.spi;
package org.hibernate.query.sqm.mutation.internal;
/**
* Handler for dealing with multi-table SQM DELETE queries.

View File

@ -4,11 +4,17 @@
* 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.spi;
package org.hibernate.query.sqm.mutation.internal;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* Simply as a matter of code structuring it is often worthwhile to put all of the execution code into a separate
* handler (executor) class. This contract helps unify those helpers.
*
* Hiding this "behind the strategy" also allows mixing approaches based on the nature of specific
* queries
*
* @author Steve Ebersole
*/
public interface Handler {

View File

@ -0,0 +1,232 @@
/*
* 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.sql.ast.SqlAstSelectTranslator;
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.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
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.JdbcSelect;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.internal.domain.basic.BasicResult;
import org.hibernate.sql.results.spi.DomainResult;
import org.jboss.logging.Logger;
/**
* Helper used to generate the SELECT for selection of an entity's identifier, here specifically intended to be used
* as the SELECT portion of a multi-table SQM mutation
*
* @author Steve Ebersole
*/
public class MatchingIdSelectionHelper {
private static final Logger log = Logger.getLogger( MatchingIdSelectionHelper.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 MatchingIdSelectionHelper#selectMatchingIds}
* * as a sub-query restriction to insert rows into an "id table"
*/
public static SelectStatement generateMatchingIdSelectStatement(
EntityMappingType targetEntityDescriptor,
SqmDeleteOrUpdateStatement sqmStatement,
Predicate restriction,
MultiTableSqmMutationConverter sqmConverter,
SessionFactoryImplementor sessionFactory) {
final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel();
log.tracef( "Starting generation of entity-id SQM selection - %s", entityDomainType.getHibernateEntityName() );
final QuerySpec idSelectionQuery = new QuerySpec( true, 1 );
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
idSelectionQuery.getFromClause().addRoot( mutatingTableGroup );
final List<DomainResult> domainResults = new ArrayList<>();
final AtomicInteger i = new AtomicInteger();
targetEntityDescriptor.getIdentifierMapping().visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> {
final int position = i.getAndIncrement();
final TableReference tableReference = mutatingTableGroup.resolveTableReference( containingTableExpression );
final Expression expression = sqmConverter.getSqlExpressionResolver().resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ),
sqlAstProcessingState -> new ColumnReference(
tableReference,
columnExpression,
jdbcMapping,
sessionFactory
)
);
idSelectionQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
position,
position + 1,
expression,
jdbcMapping
)
);
//noinspection unchecked
domainResults.add( new BasicResult( position, null, jdbcMapping.getJavaTypeDescriptor() ) );
}
);
idSelectionQuery.applyPredicate( restriction );
return new SelectStatement( idSelectionQuery, domainResults );
}
/**
* @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 MatchingIdSelectionHelper#selectMatchingIds}
* * as a sub-query restriction to insert rows into an "id table"
*/
public static QuerySpec generateMatchingIdSelectQuery(
EntityMappingType targetEntityDescriptor,
SqmDeleteOrUpdateStatement sqmStatement,
DomainParameterXref domainParameterXref,
Predicate restriction,
MultiTableSqmMutationConverter sqmConverter,
SessionFactoryImplementor sessionFactory) {
final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel();
log.tracef( "Starting generation of entity-id SQM selection - %s", entityDomainType.getHibernateEntityName() );
final QuerySpec idSelectionQuery = new QuerySpec( true, 1 );
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
idSelectionQuery.getFromClause().addRoot( mutatingTableGroup );
final AtomicInteger i = new AtomicInteger();
targetEntityDescriptor.getIdentifierMapping().visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> {
final int position = i.getAndIncrement();
final TableReference tableReference = mutatingTableGroup.resolveTableReference( containingTableExpression );
final Expression expression = sqmConverter.getSqlExpressionResolver().resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ),
sqlAstProcessingState -> new ColumnReference(
tableReference,
columnExpression,
jdbcMapping,
sessionFactory
)
);
idSelectionQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
position,
position + 1,
expression,
jdbcMapping
)
);
}
);
idSelectionQuery.applyPredicate( restriction );
return idSelectionQuery;
}
/**
* Centralized selection of ids matching the restriction of the DELETE
* or UPDATE SQM query
*/
public static List<Object> selectMatchingIds(
SqmDeleteOrUpdateStatement sqmMutationStatement,
DomainParameterXref domainParameterXref,
ExecutionContext executionContext) {
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final EntityMappingType entityDescriptor = factory.getDomainModel()
.getEntityDescriptor( sqmMutationStatement.getTarget().getModel().getHibernateEntityName() );
final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter(
entityDescriptor,
domainParameterXref,
executionContext.getQueryOptions(),
executionContext.getQueryParameterBindings(),
factory
);
final Map<SqmParameter, List<JdbcParameter>> parameterResolutions;
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
parameterResolutions = Collections.emptyMap();
}
else {
parameterResolutions = new IdentityHashMap<>();
}
final Predicate restriction = sqmConverter.visitWhereClause(
sqmMutationStatement.getWhereClause(),
columnReference -> {},
parameterResolutions::put
);
final SelectStatement matchingIdSelection = generateMatchingIdSelectStatement(
entityDescriptor,
sqmMutationStatement,
restriction,
sqmConverter,
factory
);
final JdbcServices jdbcServices = factory.getJdbcServices();
final SqlAstSelectTranslator sqlAstSelectTranslator = jdbcServices.getJdbcEnvironment()
.getSqlAstTranslatorFactory()
.buildSelectTranslator( factory );
final JdbcSelect idSelectJdbcOperation = sqlAstSelectTranslator.translate( matchingIdSelection );
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ),
factory.getDomainModel(),
navigablePath -> sqmConverter.getMutatingTableGroup(),
executionContext.getSession()
);
return jdbcServices.getJdbcSelectExecutor().list(
idSelectJdbcOperation,
jdbcParameterBindings,
executionContext,
row -> row
);
}
}

View File

@ -4,7 +4,7 @@
* 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;
package org.hibernate.query.sqm.mutation.internal;
import java.util.List;
import java.util.function.BiConsumer;
@ -109,7 +109,6 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter imp
return super.getProcessingStateStack();
}
/**
* Specialized hook to visit the assignments defined by the update SQM allow
* "listening" for each SQL assignment.
@ -128,6 +127,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter imp
public List<Assignment> visitSetClause(SqmSetClause setClause) {
throw new UnsupportedOperationException();
}
private void visitAssignment(
SqmAssignment sqmAssignment,
Consumer<Assignment> assignmentConsumer) {

View File

@ -1,128 +0,0 @@
/*
* 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;
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.internal.SqmQuerySpecCreationProcessingStateStandardImpl;
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.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.jboss.logging.Logger;
/**
* Helper used to generate the SELECT for selection of an entity's
* identifier, here specifically intended to be used as the SELECT
* portion of a multi-table SQM mutation
*
* @author Steve Ebersole
*/
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,
SqmCreationContext sqmCreationContext) {
final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel();
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 sqmCreationContext;
}
@Override
public SqmCreationOptions getCreationOptions() {
return () -> false;
}
@Override
public Stack<SqmCreationProcessingState> getProcessingStateStack() {
return processingStateStack;
}
};
// temporary - used just for creating processingState
final SqmSelectStatement sqmSelectStatement = new SqmSelectStatement( sqmCreationContext.getNodeBuilder() );
//noinspection unchecked
sqmSelectStatement.setQuerySpec( sqmQuerySpec );
final SqmCreationProcessingState processingState = new SqmQuerySpecCreationProcessingStateStandardImpl(
null,
sqmSelectStatement,
creationState
);
processingStateStack.push( processingState );
final SqmFromClause sqmFromClause = new SqmFromClause();
sqmQuerySpec.setFromClause( sqmFromClause );
//noinspection unchecked
// 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, sqmCreationContext.getNodeBuilder() );
sqmQuerySpec.setSelectClause( sqmSelectClause );
applySelections( sqmQuerySpec, sqmRoot, processingState );
if ( sqmStatement.getWhereClause() != null ) {
sqmQuerySpec.applyPredicate( sqmStatement.getWhereClause().getPredicate() );
}
return sqmQuerySpec;
}
private static void applySelections(
SqmQuerySpec sqmQuerySpec,
SqmRoot<?> sqmRoot,
SqmCreationProcessingState processingState) {
//noinspection unchecked
final SqmPath idPath = sqmRoot.getModel().getIdentifierDescriptor().createSqmPath( sqmRoot, processingState.getCreationState() );
//noinspection unchecked
sqmQuerySpec.getSelectClause().add(
new SqmSelection(
idPath,
null,
processingState.getCreationState().getCreationContext().getNodeBuilder()
)
);
}
}

View File

@ -6,46 +6,14 @@
*/
package org.hibernate.query.sqm.mutation.internal;
import java.util.List;
import java.util.Map;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.RootClass;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.spi.QueryParameterImplementor;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.sql.SqmSelectTranslation;
import org.hibernate.query.sqm.sql.SqmSelectTranslator;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.domain.SqmSimplePath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmJunctivePredicate;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
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.JdbcSelect;
/**
* @author Steve Ebersole
@ -66,220 +34,22 @@ public class SqmMutationStrategyHelper {
public static SqmMultiTableMutationStrategy resolveStrategy(
RootClass entityBootDescriptor,
EntityMappingType rootEntityDescriptor,
RuntimeModelCreationContext creationContext) {
MappingModelCreationProcess creationProcess) {
final RuntimeModelCreationContext creationContext = creationProcess.getCreationContext();
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final SessionFactoryOptions options = sessionFactory.getSessionFactoryOptions();
final SqmMultiTableMutationStrategy specifiedStrategy = options.getSqmMultiTableMutationStrategy();
if ( specifiedStrategy != null ) {
return specifiedStrategy;
}
// todo (6.0) : add capability define strategy per-hierarchy
return sessionFactory.getServiceRegistry().getService( JdbcServices.class )
.getJdbcEnvironment()
.getDialect()
.getFallbackSqmMutationStrategy( rootEntityDescriptor, creationContext );
}
/**
* Specialized "Supplier" or "tri Function" for creating the
* fallback handler if the query matches no "special cases"
*/
public interface FallbackDeleteHandlerCreator {
DeleteHandler create(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext);
}
/**
* Standard DeleteHandler resolution applying "special case" resolution
*/
public static DeleteHandler resolveDeleteHandler(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext,
FallbackDeleteHandlerCreator fallbackCreator) {
if ( sqmDelete.getWhereClause() == null ) {
// special case : unrestricted
// -> delete all rows, no need to use the id table
}
else {
// if the predicate contains refers to any non-id Navigable, we will need to use the id table
if ( ! hasNonIdReferences( sqmDelete.getWhereClause().getPredicate() ) ) {
// special case : not restricted on non-id Navigable reference
// -> we can apply the original restriction to the individual
//
// todo (6.0) : technically non-id references where the reference is mapped to the primary table
// can also be handled by this special case. Really the special case condition is "has
// any non-id references to Navigables not mapped to the primary table of the container"
}
}
// otherwise, use the fallback....
return fallbackCreator.create( sqmDelete, domainParameterXref, creationContext );
}
/**
* Specialized "Supplier" or "tri Function" for creating the
* fallback handler if the query mmatches no "special cases"
*/
public interface FallbackUpdateHandlerCreator {
UpdateHandler create(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext);
}
/**
* Standard UpdateHandler resolution applying "special case" resolution
*/
public static UpdateHandler resolveUpdateHandler(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext,
FallbackUpdateHandlerCreator fallbackCreator) {
if ( sqmUpdate.getWhereClause() == null ) {
// special case : unrestricted
// -> delete all rows, no need to use the id table
}
else {
// see if the predicate contains any non-id Navigable references
if ( ! hasNonIdReferences( sqmUpdate.getWhereClause().getPredicate() ) ) {
// special case : not restricted on non-id Navigable reference
// -> we can apply the original restriction to the individual updates without needing to use the id-table
//
// todo (6.0) : technically non-id references where the reference is mapped to the primary table
// can also be handled by this special case. Really the special case condition is "has
// any non-id references to Navigables not mapped to the primary table of the container"
}
}
// todo (6.0) : implement the above special cases
// otherwise, use the fallback....
return fallbackCreator.create( sqmUpdate, domainParameterXref, creationContext );
}
/**
* Does the given `predicate` "non-identifier Navigable references"?
*
* @see #isNonIdentifierReference
*/
@SuppressWarnings("WeakerAccess")
public static boolean hasNonIdReferences(SqmPredicate predicate) {
if ( predicate instanceof SqmGroupedPredicate ) {
return hasNonIdReferences( ( (SqmGroupedPredicate) predicate ).getSubPredicate() );
}
if ( predicate instanceof SqmJunctivePredicate ) {
return hasNonIdReferences( ( (SqmJunctivePredicate) predicate ).getLeftHandPredicate() )
&& hasNonIdReferences( ( (SqmJunctivePredicate) predicate ).getRightHandPredicate() );
}
if ( predicate instanceof SqmComparisonPredicate ) {
final SqmExpression lhs = ( (SqmComparisonPredicate) predicate ).getLeftHandExpression();
final SqmExpression rhs = ( (SqmComparisonPredicate) predicate ).getRightHandExpression();
return isNonIdentifierReference( lhs ) || isNonIdentifierReference( rhs );
}
if ( predicate instanceof SqmInListPredicate ) {
final SqmInListPredicate<?> inPredicate = (SqmInListPredicate) predicate;
if ( isNonIdentifierReference( inPredicate.getTestExpression() ) ) {
return true;
}
for ( SqmExpression listExpression : inPredicate.getListExpressions() ) {
if ( isNonIdentifierReference( listExpression ) ) {
return true;
}
}
return false;
}
if ( predicate instanceof SqmBetweenPredicate ) {
final SqmBetweenPredicate betweenPredicate = (SqmBetweenPredicate) predicate;
return isNonIdentifierReference( betweenPredicate.getExpression() )
|| isNonIdentifierReference( betweenPredicate.getLowerBound() )
|| isNonIdentifierReference( betweenPredicate.getUpperBound() );
}
return false;
}
/**
* Is the given `expression` a `SqmNavigableReference` that is also a reference
* to a non-`EntityIdentifier` `Navigable`?
*
* @see SqmSimplePath
*/
@SuppressWarnings("WeakerAccess")
public static boolean isNonIdentifierReference(SqmExpression expression) {
// if ( expression instanceof SqmNavigableReference ) {
// return ! EntityIdentifier.class.isInstance( expression );
// }
return false;
}
/**
* Centralized selection of ids matching the restriction of the DELETE
* or UPDATE SQM query
*/
public static List<Object> selectMatchingIds(
SqmDeleteOrUpdateStatement sqmDeleteStatement,
DomainParameterXref domainParameterXref,
ExecutionContext executionContext) {
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final QueryEngine queryEngine = factory.getQueryEngine();
final SqmTranslatorFactory sqmTranslatorFactory = queryEngine.getSqmTranslatorFactory();
final SqmSelectTranslator selectConverter = sqmTranslatorFactory.createSelectTranslator(
executionContext.getQueryOptions(),
domainParameterXref,
executionContext.getQueryParameterBindings(),
executionContext.getLoadQueryInfluencers(),
factory
);
final SqmQuerySpec sqmIdSelectQuerySpec = SqmIdSelectGenerator.generateSqmEntityIdSelect(
sqmDeleteStatement,
factory
);
final SqmSelectStatement sqmIdSelect = new SqmSelectStatement( factory.getNodeBuilder() );
//noinspection unchecked
sqmIdSelect.setQuerySpec( sqmIdSelectQuerySpec );
final SqmSelectTranslation sqmInterpretation = selectConverter.translate( sqmIdSelect );
final JdbcServices jdbcServices = factory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( factory ).translate( sqmInterpretation.getSqlAst() );
final Map<QueryParameterImplementor<?>, Map<SqmParameter, List<JdbcParameter>>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
domainParameterXref,
sqmInterpretation::getJdbcParamsBySqmParam
);
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
jdbcParamsXref,
factory.getDomainModel(),
selectConverter.getFromClauseAccess()::findTableGroup,
executionContext.getSession()
);
return factory.getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParameterBindings,
executionContext,
row -> row
);
}
}

View File

@ -4,7 +4,7 @@
* 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.spi;
package org.hibernate.query.sqm.mutation.internal;
/**
* Handler for dealing with multi-table SQM UPDATE queries.

View File

@ -6,9 +6,9 @@
*/
package org.hibernate.query.sqm.mutation.internal.cte;
import org.hibernate.engine.spi.SessionFactoryImplementor;
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.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.sql.ast.tree.cte.CteTable;
@ -22,15 +22,15 @@ import org.hibernate.sql.ast.tree.cte.CteTable;
public abstract class AbstractCteMutationHandler extends AbstractMutationHandler {
private final CteTable cteTable;
private final DomainParameterXref domainParameterXref;
private final CteBasedMutationStrategy strategy;
private final CteStrategy strategy;
public AbstractCteMutationHandler(
CteTable cteTable,
SqmDeleteOrUpdateStatement sqmStatement,
DomainParameterXref domainParameterXref,
CteBasedMutationStrategy strategy,
HandlerCreationContext creationContext) {
super( sqmStatement, creationContext );
CteStrategy strategy,
SessionFactoryImplementor sessionFactory) {
super( sqmStatement, sessionFactory );
this.cteTable = cteTable;
this.domainParameterXref = domainParameterXref;
@ -45,7 +45,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
return domainParameterXref;
}
public CteBasedMutationStrategy getStrategy() {
public CteStrategy getStrategy() {
return strategy;
}
}

View File

@ -19,9 +19,8 @@ import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
import org.hibernate.query.sqm.mutation.spi.DeleteHandler;
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
import org.hibernate.query.sqm.mutation.internal.DeleteHandler;
import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@ -54,12 +53,11 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
CteTable cteTable,
SqmDeleteStatement sqmDeleteStatement,
DomainParameterXref domainParameterXref,
CteBasedMutationStrategy strategy,
HandlerCreationContext creationContext) {
super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, creationContext );
CteStrategy strategy,
SessionFactoryImplementor sessionFactory) {
super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory );
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcServices jdbcServices = getSessionFactory().getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
}
@ -71,7 +69,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
@Override
public int execute(ExecutionContext executionContext) {
final List<Object> ids = SqmMutationStrategyHelper.selectMatchingIds(
final List<Object> ids = MatchingIdSelectionHelper.selectMatchingIds(
getSqmDeleteOrUpdateStatement(),
getDomainParameterXref(),
executionContext
@ -122,17 +120,15 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
);
getEntityDescriptor().visitConstraintOrderedTables(
(tableExpression, tableColumnsVisitationSupplier) -> {
executeDelete(
cteDefinitionQuerySpec,
tableExpression,
tableColumnsVisitationSupplier,
getEntityDescriptor().getIdentifierMapping(),
cteQuerySpec,
jdbcParameterBindings,
executionContext
);
}
(tableExpression, tableColumnsVisitationSupplier) -> executeDelete(
cteDefinitionQuerySpec,
tableExpression,
tableColumnsVisitationSupplier,
getEntityDescriptor().getIdentifierMapping(),
cteQuerySpec,
jdbcParameterBindings,
executionContext
)
);
return ids.size();
@ -201,7 +197,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
);
return new CteStatement(
cteDefinition,
CteBasedMutationStrategy.TABLE_NAME,
CteStrategy.TABLE_NAME,
getCteTable(),
deleteStatement
);
@ -225,16 +221,14 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
final List<ColumnReference> columnsToMatchReferences = new ArrayList<>();
columnsToMatchVisitationSupplier.get().accept(
(columnExpression, containingTableExpression, jdbcMapping) -> {
columnsToMatchReferences.add(
new ColumnReference(
targetTableReference,
columnExpression,
jdbcMapping,
sessionFactory
)
);
}
(columnExpression, containingTableExpression, jdbcMapping) -> columnsToMatchReferences.add(
new ColumnReference(
targetTableReference,
columnExpression,
jdbcMapping,
sessionFactory
)
)
);
final Expression columnsToMatchExpression;

View File

@ -10,17 +10,16 @@ import java.util.Locale;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.EntityPersister;
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.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.tree.cte.CteTable;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* @asciidoc
@ -50,51 +49,25 @@ import org.hibernate.sql.ast.tree.cte.CteTable;
* )
* ````
*
* todo (6.0) : why not:
*
* ````
* with cte_id (id) as (
* select id
* from Person p
* where ...
* )
* delete from Contact
* where (id) in (
* select id
* from cte_id
* )
*
* with cte_id (id) as (
* select id
* from Person p
* where ...
* )
* delete from Person
* where (id) in (
* select id
* from cte_id
* )
* ````
*
* @author Evandro Pires da Silva
* @author Vlad Mihalcea
* @author Steve Ebersole
*/
public class CteBasedMutationStrategy implements SqmMultiTableMutationStrategy {
public class CteStrategy implements SqmMultiTableMutationStrategy {
public static final String SHORT_NAME = "cte";
public static final String TABLE_NAME = "id_cte";
private final EntityPersister rootDescriptor;
private final SessionFactoryImplementor sessionFactory;
private final CteTable cteTable;
public CteBasedMutationStrategy(
public CteStrategy(
EntityPersister rootDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
this.rootDescriptor = rootDescriptor;
this.sessionFactory = runtimeModelCreationContext.getSessionFactory();
final Dialect dialect = runtimeModelCreationContext.getTypeConfiguration()
.getSessionFactory()
.getServiceRegistry()
final Dialect dialect = sessionFactory.getServiceRegistry()
.getService( JdbcServices.class )
.getJdbcEnvironment()
.getDialect();
@ -120,24 +93,30 @@ public class CteBasedMutationStrategy implements SqmMultiTableMutationStrategy {
);
}
this.cteTable = new CteTable( rootDescriptor, runtimeModelCreationContext.getTypeConfiguration() );
this.cteTable = new CteTable( rootDescriptor );
}
@Override
public UpdateHandler buildUpdateHandler(
SqmUpdateStatement sqmUpdateStatement,
public int executeDelete(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
checkMatch( sqmUpdateStatement, creationContext );
return new CteUpdateHandler( cteTable, sqmUpdateStatement, domainParameterXref, this, creationContext );
ExecutionContext context) {
checkMatch( sqmDelete );
return new CteDeleteHandler( cteTable, sqmDelete, domainParameterXref, this, sessionFactory ).execute( context );
}
private void checkMatch(SqmDeleteOrUpdateStatement sqmStatement, HandlerCreationContext creationContext) {
@Override
public int executeUpdate(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
checkMatch( sqmUpdate );
return new CteUpdateHandler( cteTable, sqmUpdate, domainParameterXref, this, sessionFactory ).execute( context );
}
private void checkMatch(SqmDeleteOrUpdateStatement sqmStatement) {
final String targetEntityName = sqmStatement.getTarget().getEntityName();
final EntityPersister targetEntityDescriptor = creationContext.getSessionFactory()
.getDomainModel()
.getEntityDescriptor( targetEntityName );
final EntityPersister targetEntityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( targetEntityName );
if ( targetEntityDescriptor != rootDescriptor && ! rootDescriptor.isSubclassEntityName( targetEntityDescriptor.getEntityName() ) ) {
throw new IllegalArgumentException(
@ -151,14 +130,4 @@ public class CteBasedMutationStrategy implements SqmMultiTableMutationStrategy {
}
}
@Override
public DeleteHandler buildDeleteHandler(
SqmDeleteStatement sqmDeleteStatement,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
checkMatch( sqmDeleteStatement, creationContext );
return new CteDeleteHandler( cteTable, sqmDeleteStatement, domainParameterXref, this, creationContext );
}
}

View File

@ -7,9 +7,9 @@
package org.hibernate.query.sqm.mutation.internal.cte;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.spi.HandlerCreationContext;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.mutation.internal.UpdateHandler;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.tree.cte.CteTable;
import org.hibernate.sql.exec.spi.ExecutionContext;
@ -24,13 +24,14 @@ public class CteUpdateHandler
extends AbstractCteMutationHandler
implements UpdateHandler {
@SuppressWarnings("WeakerAccess")
public CteUpdateHandler(
CteTable cteTable,
SqmUpdateStatement sqmStatement,
DomainParameterXref domainParameterXref,
CteBasedMutationStrategy strategy,
HandlerCreationContext creationContext) {
super( cteTable, sqmStatement, domainParameterXref, strategy, creationContext );
CteStrategy strategy,
SessionFactoryImplementor sessionFactory) {
super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory );
}
@Override

View File

@ -7,6 +7,7 @@
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
@ -15,25 +16,16 @@ 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.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.transaction.spi.IsolationDelegate;
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.SqmTreePrinter;
import org.hibernate.query.sqm.mutation.internal.SqmIdSelectGenerator;
import org.hibernate.query.sqm.sql.SqmQuerySpecTranslation;
import org.hibernate.query.sqm.sql.SqmSelectTranslator;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
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.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
import org.hibernate.sql.ast.SqlAstInsertSelectTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
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.StandardTableGroup;
@ -47,6 +39,7 @@ import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcInsert;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.UUIDCharType;
import org.jboss.logging.Logger;
@ -63,158 +56,83 @@ public final class ExecuteWithIdTableHelper {
}
public static int saveMatchingIdsIntoIdTable(
SqmUpdateStatement sqmMutation,
MultiTableSqmMutationConverter sqmConverter,
TableGroup mutatingTableGroup,
Predicate suppliedPredicate,
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
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
assert mutatingTableGroup.getModelPart() instanceof EntityMappingType;
final EntityMappingType mutatingEntityDescriptor = (EntityMappingType) mutatingTableGroup.getModelPart();
final InsertSelectStatement idTableInsert = new InsertSelectStatement();
final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory );
idTableInsert.setTargetTable( idTableReference );
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
final IdTableColumn column = idTable.getIdTableColumns().get( i );
idTableInsert.addTargetColumnReferences(
new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory )
);
}
final QuerySpec matchingIdSelection = new QuerySpec( true, 1 );
idTableInsert.setSourceSelectStatement( matchingIdSelection );
matchingIdSelection.getFromClause().addRoot( mutatingTableGroup );
final AtomicInteger positionWrapper = new AtomicInteger();
mutatingEntityDescriptor.getIdentifierMapping().visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> {
final int jdbcPosition = positionWrapper.getAndIncrement();
final TableReference tableReference = mutatingTableGroup.resolveTableReference( containingTableExpression );
matchingIdSelection.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
jdbcPosition,
jdbcPosition + 1,
sqmConverter.getSqlExpressionResolver().resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ),
sqlAstProcessingState -> new ColumnReference(
tableReference,
columnExpression,
jdbcMapping,
factory
)
),
jdbcMapping
)
);
}
);
if ( idTable.getSessionUidColumn() != null ) {
//noinspection unchecked
sqmIdSelect.getSelectClause().add(
new SqmSelection(
new SqmLiteral(
final int jdbcPosition = positionWrapper.getAndIncrement();
matchingIdSelection.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
jdbcPosition,
jdbcPosition + 1,
new QueryLiteral(
sessionUidAccess.apply( executionContext.getSession() ),
UUIDCharType.INSTANCE,
executionContext.getSession().getFactory().getNodeBuilder()
StandardBasicTypes.STRING
),
null,
executionContext.getSession().getFactory().getNodeBuilder()
idTable.getSessionUidColumn().getJdbcMapping()
)
);
}
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 );
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 );
insertSelectStatement.addTargetColumnReferences(
new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory )
);
}
matchingIdSelection.applyPredicate( suppliedPredicate );
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
);
}
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 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 );
final JdbcInsert jdbcInsert = sqlAstTranslator.translate( idTableInsert );
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcInsert,
@ -302,8 +220,7 @@ public final class ExecuteWithIdTableHelper {
ComparisonOperator.EQUAL,
new QueryLiteral(
sessionUidAccess.apply( executionContext.getSession() ),
UUIDCharType.INSTANCE,
Clause.WHERE
UUIDCharType.INSTANCE
)
)
);
@ -317,12 +234,23 @@ public final class ExecuteWithIdTableHelper {
TempTableDdlTransactionHandling ddlTransactionHandling,
ExecutionContext executionContext) {
if ( beforeUseAction == BeforeUseAction.CREATE ) {
IdTableHelper.createIdTable(
final IdTableHelper.IdTableCreationWork idTableCreationWork = new IdTableHelper.IdTableCreationWork(
idTable,
idTableExporterAccess.get(),
ddlTransactionHandling,
executionContext.getSession()
executionContext.getSession().getFactory()
);
if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) {
( (SessionImplementor) executionContext.getSession() ).doWork( idTableCreationWork );
}
else {
final IsolationDelegate isolationDelegate = executionContext.getSession()
.getJdbcCoordinator()
.getJdbcSessionOwner()
.getTransactionCoordinator()
.createIsolationDelegate();
isolationDelegate.delegateWork( idTableCreationWork, ddlTransactionHandling == TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT );
}
}
}
@ -342,12 +270,23 @@ public final class ExecuteWithIdTableHelper {
);
}
else if ( afterUseAction == AfterUseAction.DROP ) {
IdTableHelper.dropIdTable(
final IdTableHelper.IdTableDropWork idTableDropWork = new IdTableHelper.IdTableDropWork(
idTable,
idTableExporterAccess.get(),
ddlTransactionHandling,
executionContext.getSession()
executionContext.getSession().getFactory()
);
if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) {
( (SessionImplementor) executionContext.getSession() ).doWork( idTableDropWork );
}
else {
final IsolationDelegate isolationDelegate = executionContext.getSession()
.getJdbcCoordinator()
.getJdbcSessionOwner()
.getTransactionCoordinator()
.createIsolationDelegate();
isolationDelegate.delegateWork( idTableDropWork, ddlTransactionHandling == TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT );
}
}
}
}

View File

@ -6,14 +6,23 @@
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import org.hibernate.NotYetImplementedFor6Exception;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Supplier;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
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.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.jboss.logging.Logger;
/**
* Strategy based on ANSI SQL's definition of a "global temporary table".
@ -21,22 +30,168 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
* @author Steve Ebersole
*/
public class GlobalTemporaryTableStrategy implements SqmMultiTableMutationStrategy {
private static final Logger log = Logger.getLogger( GlobalTemporaryTableStrategy.class );
public static final String SHORT_NAME = "global_temporary";
public static final String DROP_ID_TABLES = "hibernate.hql.bulk_id_strategy.global_temporary.drop_tables";
@Override
public UpdateHandler buildUpdateHandler(
SqmUpdateStatement sqmUpdateStatement,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
private final IdTable idTable;
private final AfterUseAction afterUseAction;
private final Supplier<IdTableExporter> idTableExporterAccess;
private final SessionFactoryImplementor sessionFactory;
private boolean prepared;
private boolean created;
private boolean released;
public GlobalTemporaryTableStrategy(
IdTable idTable,
Supplier<IdTableExporter> idTableExporterAccess,
AfterUseAction afterUseAction,
SessionFactoryImplementor sessionFactory) {
this.idTable = idTable;
this.idTableExporterAccess = idTableExporterAccess;
this.afterUseAction = afterUseAction;
this.sessionFactory = sessionFactory;
if ( afterUseAction == AfterUseAction.DROP ) {
throw new IllegalArgumentException( "Global-temp ID tables cannot use AfterUseAction.DROP : " + idTable.getTableExpression() );
}
}
@Override
public DeleteHandler buildDeleteHandler(
SqmDeleteStatement sqmDeleteStatement,
public int executeUpdate(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
ExecutionContext context) {
return new TableBasedUpdateHandler(
sqmUpdate,
domainParameterXref,
idTable,
// generally a global temp table should already track a Connection-specific uid,
// but just in case a particular env needs it...
session -> session.getSessionIdentifier().toString(),
idTableExporterAccess,
BeforeUseAction.CREATE,
afterUseAction,
TempTableDdlTransactionHandling.NONE,
sessionFactory
).execute( context );
}
@Override
public int executeDelete(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
return new TableBasedDeleteHandler(
sqmDelete,
domainParameterXref,
idTable,
// generally a global temp table should already track a Connection-specific uid,
// but just in case a particular env needs it...
session -> session.getSessionIdentifier().toString(),
idTableExporterAccess,
BeforeUseAction.CREATE,
afterUseAction,
TempTableDdlTransactionHandling.NONE,
sessionFactory
).execute( context );
}
@Override
public void prepare(
MappingModelCreationProcess mappingModelCreationProcess,
JdbcConnectionAccess connectionAccess) {
if ( prepared ) {
return;
}
prepared = true;
log.debugf( "Creating global-temp ID table : %s", idTable.getTableExpression() );
final IdTableHelper.IdTableCreationWork idTableCreationWork = new IdTableHelper.IdTableCreationWork(
idTable,
idTableExporterAccess.get(),
sessionFactory
);
Connection connection;
try {
connection = connectionAccess.obtainConnection();
}
catch (UnsupportedOperationException e) {
// assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl
log.debug( "Unable to obtain JDBC connection; assuming ID tables already exist or wont be needed" );
return;
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
return;
}
try {
idTableCreationWork.execute( connection );
created = true;
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
if ( created ) {
// todo (6.0) : register strategy for dropping of the table if requested - DROP_ID_TABLES
}
}
@Override
public void release(
SessionFactoryImplementor sessionFactory,
JdbcConnectionAccess connectionAccess) {
if ( released ) {
return;
}
released = true;
if ( ! created ) {
return;
}
log.debugf( "Dropping global-temp ID table : %s", idTable.getTableExpression() );
final IdTableHelper.IdTableDropWork idTableDropWork = new IdTableHelper.IdTableDropWork(
idTable,
idTableExporterAccess.get(),
sessionFactory
);
Connection connection;
try {
connection = connectionAccess.obtainConnection();
}
catch (UnsupportedOperationException e) {
// assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl
log.debugf( "Unable to obtain JDBC connection; unable to drop global-temp ID table : %s", idTable.getTableExpression() );
return;
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
return;
}
try {
idTableDropWork.execute( connection );
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
}
}

View File

@ -13,11 +13,11 @@ import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.function.Function;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.internal.FormatStyle;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.engine.jdbc.spi.SqlStatementLogger;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
@ -26,6 +26,7 @@ import org.hibernate.jdbc.AbstractWork;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
public class IdTableHelper {
private final static CoreMessageLogger log = CoreLogging.messageLogger( IdTableHelper.class );
@ -34,35 +35,23 @@ public class IdTableHelper {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Creation
public static void createIdTable(
IdTable idTable,
IdTableExporter exporter,
TempTableDdlTransactionHandling ddlTransactionHandling,
SharedSessionContractImplementor session) {
executeWork(
new IdTableCreationWork( idTable, exporter, session ),
ddlTransactionHandling,
session
);
}
private static class IdTableCreationWork extends AbstractWork {
public static class IdTableCreationWork extends AbstractWork {
private final IdTable idTable;
private final IdTableExporter exporter;
private final SharedSessionContractImplementor session;
private final SessionFactoryImplementor sessionFactory;
IdTableCreationWork(
public IdTableCreationWork(
IdTable idTable,
IdTableExporter exporter,
SharedSessionContractImplementor session) {
SessionFactoryImplementor sessionFactory) {
this.idTable = idTable;
this.exporter = exporter;
this.session = session;
this.sessionFactory = sessionFactory;
}
@Override
public void execute(Connection connection) {
final JdbcServices jdbcServices = session.getFactory().getJdbcServices();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
try {
final String creationCommand = exporter.getSqlCreateCommand( idTable );
@ -91,35 +80,23 @@ public class IdTableHelper {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Drop
public static void dropIdTable(
IdTable idTable,
IdTableExporter exporter,
TempTableDdlTransactionHandling ddlTransactionHandling,
SharedSessionContractImplementor session) {
executeWork(
new IdTableDropWork( idTable, exporter, session ),
ddlTransactionHandling,
session
);
}
private static class IdTableDropWork extends AbstractWork {
public static class IdTableDropWork extends AbstractWork {
private final IdTable idTable;
private final IdTableExporter exporter;
private final SharedSessionContractImplementor session;
private final SessionFactoryImplementor sessionFactory;
IdTableDropWork(
IdTable idTable,
IdTableExporter exporter,
SharedSessionContractImplementor session) {
SessionFactoryImplementor sessionFactory) {
this.idTable = idTable;
this.exporter = exporter;
this.session = session;
this.sessionFactory = sessionFactory;
}
@Override
public void execute(Connection connection) {
final JdbcServices jdbcServices = session.getFactory().getJdbcServices();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
try {
final String dropCommand = exporter.getSqlCreateCommand( idTable );
@ -182,35 +159,6 @@ public class IdTableHelper {
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Misc
private static void executeWork(
AbstractWork work,
TempTableDdlTransactionHandling ddlTransactionHandling,
SharedSessionContractImplementor session) {
if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) {
// simply execute the work using a Connection obtained from JdbcConnectionAccess
//
// NOTE : we do not (potentially) release the Connection here
// via LogicalConnectionImplementor#afterStatement because
// for sure we will be immediately using it again to
// populate the id table and use it...
try {
work.execute( session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() );
}
catch (SQLException e) {
log.error( "Unable to use JDBC Connection to create perform id table management", e );
}
}
else {
session.getTransactionCoordinator()
.createIsolationDelegate()
.delegateWork( work, ddlTransactionHandling == TempTableDdlTransactionHandling.ISOLATE_AND_TRANSACT );
}
}
private static SqlExceptionHelper.WarningHandler WARNING_HANDLER = new SqlExceptionHelper.WarningHandlerLoggingSupport() {
public boolean doProcess() {
return log.isDebugEnabled();

View File

@ -10,14 +10,12 @@ import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
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.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* Strategy based on ANSI SQL's definition of a "local temporary table" (local to each db session).
@ -31,63 +29,73 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg
private final Supplier<IdTableExporter> idTableExporterAccess;
private final AfterUseAction afterUseAction;
private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final SessionFactoryImplementor sessionFactory;
public LocalTemporaryTableStrategy(
IdTable idTable,
Supplier<IdTableExporter> idTableExporterAccess,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling) {
TempTableDdlTransactionHandling ddlTransactionHandling,
SessionFactoryImplementor sessionFactory) {
this.idTable = idTable;
this.idTableExporterAccess = idTableExporterAccess;
this.afterUseAction = afterUseAction;
this.ddlTransactionHandling = ddlTransactionHandling;
this.sessionFactory = sessionFactory;
}
public LocalTemporaryTableStrategy(
IdTable idTable,
Function<Integer, String> databaseTypeNameResolver,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling) {
TempTableDdlTransactionHandling ddlTransactionHandling,
SessionFactoryImplementor sessionFactory) {
this(
idTable,
() -> new TempIdTableExporter( true, databaseTypeNameResolver ),
afterUseAction,
ddlTransactionHandling
);
}
@Override
public UpdateHandler buildUpdateHandler(
SqmUpdateStatement sqmUpdateStatement,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
return new TableBasedUpdateHandler(
sqmUpdateStatement,
domainParameterXref, idTable,
SharedSessionContractImplementor::getTenantIdentifier,
idTableExporterAccess,
BeforeUseAction.CREATE,
afterUseAction,
ddlTransactionHandling,
creationContext
sessionFactory
);
}
@Override
public DeleteHandler buildDeleteHandler(
SqmDeleteStatement sqmDeleteStatement,
public int executeUpdate(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
return new TableBasedDeleteHandler(
sqmDeleteStatement,
ExecutionContext context) {
return new TableBasedUpdateHandler(
sqmUpdate,
domainParameterXref,
idTable,
SharedSessionContractImplementor::getTenantIdentifier,
session -> {
throw new UnsupportedOperationException( "Unexpected call to access Session uid" );
},
idTableExporterAccess,
BeforeUseAction.CREATE,
afterUseAction,
ddlTransactionHandling,
creationContext
);
sessionFactory
).execute( context );
}
@Override
public int executeDelete(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
return new TableBasedDeleteHandler(
sqmDelete,
domainParameterXref,
idTable,
session -> {
throw new UnsupportedOperationException( "Unexpected call to access Session uid" );
},
idTableExporterAccess,
BeforeUseAction.CREATE,
afterUseAction,
ddlTransactionHandling,
sessionFactory
).execute( context );
}
}

View File

@ -6,14 +6,21 @@
*/
package org.hibernate.query.sqm.mutation.internal.idtable;
import org.hibernate.NotYetImplementedFor6Exception;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Supplier;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
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.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.UpdateHandler;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.jboss.logging.Logger;
/**
* This is a strategy that mimics temporary tables for databases which do not support
@ -23,6 +30,8 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
* @author Steve Ebersole
*/
public class PersistentTableStrategy implements SqmMultiTableMutationStrategy {
private static final Logger log = Logger.getLogger( PersistentTableStrategy.class );
public static final String SHORT_NAME = "persistent";
public static final String DROP_ID_TABLES = "hibernate.hql.bulk_id_strategy.persistent.drop_tables";
@ -30,19 +39,160 @@ public class PersistentTableStrategy implements SqmMultiTableMutationStrategy {
public static final String SCHEMA = "hibernate.hql.bulk_id_strategy.persistent.schema";
public static final String CATALOG = "hibernate.hql.bulk_id_strategy.persistent.catalog";
@Override
public UpdateHandler buildUpdateHandler(
SqmUpdateStatement sqmUpdateStatement,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
private final IdTable idTable;
private final AfterUseAction afterUseAction;
private final Supplier<IdTableExporter> idTableExporterAccess;
private final SessionFactoryImplementor sessionFactory;
private boolean prepared;
private boolean created;
private boolean released;
public PersistentTableStrategy(
IdTable idTable,
AfterUseAction afterUseAction,
Supplier<IdTableExporter> idTableExporterAccess,
SessionFactoryImplementor sessionFactory) {
this.idTable = idTable;
this.afterUseAction = afterUseAction;
this.idTableExporterAccess = idTableExporterAccess;
this.sessionFactory = sessionFactory;
if ( afterUseAction == AfterUseAction.DROP ) {
throw new IllegalArgumentException( "Persistent ID tables cannot use AfterUseAction.DROP : " + idTable.getTableExpression() );
}
}
@Override
public DeleteHandler buildDeleteHandler(
SqmDeleteStatement sqmDeleteStatement,
public int executeUpdate(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
ExecutionContext context) {
return new TableBasedUpdateHandler(
sqmUpdate,
domainParameterXref,
idTable,
session -> session.getSessionIdentifier().toString(),
idTableExporterAccess,
BeforeUseAction.CREATE,
afterUseAction,
TempTableDdlTransactionHandling.NONE,
sessionFactory
).execute( context );
}
@Override
public int executeDelete(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
return new TableBasedDeleteHandler(
sqmDelete,
domainParameterXref,
idTable,
session -> session.getSessionIdentifier().toString(),
idTableExporterAccess,
BeforeUseAction.CREATE,
afterUseAction,
TempTableDdlTransactionHandling.NONE,
sessionFactory
).execute( context );
}
@Override
public void prepare(
MappingModelCreationProcess mappingModelCreationProcess,
JdbcConnectionAccess connectionAccess) {
if ( prepared ) {
return;
}
prepared = true;
log.debugf( "Creating persistent ID table : %s", idTable.getTableExpression() );
final IdTableHelper.IdTableCreationWork idTableCreationWork = new IdTableHelper.IdTableCreationWork(
idTable,
idTableExporterAccess.get(),
sessionFactory
);
Connection connection;
try {
connection = connectionAccess.obtainConnection();
}
catch (UnsupportedOperationException e) {
// assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl
log.debug( "Unable to obtain JDBC connection; assuming ID tables already exist or wont be needed" );
return;
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
return;
}
try {
idTableCreationWork.execute( connection );
created = true;
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
if ( created ) {
// todo (6.0) : register strategy for dropping of the table if requested - DROP_ID_TABLES
}
}
@Override
public void release(
SessionFactoryImplementor sessionFactory,
JdbcConnectionAccess connectionAccess) {
if ( released ) {
return;
}
released = true;
if ( created ) {
return;
}
log.debugf( "Dropping persistent ID table : %s", idTable.getTableExpression() );
final IdTableHelper.IdTableDropWork idTableDropWork = new IdTableHelper.IdTableDropWork(
idTable,
idTableExporterAccess.get(),
sessionFactory
);
Connection connection;
try {
connection = connectionAccess.obtainConnection();
}
catch (UnsupportedOperationException e) {
// assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl
log.debugf( "Unable to obtain JDBC connection; unable to drop persistent ID table : %s", idTable.getTableExpression() );
return;
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
return;
}
try {
idTableDropWork.execute( connection );
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
}
}

View File

@ -7,40 +7,32 @@
package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.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.mutation.internal.MultiTableSqmMutationConverter;
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;
@ -76,7 +68,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
private final Supplier<IdTableExporter> idTableExporterAccess;
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
private final MultiTableSqmMutationConverter converter;
@SuppressWarnings("WeakerAccess")
public RestrictedDeleteExecutionDelegate(
EntityMappingType entityDescriptor,
IdTable idTable,
@ -86,7 +80,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling,
Supplier<IdTableExporter> idTableExporterAccess,
Function<SharedSessionContractImplementor,String> sessionUidAccess,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
QueryOptions queryOptions,
QueryParameterBindings queryParameterBindings,
SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
this.idTable = idTable;
@ -98,54 +94,35 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
this.idTableExporterAccess = idTableExporterAccess;
this.sessionUidAccess = sessionUidAccess;
this.sessionFactory = sessionFactory;
converter = new MultiTableSqmMutationConverter(
entityDescriptor,
domainParameterXref,
queryOptions,
queryParameterBindings,
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 TableGroup deletingTableGroup = converter.getMutatingTableGroup();
final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( hierarchyRootTableName );
assert hierarchyRootTableReference != null;
final Map<SqmParameter, List<JdbcParameter>> parameterResolutions;
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
parameterResolutions = Collections.emptyMap();
}
else {
parameterResolutions = new IdentityHashMap<>();
}
// Use the converter to interpret the where-clause. We do this for 2 reasons:
// 1) the resolved Predicate is ultimately the base for applying restriction to the deletes
// 2) we also inspect each ColumnReference that is part of the where-clause to see which
@ -158,7 +135,8 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
needsIdTableWrapper.set( true );
}
}
},
parameterResolutions::put
);
boolean needsIdTable = needsIdTableWrapper.get();
@ -167,7 +145,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
return executeWithIdTable(
predicate,
deletingTableGroup,
converter.getRestrictionSqmParameterResolutions(),
parameterResolutions,
executionContext
);
}
@ -175,7 +153,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
return executeWithoutIdTable(
predicate,
deletingTableGroup,
converter.getRestrictionSqmParameterResolutions(),
parameterResolutions,
converter.getSqlExpressionResolver(),
executionContext
);
@ -399,11 +377,10 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
ExecutionContext executionContext,
JdbcParameterBindings jdbcParameterBindings) {
final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable(
sqmDelete,
converter,
predicate,
idTable,
sessionUidAccess,
domainParameterXref,
jdbcParameterBindings,
executionContext
);
@ -468,86 +445,4 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
);
}
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

@ -10,11 +10,11 @@ import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.boot.TempTableDdlTransactionHandling;
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.internal.DeleteHandler;
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.exec.spi.ExecutionContext;
@ -50,8 +50,8 @@ public class TableBasedDeleteHandler
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling,
HandlerCreationContext creationContext) {
super( sqmDeleteStatement, creationContext );
SessionFactoryImplementor sessionFactory) {
super( sqmDeleteStatement, sessionFactory );
this.idTable = idTable;
this.ddlTransactionHandling = ddlTransactionHandling;
this.beforeUseAction = beforeUseAction;
@ -85,6 +85,8 @@ public class TableBasedDeleteHandler
ddlTransactionHandling,
exporterSupplier,
sessionUidAccess,
executionContext.getQueryOptions(),
executionContext.getQueryParameterBindings(),
getSessionFactory()
);
}

View File

@ -16,7 +16,6 @@ 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;
@ -24,16 +23,14 @@ 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.internal.MultiTableSqmMutationConverter;
import org.hibernate.query.sqm.mutation.internal.UpdateHandler;
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;
@ -63,11 +60,12 @@ public class TableBasedUpdateHandler
private final AfterUseAction afterUseAction;
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
private final Supplier<IdTableExporter> exporterSupplier;
private final DomainParameterXref domainParameterXref;
private final EntityPersister entityDescriptor;
TableBasedUpdateHandler(
SqmUpdateStatement sqmDeleteStatement,
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
IdTable idTable,
Function<SharedSessionContractImplementor, String> sessionUidAccess,
@ -75,8 +73,8 @@ public class TableBasedUpdateHandler
BeforeUseAction beforeUseAction,
AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling,
HandlerCreationContext creationContext) {
super( sqmDeleteStatement, creationContext );
SessionFactoryImplementor sessionFactory) {
super( sqmUpdate, sessionFactory );
this.idTable = idTable;
this.exporterSupplier = exporterSupplier;
this.beforeUseAction = beforeUseAction;
@ -84,6 +82,9 @@ public class TableBasedUpdateHandler
this.ddlTransactionHandling = ddlTransactionHandling;
this.sessionUidAccess = sessionUidAccess;
this.domainParameterXref = domainParameterXref;
final String targetEntityName = sqmUpdate.getTarget().getEntityName();
this.entityDescriptor = sessionFactory.getDomainModel().getEntityDescriptor( targetEntityName );
}
protected SqmUpdateStatement getSqmUpdate() {
@ -131,21 +132,7 @@ public class TableBasedUpdateHandler
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 TableGroup updatingTableGroup = converterDelegate.getMutatingTableGroup();
final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( hierarchyRootTableName );
assert hierarchyRootTableReference != null;

View File

@ -23,6 +23,7 @@ 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.mutation.internal.MultiTableSqmMutationConverter;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
@ -157,15 +158,11 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
);
try {
final int rows = ExecuteWithIdTableHelper.saveMatchingIdsIntoIdTable(
sqmUpdate,
sqmConverter,
updatingTableGroup,
suppliedPredicate,
idTable,
sessionUidAccess,
domainParameterXref,
jdbcParameterBindings,
executionContext
);

View File

@ -0,0 +1,121 @@
/*
* 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.inline;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Junction;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* MatchingIdRestrictionProducer producing a restriction based on a disjunction (OR) predicate. E.g.:
*
* ````
* delete
* from
* entity-table
* where
* ( id = 1 )
* or ( id = 2 )
* or ( id = 3 )
* or ( id = 4 )
* ````
*
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
public class DisjunctionRestrictionProducer implements MatchingIdRestrictionProducer {
@Override
public Junction produceRestriction(
List<?> matchingIdValues,
EntityMappingType entityDescriptor,
TableReference mutatingTableReference,
Supplier<Consumer<ColumnConsumer>> columnsToMatchVisitationSupplier,
ExecutionContext executionContext) {
assert matchingIdValues != null;
assert ! matchingIdValues.isEmpty();
final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory();
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
final int idColumnCount = identifierMapping.getJdbcTypeCount( sessionFactory.getTypeConfiguration() );
assert idColumnCount > 0;
final Junction predicate = new Junction( Junction.Nature.DISJUNCTION );
if ( idColumnCount == 1 ) {
final BasicValuedModelPart basicIdMapping = (BasicValuedModelPart) identifierMapping;
final String idColumn = basicIdMapping.getMappedColumnExpression();
final ColumnReference idColumnReference = new ColumnReference(
mutatingTableReference,
idColumn,
basicIdMapping.getJdbcMapping(),
sessionFactory
);
for ( int i = 0; i < matchingIdValues.size(); i++ ) {
final Object matchingId = matchingIdValues.get( i );
predicate.add(
new ComparisonPredicate(
idColumnReference,
ComparisonOperator.EQUAL,
new JdbcLiteral<>( matchingId, basicIdMapping.getJdbcMapping() )
)
);
}
}
else {
final List<ColumnReference> columnReferences = new ArrayList<>( idColumnCount );
final List<JdbcMapping> jdbcMappings = new ArrayList<>( idColumnCount );
identifierMapping.visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> {
columnReferences.add( new ColumnReference( mutatingTableReference, columnExpression, jdbcMapping, sessionFactory ) );
jdbcMappings.add( jdbcMapping );
}
);
for ( int i = 0; i < matchingIdValues.size(); i++ ) {
final Junction idMatch = new Junction( Junction.Nature.CONJUNCTION );
final Object matchingId = matchingIdValues.get( i );
assert matchingId instanceof Object[];
final Object[] matchingIdParts = (Object[]) matchingId;
for ( int p = 0; p < matchingIdParts.length; p++ ) {
idMatch.add(
new ComparisonPredicate(
columnReferences.get( p ),
ComparisonOperator.EQUAL,
new JdbcLiteral<>( matchingIdParts[ p ], jdbcMappings.get( p ) )
)
);
}
predicate.add( idMatch );
}
}
return predicate;
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.inline;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.InListPredicate;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* MatchingIdRestrictionProducer producing a restriction based on an in-values-list predicate. E.g.:
*
* ````
* delete
* from
* entity-table
* where
* ( id ) in (
* ( 1 ),
* ( 2 ),
* ( 3 ),
* ( 4 )
* )
* ````
*
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
public class InPredicateRestrictionProducer implements MatchingIdRestrictionProducer {
@Override
public InListPredicate produceRestriction(
List<?> matchingIdValues,
EntityMappingType entityDescriptor,
TableReference mutatingTableReference,
Supplier<Consumer<ColumnConsumer>> columnsToMatchVisitationSupplier,
ExecutionContext executionContext) {
assert matchingIdValues != null;
assert ! matchingIdValues.isEmpty();
final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory();
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
final int idColumnCount = identifierMapping.getJdbcTypeCount( sessionFactory.getTypeConfiguration() );
assert idColumnCount > 0;
final InListPredicate predicate;
if ( idColumnCount == 1 ) {
final BasicValuedModelPart basicIdMapping = (BasicValuedModelPart) identifierMapping;
final String idColumn = basicIdMapping.getMappedColumnExpression();
final Expression inFixture = new ColumnReference(
mutatingTableReference,
idColumn,
basicIdMapping.getJdbcMapping(),
sessionFactory
);
predicate = new InListPredicate( inFixture );
for ( int i = 0; i < matchingIdValues.size(); i++ ) {
final Object matchingId = matchingIdValues.get( i );
predicate.addExpression( new JdbcLiteral<>( matchingId, basicIdMapping.getJdbcMapping() ) );
}
}
else {
final List<ColumnReference> columnReferences = new ArrayList<>( idColumnCount );
final List<JdbcMapping> jdbcMappings = new ArrayList<>( idColumnCount );
identifierMapping.visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> {
columnReferences.add( new ColumnReference( mutatingTableReference, columnExpression, jdbcMapping, sessionFactory ) );
jdbcMappings.add( jdbcMapping );
}
);
final Expression inFixture = new SqlTuple( columnReferences, identifierMapping );
predicate = new InListPredicate( inFixture );
for ( int i = 0; i < matchingIdValues.size(); i++ ) {
final Object matchingId = matchingIdValues.get( i );
assert matchingId instanceof Object[];
final Object[] matchingIdParts = (Object[]) matchingId;
final List<JdbcLiteral> tupleParts = new ArrayList<>( idColumnCount );
for ( int p = 0; p < matchingIdParts.length; p++ ) {
tupleParts.add(
new JdbcLiteral<>( matchingIdParts[p],jdbcMappings.get( p ) )
);
}
predicate.addExpression( new SqlTuple( tupleParts, identifierMapping ) );
}
}
return predicate;
}
}

View File

@ -0,0 +1,176 @@
/*
* 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.inline;
import java.sql.PreparedStatement;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.internal.DeleteHandler;
import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper;
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.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcDelete;
import org.hibernate.sql.exec.spi.JdbcMutationExecutor;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.StatementCreatorHelper;
/**
* DeleteHandler for the in-line strategy
*
* @author Evandro Pires da Silva
* @author Vlad Mihalcea
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
public class InlineDeleteHandler implements DeleteHandler {
private final MatchingIdRestrictionProducer matchingIdsPredicateProducer;
private final SqmDeleteStatement sqmDeleteStatement;
private final DomainParameterXref domainParameterXref;
private final ExecutionContext executionContext;
private final SessionFactoryImplementor sessionFactory;
private final SqlAstTranslatorFactory sqlAstTranslatorFactory;
private final JdbcMutationExecutor jdbcMutationExecutor;
protected InlineDeleteHandler(
MatchingIdRestrictionProducer matchingIdsPredicateProducer,
SqmDeleteStatement sqmDeleteStatement,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
this.sqmDeleteStatement = sqmDeleteStatement;
this.domainParameterXref = domainParameterXref;
this.matchingIdsPredicateProducer = matchingIdsPredicateProducer;
this.executionContext = context;
this.sessionFactory = executionContext.getSession().getFactory();
this.sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory();
this.jdbcMutationExecutor = sessionFactory.getJdbcServices().getJdbcMutationExecutor();
}
@Override
public int execute(ExecutionContext executionContext) {
final List<Object> ids = MatchingIdSelectionHelper.selectMatchingIds(
sqmDeleteStatement,
domainParameterXref,
executionContext
);
if ( ids == null || ids.isEmpty() ) {
return 0;
}
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final String mutatingEntityName = sqmDeleteStatement.getTarget().getModel().getHibernateEntityName();
final EntityMappingType entityDescriptor = factory.getDomainModel().getEntityDescriptor( mutatingEntityName );
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( domainParameterXref.getQueryParameterCount() );
// delete from the tables
entityDescriptor.visitAttributeMappings(
attribute -> {
if ( attribute instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) attribute;
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
// this collection has a separate collection table, meaning it is one of:
// 1) element-collection
// 2) many-to-many
// 3) one-to many using a dedicated join-table
//
// in all of these cases, we should clean up the matching rows in the
// collection table
// todo (6.0) : implement this
// executeDelete(
// pluralAttribute.getSeparateCollectionTable(),
// matchingIdsPredicateProducer.produceRestriction(
// ids,
// () -> columnConsumer -> ,
// executionContext
// ),
// jdbcParameterBindings,
// executionContext
// );
}
}
}
);
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
executeDelete(
tableExpression,
entityDescriptor,
tableKeyColumnsVisitationSupplier,
ids,
jdbcParameterBindings,
executionContext
);
}
);
return ids.size();
}
private void executeDelete(
String targetTableExpression,
EntityMappingType entityDescriptor,
Supplier<Consumer<ColumnConsumer>> tableKeyColumnsVisitationSupplier,
List<Object> ids,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
final TableReference targetTableReference = new TableReference(
targetTableExpression,
null,
false,
sessionFactory
);
final Predicate matchingIdsPredicate = matchingIdsPredicateProducer.produceRestriction(
ids,
entityDescriptor,
targetTableReference,
tableKeyColumnsVisitationSupplier,
executionContext
);
final DeleteStatement deleteStatement = new DeleteStatement( targetTableReference, matchingIdsPredicate );
final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( sessionFactory );
final JdbcDelete jdbcOperation = sqlAstTranslator.translate( deleteStatement );
jdbcMutationExecutor.execute(
jdbcOperation,
jdbcParameterBindings,
this::prepareQueryStatement,
(integer, preparedStatement) -> {},
executionContext
);
}
private PreparedStatement prepareQueryStatement(String sql) {
return StatementCreatorHelper.prepareQueryStatement( sql, executionContext.getSession() );
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.inline;
import java.util.function.Function;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.dialect.Dialect;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* Support for multi-table SQM mutation operations which select the matching id values from the database back into
* the VM and uses that list of values to produce a restriction for the mutations. The exact form of that
* restriction is based on the {@link MatchingIdRestrictionProducer} implementation used
*
* @author Vlad Mihalcea
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
public class InlineStrategy implements SqmMultiTableMutationStrategy {
private final Function<SqmDeleteOrUpdateStatement,MatchingIdRestrictionProducer> matchingIdsStrategy;
public InlineStrategy(Dialect dialect) {
this( determinePredicateProducer( dialect ) );
}
private static Function<SqmDeleteOrUpdateStatement,MatchingIdRestrictionProducer> determinePredicateProducer(Dialect dialect) {
throw new NotYetImplementedFor6Exception();
}
public InlineStrategy(Function<SqmDeleteOrUpdateStatement,MatchingIdRestrictionProducer> matchingIdsStrategy) {
this.matchingIdsStrategy = matchingIdsStrategy;
}
@Override
public int executeUpdate(
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
final InlineUpdateHandler handler = new InlineUpdateHandler(
matchingIdsStrategy.apply( sqmUpdate ),
sqmUpdate,
domainParameterXref,
context
);
return handler.execute( context );
}
@Override
public int executeDelete(
SqmDeleteStatement sqmDelete,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
final InlineDeleteHandler deleteHandler = new InlineDeleteHandler(
matchingIdsStrategy.apply( sqmDelete ),
sqmDelete,
domainParameterXref,
context
);
return deleteHandler.execute( context );
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.inline;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.internal.UpdateHandler;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcMutationExecutor;
/**
* @author Steve Ebersole
*/
public class InlineUpdateHandler implements UpdateHandler {
private final SqmUpdateStatement sqmUpdate;
private final DomainParameterXref domainParameterXref;
private final MatchingIdRestrictionProducer matchingIdsPredicateProducer;
private final ExecutionContext executionContext;
private final SessionFactoryImplementor sessionFactory;
private final SqlAstTranslatorFactory sqlAstTranslatorFactory;
private final JdbcMutationExecutor jdbcMutationExecutor;
public InlineUpdateHandler(
MatchingIdRestrictionProducer matchingIdsPredicateProducer,
SqmUpdateStatement sqmUpdate,
DomainParameterXref domainParameterXref,
ExecutionContext context) {
this.matchingIdsPredicateProducer = matchingIdsPredicateProducer;
this.domainParameterXref = domainParameterXref;
this.sqmUpdate = sqmUpdate;
this.executionContext = context;
this.sessionFactory = executionContext.getSession().getFactory();
this.sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory();
this.jdbcMutationExecutor = sessionFactory.getJdbcServices().getJdbcMutationExecutor();
}
@Override
public int execute(ExecutionContext executionContext) {
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.inline;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* Strategy (pattern) for producing the restriction used when mutating a
* particular table in the group of tables
*
* @author Steve Ebersole
*/
public interface MatchingIdRestrictionProducer {
/**
* Produce the restriction predicate
*
* @param matchingIdValues The matching id values.
* @param mutatingTableReference The TableReference for the table being mutated
* @param columnsToMatchVisitationSupplier The columns against which to restrict the mutations
*/
Predicate produceRestriction(
List<?> matchingIdValues,
EntityMappingType entityDescriptor,
TableReference mutatingTableReference,
Supplier<Consumer<ColumnConsumer>> columnsToMatchVisitationSupplier,
ExecutionContext executionContext);
}

View File

@ -0,0 +1,57 @@
/*
* 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.inline;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* MatchingIdRestrictionProducer producing a restriction based on a SQL table-value-constructor. E.g.:
*
* ````
* delete
* from
* entity-table
* where
* ( id ) in (
* select
* id
* from (
* values
* ( 1 ),
* ( 2 ),
* ( 3 ),
* ( 4 )
* ) as HT (id)
* )
* ````
*
* @author Vlad Mihalcea
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
public class TableValueConstructorRestrictionProducer implements MatchingIdRestrictionProducer {
@Override
public InSubQueryPredicate produceRestriction(
List<?> matchingIdValues,
EntityMappingType entityDescriptor,
TableReference mutatingTableReference,
Supplier<Consumer<ColumnConsumer>> columnsToMatchVisitationSupplier,
ExecutionContext executionContext) {
// Not "yet" implemented. Not sure we will. This requires the ability to define
// "in-line views" with a table-ctor which the SQL AST does not yet define support for
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -8,6 +8,7 @@ package org.hibernate.query.sqm.mutation.spi;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.query.sqm.mutation.internal.Handler;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
/**
@ -21,9 +22,9 @@ public abstract class AbstractMutationHandler implements Handler {
public AbstractMutationHandler(
SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement,
HandlerCreationContext creationContext) {
SessionFactoryImplementor sessionFactory) {
this.sqmDeleteOrUpdateStatement = sqmDeleteOrUpdateStatement;
this.sessionFactory = creationContext.getSessionFactory();
this.sessionFactory = sessionFactory;
final String entityName = sqmDeleteOrUpdateStatement.getTarget()
.getReferencedPathSource()

View File

@ -1,21 +0,0 @@
/*
* 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.spi;
import org.hibernate.engine.spi.SessionFactoryImplementor;
/**
* Parameter object (pattern) for contextual information for
* {@link SqmMultiTableMutationStrategy#buildUpdateHandler} and
* {@link SqmMultiTableMutationStrategy#buildDeleteHandler}
*/
public interface HandlerCreationContext {
/**
* Access to the SessionFactory
*/
SessionFactoryImplementor getSessionFactory();
}

View File

@ -9,27 +9,24 @@ package org.hibernate.query.sqm.mutation.spi;
import org.hibernate.Metamodel;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext;
/**
* Pluggable strategy for defining how mutation (`UPDATE` or `DELETE`)
* queries should be handled when the target entity is mapped to multiple
* tables (generally via secondary tables or joined-inheritance).
* Pluggable strategy for defining how mutation (`UPDATE` or `DELETE`) queries should be handled when the target
* entity is mapped to multiple tables via secondary tables or certain inheritance strategies.
*
* {@link #prepare} and {@link #release} allow the strategy to perform
* any one time preparation and cleanup.
* The main contracts here are {@link #executeUpdate} and {@link #executeDelete}.
*
* The heavy lifting is handled by the {@link UpdateHandler} and {@link DeleteHandler}
* delegates obtained via {@link #buildUpdateHandler} and {@link #buildDeleteHandler}
* methods.
* {@link #prepare} and {@link #release} allow the strategy to perform any one time preparation and cleanup.
*
* @apiNote See {@link SqmMutationStrategyHelper#resolveStrategy} for standard resolution
* of the strategy to use. See also {@link SqmMutationStrategyHelper#resolveDeleteHandler}
* and {@link SqmMutationStrategyHelper#resolveUpdateHandler} for standard resolution of
* the delete and update handler to use applying standard special-case handling
* @apiNote See {@link SqmMutationStrategyHelper#resolveStrategy} for standard resolution of the strategy to use
* for each hierarchy
*
* @author Steve Ebersole
*/
@ -40,8 +37,7 @@ public interface SqmMultiTableMutationStrategy {
* is being built.
*/
default void prepare(
Metamodel runtimeMetadata,
SessionFactoryOptions sessionFactoryOptions,
MappingModelCreationProcess mappingModelCreationProcess,
JdbcConnectionAccess connectionAccess) {
// by default, nothing to do...
}
@ -49,35 +45,28 @@ public interface SqmMultiTableMutationStrategy {
/**
* Release the strategy. Called one time as the SessionFactory is
* being shut down.
*
* @param runtimeMetadata Access to the runtime mappings
* @param connectionAccess Access to the JDBC Connection
*/
default void release(Metamodel runtimeMetadata, JdbcConnectionAccess connectionAccess) {
default void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAccess connectionAccess) {
// by default, nothing to do...
}
/**
* Build a handler capable of handling the update query indicated by the given SQM tree.
* Execute the multi-table update indicated by the passed SqmUpdateStatement
*
* @param sqmUpdateStatement The SQM AST representing the update query
* @param domainParameterXref cross references between SqmParameters and QueryParameters
* @param creationContext Context info for the creation
* @return The number of rows affected
*/
UpdateHandler buildUpdateHandler(
int executeUpdate(
SqmUpdateStatement sqmUpdateStatement,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext);
ExecutionContext context);
/**
* Build a handler capable of handling the delete query indicated by the given SQM tree.
* Execute the multi-table update indicated by the passed SqmUpdateStatement
*
* @param sqmDeleteStatement The SQM AST representing the delete query
* @param domainParameterXref cross references between SqmParameters
* @param creationContext Context info for the creation
* @return The number of rows affected
*/
DeleteHandler buildDeleteHandler(
int executeDelete(
SqmDeleteStatement sqmDeleteStatement,
DomainParameterXref domainParameterXref,
HandlerCreationContext creationContext);
ExecutionContext context);
}

View File

@ -764,8 +764,7 @@ public abstract class BaseSqmToSqlAstConverter
literal,
getCreationContext().getDomainModel(),
getFromClauseAccess()::findTableGroup
),
getCurrentClauseStack().getCurrent()
)
);
}
@ -1450,8 +1449,7 @@ public abstract class BaseSqmToSqlAstConverter
public Object visitEnumLiteral(SqmEnumLiteral sqmEnumLiteral) {
return new QueryLiteral(
sqmEnumLiteral.getEnumValue(),
(BasicValuedMapping) determineValueMapping( sqmEnumLiteral ),
getCurrentClauseStack().getCurrent()
(BasicValuedMapping) determineValueMapping( sqmEnumLiteral )
);
}
@ -1459,8 +1457,7 @@ public abstract class BaseSqmToSqlAstConverter
public Object visitFieldLiteral(SqmFieldLiteral sqmFieldLiteral) {
return new QueryLiteral(
sqmFieldLiteral.getValue(),
(BasicValuedMapping) determineValueMapping( sqmFieldLiteral ),
getCurrentClauseStack().getCurrent()
(BasicValuedMapping) determineValueMapping( sqmFieldLiteral )
);
}

View File

@ -21,6 +21,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -445,9 +446,15 @@ public class SqlTreePrinter implements SqlAstWalker {
// logNode( "positional-param (%s)", parameter.getPosition() );
// }
@Override
public void visitJdbcLiteral(JdbcLiteral jdbcLiteral) {
logNode( "literal (" + jdbcLiteral.getLiteralValue() + ')' );
}
@Override
public void visitQueryLiteral(QueryLiteral queryLiteral) {
logNode( "literal (" + queryLiteral.getValue() + ')' );
logNode( "literal (" + queryLiteral.getLiteralValue() + ')' );
}
@Override

View File

@ -16,6 +16,7 @@ import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.QueryLiteralRendering;
import org.hibernate.query.UnaryArithmeticOperator;
import org.hibernate.sql.ast.Clause;
@ -25,6 +26,8 @@ import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -792,6 +795,12 @@ public abstract class AbstractSqlAstWalker
// visitJdbcParameterBinder( positionalParameter );
// }
@Override
public void visitJdbcLiteral(JdbcLiteral jdbcLiteral) {
renderAsLiteral( jdbcLiteral );
}
@Override
public void visitQueryLiteral(QueryLiteral queryLiteral) {
final QueryLiteralRendering queryLiteralRendering = getSessionFactory().getSessionFactoryOptions().getQueryLiteralRenderingMode();
@ -807,7 +816,7 @@ public abstract class AbstractSqlAstWalker
}
case AUTO:
case AS_PARAM_OUTSIDE_SELECT: {
if ( queryLiteral.isInSelect() ) {
if ( clauseStack.getCurrent() == Clause.SELECT ) {
renderAsLiteral( queryLiteral );
}
else {
@ -824,25 +833,21 @@ public abstract class AbstractSqlAstWalker
}
@SuppressWarnings("unchecked")
private void renderAsLiteral(QueryLiteral<?> queryLiteral) {
if ( queryLiteral.getValue() == null ) {
private void renderAsLiteral(Literal literal) {
if ( literal.getLiteralValue() == null ) {
// todo : not sure we allow this "higher up"
appendSql( SqlAppender.NULL_KEYWORD );
}
else {
assert queryLiteral.getExpressionType().getJdbcTypeCount( getTypeConfiguration() ) == 1;
queryLiteral.visitJdbcTypes(
jdbcMapping -> {
final JdbcLiteralFormatter literalFormatter = jdbcMapping.getSqlTypeDescriptor().getJdbcLiteralFormatter( jdbcMapping.getJavaTypeDescriptor() );
appendSql(
literalFormatter.toJdbcLiteral(
queryLiteral.getValue(),
dialect,
null
)
);
},
getTypeConfiguration()
assert literal.getExpressionType().getJdbcTypeCount( getTypeConfiguration() ) == 1;
final JdbcMapping jdbcMapping = literal.getJdbcMapping();
final JdbcLiteralFormatter literalFormatter = jdbcMapping.getSqlTypeDescriptor().getJdbcLiteralFormatter( jdbcMapping.getJavaTypeDescriptor() );
appendSql(
literalFormatter.toJdbcLiteral(
literal.getLiteralValue(),
dialect,
null
)
);
}
}

View File

@ -37,7 +37,7 @@ public class DerbyCaseExpressionWalker implements CaseExpressionWalker {
if ( otherwise != null ) {
sqlBuffer.append( " else " );
if ( otherwise instanceof QueryLiteral ) {
Object value = ( (QueryLiteral) otherwise ).getValue();
Object value = ( (QueryLiteral) otherwise ).getLiteralValue();
if ( value == null ) {
// null is not considered the same type as Integer.
sqlBuffer.append( "-1" );

View File

@ -12,6 +12,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -99,6 +100,8 @@ public interface SqlAstWalker {
void visitParameter(JdbcParameter jdbcParameter);
void visitJdbcLiteral(JdbcLiteral jdbcLiteral);
void visitQueryLiteral(QueryLiteral queryLiteral);
void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression);

View File

@ -17,7 +17,7 @@ import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
@ -29,7 +29,6 @@ import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Describes the table definition for the CTE - its name amd its columns
@ -41,10 +40,10 @@ public class CteTable {
private final List<CteColumn> cteColumns;
public CteTable(EntityMappingType entityDescriptor, TypeConfiguration typeConfiguration) {
public CteTable(EntityMappingType entityDescriptor) {
this.sessionFactory = entityDescriptor.getEntityPersister().getFactory();
final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount( typeConfiguration );
final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount( sessionFactory.getTypeConfiguration() );
cteColumns = new ArrayList<>( numberOfColumns );
entityDescriptor.getIdentifierMapping().visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> cteColumns.add(
@ -62,7 +61,7 @@ public class CteTable {
}
public String getTableExpression() {
return CteBasedMutationStrategy.TABLE_NAME;
return CteStrategy.TABLE_NAME;
}
public List<CteColumn> getCteColumns() {
@ -150,13 +149,13 @@ public class CteTable {
return new TableReference(
tableValueCtorExpressionBuffer.toString(),
CteBasedMutationStrategy.TABLE_NAME,
CteStrategy.TABLE_NAME,
false,
sessionFactory
);
}
public QuerySpec createCteSubQuery(ExecutionContext executionContext) {
public QuerySpec createCteSubQuery(@SuppressWarnings("unused") ExecutionContext executionContext) {
final QuerySpec querySpec = new QuerySpec( false );
final TableReference cteTableReference = new TableReference(

View File

@ -15,7 +15,7 @@ import java.util.function.Supplier;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.mutation.internal.cte.CteBasedMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReference;
@ -31,8 +31,9 @@ public class CteTableGroup implements TableGroup {
private final NavigablePath navigablePath;
private final TableReference cteTableReference;
@SuppressWarnings("WeakerAccess")
public CteTableGroup(TableReference cteTableReference) {
this.navigablePath = new NavigablePath( CteBasedMutationStrategy.TABLE_NAME );
this.navigablePath = new NavigablePath( CteStrategy.TABLE_NAME );
this.cteTableReference = cteTableReference;
}

View File

@ -0,0 +1,176 @@
/*
* 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.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.internal.domain.basic.BasicResult;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Represents a literal in the SQL AST. This form accepts a {@link JdbcMapping} and acts
* as its own MappingModelExpressable.
*
* @see QueryLiteral
*
* @author Steve Ebersole
*/
public class JdbcLiteral<T> implements Literal, MappingModelExpressable<T>, DomainResultProducer<T> {
private final T literalValue;
private final JdbcMapping jdbcMapping;
public JdbcLiteral(T literalValue, JdbcMapping jdbcMapping) {
this.literalValue = literalValue;
this.jdbcMapping = jdbcMapping;
}
@Override
public Object getLiteralValue() {
return literalValue;
}
@Override
public JdbcMapping getJdbcMapping() {
return jdbcMapping;
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
sqlTreeWalker.visitJdbcLiteral( this );
}
@Override
public void bindParameterValue(
PreparedStatement statement,
int startPosition,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) throws SQLException {
//noinspection unchecked
jdbcMapping.getJdbcValueBinder().bind(
statement,
literalValue,
startPosition,
executionContext.getSession()
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MappingModelExpressable
@Override
public MappingModelExpressable getExpressionType() {
return this;
}
@Override
public int getJdbcTypeCount(TypeConfiguration typeConfiguration) {
return 1;
}
@Override
public List<JdbcMapping> getJdbcMappings(TypeConfiguration typeConfiguration) {
return Collections.singletonList( jdbcMapping );
}
@Override
public void visitJdbcTypes(Consumer<JdbcMapping> action, Clause clause, TypeConfiguration typeConfiguration) {
action.accept( jdbcMapping );
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
return value;
}
@Override
public void visitDisassembledJdbcValues(
Object value,
Clause clause,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
valuesConsumer.consume( value, jdbcMapping );
}
@Override
public void visitJdbcValues(
Object value,
Clause clause,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
valuesConsumer.consume( value, jdbcMapping );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DomainResultProducer
@Override
public void visitJdbcTypes(Consumer<JdbcMapping> action, TypeConfiguration typeConfiguration) {
action.accept( jdbcMapping );
}
@Override
public DomainResult<T> createDomainResult(String resultVariable, DomainResultCreationState creationState) {
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver();
final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection(
this,
jdbcMapping.getJavaTypeDescriptor(),
sqlAstCreationState.getCreationContext().getDomainModel().getTypeConfiguration()
);
//noinspection unchecked
return new BasicResult( sqlSelection.getValuesArrayPosition(), resultVariable, jdbcMapping.getJavaTypeDescriptor() );
}
@Override
public void applySqlSelections(DomainResultCreationState creationState) {
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver();
sqlExpressionResolver.resolveSqlSelection(
this,
jdbcMapping.getJavaTypeDescriptor(),
sqlAstCreationState.getCreationContext().getDomainModel().getTypeConfiguration()
);
}
@Override
public SqlSelection createSqlSelection(
int jdbcPosition,
int valuesArrayPosition,
JavaTypeDescriptor javaTypeDescriptor,
TypeConfiguration typeConfiguration) {
return new SqlSelectionImpl(
jdbcPosition,
valuesArrayPosition,
this,
jdbcMapping
);
}
}

View File

@ -0,0 +1,18 @@
/*
* 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.expression;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
/**
* @author Steve Ebersole
*/
public interface Literal extends JdbcParameterBinder, Expression {
Object getLiteralValue();
JdbcMapping getJdbcMapping();
}

View File

@ -6,22 +6,117 @@
*/
package org.hibernate.sql.ast.tree.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.function.Consumer;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.sql.ast.Clause;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.internal.domain.basic.BasicResult;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* A literal specified in the source query.
* Represents a literal in the SQL AST. This form accepts a {@link BasicValuedMapping} is its MappingModelExpressable.
*
* @see JdbcLiteral
*
* @author Steve Ebersole
*/
public class QueryLiteral<T> extends AbstractLiteral<T> {
public QueryLiteral(Object value, BasicValuedMapping expressableType, Clause clause) {
super( value, expressableType, clause );
public class QueryLiteral<T> implements Literal, DomainResultProducer<T> {
private final Object value;
private final BasicValuedMapping type;
public QueryLiteral(Object value, BasicValuedMapping type) {
this.value = value;
this.type = type;
}
@Override
public Object getLiteralValue() {
return value;
}
@Override
public JdbcMapping getJdbcMapping() {
return type.getJdbcMapping();
}
@Override
public void accept(SqlAstWalker walker) {
walker.visitQueryLiteral( this );
}
@Override
public BasicValuedMapping getExpressionType() {
return type;
}
@Override
public DomainResult<T> createDomainResult(
String resultVariable,
DomainResultCreationState creationState) {
final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection(
this,
type.getMappedTypeDescriptor().getMappedJavaTypeDescriptor(),
creationState.getSqlAstCreationState()
.getCreationContext()
.getSessionFactory()
.getTypeConfiguration()
);
//noinspection unchecked
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
resultVariable,
type.getMappedTypeDescriptor().getMappedJavaTypeDescriptor()
);
}
@Override
public SqlSelection createSqlSelection(
int jdbcPosition,
int valuesArrayPosition,
JavaTypeDescriptor javaTypeDescriptor,
TypeConfiguration typeConfiguration) {
return new SqlSelectionImpl(
jdbcPosition,
valuesArrayPosition,
this,
type.getJdbcMapping()
);
}
@Override
public void visitJdbcTypes(
Consumer<JdbcMapping> action,
TypeConfiguration typeConfiguration) {
action.accept( type.getJdbcMapping() );
}
@Override
public void bindParameterValue(
PreparedStatement statement,
int startPosition,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) throws SQLException {
//noinspection unchecked
( (BasicType) getExpressionType() ).getJdbcValueBinder().bind(
statement,
getLiteralValue(),
startPosition,
executionContext.getSession()
);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.exec.spi;
import java.sql.PreparedStatement;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.resource.jdbc.LogicalConnection;
/**
* Helper for creating various types of
* NOTE :
* @author Steve Ebersole
*/
public class StatementCreatorHelper {
public static PreparedStatement prepareQueryStatement(
String sql,
SharedSessionContractImplementor session) {
return session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql );
}
}

View File

@ -0,0 +1,208 @@
/*
* 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.orm.test.query.sqm.mutation.multitable;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.orm.test.metamodel.mapping.SecondaryTableTests;
import org.hibernate.orm.test.metamodel.mapping.inheritance.joined.JoinedInheritanceTest;
import org.hibernate.query.internal.ParameterMetadataImpl;
import org.hibernate.query.internal.QueryParameterBindingsImpl;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
/**
* Tests for selecting matching ids related to SQM update/select statements.
*
* Matching-id-selection is used in CTE- and inline-based strategies.
*
* A "functional correctness" test for {@link MatchingIdSelectionHelper#selectMatchingIds}
*
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
@DomainModel(
standardModels = StandardDomainModel.GAMBIT,
annotatedClasses = {
SecondaryTableTests.SimpleEntityWithSecondaryTables.class,
JoinedInheritanceTest.Customer.class,
JoinedInheritanceTest.DomesticCustomer.class,
JoinedInheritanceTest.ForeignCustomer.class
}
)
@ServiceRegistry
@SessionFactory( exportSchema = true )
public class IdSelectionTests {
@Test
public void testSecondaryTableRestrictedOnRootTable(SessionFactoryScope scope) {
final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory()
.getQueryEngine()
.getHqlTranslator()
.translate( "delete SimpleEntityWithSecondaryTables where name = :n" );
final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm );
final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from(
parameterMetadata,
scope.getSessionFactory()
);
domainParamBindings.getBinding( "n" ).setBindValue( "abc" );
scope.inTransaction(
session -> {
final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings );
MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext );
}
);
}
@Test
public void testSecondaryTableRestrictedOnNonRootTable(SessionFactoryScope scope) {
final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory()
.getQueryEngine()
.getHqlTranslator()
.translate( "delete SimpleEntityWithSecondaryTables where data = :d" );
final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm );
final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from(
parameterMetadata,
scope.getSessionFactory()
);
domainParamBindings.getBinding( "d" ).setBindValue( "123" );
scope.inTransaction(
session -> {
final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings );
MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext );
}
);
}
@Test
public void testJoinedSubclassRestrictedOnRootTable(SessionFactoryScope scope) {
final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory()
.getQueryEngine()
.getHqlTranslator()
.translate( "delete Customer where name = :n" );
final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm );
final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from(
parameterMetadata,
scope.getSessionFactory()
);
domainParamBindings.getBinding( "n" ).setBindValue( "Acme" );
scope.inTransaction(
session -> {
final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings );
MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext );
}
);
}
@Test
public void testJoinedSubclassRestrictedOnNonPrimaryRootTable(SessionFactoryScope scope) {
final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory()
.getQueryEngine()
.getHqlTranslator()
.translate( "delete ForeignCustomer where name = :n" );
final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm );
final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from(
parameterMetadata,
scope.getSessionFactory()
);
domainParamBindings.getBinding( "n" ).setBindValue( "Acme" );
scope.inTransaction(
session -> {
final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings );
MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext );
}
);
}
@Test
public void testJoinedSubclassRestrictedOnPrimaryNonRootTable(SessionFactoryScope scope) {
final SqmDeleteStatement sqm = (SqmDeleteStatement) scope.getSessionFactory()
.getQueryEngine()
.getHqlTranslator()
.translate( "delete ForeignCustomer where vat = :v" );
final DomainParameterXref domainParameterXref = DomainParameterXref.from( sqm );
final ParameterMetadataImpl parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
final QueryParameterBindingsImpl domainParamBindings = QueryParameterBindingsImpl.from(
parameterMetadata,
scope.getSessionFactory()
);
domainParamBindings.getBinding( "v" ).setBindValue( "123" );
scope.inTransaction(
session -> {
final ExecutionContext executionContext = new TestExecutionContext( session, domainParamBindings );
MatchingIdSelectionHelper.selectMatchingIds( sqm, domainParameterXref, executionContext );
}
);
}
private static class TestExecutionContext implements ExecutionContext {
private final SessionImplementor session;
private final QueryParameterBindingsImpl domainParamBindings;
public TestExecutionContext(SessionImplementor session, QueryParameterBindingsImpl domainParamBindings) {
this.session = session;
this.domainParamBindings = domainParamBindings;
}
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public QueryOptions getQueryOptions() {
return QueryOptions.NONE;
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return domainParamBindings;
}
@Override
public Callback getCallback() {
return afterLoadAction -> {
};
}
}
}