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

basic working support for simple (non-multi-table) SQM UPDATE statements
This commit is contained in:
Steve Ebersole 2019-11-12 18:02:21 -06:00
parent eddd5b938b
commit 83a1eb5715
22 changed files with 737 additions and 87 deletions

View File

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

View File

@ -304,25 +304,26 @@ public SqmUpdateStatement visitUpdateStatement(HqlParser.UpdateStatementContext
visitIdentificationVariableDef( ctx.identificationVariableDef() ),
creationContext.getNodeBuilder()
);
final SqmUpdateStatement<?> updateStatement = new SqmUpdateStatement<>( root, creationContext.getNodeBuilder() );
parameterCollector = updateStatement;
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
updateStatement,
this
);
processingStateStack.push( processingState );
processingState.getPathRegistry().register( root );
processingStateStack.push( new SqmDmlCreationProcessingState( updateStatement, this ) );
try {
updateStatement.getWhereClause().setPredicate(
(SqmPredicate) ctx.whereClause().predicate().accept( this )
);
for ( HqlParser.AssignmentContext assignmentContext : ctx.setClause().assignment() ) {
final SqmPath stateField = consumeDomainPath( assignmentContext.dotIdentifierSequence() );
// todo : validate "state field" expression
updateStatement.getSetClause().addAssignment(
stateField,
updateStatement.applyAssignment(
consumeDomainPath( assignmentContext.dotIdentifierSequence() ),
(SqmExpression) assignmentContext.expression().accept( this )
);
}
updateStatement.applyPredicate( visitWhereClause( ctx.whereClause() ) );
return updateStatement;
}
finally {
@ -1055,7 +1056,12 @@ protected void consumeJpaCollectionJoin(
@Override
public SqmPredicate visitWhereClause(HqlParser.WhereClauseContext ctx) {
if ( ctx == null || ctx.predicate() == null ) {
return null;
}
return (SqmPredicate) ctx.predicate().accept( this );
}
@Override

View File

@ -33,6 +33,7 @@
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
@ -59,6 +60,7 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
private final RowTransformer<R> rowTransformer;
private JdbcSelect jdbcSelect;
private FromClauseAccess tableGroupAccess;
private Map<QueryParameterImplementor<?>, Map<SqmParameter, List<JdbcParameter>>> jdbcParamsXref;
@SuppressWarnings("WeakerAccess")
@ -172,6 +174,8 @@ public List<R> performList(ExecutionContext executionContext) {
sessionFactory
);
tableGroupAccess = sqmConverter.getFromClauseAccess();
final SqmSelectTranslation interpretation = sqmConverter.translate( sqm );
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
@ -192,8 +196,8 @@ public List<R> performList(ExecutionContext executionContext) {
executionContext.getQueryParameterBindings(),
domainParameterXref,
jdbcParamsXref,
// todo (6.0) : ugh. this one is important
null,
session.getFactory().getDomainModel(),
tableGroupAccess::findTableGroup,
session
);

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.query.sqm.internal;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
@ -21,9 +20,9 @@
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor;
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcDelete;
import org.hibernate.sql.exec.spi.JdbcParameter;
@ -37,6 +36,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan {
private final DomainParameterXref domainParameterXref;
private JdbcDelete jdbcDelete;
private FromClauseAccess tableGroupAccess;
private Map<QueryParameterImplementor<?>, Map<SqmParameter, List<JdbcParameter>>> jdbcParamsXref;
public SimpleDeleteQueryPlan(
@ -65,6 +65,8 @@ public int executeUpdate(ExecutionContext executionContext) {
final SimpleSqmDeleteTranslation sqmInterpretation = translator.translate( sqmDelete );
tableGroupAccess = translator.getFromClauseAccess();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
@ -83,30 +85,18 @@ public int executeUpdate(ExecutionContext executionContext) {
executionContext.getQueryParameterBindings(),
domainParameterXref,
jdbcParamsXref,
// todo (6.0) : ugh. this one is important
null,
factory.getDomainModel(),
tableGroupAccess::findTableGroup,
executionContext.getSession()
);
final LogicalConnectionImplementor logicalConnection = executionContext.getSession()
.getJdbcCoordinator()
.getLogicalConnection();
return jdbcServices.getJdbcDeleteExecutor().execute(
jdbcDelete,
jdbcParameterBindings,
sql -> {
try {
return logicalConnection.getPhysicalConnection().prepareStatement( sql );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Error performing DELETE",
sql
);
}
},
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {},
executionContext
);

View File

@ -9,21 +9,23 @@
import java.util.List;
import java.util.Map;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.NonSelectQueryPlan;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.spi.QueryParameterImplementor;
import org.hibernate.query.sqm.sql.SimpleSqmDeleteTranslation;
import org.hibernate.query.sqm.sql.SimpleSqmDeleteTranslator;
import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslation;
import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslator;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcUpdate;
/**
@ -34,6 +36,7 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan {
private final DomainParameterXref domainParameterXref;
private JdbcUpdate jdbcUpdate;
private FromClauseAccess tableGroupAccess;
private Map<QueryParameterImplementor<?>, Map<SqmParameter, List<JdbcParameter>>> jdbcParamsXref;
public SimpleUpdateQueryPlan(
@ -62,9 +65,40 @@ public int executeUpdate(ExecutionContext executionContext) {
final SimpleSqmUpdateTranslation sqmInterpretation = translator.translate( sqmUpdate );
tableGroupAccess = translator.getFromClauseAccess();
this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
domainParameterXref,
sqmInterpretation::getJdbcParamsBySqmParam
);
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final SqlAstUpdateTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildUpdateTranslator( factory );
jdbcUpdate = sqlAstTranslator.translate( sqmInterpretation.getSqlAst() );
}
throw new NotYetImplementedFor6Exception( );
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
jdbcParamsXref,
factory.getDomainModel(),
tableGroupAccess::findTableGroup,
executionContext.getSession()
);
return jdbcServices.getJdbcUpdateExecutor().execute(
jdbcUpdate,
jdbcParameterBindings,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {},
executionContext
);
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.internal;
import java.util.function.Function;
import javax.persistence.metamodel.Bindable;
import org.hibernate.NotYetImplementedFor6Exception;
@ -25,9 +26,9 @@
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.metamodel.spi.DomainMetamodel;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.SqmExpressable;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.domain.SqmPath;
@ -102,9 +103,10 @@ public static <J> SqmPathSource<J> resolveSqmPathSource(
public static MappingModelExpressable resolveMappingModelExpressable(
SqmTypedNode<?> sqmNode,
SqlAstCreationState creationState) {
DomainMetamodel domainModel,
Function<NavigablePath,TableGroup> tableGroupLocator) {
if ( sqmNode instanceof SqmPath ) {
return resolveSqmPath( (SqmPath) sqmNode, creationState );
return resolveSqmPath( (SqmPath) sqmNode, domainModel, tableGroupLocator );
}
final SqmExpressable<?> nodeType = sqmNode.getNodeType();
@ -115,8 +117,10 @@ public static MappingModelExpressable resolveMappingModelExpressable(
throw new NotYetImplementedFor6Exception( DomainModelHelper.class );
}
private static ModelPart resolveSqmPath(SqmPath<?> sqmPath, SqlAstCreationState creationState) {
final DomainMetamodel domainModel = creationState.getCreationContext().getDomainModel();
private static ModelPart resolveSqmPath(
SqmPath<?> sqmPath,
DomainMetamodel domainModel,
Function<NavigablePath,TableGroup> tableGroupLocator) {
if ( sqmPath instanceof SqmTreatedPath ) {
final SqmTreatedPath treatedPath = (SqmTreatedPath) sqmPath;
@ -133,7 +137,7 @@ private static ModelPart resolveSqmPath(SqmPath<?> sqmPath, SqlAstCreationState
return container.findSubPart( sqmPath.getNavigablePath().getLocalName(), container );
}
final TableGroup lhsTableGroup = creationState.getFromClauseAccess().findTableGroup( sqmPath.getLhs().getNavigablePath() );
final TableGroup lhsTableGroup = tableGroupLocator.apply( sqmPath.getLhs().getNavigablePath() );
return lhsTableGroup.getModelPart().findSubPart( sqmPath.getReferencedPathSource().getPathName(), null );
}

View File

@ -14,6 +14,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -21,7 +22,9 @@
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.spi.DomainMetamodel;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.spi.QueryParameterImplementor;
@ -32,6 +35,7 @@
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
@ -153,7 +157,8 @@ public static JdbcParameterBindings createJdbcParameterBindings(
QueryParameterBindings domainParamBindings,
DomainParameterXref domainParameterXref,
Map<QueryParameterImplementor<?>, Map<SqmParameter, List<JdbcParameter>>> jdbcParamXref,
SqlAstCreationState sqlAstCreationState,
DomainMetamodel domainModel,
Function<NavigablePath, TableGroup> tableGroupLocator,
SharedSessionContractImplementor session) {
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(
domainParameterXref.getSqmParameterCount()
@ -176,7 +181,11 @@ public static JdbcParameterBindings createJdbcParameterBindings(
final List<JdbcParameter> jdbcParams = jdbcParamMap.get( sqmParameter );
if ( ! domainParamBinding.isBound() ) {
final MappingModelExpressable mappingExpressable = SqmMappingModelHelper.resolveMappingModelExpressable( sqmParameter, sqlAstCreationState );
final MappingModelExpressable mappingExpressable = SqmMappingModelHelper.resolveMappingModelExpressable(
sqmParameter,
domainModel,
tableGroupLocator
);
mappingExpressable.visitJdbcTypes(
new Consumer<JdbcMapping>() {
int position = 0;

View File

@ -270,8 +270,8 @@ public static List<Object> selectMatchingIds(
executionContext.getQueryParameterBindings(),
domainParameterXref,
jdbcParamsXref,
// todo (6.0) : ugh. this one is important
null,
factory.getDomainModel(),
selectConverter.getFromClauseAccess()::findTableGroup,
executionContext.getSession()
);

View File

@ -256,7 +256,8 @@ protected int saveMatchingIdsIntoIdTable(ExecutionContext executionContext) {
domainParameterXref,
sqmIdSelectTranslation::getJdbcParamsBySqmParam
),
sqmTranslator,
factory.getDomainModel(),
sqmTranslator.getFromClauseAccess()::findTableGroup,
executionContext.getSession()
);

View File

@ -27,6 +27,7 @@
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.BinaryArithmeticOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.UnaryArithmeticOperator;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBinding;
@ -154,7 +155,7 @@
*/
public abstract class BaseSqmToSqlAstConverter
extends BaseSemanticQueryWalker
implements SqmToSqlAstConverter, JdbcParameterBySqmParameterAccess {
implements SqmToSqlAstConverter, JdbcParameterBySqmParameterAccess, FromClauseAccess {
private static final Logger log = Logger.getLogger( BaseSqmToSqlAstConverter.class );
@ -200,6 +201,19 @@ protected Stack<SqlAstProcessingState> getProcessingStateStack() {
return processingStateStack;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// FromClauseAccess
@Override
public TableGroup findTableGroup(NavigablePath navigablePath) {
return fromClauseIndex.findTableGroup( navigablePath );
}
@Override
public void registerTableGroup(NavigablePath navigablePath, TableGroup tableGroup) {
throw new UnsupportedOperationException();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqlAstCreationState
@ -748,7 +762,11 @@ public SqmPathInterpretation<?> visitPluralValuedPath(SqmPluralValuedSimplePath
public Expression visitLiteral(SqmLiteral literal) {
return new QueryLiteral(
literal.getLiteralValue(),
(BasicValuedMapping) SqmMappingModelHelper.resolveMappingModelExpressable( literal, this ),
(BasicValuedMapping) SqmMappingModelHelper.resolveMappingModelExpressable(
literal,
getCreationContext().getDomainModel(),
getFromClauseAccess()::findTableGroup
),
getCurrentClauseStack().getCurrent()
);
}
@ -796,7 +814,11 @@ protected MappingModelExpressable<?> determineValueMapping(SqmExpression<?> sqmE
if ( sqmExpression instanceof SqmPath ) {
log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression );
return SqmMappingModelHelper.resolveMappingModelExpressable( sqmExpression, this );
return SqmMappingModelHelper.resolveMappingModelExpressable(
sqmExpression,
getCreationContext().getDomainModel(),
getFromClauseAccess()::findTableGroup
);
}
@ -856,7 +878,7 @@ protected MappingModelExpressable<?> determineValueMapping(SqmParameter<?> sqmPa
throw new ConversionException( "Could not determine ValueMapping for SqmParameter: " + sqmParameter );
}
private final Stack<Supplier<MappingModelExpressable>> inferableTypeAccessStack = new StandardStack<>(
protected final Stack<Supplier<MappingModelExpressable>> inferableTypeAccessStack = new StandardStack<>(
() -> null
);

View File

@ -9,11 +9,12 @@
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.sql.ast.spi.FromClauseAccess;
/**
* @author Steve Ebersole
*/
public interface SqmSelectTranslator extends SqmToSqlAstConverter, JdbcParameterBySqmParameterAccess, SqmTranslator {
public interface SqmSelectTranslator extends SqmToSqlAstConverter, FromClauseAccess, JdbcParameterBySqmParameterAccess, SqmTranslator {
SqmSelectTranslation translate(SqmSelectStatement sqmStatement);
SqmQuerySpecTranslation translate(SqmQuerySpec sqmQuerySpec);
}

View File

@ -36,7 +36,11 @@ public static <T> SqmTupleInterpretation<T> from(
return new SqmTupleInterpretation<>(
sqmTuple,
groupedSqlExpressions,
SqmMappingModelHelper.resolveMappingModelExpressable( sqmTuple, sqlAstCreationState )
SqmMappingModelHelper.resolveMappingModelExpressable(
sqmTuple,
sqlAstCreationState.getCreationContext().getDomainModel(),
sqlAstCreationState.getFromClauseAccess()::findTableGroup
)
);
}

View File

@ -8,9 +8,12 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.spi.DomainMetamodel;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
@ -20,22 +23,30 @@
import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslation;
import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslator;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
import org.hibernate.query.sqm.tree.update.SqmAssignment;
import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.JoinType;
import org.hibernate.sql.ast.SqlTreeCreationLogger;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlAstTreeHelper;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Steve Ebersole
@ -53,6 +64,11 @@ public StandardSqmUpdateTranslator(
super( creationContext, queryOptions, domainParameterXref, domainParameterBindings );
}
@Override
public CteStatement translate(SqmCteStatement sqmCte) {
return visitCteStatement( sqmCte );
}
@Override
public SimpleSqmUpdateTranslation translate(SqmUpdateStatement sqmUpdate) {
final UpdateStatement sqlUpdateAst = visitUpdateStatement( sqmUpdate );
@ -83,17 +99,20 @@ public UpdateStatement visitUpdateStatement(SqmUpdateStatement sqmStatement) {
null,
JoinType.LEFT,
LockMode.WRITE,
stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ),
getSqlAliasBaseGenerator(),
getSqlExpressionResolver(),
() -> predicate -> additionalRestrictions = predicate,
getCreationContext()
);
getFromClauseIndex().registerTableGroup( rootPath, rootTableGroup );
if ( ! rootTableGroup.getTableReferenceJoins().isEmpty() ) {
throw new HibernateException( "Not expecting multiple table references for an SQM DELETE" );
}
getFromClauseIndex().registerTableGroup( rootPath, rootTableGroup );
final List<Assignment> assignments = visitSetClause( sqmStatement.getSetClause() );
Predicate suppliedPredicate = null;
final SqmWhereClause whereClause = sqmStatement.getWhereClause();
if ( whereClause != null && whereClause.getPredicate() != null ) {
@ -108,7 +127,7 @@ public UpdateStatement visitUpdateStatement(SqmUpdateStatement sqmStatement) {
return new UpdateStatement(
rootTableGroup.getPrimaryTableReference(),
visitSetClause( sqmStatement.getSetClause() ),
assignments,
SqlAstTreeHelper.combinePredicates( suppliedPredicate, additionalRestrictions )
);
}
@ -117,35 +136,152 @@ public UpdateStatement visitUpdateStatement(SqmUpdateStatement sqmStatement) {
}
}
@Override
public SqlAliasBaseGenerator getSqlAliasBaseGenerator() {
return SQL_ALIAS_BASE_GENERATOR;
}
private static final SqlAliasBaseGenerator SQL_ALIAS_BASE_GENERATOR = new SqlAliasBaseGenerator() {
private final SqlAliasBase sqlAliasBase = new SqlAliasBase() {
@Override
public String getAliasStem() {
return null;
}
@Override
public String generateNewAlias() {
return null;
}
};
@Override
public SqlAliasBase createSqlAliasBase(String stem) {
return sqlAliasBase;
}
};
@Override
public List<Assignment> visitSetClause(SqmSetClause setClause) {
final List<Assignment> assignments = new ArrayList<>();
final List<ColumnReference> targetColumnReferences = new ArrayList<>();
for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) {
final SqmPathInterpretation assignedPathInterpretation = (SqmPathInterpretation) sqmAssignment.getTargetPath().accept( this );
assignedPathInterpretation.getExpressionType().visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> {
final JdbcParameter jdbcParameter = new JdbcParameterImpl( jdbcMapping );
assignments.add(
new Assignment(
new ColumnReference(
containingTableExpression,
columnExpression,
jdbcMapping,
getCreationContext().getSessionFactory()
),
jdbcParameter
)
);
getProcessingStateStack().push(
new SqlAstProcessingStateImpl(
getProcessingStateStack().getCurrent(),
this,
getCurrentClauseStack()::getCurrent
) {
@Override
public Expression resolveSqlExpression(
String key,
Function<SqlAstProcessingState, Expression> creator) {
final Expression expression = getParentState().getSqlExpressionResolver().resolveSqlExpression( key, creator );
assert expression instanceof ColumnReference;
targetColumnReferences.add( (ColumnReference) expression );
return expression;
}
}
);
final SqmPathInterpretation assignedPathInterpretation;
try {
assignedPathInterpretation = (SqmPathInterpretation) sqmAssignment.getTargetPath().accept( this );
}
finally {
getProcessingStateStack().pop();
}
inferableTypeAccessStack.push( assignedPathInterpretation::getExpressionType );
final List<ColumnReference> valueColumnReferences = new ArrayList<>();
getProcessingStateStack().push(
new SqlAstProcessingStateImpl(
getProcessingStateStack().getCurrent(),
this,
getCurrentClauseStack()::getCurrent
) {
@Override
public Expression resolveSqlExpression(
String key,
Function<SqlAstProcessingState, Expression> creator) {
final Expression expression = getParentState().getSqlExpressionResolver().resolveSqlExpression( key, creator );
assert expression instanceof ColumnReference;
valueColumnReferences.add( (ColumnReference) expression );
return expression;
}
}
);
try {
if ( sqmAssignment.getValue() instanceof SqmParameter ) {
final SqmParameter sqmParameter = (SqmParameter) sqmAssignment.getValue();
final List<JdbcParameter> jdbcParametersForSqm = new ArrayList<>();
// create one JdbcParameter for each column in the assigned path
assignedPathInterpretation.getExpressionType().visitColumns(
(columnExpression, containingTableExpression, jdbcMapping) -> {
final JdbcParameter jdbcParameter = new JdbcParameterImpl( jdbcMapping );
jdbcParametersForSqm.add( jdbcParameter );
assignments.add(
new Assignment(
new ColumnReference(
// we do not want a qualifier (table alias) here
(String) null,
columnExpression,
jdbcMapping,
getCreationContext().getSessionFactory()
),
jdbcParameter
)
);
}
);
getJdbcParamsBySqmParam().put( sqmParameter, jdbcParametersForSqm );
}
else {
final DomainMetamodel domainModel = getCreationContext().getDomainModel();
final TypeConfiguration typeConfiguration = domainModel.getTypeConfiguration();
final Expression valueExpression = (Expression) sqmAssignment.getValue().accept( this );
final int valueExprJdbcCount = valueExpression.getExpressionType().getJdbcTypeCount( typeConfiguration );
final int assignedPathJdbcCount = assignedPathInterpretation.getExpressionType().getJdbcTypeCount( typeConfiguration );
if ( valueExprJdbcCount != assignedPathJdbcCount ) {
SqlTreeCreationLogger.LOGGER.debugf(
"JDBC type count does not match in UPDATE assignment between the assigned-path and the assigned-value; " +
"this will likely lead to problems executing the query"
);
}
assert assignedPathJdbcCount == valueExprJdbcCount;
if ( valueExpression instanceof ColumnReference ) {
assert valueExprJdbcCount == 1;
assignments.add( new Assignment( (ColumnReference) valueExpression, valueExpression ) );
}
else {
throw new NotYetImplementedFor6Exception( "Support for composite valued assignments in an UPDATE query is not yet implemented" );
}
}
}
finally {
getProcessingStateStack().pop();
inferableTypeAccessStack.pop();
}
}
return assignments;
}
@Override
public CteStatement translate(SqmCteStatement sqmCte) {
return visitCteStatement( sqmCte );
}
}

View File

@ -24,6 +24,8 @@
import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.cte.SqmCteConsumer;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
@ -156,4 +158,11 @@ public JpaPredicate getRestriction() {
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitUpdateStatement( this );
}
public void applyAssignment(SqmPath targetPath, SqmExpression value) {
if ( setClause == null ) {
setClause = new SqmSetClause();
}
setClause.addAssignment( new SqmAssignment( targetPath, value ) );
}
}

View File

@ -7,7 +7,7 @@
package org.hibernate.sql.ast;
import org.hibernate.sql.ast.spi.SqlAstToJdbcOperationConverter;
import org.hibernate.sql.ast.spi.SqlSelectAstWalker;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.exec.spi.JdbcDelete;
@ -15,8 +15,9 @@
/**
* @author Steve Ebersole
*/
public interface SqlAstDeleteTranslator extends SqlSelectAstWalker, SqlAstToJdbcOperationConverter {
public interface SqlAstDeleteTranslator extends SqlAstWalker, SqlAstToJdbcOperationConverter {
JdbcDelete translate(DeleteStatement sqlAst);
@Override
JdbcDelete translate(CteStatement cteStatement);
}

View File

@ -25,9 +25,11 @@ public interface SqlAstTranslatorFactory {
SqlAstDeleteTranslator buildDeleteTranslator(SessionFactoryImplementor sessionFactory);
/**
* Builds a single-use delete translator
* Builds a single-use insert-select translator
*/
SqlAstInsertSelectTranslator buildInsertTranslator(SessionFactoryImplementor sessionFactory);
// todo (6.0) : update, insert, etc
SqlAstUpdateTranslator buildUpdateTranslator(SessionFactoryImplementor sessionFactory);
// todo (6.0) : CTE
}

View File

@ -0,0 +1,23 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.ast;
import org.hibernate.sql.ast.spi.SqlAstToJdbcOperationConverter;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcUpdate;
/**
* @author Steve Ebersole
*/
public interface SqlAstUpdateTranslator extends SqlAstWalker, SqlAstToJdbcOperationConverter {
JdbcUpdate translate(UpdateStatement sqlAst);
@Override
JdbcUpdate translate(CteStatement cteStatement);
}

View File

@ -397,7 +397,11 @@ public void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin) {
@Override
public void visitColumnReference(ColumnReference columnReference) {
appendSql( columnReference.renderSqlFragment( getSessionFactory() ) );
if ( columnReference.getQualifier() != null ) {
appendSql( columnReference.getQualifier() );
appendSql( "." );
}
appendSql( columnReference.getColumnExpression() );
}
@Override

View File

@ -6,12 +6,12 @@
*/
package org.hibernate.sql.ast.spi;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstDeleteTranslator;
import org.hibernate.sql.ast.SqlAstInsertSelectTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.SqlAstSelectTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
/**
* @author Steve Ebersole
@ -31,4 +31,9 @@ public SqlAstDeleteTranslator buildDeleteTranslator(SessionFactoryImplementor se
public SqlAstInsertSelectTranslator buildInsertTranslator(SessionFactoryImplementor sessionFactory) {
return new StandardSqlAstInsertSelectTranslator( sessionFactory );
}
@Override
public SqlAstUpdateTranslator buildUpdateTranslator(SessionFactoryImplementor sessionFactory) {
return new StandardSqlAstUpdateTranslator( sessionFactory );
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.spi;
import java.util.List;
import java.util.Set;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstUpdateTranslator;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.exec.spi.JdbcUpdate;
/**
* @author Steve Ebersole
*/
public class StandardSqlAstUpdateTranslator
extends AbstractSqlAstToJdbcOperationConverter
implements SqlAstUpdateTranslator {
public StandardSqlAstUpdateTranslator(SessionFactoryImplementor sessionFactory) {
super( sessionFactory );
}
@Override
public JdbcUpdate translate(UpdateStatement sqlAst) {
appendSql( "update " );
appendSql( sqlAst.getTargetTable().getTableExpression() );
appendSql( " set " );
boolean firstPass = true;
for ( int i = 0; i < sqlAst.getAssignments().size(); i++ ) {
if ( firstPass ) {
firstPass = false;
}
else {
appendSql( ", " );
}
final Assignment assignment = sqlAst.getAssignments().get( i );
assignment.getColumnReference().accept( this );
appendSql( " = " );
assignment.getAssignedValue().accept( this );
}
if ( sqlAst.getRestriction() != null ) {
appendSql( " where " );
sqlAst.getRestriction().accept( this );
}
return new JdbcUpdate() {
@Override
public String getSql() {
return StandardSqlAstUpdateTranslator.this.getSql();
}
@Override
public List<JdbcParameterBinder> getParameterBinders() {
return StandardSqlAstUpdateTranslator.this.getParameterBinders();
}
@Override
public Set<String> getAffectedTableNames() {
return StandardSqlAstUpdateTranslator.this.getAffectedTableNames();
}
};
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
super.visitColumnReference( columnReference );
}
@Override
public JdbcUpdate translate(CteStatement cteStatement) {
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -11,6 +11,7 @@
import java.util.Objects;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.sql.ast.spi.SqlAstWalker;
@ -36,11 +37,11 @@ public ColumnReference(
String columnExpression,
JdbcMapping jdbcMapping,
SessionFactoryImplementor sessionFactory) {
this.qualifier = qualifier;
this.qualifier = StringHelper.nullIfEmpty( qualifier );
this.columnExpression = columnExpression;
this.referenceExpression = qualifier == null
this.referenceExpression = this.qualifier == null
? columnExpression
: qualifier + "." + columnExpression;
: this.qualifier + "." + columnExpression;
this.jdbcMapping = jdbcMapping;
}

View File

@ -0,0 +1,303 @@
/*
* 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.sql.exec;
import java.time.Instant;
import java.util.Date;
import org.hibernate.orm.test.metamodel.mapping.SecondaryTableTests;
import org.hibernate.orm.test.metamodel.mapping.inheritance.joined.JoinedInheritanceTest;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.BasicEntity;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
@DomainModel(
standardModels = StandardDomainModel.GAMBIT
// standardModels = StandardDomainModel.GAMBIT,
// annotatedClasses = {
// SecondaryTableTests.SimpleEntityWithSecondaryTables.class,
// JoinedInheritanceTest.Customer.class,
// JoinedInheritanceTest.DomesticCustomer.class,
// JoinedInheritanceTest.ForeignCustomer.class
// }
)
@ServiceRegistry
@SessionFactory( exportSchema = true )
public class HqlUpdateExecutionTests {
@Test
public void testSimpleUpdate(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createQuery( "update BasicEntity set data = :p" )
.setParameter( "p", "xyz" )
.executeUpdate()
);
}
@Test
public void testSimpleUpdateWithData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.save( new BasicEntity( 1, "abc" ) );
session.save( new BasicEntity( 2, "def" ) );
}
);
scope.inTransaction(
session -> {
final int rows = session.createQuery( "update BasicEntity set data = :p" )
.setParameter( "p", "xyz" )
.executeUpdate();
assertThat( rows, is( 2 ) );
}
);
scope.inTransaction(
session -> {
final BasicEntity basicEntity = session.get( BasicEntity.class, 1 );
assertThat( basicEntity.getData(), is( "xyz" ) );
}
);
scope.inTransaction(
session -> {
final int rows = session.createQuery( "delete BasicEntity" ).executeUpdate();
assertThat( rows, is( 2 ) );
}
);
}
@Test
public void testSimpleRestrictedUpdate(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createQuery( "update BasicEntity set data = :p where data = :filter" )
.setParameter( "p", "xyz" )
.setParameter( "filter", "abc" )
.executeUpdate()
);
}
@Test
public void testSimpleRestrictedUpdateWithData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.save( new BasicEntity( 1, "abc" ) );
session.save( new BasicEntity( 2, "def" ) );
}
);
scope.inTransaction(
session -> {
final int rows = session.createQuery( "update BasicEntity set data = :val where data = :filter" )
.setParameter( "val", "xyz" )
.setParameter( "filter", "abc" )
.executeUpdate();
assertThat( rows, is( 1 ) );
}
);
scope.inTransaction(
session -> {
final BasicEntity basicEntity = session.get( BasicEntity.class, 1 );
assertThat( basicEntity.getData(), is( "xyz" ) );
final BasicEntity basicEntity2 = session.get( BasicEntity.class, 2 );
assertThat( basicEntity2.getData(), is( "def" ) );
}
);
scope.inTransaction(
session -> {
final int rows = session.createQuery( "delete BasicEntity" ).executeUpdate();
assertThat( rows, is( 2 ) );
}
);
}
// @Test
// public void testSimpleMultiTableDelete(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> session.createQuery( "delete SimpleEntityWithSecondaryTables" )
// .executeUpdate()
// );
// }
//
// @Test
// public void testSimpleMultiTableRestrictedDelete(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> session.createQuery( "delete SimpleEntityWithSecondaryTables where data = :filter" )
// .setParameter( "filter", "abc" )
// .executeUpdate()
// );
// }
//
// @Test
// @FailureExpected( reason = "Saving of entities with secondary tables is broken atm" )
// public void testSimpleMultiTableRestrictedDeleteResults(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> {
// session.save(
// new SecondaryTableTests.SimpleEntityWithSecondaryTables(
// 1,
// "first",
// Date.from( Instant.now() ),
// "1 - cfdjdjvokfobkofbvovoijjbvoijofjdbiof"
// )
// );
// session.save(
// new SecondaryTableTests.SimpleEntityWithSecondaryTables(
// 2,
// "second",
// Date.from( Instant.now() ),
// "2 - s3o2rj9 fcojv9j gj9jfv943jv29j9j4"
// )
// );
// session.save(
// new SecondaryTableTests.SimpleEntityWithSecondaryTables(
// 3,
// "third",
// Date.from( Instant.now() ),
// "abc"
// )
// );
// }
// );
// scope.inTransaction(
// session -> {
// final int rows = session.createQuery( "delete SimpleEntityWithSecondaryTables where data = :filter" )
// .setParameter( "filter", "abc" )
// .executeUpdate();
// assertThat( rows, is ( 1 ) );
// }
// );
// scope.inTransaction(
// session -> session.createQuery( "delete SimpleEntityWithSecondaryTables" ).executeUpdate()
// );
// }
//
//
// @Test
// public void testJoinedSubclassRootDelete(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> session.createQuery( "delete Customer" ).executeUpdate()
// );
// }
//
// @Test
// public void testJoinedSubclassRootRestrictedDelete(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> session.createQuery( "delete Customer where name = 'abc'" ).executeUpdate()
// );
// }
//
// @Test
// public void testJoinedSubclassRootRestrictedDeleteResults(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> {
// session.save(
// new JoinedInheritanceTest.ForeignCustomer( 1, "Adventures Abroad", "123" )
// );
// session.save(
// new JoinedInheritanceTest.DomesticCustomer( 2, "Domestic Wonders", "456" )
// );
// }
// );
//
// scope.inTransaction(
// session -> {
// final int rows = session.createQuery( "delete Customer where name = 'Adventures Abroad'" ).executeUpdate();
// assertThat( rows, is( 1 ) );
// }
// );
//
// scope.inTransaction(
// session -> {
// final int rows = session.createQuery( "delete from Customer" ).executeUpdate();
// assertThat( rows, is( 1 ) );
// }
// );
//
// scope.inTransaction(
// session -> {
// final int rows = session.createQuery( "delete from Customer" ).executeUpdate();
// assertThat( rows, is( 0 ) );
// }
// );
// }
//
//
// @Test
// public void testJoinedSubclassLeafDelete(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> session.createQuery( "delete ForeignCustomer" ).executeUpdate()
// );
// scope.inTransaction(
// session -> session.createQuery( "delete DomesticCustomer" ).executeUpdate()
// );
// }
//
// @Test
// public void testJoinedSubclassLeafRestrictedDelete(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> session.createQuery( "delete ForeignCustomer where name = 'abc'" ).executeUpdate()
// );
// scope.inTransaction(
// session -> session.createQuery( "delete DomesticCustomer where name = 'abc'" ).executeUpdate()
// );
// }
//
// @Test
// public void testJoinedSubclassLeafRestrictedDeleteResult(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> {
// session.save(
// new JoinedInheritanceTest.ForeignCustomer( 1, "Adventures Abroad", "123" )
// );
// session.save(
// new JoinedInheritanceTest.DomesticCustomer( 2, "Domestic Wonders", "456" )
// );
// }
// );
//
// scope.inTransaction(
// session -> {
// final int rows = session.createQuery( "delete ForeignCustomer where name = 'Adventures Abroad'" )
// .executeUpdate();
// assertThat( rows, is( 1 ) );
// }
// );
//
// scope.inTransaction(
// session -> {
// final int rows = session.createQuery( "delete DomesticCustomer where name = 'Domestic Wonders'" )
// .executeUpdate();
// assertThat( rows, is( 1 ) );
// }
// );
//
// scope.inTransaction(
// session -> {
// final int rows = session.createQuery( "delete Customer" )
// .executeUpdate();
// assertThat( rows, is( 0 ) );
// }
// );
// }
}