From 800f8f6f31f9bdcc59b4d19acaacb29b4a181173 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Fri, 8 Sep 2023 12:07:12 +0200 Subject: [PATCH] HHH-17170 Add test for issue --- ...ementCollectionCustomSqlMutationsTest.java | 162 ++++++++++++++++ .../ManyToManyCustomSqlMutationsTest.java | 176 ++++++++++++++++++ .../OneToManyCustomSqlMutationsTest.java | 167 +++++++++++++++++ 3 files changed, 505 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ElementCollectionCustomSqlMutationsTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyCustomSqlMutationsTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyCustomSqlMutationsTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ElementCollectionCustomSqlMutationsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ElementCollectionCustomSqlMutationsTest.java new file mode 100644 index 0000000000..495dfaa3ab --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ElementCollectionCustomSqlMutationsTest.java @@ -0,0 +1,162 @@ +/* + * 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.orm.test.mapping.collections; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.SQLDeleteAll; +import org.hibernate.annotations.SQLUpdate; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + ElementCollectionCustomSqlMutationsTest.Project.class, + ElementCollectionCustomSqlMutationsTest.User.class, +} ) +@SessionFactory +@Jira( "https://hibernate.atlassian.net/browse/HHH-17170" ) +public class ElementCollectionCustomSqlMutationsTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User u1 = new User( "user1" ); + final User u2 = new User( "user2" ); + final Project p1 = new Project( "p1" ); + p1.getMembers().add( u1 ); + p1.getMembers().add( u2 ); + final Project p2 = new Project( "p2" ); + p2.getMembers().add( u1 ); + p2.getOrderedUsers().add( u2 ); + p2.getOrderedUsers().add( u1 ); + session.persist( p1 ); + session.persist( p2 ); + } ); + } + + @Test + public void testSQLDelete(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p1" ); + project.getMembers().remove( project.getMembers().iterator().next() ); + inspector.clear(); + } ); + // element collection always deletes all records and re-inserts the ones remaining + // the @SQLDelete annotation is ignored + assertThat( inspector.getSqlQueries() ).hasSize( 2 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "1=1" ); + scope.inTransaction( session -> assertThat( + session.find( Project.class, "p1" ).getMembers() + ).hasSize( 1 ) ); + } + + @Test + public void testSQLDeleteAll(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + project.getMembers().remove( project.getMembers().iterator().next() ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 1 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "1=1" ); + scope.inTransaction( session -> assertThat( + session.find( Project.class, "p2" ).getMembers() + ).isEmpty() ); + } + + @Test + public void testSQLUpdate(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + assertThat( project.getOrderedUsers().stream().map( User::getName ) ).containsExactly( "user2", "user1" ); + project.getOrderedUsers().sort( Comparator.comparing( User::getName ) ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 2 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "3=3" ); + assertThat( inspector.getSqlQueries().get( 1 ) ).contains( "3=3" ); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + assertThat( project.getOrderedUsers().stream().map( User::getName ) ).containsExactly( "user1", "user2" ); + } ); + } + + @Entity( name = "Project" ) + @Table( name = "t_project" ) + public static class Project { + @Id + private String name; + + @ElementCollection + @CollectionTable( name = "project_users", joinColumns = { @JoinColumn( name = "project_id" ) } ) + @SQLDeleteAll( sql = "delete from project_users where project_id = ? and 1=1" ) + private Set members = new HashSet<>(); + + @ElementCollection + @CollectionTable( name = "ordered_users", joinColumns = { @JoinColumn( name = "project_id" ) } ) + @OrderColumn( name = "order_col" ) + @SQLUpdate( sql = "update ordered_users set name = ? where project_id = ? and order_col = ? and 3=3" ) + private List orderedUsers = new ArrayList<>(); + + public Project() { + } + + public Project(String name) { + this.name = name; + } + + public Set getMembers() { + return members; + } + + public List getOrderedUsers() { + return orderedUsers; + } + } + + @Embeddable + public static class User { + private String name; + + public User() { + } + + public User(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyCustomSqlMutationsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyCustomSqlMutationsTest.java new file mode 100644 index 0000000000..a0e421746e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyCustomSqlMutationsTest.java @@ -0,0 +1,176 @@ +/* + * 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.orm.test.mapping.manytomany; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLDeleteAll; +import org.hibernate.annotations.SQLUpdate; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Same as {@link org.hibernate.orm.test.mapping.onetomany.OneToManyCustomSqlMutationsTest OneToManyCustomSqlMutationsTest} + * but with {@link ManyToMany} + * + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + ManyToManyCustomSqlMutationsTest.Project.class, + ManyToManyCustomSqlMutationsTest.User.class, +} ) +@SessionFactory( useCollectingStatementInspector = true ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-17170" ) +public class ManyToManyCustomSqlMutationsTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User u1 = new User( "user1" ); + final User u2 = new User( "user2" ); + final Project p1 = new Project( "p1" ); + p1.getMembers().add( u1 ); + p1.getMembers().add( u2 ); + final Project p2 = new Project( "p2" ); + p2.getMembers().add( u1 ); + p2.getOrderedUsers().add( u2 ); + p2.getOrderedUsers().add( u1 ); + session.persist( u1 ); + session.persist( u2 ); + session.persist( p1 ); + session.persist( p2 ); + } ); + } + + @Test + public void testSQLDelete(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p1" ); + project.getMembers().remove( project.getMembers().iterator().next() ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 1 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "1=1" ); + scope.inTransaction( session -> assertThat( + session.find( Project.class, "p1" ).getMembers() + ).hasSize( 1 ) ); + } + + @Test + public void testSQLDeleteAll(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + project.getMembers().remove( project.getMembers().iterator().next() ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 1 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "2=2" ); + scope.inTransaction( session -> assertThat( + session.find( Project.class, "p2" ).getMembers() + ).isEmpty() ); + } + + @Test + public void testSQLUpdate(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + assertThat( project.getOrderedUsers().stream().map( User::getName ) ).containsExactly( "user2", "user1" ); + project.getOrderedUsers().sort( Comparator.comparing( User::getName ) ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 2 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "3=3" ); + assertThat( inspector.getSqlQueries().get( 1 ) ).contains( "3=3" ); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + assertThat( project.getOrderedUsers().stream().map( User::getName ) ).containsExactly( "user1", "user2" ); + } ); + } + + @Entity( name = "Project" ) + @Table( name = "t_project" ) + public static class Project { + @Id + private String name; + + @ManyToMany + @JoinTable( + name = "project_users", + joinColumns = { @JoinColumn( name = "project_id" ) }, + inverseJoinColumns = { @JoinColumn( name = "user_id" ) } + ) + @SQLDelete( sql = "delete from project_users where project_id = ? and user_id = ? and 1=1" ) + @SQLDeleteAll( sql = "delete from project_users where project_id = ? and 2=2" ) + private Set members = new HashSet<>(); + + @ManyToMany + @JoinTable( + name = "ordered_users", + joinColumns = { @JoinColumn( name = "project_id" ) }, + inverseJoinColumns = { @JoinColumn( name = "user_id" ) } + ) + @OrderColumn( name = "order_col" ) + @SQLUpdate( sql = "update ordered_users set user_id = ? where project_id = ? and order_col = ? and 3=3" ) + private List orderedUsers = new ArrayList<>(); + + public Project() { + } + + public Project(String name) { + this.name = name; + } + + public Set getMembers() { + return members; + } + + public List getOrderedUsers() { + return orderedUsers; + } + } + + @Entity( name = "User" ) + @Table( name = "t_user" ) + public static class User { + @Id + private String name; + + public User() { + } + + public User(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyCustomSqlMutationsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyCustomSqlMutationsTest.java new file mode 100644 index 0000000000..f5f80b0a92 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyCustomSqlMutationsTest.java @@ -0,0 +1,167 @@ +/* + * 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.orm.test.mapping.onetomany; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLDeleteAll; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Same as {@link org.hibernate.orm.test.mapping.manytomany.ManyToManyCustomSqlMutationsTest ManyToManyCustomSqlMutationsTest} + * but with {@link OneToMany} + * + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + OneToManyCustomSqlMutationsTest.Project.class, + OneToManyCustomSqlMutationsTest.User.class, +} ) +@SessionFactory( useCollectingStatementInspector = true ) +@SQLDelete( sql = "update t_user set project_id = null where project_id = ? and name = ? and 1=1" ) +public class OneToManyCustomSqlMutationsTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User u1 = new User( "user1" ); + final User u2 = new User( "user2" ); + final Project p1 = new Project( "p1" ); + p1.getMembers().add( u1 ); + p1.getMembers().add( u2 ); + final User u3 = new User( "user3" ); + final Project p2 = new Project( "p2" ); + p2.getMembers().add( u3 ); + p2.getOrderedUsers().add( u2 ); + p2.getOrderedUsers().add( u1 ); + session.persist( u1 ); + session.persist( u2 ); + session.persist( u3 ); + session.persist( p1 ); + session.persist( p2 ); + } ); + } + + @Test + public void testSQLDelete(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p1" ); + project.getMembers().remove( project.getMembers().iterator().next() ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 1 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "1=1" ); + scope.inTransaction( session -> assertThat( + session.find( Project.class, "p1" ).getMembers() + ).hasSize( 1 ) ); + } + + @Test + public void testSQLDeleteAll(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + project.getMembers().remove( project.getMembers().iterator().next() ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 1 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "2=2" ); + scope.inTransaction( session -> assertThat( + session.find( Project.class, "p2" ).getMembers() + ).isEmpty() ); + } + + @Test + public void testSQLUpdate(SessionFactoryScope scope) { + final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + assertThat( project.getOrderedUsers().stream().map( User::getName ) ).containsExactly( "user2", "user1" ); + project.getOrderedUsers().sort( Comparator.comparing( User::getName ) ); + inspector.clear(); + } ); + assertThat( inspector.getSqlQueries() ).hasSize( 4 ); + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "3=3" ); + assertThat( inspector.getSqlQueries().get( 1 ) ).contains( "3=3" ); + scope.inTransaction( session -> { + final Project project = session.find( Project.class, "p2" ); + assertThat( project.getOrderedUsers().stream().map( User::getName ) ).containsExactly( "user1", "user2" ); + } ); + } + + @Entity( name = "Project" ) + @Table( name = "t_project" ) + public static class Project { + @Id + private String name; + + @OneToMany + @JoinColumn( name = "project_id" ) + @SQLDelete( sql = "update t_user set project_id = null where project_id = ? and name = ? and 1=1" ) + @SQLDeleteAll( sql = "update t_user set project_id = null where project_id = ? and 2=2" ) + private Set members = new HashSet<>(); + + @OneToMany + @JoinColumn( name = "ordered_project_id" ) + @OrderColumn( name = "order_col" ) + @SQLDelete( sql = "update t_user set project_id = null where project_id = ? and name = ? and 3=3" ) + private List orderedUsers = new ArrayList<>(); + + public Project() { + } + + public Project(String name) { + this.name = name; + } + + public Set getMembers() { + return members; + } + + public List getOrderedUsers() { + return orderedUsers; + } + } + + @Entity( name = "User" ) + @Table( name = "t_user" ) + public static class User { + @Id + private String name; + + public User() { + } + + public User(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +}