HHH-13111 Restore support of criteria subqueries in select clauses

When the JPA query compliance mode is disabled (the default), it should
be possible to include subqueries in select clauses of a criteria query.

This was previously accepted due to a bug. After having fixed HHH-13001, we
started to throw an IllegalStateException.
This commit is contained in:
Guillaume Smet 2018-11-23 14:59:39 +01:00
parent a89a9beeb0
commit 25554375f2
4 changed files with 211 additions and 2 deletions

View File

@ -258,8 +258,12 @@ public class CriteriaSubqueryImpl<T> extends ExpressionImpl<T> implements Subque
@Override
public String render(RenderingContext renderingContext) {
if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) {
throw new IllegalStateException( "Subquery cannot occur in select clause" );
if ( criteriaBuilder().getEntityManagerFactory().getSessionFactoryOptions().getJpaCompliance()
.isJpaQueryComplianceEnabled() &&
renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) {
throw new IllegalStateException(
"The JPA specification does not support subqueries in select clauses. " +
"Please disable the JPA query compliance if you want to use this feature." );
}
StringBuilder subqueryBuffer = new StringBuilder( "(" );

View File

@ -0,0 +1,108 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.criteria.subquery;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Before;
public abstract class AbstractSubqueryInSelectClauseTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{ Person.class, Document.class };
}
@Before
public void initData() {
doInJPA( this::entityManagerFactory, em -> {
Person p1 = new Person();
Person p2 = new Person();
Document d = new Document();
p1.getLocalized().put( 1, "p1.1" );
p1.getLocalized().put( 2, "p1.2" );
p2.getLocalized().put( 1, "p2.1" );
p2.getLocalized().put( 2, "p2.2" );
d.getContacts().put( 1, p1 );
d.getContacts().put( 2, p2 );
em.persist( p1 );
em.persist( p2 );
em.persist( d );
} );
}
@Entity(name = "Document")
public static class Document {
private Integer id;
private Map<Integer, Person> contacts = new HashMap<Integer, Person>();
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@OneToMany
@CollectionTable
@MapKeyColumn(name = "position")
public Map<Integer, Person> getContacts() {
return contacts;
}
public void setContacts(Map<Integer, Person> contacts) {
this.contacts = contacts;
}
}
@Entity(name = "Person")
public static class Person {
private Integer id;
private Map<Integer, String> localized = new HashMap<Integer, String>();
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@ElementCollection
public Map<Integer, String> getLocalized() {
return localized;
}
public void setLocalized(Map<Integer, String> localized) {
this.localized = localized;
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.criteria.subquery;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import java.util.Map;
import javax.persistence.Tuple;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
@TestForIssue(jiraKey = "HHH-13111")
public class SubqueryInSelectClauseJpaComplianceTest extends AbstractSubqueryInSelectClauseTest {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
protected void addConfigOptions(Map options) {
options.put( AvailableSettings.JPA_QUERY_COMPLIANCE, true );
}
@Test(expected = IllegalStateException.class)
public void testSubqueryInSelectClause() {
doInJPA( this::entityManagerFactory, em -> {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<Document> document = query.from( Document.class );
Join<?, ?> contacts = document.join( "contacts", JoinType.LEFT );
Subquery<Long> personCount = query.subquery( Long.class );
Root<Person> person = personCount.from( Person.class );
personCount.select( cb.count( person ) ).where( cb.equal( contacts.get( "id" ), person.get( "id" ) ) );
query.multiselect( document.get( "id" ), personCount.getSelection() );
em.createQuery( query ).getResultList();
} );
}
}

View File

@ -0,0 +1,46 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.criteria.subquery;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.persistence.Tuple;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
@TestForIssue(jiraKey = "HHH-13111")
public class SubqueryInSelectClauseTest extends AbstractSubqueryInSelectClauseTest {
@Test
public void testSubqueryInSelectClause() {
doInJPA( this::entityManagerFactory, em -> {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<Document> document = query.from( Document.class );
Join<?, ?> contacts = document.join( "contacts", JoinType.LEFT );
Subquery<Long> personCount = query.subquery( Long.class );
Root<Person> person = personCount.from( Person.class );
personCount.select( cb.count( person ) ).where( cb.equal( contacts.get( "id" ), person.get( "id" ) ) );
query.multiselect( document.get( "id" ), personCount.getSelection() );
List<?> l = em.createQuery( query ).getResultList();
assertEquals( 2, l.size() );
} );
}
}