HHH-9305 : HQL FromElement is not reused in some cases resulting in an additional join

(cherry picked from commit a0663f0d6c)
This commit is contained in:
Gail Badner 2014-07-29 16:54:18 -07:00
parent 8b5f959200
commit 88a93f8c03
5 changed files with 632 additions and 7 deletions

View File

@ -454,10 +454,8 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
boolean found = elem != null; boolean found = elem != null;
// even though we might find a pre-existing element by join path, for FromElements originating in a from-clause // even though we might find a pre-existing element by join path, we may not be able to reuse it...
// we should only ever use the found element if the aliases match (null != null here). Implied joins are boolean useFoundFromElement = found && canReuse( elem, classAlias );
// always (?) ok to reuse.
boolean useFoundFromElement = found && ( elem.isImplied() || areSame( classAlias, elem.getClassAlias() ) );
if ( ! useFoundFromElement ) { if ( ! useFoundFromElement ) {
// If this is an implied join in a from element, then use the impled join type which is part of the // If this is an implied join in a from element, then use the impled join type which is part of the
@ -502,9 +500,23 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
setFromElement( elem ); // This 'dot' expression now refers to the resulting from element. setFromElement( elem ); // This 'dot' expression now refers to the resulting from element.
} }
private boolean areSame(String alias1, String alias2) { private boolean canReuse(FromElement fromElement, String requestedAlias) {
// again, null != null here // implicit joins are always(?) ok to reuse
return !StringHelper.isEmpty( alias1 ) && !StringHelper.isEmpty( alias2 ) && alias1.equals( alias2 ); if ( isImplicitJoin( fromElement ) ) {
return true;
}
// if the from-clauses are the same, we can be a little more aggressive in terms of what we reuse
if ( fromElement.getFromClause() == getWalker().getCurrentFromClause() ) {
return true;
}
// otherwise (subquery case) dont reuse the fromElement if we are processing the from-clause of the subquery
return getWalker().getCurrentClauseType() != SqlTokenTypes.FROM;
}
private boolean isImplicitJoin(FromElement fromElement) {
return fromElement.isImplied();
} }
private void setImpliedJoin(FromElement elem) { private void setImpliedJoin(FromElement elem) {

View File

@ -137,6 +137,14 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase {
"legacy/Marelo.hbm.xml" "legacy/Marelo.hbm.xml"
}; };
} }
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
Department.class,
Employee.class,
Title.class
};
}
@Override @Override
public void configure(Configuration cfg) { public void configure(Configuration cfg) {
@ -1010,6 +1018,145 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase {
s.close(); s.close();
} }
@Test
@TestForIssue( jiraKey = "HHH-9305")
public void testExplicitToOneInnerJoin() {
final Employee employee1 = new Employee();
employee1.setFirstName( "Jane" );
employee1.setLastName( "Doe" );
final Title title1 = new Title();
title1.setDescription( "Jane's description" );
final Department dept1 = new Department();
dept1.setDeptName( "Jane's department" );
employee1.setTitle( title1 );
employee1.setDepartment( dept1 );
final Employee employee2 = new Employee();
employee2.setFirstName( "John" );
employee2.setLastName( "Doe" );
final Title title2 = new Title();
title2.setDescription( "John's title" );
employee2.setTitle( title2 );
Session s = openSession();
s.getTransaction().begin();
s.persist( title1 );
s.persist( dept1 );
s.persist( employee1 );
s.persist( title2 );
s.persist( employee2 );
s.getTransaction().commit();
s.close();
s = openSession();
s.getTransaction().begin();
Department department = (Department) s.createQuery( "select e.department from Employee e inner join e.department" ).uniqueResult();
assertEquals( employee1.getDepartment().getDeptName(), department.getDeptName() );
s.delete( employee1 );
s.delete( title1 );
s.delete( department );
s.delete( employee2 );
s.delete( title2 );
s.getTransaction().commit();
s.close();
}
@Test
public void testExplicitToOneOuterJoin() {
final Employee employee1 = new Employee();
employee1.setFirstName( "Jane" );
employee1.setLastName( "Doe" );
final Title title1 = new Title();
title1.setDescription( "Jane's description" );
final Department dept1 = new Department();
dept1.setDeptName( "Jane's department" );
employee1.setTitle( title1 );
employee1.setDepartment( dept1 );
final Employee employee2 = new Employee();
employee2.setFirstName( "John" );
employee2.setLastName( "Doe" );
final Title title2 = new Title();
title2.setDescription( "John's title" );
employee2.setTitle( title2 );
Session s = openSession();
s.getTransaction().begin();
s.persist( title1 );
s.persist( dept1 );
s.persist( employee1 );
s.persist( title2 );
s.persist( employee2 );
s.getTransaction().commit();
s.close();
s = openSession();
s.getTransaction().begin();
List list = s.createQuery( "select e.department from Employee e left join e.department" ).list();
assertEquals( 2, list.size() );
final Department dept;
if ( list.get( 0 ) == null ) {
dept = (Department) list.get( 1 );
}
else {
dept = (Department) list.get( 0 );
assertNull( list.get( 1 ) );
}
assertEquals( dept1.getDeptName(), dept.getDeptName() );
s.delete( employee1 );
s.delete( title1 );
s.delete( dept );
s.delete( employee2 );
s.delete( title2 );
s.getTransaction().commit();
s.close();
}
@Test
public void testExplicitToOneInnerJoinAndImplicitToOne() {
final Employee employee1 = new Employee();
employee1.setFirstName( "Jane" );
employee1.setLastName( "Doe" );
final Title title1 = new Title();
title1.setDescription( "Jane's description" );
final Department dept1 = new Department();
dept1.setDeptName( "Jane's department" );
employee1.setTitle( title1 );
employee1.setDepartment( dept1 );
final Employee employee2 = new Employee();
employee2.setFirstName( "John" );
employee2.setLastName( "Doe" );
final Title title2 = new Title();
title2.setDescription( "John's title" );
employee2.setTitle( title2 );
Session s = openSession();
s.getTransaction().begin();
s.persist( title1 );
s.persist( dept1 );
s.persist( employee1 );
s.persist( title2 );
s.persist( employee2 );
s.getTransaction().commit();
s.close();
s = openSession();
s.getTransaction().begin();
Object[] result = (Object[]) s.createQuery(
"select e.firstName, e.lastName, e.title.description, e.department from Employee e inner join e.department"
).uniqueResult();
assertEquals( employee1.getFirstName(), result[0] );
assertEquals( employee1.getLastName(), result[1] );
assertEquals( employee1.getTitle().getDescription(), result[2] );
assertEquals( employee1.getDepartment().getDeptName(), ( (Department) result[3] ).getDeptName() );
s.delete( employee1 );
s.delete( title1 );
s.delete( result[3] );
s.delete( employee2 );
s.delete( title2 );
s.getTransaction().commit();
s.close();
}
@Test @Test
public void testNestedComponentIsNull() { public void testNestedComponentIsNull() {
// (1) From MapTest originally... // (1) From MapTest originally...
@ -1657,6 +1804,134 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase {
s.close(); s.close();
} }
@Test
@TestForIssue( jiraKey = "HHH-9305")
@SuppressWarnings( {"unchecked"})
public void testSelectClauseImplicitJoinOrderByJoinedProperty() {
Session s = openSession();
Transaction t = s.beginTransaction();
Zoo zoo = new Zoo();
zoo.setName("The Zoo");
zoo.setMammals( new HashMap() );
zoo.setAnimals( new HashMap() );
Mammal plat = new Mammal();
plat.setBodyWeight( 11f );
plat.setDescription( "Platypus" );
plat.setZoo( zoo );
plat.setSerialNumber( "plat123" );
zoo.getMammals().put( "Platypus", plat );
zoo.getAnimals().put("plat123", plat);
Zoo otherZoo = new Zoo();
otherZoo.setName("The Other Zoo");
otherZoo.setMammals( new HashMap() );
otherZoo.setAnimals( new HashMap() );
Mammal zebra = new Mammal();
zebra.setBodyWeight( 110f );
zebra.setDescription( "Zebra" );
zebra.setZoo( otherZoo );
zebra.setSerialNumber( "zebra123" );
otherZoo.getMammals().put( "Zebra", zebra );
otherZoo.getAnimals().put("zebra123", zebra);
Mammal elephant = new Mammal();
elephant.setBodyWeight( 550f );
elephant.setDescription( "Elephant" );
elephant.setZoo( otherZoo );
elephant.setSerialNumber( "elephant123" );
otherZoo.getMammals().put( "Elephant", elephant );
otherZoo.getAnimals().put( "elephant123", elephant );
s.persist( plat );
s.persist(zoo);
s.persist( zebra );
s.persist( elephant );
s.persist( otherZoo );
s.flush();
s.clear();
Query q = s.createQuery("select a.zoo from Animal a where a.zoo is not null order by a.zoo.name");
Type type = q.getReturnTypes()[0];
assertTrue( type instanceof ManyToOneType );
assertEquals( ( (ManyToOneType) type ).getAssociatedEntityName(), "org.hibernate.test.hql.Zoo" );
List<Zoo> zoos = (List<Zoo>) q.list();
assertEquals( 3, zoos.size() );
assertEquals( otherZoo.getName(), zoos.get( 0 ).getName() );
assertEquals( 2, zoos.get( 0 ).getMammals().size() );
assertEquals( 2, zoos.get( 0 ).getAnimals().size() );
assertSame( zoos.get( 0 ), zoos.get( 1 ) );
assertEquals( zoo.getName(), zoos.get( 2 ).getName() );
assertEquals( 1, zoos.get( 2 ).getMammals().size() );
assertEquals( 1, zoos.get( 2 ).getAnimals().size() );
s.clear();
s.delete(plat);
s.delete( zebra );
s.delete( elephant );
s.delete(zoo);
s.delete( otherZoo );
t.commit();
s.close();
}
@Test
@SuppressWarnings( {"unchecked"})
public void testSelectClauseDistinctImplicitJoinOrderByJoinedProperty() {
Session s = openSession();
Transaction t = s.beginTransaction();
Zoo zoo = new Zoo();
zoo.setName("The Zoo");
zoo.setMammals( new HashMap() );
zoo.setAnimals( new HashMap() );
Mammal plat = new Mammal();
plat.setBodyWeight( 11f );
plat.setDescription( "Platypus" );
plat.setZoo( zoo );
plat.setSerialNumber( "plat123" );
zoo.getMammals().put( "Platypus", plat );
zoo.getAnimals().put("plat123", plat);
Zoo otherZoo = new Zoo();
otherZoo.setName("The Other Zoo");
otherZoo.setMammals( new HashMap() );
otherZoo.setAnimals( new HashMap() );
Mammal zebra = new Mammal();
zebra.setBodyWeight( 110f );
zebra.setDescription( "Zebra" );
zebra.setZoo( otherZoo );
zebra.setSerialNumber( "zebra123" );
otherZoo.getMammals().put( "Zebra", zebra );
otherZoo.getAnimals().put("zebra123", zebra);
Mammal elephant = new Mammal();
elephant.setBodyWeight( 550f );
elephant.setDescription( "Elephant" );
elephant.setZoo( otherZoo );
elephant.setSerialNumber( "elephant123" );
otherZoo.getMammals().put( "Elephant", elephant );
otherZoo.getAnimals().put( "elephant123", elephant );
s.persist( plat );
s.persist(zoo);
s.persist( zebra );
s.persist( elephant );
s.persist( otherZoo );
s.flush();
s.clear();
Query q = s.createQuery("select distinct a.zoo from Animal a where a.zoo is not null order by a.zoo.name");
Type type = q.getReturnTypes()[0];
assertTrue( type instanceof ManyToOneType );
assertEquals( ( (ManyToOneType) type ).getAssociatedEntityName(), "org.hibernate.test.hql.Zoo" );
List<Zoo> zoos = (List<Zoo>) q.list();
assertEquals( 2, zoos.size() );
assertEquals( otherZoo.getName(), zoos.get( 0 ).getName() );
assertEquals( 2, zoos.get( 0 ).getMammals().size() );
assertEquals( 2, zoos.get( 0 ).getAnimals().size() );
assertEquals( zoo.getName(), zoos.get( 1 ).getName() );
assertEquals( 1, zoos.get( 1 ).getMammals().size() );
assertEquals( 1, zoos.get( 1 ).getAnimals().size() );
s.clear();
s.delete(plat);
s.delete( zebra );
s.delete( elephant );
s.delete(zoo);
s.delete( otherZoo );
t.commit();
s.close();
}
@Test @Test
@SuppressWarnings( {"unchecked"}) @SuppressWarnings( {"unchecked"})
public void testSelectClauseImplicitJoinWithIterate() { public void testSelectClauseImplicitJoinWithIterate() {
@ -2770,6 +3045,118 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase {
destroyTestBaseData(); destroyTestBaseData();
} }
@Test
@TestForIssue( jiraKey = "HHH-9305")
public void testDynamicInstantiationWithToOneQueries() throws Exception {
final Employee employee1 = new Employee();
employee1.setFirstName( "Jane" );
employee1.setLastName( "Doe" );
final Title title1 = new Title();
title1.setDescription( "Jane's description" );
final Department dept1 = new Department();
dept1.setDeptName( "Jane's department" );
employee1.setTitle( title1 );
employee1.setDepartment( dept1 );
final Employee employee2 = new Employee();
employee2.setFirstName( "John" );
employee2.setLastName( "Doe" );
final Title title2 = new Title();
title2.setDescription( "John's title" );
employee2.setTitle( title2 );
Session s = openSession();
s.getTransaction().begin();
s.persist( title1 );
s.persist( dept1 );
s.persist( employee1 );
s.persist( title2 );
s.persist( employee2 );
s.getTransaction().commit();
s.close();
// There are 2 to-one associations: Employee.title and Employee.department.
// It appears that adding an explicit join for one of these to-one associations keeps ANSI joins
// at the beginning of the FROM clause, avoiding failures on DBs that cannot handle cross joins
// interleaved with ANSI joins (e.g., PostgreSql).
s = openSession();
s.getTransaction().begin();
List results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e inner join e.title"
).list();
assertEquals( "Incorrect result size", 1, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, t.id, t.description, e.department, e.firstName) from Employee e inner join e.title t"
).list();
assertEquals( "Incorrect result size", 1, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e inner join e.department"
).list();
assertEquals( "Incorrect result size", 1, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, d, e.firstName) from Employee e inner join e.department d"
).list();
assertEquals( "Incorrect result size", 1, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, d, e.firstName) from Employee e left outer join e.department d"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department inner join e.title"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, t.id, t.description, d, e.firstName) from Employee e left outer join e.department d inner join e.title t"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department left outer join e.title"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, t.id, t.description, d, e.firstName) from Employee e left outer join e.department d left outer join e.title t"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department order by e.title.description"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
results = session.createQuery(
"select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department d order by e.title.description"
).list();
assertEquals( "Incorrect result size", 2, results.size() );
assertClassAssignability( results.get( 0 ).getClass(), Employee.class );
s.getTransaction().commit();
s.close();
s = openSession();
s.getTransaction().begin();
s.delete( employee1 );
s.delete( title1 );
s.delete( dept1 );
s.delete( employee2 );
s.delete( title2 );
s.getTransaction().commit();
s.close();
}
@Test @Test
@SuppressWarnings( {"UnusedAssignment"}) @SuppressWarnings( {"UnusedAssignment"})
public void testCachedJoinedAndJoinFetchedManyToOne() throws Exception { public void testCachedJoinedAndJoinFetchedManyToOne() throws Exception {

View File

@ -0,0 +1,58 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.hql;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="department")
public class Department implements java.io.Serializable {
private Integer deptNo;
private String deptName;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id_dep")
public Integer getDeptNo() {
return this.deptNo;
}
public void setDeptNo(Integer deptNo) {
this.deptNo = deptNo;
}
public String getDeptName() {
return this.deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}

View File

@ -0,0 +1,107 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.hql;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name="employee")
public class Employee implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id_emp")
private Integer id;
private String firstName;
private String lastName;
@OneToOne
@JoinColumn(name="id_title")
private Title title;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="id_depto")
private Department department;
public Employee() {}
public Employee(Integer _id, String _lastName, Integer _idTitle, String _descriptionTitle, Department _dept, String _fname) {
setId(_id);
setLastName(_lastName);
Title _title = new Title();
_title.setId(_idTitle);
_title.setDescription(_descriptionTitle);
setTitle(_title);
setDepartment(_dept);
setFirstName(_fname);
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Title getTitle() {
return title;
}
public void setTitle(Title title) {
this.title = title;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}

View File

@ -0,0 +1,61 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.hql;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="title")
public class Title implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id_title")
private Integer id;
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}