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:
parent
375076df35
commit
5f2745a503
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 -> {};
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ public class SqmInsertSelectTranslation {
|
|||
return sqlAst;
|
||||
}
|
||||
|
||||
public Map<SqmParameter, List<JdbcParameter>> getJdbcParamMap() {
|
||||
public Map<SqmParameter, List<JdbcParameter>> getJdbcParamsBySqmParam() {
|
||||
return jdbcParamMap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue