HQL 'insert ... select' and 'update'

- fill in the implementation of HQL insert ... select
- clean up grammar for HQL insert/delete/update
- fix syntax for 'set' clause of HQL update
- fix translation of set value expression in HQL update
- tentative fix for attribute resolution with repeated entities
This commit is contained in:
gavinking 2020-02-15 22:54:49 +01:00 committed by Steve Ebersole
parent 375076df35
commit 5f2745a503
9 changed files with 222 additions and 44 deletions

View File

@ -35,16 +35,20 @@ subQuery
: querySpec
;
rootEntity
: entityName identificationVariableDef?
;
deleteStatement
: DELETE FROM? entityName identificationVariableDef? whereClause?
: DELETE FROM? rootEntity whereClause?
;
updateStatement
: UPDATE FROM? entityName identificationVariableDef? setClause whereClause?
: UPDATE rootEntity setClause whereClause?
;
setClause
: SET assignment+
: SET assignment (COMMA assignment)*
;
assignment
@ -52,16 +56,7 @@ assignment
;
insertStatement
// todo (6.0 : VERSIONED
: INSERT insertSpec querySpec
;
insertSpec
: intoSpec targetFieldsSpec
;
intoSpec
: INTO entityName
: INSERT INTO? rootEntity targetFieldsSpec querySpec
;
targetFieldsSpec

View File

@ -304,24 +304,28 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmInsertSelectStatement visitInsertStatement(HqlParser.InsertStatementContext ctx) {
final EntityDomainType<?> targetType = visitEntityName( ctx.insertSpec().intoSpec().entityName() );
final SqmRoot<?> root = new SqmRoot<>( targetType, null, creationContext.getNodeBuilder() );
processingStateStack.getCurrent().getPathRegistry().register( root );
final SqmRoot<?> root = new SqmRoot<>(
visitEntityName( ctx.rootEntity().entityName() ),
visitIdentificationVariableDef( ctx.rootEntity().identificationVariableDef() ),
creationContext.getNodeBuilder()
);
// for now we only support the INSERT-SELECT form
final SqmInsertSelectStatement insertStatement = new SqmInsertSelectStatement<>(
root,
creationContext.getQueryEngine().getCriteriaBuilder()
);
final SqmInsertSelectStatement<?> insertStatement = new SqmInsertSelectStatement<>( root, creationContext.getNodeBuilder() );
parameterCollector = insertStatement;
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
insertStatement,
this
);
processingStateStack.push( new SqmDmlCreationProcessingState( insertStatement, this ) );
processingStateStack.push( processingState );
processingState.getPathRegistry().register( root );
try {
insertStatement.setSelectQuerySpec( visitQuerySpec( ctx.querySpec() ) );
for ( HqlParser.DotIdentifierSequenceContext stateFieldCtx : ctx.insertSpec().targetFieldsSpec().dotIdentifierSequence() ) {
for ( HqlParser.DotIdentifierSequenceContext stateFieldCtx : ctx.targetFieldsSpec().dotIdentifierSequence() ) {
final SqmPath stateField = (SqmPath) visitDotIdentifierSequence( stateFieldCtx );
// todo : validate each resolved stateField...
insertStatement.addInsertTargetStateField( stateField );
@ -337,8 +341,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmUpdateStatement visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
final SqmRoot<?> root = new SqmRoot<>(
visitEntityName( ctx.entityName() ),
visitIdentificationVariableDef( ctx.identificationVariableDef() ),
visitEntityName( ctx.rootEntity().entityName() ),
visitIdentificationVariableDef( ctx.rootEntity().identificationVariableDef() ),
creationContext.getNodeBuilder()
);
@ -371,8 +375,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmDeleteStatement visitDeleteStatement(HqlParser.DeleteStatementContext ctx) {
final SqmRoot<?> root = new SqmRoot<>(
visitEntityName( ctx.entityName() ),
visitIdentificationVariableDef( ctx.identificationVariableDef() ),
visitEntityName( ctx.rootEntity().entityName() ),
visitIdentificationVariableDef( ctx.rootEntity().identificationVariableDef() ),
creationContext.getNodeBuilder()
);

View File

@ -170,7 +170,7 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
for ( SqmFrom fromElement : sqmFromByPath.values() ) {
if ( definesAttribute( fromElement.getReferencedPathSource(), navigableName ) ) {
if ( found != null ) {
throw new IllegalStateException( "Multiple from-elements expose unqualified attribute : " + navigableName );
// throw new IllegalStateException( "Multiple from-elements expose unqualified attribute : " + navigableName );
}
found = fromElement;
}

View File

@ -51,6 +51,8 @@ import org.hibernate.query.sqm.tree.SqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
@ -531,6 +533,10 @@ public class QuerySqmImpl<R>
return buildUpdateQueryPlan();
}
if ( getSqmStatement() instanceof SqmInsertStatement ) {
return buildInsertQueryPlan();
}
throw new NotYetImplementedException( "Query#executeUpdate for Statements of type [" + getSqmStatement() + "not yet supported" );
}
@ -564,6 +570,22 @@ public class QuerySqmImpl<R>
}
}
private NonSelectQueryPlan buildInsertQueryPlan() {
final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement();
final String entityNameToUpdate = sqmInsert.getTarget().getReferencedPathSource().getHibernateEntityName();
final EntityPersister entityDescriptor = getSessionFactory().getDomainModel().findEntityDescriptor( entityNameToUpdate );
// final SqmMultiTableMutationStrategy multiTableStrategy = entityDescriptor.getSqmMultiTableMutationStrategy();
// if ( multiTableStrategy == null ) {
return new SimpleInsertQueryPlan( (SqmInsertSelectStatement) sqmInsert, domainParameterXref );
// }
// else {
//TODO:
// return new MultiTableUpdateQueryPlan( sqmInsert, domainParameterXref, multiTableStrategy );
// }
}
@Override
public Callback getCallback() {
return afterLoadAction -> {};

View File

@ -0,0 +1,104 @@
/*
* 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.internal;
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.SqmInsertSelectTranslation;
import org.hibernate.query.sqm.sql.SqmInsertSelectTranslator;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.sql.ast.SqlAstInsertSelectTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcInsert;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import java.util.List;
import java.util.Map;
/**
* @author Gavin King
*/
public class SimpleInsertQueryPlan implements NonSelectQueryPlan {
private final SqmInsertSelectStatement sqmInsert;
private final DomainParameterXref domainParameterXref;
private JdbcInsert jdbcInsert;
private FromClauseAccess tableGroupAccess;
private Map<QueryParameterImplementor<?>, Map<SqmParameter, List<JdbcParameter>>> jdbcParamsXref;
public SimpleInsertQueryPlan(
SqmInsertSelectStatement sqmInsert,
DomainParameterXref domainParameterXref) {
this.sqmInsert = sqmInsert;
this.domainParameterXref = domainParameterXref;
}
@Override
public int executeUpdate(ExecutionContext executionContext) {
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final JdbcServices jdbcServices = factory.getJdbcServices();
if ( jdbcInsert == null ) {
final QueryEngine queryEngine = factory.getQueryEngine();
final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory();
final SqmInsertSelectTranslator translator = translatorFactory.createInsertSelectTranslator(
executionContext.getQueryOptions(),
domainParameterXref,
executionContext.getQueryParameterBindings(),
executionContext.getLoadQueryInfluencers(),
factory
);
final SqmInsertSelectTranslation sqmInterpretation = translator.translate(sqmInsert);
tableGroupAccess = translator.getFromClauseAccess();
this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
domainParameterXref,
sqmInterpretation::getJdbcParamsBySqmParam
);
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final SqlAstInsertSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory );
jdbcInsert = sqlAstTranslator.translate( sqmInterpretation.getSqlAst() );
}
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
domainParameterXref,
jdbcParamsXref,
factory.getDomainModel(),
tableGroupAccess::findTableGroup,
executionContext.getSession()
);
return jdbcServices.getJdbcMutationExecutor().execute(
jdbcInsert,
jdbcParameterBindings,
sql -> executionContext.getSession()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql ),
(integer, preparedStatement) -> {},
executionContext
);
}
}

View File

@ -31,7 +31,7 @@ public class SqmInsertSelectTranslation {
return sqlAst;
}
public Map<SqmParameter, List<JdbcParameter>> getJdbcParamMap() {
public Map<SqmParameter, List<JdbcParameter>> getJdbcParamsBySqmParam() {
return jdbcParamMap;
}
}

View File

@ -80,7 +80,7 @@ public class StandardSqmDeleteTranslator
final NavigablePath rootPath = statement.getTarget().getNavigablePath();
final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup(
rootPath,
null,
statement.getRoot().getAlias(),
false,
LockMode.WRITE,
stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ),

View File

@ -8,6 +8,7 @@ package org.hibernate.query.sqm.sql.internal;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
@ -17,18 +18,32 @@ import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter;
import org.hibernate.query.sqm.sql.SqmInsertSelectTranslation;
import org.hibernate.query.sqm.sql.SqmInsertSelectTranslator;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.Assignable;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import java.util.List;
/**
* @author Steve Ebersole
*/
public class StandardSqmInsertSelectTranslator
extends BaseSqmToSqlAstConverter
implements SqmInsertSelectTranslator {
implements SqmInsertSelectTranslator, DomainResultCreationState {
private final List<DomainResult> domainResults = CollectionHelper.arrayList( 10 );
public StandardSqmInsertSelectTranslator(
SqlAstCreationContext creationContext,
QueryOptions queryOptions,
@ -64,10 +79,10 @@ public class StandardSqmInsertSelectTranslator
);
try {
final NavigablePath rootPath = new NavigablePath( entityName );
final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath();
final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup(
rootPath,
null,
sqmStatement.getTarget().getExplicitAlias(),
false,
LockMode.WRITE,
stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ),
@ -77,7 +92,7 @@ public class StandardSqmInsertSelectTranslator
);
if ( ! rootTableGroup.getTableReferenceJoins().isEmpty()
|| rootTableGroup.getTableGroupJoins().isEmpty() ) {
|| ! rootTableGroup.getTableGroupJoins().isEmpty() ) {
throw new HibernateException( "Not expecting multiple table references for an SQM INSERT-SELECT" );
}
@ -85,6 +100,12 @@ public class StandardSqmInsertSelectTranslator
insertSelectStatement.setTargetTable( rootTableGroup.getPrimaryTableReference() );
List<SqmPath> targetPaths = sqmStatement.getInsertionTargetPaths();
for (SqmPath target : targetPaths) {
Assignable assignable = (Assignable) target.accept(this);
insertSelectStatement.addTargetColumnReferences( assignable.getColumnReferences() );
}
insertSelectStatement.setSourceSelectStatement(
visitQuerySpec( sqmStatement.getSelectQuerySpec() )
);
@ -95,4 +116,40 @@ public class StandardSqmInsertSelectTranslator
getProcessingStateStack().pop();
}
}
private DomainResultProducer resolveDomainResultProducer(SqmSelection sqmSelection) {
return (DomainResultProducer) sqmSelection.getSelectableNode().accept( this );
}
@Override
public Void visitSelection(SqmSelection sqmSelection) {
final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection );
// if ( getProcessingStateStack().depth() > 1 ) {
// resultProducer.applySqlSelections( this );
// }
// else {
final DomainResult domainResult = resultProducer.createDomainResult(
sqmSelection.getAlias(),
this
);
domainResults.add( domainResult );
// }
return null;
}
@Override
public SelectStatement visitSelectStatement(SqmSelectStatement statement) {
final QuerySpec querySpec = visitQuerySpec( statement.getQuerySpec() );
return new SelectStatement( querySpec, domainResults );
}
@Override
public SqlAstCreationState getSqlAstCreationState() {
return this;
}
}

View File

@ -12,7 +12,6 @@ import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
@ -94,7 +93,7 @@ public class StandardSqmUpdateTranslator
final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath();
final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup(
rootPath,
null,
sqmStatement.getRoot().getAlias(),
false,
LockMode.WRITE,
getSqlAliasBaseGenerator(),
@ -163,9 +162,9 @@ public class StandardSqmUpdateTranslator
public List<Assignment> visitSetClause(SqmSetClause setClause) {
final List<Assignment> assignments = new ArrayList<>();
final List<ColumnReference> targetColumnReferences = new ArrayList<>();
for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) {
final List<ColumnReference> targetColumnReferences = new ArrayList<>();
getProcessingStateStack().push(
new SqlAstProcessingStateImpl(
getProcessingStateStack().getCurrent(),
@ -263,13 +262,10 @@ public class StandardSqmUpdateTranslator
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" );
for (ColumnReference columnReference : targetColumnReferences) {
assignments.add(
new Assignment( columnReference, valueExpression )
);
}
}
}