HHH-14197 Criteria API doubly-nested subquery generates invalid SQL - missing subquery root
This commit is contained in:
parent
17d365ecf8
commit
37a60ea8bb
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<Employee> employees = new HashSet<>();
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@OneToMany(mappedBy = "department")
|
||||
public Set<Employee> getEmployees() {
|
||||
return this.employees;
|
||||
}
|
||||
public void setEmployees(Set<Employee> employees) {
|
||||
this.employees = employees;
|
||||
}
|
||||
}
|
|
@ -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<Employee> directReports = new HashSet<>();
|
||||
private Map<String, String> 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<Employee> getDirectReports() {
|
||||
return this.directReports;
|
||||
}
|
||||
public void setDirectReports(Set<Employee> directReports) {
|
||||
this.directReports = directReports;
|
||||
}
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name = "name", length = 180)
|
||||
@Column(name = "value", length = 65535, nullable = false)
|
||||
public Map<String, String> getAnnotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
public void setAnnotations(Map<String, String> 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;
|
||||
}
|
||||
}
|
|
@ -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<Employee> query = cb.createQuery( Employee.class );
|
||||
final Root<Employee> employee = query.from( Employee.class );
|
||||
|
||||
final Subquery<Employee> subquery1 = query.subquery( Employee.class );
|
||||
final Root<Employee> employee2 = subquery1.correlate( employee );
|
||||
final SetJoin<Employee, Employee> directReport = employee2.join( Employee_.directReports );
|
||||
|
||||
final Subquery<Employee> subquery2 = subquery1.subquery( Employee.class );
|
||||
final SetJoin<Employee, Employee> 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();
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue