diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacySqlAstTranslator.java index 60f4d0562e58..705d7ea8ebca 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacySqlAstTranslator.java @@ -399,4 +399,13 @@ protected void renderStringContainsExactlyPredicate(Expression haystack, Express needle.accept( this ); appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" ); } + + @Override + protected void appendAssignmentColumn(ColumnReference column) { + column.appendColumnForWrite( + this, + getAffectedTableNames().size() > 1 && !(getStatement() instanceof InsertSelectStatement) + ? determineColumnReferenceQualifier( column ) + : null ); + } } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacySqlAstTranslator.java index abcd4115a117..66d3bcdc0851 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacySqlAstTranslator.java @@ -390,4 +390,13 @@ protected void renderStringContainsExactlyPredicate(Expression haystack, Express needle.accept( this ); appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" ); } + + @Override + protected void appendAssignmentColumn(ColumnReference column) { + column.appendColumnForWrite( + this, + getAffectedTableNames().size() > 1 && !(getStatement() instanceof InsertSelectStatement) + ? determineColumnReferenceQualifier( column ) + : null ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java index ced57f3cfbd9..f37a73b491b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MariaDBSqlAstTranslator.java @@ -411,4 +411,13 @@ protected void renderStringContainsExactlyPredicate(Expression haystack, Express needle.accept( this ); appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" ); } + + @Override + protected void appendAssignmentColumn(ColumnReference column) { + column.appendColumnForWrite( + this, + getAffectedTableNames().size() > 1 && !(getStatement() instanceof InsertSelectStatement) + ? determineColumnReferenceQualifier( column ) + : null ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java index f4d714987de6..fe52bb0cac8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/MySQLSqlAstTranslator.java @@ -437,4 +437,13 @@ protected void renderStringContainsExactlyPredicate(Expression haystack, Express needle.accept( this ); appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" ); } + + @Override + protected void appendAssignmentColumn(ColumnReference column) { + column.appendColumnForWrite( + this, + getAffectedTableNames().size() > 1 && !(getStatement() instanceof InsertSelectStatement) + ? determineColumnReferenceQualifier( column ) + : null ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 8e8111ba5489..5e62fcc0a235 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -1160,7 +1160,7 @@ protected void visitSetAssignment(Assignment assignment) { final List columnReferences = assignable.getColumnReferences(); final Expression assignedValue = assignment.getAssignedValue(); if ( columnReferences.size() == 1 ) { - columnReferences.get( 0 ).appendColumnForWrite( this, null ); + appendAssignmentColumn( columnReferences.get( 0 ) ); appendSql( '=' ); final SqlTuple sqlTuple = getSqlTuple( assignedValue ); if ( sqlTuple != null ) { @@ -1175,7 +1175,7 @@ else if ( assignedValue instanceof SelectStatement ) { char separator = OPEN_PARENTHESIS; for ( ColumnReference columnReference : columnReferences ) { appendSql( separator ); - columnReference.appendColumnForWrite( this, null ); + appendAssignmentColumn( columnReference ); separator = COMMA_SEPARATOR_CHAR; } appendSql( ")=" ); @@ -1184,7 +1184,7 @@ else if ( assignedValue instanceof SelectStatement ) { else { assert assignedValue instanceof SqlTupleContainer; final List expressions = ( (SqlTupleContainer) assignedValue ).getSqlTuple().getExpressions(); - columnReferences.get( 0 ).appendColumnForWrite( this, null ); + appendAssignmentColumn( columnReferences.get( 0 ) ); appendSql( '=' ); expressions.get( 0 ).accept( this ); for ( int i = 1; i < columnReferences.size(); i++ ) { @@ -1196,6 +1196,10 @@ else if ( assignedValue instanceof SelectStatement ) { } } + protected void appendAssignmentColumn(ColumnReference column) { + column.appendColumnForWrite( this, null ); + } + protected void visitSetAssignmentEmulateJoin(Assignment assignment, UpdateStatement statement) { final Assignable assignable = assignment.getAssignable(); if ( assignable instanceof SqmPathInterpretation sqmPathInterpretation ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/CriteriaUpdateAndDeleteWithJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/CriteriaUpdateAndDeleteWithJoinTest.java new file mode 100644 index 000000000000..234f45c169fa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/CriteriaUpdateAndDeleteWithJoinTest.java @@ -0,0 +1,152 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Root; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Jpa( + annotatedClasses = { + CriteriaUpdateAndDeleteWithJoinTest.Parent.class, + CriteriaUpdateAndDeleteWithJoinTest.Child.class + } +) +@JiraKey( "HHH-19579" ) +public class CriteriaUpdateAndDeleteWithJoinTest { + private static final String CHILD_CODE = "123"; + + @BeforeEach + public void setup(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Child child = new Child( 1L, CHILD_CODE ); + Parent parent = new Parent( 2L, "456", child ); + entityManager.persist( parent ); + } + ); + } + + @AfterEach + public void teardown(EntityManagerFactoryScope scope) { + scope.releaseEntityManagerFactory(); + } + + @Test + public void testUpdate(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaUpdate update = cb.createCriteriaUpdate(Parent.class); + + Root root = update.from(Parent.class); + Join joinColor = root.join("child", JoinType.INNER); + + update.set(root.get("code"), "l1s2"); + update.where(cb.equal(joinColor.get("code"), cb.parameter(String.class, "code"))); + + int count = entityManager.createQuery(update).setParameter("code", CHILD_CODE).executeUpdate(); + assertThat(count).isEqualTo(1); + } + ); + } + + @Test + public void testDelete(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaDelete delete = cb.createCriteriaDelete(Parent.class); + + Root root = delete.from(Parent.class); + Join joinColor = root.join("child", JoinType.INNER); + + delete.where(cb.equal(joinColor.get("code"), cb.parameter(String.class, "code"))); + + int count = entityManager.createQuery(delete).setParameter("code", CHILD_CODE).executeUpdate(); + assertThat(count).isEqualTo(1); + } + ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + private Long id; + + @Column(name = "code") + private String code; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @JoinColumn(name = "color_id") + private Child child; + + public Parent() { + } + + public Parent(Long id, String code, Child child) { + this.id = id; + this.code = code; + this.child = child; + } + + public Long getId() { + return id; + } + + public String getCode() { + return code; + } + + public Child getChild() { + return child; + } + } + + @Entity(name = "Child") + public static class Child { + + @Id + private Long id; + + @Column(name = "code") + private String code; + + public Child() { + } + + public Child(Long id, String code) { + this.id = id; + this.code = code; + } + + public Long getId() { + return id; + } + + public String getCode() { + return code; + } + } +}