HHH-14197 Criteria API doubly-nested subquery generates invalid SQL - missing subquery root

This commit is contained in:
Nathan Xu 2020-09-03 10:26:54 -04:00 committed by Christian Beikov
parent 17d365ecf8
commit 37a60ea8bb
5 changed files with 244 additions and 4 deletions

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
} );
}
}