HHH-17956 Criteria multiselect ignores type of the criteria query and always returns list of Object[]
This commit is contained in:
parent
f5062b2aef
commit
a567226a72
|
@ -740,9 +740,9 @@ The following functions are abbreviations for `extract()`:
|
|||
| `year(x)` | `extract(year from x)` | ✖
|
||||
| `month(x)` | `extract(month from x)` | ✖
|
||||
| `day(x)` | `extract(day from x)` | ✖
|
||||
| `hour(x)` | `extract(year from x)` | ✖
|
||||
| `minute(x)` | `extract(year from x)` | ✖
|
||||
| `second(x)` | `extract(year from x)` | ✖
|
||||
| `hour(x)` | `extract(hour from x)` | ✖
|
||||
| `minute(x)` | `extract(minute 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.
|
||||
|
|
|
@ -41,7 +41,7 @@ import static org.hibernate.type.SqlTypes.*;
|
|||
*
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
class SumReturnTypeResolver implements FunctionReturnTypeResolver {
|
||||
public class SumReturnTypeResolver implements FunctionReturnTypeResolver {
|
||||
|
||||
private final BasicType<Long> longType;
|
||||
private final BasicType<Double> doubleType;
|
||||
|
|
|
@ -115,6 +115,8 @@ import jakarta.persistence.criteria.CriteriaDelete;
|
|||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.CriteriaUpdate;
|
||||
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 org.hibernate.internal.util.ReflectHelper.isClass;
|
||||
|
@ -962,8 +964,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
query.addEntity( resultClass, LockMode.READ );
|
||||
}
|
||||
else if ( resultClass != Object.class && resultClass != Object[].class ) {
|
||||
if ( isClass( resultClass )
|
||||
&& getTypeConfiguration().getJavaTypeRegistry().findDescriptor( resultClass ) == null ) {
|
||||
if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) {
|
||||
// not a basic type
|
||||
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
|
||||
public <T> NativeQueryImplementor<T> createNativeQuery(String sqlString, Class<T> resultClass, String tableAlias) {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -30,6 +30,7 @@ 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 org.hibernate.CacheMode;
|
||||
import org.hibernate.FlushMode;
|
||||
|
@ -260,12 +261,11 @@ public abstract class AbstractSelectionQuery<R>
|
|||
SqmQueryPart<R> queryPart,
|
||||
Class<R> expectedResultType,
|
||||
SessionFactoryImplementor factory) {
|
||||
assert getQueryString().equals( CRITERIA_HQL_STRING );
|
||||
|
||||
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();
|
||||
|
@ -280,6 +280,7 @@ public abstract class AbstractSelectionQuery<R>
|
|||
throw new IllegalArgumentException( "Criteria has multiple query roots" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( expectedResultType != null ) {
|
||||
checkQueryReturnType( sqmQuerySpec, expectedResultType, factory );
|
||||
|
@ -302,8 +303,34 @@ public abstract class AbstractSelectionQuery<R>
|
|||
if ( selections.size() == 1 ) {
|
||||
// we have one item in the select list,
|
||||
// the type has to match (no instantiation)
|
||||
final SqmSelection<?> sqmSelection = selections.get(0);
|
||||
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 {
|
||||
verifySelectionType( expectedResultClass, sessionFactory, sqmSelection.getSelectableNode() );
|
||||
}
|
||||
}
|
||||
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!
|
||||
}
|
||||
}
|
||||
|
||||
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 ) {
|
||||
|
@ -317,10 +344,27 @@ public abstract class AbstractSelectionQuery<R>
|
|||
}
|
||||
|
||||
if ( !sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
|
||||
verifyResultType( expectedResultClass, sqmSelection.getExpressible() );
|
||||
verifyResultType( expectedResultClass, selection.getExpressible() );
|
||||
}
|
||||
}
|
||||
// else, let's assume we can instantiate it!
|
||||
|
||||
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() );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,17 +378,18 @@ public abstract class AbstractSelectionQuery<R>
|
|||
private static <T> boolean isResultTypeAlwaysAllowed(Class<T> expectedResultClass) {
|
||||
return expectedResultClass == null
|
||||
|| expectedResultClass == Object.class
|
||||
|| expectedResultClass == Object[].class
|
||||
|| expectedResultClass == List.class
|
||||
|| expectedResultClass == Tuple.class
|
||||
|| expectedResultClass.isArray();
|
||||
|| expectedResultClass == Map.class
|
||||
|| expectedResultClass == Tuple.class;
|
||||
}
|
||||
|
||||
protected static <T> void verifyResultType(Class<T> resultClass, SqmExpressible<?> sqmExpressible) {
|
||||
assert sqmExpressible != null;
|
||||
protected static <T> void verifyResultType(Class<T> resultClass, @Nullable SqmExpressible<?> sqmExpressible) {
|
||||
if ( sqmExpressible != null ) {
|
||||
final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType();
|
||||
assert expressibleJavaType != null;
|
||||
final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass();
|
||||
if ( !resultClass.isAssignableFrom( javaTypeClass ) ) {
|
||||
if ( javaTypeClass != Object.class && !resultClass.isAssignableFrom( javaTypeClass ) ) {
|
||||
if ( expressibleJavaType instanceof PrimitiveJavaType ) {
|
||||
final PrimitiveJavaType<?> javaType = (PrimitiveJavaType<?>) expressibleJavaType;
|
||||
if ( javaType.getPrimitiveClass() != resultClass ) {
|
||||
|
@ -357,6 +402,7 @@ public abstract class AbstractSelectionQuery<R>
|
|||
// else special case, we are good
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -38,4 +38,8 @@ public interface DomainQueryExecutionContext {
|
|||
* The underlying session
|
||||
*/
|
||||
SharedSessionContractImplementor getSession();
|
||||
|
||||
default Class<?> getResultType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,11 +137,13 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
|||
jdbcParameterBindings
|
||||
);
|
||||
session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true );
|
||||
//noinspection unchecked
|
||||
return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
|
||||
jdbcSelect,
|
||||
jdbcParameterBindings,
|
||||
listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ),
|
||||
rowTransformer,
|
||||
(Class<R>) executionContext.getResultType(),
|
||||
uniqueSemantic
|
||||
);
|
||||
}
|
||||
|
|
|
@ -251,34 +251,7 @@ public class QuerySqmImpl<R>
|
|||
bindCriteriaParameter((SqmJpaCriteriaParameterWrapper<?>) sqmParameter);
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
validateStatement( sqm, expectedResultType );
|
||||
|
||||
resultType = expectedResultType;
|
||||
tupleMetadata = buildTupleMetadata( criteria, expectedResultType );
|
||||
|
@ -298,7 +271,12 @@ public class QuerySqmImpl<R>
|
|||
|
||||
private void validateStatement(SqmStatement<R> sqmStatement, Class<R> resultType) {
|
||||
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 {
|
||||
if ( resultType != null ) {
|
||||
|
@ -307,6 +285,10 @@ public class QuerySqmImpl<R>
|
|||
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<?> ) {
|
||||
|
|
|
@ -34,6 +34,8 @@ import java.util.Set;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
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.engine.spi.SessionFactoryImplementor;
|
||||
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.SqmFunctionDescriptor;
|
||||
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.spi.SqmCreationContext;
|
||||
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<Long> longType;
|
||||
private transient BasicType<Character> characterType;
|
||||
private transient FunctionReturnTypeResolver sumReturnTypeResolver;
|
||||
private transient FunctionReturnTypeResolver avgReturnTypeResolver;
|
||||
private final transient Map<Class<? extends HibernateCriteriaBuilder>, HibernateCriteriaBuilder> extensions;
|
||||
|
||||
public SqmCriteriaNodeBuilder(
|
||||
|
@ -248,7 +253,6 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public BasicType<Boolean> getBooleanType() {
|
||||
final BasicType<Boolean> booleanType = this.booleanType;
|
||||
|
@ -293,6 +297,22 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
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
|
||||
public QueryEngine getQueryEngine() {
|
||||
return queryEngine;
|
||||
|
|
|
@ -6,17 +6,23 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.tree.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.hql.spi.SqmCreationState;
|
||||
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.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SqmElementAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> {
|
||||
private final String functionName;
|
||||
private final ReturnableType<T> returnableType;
|
||||
|
||||
public SqmElementAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) {
|
||||
//noinspection unchecked
|
||||
|
@ -27,6 +33,46 @@ public class SqmElementAggregateFunction<T> extends AbstractSqmSpecificPluralPar
|
|||
( (PluralPersistentAttribute<?, ?, T>) pluralDomainPath.getReferencedPathSource() ).getElementPathSource()
|
||||
);
|
||||
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
|
||||
|
|
|
@ -6,17 +6,25 @@
|
|||
*/
|
||||
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.query.ReturnableType;
|
||||
import org.hibernate.query.hql.spi.SqmCreationState;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.SqmPathSource;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SqmIndexAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> {
|
||||
private final String functionName;
|
||||
private final ReturnableType<T> returnableType;
|
||||
|
||||
public SqmIndexAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) {
|
||||
//noinspection unchecked
|
||||
|
@ -27,6 +35,46 @@ public class SqmIndexAggregateFunction<T> extends AbstractSqmSpecificPluralPartP
|
|||
(SqmPathSource<T>) ( (PluralPersistentAttribute<?, ?, ?>) pluralDomainPath.getReferencedPathSource() ).getIndexPathSource()
|
||||
);
|
||||
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
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.sql.results.internal;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -41,6 +42,9 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
private final RowTransformer<T> rowTransformer;
|
||||
private final Class<T> domainResultJavaType;
|
||||
|
||||
private final ComponentType componentType;
|
||||
private final Class<?> resultElementClass;
|
||||
|
||||
private static final Logger LOGGER = LoadingLogger.LOGGER;
|
||||
|
||||
public StandardRowReader(
|
||||
|
@ -76,6 +80,19 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
this.hasCollectionInitializers = hasCollectionInitializers;
|
||||
this.rowTransformer = rowTransformer;
|
||||
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
|
||||
|
@ -84,16 +101,7 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getResultJavaType() {
|
||||
if ( resultAssemblers.length == 1 ) {
|
||||
return resultAssemblers[0].getAssembledJavaType().getJavaTypeClass();
|
||||
}
|
||||
|
||||
return Object[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaType<?>> getResultJavaTypes() {
|
||||
public List<@Nullable JavaType<?>> getResultJavaTypes() {
|
||||
List<JavaType<?>> javaTypes = new ArrayList<>( resultAssemblers.length );
|
||||
for ( DomainResultAssembler resultAssembler : resultAssemblers ) {
|
||||
javaTypes.add( resultAssembler.getAssembledJavaType() );
|
||||
|
@ -128,7 +136,101 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
public T readRow(RowProcessingState 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++ ) {
|
||||
final DomainResultAssembler assembler = resultAssemblers[i];
|
||||
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];
|
||||
|
@ -139,6 +241,7 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
|
||||
return rowTransformer.transformRow( resultRow );
|
||||
}
|
||||
}
|
||||
|
||||
private void afterRow(RowProcessingState rowProcessingState) {
|
||||
finishUpRow();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import org.hibernate.type.descriptor.java.spi.EntityJavaType;
|
|||
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* ResultsConsumer for creating a List of results
|
||||
*
|
||||
|
@ -247,22 +249,37 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
|||
|
||||
private JavaType<R> resolveDomainResultJavaType(
|
||||
Class<R> domainResultResultJavaType,
|
||||
List<JavaType<?>> resultJavaTypes,
|
||||
List<@Nullable JavaType<?>> resultJavaTypes,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
|
||||
|
||||
if ( domainResultResultJavaType != null ) {
|
||||
return javaTypeRegistry.resolveDescriptor( domainResultResultJavaType );
|
||||
}
|
||||
|
||||
if ( resultJavaTypes.size() == 1 ) {
|
||||
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 ) {
|
||||
final JavaType<?> firstJavaType = resultJavaTypes.get( 0 );
|
||||
if ( firstJavaType == null ) {
|
||||
return javaTypeRegistry.resolveDescriptor( Object.class );
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (JavaType<R>) firstJavaType;
|
||||
}
|
||||
|
||||
return javaTypeRegistry.resolveDescriptor( Object[].class );
|
||||
}
|
||||
|
||||
private static boolean isMoreConcrete(JavaType<?> resultJavaType, @Nullable JavaType<?> javaType) {
|
||||
return javaType != null && resultJavaType.getJavaTypeClass().isAssignableFrom( javaType.getJavaTypeClass() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canResultsBeCached() {
|
||||
return true;
|
||||
|
|
|
@ -30,19 +30,10 @@ public interface RowReader<R> {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
List<JavaType<?>> getResultJavaTypes();
|
||||
List<@Nullable JavaType<?>> getResultJavaTypes();
|
||||
|
||||
int getInitializerCount();
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ public class ArrayAggregateTest {
|
|||
@Test
|
||||
public void testCompareAgainstArray(SessionFactoryScope scope) {
|
||||
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();
|
||||
assertEquals( 1, results.size() );
|
||||
} );
|
||||
|
|
|
@ -28,15 +28,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
* @author Marco Belladelli
|
||||
*/
|
||||
@DomainModel( annotatedClasses = {
|
||||
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.AbstractSuperclass.class,
|
||||
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.Range.class,
|
||||
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.IntegerRange.class,
|
||||
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.ToleranceRange.class,
|
||||
EmbeddableInheritanceMappedSuperclassAdnGenericsTest.TestEntity.class,
|
||||
EmbeddableInheritanceMappedSuperclassAndGenericsTest.AbstractSuperclass.class,
|
||||
EmbeddableInheritanceMappedSuperclassAndGenericsTest.Range.class,
|
||||
EmbeddableInheritanceMappedSuperclassAndGenericsTest.IntegerRange.class,
|
||||
EmbeddableInheritanceMappedSuperclassAndGenericsTest.ToleranceRange.class,
|
||||
EmbeddableInheritanceMappedSuperclassAndGenericsTest.TestEntity.class,
|
||||
} )
|
||||
@SessionFactory
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-18172" )
|
||||
public class EmbeddableInheritanceMappedSuperclassAdnGenericsTest {
|
||||
public class EmbeddableInheritanceMappedSuperclassAndGenericsTest {
|
||||
@Test
|
||||
public void testFind(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
|
@ -62,9 +62,9 @@ public class EmbeddableInheritanceMappedSuperclassAdnGenericsTest {
|
|||
@Test
|
||||
public void testQueryEmbeddable(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
final IntegerRange result = session.createQuery(
|
||||
final Range<?> result = session.createQuery(
|
||||
"select range from TestEntity where id = 1",
|
||||
IntegerRange.class
|
||||
Range.class
|
||||
).getSingleResult();
|
||||
assertThat( result.getName() ).isEqualTo( "tolerance_range" );
|
||||
assertThat( result ).isExactlyInstanceOf( ToleranceRange.class );
|
||||
|
@ -74,9 +74,9 @@ public class EmbeddableInheritanceMappedSuperclassAdnGenericsTest {
|
|||
@Test
|
||||
public void testQueryJoinedEmbeddable(SessionFactoryScope scope) {
|
||||
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",
|
||||
IntegerRange.class
|
||||
Range.class
|
||||
).getSingleResult();
|
||||
assertThat( result.getName() ).isEqualTo( "integer_range" );
|
||||
assertThat( result ).isExactlyInstanceOf( IntegerRange.class );
|
|
@ -2,6 +2,8 @@ package org.hibernate.orm.test.jpa.criteria;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.query.QueryTypeMismatchException;
|
||||
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
|
@ -15,6 +17,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
|
|||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.fail;
|
||||
|
||||
@Jpa(
|
||||
annotatedClasses = {
|
||||
|
@ -45,6 +48,7 @@ public class MultiSelectResultTypeTest {
|
|||
List<Integer[]> idPairs = entityManager.createQuery( q ).getResultList();
|
||||
assertThat( idPairs.size() ).isEqualTo( 1 );
|
||||
Integer[] ids = idPairs.get( 0 );
|
||||
assertThat( ids[0] ).isEqualTo( 1 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -60,6 +64,7 @@ public class MultiSelectResultTypeTest {
|
|||
List<int[]> idPairs = entityManager.createQuery( q ).getResultList();
|
||||
assertThat( idPairs.size() ).isEqualTo( 1 );
|
||||
int[] ids = idPairs.get( 0 );
|
||||
assertThat( ids[0] ).isEqualTo( 1 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -75,8 +80,8 @@ public class MultiSelectResultTypeTest {
|
|||
List<Object[]> values = entityManager.createQuery( q ).getResultList();
|
||||
assertThat( values.size() ).isEqualTo( 1 );
|
||||
Object[] value = values.get( 0 );
|
||||
Integer id = (Integer) value[0];
|
||||
String name = (String) value[1];
|
||||
assertThat( value[0] ).isEqualTo( 1 );
|
||||
assertThat( value[1] ).isEqualTo( "a" );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -91,7 +96,45 @@ public class MultiSelectResultTypeTest {
|
|||
q.select( r.get( "id" ) );
|
||||
List<Integer> idPairs = entityManager.createQuery( q ).getResultList();
|
||||
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() );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,54 +21,54 @@ public class TemporalParameterPlusDurationTest {
|
|||
@Test
|
||||
void timestampVsTimestampParameterPlusDuration(SessionFactoryScope scope) {
|
||||
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() )
|
||||
.getResultCount();
|
||||
.getResultList();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void timestampParameterPlusDurationVsTimestamp(SessionFactoryScope scope) {
|
||||
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() )
|
||||
.getResultCount();
|
||||
.getResultList();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void dateVsDateParameterPlusDuration(SessionFactoryScope scope) {
|
||||
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() )
|
||||
.getResultCount();
|
||||
.getResultList();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void dateParameterPlusDurationVsDate(SessionFactoryScope scope) {
|
||||
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() )
|
||||
.getResultCount();
|
||||
.getResultList();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void durationVsDurationParameterPlusDuration(SessionFactoryScope scope) {
|
||||
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 ) )
|
||||
.getResultCount();
|
||||
.getResultList();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void durationParameterVsDurationPlusDuration(SessionFactoryScope scope) {
|
||||
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 ) )
|
||||
.getResultCount();
|
||||
.getResultList();
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
@ -2105,7 +2105,7 @@ public class FunctionTests {
|
|||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class, comment = "We extract the offset with a format function")
|
||||
public void testExtractFunctionTimeZoneOffset(SessionFactoryScope scope) {
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue