diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java index e1747d3e12..fb6fdbc7a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java @@ -205,7 +205,7 @@ public class QuerySplitter { } @Override - public SqmAssignment visitAssignment(SqmAssignment assignment) { + public SqmAssignment visitAssignment(SqmAssignment assignment) { throw new UnsupportedOperationException( "Not valid" ); } 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 b2c2fdf368..78623a9b0e 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 @@ -546,8 +546,9 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem for ( ParseTree subCtx : setClauseCtx.children ) { if ( subCtx instanceof HqlParser.AssignmentContext ) { final HqlParser.AssignmentContext assignmentContext = (HqlParser.AssignmentContext) subCtx; + //noinspection unchecked updateStatement.applyAssignment( - consumeDomainPath( (HqlParser.SimplePathContext) assignmentContext.getChild( 0 ) ), + (SqmPath) consumeDomainPath( (HqlParser.SimplePathContext) assignmentContext.getChild( 0 ) ), (SqmExpression) assignmentContext.getChild( 2 ).accept( this ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java index 256e286838..81e5e97240 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java @@ -106,7 +106,7 @@ public interface SemanticQueryWalker { T visitSetClause(SqmSetClause setClause); - T visitAssignment(SqmAssignment assignment); + T visitAssignment(SqmAssignment assignment); T visitInsertSelectStatement(SqmInsertSelectStatement statement); 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 bec0e7b417..59f75f1721 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 @@ -84,6 +84,7 @@ import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; @@ -93,6 +94,7 @@ import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; +import org.hibernate.query.sqm.tree.update.SqmAssignment; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.results.internal.TupleMetadata; @@ -302,8 +304,9 @@ public class QuerySqmImpl ); } if ( sqmStatement instanceof SqmUpdateStatement ) { - SqmUpdateStatement updateStatement = (SqmUpdateStatement) sqmStatement; + final SqmUpdateStatement updateStatement = (SqmUpdateStatement) sqmStatement; verifyImmutableEntityUpdate( hql, updateStatement, getSessionFactory() ); + verifyUpdateTypesMatch( hql, updateStatement ); } else if ( sqmStatement instanceof SqmInsertStatement ) { verifyInsertTypesMatch( hql, (SqmInsertStatement) sqmStatement ); @@ -342,6 +345,31 @@ public class QuerySqmImpl } } + private void verifyUpdateTypesMatch(String hqlString, SqmUpdateStatement sqmStatement) { + final List> assignments = sqmStatement.getSetClause().getAssignments(); + for ( int i = 0; i < assignments.size(); i++ ) { + final SqmAssignment assignment = assignments.get( i ); + final SqmPath targetPath = assignment.getTargetPath(); + final SqmExpression expression = assignment.getValue(); + if ( targetPath.getNodeJavaType() == null || expression.getNodeJavaType() == null ) { + continue; + } + if ( targetPath.getNodeJavaType() != expression.getNodeJavaType() + && !targetPath.getNodeJavaType().isWider( expression.getNodeJavaType() ) ) { + throw new SemanticException( + String.format( + "The assignment expression type [%s] did not match the assignment path type [%s] for the path [%s]", + expression.getNodeJavaType().getJavaType().getTypeName(), + targetPath.getNodeJavaType().getJavaType().getTypeName(), + targetPath.toHqlString() + ), + hqlString, + null + ); + } + } + } + private void verifyInsertTypesMatch(String hqlString, SqmInsertStatement sqmStatement) { final List> insertionTargetPaths = sqmStatement.getInsertionTargetPaths(); if ( sqmStatement instanceof SqmInsertValuesStatement ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index f77df8232d..4285f177a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -383,7 +383,7 @@ public class SqmTreePrinter implements SemanticQueryWalker { } @Override - public Object visitAssignment(SqmAssignment assignment) { + public Object visitAssignment(SqmAssignment assignment) { processStanza( "assignment", () -> { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index f885efdcc2..e3755223e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -155,8 +155,8 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter sqmParameter) { + protected Expression consumeSqmParameter( + SqmParameter sqmParameter, + MappingModelExpressible valueMapping, + BiConsumer jdbcParameterConsumer) { assert parameterResolutionConsumer != null; - final Expression expression = super.consumeSingleSqmParameter( sqmParameter ); + final Expression expression = super.consumeSqmParameter( sqmParameter, valueMapping, jdbcParameterConsumer ); final List> jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter ); final MappingModelExpressible mappingType = getSqmParameterMappingModelExpressibleResolutions().get( sqmParameter ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java index 9ee1378740..02ce83d592 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java @@ -144,7 +144,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker assignment : setClause.getAssignments() ) { visitAssignment( assignment ); } } @@ -152,7 +152,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker assignment) { assignment.getTargetPath().accept( this ); assignment.getValue().accept( this ); return assignment; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 43f3d98eed..8831a7e94e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -837,7 +837,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base public List visitSetClause(SqmSetClause setClause) { final List assignments = new ArrayList<>( setClause.getAssignments().size() ); - for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) { + for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) { final List targetColumnReferences = new ArrayList<>(); pushProcessingState( @@ -4546,12 +4546,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base return consumeSqmParameter( expression ); } - protected Expression consumeSqmParameter( - SqmParameter sqmParameter, - BiConsumer jdbcParameterConsumer) { - return consumeSqmParameter( sqmParameter, determineValueMapping( sqmParameter ), jdbcParameterConsumer ); - } - protected Expression consumeSqmParameter( SqmParameter sqmParameter, MappingModelExpressible valueMapping, @@ -4650,37 +4644,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base } protected Expression consumeSingleSqmParameter(SqmParameter sqmParameter) { - final MappingModelExpressible valueMapping = determineValueMapping( sqmParameter ); - - final List jdbcParametersForSqm = new ArrayList<>(); - - resolveSqmParameter( - sqmParameter, - valueMapping, - jdbcParametersForSqm::add - ); - - this.jdbcParameters.addParameters( jdbcParametersForSqm ); - this.jdbcParamsBySqmParam - .computeIfAbsent( sqmParameter, k -> new ArrayList<>( 1 ) ) - .add( jdbcParametersForSqm ); - - final QueryParameterImplementor queryParameter = domainParameterXref.getQueryParameter( sqmParameter ); - final QueryParameterBinding binding = domainParameterBindings.getBinding( queryParameter ); - if ( binding.setType( valueMapping ) ) { - replaceJdbcParametersType( - sqmParameter, - domainParameterXref.getSqmParameters( queryParameter ), - valueMapping - ); - } - return new SqmParameterInterpretation( - sqmParameter, - queryParameter, - jdbcParametersForSqm, - valueMapping, - qp -> binding - ); + return consumeSqmParameter( sqmParameter, determineValueMapping( sqmParameter ), (integer, jdbcParameter) -> {} ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java index 072cc56935..a9d429bfa7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java @@ -13,33 +13,31 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression; /** * @author Steve Ebersole */ -public class SqmAssignment { - private final SqmPath targetPath; - private final SqmExpression value; +public class SqmAssignment { + private final SqmPath targetPath; + private final SqmExpression value; - public SqmAssignment(SqmPath targetPath, SqmExpression value) { + public SqmAssignment(SqmPath targetPath, SqmExpression value) { this.targetPath = targetPath; this.value = value; - - //noinspection unchecked this.value.applyInferableType( targetPath.getNodeType() ); } - public SqmAssignment copy(SqmCopyContext context) { - return new SqmAssignment( targetPath.copy( context ), value.copy( context ) ); + public SqmAssignment copy(SqmCopyContext context) { + return new SqmAssignment<>( targetPath.copy( context ), value.copy( context ) ); } /** * The attribute/path to be updated */ - public SqmPath getTargetPath() { + public SqmPath getTargetPath() { return targetPath; } /** * The new value */ - public SqmExpression getValue() { + public SqmExpression getValue() { return value; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java index df3e4f9a61..c9a3b94d27 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java @@ -18,33 +18,33 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression; * @author Steve Ebersole */ public class SqmSetClause { - private final List assignments; + private final List> assignments; public SqmSetClause() { this.assignments = new ArrayList<>(); } - private SqmSetClause(List assignments) { + private SqmSetClause(List> assignments) { this.assignments = assignments; } public SqmSetClause copy(SqmCopyContext context) { - final List assignments = new ArrayList<>( this.assignments.size() ); - for ( SqmAssignment assignment : this.assignments ) { + final List> assignments = new ArrayList<>( this.assignments.size() ); + for ( SqmAssignment assignment : this.assignments ) { assignments.add( assignment.copy( context ) ); } return new SqmSetClause( assignments ); } - public List getAssignments() { + public List> getAssignments() { return Collections.unmodifiableList( assignments ); } - public void addAssignment(SqmAssignment assignment) { + public void addAssignment(SqmAssignment assignment) { assignments.add( assignment ); } - public void addAssignment(SqmPath targetPath, SqmExpression value) { - addAssignment( new SqmAssignment( targetPath, value ) ); + public void addAssignment(SqmPath targetPath, SqmExpression value) { + addAssignment( new SqmAssignment<>( targetPath, value ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java index c45c1a8ca2..31e991868d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java @@ -23,7 +23,6 @@ import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; -import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Path; @@ -78,7 +77,7 @@ public class SqmUpdateStatement } final SqmUpdateStatement statement = context.registerCopy( this, - new SqmUpdateStatement( + new SqmUpdateStatement<>( nodeBuilder(), getQuerySource(), copyParameters( context ), @@ -111,25 +110,29 @@ public class SqmUpdateStatement @Override public SqmUpdateStatement set(SingularAttribute attribute, Expression value) { - applyAssignment( getTarget().get( attribute ), (SqmExpression) value ); + //noinspection unchecked + applyAssignment( getTarget().get( attribute ), (SqmExpression) value ); return this; } @Override public SqmUpdateStatement set(Path attribute, X value) { - applyAssignment( (SqmPath) attribute, nodeBuilder().value( value ) ); + applyAssignment( (SqmPath) attribute, nodeBuilder().value( value ) ); return this; } @Override public SqmUpdateStatement set(Path attribute, Expression value) { - applyAssignment( (SqmPath) attribute, (SqmExpression) value ); + //noinspection unchecked + applyAssignment( (SqmPath) attribute, (SqmExpression) value ); return this; } @Override public SqmUpdateStatement set(String attributeName, Object value) { - applyAssignment( getTarget().get( attributeName ), nodeBuilder().value( value ) ); + //noinspection unchecked + final SqmPath sqmPath = (SqmPath) getTarget().get( attributeName ); + applyAssignment( sqmPath, nodeBuilder().value( value ) ); return this; } @@ -167,11 +170,11 @@ public class SqmUpdateStatement return walker.visitUpdateStatement( this ); } - public void applyAssignment(SqmPath targetPath, SqmExpression value) { + public void applyAssignment(SqmPath targetPath, SqmExpression value) { if ( setClause == null ) { setClause = new SqmSetClause(); } - setClause.addAssignment( new SqmAssignment( targetPath, value ) ); + setClause.addAssignment( new SqmAssignment<>( targetPath, value ) ); } @Override @@ -183,7 +186,7 @@ public class SqmUpdateStatement sb.append( getTarget().getEntityName() ); sb.append( ' ' ).append( getTarget().resolveAlias() ); sb.append( " set " ); - final List assignments = setClause.getAssignments(); + final List> assignments = setClause.getAssignments(); appendAssignment( assignments.get( 0 ), sb ); for ( int i = 1; i < assignments.size(); i++ ) { sb.append( ", " ); @@ -193,7 +196,7 @@ public class SqmUpdateStatement super.appendHqlString( sb ); } - private static void appendAssignment(SqmAssignment sqmAssignment, StringBuilder sb) { + private static void appendAssignment(SqmAssignment sqmAssignment, StringBuilder sb) { sqmAssignment.getTargetPath().appendHqlString( sb ); sb.append( " = " ); sqmAssignment.getValue().appendHqlString( sb ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/Child.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/Child.java index ee5abca6ad..d1a40150d6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/Child.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/Child.java @@ -13,6 +13,7 @@ import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToOne; /** * Entity having a many to one in its pk @@ -25,4 +26,7 @@ public class Child { @EmbeddedId @AttributeOverride(name = "nthChild", column = @Column(name = "nth")) public ChildPk id; + + @ManyToOne + public Parent parent1; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/CompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/CompositeIdTest.java index aa69e4c2fe..3105ba377f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/CompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/cid/CompositeIdTest.java @@ -103,6 +103,15 @@ public class CompositeIdTest { ); } + @Test + public void testUpdateCompositeIdFkAssociation(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "update Child c set c.parent1 = null" ).executeUpdate(); + } + ); + } + /** * This feature is not supported by the EJB3 diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/CompositeIdFkUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/CompositeIdFkUpdateTest.java new file mode 100644 index 0000000000..9c4fafbdef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/CompositeIdFkUpdateTest.java @@ -0,0 +1,61 @@ +package org.hibernate.orm.test.jpa; + +import org.hibernate.query.SemanticException; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Query; + +import static org.assertj.core.api.Assertions.assertThat; + +@Jpa(annotatedClasses = { + EntityWithCompositeIdFkAssociation.class, + EntityWithCompositeId.class, + CompositeId.class +}) +public class CompositeIdFkUpdateTest { + + @Test + public void testUpdateAssociationSetNull(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Query q = entityManager.createQuery( + "update EntityWithCompositeIdFkAssociation e set e.association = null" ); + + q.executeUpdate(); + } + ); + } + + @Test + public void testUpdateAssociationSetRubbish(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + try { + entityManager.createQuery( + "update EntityWithCompositeIdFkAssociation e set e.association = 1" ); + Assertions.fail( "Expected query type validation to fail due to illegal assignment" ); + } + catch (IllegalArgumentException ex) { + assertThat( ex.getCause() ).isInstanceOf( SemanticException.class ); + assertThat( ex.getCause() ).hasMessageContaining( "did not match" ); + } + } + ); + } + + @Test + public void testUpdateAssociationSetAssociationPart(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Query q = entityManager.createQuery( + "update EntityWithCompositeIdFkAssociation e set e.association.id.id1 = 1" ); + + q.executeUpdate(); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/EntityWithCompositeIdFkAssociation.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/EntityWithCompositeIdFkAssociation.java new file mode 100644 index 0000000000..9c7fb17267 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/EntityWithCompositeIdFkAssociation.java @@ -0,0 +1,43 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.jpa; + +import java.io.Serializable; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +/** + * + */ +@Entity +@Table(name = "entity_composite_fk") +public class EntityWithCompositeIdFkAssociation implements Serializable { + + @Id + private int id; + @ManyToOne + private EntityWithCompositeId association; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public EntityWithCompositeId getAssociation() { + return association; + } + + public void setAssociation(EntityWithCompositeId association) { + this.association = association; + } +}