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 ) {
|
if ( explicitSubqueryFromElement ) {
|
||||||
// Treat explict from elements in sub-queries properly.
|
// Treat explicit from elements in sub-queries properly.
|
||||||
elem.setInProjectionList( true );
|
elem.setInProjectionList( true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,14 +361,14 @@ public class FromElementFactory implements SqlTokenTypes {
|
||||||
destination.initializeCollection( fromClause, classAlias, tableAlias );
|
destination.initializeCollection( fromClause, classAlias, tableAlias );
|
||||||
destination.setType( JOIN_FRAGMENT ); // Tag this node as a JOIN.
|
destination.setType( JOIN_FRAGMENT ); // Tag this node as a JOIN.
|
||||||
destination.setIncludeSubclasses( false ); // Don't include subclasses in the 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.setJoinSequence( collectionJoinSequence );
|
||||||
destination.setOrigin( origin, false );
|
destination.setOrigin( origin, false );
|
||||||
destination.setCollectionTableAlias( tableAlias );
|
destination.setCollectionTableAlias( tableAlias );
|
||||||
// origin.addDestination( destination );
|
// origin.addDestination( destination );
|
||||||
// This was the cause of HHH-242
|
// This was the cause of HHH-242
|
||||||
// origin.setType( FROM_FRAGMENT ); // Set the parent node type so that the AST is properly formed.
|
// 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.setText( "" ); // The destination node will have all the FROM text.
|
||||||
}
|
}
|
||||||
origin.setCollectionJoin( true ); // The parent node is a collection join too (voodoo - see JoinProcessor)
|
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() {
|
private String[] getColumns() {
|
||||||
if ( columns == null ) {
|
if ( columns == null ) {
|
||||||
throw new IllegalStateException( "No foriegn key columns were supplied!" );
|
throw new IllegalStateException( "No foreign key columns were supplied!" );
|
||||||
}
|
}
|
||||||
return columns;
|
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