diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java index f304ff7123..c16a874bc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementFactory.java @@ -226,7 +226,7 @@ public class FromElementFactory implements SqlTokenTypes { } if ( explicitSubqueryFromElement ) { - // Treat explict from elements in sub-queries properly. + // Treat explicit from elements in sub-queries properly. elem.setInProjectionList( true ); } @@ -361,14 +361,14 @@ public class FromElementFactory implements SqlTokenTypes { destination.initializeCollection( fromClause, classAlias, tableAlias ); destination.setType( JOIN_FRAGMENT ); // Tag this node as a JOIN. destination.setIncludeSubclasses( false ); // Don't include subclasses in the join. - destination.setCollectionJoin( true ); // This is a clollection join. + destination.setCollectionJoin( true ); // This is a collection join. destination.setJoinSequence( collectionJoinSequence ); destination.setOrigin( origin, false ); destination.setCollectionTableAlias( tableAlias ); // origin.addDestination( destination ); // This was the cause of HHH-242 // origin.setType( FROM_FRAGMENT ); // Set the parent node type so that the AST is properly formed. - if ( origin.getQueryableCollection() != null ) { + if ( origin.getQueryableCollection() != null && !origin.inProjectionList() ) { origin.setText( "" ); // The destination node will have all the FROM text. } origin.setCollectionJoin( true ); // The parent node is a collection join too (voodoo - see JoinProcessor) @@ -546,7 +546,7 @@ public class FromElementFactory implements SqlTokenTypes { private String[] getColumns() { if ( columns == null ) { - throw new IllegalStateException( "No foriegn key columns were supplied!" ); + throw new IllegalStateException( "No foreign key columns were supplied!" ); } return columns; } diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/AbstractPersistent.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/AbstractPersistent.java new file mode 100644 index 0000000000..8ab3a89042 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/AbstractPersistent.java @@ -0,0 +1,30 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Archie Cobbs + */ + +@MappedSuperclass +public abstract class AbstractPersistent { + + private long id; + + protected AbstractPersistent() { + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + public long getId() { + return this.id; + } + public void setId(long id) { + this.id = id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Department.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Department.java new file mode 100644 index 0000000000..f3ec2eb5d6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Department.java @@ -0,0 +1,32 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.OneToMany; + +/** + * @author Archie Cobbs + */ + +@Entity +public class Department extends AbstractPersistent { + + private String name; + private Set employees = new HashSet<>(); + + public String getName() { + return this.name; + } + public void setName(String name) { + this.name = name; + } + + @OneToMany(mappedBy = "department") + public Set getEmployees() { + return this.employees; + } + public void setEmployees(Set employees) { + this.employees = employees; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java new file mode 100644 index 0000000000..77c18d3105 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java @@ -0,0 +1,118 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Archie Cobbs + */ + +@Entity +public class Employee extends AbstractPersistent { + + private String name; + private float salary; + private Seniority seniority; + private Date startDate; + private Department department; + private Employee manager; + private Set directReports = new HashSet<>(); + private Map annotations = new HashMap<>(); + + public String getName() { + return this.name; + } + public void setName(String name) { + this.name = name; + } + + public float getSalary() { + return this.salary; + } + public void setSalary(float salary) { + this.salary = salary; + } + + @Enumerated(EnumType.STRING) + public Seniority getSeniority() { + return this.seniority; + } + public void setSeniority(Seniority seniority) { + this.seniority = seniority; + } + + @Temporal(TemporalType.TIMESTAMP) + public Date getStartDate() { + return this.startDate; + } + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + @ManyToOne + public Employee getManager() { + return this.manager; + } + public void setManager(Employee manager) { + this.manager = manager; + } + + @ManyToOne + public Department getDepartment() { + return this.department; + } + public void setDepartment(Department department) { + this.department = department; + } + + @OneToMany(mappedBy = "manager") + public Set getDirectReports() { + return this.directReports; + } + public void setDirectReports(Set directReports) { + this.directReports = directReports; + } + + @ElementCollection + @MapKeyColumn(name = "name", length = 180) + @Column(name = "value", length = 65535, nullable = false) + public Map getAnnotations() { + return this.annotations; + } + public void setAnnotations(Map annotations) { + this.annotations = annotations; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + "[name=" + (this.name != null ? "\"" + this.name + "\"" : null) + + ",salary=" + this.salary + + ",startDate=" + this.startDate + + ",department=" + this.department + + ",manager=" + this.manager + + ",directReports=" + this.directReports + + ",annotations=" + this.annotations + + "]"; + } + +// Seniority + + public enum Seniority { + JUNIOR, + SENIOR; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/HHH14197Test.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/HHH14197Test.java new file mode 100644 index 0000000000..42eb514cff --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/HHH14197Test.java @@ -0,0 +1,60 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.MapJoin; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.SetJoin; +import javax.persistence.criteria.Subquery; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Archie Cobbs + * @author Nathan Xu + */ +@TestForIssue( jiraKey = "HHH-14197" ) +public class HHH14197Test extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Department.class, + Employee.class + }; + } + + @Test + public void testValidSQLGenerated() { + // without fixing HHH-14197, invalid SQL would be generated without root + // "... where exists (select employee0_.id as id1_1_ from where ...) ... " + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Employee.class ); + final Root employee = query.from( Employee.class ); + + final Subquery subquery1 = query.subquery( Employee.class ); + final Root employee2 = subquery1.correlate( employee ); + final SetJoin directReport = employee2.join( Employee_.directReports ); + + final Subquery subquery2 = subquery1.subquery( Employee.class ); + final SetJoin directReport2 = subquery2.correlate( directReport ); + directReport2.join( Employee_.annotations ); + + subquery2.select( directReport2 ); + + subquery1.select( employee2 ).where( cb.exists( subquery2 ) ); + + query.select( employee ).where( cb.exists( subquery1 ) ); + + entityManager.createQuery( query ).getResultList(); + } ); + } + +}