diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index 659f148958..1f8067722f 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 4536549700..086f39d27e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -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() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java index 8eb996076c..5396898a30 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java @@ -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; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index 35ba80955b..d9d063e4d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -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 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 } } + 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 -> {}; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java new file mode 100644 index 0000000000..a98a7bd672 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java @@ -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, Map>> 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 + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertSelectTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertSelectTranslation.java index d4c4169a89..87d30f339f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertSelectTranslation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertSelectTranslation.java @@ -31,7 +31,7 @@ public class SqmInsertSelectTranslation { return sqlAst; } - public Map> getJdbcParamMap() { + public Map> getJdbcParamsBySqmParam() { return jdbcParamMap; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java index 014b0ec635..0f5746c2be 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java @@ -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 ), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertSelectTranslator.java index 8e4fb83b50..c3ebff1ed2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertSelectTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertSelectTranslator.java @@ -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 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 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; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java index 7ac3e53927..8d2df6cd67 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java @@ -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 visitSetClause(SqmSetClause setClause) { final List assignments = new ArrayList<>(); - final List targetColumnReferences = new ArrayList<>(); - for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) { + final List 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 ) + ); } } }