From a356a08d4b29f882433ed5ec36370c41c047cb81 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 8 Nov 2016 13:23:37 -0600 Subject: [PATCH] HHH-11225 - Add CriteriaBuilder overloads for methods dealing with Collection to also deal with Map --- .../criteria/HibernateCriteriaBuilder.java | 53 +++++ .../internal/CriteriaBuilderImpl.java | 56 ++++- ...a => SizeOfPluralAttributeExpression.java} | 25 ++- .../paths/PluralAttributeExpressionsTest.java | 197 ++++++++++++++++++ .../hibernate/jpa/test/metamodel/Article.java | 24 +++ .../jpa/test/metamodel/Translation.java | 21 ++ 6 files changed, 357 insertions(+), 19 deletions(-) rename hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/{SizeOfCollectionExpression.java => SizeOfPluralAttributeExpression.java} (70%) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/PluralAttributeExpressionsTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Article.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Translation.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java index 80792f488f..ac787039b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java @@ -6,7 +6,10 @@ */ package org.hibernate.query.criteria; +import java.util.Map; import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Predicate; /** * Hibernate extensions to the JPA CriteriaBuilder. Currently there are no extensions; these are coming in 6.0 @@ -14,4 +17,54 @@ * @author Steve Ebersole */ public interface HibernateCriteriaBuilder extends CriteriaBuilder { + /** + * Create a predicate that tests whether a Map is empty. + *

+ * NOTE : Due to type-erasure we cannot name this the same as + * {@link CriteriaBuilder#isEmpty} + * + * + * @param mapExpression The expression resolving to a Map which we + * want to check for emptiness + * + * @return is-empty predicate + */ + > Predicate isMapEmpty(Expression mapExpression); + + /** + * Create a predicate that tests whether a Map is + * not empty. + *

+ * NOTE : Due to type-erasure we cannot name this the same as + * {@link CriteriaBuilder#isNotEmpty} + * + * @param mapExpression The expression resolving to a Map which we + * want to check for non-emptiness + * + * @return is-not-empty predicate + */ + > Predicate isMapNotEmpty(Expression mapExpression); + + /** + * Create an expression that tests the size of a map. + *

+ * NOTE : Due to type-erasure we cannot name this the same as + * {@link CriteriaBuilder#size} + * + * @param mapExpression The expression resolving to a Map for which we + * want to know the size + * + * @return size expression + */ + > Expression mapSize(Expression mapExpression); + + /** + * Create an expression that tests the size of a map. + * + * @param map The Map for which we want to know the size + * + * @return size expression + */ + > Expression mapSize(M map); + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaBuilderImpl.java index c18b10f7b2..79e049a6ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaBuilderImpl.java @@ -50,7 +50,7 @@ import org.hibernate.query.criteria.internal.expression.ParameterExpressionImpl; import org.hibernate.query.criteria.internal.expression.SearchedCaseExpression; import org.hibernate.query.criteria.internal.expression.SimpleCaseExpression; -import org.hibernate.query.criteria.internal.expression.SizeOfCollectionExpression; +import org.hibernate.query.criteria.internal.expression.SizeOfPluralAttributeExpression; import org.hibernate.query.criteria.internal.expression.SubqueryComparisonModifierExpression; import org.hibernate.query.criteria.internal.expression.UnaryArithmeticOperation; import org.hibernate.query.criteria.internal.expression.function.AbsFunction; @@ -1275,7 +1275,7 @@ public Case selectCase(Class type) { @Override public > Expression size(C c) { int size = c == null ? 0 : c.size(); - return new LiteralExpression(this, Integer.class, size); + return new LiteralExpression<>(this, Integer.class, size); } @Override @@ -1284,7 +1284,7 @@ public > Expression size(Expression exp) { return size( ( (LiteralExpression) exp ).getLiteral() ); } else if ( PluralAttributePath.class.isInstance(exp) ) { - return new SizeOfCollectionExpression(this, (PluralAttributePath) exp ); + return new SizeOfPluralAttributeExpression( this, (PluralAttributePath) exp ); } // TODO : what other specific types? any? throw new IllegalArgumentException("unknown collection expression type [" + exp.getClass().getName() + "]" ); @@ -1292,12 +1292,12 @@ else if ( PluralAttributePath.class.isInstance(exp) ) { @Override public > Expression> values(M map) { - return new LiteralExpression>( this, map.values() ); + return new LiteralExpression<>( this, map.values() ); } @Override public > Expression> keys(M map) { - return new LiteralExpression>( this, map.keySet() ); + return new LiteralExpression<>( this, map.keySet() ); } @Override @@ -1324,10 +1324,10 @@ public > Predicate isMember(E e, Expression collec "unknown collection expression type [" + collectionExpression.getClass().getName() + "]" ); } - return new MemberOfPredicate( + return new MemberOfPredicate<>( this, e, - (PluralAttributePath)collectionExpression + (PluralAttributePath) collectionExpression ); } @@ -1343,10 +1343,10 @@ public > Predicate isMember(Expression elementExpr "unknown collection expression type [" + collectionExpression.getClass().getName() + "]" ); } - return new MemberOfPredicate( + return new MemberOfPredicate<>( this, elementExpression, - (PluralAttributePath)collectionExpression + (PluralAttributePath) collectionExpression ); } @@ -1355,13 +1355,49 @@ public > Predicate isNotMember(Expression eExpress return isMember(eExpression, cExpression).not(); } + @Override + @SuppressWarnings({ "unchecked" }) + public > Predicate isMapEmpty(Expression mapExpression) { + if ( PluralAttributePath.class.isInstance( mapExpression ) ) { + return new IsEmptyPredicate( this, (PluralAttributePath) mapExpression ); + } + // TODO : what other specific types? any? + throw new IllegalArgumentException( + "unknown collection expression type [" + mapExpression.getClass().getName() + "]" + ); + } + + @Override + public > Predicate isMapNotEmpty(Expression mapExpression) { + return isMapEmpty( mapExpression ).not(); + } + + @Override + public > Expression mapSize(Expression mapExpression) { + if ( LiteralExpression.class.isInstance( mapExpression ) ) { + return mapSize( ( (LiteralExpression) mapExpression ).getLiteral() ); + } + else if ( PluralAttributePath.class.isInstance( mapExpression ) ) { + return new SizeOfPluralAttributeExpression( this, (PluralAttributePath) mapExpression ); + } + // TODO : what other specific types? any? + throw new IllegalArgumentException("unknown collection expression type [" + mapExpression.getClass().getName() + "]" ); + } + + @Override + public > Expression mapSize(M map) { + int size = map == null ? 0 : map.size(); + return new LiteralExpression<>( this, Integer.class, size ); + } + + @SuppressWarnings("unchecked") private K treat( Join join, Class type, BiFunction, Class, K> f) { final Set> joins = join.getParent().getJoins(); final K treatAs = f.apply( join, type ); - joins.add(treatAs); + joins.add( treatAs ); return treatAs; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfCollectionExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfPluralAttributeExpression.java similarity index 70% rename from hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfCollectionExpression.java rename to hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfPluralAttributeExpression.java index 7c4728f170..7c03411b5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfCollectionExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SizeOfPluralAttributeExpression.java @@ -7,7 +7,6 @@ package org.hibernate.query.criteria.internal.expression; import java.io.Serializable; -import java.util.Collection; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.ParameterRegistry; @@ -20,20 +19,28 @@ * * @author Steve Ebersole */ -public class SizeOfCollectionExpression +public class SizeOfPluralAttributeExpression extends ExpressionImpl implements Serializable { - private final PluralAttributePath collectionPath; + private final PluralAttributePath path; - public SizeOfCollectionExpression( + public SizeOfPluralAttributeExpression( CriteriaBuilderImpl criteriaBuilder, - PluralAttributePath collectionPath) { + PluralAttributePath path) { super( criteriaBuilder, Integer.class); - this.collectionPath = collectionPath; + this.path = path; } - public PluralAttributePath getCollectionPath() { - return collectionPath; + /** + * @deprecated Use {@link #getPluralAttributePath()} instead + */ + @Deprecated + public PluralAttributePath getCollectionPath() { + return path; + } + + public PluralAttributePath getPluralAttributePath() { + return path; } public void registerParameters(ParameterRegistry registry) { @@ -41,7 +48,7 @@ public void registerParameters(ParameterRegistry registry) { } public String render(RenderingContext renderingContext) { - return "size(" + getCollectionPath().render( renderingContext ) + ")"; + return "size(" + getPluralAttributePath().render( renderingContext ) + ")"; } public String renderProjection(RenderingContext renderingContext) { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/PluralAttributeExpressionsTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/PluralAttributeExpressionsTest.java new file mode 100644 index 0000000000..3b769ac0ce --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/paths/PluralAttributeExpressionsTest.java @@ -0,0 +1,197 @@ +/* + * 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.paths; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.hibernate.jpa.test.metamodel.AbstractMetamodelSpecificTest; +import org.hibernate.jpa.test.metamodel.Address; +import org.hibernate.jpa.test.metamodel.Address_; +import org.hibernate.jpa.test.metamodel.Article; +import org.hibernate.jpa.test.metamodel.Article_; +import org.hibernate.jpa.test.metamodel.MapEntity; +import org.hibernate.jpa.test.metamodel.MapEntityLocal; +import org.hibernate.jpa.test.metamodel.MapEntity_; +import org.hibernate.jpa.test.metamodel.Translation; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Steve Ebersole + */ +public class PluralAttributeExpressionsTest extends AbstractMetamodelSpecificTest { + @Override + public Class[] getAnnotatedClasses() { + List classes = new ArrayList<>(); + Collections.addAll( classes, super.getAnnotatedClasses() ); + classes.add( MapEntity.class ); + classes.add( MapEntityLocal.class ); + classes.add( Article.class ); + classes.add( Translation.class ); + + return classes.toArray( new Class[ classes.size() ] ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // IS [NOT] EMPTY + + @Test + public void testCollectionIsEmptyHql() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "select a from Address a where a.phones is empty" ).getResultList(); + }); + } + + @Test + public void testCollectionIsEmptyCriteria() { + doInJPA( this::entityManagerFactory, entityManager -> { + final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + final CriteriaQuery

criteria = cb.createQuery( Address.class ); + final Root
root = criteria.from( Address.class); + + criteria.select( root ) + .where( cb.isEmpty( root.get( Address_.phones ) ) ); + + entityManager.createQuery( criteria ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + @FailureExpected( jiraKey = "HHH-6686") + public void testElementMapIsEmptyHql() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "select m from MapEntity m where m.localized is empty" ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + @FailureExpected( jiraKey = "HHH-6686") + public void testElementMapIsEmptyCriteria() { + doInJPA( this::entityManagerFactory, entityManager -> { + final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + final CriteriaQuery criteria = cb.createQuery( MapEntity.class ); + final Root root = criteria.from( MapEntity.class); + + criteria.select( root ) + .where( cb.isMapEmpty( root.get( MapEntity_.localized ) ) ); + + entityManager.createQuery( criteria ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + public void testEntityMapIsEmptyHql() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "select a from Article a where a.translations is empty" ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + public void testEntityMapIsEmptyCriteria() { + doInJPA( this::entityManagerFactory, entityManager -> { + final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + final CriteriaQuery
criteria = cb.createQuery( Article.class ); + final Root
root = criteria.from( Article.class); + + criteria.select( root ) + .where( cb.isEmpty( root.get( Article_.translations ) ) ); + + entityManager.createQuery( criteria ).getResultList(); + }); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SIZE + + @Test + public void testCollectionSizeHql() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "select a from Address a where size(a.phones) > 1" ).getResultList(); + }); + } + + @Test + public void testCollectionSizeCriteria() { + doInJPA( this::entityManagerFactory, entityManager -> { + final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + final CriteriaQuery
criteria = cb.createQuery( Address.class ); + final Root
root = criteria.from( Address.class); + + criteria.select( root ) + .where( cb.gt( cb.size( root.get( Address_.phones ) ), 1 ) ); + + entityManager.createQuery( criteria ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + public void testElementMapSizeHql() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "select m from MapEntity m where size( m.localized ) > 1" ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + public void testElementMapSizeCriteria() { + doInJPA( this::entityManagerFactory, entityManager -> { + final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + final CriteriaQuery criteria = cb.createQuery( MapEntity.class ); + final Root root = criteria.from( MapEntity.class); + + criteria.select( root ) + .where( cb.gt( cb.mapSize( root.get( MapEntity_.localized ) ), 1 ) ); + + entityManager.createQuery( criteria ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + public void testEntityMapSizeHql() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "select a from Article a where size(a.translations) > 1" ).getResultList(); + }); + } + + @Test + @TestForIssue( jiraKey = "HHH-11225" ) + public void testEntityMapSizeCriteria() { + doInJPA( this::entityManagerFactory, entityManager -> { + final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + final CriteriaQuery
criteria = cb.createQuery( Article.class ); + final Root
root = criteria.from( Article.class); + + criteria.select( root ) + .where( cb.gt( cb.mapSize( root.get( Article_.translations ) ), 1 ) ); + + entityManager.createQuery( criteria ).getResultList(); + }); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Article.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Article.java new file mode 100644 index 0000000000..163531eabe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Article.java @@ -0,0 +1,24 @@ +/* + * 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.metamodel; + +import java.util.Locale; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Steve Ebersole + */ +@Entity(name = "Article") +public class Article { + @Id + Integer id; + @OneToMany + Map translations; +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Translation.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Translation.java new file mode 100644 index 0000000000..dc54bf1613 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/metamodel/Translation.java @@ -0,0 +1,21 @@ +/* + * 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.metamodel; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Translation { + @Id + Integer id; + String title; + String text; +}