diff --git a/documentation/src/main/asciidoc/introduction/Interacting.adoc b/documentation/src/main/asciidoc/introduction/Interacting.adoc index 943d743a1344..f3286caa5d4c 100644 --- a/documentation/src/main/asciidoc/introduction/Interacting.adoc +++ b/documentation/src/main/asciidoc/introduction/Interacting.adoc @@ -919,7 +919,7 @@ It's even possible to transform a HQL query string to a criteria query, and modi ---- HibernateCriteriaBuilder builder = sessionFactory.getCriteriaBuilder(); var query = builder.createQuery("from Book where year(publicationDate) > 2000", Book.class); -var root = (Root) query.getRootList().get(0); +var root = query.getRoot(0, Book.class); 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))); List matchingBooks = session.createSelectionQuery(query).getResultList(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/CriteriaDefinition.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/CriteriaDefinition.java index ee1e4b2ad103..e329869c5486 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/CriteriaDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/CriteriaDefinition.java @@ -53,7 +53,7 @@ * List<Book> books * = new CriteriaDefinition<>(sessionFactory, Book.class, * "from Book left join fetch authors where type = BOOK") {{ - * var book = (JpaRoot<Book>) getSelection(); + * var book = getRoot(0, Book.class); * where(getRestriction(), like(book.get(Book_.title), "%Hibernate%")); * orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn))); * }} @@ -148,8 +148,10 @@ public CriteriaDefinition(CriteriaDefinition template) { */ public CriteriaDefinition(CriteriaDefinition template, Class resultType) { super( template.getCriteriaBuilder() ); - query = ((SqmSelectStatement) template.query) - .createCopy( SqmCopyContext.simpleContext(), resultType ); + if ( !(template.query instanceof SqmSelectStatement selectStatement) ) { + throw new IllegalArgumentException( "Not a SqmSelectStatement" ); + } + query = selectStatement.createCopy( SqmCopyContext.simpleContext(), resultType ); } public CriteriaDefinition(SessionFactory factory, Class resultType) { @@ -220,6 +222,11 @@ public JpaCriteriaQuery restrict(Predicate predicate) { return existing == null ? where( predicate ) : where( existing, predicate ); } + @Override + public HibernateCriteriaBuilder getCriteriaBuilder() { + return query.getCriteriaBuilder(); + } + @Override public JpaCriteriaQuery select(Selection selection) { return query.select(selection); @@ -401,10 +408,20 @@ public FetchClauseType getFetchClauseType() { } @Override - public List> getRootList() { + public List> getRootList() { return query.getRootList(); } + @Override + public JpaRoot getRoot(int position, Class type) { + return query.getRoot( position, type ); + } + + @Override + public JpaRoot getRoot(String alias, Class type) { + return query.getRoot( alias, type ); + } + @Override public Collection> getCteCriterias() { return query.getCteCriterias(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaQuery.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaQuery.java index f52a96b27593..091b229ea057 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCriteriaQuery.java @@ -61,13 +61,33 @@ public interface JpaCriteriaQuery extends CriteriaQuery, JpaQueryableCrite /** * Return the {@linkplain #getRoots() roots} as a list. */ - List> getRootList(); + List> getRootList(); - @Override - @SuppressWarnings("unchecked") - default List getOrderList() { - return (List) getQueryPart().getSortSpecifications(); - } + /** + * Get a {@linkplain Root query root} element at the given position + * with the given type. + * + * @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 + */ + JpaRoot getRoot(int position, Class 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 + */ + JpaRoot getRoot(String alias, Class type); /** * {@inheritDoc} @@ -132,4 +152,6 @@ default List getOrderList() { @Override JpaSubQuery subquery(EntityType type); + + HibernateCriteriaBuilder getCriteriaBuilder(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java index 93f3aa64be86..4f9c020ff24a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java @@ -66,9 +66,9 @@ public interface JpaQueryStructure extends JpaQueryPart { List> getGroupingExpressions(); - JpaQueryStructure setGroupingExpressions(List> grouping); + JpaQueryStructure setGroupingExpressions(List> grouping); - JpaQueryStructure setGroupingExpressions(JpaExpression... grouping); + JpaQueryStructure setGroupingExpressions(Expression... grouping); JpaPredicate getGroupRestriction(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java b/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java index 6aec5798fa23..ce78e274c18d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java @@ -5,6 +5,7 @@ package org.hibernate.query.restriction; import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import jakarta.persistence.metamodel.SingularAttribute; @@ -12,6 +13,7 @@ import org.hibernate.Internal; import org.hibernate.query.Order; import org.hibernate.query.SelectionQuery; +import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.range.Range; import java.util.List; @@ -77,6 +79,17 @@ default Restriction and(Restriction restriction) { @Internal Predicate toPredicate(Root root, CriteriaBuilder builder); + /** + * Apply this restriction to the given root entity of the given + * {@linkplain CriteriaQuery criteria query}. + */ + default void apply(CriteriaQuery query, Root root) { + if ( !(query instanceof JpaCriteriaQuery criteriaQuery) ) { + throw new IllegalArgumentException( "Not a JpaCriteriaQuery" ); + } + query.where( query.getRestriction(), toPredicate( root, criteriaQuery.getCriteriaBuilder() ) ); + } + /** * Restrict the allowed values of the given attribute to the given * {@linkplain Range range}. diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java index 1aceebe5b4e5..5eba6bf61c78 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java @@ -908,19 +908,19 @@ SqmJsonValueExpression jsonValue( SqmSelectStatement createTupleQuery(); @Override - JpaCompoundSelection construct(Class resultClass, Selection[] selections); + JpaCompoundSelection construct(Class resultClass, Selection... selections); @Override JpaCompoundSelection construct(Class resultClass, List> arguments); @Override - JpaCompoundSelection tuple(Selection[] selections); + JpaCompoundSelection tuple(Selection... selections); @Override JpaCompoundSelection tuple(List> selections); @Override - JpaCompoundSelection array(Selection[] selections); + JpaCompoundSelection array(Selection... selections); @Override JpaCompoundSelection array(List> selections); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java index 68216deaf550..db17119a7195 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java @@ -4,7 +4,6 @@ */ package org.hibernate.query.sqm.internal; -import jakarta.persistence.criteria.Root; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.spi.AppliedGraph; @@ -150,9 +149,7 @@ public SelectionQuery setOrder(Order order) { @Override public SelectionQuery addRestriction(Restriction restriction) { final SqmSelectStatement selectStatement = getSqmSelectStatement().copy( noParamCopyContext() ); - final Root root = (Root) selectStatement.getRootList().get( 0 ); - selectStatement.where( selectStatement.getRestriction(), - restriction.toPredicate( root, selectStatement.nodeBuilder() ) ); + restriction.apply( selectStatement, selectStatement.getRoot( 0, getExpectedResultType() ) ); // TODO: when the QueryInterpretationCache can handle caching criteria queries, // simply cache the new SQM as if it were a criteria query, and remove this: getQueryOptions().setQueryPlanCachingEnabled( false ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/KeyBasedPagination.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/KeyBasedPagination.java index 12ed3ca3231f..eec0f0a6710a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/KeyBasedPagination.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/KeyBasedPagination.java @@ -111,7 +111,7 @@ private static JpaCompoundSelection> keyedResultConstructor( return builder.construct( resultClass, asList( selected, builder.construct(List.class, newItems ) ) ); } - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings("rawtypes") private static > SqmPredicate keyPredicate( Expression key, C keyValue, SortDirection direction, List> previousKeys, List> keyValues, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index 35dd4b1d9bad..54fb8c4af8c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -40,7 +40,6 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; @@ -167,6 +166,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; 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.sqm.TrimSpec.fromCriteriaTrimSpec; @@ -872,7 +872,7 @@ public JpaSearchOrder desc(JpaCteCriteriaAttribute x, boolean nullsFirst) { } @Override - public JpaCompoundSelection tuple(Selection[] selections) { + public JpaCompoundSelection tuple(Selection... selections) { return tuple( Arrays.asList( selections ) ); } @@ -916,62 +916,69 @@ public SqmTuple tuple(SqmExpressible tupleType, List array(Selection[] selections) { - return array( Arrays.asList( selections ) ); + public JpaCompoundSelection array(Selection... selections) { + return array( Object[].class, + Arrays.stream( selections ).map( selection -> (SqmSelectableNode) selection ).toList() ); } @Override public JpaCompoundSelection array(List> selections) { - //noinspection unchecked,rawtypes - return array( Object[].class, (List) selections ); + return arrayInternal( Object[].class, + selections.stream().map( selection -> (SqmSelectableNode) selection ).toList() ); } @Override - public JpaCompoundSelection array(Class resultClass, Selection[] selections) { - //noinspection unchecked - return array( resultClass, (List>) (List) Arrays.asList( selections ) ); + public JpaCompoundSelection array(Class resultClass, Selection... selections) { + return arrayInternal( resultClass, + Arrays.stream( selections ).map( selection -> (SqmSelectableNode) selection ).toList() ); } @Override public JpaCompoundSelection array(Class resultClass, List> selections) { - //noinspection rawtypes,unchecked - checkMultiselect( (List) selections ); + return arrayInternal( resultClass, + selections.stream().map( selection -> (SqmSelectableNode) selection ).toList() ); + } + + public JpaCompoundSelection arrayInternal(Class resultClass, List> selections) { + checkMultiselect( selections ); final JavaType javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( resultClass ); - //noinspection unchecked - return new SqmJpaCompoundSelection<>( (List>) selections, javaType, this ); + return new SqmJpaCompoundSelection<>( selections, javaType, this ); } @Override - public JpaCompoundSelection construct(Class resultClass, Selection[] arguments) { - //noinspection unchecked - return construct( resultClass, (List>) (List) Arrays.asList( arguments ) ); + public JpaCompoundSelection construct(Class resultClass, Selection... arguments) { + return constructInternal( resultClass, + Arrays.stream( arguments ).map( arg -> (SqmSelectableNode) arg ).toList() ); } @Override public JpaCompoundSelection construct(Class resultClass, List> arguments) { - //noinspection unchecked,rawtypes - checkMultiselect( (List) arguments ); - final SqmDynamicInstantiation instantiation; + return constructInternal( resultClass, + arguments.stream().map( arg -> (SqmSelectableNode) arg ).toList() ); + } + + private JpaCompoundSelection constructInternal(Class resultClass, List> arguments) { + checkMultiselect( arguments ); + final SqmDynamicInstantiation instantiation = createInstantiation( resultClass ); + for ( SqmSelectableNode argument : arguments ) { + final SqmDynamicInstantiationArgument arg = + new SqmDynamicInstantiationArgument<>( argument, argument.getAlias(), this ); + instantiation.addArgument( arg ); + } + return instantiation; + } + + @SuppressWarnings("unchecked") + private SqmDynamicInstantiation createInstantiation(Class resultClass) { if ( List.class.equals( resultClass ) ) { - //noinspection unchecked - instantiation = (SqmDynamicInstantiation) SqmDynamicInstantiation.forListInstantiation( this ); + return (SqmDynamicInstantiation) SqmDynamicInstantiation.forListInstantiation( this ); } else if ( Map.class.equals( resultClass ) ) { - //noinspection unchecked - instantiation = (SqmDynamicInstantiation) SqmDynamicInstantiation.forMapInstantiation( this ); + return (SqmDynamicInstantiation) SqmDynamicInstantiation.forMapInstantiation( this ); } 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 @@ else if ( Map.class.equals( resultClass ) ) { * "An argument to the multiselect method must not be a tuple- * or array-valued compound selection item." */ - void checkMultiselect(List> selections) { - final HashSet aliases = new HashSet<>( CollectionHelper.determineProperSizing( selections.size() ) ); + void checkMultiselect(List> selections) { + final HashSet aliases = new HashSet<>( determineProperSizing( selections.size() ) ); for ( Selection it : selections ) { final JpaSelection selection = (JpaSelection) it; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index eda5e0da259a..d744ad13421a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -2437,7 +2437,7 @@ private int indexOfExpression(int offset, List> sele offset = -subResult - i; } else if ( selectableNode instanceof SqmJpaCompoundSelection compoundSelection ) { - final List> selectionItems = compoundSelection.getSelectionItems(); + final List> selectionItems = compoundSelection.getSelectionItems(); for ( int j = 0; j < selectionItems.size(); j++ ) { if ( selectionItems.get( j ) == node ) { return offset + i + j; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java index f409736718a7..203ad00563c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java @@ -6,7 +6,6 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -14,6 +13,9 @@ import org.hibernate.query.sqm.tree.SqmCopyContext; 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. *

@@ -50,7 +52,7 @@ public SqmFromClause copy(SqmCopyContext context) { * mutate the roots */ public List> getRoots() { - return domainRoots == null ? Collections.emptyList() : Collections.unmodifiableList( domainRoots ); + return domainRoots == null ? emptyList() : unmodifiableList( domainRoots ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java index 866732fc62f9..f7139a175d7e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java @@ -4,7 +4,6 @@ */ package org.hibernate.query.sqm.tree.select; -import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -12,6 +11,7 @@ import java.util.Set; import java.util.function.Function; +import org.hibernate.AssertionFailure; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.criteria.JpaCteCriteria; import org.hibernate.query.criteria.JpaFunctionRoot; @@ -40,11 +40,12 @@ import jakarta.persistence.metamodel.EntityType; import static java.lang.Character.isAlphabetic; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; /** * @author Steve Ebersole */ -@SuppressWarnings("unchecked") public abstract class AbstractSqmSelectQuery extends AbstractSqmNode implements SqmSelectQuery { @@ -110,7 +111,7 @@ public Collection> getCteCriterias() { return cteStatements.values(); } - @Override + @Override @SuppressWarnings("unchecked") public JpaCteCriteria getCteCriteria(String cteName) { return (JpaCteCriteria) cteStatements.get( cteName ); } @@ -222,14 +223,54 @@ public void setQueryPart(SqmQueryPart sqmQueryPart) { } @Override - @SuppressWarnings("rawtypes") public Set> getRoots() { - return (Set) getQuerySpec().getRoots(); + return unmodifiableSet( getQuerySpec().getRoots() ); + } + + /** + * @see org.hibernate.query.criteria.JpaCriteriaQuery#getRootList() + */ + public List> getRootList() { + return getQuerySpec().getRootList(); + } + + /** + * @see org.hibernate.query.criteria.JpaCriteriaQuery#getRoot(int, Class) + */ + public JpaRoot getRoot(int position, Class type) { + final List> 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 JpaRoot getRoot(String alias, Class type) { + final List> 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 ); } - @SuppressWarnings("rawtypes") - public List> getRootList() { - return (List) getQuerySpec().getRootList(); + private static JpaRoot castRoot(JpaRoot root, Class 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 result = (JpaRoot) root; + return result; } @Override @@ -329,20 +370,19 @@ public SqmSelectQuery where(Predicate... restrictions) { // Grouping @Override - @SuppressWarnings("rawtypes") public List> getGroupList() { - return (List) getQuerySpec().getGroupingExpressions(); + return unmodifiableList( getQuerySpec().getGroupingExpressions() ); } @Override public SqmSelectQuery groupBy(Expression... expressions) { - return groupBy( Arrays.asList( expressions ) ); + getQuerySpec().setGroupingExpressions( List.of( expressions ) ); + return this; } @Override - @SuppressWarnings("rawtypes") public SqmSelectQuery groupBy(List> grouping) { - getQuerySpec().setGroupingExpressions( (List) grouping ); + getQuerySpec().setGroupingExpressions( grouping ); return this; } @@ -363,35 +403,6 @@ public SqmSelectQuery having(Predicate... predicates) { return this; } -// -// // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// // Limit -// -// -// @Override -// @SuppressWarnings("unchecked") -// public ExpressionImplementor getLimit() { -// return limit; -// } -// -// @Override -// public C setLimit(JpaExpression limit) { -// this.limit = (ExpressionImplementor) limit; -// return this; -// } -// -// @Override -// @SuppressWarnings("unchecked") -// public ExpressionImplementor getOffset() { -// return offset; -// } -// -// @Override -// public C setOffset(JpaExpression offset) { -// this.offset = (ExpressionImplementor) offset; -// return this; -// } - public void appendHqlString(StringBuilder sb) { if ( !cteStatements.isEmpty() ) { sb.append( "with " ); @@ -404,35 +415,28 @@ public void appendHqlString(StringBuilder sb) { sqmQueryPart.appendHqlString( sb ); } + @SuppressWarnings("unchecked") protected Selection getResultSelection(Selection[] selections) { - final Selection resultSelection; - Class resultType = getResultType(); + final Class resultType = getResultType(); if ( resultType == null || resultType == Object.class ) { switch ( selections.length ) { - case 0: { - throw new IllegalArgumentException( - "empty selections passed to criteria query typed as Object" - ); - } - case 1: { - resultSelection = ( Selection ) selections[0]; - break; - } - default: { - resultSelection = ( Selection ) nodeBuilder().array( selections ); - } + case 0: + throw new IllegalArgumentException( "Empty selections passed to criteria query typed as Object" ); + case 1: + return (Selection) selections[0]; + default: + return (Selection) nodeBuilder().array( selections ); } } else if ( Tuple.class.isAssignableFrom( resultType ) ) { - resultSelection = ( Selection ) nodeBuilder().tuple( selections ); + return (Selection) nodeBuilder().tuple( selections ); } else if ( resultType.isArray() ) { - resultSelection = nodeBuilder().array( resultType, selections ); + return nodeBuilder().array( resultType, selections ); } else { - resultSelection = nodeBuilder().construct( resultType, selections ); + return nodeBuilder().construct( resultType, selections ); } - return resultSelection; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java index cef43e909df8..3be65d7f95a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java @@ -56,11 +56,11 @@ public class SqmJpaCompoundSelection // 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` - private final List> selectableNodes; + private final List> selectableNodes; private final JavaType javaType; public SqmJpaCompoundSelection( - List> selectableNodes, + List> selectableNodes, JavaType javaType, NodeBuilder criteriaBuilder) { super( null, criteriaBuilder ); @@ -111,7 +111,7 @@ public Class getBindableJavaType() { } @Override - public List> getSelectionItems() { + public List> getSelectionItems() { return selectableNodes; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java index 6e74afaece98..6fb48693ca7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java @@ -395,10 +395,10 @@ public List> getGroupingExpressions() { } @Override - public SqmQuerySpec setGroupingExpressions(List> groupExpressions) { + public SqmQuerySpec setGroupingExpressions(List> groupExpressions) { this.hasPositionalGroupItem = false; this.groupByClauseExpressions = new ArrayList<>( groupExpressions.size() ); - for ( JpaExpression groupExpression : groupExpressions ) { + for ( Expression groupExpression : groupExpressions ) { if ( groupExpression instanceof SqmAliasedNodeRef ) { this.hasPositionalGroupItem = true; } @@ -408,10 +408,10 @@ public SqmQuerySpec setGroupingExpressions(List> g } @Override - public SqmQuerySpec setGroupingExpressions(JpaExpression... groupExpressions) { + public SqmQuerySpec setGroupingExpressions(Expression... groupExpressions) { this.hasPositionalGroupItem = false; this.groupByClauseExpressions = new ArrayList<>( groupExpressions.length ); - for ( JpaExpression groupExpression : groupExpressions ) { + for ( Expression groupExpression : groupExpressions ) { if ( groupExpression instanceof SqmAliasedNodeRef ) { this.hasPositionalGroupItem = true; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java index 91d76f35d563..371f00b4835e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java @@ -20,6 +20,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.internal.ParameterCollector; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; @@ -40,6 +41,7 @@ import jakarta.persistence.metamodel.EntityType; import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableSet; import static org.hibernate.query.sqm.spi.SqmCreationHelper.combinePredicates; import static org.hibernate.query.sqm.SqmQuerySource.CRITERIA; @@ -49,8 +51,8 @@ /** * @author Steve Ebersole */ -public class SqmSelectStatement extends AbstractSqmSelectQuery implements JpaCriteriaQuery, SqmStatement, - org.hibernate.query.sqm.internal.ParameterCollector { +public class SqmSelectStatement extends AbstractSqmSelectQuery + implements JpaCriteriaQuery, SqmStatement, ParameterCollector { private final SqmQuerySource querySource; private Set> parameters; @@ -160,6 +162,16 @@ public void validateResultType(Class resultType) { SqmUtil.validateQueryReturnType( getQueryPart(), resultType ); } + @Override + public NodeBuilder getCriteriaBuilder() { + return nodeBuilder(); + } + + @Override + public List getOrderList() { + return unmodifiableList( getQueryPart().getSortSpecifications() ); + } + @Override public SqmQuerySource getQuerySource() { return querySource; @@ -362,33 +374,26 @@ public SqmSelectStatement multiselect(List> selectionList) { } @SuppressWarnings("unchecked") - private Selection getResultSelection(List selectionList) { + private Selection getResultSelection(List selections) { final Class resultType = getResultType(); - //noinspection rawtypes - final List> selections = (List) selectionList; if ( resultType == null || resultType == Object.class ) { - switch ( selectionList.size() ) { - case 0: { - throw new IllegalArgumentException( - "empty selections passed to criteria query typed as Object" - ); - } - case 1: { - return (Selection) selectionList.get( 0 ); - } - default: { - return (Selection) nodeBuilder().array( (List) selectionList ); - } + switch ( selections.size() ) { + case 0: + throw new IllegalArgumentException( "Empty selections passed to criteria query typed as Object" ); + case 1: + return (Selection) selections.get( 0 ); + default: + return (Selection) nodeBuilder().array( (List>) selections ); } } else if ( Tuple.class.isAssignableFrom( resultType ) ) { - return (Selection) nodeBuilder().tuple( (List) selectionList ); + return (Selection) nodeBuilder().tuple( (List>) selections ); } else if ( resultType.isArray() ) { - return nodeBuilder().array( resultType, selections ); + return nodeBuilder().array( resultType, (List>) selections ); } else { - return nodeBuilder().construct( resultType, selections ); + return nodeBuilder().construct( resultType, (List>) selections ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/CriteriaDefinitionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/CriteriaDefinitionTest.java index ca1b019f9a1e..feeaaba5db8a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/CriteriaDefinitionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/CriteriaDefinitionTest.java @@ -51,6 +51,12 @@ public class CriteriaDefinitionTest { 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 message = from(Message.class); restrict(like(message.get("text"), "hell%")); @@ -77,6 +83,9 @@ public class CriteriaDefinitionTest { var messages = session.createSelectionQuery(query3).getResultList(); assertEquals(2,messages.size()); + var messagesPrime = session.createSelectionQuery(query3prime).getResultList(); + assertEquals(2,messagesPrime.size()); + var msg = session.createSelectionQuery(query4).getSingleResult(); assertNotNull(msg); assertEquals(1L,msg.id); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java index f1275e5fcbc7..4366e7082f4c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java @@ -216,6 +216,59 @@ void testPath(SessionFactoryScope scope) { assertEquals( 2, unsafeTest.size() ); } + @Test + void testCriteria(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( session -> { + session.persist(new Book("9781932394153", "Hibernate in Action", 400)); + session.persist(new Book("9781617290459", "Java Persistence with Hibernate", 1000)); + }); + + var bookType = scope.getSessionFactory().getJpaMetamodel().findEntityType(Book.class); + @SuppressWarnings( "unchecked" ) + var title = (SingularAttribute) bookType.findSingularAttribute("title"); + @SuppressWarnings( "unchecked" ) + var isbn = (SingularAttribute) bookType.findSingularAttribute("isbn"); + @SuppressWarnings( "unchecked" ) + var pages = (SingularAttribute) bookType.findSingularAttribute("pages"); + + scope.inSession( session -> { + var query = session.getCriteriaBuilder().createQuery(String.class); + var root = query.from( Book.class ); + like( title, "%Hibernate%" ).apply( query, root ); + query.select( root.get( title ) ); + List titles = session.createQuery( query ).getResultList(); + assertEquals( 2, titles.size() ); + } ); + scope.inSession( session -> { + var query = session.getCriteriaBuilder().createQuery(String.class); + var root = query.from( Book.class ); + equal( isbn, "9781932394153" ).apply( query, root ); + query.select( root.get( title ) ); + List titles = session.createQuery( query ).getResultList(); + assertEquals( 1, titles.size() ); + } ); + scope.inSession( session -> { + var query = session.getCriteriaBuilder().createQuery("select title from Book", String.class); + var root = query.getRoot(0, Book.class); + equal( isbn, "9781932394153" ).apply( query, root ); + List titles = session.createQuery( query ).getResultList(); + 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 matchingBooks = session.createSelectionQuery(query).getResultList(); + assertEquals( 1, matchingBooks.size() ); + }); + + } + + @Entity(name="Book") static class Book { @Id