HHH-11225 - Add CriteriaBuilder overloads for methods dealing with Collection to also deal with Map
This commit is contained in:
parent
66e1ee3851
commit
a356a08d4b
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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,10 +1324,10 @@ 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
|
||||
(PluralAttributePath<C>) collectionExpression
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1343,10 +1343,10 @@ 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
|
||||
(PluralAttributePath<C>) collectionExpression
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1355,13 +1355,49 @@ 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,
|
||||
BiFunction<Join<X, T>, Class<V>, K> f) {
|
||||
final Set<Join<X, ?>> joins = join.getParent().getJoins();
|
||||
final K treatAs = f.apply( join, type );
|
||||
joins.add(treatAs);
|
||||
joins.add( treatAs );
|
||||
return treatAs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue