diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index bdd521da6a..03e6ecc676 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -452,6 +452,7 @@ constructor aggregateExpr : expr [ null ] //p:propertyRef { resolve(#p); } | collectionFunction + | selectStatement ; // Establishes the list of aliases being used by this query. diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index cd2d86473d..08f2854b89 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -819,7 +819,7 @@ castedIdentPrimaryBase ; aggregate - : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! additiveExpression CLOSE! { #aggregate.setType(AGGREGATE); } + : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! ( additiveExpression | selectStatement ) CLOSE! { #aggregate.setType(AGGREGATE); } // Special case for count - It's 'parameters' can be keywords. | COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr | NUM_INT | caseExpression ) ) ) CLOSE! | collectionExpr diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/AggregateFunctionsWithSubSelectTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/AggregateFunctionsWithSubSelectTest.java new file mode 100644 index 0000000000..aee32965f2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/AggregateFunctionsWithSubSelectTest.java @@ -0,0 +1,230 @@ +/* + * 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 . + */ +package org.hibernate.test.hql; + +import java.util.HashMap; +import java.util.List; +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 javax.persistence.Tuple; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author bjoern.moritz + */ +@TestForIssue(jiraKey = "HHH-9331") +@RequiresDialect(H2Dialect.class) +public class AggregateFunctionsWithSubSelectTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Document.class, + Person.class + }; + } + + @Override + protected void prepareTest() throws Exception { + doInHibernate( this::sessionFactory, session -> { + Document document = new Document(); + document.setId( 1 ); + + Person p1 = new Person(); + Person p2 = new Person(); + + p1.getLocalized().put(1, "p1.1"); + p1.getLocalized().put(2, "p1.2"); + p2.getLocalized().put(1, "p2.1"); + p2.getLocalized().put(2, "p2.2"); + + document.getContacts().put(1, p1); + document.getContacts().put(2, p2); + + session.persist(p1); + session.persist(p2); + session.persist(document); + } ); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Test + public void testSum() { + + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "SELECT " + + " d.id, " + + " SUM(" + + " (" + + " SELECT COUNT(localized) " + + " FROM Person p " + + " LEFT JOIN p.localized localized " + + " WHERE p.id = c.id" + + " )" + + " ) AS localizedCount " + + "FROM Document d " + + "LEFT JOIN d.contacts c " + + "GROUP BY d.id") + .getResultList(); + + assertEquals(1, results.size()); + Object[] tuple = (Object[]) results.get( 0 ); + assertEquals(1, tuple[0]); + } ); + } + + @Test + public void testMin() { + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "SELECT " + + " d.id, " + + " MIN(" + + " (" + + " SELECT COUNT(localized) " + + " FROM Person p " + + " LEFT JOIN p.localized localized " + + " WHERE p.id = c.id" + + " )" + + " ) AS localizedCount " + + "FROM Document d " + + "LEFT JOIN d.contacts c " + + "GROUP BY d.id") + .getResultList(); + + assertEquals(1, results.size()); + Object[] tuple = (Object[]) results.get( 0 ); + assertEquals(1, tuple[0]); + } ); + } + + @Test + public void testMax() { + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "SELECT " + + " d.id, " + + " MAX(" + + " (" + + " SELECT COUNT(localized) " + + " FROM Person p " + + " LEFT JOIN p.localized localized " + + " WHERE p.id = c.id" + + " )" + + " ) AS localizedCount " + + "FROM Document d " + + "LEFT JOIN d.contacts c " + + "GROUP BY d.id") + .getResultList(); + + assertEquals(1, results.size()); + Object[] tuple = (Object[]) results.get( 0 ); + assertEquals(1, tuple[0]); + } ); + } + + @Test + public void testAvg() { + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "SELECT " + + " d.id, " + + " AVG(" + + " (" + + " SELECT COUNT(localized) " + + " FROM Person p " + + " LEFT JOIN p.localized localized " + + " WHERE p.id = c.id" + + " )" + + " ) AS localizedCount " + + "FROM Document d " + + "LEFT JOIN d.contacts c " + + "GROUP BY d.id") + .getResultList(); + + assertEquals(1, results.size()); + Object[] tuple = (Object[]) results.get( 0 ); + assertEquals(1, tuple[0]); + } ); + } + + @Entity(name = "Document") + public static class Document { + + private Integer id; + private Map contacts = new HashMap<>(); + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany + @CollectionTable + @MapKeyColumn(name = "position") + public Map getContacts() { + return contacts; + } + + public void setContacts(Map contacts) { + this.contacts = contacts; + } + } + + + @Entity(name = "Person") + public static class Person { + + private Integer id; + + private Map localized = new HashMap<>(); + + @Id + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ElementCollection + public Map getLocalized() { + return localized; + } + + public void setLocalized(Map localized) { + this.localized = localized; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/InsertWithSubSelectTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/InsertWithSubSelectTest.java index d07ad16451..d44f41b63d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/InsertWithSubSelectTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/InsertWithSubSelectTest.java @@ -10,20 +10,16 @@ import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; -import org.hibernate.QueryException; -import org.hibernate.Session; -import org.hibernate.Transaction; - +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; -import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.junit.Assert.fail; /** * @author bjoern.moritz */ +@TestForIssue(jiraKey = "HHH-5274") public class InsertWithSubSelectTest extends BaseCoreFunctionalTestCase { @Override