HHH-17956 Criteria multiselect ignores type of the criteria query and always returns list of Object[]

This commit is contained in:
Andrea Boriero 2024-04-22 10:58:17 +02:00 committed by Christian Beikov
parent f5062b2aef
commit a567226a72
18 changed files with 496 additions and 138 deletions

View File

@ -740,9 +740,9 @@ The following functions are abbreviations for `extract()`:
| `year(x)` | `extract(year from x)` | ✖ | `year(x)` | `extract(year from x)` | ✖
| `month(x)` | `extract(month from x)` | ✖ | `month(x)` | `extract(month from x)` | ✖
| `day(x)` | `extract(day from x)` | ✖ | `day(x)` | `extract(day from x)` | ✖
| `hour(x)` | `extract(year from x)` | ✖ | `hour(x)` | `extract(hour from x)` | ✖
| `minute(x)` | `extract(year from x)` | ✖ | `minute(x)` | `extract(minute from x)` | ✖
| `second(x)` | `extract(year from x)` | ✖ | `second(x)` | `extract(second from x)` | ✖
|=== |===
TIP: These abbreviations aren't part of the JPQL standard, but on the other hand they're a lot less verbose. TIP: These abbreviations aren't part of the JPQL standard, but on the other hand they're a lot less verbose.

View File

@ -41,7 +41,7 @@ import static org.hibernate.type.SqlTypes.*;
* *
* @author Christian Beikov * @author Christian Beikov
*/ */
class SumReturnTypeResolver implements FunctionReturnTypeResolver { public class SumReturnTypeResolver implements FunctionReturnTypeResolver {
private final BasicType<Long> longType; private final BasicType<Long> longType;
private final BasicType<Double> doubleType; private final BasicType<Double> doubleType;

View File

@ -115,6 +115,8 @@ import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaUpdate; import jakarta.persistence.criteria.CriteriaUpdate;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static org.hibernate.internal.util.ReflectHelper.isClass; import static org.hibernate.internal.util.ReflectHelper.isClass;
@ -962,8 +964,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
query.addEntity( resultClass, LockMode.READ ); query.addEntity( resultClass, LockMode.READ );
} }
else if ( resultClass != Object.class && resultClass != Object[].class ) { else if ( resultClass != Object.class && resultClass != Object[].class ) {
if ( isClass( resultClass ) if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) {
&& getTypeConfiguration().getJavaTypeRegistry().findDescriptor( resultClass ) == null ) {
// not a basic type // not a basic type
query.setTupleTransformer( new NativeQueryConstructorTransformer<>( resultClass ) ); query.setTupleTransformer( new NativeQueryConstructorTransformer<>( resultClass ) );
} }
@ -973,6 +974,11 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
} }
} }
private <T> boolean hasJavaTypeDescriptor(Class<T> resultClass) {
final JavaType<Object> descriptor = getTypeConfiguration().getJavaTypeRegistry().findDescriptor( resultClass );
return descriptor != null && descriptor.getClass() != UnknownBasicJavaType.class;
}
@Override @Override
public <T> NativeQueryImplementor<T> createNativeQuery(String sqlString, Class<T> resultClass, String tableAlias) { public <T> NativeQueryImplementor<T> createNativeQuery(String sqlString, Class<T> resultClass, String tableAlias) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -30,6 +30,7 @@ import jakarta.persistence.TemporalType;
import jakarta.persistence.Tuple; import jakarta.persistence.Tuple;
import jakarta.persistence.TupleElement; import jakarta.persistence.TupleElement;
import jakarta.persistence.criteria.CompoundSelection; import jakarta.persistence.criteria.CompoundSelection;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.CacheMode; import org.hibernate.CacheMode;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
@ -260,24 +261,24 @@ public abstract class AbstractSelectionQuery<R>
SqmQueryPart<R> queryPart, SqmQueryPart<R> queryPart,
Class<R> expectedResultType, Class<R> expectedResultType,
SessionFactoryImplementor factory) { SessionFactoryImplementor factory) {
assert getQueryString().equals( CRITERIA_HQL_STRING );
if ( queryPart instanceof SqmQuerySpec<?> ) { if ( queryPart instanceof SqmQuerySpec<?> ) {
final SqmQuerySpec<R> sqmQuerySpec = (SqmQuerySpec<R>) queryPart; final SqmQuerySpec<R> sqmQuerySpec = (SqmQuerySpec<R>) queryPart;
final List<SqmSelection<?>> sqmSelections = sqmQuerySpec.getSelectClause().getSelections(); final List<SqmSelection<?>> sqmSelections = sqmQuerySpec.getSelectClause().getSelections();
if ( sqmSelections == null || sqmSelections.isEmpty() ) { if ( getQueryString() == CRITERIA_HQL_STRING ) {
// make sure there is at least one root if ( sqmSelections == null || sqmSelections.isEmpty() ) {
final List<SqmRoot<?>> sqmRoots = sqmQuerySpec.getFromClause().getRoots(); // make sure there is at least one root
if ( sqmRoots == null || sqmRoots.isEmpty() ) { final List<SqmRoot<?>> sqmRoots = sqmQuerySpec.getFromClause().getRoots();
throw new IllegalArgumentException( "Criteria did not define any query roots" ); 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 ) { // if there is a single root, use that as the selection
sqmQuerySpec.getSelectClause().add( sqmRoots.get( 0 ), null ); if ( sqmRoots.size() == 1 ) {
} sqmQuerySpec.getSelectClause().add( sqmRoots.get( 0 ), null );
else { }
throw new IllegalArgumentException( "Criteria has multiple query roots" ); else {
throw new IllegalArgumentException( "Criteria has multiple query roots" );
}
} }
} }
@ -302,28 +303,71 @@ public abstract class AbstractSelectionQuery<R>
if ( selections.size() == 1 ) { if ( selections.size() == 1 ) {
// we have one item in the select list, // we have one item in the select list,
// the type has to match (no instantiation) // the type has to match (no instantiation)
final SqmSelection<?> sqmSelection = selections.get(0); final SqmSelection<?> sqmSelection = selections.get( 0 );
final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
// special case for parameters in the select list if ( selectableNode.isCompoundSelection() ) {
final SqmSelectableNode<?> selection = sqmSelection.getSelectableNode(); final Class<?> expectedSelectItemType = expectedResultClass.isArray()
if ( selection instanceof SqmParameter ) { ? expectedResultClass.getComponentType()
final SqmParameter<?> sqmParameter = (SqmParameter<?>) selection; : expectedResultClass;
final SqmExpressible<?> nodeType = sqmParameter.getNodeType(); for ( JpaSelection<?> selection : selectableNode.getSelectionItems() ) {
// we may not yet know a selection type verifySelectionType( expectedSelectItemType, sessionFactory, (SqmSelectableNode<?>) selection );
if ( nodeType == null || nodeType.getExpressibleJavaType() == null ) {
// we can't verify the result type up front
return;
} }
} }
else {
if ( !sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) { verifySelectionType( expectedResultClass, sessionFactory, sqmSelection.getSelectableNode() );
verifyResultType( expectedResultClass, sqmSelection.getExpressible() ); }
}
else if ( expectedResultClass.isArray() ) {
final Class<?> componentType = expectedResultClass.getComponentType();
for ( SqmSelection<?> selection : selections ) {
verifySelectionType( componentType, sessionFactory, selection.getSelectableNode() );
} }
} }
// else, let's assume we can instantiate it! // else, let's assume we can instantiate it!
} }
} }
private static <T> void verifySelectionType(
Class<T> expectedResultClass,
SessionFactoryImplementor sessionFactory,
SqmSelection<?> sqmSelection) {
// special case for parameters in the select list
final SqmSelectableNode<?> selection = sqmSelection.getSelectableNode();
if ( selection instanceof SqmParameter ) {
final SqmParameter<?> sqmParameter = (SqmParameter<?>) selection;
final SqmExpressible<?> nodeType = sqmParameter.getNodeType();
// 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 <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) { private static boolean isInstantiableWithoutMetadata(Class<?> resultType) {
return resultType == null return resultType == null
|| resultType.isArray() || resultType.isArray()
@ -334,27 +378,29 @@ public abstract class AbstractSelectionQuery<R>
private static <T> boolean isResultTypeAlwaysAllowed(Class<T> expectedResultClass) { private static <T> boolean isResultTypeAlwaysAllowed(Class<T> expectedResultClass) {
return expectedResultClass == null return expectedResultClass == null
|| expectedResultClass == Object.class || expectedResultClass == Object.class
|| expectedResultClass == Object[].class
|| expectedResultClass == List.class || expectedResultClass == List.class
|| expectedResultClass == Tuple.class || expectedResultClass == Map.class
|| expectedResultClass.isArray(); || expectedResultClass == Tuple.class;
} }
protected static <T> void verifyResultType(Class<T> resultClass, SqmExpressible<?> sqmExpressible) { protected static <T> void verifyResultType(Class<T> resultClass, @Nullable SqmExpressible<?> sqmExpressible) {
assert sqmExpressible != null; if ( sqmExpressible != null ) {
final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType(); final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType();
assert expressibleJavaType != null; assert expressibleJavaType != null;
final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass(); final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass();
if ( !resultClass.isAssignableFrom( javaTypeClass ) ) { if ( javaTypeClass != Object.class && !resultClass.isAssignableFrom( javaTypeClass ) ) {
if ( expressibleJavaType instanceof PrimitiveJavaType ) { if ( expressibleJavaType instanceof PrimitiveJavaType ) {
final PrimitiveJavaType<?> javaType = (PrimitiveJavaType<?>) expressibleJavaType; final PrimitiveJavaType<?> javaType = (PrimitiveJavaType<?>) expressibleJavaType;
if ( javaType.getPrimitiveClass() != resultClass ) { if ( javaType.getPrimitiveClass() != resultClass ) {
throwQueryTypeMismatchException( resultClass, sqmExpressible );
}
}
else if ( !isMatchingDateType( javaTypeClass, resultClass, sqmExpressible ) ) {
throwQueryTypeMismatchException( resultClass, sqmExpressible ); throwQueryTypeMismatchException( resultClass, sqmExpressible );
} }
// else special case, we are good
} }
else if ( !isMatchingDateType( javaTypeClass, resultClass, sqmExpressible ) ) {
throwQueryTypeMismatchException( resultClass, sqmExpressible );
}
// else special case, we are good
} }
} }

View File

@ -38,4 +38,8 @@ public interface DomainQueryExecutionContext {
* The underlying session * The underlying session
*/ */
SharedSessionContractImplementor getSession(); SharedSessionContractImplementor getSession();
default Class<?> getResultType() {
return null;
}
} }

View File

@ -137,11 +137,13 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
jdbcParameterBindings jdbcParameterBindings
); );
session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true ); session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true );
//noinspection unchecked
return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect, jdbcSelect,
jdbcParameterBindings, jdbcParameterBindings,
listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ),
rowTransformer, rowTransformer,
(Class<R>) executionContext.getResultType(),
uniqueSemantic uniqueSemantic
); );
} }

View File

@ -251,34 +251,7 @@ public class QuerySqmImpl<R>
bindCriteriaParameter((SqmJpaCriteriaParameterWrapper<?>) sqmParameter); bindCriteriaParameter((SqmJpaCriteriaParameterWrapper<?>) sqmParameter);
} }
} }
validateStatement( sqm, expectedResultType );
if ( sqm instanceof SqmSelectStatement<?> ) {
SqmUtil.verifyIsSelectStatement( sqm, null );
final SqmQueryPart<R> queryPart = ( (SqmSelectStatement<R>) sqm ).getQueryPart();
// For criteria queries, we have to validate the fetch structure here
queryPart.validateQueryStructureAndFetchOwners();
visitQueryReturnType(
queryPart,
expectedResultType,
producer.getFactory()
);
}
else {
if ( expectedResultType != null ) {
throw new IllegalQueryOperationException( "Result type given for a non-SELECT Query", hql, null );
}
if ( sqm instanceof SqmUpdateStatement<?> ) {
final SqmUpdateStatement<R> updateStatement = (SqmUpdateStatement<R>) sqm;
verifyImmutableEntityUpdate( CRITERIA_HQL_STRING, updateStatement, producer.getFactory() );
if ( updateStatement.getSetClause() == null
|| updateStatement.getSetClause().getAssignments().isEmpty() ) {
throw new IllegalArgumentException( "No assignments specified as part of UPDATE criteria" );
}
}
else if ( sqm instanceof SqmInsertStatement<?> ) {
verifyInsertTypesMatch( CRITERIA_HQL_STRING, (SqmInsertStatement<R>) sqm );
}
}
resultType = expectedResultType; resultType = expectedResultType;
tupleMetadata = buildTupleMetadata( criteria, expectedResultType ); tupleMetadata = buildTupleMetadata( criteria, expectedResultType );
@ -298,7 +271,12 @@ public class QuerySqmImpl<R>
private void validateStatement(SqmStatement<R> sqmStatement, Class<R> resultType) { private void validateStatement(SqmStatement<R> sqmStatement, Class<R> resultType) {
if ( sqmStatement instanceof SqmSelectStatement<?> ) { if ( sqmStatement instanceof SqmSelectStatement<?> ) {
SqmUtil.verifyIsSelectStatement( sqmStatement, hql ); 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 { else {
if ( resultType != null ) { if ( resultType != null ) {
@ -307,6 +285,10 @@ public class QuerySqmImpl<R>
if ( sqmStatement instanceof SqmUpdateStatement<?> ) { if ( sqmStatement instanceof SqmUpdateStatement<?> ) {
final SqmUpdateStatement<R> updateStatement = (SqmUpdateStatement<R>) sqmStatement; final SqmUpdateStatement<R> updateStatement = (SqmUpdateStatement<R>) sqmStatement;
verifyImmutableEntityUpdate( hql, updateStatement, getSessionFactory() ); 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 ); verifyUpdateTypesMatch( hql, updateStatement );
} }
else if ( sqmStatement instanceof SqmInsertStatement<?> ) { else if ( sqmStatement instanceof SqmInsertStatement<?> ) {

View File

@ -34,6 +34,8 @@ import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.dialect.function.AvgFunction;
import org.hibernate.dialect.function.SumReturnTypeResolver;
import org.hibernate.dialect.function.array.DdlTypeHelper; import org.hibernate.dialect.function.array.DdlTypeHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
@ -81,6 +83,7 @@ import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor; import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.FunctionArgumentException; import org.hibernate.query.sqm.produce.function.FunctionArgumentException;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.query.sqm.spi.SqmCreationContext; import org.hibernate.query.sqm.spi.SqmCreationContext;
import org.hibernate.query.sqm.tree.SqmQuery; import org.hibernate.query.sqm.tree.SqmQuery;
@ -201,6 +204,8 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
private transient BasicType<Integer> integerType; private transient BasicType<Integer> integerType;
private transient BasicType<Long> longType; private transient BasicType<Long> longType;
private transient BasicType<Character> characterType; private transient BasicType<Character> characterType;
private transient FunctionReturnTypeResolver sumReturnTypeResolver;
private transient FunctionReturnTypeResolver avgReturnTypeResolver;
private final transient Map<Class<? extends HibernateCriteriaBuilder>, HibernateCriteriaBuilder> extensions; private final transient Map<Class<? extends HibernateCriteriaBuilder>, HibernateCriteriaBuilder> extensions;
public SqmCriteriaNodeBuilder( public SqmCriteriaNodeBuilder(
@ -248,7 +253,6 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
} }
@Override @Override
public BasicType<Boolean> getBooleanType() { public BasicType<Boolean> getBooleanType() {
final BasicType<Boolean> booleanType = this.booleanType; final BasicType<Boolean> booleanType = this.booleanType;
@ -293,6 +297,22 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return characterType; return characterType;
} }
public FunctionReturnTypeResolver getSumReturnTypeResolver() {
final FunctionReturnTypeResolver resolver = sumReturnTypeResolver;
if ( resolver == null ) {
return this.sumReturnTypeResolver = new SumReturnTypeResolver( getTypeConfiguration() );
}
return resolver;
}
public FunctionReturnTypeResolver getAvgReturnTypeResolver() {
final FunctionReturnTypeResolver resolver = avgReturnTypeResolver;
if ( resolver == null ) {
return this.avgReturnTypeResolver = new AvgFunction.ReturnTypeResolver( getTypeConfiguration() );
}
return resolver;
}
@Override @Override
public QueryEngine getQueryEngine() { public QueryEngine getQueryEngine() {
return queryEngine; return queryEngine;

View File

@ -6,17 +6,23 @@
*/ */
package org.hibernate.query.sqm.tree.domain; package org.hibernate.query.sqm.tree.domain;
import java.util.List;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.type.descriptor.java.JavaType;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class SqmElementAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> { public class SqmElementAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> {
private final String functionName; private final String functionName;
private final ReturnableType<T> returnableType;
public SqmElementAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) { public SqmElementAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) {
//noinspection unchecked //noinspection unchecked
@ -27,6 +33,46 @@ public class SqmElementAggregateFunction<T> extends AbstractSqmSpecificPluralPar
( (PluralPersistentAttribute<?, ?, T>) pluralDomainPath.getReferencedPathSource() ).getElementPathSource() ( (PluralPersistentAttribute<?, ?, T>) pluralDomainPath.getReferencedPathSource() ).getElementPathSource()
); );
this.functionName = functionName; this.functionName = functionName;
switch ( functionName ) {
case "sum":
//noinspection unchecked
this.returnableType = (ReturnableType<T>) nodeBuilder().getSumReturnTypeResolver()
.resolveFunctionReturnType(
null,
(SqmToSqlAstConverter) null,
List.of( pluralDomainPath ),
nodeBuilder().getTypeConfiguration()
);
break;
case "avg":
//noinspection unchecked
this.returnableType = (ReturnableType<T>) nodeBuilder().getAvgReturnTypeResolver()
.resolveFunctionReturnType(
null,
(SqmToSqlAstConverter) null,
List.of( pluralDomainPath ),
nodeBuilder().getTypeConfiguration()
);
break;
default:
this.returnableType = null;
break;
}
}
@Override
public SqmExpressible<T> getExpressible() {
return returnableType == null ? super.getExpressible() : returnableType;
}
@Override
public JavaType<T> getJavaTypeDescriptor() {
return returnableType == null ? super.getJavaTypeDescriptor() : returnableType.getExpressibleJavaType();
}
@Override
public JavaType<T> getNodeJavaType() {
return returnableType == null ? super.getNodeJavaType() : returnableType.getExpressibleJavaType();
} }
@Override @Override

View File

@ -6,17 +6,25 @@
*/ */
package org.hibernate.query.sqm.tree.domain; package org.hibernate.query.sqm.tree.domain;
import java.util.List;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.type.descriptor.java.JavaType;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class SqmIndexAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> { public class SqmIndexAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> {
private final String functionName; private final String functionName;
private final ReturnableType<T> returnableType;
public SqmIndexAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) { public SqmIndexAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) {
//noinspection unchecked //noinspection unchecked
@ -27,6 +35,46 @@ public class SqmIndexAggregateFunction<T> extends AbstractSqmSpecificPluralPartP
(SqmPathSource<T>) ( (PluralPersistentAttribute<?, ?, ?>) pluralDomainPath.getReferencedPathSource() ).getIndexPathSource() (SqmPathSource<T>) ( (PluralPersistentAttribute<?, ?, ?>) pluralDomainPath.getReferencedPathSource() ).getIndexPathSource()
); );
this.functionName = functionName; this.functionName = functionName;
switch ( functionName ) {
case "sum":
//noinspection unchecked
this.returnableType = (ReturnableType<T>) nodeBuilder().getSumReturnTypeResolver()
.resolveFunctionReturnType(
null,
(SqmToSqlAstConverter) null,
List.of( pluralDomainPath.get( CollectionPart.Nature.INDEX.getName() ) ),
nodeBuilder().getTypeConfiguration()
);
break;
case "avg":
//noinspection unchecked
this.returnableType = (ReturnableType<T>) nodeBuilder().getAvgReturnTypeResolver()
.resolveFunctionReturnType(
null,
(SqmToSqlAstConverter) null,
List.of( pluralDomainPath.get( CollectionPart.Nature.INDEX.getName() ) ),
nodeBuilder().getTypeConfiguration()
);
break;
default:
this.returnableType = null;
break;
}
}
@Override
public SqmExpressible<T> getExpressible() {
return returnableType == null ? super.getExpressible() : returnableType;
}
@Override
public JavaType<T> getJavaTypeDescriptor() {
return returnableType == null ? super.getJavaTypeDescriptor() : returnableType.getExpressibleJavaType();
}
@Override
public JavaType<T> getNodeJavaType() {
return returnableType == null ? super.getNodeJavaType() : returnableType.getExpressibleJavaType();
} }
@Override @Override

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.sql.results.internal; package org.hibernate.sql.results.internal;
import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -41,6 +42,9 @@ public class StandardRowReader<T> implements RowReader<T> {
private final RowTransformer<T> rowTransformer; private final RowTransformer<T> rowTransformer;
private final Class<T> domainResultJavaType; private final Class<T> domainResultJavaType;
private final ComponentType componentType;
private final Class<?> resultElementClass;
private static final Logger LOGGER = LoadingLogger.LOGGER; private static final Logger LOGGER = LoadingLogger.LOGGER;
public StandardRowReader( public StandardRowReader(
@ -76,6 +80,19 @@ public class StandardRowReader<T> implements RowReader<T> {
this.hasCollectionInitializers = hasCollectionInitializers; this.hasCollectionInitializers = hasCollectionInitializers;
this.rowTransformer = rowTransformer; this.rowTransformer = rowTransformer;
this.domainResultJavaType = domainResultJavaType; this.domainResultJavaType = domainResultJavaType;
if ( domainResultJavaType == null
|| domainResultJavaType == Object[].class
|| domainResultJavaType == Object.class
|| !domainResultJavaType.isArray()
|| resultAssemblers.length == 1
&& domainResultJavaType == resultAssemblers[0].getAssembledJavaType().getJavaTypeClass() ) {
this.resultElementClass = Object.class;
this.componentType = ComponentType.OBJECT;
}
else {
this.resultElementClass = domainResultJavaType.getComponentType();
this.componentType = ComponentType.determineComponentType( domainResultJavaType );
}
} }
@Override @Override
@ -84,16 +101,7 @@ public class StandardRowReader<T> implements RowReader<T> {
} }
@Override @Override
public Class<?> getResultJavaType() { public List<@Nullable JavaType<?>> getResultJavaTypes() {
if ( resultAssemblers.length == 1 ) {
return resultAssemblers[0].getAssembledJavaType().getJavaTypeClass();
}
return Object[].class;
}
@Override
public List<JavaType<?>> getResultJavaTypes() {
List<JavaType<?>> javaTypes = new ArrayList<>( resultAssemblers.length ); List<JavaType<?>> javaTypes = new ArrayList<>( resultAssemblers.length );
for ( DomainResultAssembler resultAssembler : resultAssemblers ) { for ( DomainResultAssembler resultAssembler : resultAssemblers ) {
javaTypes.add( resultAssembler.getAssembledJavaType() ); javaTypes.add( resultAssembler.getAssembledJavaType() );
@ -128,16 +136,111 @@ public class StandardRowReader<T> implements RowReader<T> {
public T readRow(RowProcessingState rowProcessingState) { public T readRow(RowProcessingState rowProcessingState) {
coordinateInitializers( rowProcessingState ); coordinateInitializers( rowProcessingState );
final Object[] resultRow = new Object[ resultAssemblers.length ]; // The following is ugly, but unfortunately necessary to not hurt performance.
// This implementation was micro-benchmarked and discussed with Francesco Nigro,
// who hinted that using this style instead of the reflective Array.getLength(), Array.set()
// is easier for the JVM to optimize
switch ( componentType ) {
case BOOLEAN:
final boolean[] resultBooleanRow = new boolean[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; final DomainResultAssembler assembler = resultAssemblers[i];
resultRow[i] = assembler.assemble( rowProcessingState ); resultBooleanRow[i] = (boolean) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultBooleanRow;
case BYTE:
final byte[] resultByteRow = new byte[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultByteRow[i] = (byte) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultByteRow;
case CHAR:
final char[] resultCharRow = new char[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultCharRow[i] = (char) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultCharRow;
case SHORT:
final short[] resultShortRow = new short[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultShortRow[i] = (short) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultShortRow;
case INT:
final int[] resultIntRow = new int[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultIntRow[i] = (int) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultIntRow;
case LONG:
final long[] resultLongRow = new long[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultLongRow[i] = (long) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultLongRow;
case FLOAT:
final float[] resultFloatRow = new float[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultFloatRow[i] = (float) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultFloatRow;
case DOUBLE:
final double[] resultDoubleRow = new double[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultDoubleRow[i] = (double) assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return (T) resultDoubleRow;
default:
final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length );
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultRow[i] = assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return rowTransformer.transformRow( resultRow );
} }
afterRow( rowProcessingState );
return rowTransformer.transformRow( resultRow );
} }
private void afterRow(RowProcessingState rowProcessingState) { private void afterRow(RowProcessingState rowProcessingState) {
@ -188,4 +291,54 @@ public class StandardRowReader<T> implements RowReader<T> {
} }
} }
enum ComponentType {
BOOLEAN(boolean.class),
BYTE(byte.class),
SHORT(short.class),
CHAR(char.class),
INT(int.class),
LONG(long.class),
FLOAT(float.class),
DOUBLE(double.class),
OBJECT(Object.class);
private final Class<?> componentType;
ComponentType(Class<?> componentType) {
this.componentType = componentType;
}
public static ComponentType determineComponentType(Class<?> resultType) {
if ( resultType == boolean[].class) {
return BOOLEAN;
}
else if ( resultType == byte[].class) {
return BYTE;
}
else if ( resultType == short[].class) {
return SHORT;
}
else if ( resultType == char[].class) {
return CHAR;
}
else if ( resultType == int[].class) {
return INT;
}
else if ( resultType == long[].class) {
return LONG;
}
else if ( resultType == float[].class) {
return FLOAT;
}
else if ( resultType == double[].class) {
return DOUBLE;
}
return OBJECT;
}
public Class<?> getComponentType() {
return componentType;
}
}
} }

View File

@ -25,6 +25,8 @@ import org.hibernate.type.descriptor.java.spi.EntityJavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* ResultsConsumer for creating a List of results * ResultsConsumer for creating a List of results
* *
@ -247,22 +249,37 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
private JavaType<R> resolveDomainResultJavaType( private JavaType<R> resolveDomainResultJavaType(
Class<R> domainResultResultJavaType, Class<R> domainResultResultJavaType,
List<JavaType<?>> resultJavaTypes, List<@Nullable JavaType<?>> resultJavaTypes,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
if ( domainResultResultJavaType != null ) { if ( domainResultResultJavaType != null ) {
return javaTypeRegistry.resolveDescriptor( domainResultResultJavaType ); final JavaType<R> resultJavaType = javaTypeRegistry.resolveDescriptor( domainResultResultJavaType );
// Could be that the user requested a more general type than the actual type,
// so resolve the most concrete type since this type is used to determine equality of objects
if ( resultJavaTypes.size() == 1 && isMoreConcrete( resultJavaType, resultJavaTypes.get( 0 ) ) ) {
//noinspection unchecked
return (JavaType<R>) resultJavaTypes.get( 0 );
}
return resultJavaType;
} }
if ( resultJavaTypes.size() == 1 ) { if ( resultJavaTypes.size() == 1 ) {
final JavaType<?> firstJavaType = resultJavaTypes.get( 0 );
if ( firstJavaType == null ) {
return javaTypeRegistry.resolveDescriptor( Object.class );
}
//noinspection unchecked //noinspection unchecked
return (JavaType<R>) resultJavaTypes.get( 0 ); return (JavaType<R>) firstJavaType;
} }
return javaTypeRegistry.resolveDescriptor( Object[].class ); return javaTypeRegistry.resolveDescriptor( Object[].class );
} }
private static boolean isMoreConcrete(JavaType<?> resultJavaType, @Nullable JavaType<?> javaType) {
return javaType != null && resultJavaType.getJavaTypeClass().isAssignableFrom( javaType.getJavaTypeClass() );
}
@Override @Override
public boolean canResultsBeCached() { public boolean canResultsBeCached() {
return true; return true;

View File

@ -30,19 +30,10 @@ public interface RowReader<R> {
*/ */
Class<R> getDomainResultResultJavaType(); Class<R> getDomainResultResultJavaType();
/**
* The row result Java type, before any transformations.
*
* @apiNote along with {@link #getResultJavaTypes()}, describes the "raw"
* values as determined from the {@link org.hibernate.sql.results.graph.DomainResult}
* references associated with the JdbcValues being processed
*/
Class<?> getResultJavaType();
/** /**
* The individual JavaType for each DomainResult * The individual JavaType for each DomainResult
*/ */
List<JavaType<?>> getResultJavaTypes(); List<@Nullable JavaType<?>> getResultJavaTypes();
int getInitializerCount(); int getInitializerCount();

View File

@ -141,7 +141,7 @@ public class ArrayAggregateTest {
@Test @Test
public void testCompareAgainstArray(SessionFactoryScope scope) { public void testCompareAgainstArray(SessionFactoryScope scope) {
scope.inSession( em -> { scope.inSession( em -> {
List<String[]> results = em.createQuery( "select 1 where array('abc','def',null) is not distinct from (select array_agg(e.theString) within group (order by e.theString asc nulls last) from EntityOfBasics e)", String[].class ) List<Integer> results = em.createQuery( "select 1 where array('abc','def',null) is not distinct from (select array_agg(e.theString) within group (order by e.theString asc nulls last) from EntityOfBasics e)", Integer.class )
.getResultList(); .getResultList();
assertEquals( 1, results.size() ); assertEquals( 1, results.size() );
} ); } );

View File

@ -28,15 +28,15 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Marco Belladelli * @author Marco Belladelli
*/ */
@DomainModel( annotatedClasses = { @DomainModel( annotatedClasses = {
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.AbstractSuperclass.class, EmbeddableInheritanceMappedSuperclassAndGenericsTest.AbstractSuperclass.class,
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.Range.class, EmbeddableInheritanceMappedSuperclassAndGenericsTest.Range.class,
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.IntegerRange.class, EmbeddableInheritanceMappedSuperclassAndGenericsTest.IntegerRange.class,
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.ToleranceRange.class, EmbeddableInheritanceMappedSuperclassAndGenericsTest.ToleranceRange.class,
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.TestEntity.class, EmbeddableInheritanceMappedSuperclassAndGenericsTest.TestEntity.class,
} ) } )
@SessionFactory @SessionFactory
@Jira( "https://hibernate.atlassian.net/browse/HHH-18172" ) @Jira( "https://hibernate.atlassian.net/browse/HHH-18172" )
public class EmbeddableInheritanceMappedSuperclassAdnGenericsTest { public class EmbeddableInheritanceMappedSuperclassAndGenericsTest {
@Test @Test
public void testFind(SessionFactoryScope scope) { public void testFind(SessionFactoryScope scope) {
scope.inTransaction( session -> { scope.inTransaction( session -> {
@ -62,9 +62,9 @@ public class EmbeddableInheritanceMappedSuperclassAdnGenericsTest {
@Test @Test
public void testQueryEmbeddable(SessionFactoryScope scope) { public void testQueryEmbeddable(SessionFactoryScope scope) {
scope.inTransaction( session -> { scope.inTransaction( session -> {
final IntegerRange result = session.createQuery( final Range<?> result = session.createQuery(
"select range from TestEntity where id = 1", "select range from TestEntity where id = 1",
IntegerRange.class Range.class
).getSingleResult(); ).getSingleResult();
assertThat( result.getName() ).isEqualTo( "tolerance_range" ); assertThat( result.getName() ).isEqualTo( "tolerance_range" );
assertThat( result ).isExactlyInstanceOf( ToleranceRange.class ); assertThat( result ).isExactlyInstanceOf( ToleranceRange.class );
@ -74,9 +74,9 @@ public class EmbeddableInheritanceMappedSuperclassAdnGenericsTest {
@Test @Test
public void testQueryJoinedEmbeddable(SessionFactoryScope scope) { public void testQueryJoinedEmbeddable(SessionFactoryScope scope) {
scope.inTransaction( session -> { scope.inTransaction( session -> {
final IntegerRange result = session.createQuery( final Range<?> result = session.createQuery(
"select r from TestEntity t join t.range r where id = 2", "select r from TestEntity t join t.range r where id = 2",
IntegerRange.class Range.class
).getSingleResult(); ).getSingleResult();
assertThat( result.getName() ).isEqualTo( "integer_range" ); assertThat( result.getName() ).isEqualTo( "integer_range" );
assertThat( result ).isExactlyInstanceOf( IntegerRange.class ); assertThat( result ).isExactlyInstanceOf( IntegerRange.class );

View File

@ -2,6 +2,8 @@ package org.hibernate.orm.test.jpa.criteria;
import java.util.List; import java.util.List;
import org.hibernate.query.QueryTypeMismatchException;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.Jpa;
@ -15,6 +17,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.fail;
@Jpa( @Jpa(
annotatedClasses = { annotatedClasses = {
@ -45,6 +48,7 @@ public class MultiSelectResultTypeTest {
List<Integer[]> idPairs = entityManager.createQuery( q ).getResultList(); List<Integer[]> idPairs = entityManager.createQuery( q ).getResultList();
assertThat( idPairs.size() ).isEqualTo( 1 ); assertThat( idPairs.size() ).isEqualTo( 1 );
Integer[] ids = idPairs.get( 0 ); Integer[] ids = idPairs.get( 0 );
assertThat( ids[0] ).isEqualTo( 1 );
} }
); );
} }
@ -60,6 +64,7 @@ public class MultiSelectResultTypeTest {
List<int[]> idPairs = entityManager.createQuery( q ).getResultList(); List<int[]> idPairs = entityManager.createQuery( q ).getResultList();
assertThat( idPairs.size() ).isEqualTo( 1 ); assertThat( idPairs.size() ).isEqualTo( 1 );
int[] ids = idPairs.get( 0 ); int[] ids = idPairs.get( 0 );
assertThat( ids[0] ).isEqualTo( 1 );
} }
); );
} }
@ -75,8 +80,8 @@ public class MultiSelectResultTypeTest {
List<Object[]> values = entityManager.createQuery( q ).getResultList(); List<Object[]> values = entityManager.createQuery( q ).getResultList();
assertThat( values.size() ).isEqualTo( 1 ); assertThat( values.size() ).isEqualTo( 1 );
Object[] value = values.get( 0 ); Object[] value = values.get( 0 );
Integer id = (Integer) value[0]; assertThat( value[0] ).isEqualTo( 1 );
String name = (String) value[1]; assertThat( value[1] ).isEqualTo( "a" );
} }
); );
} }
@ -91,7 +96,45 @@ public class MultiSelectResultTypeTest {
q.select( r.get( "id" ) ); q.select( r.get( "id" ) );
List<Integer> idPairs = entityManager.createQuery( q ).getResultList(); List<Integer> idPairs = entityManager.createQuery( q ).getResultList();
assertThat( idPairs.size() ).isEqualTo( 1 ); assertThat( idPairs.size() ).isEqualTo( 1 );
Integer id = idPairs.get( 0 ); assertThat( idPairs.get( 0 ) ).isEqualTo( 1 );
}
);
}
@Test
public void testValidateSelectItemAgainstArrayComponentType(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String[]> q = cb.createQuery( String[].class );
Root<TestEntity> r = q.from( TestEntity.class );
q.select( r.get( "id" ) );
try {
entityManager.createQuery( q );
fail( "Should fail with a type validation error" );
}
catch (QueryTypeMismatchException ex) {
assertThat( ex.getMessage() ).contains( String[].class.getName(), Integer.class.getName() );
}
}
);
}
@Test
public void testValidateSelectItemAgainstArrayComponentType2(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String[]> q = cb.createQuery( String[].class );
Root<TestEntity> r = q.from( TestEntity.class );
q.multiselect( r.get( "name" ), r.get( "id" ) );
try {
entityManager.createQuery( q );
fail( "Should fail with a type validation error" );
}
catch (QueryTypeMismatchException ex) {
assertThat( ex.getMessage() ).contains( String.class.getName(), Integer.class.getName() );
}
} }
); );
} }

View File

@ -21,54 +21,54 @@ public class TemporalParameterPlusDurationTest {
@Test @Test
void timestampVsTimestampParameterPlusDuration(SessionFactoryScope scope) { void timestampVsTimestampParameterPlusDuration(SessionFactoryScope scope) {
scope.inSession( session -> { scope.inSession( session -> {
session.createQuery( "select count(*) from SimpleEntity where inst > :i + 1 second + 2 second", SimpleEntity.class ) session.createQuery( "from SimpleEntity where inst > :i + 1 second + 2 second", SimpleEntity.class )
.setParameter( "i", Instant.now() ) .setParameter( "i", Instant.now() )
.getResultCount(); .getResultList();
} ); } );
} }
@Test @Test
void timestampParameterPlusDurationVsTimestamp(SessionFactoryScope scope) { void timestampParameterPlusDurationVsTimestamp(SessionFactoryScope scope) {
scope.inSession( session -> { scope.inSession( session -> {
session.createQuery( "select count(*) from SimpleEntity where :i + 1 second + 2 second > inst", SimpleEntity.class ) session.createQuery( "from SimpleEntity where :i + 1 second + 2 second > inst", SimpleEntity.class )
.setParameter( "i", Instant.now() ) .setParameter( "i", Instant.now() )
.getResultCount(); .getResultList();
} ); } );
} }
@Test @Test
void dateVsDateParameterPlusDuration(SessionFactoryScope scope) { void dateVsDateParameterPlusDuration(SessionFactoryScope scope) {
scope.inSession( session -> { scope.inSession( session -> {
session.createQuery( "select count(*) from SimpleEntity where ldate > :i + 3 day + 2 day", SimpleEntity.class ) session.createQuery( "from SimpleEntity where ldate > :i + 3 day + 2 day", SimpleEntity.class )
.setParameter( "i", LocalDate.now() ) .setParameter( "i", LocalDate.now() )
.getResultCount(); .getResultList();
} ); } );
} }
@Test @Test
void dateParameterPlusDurationVsDate(SessionFactoryScope scope) { void dateParameterPlusDurationVsDate(SessionFactoryScope scope) {
scope.inSession( session -> { scope.inSession( session -> {
session.createQuery( "select count(*) from SimpleEntity where :i + 3 day + 2 day > ldate", SimpleEntity.class ) session.createQuery( "from SimpleEntity where :i + 3 day + 2 day > ldate", SimpleEntity.class )
.setParameter( "i", LocalDate.now() ) .setParameter( "i", LocalDate.now() )
.getResultCount(); .getResultList();
} ); } );
} }
@Test @Test
void durationVsDurationParameterPlusDuration(SessionFactoryScope scope) { void durationVsDurationParameterPlusDuration(SessionFactoryScope scope) {
scope.inSession( session -> { scope.inSession( session -> {
session.createQuery( "select count(*) from SimpleEntity where dur > :i + 1 second", Number.class ) session.createQuery( "from SimpleEntity where dur > :i + 1 second", SimpleEntity.class )
.setParameter( "i", Duration.ofMinutes( 1 ) ) .setParameter( "i", Duration.ofMinutes( 1 ) )
.getResultCount(); .getResultList();
} ); } );
} }
@Test @Test
void durationParameterVsDurationPlusDuration(SessionFactoryScope scope) { void durationParameterVsDurationPlusDuration(SessionFactoryScope scope) {
scope.inSession( session -> { scope.inSession( session -> {
session.createQuery( "select count(*) from SimpleEntity where :i + 1 second > dur", Number.class ) session.createQuery( "from SimpleEntity where :i + 1 second > dur", SimpleEntity.class )
.setParameter( "i", Duration.ofMinutes( 1 ) ) .setParameter( "i", Duration.ofMinutes( 1 ) )
.getResultCount(); .getResultList();
} ); } );
} }

View File

@ -2105,7 +2105,7 @@ public class FunctionTests {
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class, comment = "We extract the offset with a format function") @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class, comment = "We extract the offset with a format function")
public void testExtractFunctionTimeZoneOffset(SessionFactoryScope scope) { public void testExtractFunctionTimeZoneOffset(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> session.createQuery( "select extract(offset from e.theZonedDateTime) from EntityOfBasics e", Integer.class) session -> session.createQuery( "select extract(offset from e.theZonedDateTime) from EntityOfBasics e", ZoneOffset.class)
.list() .list()
); );
} }