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 );
|
queryExpressionContext.accept( this );
|
||||||
|
|
||||||
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
|
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
|
||||||
|
insertStatement.validate( query );
|
||||||
return insertStatement;
|
return insertStatement;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -613,6 +614,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
|
|
||||||
insertStatement.values( valuesList );
|
insertStatement.values( valuesList );
|
||||||
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
|
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
|
||||||
|
insertStatement.validate( query );
|
||||||
return insertStatement;
|
return insertStatement;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -684,6 +686,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
updateStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
|
updateStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStatement.validate( query );
|
||||||
return updateStatement;
|
return updateStatement;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.query.spi.SelectQueryPlan;
|
||||||
import org.hibernate.query.sql.spi.ParameterInterpretation;
|
import org.hibernate.query.sql.spi.ParameterInterpretation;
|
||||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||||
|
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +73,7 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation
|
||||||
final DomainParameterXref domainParameterXref;
|
final DomainParameterXref domainParameterXref;
|
||||||
final ParameterMetadataImplementor parameterMetadata;
|
final ParameterMetadataImplementor parameterMetadata;
|
||||||
if ( sqmStatement.getSqmParameters().isEmpty() ) {
|
if ( sqmStatement.getSqmParameters().isEmpty() ) {
|
||||||
domainParameterXref = DomainParameterXref.empty();
|
domainParameterXref = DomainParameterXref.EMPTY;
|
||||||
parameterMetadata = ParameterMetadataImpl.EMPTY;
|
parameterMetadata = ParameterMetadataImpl.EMPTY;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -101,6 +102,12 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation
|
||||||
public DomainParameterXref getDomainParameterXref() {
|
public DomainParameterXref getDomainParameterXref() {
|
||||||
return domainParameterXref;
|
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;
|
final DomainParameterXref domainParameterXref;
|
||||||
|
|
||||||
if ( sqmStatement.getSqmParameters().isEmpty() ) {
|
if ( sqmStatement.getSqmParameters().isEmpty() ) {
|
||||||
domainParameterXref = DomainParameterXref.empty();
|
domainParameterXref = DomainParameterXref.EMPTY;
|
||||||
parameterMetadata = ParameterMetadataImpl.EMPTY;
|
parameterMetadata = ParameterMetadataImpl.EMPTY;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.query.spi;
|
package org.hibernate.query.spi;
|
||||||
|
|
||||||
import java.sql.Types;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -33,40 +32,14 @@ import org.hibernate.graph.GraphSemantic;
|
||||||
import org.hibernate.graph.spi.AppliedGraph;
|
import org.hibernate.graph.spi.AppliedGraph;
|
||||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||||
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
|
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.BindableType;
|
||||||
import org.hibernate.query.IllegalQueryOperationException;
|
import org.hibernate.query.IllegalQueryOperationException;
|
||||||
import org.hibernate.query.QueryParameter;
|
import org.hibernate.query.QueryParameter;
|
||||||
import org.hibernate.query.QueryTypeMismatchException;
|
|
||||||
import org.hibernate.query.SelectionQuery;
|
import org.hibernate.query.SelectionQuery;
|
||||||
import org.hibernate.query.criteria.JpaSelection;
|
|
||||||
import org.hibernate.query.internal.ScrollableResultsIterator;
|
import org.hibernate.query.internal.ScrollableResultsIterator;
|
||||||
import org.hibernate.query.named.NamedQueryMemento;
|
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.internal.CallbackImpl;
|
||||||
import org.hibernate.sql.exec.spi.Callback;
|
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.CacheRetrieveMode;
|
||||||
import jakarta.persistence.CacheStoreMode;
|
import jakarta.persistence.CacheStoreMode;
|
||||||
|
@ -76,10 +49,6 @@ import jakarta.persistence.LockModeType;
|
||||||
import jakarta.persistence.NoResultException;
|
import jakarta.persistence.NoResultException;
|
||||||
import jakarta.persistence.Parameter;
|
import jakarta.persistence.Parameter;
|
||||||
import jakarta.persistence.TemporalType;
|
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 java.util.Spliterators.spliteratorUnknownSize;
|
||||||
import static org.hibernate.CacheMode.fromJpaModes;
|
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_FETCH_SIZE;
|
||||||
import static org.hibernate.jpa.HibernateHints.HINT_FOLLOW_ON_LOCKING;
|
import static org.hibernate.jpa.HibernateHints.HINT_FOLLOW_ON_LOCKING;
|
||||||
import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY;
|
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
|
* @author Steve Ebersole
|
||||||
|
@ -114,110 +81,6 @@ public abstract class AbstractSelectionQuery<R>
|
||||||
super( session );
|
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) {
|
protected void applyOptions(NamedQueryMemento memento) {
|
||||||
if ( memento.getHints() != null ) {
|
if ( memento.getHints() != null ) {
|
||||||
memento.getHints().forEach( this::applyHint );
|
memento.getHints().forEach( this::applyHint );
|
||||||
|
@ -258,265 +121,6 @@ public abstract class AbstractSelectionQuery<R>
|
||||||
|
|
||||||
protected abstract String getQueryString();
|
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
|
// execution
|
||||||
|
|
|
@ -20,4 +20,7 @@ public interface HqlInterpretation<R> {
|
||||||
ParameterMetadataImplementor getParameterMetadata();
|
ParameterMetadataImplementor getParameterMetadata();
|
||||||
|
|
||||||
DomainParameterXref getDomainParameterXref();
|
DomainParameterXref getDomainParameterXref();
|
||||||
|
|
||||||
|
void validateResultType(Class<?> resultType);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,12 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.query.spi;
|
package org.hibernate.query.spi;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
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.SqmStatement;
|
||||||
|
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
@ -16,6 +20,7 @@ public class SimpleHqlInterpretationImpl<R> implements HqlInterpretation<R> {
|
||||||
private final SqmStatement<R> sqmStatement;
|
private final SqmStatement<R> sqmStatement;
|
||||||
private final ParameterMetadataImplementor parameterMetadata;
|
private final ParameterMetadataImplementor parameterMetadata;
|
||||||
private final DomainParameterXref domainParameterXref;
|
private final DomainParameterXref domainParameterXref;
|
||||||
|
private final ConcurrentHashMap<Class<?>, Object> allowedReturnTypes;
|
||||||
|
|
||||||
public SimpleHqlInterpretationImpl(
|
public SimpleHqlInterpretationImpl(
|
||||||
SqmStatement<R> sqmStatement,
|
SqmStatement<R> sqmStatement,
|
||||||
|
@ -24,6 +29,7 @@ public class SimpleHqlInterpretationImpl<R> implements HqlInterpretation<R> {
|
||||||
this.sqmStatement = sqmStatement;
|
this.sqmStatement = sqmStatement;
|
||||||
this.parameterMetadata = parameterMetadata;
|
this.parameterMetadata = parameterMetadata;
|
||||||
this.domainParameterXref = domainParameterXref;
|
this.domainParameterXref = domainParameterXref;
|
||||||
|
this.allowedReturnTypes = new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -40,4 +46,15 @@ public class SimpleHqlInterpretationImpl<R> implements HqlInterpretation<R> {
|
||||||
public DomainParameterXref getDomainParameterXref() {
|
public DomainParameterXref getDomainParameterXref() {
|
||||||
return domainParameterXref.copy();
|
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) {
|
public InterpretationException(String query, String message) {
|
||||||
super(
|
super(
|
||||||
"Error interpreting query [" + message + "] [" + query + "]",
|
"Error interpreting query [" + message + "]",
|
||||||
query
|
query
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public InterpretationException(String query, Exception cause) {
|
public InterpretationException(String query, Exception cause) {
|
||||||
super(
|
super(
|
||||||
"Error interpreting query [" + cause.getMessage() + "] [" + query + "]",
|
"Error interpreting query [" + cause.getMessage() + "]",
|
||||||
query,
|
query,
|
||||||
cause
|
cause
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,18 +16,38 @@ import org.hibernate.query.Order;
|
||||||
import org.hibernate.query.Page;
|
import org.hibernate.query.Page;
|
||||||
import org.hibernate.query.QueryLogging;
|
import org.hibernate.query.QueryLogging;
|
||||||
import org.hibernate.query.SelectionQuery;
|
import org.hibernate.query.SelectionQuery;
|
||||||
|
import org.hibernate.query.criteria.JpaSelection;
|
||||||
import org.hibernate.query.criteria.ValueHandlingMode;
|
import org.hibernate.query.criteria.ValueHandlingMode;
|
||||||
|
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
||||||
import org.hibernate.query.hql.internal.QuerySplitter;
|
import org.hibernate.query.hql.internal.QuerySplitter;
|
||||||
|
import org.hibernate.query.named.NamedQueryMemento;
|
||||||
import org.hibernate.query.spi.AbstractSelectionQuery;
|
import org.hibernate.query.spi.AbstractSelectionQuery;
|
||||||
|
import org.hibernate.query.spi.HqlInterpretation;
|
||||||
import org.hibernate.query.spi.MutableQueryOptions;
|
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.QueryOptions;
|
||||||
import org.hibernate.query.spi.SelectQueryPlan;
|
import org.hibernate.query.spi.SelectQueryPlan;
|
||||||
import org.hibernate.query.sqm.NodeBuilder;
|
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.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.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.sql.results.internal.TupleMetadata;
|
||||||
|
import org.hibernate.type.BasicType;
|
||||||
|
import org.hibernate.type.BasicTypeRegistry;
|
||||||
|
|
||||||
import java.util.List;
|
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 java.util.stream.Collectors.toList;
|
||||||
import static org.hibernate.cfg.QuerySettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
|
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.KeyBasedPagination.paginate;
|
||||||
import static org.hibernate.query.sqm.internal.KeyedResult.collectKeys;
|
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.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.internal.SqmUtil.sortSpecification;
|
||||||
import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext;
|
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) {
|
private <T> SelectQueryPlan<T> buildConcreteQueryPlan(SqmSelectStatement<T> sqmStatement, QueryOptions options) {
|
||||||
return buildConcreteQueryPlan( sqmStatement, null, null, 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.io.Serializable;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -28,7 +27,6 @@ import org.hibernate.LockOptions;
|
||||||
import org.hibernate.ScrollMode;
|
import org.hibernate.ScrollMode;
|
||||||
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
|
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.generator.Generator;
|
import org.hibernate.generator.Generator;
|
||||||
import org.hibernate.graph.GraphSemantic;
|
import org.hibernate.graph.GraphSemantic;
|
||||||
|
@ -47,13 +45,11 @@ import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.query.BindableType;
|
import org.hibernate.query.BindableType;
|
||||||
import org.hibernate.query.IllegalQueryOperationException;
|
import org.hibernate.query.IllegalQueryOperationException;
|
||||||
import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
|
|
||||||
import org.hibernate.query.Order;
|
import org.hibernate.query.Order;
|
||||||
import org.hibernate.query.Page;
|
import org.hibernate.query.Page;
|
||||||
import org.hibernate.query.Query;
|
import org.hibernate.query.Query;
|
||||||
import org.hibernate.query.QueryParameter;
|
import org.hibernate.query.QueryParameter;
|
||||||
import org.hibernate.query.ResultListTransformer;
|
import org.hibernate.query.ResultListTransformer;
|
||||||
import org.hibernate.query.SemanticException;
|
|
||||||
import org.hibernate.query.TupleTransformer;
|
import org.hibernate.query.TupleTransformer;
|
||||||
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
|
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
|
||||||
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
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.hql.spi.SqmQueryImplementor;
|
||||||
import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
|
import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
|
||||||
import org.hibernate.query.internal.ParameterMetadataImpl;
|
import org.hibernate.query.internal.ParameterMetadataImpl;
|
||||||
import org.hibernate.query.internal.QueryParameterBindingsImpl;
|
|
||||||
import org.hibernate.query.named.NamedQueryMemento;
|
import org.hibernate.query.named.NamedQueryMemento;
|
||||||
import org.hibernate.query.spi.DelegatingQueryOptions;
|
import org.hibernate.query.spi.DelegatingQueryOptions;
|
||||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
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.MutableQueryOptions;
|
||||||
import org.hibernate.query.spi.NonSelectQueryPlan;
|
import org.hibernate.query.spi.NonSelectQueryPlan;
|
||||||
import org.hibernate.query.spi.ParameterMetadataImplementor;
|
import org.hibernate.query.spi.ParameterMetadataImplementor;
|
||||||
import org.hibernate.query.spi.QueryEngine;
|
|
||||||
import org.hibernate.query.spi.QueryInterpretationCache;
|
import org.hibernate.query.spi.QueryInterpretationCache;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
import org.hibernate.query.spi.QueryParameterBindings;
|
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.SqmPathSource;
|
||||||
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
|
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
|
||||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
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.SqmCopyContext;
|
||||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
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.delete.SqmDeleteStatement;
|
||||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||||
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
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.SqmJpaCriteriaParameterWrapper;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
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.SqmInsertStatement;
|
||||||
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
|
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
|
||||||
import org.hibernate.query.sqm.tree.insert.SqmValues;
|
import org.hibernate.query.sqm.tree.insert.SqmValues;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
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.query.sqm.tree.update.SqmUpdateStatement;
|
||||||
import org.hibernate.sql.results.internal.TupleMetadata;
|
import org.hibernate.sql.results.internal.TupleMetadata;
|
||||||
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
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.SqmInterpretationsKey.generateNonSelectKey;
|
||||||
import static org.hibernate.query.sqm.internal.SqmUtil.isSelect;
|
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.SqmUtil.verifyIsNonSelectStatement;
|
||||||
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Query} implementation based on an SQM
|
* {@link Query} implementation based on an SQM
|
||||||
|
@ -144,7 +133,7 @@ public class QuerySqmImpl<R>
|
||||||
private final ParameterMetadataImplementor parameterMetadata;
|
private final ParameterMetadataImplementor parameterMetadata;
|
||||||
private final DomainParameterXref domainParameterXref;
|
private final DomainParameterXref domainParameterXref;
|
||||||
|
|
||||||
private final QueryParameterBindingsImpl parameterBindings;
|
private final QueryParameterBindings parameterBindings;
|
||||||
|
|
||||||
private final Class<R> resultType;
|
private final Class<R> resultType;
|
||||||
private final TupleMetadata tupleMetadata;
|
private final TupleMetadata tupleMetadata;
|
||||||
|
@ -156,29 +145,13 @@ public class QuerySqmImpl<R>
|
||||||
NamedHqlQueryMementoImpl memento,
|
NamedHqlQueryMementoImpl memento,
|
||||||
Class<R> expectedResultType,
|
Class<R> expectedResultType,
|
||||||
SharedSessionContractImplementor session) {
|
SharedSessionContractImplementor session) {
|
||||||
super( session );
|
this(
|
||||||
|
memento.getHqlString(),
|
||||||
this.hql = memento.getHqlString();
|
interpretation( memento, expectedResultType, session ),
|
||||||
this.resultType = expectedResultType;
|
expectedResultType,
|
||||||
|
session
|
||||||
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 );
|
|
||||||
|
|
||||||
|
|
||||||
applyOptions( memento );
|
applyOptions( memento );
|
||||||
this.tupleMetadata = buildTupleMetadata( sqm, resultType );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public QuerySqmImpl(
|
public QuerySqmImpl(
|
||||||
|
@ -207,9 +180,16 @@ public class QuerySqmImpl<R>
|
||||||
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
||||||
this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
|
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 );
|
setComment( hql );
|
||||||
|
|
||||||
this.tupleMetadata = buildTupleMetadata( sqm, resultType );
|
this.tupleMetadata = buildTupleMetadata( sqm, resultType );
|
||||||
|
@ -243,7 +223,7 @@ public class QuerySqmImpl<R>
|
||||||
parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
|
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
|
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
|
||||||
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
|
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
|
||||||
|
@ -251,7 +231,20 @@ public class QuerySqmImpl<R>
|
||||||
bindCriteriaParameter((SqmJpaCriteriaParameterWrapper<?>) sqmParameter);
|
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;
|
resultType = expectedResultType;
|
||||||
tupleMetadata = buildTupleMetadata( criteria, 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
|
@Override
|
||||||
public TupleMetadata getTupleMetadata() {
|
public TupleMetadata getTupleMetadata() {
|
||||||
return tupleMetadata;
|
return tupleMetadata;
|
||||||
|
|
|
@ -31,19 +31,15 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.graph.spi.AppliedGraph;
|
import org.hibernate.graph.spi.AppliedGraph;
|
||||||
import org.hibernate.internal.util.collections.IdentitySet;
|
import org.hibernate.internal.util.collections.IdentitySet;
|
||||||
import org.hibernate.query.BindableType;
|
import org.hibernate.query.BindableType;
|
||||||
import org.hibernate.query.Page;
|
|
||||||
import org.hibernate.query.QueryParameter;
|
import org.hibernate.query.QueryParameter;
|
||||||
import org.hibernate.query.SelectionQuery;
|
|
||||||
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
|
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
|
||||||
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
||||||
import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
|
import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
|
||||||
import org.hibernate.query.internal.ParameterMetadataImpl;
|
import org.hibernate.query.internal.ParameterMetadataImpl;
|
||||||
import org.hibernate.query.internal.QueryParameterBindingsImpl;
|
|
||||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
import org.hibernate.query.spi.HqlInterpretation;
|
import org.hibernate.query.spi.HqlInterpretation;
|
||||||
import org.hibernate.query.spi.MutableQueryOptions;
|
import org.hibernate.query.spi.MutableQueryOptions;
|
||||||
import org.hibernate.query.spi.ParameterMetadataImplementor;
|
import org.hibernate.query.spi.ParameterMetadataImplementor;
|
||||||
import org.hibernate.query.spi.QueryEngine;
|
|
||||||
import org.hibernate.query.spi.QueryInterpretationCache;
|
import org.hibernate.query.spi.QueryInterpretationCache;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
import org.hibernate.query.spi.QueryParameterBindings;
|
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.JpaCriteriaParameter;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
|
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
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.SqmSelectStatement;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
||||||
import org.hibernate.sql.results.internal.TupleMetadata;
|
import org.hibernate.sql.results.internal.TupleMetadata;
|
||||||
|
@ -86,7 +83,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
||||||
|
|
||||||
private final ParameterMetadataImplementor parameterMetadata;
|
private final ParameterMetadataImplementor parameterMetadata;
|
||||||
private final DomainParameterXref domainParameterXref;
|
private final DomainParameterXref domainParameterXref;
|
||||||
private final QueryParameterBindingsImpl parameterBindings;
|
private final QueryParameterBindings parameterBindings;
|
||||||
|
|
||||||
private final Class<R> expectedResultType;
|
private final Class<R> expectedResultType;
|
||||||
private final Class<?> resultType;
|
private final Class<?> resultType;
|
||||||
|
@ -100,76 +97,34 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
||||||
super( session );
|
super( session );
|
||||||
this.hql = hql;
|
this.hql = hql;
|
||||||
|
|
||||||
|
SqmUtil.verifyIsSelectStatement( hqlInterpretation.getSqmStatement(), hql );
|
||||||
this.sqm = (SqmSelectStatement<R>) hqlInterpretation.getSqmStatement();
|
this.sqm = (SqmSelectStatement<R>) hqlInterpretation.getSqmStatement();
|
||||||
|
|
||||||
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
this.parameterMetadata = hqlInterpretation.getParameterMetadata();
|
||||||
this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
|
this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
|
||||||
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
|
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
|
||||||
|
|
||||||
|
|
||||||
this.expectedResultType = expectedResultType;
|
this.expectedResultType = expectedResultType;
|
||||||
visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
|
this.resultType = determineResultType( sqm, expectedResultType );
|
||||||
this.resultType = determineResultType( sqm );
|
|
||||||
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
|
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
|
||||||
|
|
||||||
|
hqlInterpretation.validateResultType( resultType );
|
||||||
setComment( hql );
|
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(
|
public SqmSelectionQueryImpl(
|
||||||
NamedHqlQueryMementoImpl memento,
|
NamedHqlQueryMementoImpl memento,
|
||||||
Class<R> resultType,
|
Class<R> resultType,
|
||||||
SharedSessionContractImplementor session) {
|
SharedSessionContractImplementor session) {
|
||||||
super( session );
|
this(
|
||||||
this.hql = memento.getHqlString();
|
memento.getHqlString(),
|
||||||
this.expectedResultType = resultType;
|
interpretation( memento, resultType, session ),
|
||||||
this.resultType = resultType;
|
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 );
|
applyOptions( memento );
|
||||||
|
|
||||||
this.tupleMetadata = buildTupleMetadata( sqm, resultType );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SqmSelectionQueryImpl(
|
public SqmSelectionQueryImpl(
|
||||||
|
@ -201,7 +156,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
||||||
? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() )
|
? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() )
|
||||||
: ParameterMetadataImpl.EMPTY;
|
: 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
|
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
|
||||||
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
|
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
|
||||||
|
@ -211,14 +166,49 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
|
||||||
}
|
}
|
||||||
|
|
||||||
this.expectedResultType = expectedResultType;
|
this.expectedResultType = expectedResultType;
|
||||||
this.resultType = determineResultType( sqm );
|
this.resultType = determineResultType( sqm, expectedResultType );
|
||||||
visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
|
|
||||||
|
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 );
|
setComment( hql );
|
||||||
|
|
||||||
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
|
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) {
|
private <T> void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper<T> sqmParameter) {
|
||||||
final JpaCriteriaParameter<T> jpaCriteriaParameter = sqmParameter.getJpaCriteriaParameter();
|
final JpaCriteriaParameter<T> jpaCriteriaParameter = sqmParameter.getJpaCriteriaParameter();
|
||||||
final T value = jpaCriteriaParameter.getValue();
|
final T value = jpaCriteriaParameter.getValue();
|
||||||
|
|
|
@ -6,16 +6,17 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.query.sqm.internal;
|
package org.hibernate.query.sqm.internal;
|
||||||
|
|
||||||
|
import java.sql.Types;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
import java.util.function.Function;
|
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.ModelPart;
|
||||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
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.IllegalQueryOperationException;
|
||||||
import org.hibernate.query.IllegalSelectQueryException;
|
import org.hibernate.query.IllegalSelectQueryException;
|
||||||
import org.hibernate.query.Order;
|
import org.hibernate.query.Order;
|
||||||
|
import org.hibernate.query.QueryTypeMismatchException;
|
||||||
import org.hibernate.query.criteria.JpaOrder;
|
import org.hibernate.query.criteria.JpaOrder;
|
||||||
|
import org.hibernate.query.criteria.JpaSelection;
|
||||||
import org.hibernate.query.spi.QueryParameterBinding;
|
import org.hibernate.query.spi.QueryParameterBinding;
|
||||||
import org.hibernate.query.spi.QueryParameterBindings;
|
import org.hibernate.query.spi.QueryParameterBindings;
|
||||||
import org.hibernate.query.spi.QueryParameterImplementor;
|
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.SqmQualifiedJoin;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
|
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.SqmQueryPart;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
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.JavaObjectType;
|
||||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||||
import org.hibernate.type.descriptor.java.JavaType;
|
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.BasicTypeImpl;
|
||||||
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
|
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
|
||||||
import org.hibernate.type.spi.TypeConfiguration;
|
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 java.util.stream.Collectors.toList;
|
||||||
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
|
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
|
||||||
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
|
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
|
||||||
|
@ -857,4 +872,247 @@ public class SqmUtil {
|
||||||
return jpaCriteriaParamResolutions;
|
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 org.hibernate.query.sqm.tree.select.SqmSubQuery;
|
||||||
|
|
||||||
import jakarta.persistence.criteria.AbstractQuery;
|
import jakarta.persistence.criteria.AbstractQuery;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
@ -72,6 +73,8 @@ public abstract class AbstractSqmDmlStatement<E>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void validate(@Nullable String hql);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<SqmCteStatement<?>> getCteStatements() {
|
public Collection<SqmCteStatement<?>> getCteStatements() {
|
||||||
return cteStatements.values();
|
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.expression.SqmParameter;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmFromClause;
|
import org.hibernate.query.sqm.tree.from.SqmFromClause;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
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.Expression;
|
||||||
import jakarta.persistence.criteria.Predicate;
|
import jakarta.persistence.criteria.Predicate;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
@ -103,6 +103,11 @@ public class SqmDeleteStatement<T>
|
||||||
return statement;
|
return statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(@Nullable String hql) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SqmDeleteStatement<T> where(Expression<Boolean> restriction) {
|
public SqmDeleteStatement<T> where(Expression<Boolean> restriction) {
|
||||||
setWhere( restriction );
|
setWhere( restriction );
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.query.SemanticException;
|
import org.hibernate.query.SemanticException;
|
||||||
import org.hibernate.query.criteria.JpaConflictClause;
|
import org.hibernate.query.criteria.JpaConflictClause;
|
||||||
import org.hibernate.query.criteria.JpaCriteriaInsert;
|
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.SqmQuerySource;
|
||||||
import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement;
|
import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement;
|
||||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
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.cte.SqmCteStatement;
|
||||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||||
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
|
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 jakarta.persistence.criteria.Path;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience base class for InsertSqmStatement implementations.
|
* 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
|
@Override
|
||||||
public void setTarget(JpaRoot<T> root) {
|
public void setTarget(JpaRoot<T> root) {
|
||||||
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
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.SqmQueryPart;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||||
|
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||||
|
|
||||||
import jakarta.persistence.Tuple;
|
import jakarta.persistence.Tuple;
|
||||||
import jakarta.persistence.criteria.CriteriaQuery;
|
import jakarta.persistence.criteria.CriteriaQuery;
|
||||||
import jakarta.persistence.criteria.Path;
|
import jakarta.persistence.criteria.Path;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @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
|
@Override
|
||||||
public SqmInsertSelectStatement<T> select(CriteriaQuery<Tuple> criteriaQuery) {
|
public SqmInsertSelectStatement<T> select(CriteriaQuery<Tuple> criteriaQuery) {
|
||||||
final SqmSelectStatement<Tuple> selectStatement = (SqmSelectStatement<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() {
|
public List<SqmValues> getValuesList() {
|
||||||
return valuesList == null
|
return valuesList == null
|
||||||
? Collections.emptyList()
|
? Collections.emptyList()
|
||||||
|
|
|
@ -149,6 +149,10 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
||||||
return statement;
|
return statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void validateResultType(Class<?> resultType) {
|
||||||
|
SqmUtil.validateQueryReturnType( getQueryPart(), resultType );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SqmQuerySource getQuerySource() {
|
public SqmQuerySource getQuerySource() {
|
||||||
return querySource;
|
return querySource;
|
||||||
|
|
|
@ -6,9 +6,17 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.query.sqm.tree.update;
|
package org.hibernate.query.sqm.tree.update;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.SemanticException;
|
||||||
import org.hibernate.query.criteria.JpaCriteriaUpdate;
|
import org.hibernate.query.criteria.JpaCriteriaUpdate;
|
||||||
import org.hibernate.query.criteria.JpaRoot;
|
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.SqmCopyContext;
|
||||||
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
|
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
|
||||||
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
|
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.SqmPath;
|
||||||
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
|
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
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.Path;
|
||||||
import jakarta.persistence.criteria.Predicate;
|
import jakarta.persistence.criteria.Predicate;
|
||||||
import jakarta.persistence.metamodel.SingularAttribute;
|
import jakarta.persistence.metamodel.SingularAttribute;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
|
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>
|
public class SqmUpdateStatement<T>
|
||||||
extends AbstractSqmRestrictedDmlStatement<T>
|
extends AbstractSqmRestrictedDmlStatement<T>
|
||||||
implements SqmDeleteOrUpdateStatement<T>, JpaCriteriaUpdate<T> {
|
implements SqmDeleteOrUpdateStatement<T>, JpaCriteriaUpdate<T> {
|
||||||
|
|
||||||
|
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( SqmUpdateStatement.class );
|
||||||
|
|
||||||
private boolean versioned;
|
private boolean versioned;
|
||||||
private SqmSetClause setClause;
|
private SqmSetClause setClause;
|
||||||
|
|
||||||
|
@ -110,6 +121,46 @@ public class SqmUpdateStatement<T>
|
||||||
return statement;
|
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() {
|
public SqmSetClause getSetClause() {
|
||||||
return setClause;
|
return setClause;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,18 +57,14 @@ public class ImmutableEntityUpdateQueryHandlingModeExceptionTest extends BaseNon
|
||||||
|
|
||||||
try {
|
try {
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
doInHibernate( this::sessionFactory, session -> {
|
||||||
session.createQuery(
|
session.createQuery("update Country set name = :name" );
|
||||||
"update Country " +
|
|
||||||
"set name = :name" )
|
|
||||||
.setParameter( "name", "N/A" )
|
|
||||||
.executeUpdate();
|
|
||||||
} );
|
} );
|
||||||
fail("Should throw PersistenceException");
|
fail("Should throw PersistenceException");
|
||||||
}
|
}
|
||||||
catch (PersistenceException e) {
|
catch (PersistenceException e) {
|
||||||
assertTrue( e instanceof HibernateException );
|
assertTrue( e instanceof HibernateException );
|
||||||
assertEquals(
|
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()
|
e.getMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
package org.hibernate.orm.test.annotations.immutable;
|
package org.hibernate.orm.test.annotations.immutable;
|
||||||
|
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
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.TestForIssue;
|
||||||
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
@ -30,7 +30,7 @@ public class ImmutableEntityUpdateQueryHandlingModeWarningTest extends BaseNonCo
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public LoggerInspectionRule logInspection = new LoggerInspectionRule(
|
public LoggerInspectionRule logInspection = new LoggerInspectionRule(
|
||||||
Logger.getMessageLogger( CoreMessageLogger.class, QuerySqmImpl.class.getName() ) );
|
Logger.getMessageLogger( CoreMessageLogger.class, SqmUpdateStatement.class.getName() ) );
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Class[] getAnnotatedClasses() {
|
protected Class[] getAnnotatedClasses() {
|
||||||
|
|
Loading…
Reference in New Issue