HHH-11225 - Add CriteriaBuilder overloads for methods dealing with Collection to also deal with Map

This commit is contained in:
Steve Ebersole 2016-11-08 13:23:37 -06:00
parent 66e1ee3851
commit a356a08d4b
6 changed files with 357 additions and 19 deletions

View File

@ -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 @@ import javax.persistence.criteria.CriteriaBuilder;
* @author Steve Ebersole
*/
public interface HibernateCriteriaBuilder extends CriteriaBuilder {
/**
* Create a predicate that tests whether a Map is empty.
* <p/>
* 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
*/
<M extends Map<?,?>> Predicate isMapEmpty(Expression<M> mapExpression);
/**
* Create a predicate that tests whether a Map is
* not empty.
* <p/>
* 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
*/
<M extends Map<?,?>> Predicate isMapNotEmpty(Expression<M> mapExpression);
/**
* Create an expression that tests the size of a map.
* <p/>
* 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
*/
<M extends Map<?,?>> Expression<Integer> mapSize(Expression<M> 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
*/
<M extends Map<?,?>> Expression<Integer> mapSize(M map);
}

View File

@ -50,7 +50,7 @@ import org.hibernate.query.criteria.internal.expression.NullifExpression;
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 class CriteriaBuilderImpl implements HibernateCriteriaBuilder, Serializab
@Override
public <C extends Collection<?>> Expression<Integer> size(C c) {
int size = c == null ? 0 : c.size();
return new LiteralExpression<Integer>(this, Integer.class, size);
return new LiteralExpression<>(this, Integer.class, size);
}
@Override
@ -1284,7 +1284,7 @@ public class CriteriaBuilderImpl implements HibernateCriteriaBuilder, Serializab
return size( ( (LiteralExpression<C>) exp ).getLiteral() );
}
else if ( PluralAttributePath.class.isInstance(exp) ) {
return new SizeOfCollectionExpression<C>(this, (PluralAttributePath<C>) exp );
return new SizeOfPluralAttributeExpression( this, (PluralAttributePath<C>) exp );
}
// TODO : what other specific types? any?
throw new IllegalArgumentException("unknown collection expression type [" + exp.getClass().getName() + "]" );
@ -1292,12 +1292,12 @@ public class CriteriaBuilderImpl implements HibernateCriteriaBuilder, Serializab
@Override
public <V, M extends Map<?, V>> Expression<Collection<V>> values(M map) {
return new LiteralExpression<Collection<V>>( this, map.values() );
return new LiteralExpression<>( this, map.values() );
}
@Override
public <K, M extends Map<K, ?>> Expression<Set<K>> keys(M map) {
return new LiteralExpression<Set<K>>( this, map.keySet() );
return new LiteralExpression<>( this, map.keySet() );
}
@Override
@ -1324,7 +1324,7 @@ public class CriteriaBuilderImpl implements HibernateCriteriaBuilder, Serializab
"unknown collection expression type [" + collectionExpression.getClass().getName() + "]"
);
}
return new MemberOfPredicate<E, C>(
return new MemberOfPredicate<>(
this,
e,
(PluralAttributePath<C>) collectionExpression
@ -1343,7 +1343,7 @@ public class CriteriaBuilderImpl implements HibernateCriteriaBuilder, Serializab
"unknown collection expression type [" + collectionExpression.getClass().getName() + "]"
);
}
return new MemberOfPredicate<E, C>(
return new MemberOfPredicate<>(
this,
elementExpression,
(PluralAttributePath<C>) collectionExpression
@ -1355,6 +1355,42 @@ public class CriteriaBuilderImpl implements HibernateCriteriaBuilder, Serializab
return isMember(eExpression, cExpression).not();
}
@Override
@SuppressWarnings({ "unchecked" })
public <M extends Map<?, ?>> Predicate isMapEmpty(Expression<M> mapExpression) {
if ( PluralAttributePath.class.isInstance( mapExpression ) ) {
return new IsEmptyPredicate( this, (PluralAttributePath<M>) mapExpression );
}
// TODO : what other specific types? any?
throw new IllegalArgumentException(
"unknown collection expression type [" + mapExpression.getClass().getName() + "]"
);
}
@Override
public <M extends Map<?, ?>> Predicate isMapNotEmpty(Expression<M> mapExpression) {
return isMapEmpty( mapExpression ).not();
}
@Override
public <M extends Map<?, ?>> Expression<Integer> mapSize(Expression<M> mapExpression) {
if ( LiteralExpression.class.isInstance( mapExpression ) ) {
return mapSize( ( (LiteralExpression<M>) 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 <M extends Map<?, ?>> Expression<Integer> mapSize(M map) {
int size = map == null ? 0 : map.size();
return new LiteralExpression<>( this, Integer.class, size );
}
@SuppressWarnings("unchecked")
private <X, T, V extends T, K extends JoinImplementor> K treat(
Join<X, T> join,
Class<V> type,

View File

@ -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 @@ import org.hibernate.query.criteria.internal.path.PluralAttributePath;
*
* @author Steve Ebersole
*/
public class SizeOfCollectionExpression<C extends Collection>
public class SizeOfPluralAttributeExpression
extends ExpressionImpl<Integer>
implements Serializable {
private final PluralAttributePath<C> collectionPath;
private final PluralAttributePath path;
public SizeOfCollectionExpression(
public SizeOfPluralAttributeExpression(
CriteriaBuilderImpl criteriaBuilder,
PluralAttributePath<C> collectionPath) {
PluralAttributePath path) {
super( criteriaBuilder, Integer.class);
this.collectionPath = collectionPath;
this.path = path;
}
public PluralAttributePath<C> 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 class SizeOfCollectionExpression<C extends Collection>
}
public String render(RenderingContext renderingContext) {
return "size(" + getCollectionPath().render( renderingContext ) + ")";
return "size(" + getPluralAttributePath().render( renderingContext ) + ")";
}
public String renderProjection(RenderingContext renderingContext) {

View File

@ -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<Class> 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<Address> criteria = cb.createQuery( Address.class );
final Root<Address> 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<MapEntity> criteria = cb.createQuery( MapEntity.class );
final Root<MapEntity> 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<Article> criteria = cb.createQuery( Article.class );
final Root<Article> 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<Address> criteria = cb.createQuery( Address.class );
final Root<Address> 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<MapEntity> criteria = cb.createQuery( MapEntity.class );
final Root<MapEntity> 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<Article> criteria = cb.createQuery( Article.class );
final Root<Article> root = criteria.from( Article.class);
criteria.select( root )
.where( cb.gt( cb.mapSize( root.get( Article_.translations ) ), 1 ) );
entityManager.createQuery( criteria ).getResultList();
});
}
}

View File

@ -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<Locale, Translation> translations;
}

View File

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