HHH-18979 add JpaCriteriaQuery.getRoot()

making it cleaner to manipulate criteria constructed from HQL

also clean up some dodgy unchecked casts
This commit is contained in:
Gavin King 2024-12-29 12:28:37 +01:00
parent 5e107b8981
commit 80fb5950ae
16 changed files with 202 additions and 148 deletions

View File

@ -919,7 +919,7 @@ It's even possible to transform a HQL query string to a criteria query, and modi
---- ----
HibernateCriteriaBuilder builder = sessionFactory.getCriteriaBuilder(); HibernateCriteriaBuilder builder = sessionFactory.getCriteriaBuilder();
var query = builder.createQuery("from Book where year(publicationDate) > 2000", Book.class); var query = builder.createQuery("from Book where year(publicationDate) > 2000", Book.class);
var root = (Root<Book>) query.getRootList().get(0); var root = query.getRoot(0, Book.class);
query.where(builder.like(root.get(Book_.title), builder.literal("Hibernate%"))); query.where(builder.like(root.get(Book_.title), builder.literal("Hibernate%")));
query.orderBy(builder.asc(root.get(Book_.title)), builder.desc(root.get(Book_.isbn))); query.orderBy(builder.asc(root.get(Book_.title)), builder.desc(root.get(Book_.isbn)));
List<Book> matchingBooks = session.createSelectionQuery(query).getResultList(); List<Book> matchingBooks = session.createSelectionQuery(query).getResultList();

View File

@ -53,7 +53,7 @@ import java.util.function.Function;
* List&lt;Book&gt; books * List&lt;Book&gt; books
* = new CriteriaDefinition&lt;&gt;(sessionFactory, Book.class, * = new CriteriaDefinition&lt;&gt;(sessionFactory, Book.class,
* "from Book left join fetch authors where type = BOOK") {{ * "from Book left join fetch authors where type = BOOK") {{
* var book = (JpaRoot&lt;Book&gt;) getSelection(); * var book = getRoot(0, Book.class);
* where(getRestriction(), like(book.get(Book_.title), "%Hibernate%")); * where(getRestriction(), like(book.get(Book_.title), "%Hibernate%"));
* orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn))); * orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn)));
* }} * }}
@ -408,10 +408,20 @@ public abstract class CriteriaDefinition<R>
} }
@Override @Override
public List<Root<?>> getRootList() { public List<? extends JpaRoot<?>> getRootList() {
return query.getRootList(); return query.getRootList();
} }
@Override
public <E> JpaRoot<? extends E> getRoot(int position, Class<E> type) {
return query.getRoot( position, type );
}
@Override
public <E> JpaRoot<? extends E> getRoot(String alias, Class<E> type) {
return query.getRoot( alias, type );
}
@Override @Override
public Collection<? extends JpaCteCriteria<?>> getCteCriterias() { public Collection<? extends JpaCteCriteria<?>> getCteCriterias() {
return query.getCteCriterias(); return query.getCteCriterias();

View File

@ -61,13 +61,33 @@ public interface JpaCriteriaQuery<T> extends CriteriaQuery<T>, JpaQueryableCrite
/** /**
* Return the {@linkplain #getRoots() roots} as a list. * Return the {@linkplain #getRoots() roots} as a list.
*/ */
List<Root<?>> getRootList(); List<? extends JpaRoot<?>> getRootList();
@Override /**
@SuppressWarnings("unchecked") * Get a {@linkplain Root query root} element at the given position
default List<Order> getOrderList() { * with the given type.
return (List) getQueryPart().getSortSpecifications(); *
} * @param position the position of this root element
* @param type the type of the root entity
*
* @throws IllegalArgumentException if the root entity at the given
* position is not of the given type, or if there are not
* enough root entities in the query
*/
<E> JpaRoot<? extends E> getRoot(int position, Class<E> type);
/**
* Get a {@linkplain Root query root} element with the given alias
* and the given type.
*
* @param alias the identification variable of the root element
* @param type the type of the root entity
*
* @throws IllegalArgumentException if the root entity with the
* given alias is not of the given type, or if there is
* no root entities with the given alias
*/
<E> JpaRoot<? extends E> getRoot(String alias, Class<E> type);
/** /**
* {@inheritDoc} * {@inheritDoc}

View File

@ -66,9 +66,9 @@ public interface JpaQueryStructure<T> extends JpaQueryPart<T> {
List<? extends JpaExpression<?>> getGroupingExpressions(); List<? extends JpaExpression<?>> getGroupingExpressions();
JpaQueryStructure<T> setGroupingExpressions(List<? extends JpaExpression<?>> grouping); JpaQueryStructure<T> setGroupingExpressions(List<? extends Expression<?>> grouping);
JpaQueryStructure<T> setGroupingExpressions(JpaExpression<?>... grouping); JpaQueryStructure<T> setGroupingExpressions(Expression<?>... grouping);
JpaPredicate getGroupRestriction(); JpaPredicate getGroupRestriction();

View File

@ -908,19 +908,19 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
SqmSelectStatement<Tuple> createTupleQuery(); SqmSelectStatement<Tuple> createTupleQuery();
@Override @Override
<Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, Selection<?>[] selections); <Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, Selection<?>... selections);
@Override @Override
<Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, List<? extends JpaSelection<?>> arguments); <Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, List<? extends JpaSelection<?>> arguments);
@Override @Override
JpaCompoundSelection<Tuple> tuple(Selection<?>[] selections); JpaCompoundSelection<Tuple> tuple(Selection<?>... selections);
@Override @Override
JpaCompoundSelection<Tuple> tuple(List<Selection<?>> selections); JpaCompoundSelection<Tuple> tuple(List<Selection<?>> selections);
@Override @Override
JpaCompoundSelection<Object[]> array(Selection<?>[] selections); JpaCompoundSelection<Object[]> array(Selection<?>... selections);
@Override @Override
JpaCompoundSelection<Object[]> array(List<Selection<?>> selections); JpaCompoundSelection<Object[]> array(List<Selection<?>> selections);

View File

@ -4,7 +4,6 @@
*/ */
package org.hibernate.query.sqm.internal; package org.hibernate.query.sqm.internal;
import jakarta.persistence.criteria.Root;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.graph.spi.AppliedGraph; import org.hibernate.graph.spi.AppliedGraph;
@ -150,13 +149,7 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
@Override @Override
public SelectionQuery<R> addRestriction(Restriction<? super R> restriction) { public SelectionQuery<R> addRestriction(Restriction<? super R> restriction) {
final SqmSelectStatement<R> selectStatement = getSqmSelectStatement().copy( noParamCopyContext() ); final SqmSelectStatement<R> selectStatement = getSqmSelectStatement().copy( noParamCopyContext() );
final Root<?> firstRoot = selectStatement.getRootList().get( 0 ); restriction.apply( selectStatement, selectStatement.<R>getRoot( 0, getExpectedResultType() ) );
if ( !getExpectedResultType().isAssignableFrom( firstRoot.getJavaType() ) ) {
throw new IllegalStateException("First root entity of the query did not have the query result type");
}
@SuppressWarnings("unchecked") // safe, we just checked
final Root<? extends R> root = (Root<? extends R>) firstRoot;
restriction.apply( selectStatement, root );
// TODO: when the QueryInterpretationCache can handle caching criteria queries, // TODO: when the QueryInterpretationCache can handle caching criteria queries,
// simply cache the new SQM as if it were a criteria query, and remove this: // simply cache the new SQM as if it were a criteria query, and remove this:
getQueryOptions().setQueryPlanCachingEnabled( false ); getQueryOptions().setQueryPlanCachingEnabled( false );

View File

@ -111,7 +111,7 @@ public class KeyBasedPagination {
return builder.construct( resultClass, asList( selected, builder.construct(List.class, newItems ) ) ); return builder.construct( resultClass, asList( selected, builder.construct(List.class, newItems ) ) );
} }
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings("rawtypes")
private static <C extends Comparable<? super C>> SqmPredicate keyPredicate( private static <C extends Comparable<? super C>> SqmPredicate keyPredicate(
Expression<? extends C> key, C keyValue, SortDirection direction, Expression<? extends C> key, C keyValue, SortDirection direction,
List<SqmPath<?>> previousKeys, List<Comparable<?>> keyValues, List<SqmPath<?>> previousKeys, List<Comparable<?>> keyValues,

View File

@ -40,7 +40,6 @@ import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.JpaMetamodel;
@ -167,6 +166,7 @@ import jakarta.persistence.metamodel.Bindable;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.hibernate.internal.util.collections.CollectionHelper.determineProperSizing;
import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType; import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType;
import static org.hibernate.query.sqm.TrimSpec.fromCriteriaTrimSpec; import static org.hibernate.query.sqm.TrimSpec.fromCriteriaTrimSpec;
@ -872,7 +872,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
} }
@Override @Override
public JpaCompoundSelection<Tuple> tuple(Selection<?>[] selections) { public JpaCompoundSelection<Tuple> tuple(Selection<?>... selections) {
return tuple( Arrays.asList( selections ) ); return tuple( Arrays.asList( selections ) );
} }
@ -916,62 +916,69 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
} }
@Override @Override
public JpaCompoundSelection<Object[]> array(Selection<?>[] selections) { public JpaCompoundSelection<Object[]> array(Selection<?>... selections) {
return array( Arrays.asList( selections ) ); return array( Object[].class,
Arrays.stream( selections ).map( selection -> (SqmSelectableNode<?>) selection ).toList() );
} }
@Override @Override
public JpaCompoundSelection<Object[]> array(List<Selection<?>> selections) { public JpaCompoundSelection<Object[]> array(List<Selection<?>> selections) {
//noinspection unchecked,rawtypes return arrayInternal( Object[].class,
return array( Object[].class, (List) selections ); selections.stream().map( selection -> (SqmSelectableNode<?>) selection ).toList() );
} }
@Override @Override
public <Y> JpaCompoundSelection<Y> array(Class<Y> resultClass, Selection<?>[] selections) { public <Y> JpaCompoundSelection<Y> array(Class<Y> resultClass, Selection<?>... selections) {
//noinspection unchecked return arrayInternal( resultClass,
return array( resultClass, (List<SqmSelectableNode<?>>) (List<?>) Arrays.asList( selections ) ); Arrays.stream( selections ).map( selection -> (SqmSelectableNode<?>) selection ).toList() );
} }
@Override @Override
public <Y> JpaCompoundSelection<Y> array(Class<Y> resultClass, List<? extends JpaSelection<?>> selections) { public <Y> JpaCompoundSelection<Y> array(Class<Y> resultClass, List<? extends JpaSelection<?>> selections) {
//noinspection rawtypes,unchecked return arrayInternal( resultClass,
checkMultiselect( (List) selections ); selections.stream().map( selection -> (SqmSelectableNode<?>) selection ).toList() );
}
public <Y> JpaCompoundSelection<Y> arrayInternal(Class<Y> resultClass, List<? extends SqmSelectableNode<?>> selections) {
checkMultiselect( selections );
final JavaType<Y> javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( resultClass ); final JavaType<Y> javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( resultClass );
//noinspection unchecked return new SqmJpaCompoundSelection<>( selections, javaType, this );
return new SqmJpaCompoundSelection<>( (List<SqmSelectableNode<?>>) selections, javaType, this );
} }
@Override @Override
public <Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, Selection<?>[] arguments) { public <Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, Selection<?>... arguments) {
//noinspection unchecked return constructInternal( resultClass,
return construct( resultClass, (List<JpaSelection<?>>) (List<?>) Arrays.asList( arguments ) ); Arrays.stream( arguments ).map( arg -> (SqmSelectableNode<?>) arg ).toList() );
} }
@Override @Override
public <Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, List<? extends JpaSelection<?>> arguments) { public <Y> JpaCompoundSelection<Y> construct(Class<Y> resultClass, List<? extends JpaSelection<?>> arguments) {
//noinspection unchecked,rawtypes return constructInternal( resultClass,
checkMultiselect( (List) arguments ); arguments.stream().map( arg -> (SqmSelectableNode<?>) arg ).toList() );
final SqmDynamicInstantiation<Y> instantiation; }
private <Y> JpaCompoundSelection<Y> constructInternal(Class<Y> resultClass, List<? extends SqmSelectableNode<?>> arguments) {
checkMultiselect( arguments );
final SqmDynamicInstantiation<Y> instantiation = createInstantiation( resultClass );
for ( SqmSelectableNode<?> argument : arguments ) {
final SqmDynamicInstantiationArgument<?> arg =
new SqmDynamicInstantiationArgument<>( argument, argument.getAlias(), this );
instantiation.addArgument( arg );
}
return instantiation;
}
@SuppressWarnings("unchecked")
private <Y> SqmDynamicInstantiation<Y> createInstantiation(Class<Y> resultClass) {
if ( List.class.equals( resultClass ) ) { if ( List.class.equals( resultClass ) ) {
//noinspection unchecked return (SqmDynamicInstantiation<Y>) SqmDynamicInstantiation.forListInstantiation( this );
instantiation = (SqmDynamicInstantiation<Y>) SqmDynamicInstantiation.forListInstantiation( this );
} }
else if ( Map.class.equals( resultClass ) ) { else if ( Map.class.equals( resultClass ) ) {
//noinspection unchecked return (SqmDynamicInstantiation<Y>) SqmDynamicInstantiation.forMapInstantiation( this );
instantiation = (SqmDynamicInstantiation<Y>) SqmDynamicInstantiation.forMapInstantiation( this );
} }
else { else {
instantiation = SqmDynamicInstantiation.forClassInstantiation( resultClass, this ); return SqmDynamicInstantiation.forClassInstantiation( resultClass, this );
} }
for ( Selection<?> argument : arguments ) {
final SqmSelectableNode<?> arg = (SqmSelectableNode<?>) argument;
instantiation.addArgument(
new SqmDynamicInstantiationArgument<>( arg, argument.getAlias(), this )
);
}
return instantiation;
} }
/** /**
@ -985,8 +992,8 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
* <i>&quot;An argument to the multiselect method must not be a tuple- * <i>&quot;An argument to the multiselect method must not be a tuple-
* or array-valued compound selection item.&quot;</i> * or array-valued compound selection item.&quot;</i>
*/ */
void checkMultiselect(List<Selection<?>> selections) { void checkMultiselect(List<? extends Selection<?>> selections) {
final HashSet<String> aliases = new HashSet<>( CollectionHelper.determineProperSizing( selections.size() ) ); final HashSet<String> aliases = new HashSet<>( determineProperSizing( selections.size() ) );
for ( Selection<?> it : selections ) { for ( Selection<?> it : selections ) {
final JpaSelection<?> selection = (JpaSelection<?>) it; final JpaSelection<?> selection = (JpaSelection<?>) it;

View File

@ -2437,7 +2437,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
offset = -subResult - i; offset = -subResult - i;
} }
else if ( selectableNode instanceof SqmJpaCompoundSelection<?> compoundSelection ) { else if ( selectableNode instanceof SqmJpaCompoundSelection<?> compoundSelection ) {
final List<SqmSelectableNode<?>> selectionItems = compoundSelection.getSelectionItems(); final List<? extends SqmSelectableNode<?>> selectionItems = compoundSelection.getSelectionItems();
for ( int j = 0; j < selectionItems.size(); j++ ) { for ( int j = 0; j < selectionItems.size(); j++ ) {
if ( selectionItems.get( j ) == node ) { if ( selectionItems.get( j ) == node ) {
return offset + i + j; return offset + i + j;

View File

@ -6,7 +6,6 @@ package org.hibernate.query.sqm.tree.from;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -14,6 +13,9 @@ import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
/** /**
* Contract representing a from clause. * Contract representing a from clause.
* <p> * <p>
@ -50,7 +52,7 @@ public class SqmFromClause implements Serializable {
* mutate the roots * mutate the roots
*/ */
public List<SqmRoot<?>> getRoots() { public List<SqmRoot<?>> getRoots() {
return domainRoots == null ? Collections.emptyList() : Collections.unmodifiableList( domainRoots ); return domainRoots == null ? emptyList() : unmodifiableList( domainRoots );
} }
/** /**

View File

@ -4,7 +4,6 @@
*/ */
package org.hibernate.query.sqm.tree.select; package org.hibernate.query.sqm.tree.select;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -12,6 +11,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import org.hibernate.AssertionFailure;
import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.criteria.JpaCteCriteria; import org.hibernate.query.criteria.JpaCteCriteria;
import org.hibernate.query.criteria.JpaFunctionRoot; import org.hibernate.query.criteria.JpaFunctionRoot;
@ -40,11 +40,12 @@ import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.EntityType; import jakarta.persistence.metamodel.EntityType;
import static java.lang.Character.isAlphabetic; import static java.lang.Character.isAlphabetic;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableSet;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@SuppressWarnings("unchecked")
public abstract class AbstractSqmSelectQuery<T> public abstract class AbstractSqmSelectQuery<T>
extends AbstractSqmNode extends AbstractSqmNode
implements SqmSelectQuery<T> { implements SqmSelectQuery<T> {
@ -110,7 +111,7 @@ public abstract class AbstractSqmSelectQuery<T>
return cteStatements.values(); return cteStatements.values();
} }
@Override @Override @SuppressWarnings("unchecked")
public <X> JpaCteCriteria<X> getCteCriteria(String cteName) { public <X> JpaCteCriteria<X> getCteCriteria(String cteName) {
return (JpaCteCriteria<X>) cteStatements.get( cteName ); return (JpaCteCriteria<X>) cteStatements.get( cteName );
} }
@ -222,14 +223,54 @@ public abstract class AbstractSqmSelectQuery<T>
} }
@Override @Override
@SuppressWarnings("rawtypes")
public Set<Root<?>> getRoots() { public Set<Root<?>> getRoots() {
return (Set) getQuerySpec().getRoots(); return unmodifiableSet( getQuerySpec().getRoots() );
} }
@SuppressWarnings("rawtypes") /**
public List<Root<?>> getRootList() { * @see org.hibernate.query.criteria.JpaCriteriaQuery#getRootList()
return (List) getQuerySpec().getRootList(); */
public List<? extends JpaRoot<?>> getRootList() {
return getQuerySpec().getRootList();
}
/**
* @see org.hibernate.query.criteria.JpaCriteriaQuery#getRoot(int, Class)
*/
public <E> JpaRoot<? extends E> getRoot(int position, Class<E> type) {
final List<SqmRoot<?>> rootList = getQuerySpec().getRootList();
if ( rootList.size() <= position ) {
throw new IllegalArgumentException( "Not enough root entities" );
}
return castRoot( rootList.get( position ), type );
}
/**
* @see org.hibernate.query.criteria.JpaCriteriaQuery#getRoot(String, Class)
*/
public <E> JpaRoot<? extends E> getRoot(String alias, Class<E> type) {
final List<SqmRoot<?>> rootList = getQuerySpec().getRootList();
for ( SqmRoot<?> root : rootList ) {
final String rootAlias = root.getAlias();
if ( rootAlias != null && rootAlias.equals( alias ) ) {
return castRoot( root, type );
}
}
throw new IllegalArgumentException( "No root entity with alias " + alias );
}
private static <E> JpaRoot<? extends E> castRoot(JpaRoot<?> root, Class<E> type) {
final Class<?> rootEntityType = root.getJavaType();
if ( rootEntityType == null ) {
throw new AssertionFailure( "Java type of root entity was null" );
}
if ( !type.isAssignableFrom( rootEntityType ) ) {
throw new IllegalArgumentException( "Root entity of type '" + rootEntityType.getTypeName()
+ "' did not have the given type '" + type.getTypeName() + "'");
}
@SuppressWarnings("unchecked") // safe, we just checked
final JpaRoot<? extends E> result = (JpaRoot<? extends E>) root;
return result;
} }
@Override @Override
@ -329,20 +370,19 @@ public abstract class AbstractSqmSelectQuery<T>
// Grouping // Grouping
@Override @Override
@SuppressWarnings("rawtypes")
public List<Expression<?>> getGroupList() { public List<Expression<?>> getGroupList() {
return (List) getQuerySpec().getGroupingExpressions(); return unmodifiableList( getQuerySpec().getGroupingExpressions() );
} }
@Override @Override
public SqmSelectQuery<T> groupBy(Expression<?>... expressions) { public SqmSelectQuery<T> groupBy(Expression<?>... expressions) {
return groupBy( Arrays.asList( expressions ) ); getQuerySpec().setGroupingExpressions( List.of( expressions ) );
return this;
} }
@Override @Override
@SuppressWarnings("rawtypes")
public SqmSelectQuery<T> groupBy(List<Expression<?>> grouping) { public SqmSelectQuery<T> groupBy(List<Expression<?>> grouping) {
getQuerySpec().setGroupingExpressions( (List) grouping ); getQuerySpec().setGroupingExpressions( grouping );
return this; return this;
} }
@ -363,35 +403,6 @@ public abstract class AbstractSqmSelectQuery<T>
return this; return this;
} }
//
// // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// // Limit
//
//
// @Override
// @SuppressWarnings("unchecked")
// public <X> ExpressionImplementor<X> getLimit() {
// return limit;
// }
//
// @Override
// public C setLimit(JpaExpression<?> limit) {
// this.limit = (ExpressionImplementor) limit;
// return this;
// }
//
// @Override
// @SuppressWarnings("unchecked")
// public <X> ExpressionImplementor<X> getOffset() {
// return offset;
// }
//
// @Override
// public C setOffset(JpaExpression offset) {
// this.offset = (ExpressionImplementor) offset;
// return this;
// }
public void appendHqlString(StringBuilder sb) { public void appendHqlString(StringBuilder sb) {
if ( !cteStatements.isEmpty() ) { if ( !cteStatements.isEmpty() ) {
sb.append( "with " ); sb.append( "with " );
@ -404,35 +415,28 @@ public abstract class AbstractSqmSelectQuery<T>
sqmQueryPart.appendHqlString( sb ); sqmQueryPart.appendHqlString( sb );
} }
@SuppressWarnings("unchecked")
protected Selection<? extends T> getResultSelection(Selection<?>[] selections) { protected Selection<? extends T> getResultSelection(Selection<?>[] selections) {
final Selection<? extends T> resultSelection; final Class<T> resultType = getResultType();
Class<T> resultType = getResultType();
if ( resultType == null || resultType == Object.class ) { if ( resultType == null || resultType == Object.class ) {
switch ( selections.length ) { switch ( selections.length ) {
case 0: { case 0:
throw new IllegalArgumentException( throw new IllegalArgumentException( "Empty selections passed to criteria query typed as Object" );
"empty selections passed to criteria query typed as Object" case 1:
); return (Selection<? extends T>) selections[0];
} default:
case 1: { return (Selection<? extends T>) nodeBuilder().array( selections );
resultSelection = ( Selection<? extends T> ) selections[0];
break;
}
default: {
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
}
} }
} }
else if ( Tuple.class.isAssignableFrom( resultType ) ) { else if ( Tuple.class.isAssignableFrom( resultType ) ) {
resultSelection = ( Selection<? extends T> ) nodeBuilder().tuple( selections ); return (Selection<? extends T>) nodeBuilder().tuple( selections );
} }
else if ( resultType.isArray() ) { else if ( resultType.isArray() ) {
resultSelection = nodeBuilder().array( resultType, selections ); return nodeBuilder().array( resultType, selections );
} }
else { else {
resultSelection = nodeBuilder().construct( resultType, selections ); return nodeBuilder().construct( resultType, selections );
} }
return resultSelection;
} }
} }

View File

@ -56,11 +56,11 @@ public class SqmJpaCompoundSelection<T>
// can support using tuples in other clauses. If we keep the Easy way is to add a switch in creation of these // can support using tuples in other clauses. If we keep the Easy way is to add a switch in creation of these
// whether `SqmJpaCompoundSelection` or `SqmTuple` is used based on `JpaCompliance#isJpaQueryComplianceEnabled` // whether `SqmJpaCompoundSelection` or `SqmTuple` is used based on `JpaCompliance#isJpaQueryComplianceEnabled`
private final List<SqmSelectableNode<?>> selectableNodes; private final List<? extends SqmSelectableNode<?>> selectableNodes;
private final JavaType<T> javaType; private final JavaType<T> javaType;
public SqmJpaCompoundSelection( public SqmJpaCompoundSelection(
List<SqmSelectableNode<?>> selectableNodes, List<? extends SqmSelectableNode<?>> selectableNodes,
JavaType<T> javaType, JavaType<T> javaType,
NodeBuilder criteriaBuilder) { NodeBuilder criteriaBuilder) {
super( null, criteriaBuilder ); super( null, criteriaBuilder );
@ -111,7 +111,7 @@ public class SqmJpaCompoundSelection<T>
} }
@Override @Override
public List<SqmSelectableNode<?>> getSelectionItems() { public List<? extends SqmSelectableNode<?>> getSelectionItems() {
return selectableNodes; return selectableNodes;
} }

View File

@ -395,10 +395,10 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
} }
@Override @Override
public SqmQuerySpec<T> setGroupingExpressions(List<? extends JpaExpression<?>> groupExpressions) { public SqmQuerySpec<T> setGroupingExpressions(List<? extends Expression<?>> groupExpressions) {
this.hasPositionalGroupItem = false; this.hasPositionalGroupItem = false;
this.groupByClauseExpressions = new ArrayList<>( groupExpressions.size() ); this.groupByClauseExpressions = new ArrayList<>( groupExpressions.size() );
for ( JpaExpression<?> groupExpression : groupExpressions ) { for ( Expression<?> groupExpression : groupExpressions ) {
if ( groupExpression instanceof SqmAliasedNodeRef ) { if ( groupExpression instanceof SqmAliasedNodeRef ) {
this.hasPositionalGroupItem = true; this.hasPositionalGroupItem = true;
} }
@ -408,10 +408,10 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
} }
@Override @Override
public SqmQuerySpec<T> setGroupingExpressions(JpaExpression<?>... groupExpressions) { public SqmQuerySpec<T> setGroupingExpressions(Expression<?>... groupExpressions) {
this.hasPositionalGroupItem = false; this.hasPositionalGroupItem = false;
this.groupByClauseExpressions = new ArrayList<>( groupExpressions.length ); this.groupByClauseExpressions = new ArrayList<>( groupExpressions.length );
for ( JpaExpression<?> groupExpression : groupExpressions ) { for ( Expression<?> groupExpression : groupExpressions ) {
if ( groupExpression instanceof SqmAliasedNodeRef ) { if ( groupExpression instanceof SqmAliasedNodeRef ) {
this.hasPositionalGroupItem = true; this.hasPositionalGroupItem = true;
} }

View File

@ -41,6 +41,7 @@ import jakarta.persistence.criteria.Selection;
import jakarta.persistence.metamodel.EntityType; import jakarta.persistence.metamodel.EntityType;
import static java.util.Collections.emptySet; import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableSet; import static java.util.Collections.unmodifiableSet;
import static org.hibernate.query.sqm.spi.SqmCreationHelper.combinePredicates; import static org.hibernate.query.sqm.spi.SqmCreationHelper.combinePredicates;
import static org.hibernate.query.sqm.SqmQuerySource.CRITERIA; import static org.hibernate.query.sqm.SqmQuerySource.CRITERIA;
@ -166,6 +167,11 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T>
return nodeBuilder(); return nodeBuilder();
} }
@Override
public List<Order> getOrderList() {
return unmodifiableList( getQueryPart().getSortSpecifications() );
}
@Override @Override
public SqmQuerySource getQuerySource() { public SqmQuerySource getQuerySource() {
return querySource; return querySource;
@ -368,33 +374,26 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T>
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Selection<? extends T> getResultSelection(List<?> selectionList) { private Selection<? extends T> getResultSelection(List<?> selections) {
final Class<T> resultType = getResultType(); final Class<T> resultType = getResultType();
//noinspection rawtypes
final List<? extends JpaSelection<?>> selections = (List) selectionList;
if ( resultType == null || resultType == Object.class ) { if ( resultType == null || resultType == Object.class ) {
switch ( selectionList.size() ) { switch ( selections.size() ) {
case 0: { case 0:
throw new IllegalArgumentException( throw new IllegalArgumentException( "Empty selections passed to criteria query typed as Object" );
"empty selections passed to criteria query typed as Object" case 1:
); return (Selection<? extends T>) selections.get( 0 );
} default:
case 1: { return (Selection<? extends T>) nodeBuilder().array( (List<Selection<?>>) selections );
return (Selection<? extends T>) selectionList.get( 0 );
}
default: {
return (Selection<? extends T>) nodeBuilder().array( (List) selectionList );
}
} }
} }
else if ( Tuple.class.isAssignableFrom( resultType ) ) { else if ( Tuple.class.isAssignableFrom( resultType ) ) {
return (Selection<? extends T>) nodeBuilder().tuple( (List) selectionList ); return (Selection<? extends T>) nodeBuilder().tuple( (List<Selection<?>>) selections );
} }
else if ( resultType.isArray() ) { else if ( resultType.isArray() ) {
return nodeBuilder().array( resultType, selections ); return nodeBuilder().array( resultType, (List<? extends JpaSelection<?>>) selections );
} }
else { else {
return nodeBuilder().construct( resultType, selections ); return nodeBuilder().construct( resultType, (List<? extends JpaSelection<?>>) selections );
} }
} }

View File

@ -51,6 +51,12 @@ public class CriteriaDefinitionTest {
orderBy(asc(message.get("text"))); orderBy(asc(message.get("text")));
}}; }};
var query3prime = new CriteriaDefinition<>(factory, Message.class, "from Msg") {{
var message = getRoot( 0, Message.class );
where(ilike(message.get("text"), "%e%"));
orderBy(asc(message.get("text")));
}};
var query4 = new CriteriaDefinition<>(factory, Message.class) {{ var query4 = new CriteriaDefinition<>(factory, Message.class) {{
var message = from(Message.class); var message = from(Message.class);
restrict(like(message.get("text"), "hell%")); restrict(like(message.get("text"), "hell%"));
@ -77,6 +83,9 @@ public class CriteriaDefinitionTest {
var messages = session.createSelectionQuery(query3).getResultList(); var messages = session.createSelectionQuery(query3).getResultList();
assertEquals(2,messages.size()); assertEquals(2,messages.size());
var messagesPrime = session.createSelectionQuery(query3prime).getResultList();
assertEquals(2,messagesPrime.size());
var msg = session.createSelectionQuery(query4).getSingleResult(); var msg = session.createSelectionQuery(query4).getSingleResult();
assertNotNull(msg); assertNotNull(msg);
assertEquals(1L,msg.id); assertEquals(1L,msg.id);

View File

@ -8,7 +8,6 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Version; import jakarta.persistence.Version;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.SingularAttribute;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
@ -251,11 +250,22 @@ public class RestrictionTest {
} ); } );
scope.inSession( session -> { scope.inSession( session -> {
var query = session.getCriteriaBuilder().createQuery("select title from Book", String.class); var query = session.getCriteriaBuilder().createQuery("select title from Book", String.class);
var root = (Root<Book>) query.getRootList().get(0); var root = query.getRoot(0, Book.class);
equal( isbn, "9781932394153" ).apply( query, root ); equal( isbn, "9781932394153" ).apply( query, root );
List<String> titles = session.createQuery( query ).getResultList(); List<String> titles = session.createQuery( query ).getResultList();
assertEquals( 1, titles.size() ); assertEquals( 1, titles.size() );
} ); } );
var builder = scope.getSessionFactory().getCriteriaBuilder();
var query = builder.createQuery("from Book where pages > 200", Book.class);
var root = query.getRoot(0, Book.class);
like( title, "Hibernate%" ).apply( query, root );
query.orderBy(builder.asc(root.get(title)), builder.desc(root.get(isbn)));
scope.inSession( session -> {
List<Book> matchingBooks = session.createSelectionQuery(query).getResultList();
assertEquals( 1, matchingBooks.size() );
});
} }