HHH-18271 Avoid query validations of cached queries by doing validation eagerly. Cache allowed result types per query interpretation
This commit is contained in:
parent
cf44c30bf2
commit
850a2a0753
|
@ -555,6 +555,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
queryExpressionContext.accept( this );
|
||||
|
||||
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
|
||||
insertStatement.validate( query );
|
||||
return insertStatement;
|
||||
}
|
||||
finally {
|
||||
|
@ -613,6 +614,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
insertStatement.values( valuesList );
|
||||
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
|
||||
insertStatement.validate( query );
|
||||
return insertStatement;
|
||||
}
|
||||
finally {
|
||||
|
@ -684,6 +686,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
updateStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
|
||||
}
|
||||
|
||||
updateStatement.validate( query );
|
||||
return updateStatement;
|
||||
}
|
||||
finally {
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.query.spi.SelectQueryPlan;
|
|||
import org.hibernate.query.sql.spi.ParameterInterpretation;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||
|
||||
/**
|
||||
|
@ -72,7 +73,7 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation
|
|||
final DomainParameterXref domainParameterXref;
|
||||
final ParameterMetadataImplementor parameterMetadata;
|
||||
if ( sqmStatement.getSqmParameters().isEmpty() ) {
|
||||
domainParameterXref = DomainParameterXref.empty();
|
||||
domainParameterXref = DomainParameterXref.EMPTY;
|
||||
parameterMetadata = ParameterMetadataImpl.EMPTY;
|
||||
}
|
||||
else {
|
||||
|
@ -101,6 +102,12 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation
|
|||
public DomainParameterXref getDomainParameterXref() {
|
||||
return domainParameterXref;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateResultType(Class<?> resultType) {
|
||||
assert sqmStatement instanceof SqmSelectStatement<?>;
|
||||
( (SqmSelectStatement<R>) sqmStatement ).validateResultType( resultType );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
|
|||
final DomainParameterXref domainParameterXref;
|
||||
|
||||
if ( sqmStatement.getSqmParameters().isEmpty() ) {
|
||||
domainParameterXref = DomainParameterXref.empty();
|
||||
domainParameterXref = DomainParameterXref.EMPTY;
|
||||
parameterMetadata = ParameterMetadataImpl.EMPTY;
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.query.spi;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
|
@ -33,40 +32,14 @@ import org.hibernate.graph.GraphSemantic;
|
|||
import org.hibernate.graph.spi.AppliedGraph;
|
||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
|
||||
import org.hibernate.metamodel.model.domain.BasicDomainType;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
|
||||
import org.hibernate.metamodel.model.domain.SimpleDomainType;
|
||||
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.QueryParameter;
|
||||
import org.hibernate.query.QueryTypeMismatchException;
|
||||
import org.hibernate.query.SelectionQuery;
|
||||
import org.hibernate.query.criteria.JpaSelection;
|
||||
import org.hibernate.query.internal.ScrollableResultsIterator;
|
||||
import org.hibernate.query.named.NamedQueryMemento;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.SqmPathSource;
|
||||
import org.hibernate.query.sqm.spi.NamedSqmQueryMemento;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryGroup;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
||||
import org.hibernate.sql.exec.internal.CallbackImpl;
|
||||
import org.hibernate.sql.exec.spi.Callback;
|
||||
import org.hibernate.sql.results.internal.TupleMetadata;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.BasicTypeRegistry;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
|
||||
import jakarta.persistence.CacheRetrieveMode;
|
||||
import jakarta.persistence.CacheStoreMode;
|
||||
|
@ -76,10 +49,6 @@ import jakarta.persistence.LockModeType;
|
|||
import jakarta.persistence.NoResultException;
|
||||
import jakarta.persistence.Parameter;
|
||||
import jakarta.persistence.TemporalType;
|
||||
import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.TupleElement;
|
||||
import jakarta.persistence.criteria.CompoundSelection;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Spliterators.spliteratorUnknownSize;
|
||||
import static org.hibernate.CacheMode.fromJpaModes;
|
||||
|
@ -94,8 +63,6 @@ import static org.hibernate.jpa.HibernateHints.HINT_CACHE_REGION;
|
|||
import static org.hibernate.jpa.HibernateHints.HINT_FETCH_SIZE;
|
||||
import static org.hibernate.jpa.HibernateHints.HINT_FOLLOW_ON_LOCKING;
|
||||
import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.isHqlTuple;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -114,110 +81,6 @@ public abstract class AbstractSelectionQuery<R>
|
|||
super( session );
|
||||
}
|
||||
|
||||
protected TupleMetadata buildTupleMetadata(SqmStatement<?> statement, Class<R> resultType) {
|
||||
if ( statement instanceof SqmSelectStatement<?> ) {
|
||||
final SqmSelectStatement<?> select = (SqmSelectStatement<?>) statement;
|
||||
final List<SqmSelection<?>> selections =
|
||||
select.getQueryPart().getFirstQuerySpec().getSelectClause()
|
||||
.getSelections();
|
||||
return isTupleMetadataRequired( resultType, selections.get(0) )
|
||||
? getTupleMetadata( selections )
|
||||
: null;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static <R> boolean isTupleMetadataRequired(Class<R> resultType, SqmSelection<?> selection) {
|
||||
return isHqlTuple( selection )
|
||||
|| !isInstantiableWithoutMetadata( resultType )
|
||||
&& !isSelectionAssignableToResultType( selection, resultType );
|
||||
}
|
||||
|
||||
private TupleMetadata getTupleMetadata(List<SqmSelection<?>> selections) {
|
||||
if ( getQueryOptions().getTupleTransformer() == null ) {
|
||||
return new TupleMetadata( buildTupleElementArray( selections ), buildTupleAliasArray( selections ) );
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer: "
|
||||
+ getQueryOptions().getTupleTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static TupleElement<?>[] buildTupleElementArray(List<SqmSelection<?>> selections) {
|
||||
if ( selections.size() == 1 ) {
|
||||
final SqmSelectableNode<?> selectableNode = selections.get(0).getSelectableNode();
|
||||
if ( selectableNode instanceof CompoundSelection<?> ) {
|
||||
final List<? extends JpaSelection<?>> selectionItems = selectableNode.getSelectionItems();
|
||||
final TupleElement<?>[] elements = new TupleElement<?>[ selectionItems.size() ];
|
||||
for ( int i = 0; i < selectionItems.size(); i++ ) {
|
||||
elements[i] = selectionItems.get( i );
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
else {
|
||||
return new TupleElement<?>[] { selectableNode };
|
||||
}
|
||||
}
|
||||
else {
|
||||
final TupleElement<?>[] elements = new TupleElement<?>[ selections.size() ];
|
||||
for ( int i = 0; i < selections.size(); i++ ) {
|
||||
elements[i] = selections.get( i ).getSelectableNode();
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] buildTupleAliasArray(List<SqmSelection<?>> selections) {
|
||||
if ( selections.size() == 1 ) {
|
||||
final SqmSelectableNode<?> selectableNode = selections.get(0).getSelectableNode();
|
||||
if ( selectableNode instanceof CompoundSelection<?> ) {
|
||||
final List<? extends JpaSelection<?>> selectionItems = selectableNode.getSelectionItems();
|
||||
final String[] elements = new String[ selectionItems.size() ];
|
||||
for ( int i = 0; i < selectionItems.size(); i++ ) {
|
||||
elements[i] = selectionItems.get( i ).getAlias();
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
else {
|
||||
return new String[] { selectableNode.getAlias() };
|
||||
}
|
||||
}
|
||||
else {
|
||||
final String[] elements = new String[ selections.size() ];
|
||||
for ( int i = 0; i < selections.size(); i++ ) {
|
||||
elements[i] = selections.get( i ).getAlias();
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyOptions(NamedSqmQueryMemento memento) {
|
||||
applyOptions( (NamedQueryMemento) memento );
|
||||
|
||||
if ( memento.getFirstResult() != null ) {
|
||||
setFirstResult( memento.getFirstResult() );
|
||||
}
|
||||
|
||||
if ( memento.getMaxResults() != null ) {
|
||||
setMaxResults( memento.getMaxResults() );
|
||||
}
|
||||
|
||||
if ( memento.getParameterTypes() != null ) {
|
||||
final BasicTypeRegistry basicTypeRegistry =
|
||||
getSessionFactory().getTypeConfiguration().getBasicTypeRegistry();
|
||||
for ( Map.Entry<String, String> entry : memento.getParameterTypes().entrySet() ) {
|
||||
final BasicType<?> type =
|
||||
basicTypeRegistry.getRegisteredType( entry.getValue() );
|
||||
getParameterMetadata()
|
||||
.getQueryParameter( entry.getKey() ).applyAnticipatedType( type );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyOptions(NamedQueryMemento memento) {
|
||||
if ( memento.getHints() != null ) {
|
||||
memento.getHints().forEach( this::applyHint );
|
||||
|
@ -258,265 +121,6 @@ public abstract class AbstractSelectionQuery<R>
|
|||
|
||||
protected abstract String getQueryString();
|
||||
|
||||
/**
|
||||
* Used to validate that the specified query return type is valid (i.e. the user
|
||||
* did not pass {@code Integer.class} when the selection is an entity)
|
||||
*/
|
||||
protected void visitQueryReturnType(
|
||||
SqmQueryPart<R> queryPart,
|
||||
Class<R> expectedResultType,
|
||||
SessionFactoryImplementor factory) {
|
||||
if ( queryPart instanceof SqmQuerySpec<?> ) {
|
||||
final SqmQuerySpec<R> sqmQuerySpec = (SqmQuerySpec<R>) queryPart;
|
||||
final List<SqmSelection<?>> sqmSelections = sqmQuerySpec.getSelectClause().getSelections();
|
||||
|
||||
if ( getQueryString() == CRITERIA_HQL_STRING ) {
|
||||
if ( sqmSelections == null || sqmSelections.isEmpty() ) {
|
||||
// make sure there is at least one root
|
||||
final List<SqmRoot<?>> sqmRoots = sqmQuerySpec.getFromClause().getRoots();
|
||||
if ( sqmRoots == null || sqmRoots.isEmpty() ) {
|
||||
throw new IllegalArgumentException( "Criteria did not define any query roots" );
|
||||
}
|
||||
// if there is a single root, use that as the selection
|
||||
if ( sqmRoots.size() == 1 ) {
|
||||
sqmQuerySpec.getSelectClause().add( sqmRoots.get( 0 ), null );
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException( "Criteria has multiple query roots" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( expectedResultType != null ) {
|
||||
checkQueryReturnType( sqmQuerySpec, expectedResultType, factory );
|
||||
}
|
||||
}
|
||||
else {
|
||||
final SqmQueryGroup<R> queryGroup = (SqmQueryGroup<R>) queryPart;
|
||||
for ( SqmQueryPart<R> sqmQueryPart : queryGroup.getQueryParts() ) {
|
||||
visitQueryReturnType( sqmQueryPart, expectedResultType, factory );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static <T> void checkQueryReturnType(
|
||||
SqmQuerySpec<T> querySpec,
|
||||
Class<T> expectedResultClass,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
if ( isResultTypeAlwaysAllowed( expectedResultClass ) ) {
|
||||
// the result-class is always safe to use (Object, ...)
|
||||
return;
|
||||
}
|
||||
|
||||
final List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections();
|
||||
if ( selections.size() == 1 ) {
|
||||
final SqmSelection<?> sqmSelection = selections.get( 0 );
|
||||
final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
|
||||
if ( selectableNode.isCompoundSelection() ) {
|
||||
final Class<?> expectedSelectItemType = expectedResultClass.isArray()
|
||||
? expectedResultClass.getComponentType()
|
||||
: expectedResultClass;
|
||||
for ( JpaSelection<?> selection : selectableNode.getSelectionItems() ) {
|
||||
verifySelectionType( expectedSelectItemType, sessionFactory, (SqmSelectableNode<?>) selection );
|
||||
}
|
||||
}
|
||||
else {
|
||||
verifySingularSelectionType( expectedResultClass, sessionFactory, sqmSelection );
|
||||
}
|
||||
}
|
||||
else if ( expectedResultClass.isArray() ) {
|
||||
final Class<?> componentType = expectedResultClass.getComponentType();
|
||||
for ( SqmSelection<?> selection : selections ) {
|
||||
verifySelectionType( componentType, sessionFactory, selection.getSelectableNode() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case for a single, non-compound selection-item. It is essentially
|
||||
* a special case of {@linkplain #verifySelectionType} which additionally
|
||||
* handles the case where the type of the selection-item can be used to
|
||||
* instantiate the result-class (result-class has a matching constructor).
|
||||
*
|
||||
* @apiNote We don't want to hoist this into {@linkplain #verifySelectionType}
|
||||
* itself because this can only happen for the root non-compound case, and we
|
||||
* want to avoid the try/catch otherwise
|
||||
*/
|
||||
private static <T> void verifySingularSelectionType(
|
||||
Class<T> expectedResultClass,
|
||||
SessionFactoryImplementor sessionFactory,
|
||||
SqmSelection<?> sqmSelection) {
|
||||
final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
|
||||
try {
|
||||
verifySelectionType( expectedResultClass, sessionFactory, selectableNode );
|
||||
}
|
||||
catch (QueryTypeMismatchException mismatchException) {
|
||||
// Check for special case of a single selection item and implicit instantiation.
|
||||
// See if the selected type can be used to instantiate the expected-type
|
||||
final JavaType<?> javaTypeDescriptor = selectableNode.getJavaTypeDescriptor();
|
||||
if ( javaTypeDescriptor != null ) {
|
||||
final Class<?> selectedJavaType = javaTypeDescriptor.getJavaTypeClass();
|
||||
// ignore the exception if the expected type has a constructor accepting the selected item type
|
||||
if ( hasMatchingConstructor( expectedResultClass, selectedJavaType ) ) {
|
||||
// ignore it
|
||||
}
|
||||
else {
|
||||
throw mismatchException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> boolean hasMatchingConstructor(Class<T> expectedResultClass, Class<?> selectedJavaType) {
|
||||
try {
|
||||
expectedResultClass.getDeclaredConstructor( selectedJavaType );
|
||||
return true;
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> void verifySelectionType(
|
||||
Class<T> expectedResultClass,
|
||||
SessionFactoryImplementor sessionFactory,
|
||||
SqmSelectableNode<?> selection) {
|
||||
// special case for parameters in the select list
|
||||
if ( selection instanceof SqmParameter ) {
|
||||
final SqmParameter<?> sqmParameter = (SqmParameter<?>) selection;
|
||||
final SqmExpressible<?> nodeType = sqmParameter.getExpressible();
|
||||
// we may not yet know a selection type
|
||||
if ( nodeType == null || nodeType.getExpressibleJavaType() == null ) {
|
||||
// we can't verify the result type up front
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
|
||||
verifyResultType( expectedResultClass, selection.getExpressible() );
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInstantiableWithoutMetadata(Class<?> resultType) {
|
||||
return resultType == null
|
||||
|| resultType.isArray()
|
||||
|| Object.class == resultType
|
||||
|| List.class == resultType;
|
||||
}
|
||||
|
||||
private static <T> boolean isResultTypeAlwaysAllowed(Class<T> expectedResultClass) {
|
||||
return expectedResultClass == null
|
||||
|| expectedResultClass == Object.class
|
||||
|| expectedResultClass == Object[].class
|
||||
|| expectedResultClass == List.class
|
||||
|| expectedResultClass == Map.class
|
||||
|| expectedResultClass == Tuple.class;
|
||||
}
|
||||
|
||||
protected static <T> void verifyResultType(Class<T> resultClass, @Nullable SqmExpressible<?> selectionExpressible) {
|
||||
if ( selectionExpressible == null ) {
|
||||
// nothing we can validate
|
||||
return;
|
||||
}
|
||||
|
||||
final JavaType<?> selectionExpressibleJavaType = selectionExpressible.getExpressibleJavaType();
|
||||
assert selectionExpressibleJavaType != null;
|
||||
|
||||
final Class<?> selectionExpressibleJavaTypeClass = selectionExpressibleJavaType.getJavaTypeClass();
|
||||
if ( selectionExpressibleJavaTypeClass == Object.class ) {
|
||||
|
||||
}
|
||||
if ( selectionExpressibleJavaTypeClass != Object.class ) {
|
||||
// performs a series of opt-out checks for validity... each if branch and return indicates a valid case
|
||||
if ( resultClass.isAssignableFrom( selectionExpressibleJavaTypeClass ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( selectionExpressibleJavaType instanceof PrimitiveJavaType ) {
|
||||
final PrimitiveJavaType<?> primitiveJavaType = (PrimitiveJavaType<?>) selectionExpressibleJavaType;
|
||||
if ( primitiveJavaType.getPrimitiveClass() == resultClass ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isMatchingDateType( selectionExpressibleJavaTypeClass, resultClass, selectionExpressible ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isEntityIdType( selectionExpressible, resultClass ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
throwQueryTypeMismatchException( resultClass, selectionExpressible );
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> boolean isEntityIdType(SqmExpressible<?> selectionExpressible, Class<T> resultClass) {
|
||||
if ( selectionExpressible instanceof IdentifiableDomainType ) {
|
||||
final IdentifiableDomainType<?> identifiableDomainType = (IdentifiableDomainType<?>) selectionExpressible;
|
||||
final SimpleDomainType<?> idType = identifiableDomainType.getIdType();
|
||||
return resultClass.isAssignableFrom( idType.getBindableJavaType() );
|
||||
}
|
||||
else if ( selectionExpressible instanceof EntitySqmPathSource ) {
|
||||
final EntitySqmPathSource<?> entityPath = (EntitySqmPathSource<?>) selectionExpressible;
|
||||
final EntityDomainType<?> entityType = entityPath.getSqmPathType();
|
||||
final SimpleDomainType<?> idType = entityType.getIdType();
|
||||
return resultClass.isAssignableFrom( idType.getBindableJavaType() );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special case for date because we always report java.util.Date as expression type
|
||||
// But the expected resultClass could be a subtype of that, so we need to check the JdbcType
|
||||
private static <T> boolean isMatchingDateType(
|
||||
Class<?> javaTypeClass,
|
||||
Class<T> resultClass,
|
||||
SqmExpressible<?> sqmExpressible) {
|
||||
return javaTypeClass == Date.class
|
||||
&& isMatchingDateJdbcType( resultClass, getJdbcType( sqmExpressible ) );
|
||||
}
|
||||
|
||||
private static JdbcType getJdbcType(SqmExpressible<?> sqmExpressible) {
|
||||
if ( sqmExpressible instanceof BasicDomainType<?> ) {
|
||||
return ( (BasicDomainType<?>) sqmExpressible).getJdbcType();
|
||||
}
|
||||
else if ( sqmExpressible instanceof SqmPathSource<?> ) {
|
||||
final SqmPathSource<?> pathSource = (SqmPathSource<?>) sqmExpressible;
|
||||
final DomainType<?> domainType = pathSource.getSqmPathType();
|
||||
if ( domainType instanceof BasicDomainType<?> ) {
|
||||
return ( (BasicDomainType<?>) domainType ).getJdbcType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static <T> boolean isMatchingDateJdbcType(Class<T> resultClass, JdbcType jdbcType) {
|
||||
if ( jdbcType != null ) {
|
||||
switch ( jdbcType.getDefaultSqlTypeCode() ) {
|
||||
case Types.DATE:
|
||||
return resultClass.isAssignableFrom( java.sql.Date.class );
|
||||
case Types.TIME:
|
||||
return resultClass.isAssignableFrom( java.sql.Time.class );
|
||||
case Types.TIMESTAMP:
|
||||
return resultClass.isAssignableFrom( java.sql.Timestamp.class );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> void throwQueryTypeMismatchException(Class<T> resultClass, SqmExpressible<?> sqmExpressible) {
|
||||
throw new QueryTypeMismatchException( String.format(
|
||||
"Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array",
|
||||
resultClass.getName(),
|
||||
sqmExpressible.getTypeName()
|
||||
) );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// execution
|
||||
|
|
|
@ -20,4 +20,7 @@ public interface HqlInterpretation<R> {
|
|||
ParameterMetadataImplementor getParameterMetadata();
|
||||
|
||||
DomainParameterXref getDomainParameterXref();
|
||||
|
||||
void validateResultType(Class<?> resultType);
|
||||
|
||||
}
|
||||
|
|
|
@ -6,8 +6,12 @@
|
|||
*/
|
||||
package org.hibernate.query.spi;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -16,6 +20,7 @@ public class SimpleHqlInterpretationImpl<R> implements HqlInterpretation<R> {
|
|||
private final SqmStatement<R> sqmStatement;
|
||||
private final ParameterMetadataImplementor parameterMetadata;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
private final ConcurrentHashMap<Class<?>, Object> allowedReturnTypes;
|
||||
|
||||
public SimpleHqlInterpretationImpl(
|
||||
SqmStatement<R> sqmStatement,
|
||||
|
@ -24,6 +29,7 @@ public class SimpleHqlInterpretationImpl<R> implements HqlInterpretation<R> {
|
|||
this.sqmStatement = sqmStatement;
|
||||
this.parameterMetadata = parameterMetadata;
|
||||
this.domainParameterXref = domainParameterXref;
|
||||
this.allowedReturnTypes = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -40,4 +46,15 @@ public class SimpleHqlInterpretationImpl<R> implements HqlInterpretation<R> {
|
|||
public DomainParameterXref getDomainParameterXref() {
|
||||
return domainParameterXref.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateResultType(Class<?> resultType) {
|
||||
assert sqmStatement instanceof SqmSelectStatement<?>;
|
||||
if ( resultType != null && !SqmUtil.isResultTypeAlwaysAllowed( resultType ) ) {
|
||||
if ( !allowedReturnTypes.containsKey( resultType ) ) {
|
||||
SqmUtil.checkQueryReturnType( ( (SqmSelectStatement<R>) sqmStatement ).getQueryPart(), resultType );
|
||||
allowedReturnTypes.put( resultType, Boolean.TRUE );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,13 +31,13 @@ public class InterpretationException extends QueryException {
|
|||
|
||||
public InterpretationException(String query, String message) {
|
||||
super(
|
||||
"Error interpreting query [" + message + "] [" + query + "]",
|
||||
"Error interpreting query [" + message + "]",
|
||||
query
|
||||
);
|
||||
}
|
||||
public InterpretationException(String query, Exception cause) {
|
||||
super(
|
||||
"Error interpreting query [" + cause.getMessage() + "] [" + query + "]",
|
||||
"Error interpreting query [" + cause.getMessage() + "]",
|
||||
query,
|
||||
cause
|
||||
);
|
||||
|
|
|
@ -16,18 +16,38 @@ import org.hibernate.query.Order;
|
|||
import org.hibernate.query.Page;
|
||||
import org.hibernate.query.QueryLogging;
|
||||
import org.hibernate.query.SelectionQuery;
|
||||
import org.hibernate.query.criteria.JpaSelection;
|
||||
import org.hibernate.query.criteria.ValueHandlingMode;
|
||||
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
||||
import org.hibernate.query.hql.internal.QuerySplitter;
|
||||
import org.hibernate.query.named.NamedQueryMemento;
|
||||
import org.hibernate.query.spi.AbstractSelectionQuery;
|
||||
import org.hibernate.query.spi.HqlInterpretation;
|
||||
import org.hibernate.query.spi.MutableQueryOptions;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.spi.QueryInterpretationCache;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.query.spi.SelectQueryPlan;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.spi.NamedSqmQueryMemento;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryGroup;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
||||
import org.hibernate.sql.results.internal.TupleMetadata;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.BasicTypeRegistry;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.persistence.TupleElement;
|
||||
import jakarta.persistence.criteria.CompoundSelection;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.hibernate.cfg.QuerySettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
|
||||
|
@ -35,6 +55,8 @@ import static org.hibernate.query.KeyedPage.KeyInterpretation.KEY_OF_FIRST_ON_NE
|
|||
import static org.hibernate.query.sqm.internal.KeyBasedPagination.paginate;
|
||||
import static org.hibernate.query.sqm.internal.KeyedResult.collectKeys;
|
||||
import static org.hibernate.query.sqm.internal.KeyedResult.collectResults;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.isHqlTuple;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.sortSpecification;
|
||||
import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext;
|
||||
|
||||
|
@ -257,4 +279,157 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
|
|||
private <T> SelectQueryPlan<T> buildConcreteQueryPlan(SqmSelectStatement<T> sqmStatement, QueryOptions options) {
|
||||
return buildConcreteQueryPlan( sqmStatement, null, null, options );
|
||||
}
|
||||
|
||||
protected void applyOptions(NamedSqmQueryMemento memento) {
|
||||
applyOptions( (NamedQueryMemento) memento );
|
||||
|
||||
if ( memento.getFirstResult() != null ) {
|
||||
setFirstResult( memento.getFirstResult() );
|
||||
}
|
||||
|
||||
if ( memento.getMaxResults() != null ) {
|
||||
setMaxResults( memento.getMaxResults() );
|
||||
}
|
||||
|
||||
if ( memento.getParameterTypes() != null ) {
|
||||
final BasicTypeRegistry basicTypeRegistry =
|
||||
getSessionFactory().getTypeConfiguration().getBasicTypeRegistry();
|
||||
for ( Map.Entry<String, String> entry : memento.getParameterTypes().entrySet() ) {
|
||||
final BasicType<?> type =
|
||||
basicTypeRegistry.getRegisteredType( entry.getValue() );
|
||||
getParameterMetadata()
|
||||
.getQueryParameter( entry.getKey() ).applyAnticipatedType( type );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected TupleMetadata buildTupleMetadata(SqmStatement<?> statement, Class<R> resultType) {
|
||||
if ( statement instanceof SqmSelectStatement<?> ) {
|
||||
final SqmSelectStatement<?> select = (SqmSelectStatement<?>) statement;
|
||||
final SqmSelectClause selectClause = select.getQueryPart().getFirstQuerySpec().getSelectClause();
|
||||
final List<SqmSelection<?>> selections =
|
||||
selectClause
|
||||
.getSelections();
|
||||
return isTupleMetadataRequired( resultType, selections.get(0) )
|
||||
? getTupleMetadata( selections )
|
||||
: null;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static <R> boolean isTupleMetadataRequired(Class<R> resultType, SqmSelection<?> selection) {
|
||||
return isHqlTuple( selection )
|
||||
|| !isInstantiableWithoutMetadata( resultType )
|
||||
&& !isSelectionAssignableToResultType( selection, resultType );
|
||||
}
|
||||
|
||||
private static boolean isInstantiableWithoutMetadata(Class<?> resultType) {
|
||||
return resultType == null
|
||||
|| resultType.isArray()
|
||||
|| Object.class == resultType
|
||||
|| List.class == resultType;
|
||||
}
|
||||
|
||||
private TupleMetadata getTupleMetadata(List<SqmSelection<?>> selections) {
|
||||
if ( getQueryOptions().getTupleTransformer() == null ) {
|
||||
return new TupleMetadata( buildTupleElementArray( selections ), buildTupleAliasArray( selections ) );
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer: "
|
||||
+ getQueryOptions().getTupleTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static TupleElement<?>[] buildTupleElementArray(List<SqmSelection<?>> selections) {
|
||||
if ( selections.size() == 1 ) {
|
||||
final SqmSelectableNode<?> selectableNode = selections.get( 0).getSelectableNode();
|
||||
if ( selectableNode instanceof CompoundSelection<?> ) {
|
||||
final List<? extends JpaSelection<?>> selectionItems = selectableNode.getSelectionItems();
|
||||
final TupleElement<?>[] elements = new TupleElement<?>[ selectionItems.size() ];
|
||||
for ( int i = 0; i < selectionItems.size(); i++ ) {
|
||||
elements[i] = selectionItems.get( i );
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
else {
|
||||
return new TupleElement<?>[] { selectableNode };
|
||||
}
|
||||
}
|
||||
else {
|
||||
final TupleElement<?>[] elements = new TupleElement<?>[ selections.size() ];
|
||||
for ( int i = 0; i < selections.size(); i++ ) {
|
||||
elements[i] = selections.get( i ).getSelectableNode();
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] buildTupleAliasArray(List<SqmSelection<?>> selections) {
|
||||
if ( selections.size() == 1 ) {
|
||||
final SqmSelectableNode<?> selectableNode = selections.get(0).getSelectableNode();
|
||||
if ( selectableNode instanceof CompoundSelection<?> ) {
|
||||
final List<? extends JpaSelection<?>> selectionItems = selectableNode.getSelectionItems();
|
||||
final String[] elements = new String[ selectionItems.size() ];
|
||||
for ( int i = 0; i < selectionItems.size(); i++ ) {
|
||||
elements[i] = selectionItems.get( i ).getAlias();
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
else {
|
||||
return new String[] { selectableNode.getAlias() };
|
||||
}
|
||||
}
|
||||
else {
|
||||
final String[] elements = new String[ selections.size() ];
|
||||
for ( int i = 0; i < selections.size(); i++ ) {
|
||||
elements[i] = selections.get( i ).getAlias();
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
||||
protected static void validateCriteriaQuery(SqmQueryPart<?> queryPart) {
|
||||
if ( queryPart instanceof SqmQuerySpec<?> ) {
|
||||
final SqmQuerySpec<?> sqmQuerySpec = (SqmQuerySpec<?>) queryPart;
|
||||
final List<SqmSelection<?>> selections = sqmQuerySpec.getSelectClause().getSelections();
|
||||
if ( selections.isEmpty() ) {
|
||||
// make sure there is at least one root
|
||||
final List<SqmRoot<?>> sqmRoots = sqmQuerySpec.getFromClause().getRoots();
|
||||
if ( sqmRoots == null || sqmRoots.isEmpty() ) {
|
||||
throw new IllegalArgumentException( "Criteria did not define any query roots" );
|
||||
}
|
||||
// if there is a single root, use that as the selection
|
||||
if ( sqmRoots.size() == 1 ) {
|
||||
sqmQuerySpec.getSelectClause().add( sqmRoots.get( 0 ), null );
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException( "Criteria has multiple query roots" );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
final SqmQueryGroup<?> queryGroup = (SqmQueryGroup<?>) queryPart;
|
||||
for ( SqmQueryPart<?> part : queryGroup.getQueryParts() ) {
|
||||
validateCriteriaQuery( part );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static <T> HqlInterpretation<T> interpretation(
|
||||
NamedHqlQueryMementoImpl memento,
|
||||
Class<T> expectedResultType,
|
||||
SharedSessionContractImplementor session) {
|
||||
final QueryEngine queryEngine = session.getFactory().getQueryEngine();
|
||||
final QueryInterpretationCache interpretationCache = queryEngine.getInterpretationCache();
|
||||
final HqlInterpretation<T> interpretation = interpretationCache.resolveHqlInterpretation(
|
||||
memento.getHqlString(),
|
||||
expectedResultType,
|
||||
queryEngine.getHqlTranslator()
|
||||
);
|
||||
return interpretation;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ package org.hibernate.query.sqm.internal;
|
|||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -28,7 +27,6 @@ import org.hibernate.LockOptions;
|
|||
import org.hibernate.ScrollMode;
|
||||
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.generator.Generator;
|
||||
import org.hibernate.graph.GraphSemantic;
|
||||
|
@ -47,13 +45,11 @@ import org.hibernate.persister.entity.AbstractEntityPersister;
|
|||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
|
||||
import org.hibernate.query.Order;
|
||||
import org.hibernate.query.Page;
|
||||
import org.hibernate.query.Query;
|
||||
import org.hibernate.query.QueryParameter;
|
||||
import org.hibernate.query.ResultListTransformer;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.TupleTransformer;
|
||||
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
|
||||
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
||||
|
@ -61,7 +57,6 @@ import org.hibernate.query.hql.internal.QuerySplitter;
|
|||
import org.hibernate.query.hql.spi.SqmQueryImplementor;
|
||||
import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
|
||||
import org.hibernate.query.internal.ParameterMetadataImpl;
|
||||
import org.hibernate.query.internal.QueryParameterBindingsImpl;
|
||||
import org.hibernate.query.named.NamedQueryMemento;
|
||||
import org.hibernate.query.spi.DelegatingQueryOptions;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
|
@ -69,7 +64,6 @@ import org.hibernate.query.spi.HqlInterpretation;
|
|||
import org.hibernate.query.spi.MutableQueryOptions;
|
||||
import org.hibernate.query.spi.NonSelectQueryPlan;
|
||||
import org.hibernate.query.spi.ParameterMetadataImplementor;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.spi.QueryInterpretationCache;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.query.spi.QueryParameterBindings;
|
||||
|
@ -78,24 +72,20 @@ import org.hibernate.query.spi.SelectQueryPlan;
|
|||
import org.hibernate.query.sqm.SqmPathSource;
|
||||
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
|
||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||
import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
|
||||
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
|
||||
import org.hibernate.query.sqm.tree.insert.SqmValues;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||
import org.hibernate.query.sqm.tree.update.SqmAssignment;
|
||||
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
||||
import org.hibernate.sql.results.internal.TupleMetadata;
|
||||
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
||||
|
@ -126,7 +116,6 @@ import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.createInter
|
|||
import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.generateNonSelectKey;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.isSelect;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.verifyIsNonSelectStatement;
|
||||
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
|
||||
|
||||
/**
|
||||
* {@link Query} implementation based on an SQM
|
||||
|
@ -144,7 +133,7 @@ public class QuerySqmImpl<R>
|
|||
private final ParameterMetadataImplementor parameterMetadata;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
|
||||
private final QueryParameterBindingsImpl parameterBindings;
|
||||
private final QueryParameterBindings parameterBindings;
|
||||
|
||||
private final Class<R> resultType;
|
||||
private final TupleMetadata tupleMetadata;
|
||||
|
@ -156,29 +145,13 @@ public class QuerySqmImpl<R>
|
|||
NamedHqlQueryMementoImpl memento,
|
||||
Class<R> expectedResultType,
|
||||
SharedSessionContractImplementor session) {
|
||||
super( session );
|
||||
|
||||
this.hql = memento.getHqlString();
|
||||
this.resultType = expectedResultType;
|
||||
|
||||
final QueryEngine queryEngine = session.getFactory().getQueryEngine();
|
||||
final QueryInterpretationCache interpretationCache = queryEngine.getInterpretationCache();
|
||||
final HqlInterpretation<R> hqlInterpretation =
|
||||
interpretationCache.resolveHqlInterpretation( hql, expectedResultType, queryEngine.getHqlTranslator() );
|
||||
|
||||
this.sqm = hqlInterpretation.getSqmStatement();
|
||||
|
||||
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
||||
this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
|
||||
|
||||
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
|
||||
|
||||
validateStatement( sqm, resultType );
|
||||
setComment( hql );
|
||||
|
||||
|
||||
this(
|
||||
memento.getHqlString(),
|
||||
interpretation( memento, expectedResultType, session ),
|
||||
expectedResultType,
|
||||
session
|
||||
);
|
||||
applyOptions( memento );
|
||||
this.tupleMetadata = buildTupleMetadata( sqm, resultType );
|
||||
}
|
||||
|
||||
public QuerySqmImpl(
|
||||
|
@ -207,9 +180,16 @@ public class QuerySqmImpl<R>
|
|||
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
||||
this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
|
||||
|
||||
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
|
||||
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
|
||||
|
||||
validateStatement( sqm, resultType );
|
||||
if ( sqm instanceof SqmSelectStatement<?> ) {
|
||||
hqlInterpretation.validateResultType( resultType );
|
||||
}
|
||||
else {
|
||||
if ( resultType != null ) {
|
||||
throw new IllegalQueryOperationException( "Result type given for a non-SELECT Query", hql, null );
|
||||
}
|
||||
}
|
||||
setComment( hql );
|
||||
|
||||
this.tupleMetadata = buildTupleMetadata( sqm, resultType );
|
||||
|
@ -243,7 +223,7 @@ public class QuerySqmImpl<R>
|
|||
parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
|
||||
}
|
||||
|
||||
parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, producer.getFactory() );
|
||||
this.parameterBindings = parameterMetadata.createBindings( producer.getFactory() );
|
||||
|
||||
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
|
||||
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
|
||||
|
@ -251,7 +231,20 @@ public class QuerySqmImpl<R>
|
|||
bindCriteriaParameter((SqmJpaCriteriaParameterWrapper<?>) sqmParameter);
|
||||
}
|
||||
}
|
||||
validateStatement( sqm, expectedResultType );
|
||||
if ( sqm instanceof SqmSelectStatement<?> ) {
|
||||
final SqmSelectStatement<R> selectStatement = (SqmSelectStatement<R>) sqm;
|
||||
final SqmQueryPart<R> queryPart = selectStatement.getQueryPart();
|
||||
// For criteria queries, we have to validate the fetch structure here
|
||||
queryPart.validateQueryStructureAndFetchOwners();
|
||||
validateCriteriaQuery( queryPart );
|
||||
selectStatement.validateResultType( expectedResultType );
|
||||
}
|
||||
else {
|
||||
if ( expectedResultType != null ) {
|
||||
throw new IllegalQueryOperationException( "Result type given for a non-SELECT Query", hql, null );
|
||||
}
|
||||
( (AbstractSqmDmlStatement<?>) sqm ).validate( hql );
|
||||
}
|
||||
|
||||
resultType = expectedResultType;
|
||||
tupleMetadata = buildTupleMetadata( criteria, expectedResultType );
|
||||
|
@ -269,127 +262,6 @@ public class QuerySqmImpl<R>
|
|||
}
|
||||
}
|
||||
|
||||
private void validateStatement(SqmStatement<R> sqmStatement, Class<R> resultType) {
|
||||
if ( sqmStatement instanceof SqmSelectStatement<?> ) {
|
||||
final SqmQueryPart<R> queryPart = ( (SqmSelectStatement<R>) sqm ).getQueryPart();
|
||||
if ( hql == CRITERIA_HQL_STRING ) {
|
||||
// For criteria queries, we have to validate the fetch structure here
|
||||
queryPart.validateQueryStructureAndFetchOwners();
|
||||
}
|
||||
visitQueryReturnType( queryPart, resultType, getSessionFactory() );
|
||||
}
|
||||
else {
|
||||
if ( resultType != null ) {
|
||||
throw new IllegalQueryOperationException( "Result type given for a non-SELECT Query", hql, null );
|
||||
}
|
||||
if ( sqmStatement instanceof SqmUpdateStatement<?> ) {
|
||||
final SqmUpdateStatement<R> updateStatement = (SqmUpdateStatement<R>) sqmStatement;
|
||||
verifyImmutableEntityUpdate( hql, updateStatement, getSessionFactory() );
|
||||
if ( updateStatement.getSetClause() == null
|
||||
|| updateStatement.getSetClause().getAssignments().isEmpty() ) {
|
||||
throw new IllegalArgumentException( "No assignments specified as part of UPDATE criteria" );
|
||||
}
|
||||
verifyUpdateTypesMatch( hql, updateStatement );
|
||||
}
|
||||
else if ( sqmStatement instanceof SqmInsertStatement<?> ) {
|
||||
verifyInsertTypesMatch( hql, (SqmInsertStatement<R>) sqmStatement );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyImmutableEntityUpdate(
|
||||
String hqlString,
|
||||
SqmUpdateStatement<R> sqmStatement,
|
||||
SessionFactoryImplementor factory) {
|
||||
final EntityPersister persister =
|
||||
factory.getMappingMetamodel().getEntityDescriptor( sqmStatement.getTarget().getEntityName() );
|
||||
if ( !persister.isMutable() ) {
|
||||
final ImmutableEntityUpdateQueryHandlingMode mode =
|
||||
factory.getSessionFactoryOptions().getImmutableEntityUpdateQueryHandlingMode();
|
||||
final String querySpaces = Arrays.toString( persister.getQuerySpaces() );
|
||||
switch ( mode ) {
|
||||
case WARNING:
|
||||
LOG.immutableEntityUpdateQuery( hqlString, querySpaces );
|
||||
break;
|
||||
case EXCEPTION:
|
||||
throw new HibernateException( "The query [" + hqlString + "] attempts to update an immutable entity: "
|
||||
+ querySpaces );
|
||||
default:
|
||||
throw new UnsupportedOperationException( "The " + mode + " is not supported" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyUpdateTypesMatch(String hqlString, SqmUpdateStatement<R> sqmStatement) {
|
||||
final List<SqmAssignment<?>> assignments = sqmStatement.getSetClause().getAssignments();
|
||||
for ( int i = 0; i < assignments.size(); i++ ) {
|
||||
final SqmAssignment<?> assignment = assignments.get( i );
|
||||
final SqmPath<?> targetPath = assignment.getTargetPath();
|
||||
final SqmExpression<?> expression = assignment.getValue();
|
||||
assertAssignable( hqlString, targetPath, expression, getSessionFactory() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void verifyInsertTypesMatch(String hqlString, SqmInsertStatement<R> sqmStatement) {
|
||||
final List<SqmPath<?>> insertionTargetPaths = sqmStatement.getInsertionTargetPaths();
|
||||
if ( sqmStatement instanceof SqmInsertValuesStatement<?> ) {
|
||||
final SqmInsertValuesStatement<R> statement = (SqmInsertValuesStatement<R>) sqmStatement;
|
||||
for ( SqmValues sqmValues : statement.getValuesList() ) {
|
||||
verifyInsertTypesMatch( hqlString, insertionTargetPaths, sqmValues.getExpressions() );
|
||||
}
|
||||
}
|
||||
else {
|
||||
final SqmInsertSelectStatement<R> statement = (SqmInsertSelectStatement<R>) sqmStatement;
|
||||
final List<SqmSelectableNode<?>> selections =
|
||||
statement.getSelectQueryPart()
|
||||
.getFirstQuerySpec()
|
||||
.getSelectClause()
|
||||
.getSelectionItems();
|
||||
verifyInsertTypesMatch( hqlString, insertionTargetPaths, selections );
|
||||
statement.getSelectQueryPart().validateQueryStructureAndFetchOwners();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyInsertTypesMatch(
|
||||
String hqlString,
|
||||
List<SqmPath<?>> insertionTargetPaths,
|
||||
List<? extends SqmTypedNode<?>> expressions) {
|
||||
final int size = insertionTargetPaths.size();
|
||||
final int expressionsSize = expressions.size();
|
||||
if ( size != expressionsSize ) {
|
||||
throw new SemanticException(
|
||||
String.format(
|
||||
"Expected insert attribute count [%d] did not match Query selection count [%d]",
|
||||
size,
|
||||
expressionsSize
|
||||
),
|
||||
hqlString,
|
||||
null
|
||||
);
|
||||
}
|
||||
for ( int i = 0; i < expressionsSize; i++ ) {
|
||||
final SqmTypedNode<?> expression = expressions.get( i );
|
||||
final SqmPath<?> targetPath = insertionTargetPaths.get(i);
|
||||
assertAssignable( hqlString, targetPath, expression, getSessionFactory() );
|
||||
// if ( expression.getNodeJavaType() == null ) {
|
||||
// continue;
|
||||
// }
|
||||
// if ( insertionTargetPaths.get( i ).getJavaTypeDescriptor() != expression.getNodeJavaType() ) {
|
||||
// throw new SemanticException(
|
||||
// String.format(
|
||||
// "Expected insert attribute type [%s] did not match Query selection type [%s] at selection index [%d]",
|
||||
// insertionTargetPaths.get( i ).getJavaTypeDescriptor().getTypeName(),
|
||||
// expression.getNodeJavaType().getTypeName(),
|
||||
// i
|
||||
// ),
|
||||
// hqlString,
|
||||
// null
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TupleMetadata getTupleMetadata() {
|
||||
return tupleMetadata;
|
||||
|
|
|
@ -31,19 +31,15 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|||
import org.hibernate.graph.spi.AppliedGraph;
|
||||
import org.hibernate.internal.util.collections.IdentitySet;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.Page;
|
||||
import org.hibernate.query.QueryParameter;
|
||||
import org.hibernate.query.SelectionQuery;
|
||||
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
|
||||
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
||||
import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
|
||||
import org.hibernate.query.internal.ParameterMetadataImpl;
|
||||
import org.hibernate.query.internal.QueryParameterBindingsImpl;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
import org.hibernate.query.spi.HqlInterpretation;
|
||||
import org.hibernate.query.spi.MutableQueryOptions;
|
||||
import org.hibernate.query.spi.ParameterMetadataImplementor;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.spi.QueryInterpretationCache;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.query.spi.QueryParameterBindings;
|
||||
|
@ -56,6 +52,7 @@ import org.hibernate.query.sqm.tree.SqmCopyContext;
|
|||
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
||||
import org.hibernate.sql.results.internal.TupleMetadata;
|
||||
|
@ -86,7 +83,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
|||
|
||||
private final ParameterMetadataImplementor parameterMetadata;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
private final QueryParameterBindingsImpl parameterBindings;
|
||||
private final QueryParameterBindings parameterBindings;
|
||||
|
||||
private final Class<R> expectedResultType;
|
||||
private final Class<?> resultType;
|
||||
|
@ -100,76 +97,34 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
|||
super( session );
|
||||
this.hql = hql;
|
||||
|
||||
SqmUtil.verifyIsSelectStatement( hqlInterpretation.getSqmStatement(), hql );
|
||||
this.sqm = (SqmSelectStatement<R>) hqlInterpretation.getSqmStatement();
|
||||
|
||||
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
||||
this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
|
||||
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
|
||||
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
|
||||
|
||||
|
||||
this.expectedResultType = expectedResultType;
|
||||
visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
|
||||
this.resultType = determineResultType( sqm );
|
||||
this.resultType = determineResultType( sqm, expectedResultType );
|
||||
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
|
||||
|
||||
hqlInterpretation.validateResultType( resultType );
|
||||
setComment( hql );
|
||||
}
|
||||
|
||||
private Class<?> determineResultType(SqmSelectStatement<?> sqm) {
|
||||
final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections();
|
||||
if ( selections.size() == 1 ) {
|
||||
if ( Object[].class.equals( expectedResultType ) ) {
|
||||
// for JPA compatibility
|
||||
return Object[].class;
|
||||
}
|
||||
else {
|
||||
final SqmSelection<?> selection = selections.get(0);
|
||||
if ( isSelectionAssignableToResultType( selection, expectedResultType ) ) {
|
||||
return selection.getNodeJavaType().getJavaTypeClass();
|
||||
}
|
||||
else {
|
||||
// let's assume there's some
|
||||
// way to instantiate it
|
||||
return expectedResultType;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( expectedResultType != null ) {
|
||||
// assume we can repackage the tuple as
|
||||
// the given type (worry about how later)
|
||||
return expectedResultType;
|
||||
}
|
||||
else {
|
||||
// for JPA compatibility
|
||||
return Object[].class;
|
||||
}
|
||||
}
|
||||
|
||||
public SqmSelectionQueryImpl(
|
||||
NamedHqlQueryMementoImpl memento,
|
||||
Class<R> resultType,
|
||||
SharedSessionContractImplementor session) {
|
||||
super( session );
|
||||
this.hql = memento.getHqlString();
|
||||
this.expectedResultType = resultType;
|
||||
this.resultType = resultType;
|
||||
this(
|
||||
memento.getHqlString(),
|
||||
interpretation( memento, resultType, session ),
|
||||
resultType,
|
||||
session
|
||||
);
|
||||
|
||||
final QueryEngine queryEngine = session.getFactory().getQueryEngine();
|
||||
final QueryInterpretationCache interpretationCache = queryEngine.getInterpretationCache();
|
||||
final HqlInterpretation<R> hqlInterpretation =
|
||||
interpretationCache.resolveHqlInterpretation( hql, resultType, queryEngine.getHqlTranslator() );
|
||||
|
||||
SqmUtil.verifyIsSelectStatement( hqlInterpretation.getSqmStatement(), hql );
|
||||
this.sqm = (SqmSelectStatement<R>) hqlInterpretation.getSqmStatement();
|
||||
|
||||
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
||||
this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
|
||||
|
||||
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
|
||||
|
||||
setComment( hql );
|
||||
applyOptions( memento );
|
||||
|
||||
this.tupleMetadata = buildTupleMetadata( sqm, resultType );
|
||||
}
|
||||
|
||||
public SqmSelectionQueryImpl(
|
||||
|
@ -201,7 +156,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
|||
? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() )
|
||||
: ParameterMetadataImpl.EMPTY;
|
||||
|
||||
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
|
||||
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
|
||||
|
||||
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
|
||||
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
|
||||
|
@ -211,14 +166,49 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
|||
}
|
||||
|
||||
this.expectedResultType = expectedResultType;
|
||||
this.resultType = determineResultType( sqm );
|
||||
visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
|
||||
this.resultType = determineResultType( sqm, expectedResultType );
|
||||
|
||||
final SqmQueryPart<R> queryPart = sqm.getQueryPart();
|
||||
// For criteria queries, we have to validate the fetch structure here
|
||||
queryPart.validateQueryStructureAndFetchOwners();
|
||||
validateCriteriaQuery( queryPart );
|
||||
sqm.validateResultType( resultType );
|
||||
|
||||
setComment( hql );
|
||||
|
||||
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
|
||||
}
|
||||
|
||||
private static Class<?> determineResultType(SqmSelectStatement<?> sqm, Class<?> expectedResultType) {
|
||||
final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections();
|
||||
if ( selections.size() == 1 ) {
|
||||
if ( Object[].class.equals( expectedResultType ) ) {
|
||||
// for JPA compatibility
|
||||
return Object[].class;
|
||||
}
|
||||
else {
|
||||
final SqmSelection<?> selection = selections.get(0);
|
||||
if ( isSelectionAssignableToResultType( selection, expectedResultType ) ) {
|
||||
return selection.getNodeJavaType().getJavaTypeClass();
|
||||
}
|
||||
else {
|
||||
// let's assume there's some
|
||||
// way to instantiate it
|
||||
return expectedResultType;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( expectedResultType != null ) {
|
||||
// assume we can repackage the tuple as
|
||||
// the given type (worry about how later)
|
||||
return expectedResultType;
|
||||
}
|
||||
else {
|
||||
// for JPA compatibility
|
||||
return Object[].class;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper<T> sqmParameter) {
|
||||
final JpaCriteriaParameter<T> jpaCriteriaParameter = sqmParameter.getJpaCriteriaParameter();
|
||||
final T value = jpaCriteriaParameter.getValue();
|
||||
|
|
|
@ -6,16 +6,17 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.internal;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.function.Function;
|
||||
|
@ -36,10 +37,18 @@ import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
|||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.model.domain.BasicDomainType;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
|
||||
import org.hibernate.metamodel.model.domain.SimpleDomainType;
|
||||
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.IllegalSelectQueryException;
|
||||
import org.hibernate.query.Order;
|
||||
import org.hibernate.query.QueryTypeMismatchException;
|
||||
import org.hibernate.query.criteria.JpaOrder;
|
||||
import org.hibernate.query.criteria.JpaSelection;
|
||||
import org.hibernate.query.spi.QueryParameterBinding;
|
||||
import org.hibernate.query.spi.QueryParameterBindings;
|
||||
import org.hibernate.query.spi.QueryParameterImplementor;
|
||||
|
@ -66,6 +75,7 @@ import org.hibernate.query.sqm.tree.from.SqmJoin;
|
|||
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryGroup;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
|
@ -84,10 +94,15 @@ import org.hibernate.sql.exec.spi.JdbcParametersList;
|
|||
import org.hibernate.type.JavaObjectType;
|
||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.internal.BasicTypeImpl;
|
||||
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.Tuple;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
|
||||
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
|
||||
|
@ -857,4 +872,247 @@ public class SqmUtil {
|
|||
return jpaCriteriaParamResolutions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate that the specified query return type is valid (i.e. the user
|
||||
* did not pass {@code Integer.class} when the selection is an entity)
|
||||
*/
|
||||
public static void validateQueryReturnType(SqmQueryPart<?> queryPart, @Nullable Class<?> expectedResultType) {
|
||||
if ( expectedResultType != null && !isResultTypeAlwaysAllowed( expectedResultType ) ) {
|
||||
// the result-class is always safe to use (Object, ...)
|
||||
checkQueryReturnType( queryPart, expectedResultType );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #validateQueryReturnType(SqmQueryPart, Class)} but does not check if {@link #isResultTypeAlwaysAllowed(Class)}.
|
||||
*/
|
||||
public static void checkQueryReturnType(SqmQueryPart<?> queryPart, Class<?> expectedResultType) {
|
||||
if ( queryPart instanceof SqmQuerySpec<?> ) {
|
||||
checkQueryReturnType( (SqmQuerySpec<?>) queryPart, expectedResultType );
|
||||
}
|
||||
else {
|
||||
final SqmQueryGroup<?> queryGroup = (SqmQueryGroup<?>) queryPart;
|
||||
for ( SqmQueryPart<?> sqmQueryPart : queryGroup.getQueryParts() ) {
|
||||
checkQueryReturnType( sqmQueryPart, expectedResultType );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkQueryReturnType(SqmQuerySpec<?> querySpec, Class<?> expectedResultClass) {
|
||||
final SessionFactoryImplementor sessionFactory = querySpec.nodeBuilder().getSessionFactory();
|
||||
final List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections();
|
||||
if ( selections == null || selections.isEmpty() ) {
|
||||
// make sure there is at least one root
|
||||
final List<SqmRoot<?>> sqmRoots = querySpec.getFromClause().getRoots();
|
||||
if ( sqmRoots == null || sqmRoots.isEmpty() ) {
|
||||
throw new IllegalArgumentException( "Criteria did not define any query roots" );
|
||||
}
|
||||
// if there is a single root, use that as the selection
|
||||
if ( sqmRoots.size() == 1 ) {
|
||||
verifySingularSelectionType( expectedResultClass, sessionFactory, sqmRoots.get( 0 ) );
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException( "Criteria has multiple query roots" );
|
||||
}
|
||||
}
|
||||
else if ( selections.size() == 1 ) {
|
||||
final SqmSelection<?> sqmSelection = selections.get( 0 );
|
||||
final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
|
||||
if ( selectableNode.isCompoundSelection() ) {
|
||||
final Class<?> expectedSelectItemType = expectedResultClass.isArray()
|
||||
? expectedResultClass.getComponentType()
|
||||
: expectedResultClass;
|
||||
for ( JpaSelection<?> selection : selectableNode.getSelectionItems() ) {
|
||||
verifySelectionType( expectedSelectItemType, sessionFactory, (SqmSelectableNode<?>) selection );
|
||||
}
|
||||
}
|
||||
else {
|
||||
verifySingularSelectionType( expectedResultClass, sessionFactory, sqmSelection.getSelectableNode() );
|
||||
}
|
||||
}
|
||||
else if ( expectedResultClass.isArray() ) {
|
||||
final Class<?> componentType = expectedResultClass.getComponentType();
|
||||
for ( SqmSelection<?> selection : selections ) {
|
||||
verifySelectionType( componentType, sessionFactory, selection.getSelectableNode() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case for a single, non-compound selection-item. It is essentially
|
||||
* a special case of {@linkplain #verifySelectionType} which additionally
|
||||
* handles the case where the type of the selection-item can be used to
|
||||
* instantiate the result-class (result-class has a matching constructor).
|
||||
*
|
||||
* @apiNote We don't want to hoist this into {@linkplain #verifySelectionType}
|
||||
* itself because this can only happen for the root non-compound case, and we
|
||||
* want to avoid the try/catch otherwise
|
||||
*/
|
||||
private static void verifySingularSelectionType(
|
||||
Class<?> expectedResultClass,
|
||||
SessionFactoryImplementor sessionFactory,
|
||||
SqmSelectableNode<?> selectableNode) {
|
||||
try {
|
||||
verifySelectionType( expectedResultClass, sessionFactory, selectableNode );
|
||||
}
|
||||
catch (QueryTypeMismatchException mismatchException) {
|
||||
// Check for special case of a single selection item and implicit instantiation.
|
||||
// See if the selected type can be used to instantiate the expected-type
|
||||
final JavaType<?> javaTypeDescriptor = selectableNode.getJavaTypeDescriptor();
|
||||
if ( javaTypeDescriptor != null ) {
|
||||
final Class<?> selectedJavaType = javaTypeDescriptor.getJavaTypeClass();
|
||||
// ignore the exception if the expected type has a constructor accepting the selected item type
|
||||
if ( hasMatchingConstructor( expectedResultClass, selectedJavaType ) ) {
|
||||
// ignore it
|
||||
}
|
||||
else {
|
||||
throw mismatchException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> boolean hasMatchingConstructor(Class<T> expectedResultClass, Class<?> selectedJavaType) {
|
||||
try {
|
||||
expectedResultClass.getDeclaredConstructor( selectedJavaType );
|
||||
return true;
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> void verifySelectionType(
|
||||
Class<T> expectedResultClass,
|
||||
SessionFactoryImplementor sessionFactory,
|
||||
SqmSelectableNode<?> selection) {
|
||||
// special case for parameters in the select list
|
||||
if ( selection instanceof SqmParameter ) {
|
||||
final SqmParameter<?> sqmParameter = (SqmParameter<?>) selection;
|
||||
final SqmExpressible<?> nodeType = sqmParameter.getExpressible();
|
||||
// we may not yet know a selection type
|
||||
if ( nodeType == null || nodeType.getExpressibleJavaType() == null ) {
|
||||
// we can't verify the result type up front
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
|
||||
verifyResultType( expectedResultClass, selection.getExpressible() );
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isResultTypeAlwaysAllowed(Class<?> expectedResultClass) {
|
||||
return expectedResultClass == null
|
||||
|| expectedResultClass == Object.class
|
||||
|| expectedResultClass == Object[].class
|
||||
|| expectedResultClass == List.class
|
||||
|| expectedResultClass == Map.class
|
||||
|| expectedResultClass == Tuple.class;
|
||||
}
|
||||
|
||||
protected static void verifyResultType(Class<?> resultClass, @Nullable SqmExpressible<?> selectionExpressible) {
|
||||
if ( selectionExpressible == null ) {
|
||||
// nothing we can validate
|
||||
return;
|
||||
}
|
||||
|
||||
final JavaType<?> selectionExpressibleJavaType = selectionExpressible.getExpressibleJavaType();
|
||||
assert selectionExpressibleJavaType != null;
|
||||
|
||||
final Class<?> selectionExpressibleJavaTypeClass = selectionExpressibleJavaType.getJavaTypeClass();
|
||||
if ( selectionExpressibleJavaTypeClass == Object.class ) {
|
||||
|
||||
}
|
||||
if ( selectionExpressibleJavaTypeClass != Object.class ) {
|
||||
// performs a series of opt-out checks for validity... each if branch and return indicates a valid case
|
||||
if ( resultClass.isAssignableFrom( selectionExpressibleJavaTypeClass ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( selectionExpressibleJavaType instanceof PrimitiveJavaType ) {
|
||||
final PrimitiveJavaType<?> primitiveJavaType = (PrimitiveJavaType<?>) selectionExpressibleJavaType;
|
||||
if ( primitiveJavaType.getPrimitiveClass() == resultClass ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isMatchingDateType( selectionExpressibleJavaTypeClass, resultClass, selectionExpressible ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isEntityIdType( selectionExpressible, resultClass ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
throwQueryTypeMismatchException( resultClass, selectionExpressible );
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEntityIdType(SqmExpressible<?> selectionExpressible, Class<?> resultClass) {
|
||||
if ( selectionExpressible instanceof IdentifiableDomainType ) {
|
||||
final IdentifiableDomainType<?> identifiableDomainType = (IdentifiableDomainType<?>) selectionExpressible;
|
||||
final SimpleDomainType<?> idType = identifiableDomainType.getIdType();
|
||||
return resultClass.isAssignableFrom( idType.getBindableJavaType() );
|
||||
}
|
||||
else if ( selectionExpressible instanceof EntitySqmPathSource ) {
|
||||
final EntitySqmPathSource<?> entityPath = (EntitySqmPathSource<?>) selectionExpressible;
|
||||
final EntityDomainType<?> entityType = entityPath.getSqmPathType();
|
||||
final SimpleDomainType<?> idType = entityType.getIdType();
|
||||
return resultClass.isAssignableFrom( idType.getBindableJavaType() );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special case for date because we always report java.util.Date as expression type
|
||||
// But the expected resultClass could be a subtype of that, so we need to check the JdbcType
|
||||
private static boolean isMatchingDateType(
|
||||
Class<?> javaTypeClass,
|
||||
Class<?> resultClass,
|
||||
SqmExpressible<?> sqmExpressible) {
|
||||
return javaTypeClass == Date.class
|
||||
&& isMatchingDateJdbcType( resultClass, getJdbcType( sqmExpressible ) );
|
||||
}
|
||||
|
||||
private static JdbcType getJdbcType(SqmExpressible<?> sqmExpressible) {
|
||||
if ( sqmExpressible instanceof BasicDomainType<?> ) {
|
||||
return ( (BasicDomainType<?>) sqmExpressible).getJdbcType();
|
||||
}
|
||||
else if ( sqmExpressible instanceof SqmPathSource<?> ) {
|
||||
final SqmPathSource<?> pathSource = (SqmPathSource<?>) sqmExpressible;
|
||||
final DomainType<?> domainType = pathSource.getSqmPathType();
|
||||
if ( domainType instanceof BasicDomainType<?> ) {
|
||||
return ( (BasicDomainType<?>) domainType ).getJdbcType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isMatchingDateJdbcType(Class<?> resultClass, JdbcType jdbcType) {
|
||||
if ( jdbcType != null ) {
|
||||
switch ( jdbcType.getDefaultSqlTypeCode() ) {
|
||||
case Types.DATE:
|
||||
return resultClass.isAssignableFrom( java.sql.Date.class );
|
||||
case Types.TIME:
|
||||
return resultClass.isAssignableFrom( java.sql.Time.class );
|
||||
case Types.TIMESTAMP:
|
||||
return resultClass.isAssignableFrom( java.sql.Timestamp.class );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void throwQueryTypeMismatchException(Class<?> resultClass, SqmExpressible<?> sqmExpressible) {
|
||||
throw new QueryTypeMismatchException( String.format(
|
||||
"Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array",
|
||||
resultClass.getName(),
|
||||
sqmExpressible.getTypeName()
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
|
|||
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
|
||||
|
||||
import jakarta.persistence.criteria.AbstractQuery;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -72,6 +73,8 @@ public abstract class AbstractSqmDmlStatement<E>
|
|||
}
|
||||
}
|
||||
|
||||
public abstract void validate(@Nullable String hql);
|
||||
|
||||
@Override
|
||||
public Collection<SqmCteStatement<?>> getCteStatements() {
|
||||
return cteStatements.values();
|
||||
|
|
|
@ -20,10 +20,10 @@ import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.from.SqmFromClause;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
|
||||
|
||||
import jakarta.persistence.criteria.Expression;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -103,6 +103,11 @@ public class SqmDeleteStatement<T>
|
|||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(@Nullable String hql) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmDeleteStatement<T> where(Expression<Boolean> restriction) {
|
||||
setWhere( restriction );
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.criteria.JpaConflictClause;
|
||||
import org.hibernate.query.criteria.JpaCriteriaInsert;
|
||||
|
@ -22,6 +23,7 @@ import org.hibernate.query.sqm.NodeBuilder;
|
|||
import org.hibernate.query.sqm.SqmQuerySource;
|
||||
import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
|
||||
|
@ -31,6 +33,8 @@ import org.hibernate.query.sqm.tree.from.SqmRoot;
|
|||
import jakarta.persistence.criteria.Path;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
|
||||
|
||||
/**
|
||||
* Convenience base class for InsertSqmStatement implementations.
|
||||
*
|
||||
|
@ -85,6 +89,45 @@ public abstract class AbstractSqmInsertStatement<T> extends AbstractSqmDmlStatem
|
|||
}
|
||||
}
|
||||
|
||||
protected void verifyInsertTypesMatch(
|
||||
List<SqmPath<?>> insertionTargetPaths,
|
||||
List<? extends SqmTypedNode<?>> expressions) {
|
||||
final int size = insertionTargetPaths.size();
|
||||
final int expressionsSize = expressions.size();
|
||||
if ( size != expressionsSize ) {
|
||||
throw new SemanticException(
|
||||
String.format(
|
||||
"Expected insert attribute count [%d] did not match Query selection count [%d]",
|
||||
size,
|
||||
expressionsSize
|
||||
),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
final SessionFactoryImplementor factory = nodeBuilder().getSessionFactory();
|
||||
for ( int i = 0; i < expressionsSize; i++ ) {
|
||||
final SqmTypedNode<?> expression = expressions.get( i );
|
||||
final SqmPath<?> targetPath = insertionTargetPaths.get(i);
|
||||
assertAssignable( null, targetPath, expression, factory );
|
||||
// if ( expression.getNodeJavaType() == null ) {
|
||||
// continue;
|
||||
// }
|
||||
// if ( insertionTargetPaths.get( i ).getJavaTypeDescriptor() != expression.getNodeJavaType() ) {
|
||||
// throw new SemanticException(
|
||||
// String.format(
|
||||
// "Expected insert attribute type [%s] did not match Query selection type [%s] at selection index [%d]",
|
||||
// insertionTargetPaths.get( i ).getJavaTypeDescriptor().getTypeName(),
|
||||
// expression.getNodeJavaType().getTypeName(),
|
||||
// i
|
||||
// ),
|
||||
// hqlString,
|
||||
// null
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTarget(JpaRoot<T> root) {
|
||||
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
||||
|
|
|
@ -25,10 +25,12 @@ import org.hibernate.query.sqm.tree.from.SqmRoot;
|
|||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||
|
||||
import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -90,6 +92,17 @@ public class SqmInsertSelectStatement<T> extends AbstractSqmInsertStatement<T> i
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(@Nullable String hql) {
|
||||
final List<SqmPath<?>> insertionTargetPaths = getInsertionTargetPaths();
|
||||
final List<SqmSelectableNode<?>> selections = getSelectQueryPart()
|
||||
.getFirstQuerySpec()
|
||||
.getSelectClause()
|
||||
.getSelectionItems();
|
||||
verifyInsertTypesMatch( insertionTargetPaths, selections );
|
||||
getSelectQueryPart().validateQueryStructureAndFetchOwners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmInsertSelectStatement<T> select(CriteriaQuery<Tuple> criteriaQuery) {
|
||||
final SqmSelectStatement<Tuple> selectStatement = (SqmSelectStatement<Tuple>) criteriaQuery;
|
||||
|
|
|
@ -113,6 +113,14 @@ public class SqmInsertValuesStatement<T> extends AbstractSqmInsertStatement<T> i
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(@Nullable String hql) {
|
||||
final List<SqmPath<?>> insertionTargetPaths = getInsertionTargetPaths();
|
||||
for ( SqmValues sqmValues : getValuesList() ) {
|
||||
verifyInsertTypesMatch( insertionTargetPaths, sqmValues.getExpressions() );
|
||||
}
|
||||
}
|
||||
|
||||
public List<SqmValues> getValuesList() {
|
||||
return valuesList == null
|
||||
? Collections.emptyList()
|
||||
|
|
|
@ -149,6 +149,10 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
return statement;
|
||||
}
|
||||
|
||||
public void validateResultType(Class<?> resultType) {
|
||||
SqmUtil.validateQueryReturnType( getQueryPart(), resultType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmQuerySource getQuerySource() {
|
||||
return querySource;
|
||||
|
|
|
@ -6,9 +6,17 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.tree.update;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.criteria.JpaCriteriaUpdate;
|
||||
import org.hibernate.query.criteria.JpaRoot;
|
||||
|
@ -20,7 +28,6 @@ import org.hibernate.query.sqm.tree.AbstractSqmRestrictedDmlStatement;
|
|||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
|
||||
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
@ -32,6 +39,7 @@ import jakarta.persistence.criteria.Expression;
|
|||
import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.metamodel.SingularAttribute;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
|
||||
|
||||
|
@ -41,6 +49,9 @@ import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
|
|||
public class SqmUpdateStatement<T>
|
||||
extends AbstractSqmRestrictedDmlStatement<T>
|
||||
implements SqmDeleteOrUpdateStatement<T>, JpaCriteriaUpdate<T> {
|
||||
|
||||
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( SqmUpdateStatement.class );
|
||||
|
||||
private boolean versioned;
|
||||
private SqmSetClause setClause;
|
||||
|
||||
|
@ -110,6 +121,46 @@ public class SqmUpdateStatement<T>
|
|||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(@Nullable String hql) {
|
||||
verifyImmutableEntityUpdate( hql );
|
||||
if ( getSetClause() == null || getSetClause().getAssignments().isEmpty() ) {
|
||||
throw new IllegalArgumentException( "No assignments specified as part of UPDATE criteria" );
|
||||
}
|
||||
verifyUpdateTypesMatch();
|
||||
}
|
||||
|
||||
private void verifyImmutableEntityUpdate(String hql) {
|
||||
final SessionFactoryImplementor factory = nodeBuilder().getSessionFactory();
|
||||
final EntityPersister persister =
|
||||
factory.getMappingMetamodel().getEntityDescriptor( getTarget().getEntityName() );
|
||||
if ( !persister.isMutable() ) {
|
||||
final ImmutableEntityUpdateQueryHandlingMode mode =
|
||||
factory.getSessionFactoryOptions().getImmutableEntityUpdateQueryHandlingMode();
|
||||
final String querySpaces = Arrays.toString( persister.getQuerySpaces() );
|
||||
switch ( mode ) {
|
||||
case WARNING:
|
||||
LOG.immutableEntityUpdateQuery( hql, querySpaces );
|
||||
break;
|
||||
case EXCEPTION:
|
||||
throw new HibernateException( "The query attempts to update an immutable entity: " + querySpaces );
|
||||
default:
|
||||
throw new UnsupportedOperationException( "The " + mode + " is not supported" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyUpdateTypesMatch() {
|
||||
final SessionFactoryImplementor factory = nodeBuilder().getSessionFactory();
|
||||
final List<SqmAssignment<?>> assignments = getSetClause().getAssignments();
|
||||
for ( int i = 0; i < assignments.size(); i++ ) {
|
||||
final SqmAssignment<?> assignment = assignments.get( i );
|
||||
final SqmPath<?> targetPath = assignment.getTargetPath();
|
||||
final SqmExpression<?> expression = assignment.getValue();
|
||||
assertAssignable( null, targetPath, expression, factory );
|
||||
}
|
||||
}
|
||||
|
||||
public SqmSetClause getSetClause() {
|
||||
return setClause;
|
||||
}
|
||||
|
|
|
@ -57,18 +57,14 @@ public class ImmutableEntityUpdateQueryHandlingModeExceptionTest extends BaseNon
|
|||
|
||||
try {
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
session.createQuery(
|
||||
"update Country " +
|
||||
"set name = :name" )
|
||||
.setParameter( "name", "N/A" )
|
||||
.executeUpdate();
|
||||
session.createQuery("update Country set name = :name" );
|
||||
} );
|
||||
fail("Should throw PersistenceException");
|
||||
}
|
||||
catch (PersistenceException e) {
|
||||
assertTrue( e instanceof HibernateException );
|
||||
assertEquals(
|
||||
"The query [update Country set name = :name] attempts to update an immutable entity: [Country]",
|
||||
"Error interpreting query [The query attempts to update an immutable entity: [Country]] [update Country set name = :name]",
|
||||
e.getMessage()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
package org.hibernate.orm.test.annotations.immutable;
|
||||
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.query.sqm.internal.QuerySqmImpl;
|
||||
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||
|
@ -30,7 +30,7 @@ public class ImmutableEntityUpdateQueryHandlingModeWarningTest extends BaseNonCo
|
|||
|
||||
@Rule
|
||||
public LoggerInspectionRule logInspection = new LoggerInspectionRule(
|
||||
Logger.getMessageLogger( CoreMessageLogger.class, QuerySqmImpl.class.getName() ) );
|
||||
Logger.getMessageLogger( CoreMessageLogger.class, SqmUpdateStatement.class.getName() ) );
|
||||
|
||||
@Override
|
||||
protected Class[] getAnnotatedClasses() {
|
||||
|
|
Loading…
Reference in New Issue