HHH-16710 implicit instantiation of record classes
This commit is contained in:
parent
87a2b967c7
commit
72f03d9d0f
|
@ -748,7 +748,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
||||||
throw new QueryTypeMismatchException(
|
throw new QueryTypeMismatchException(
|
||||||
String.format(
|
String.format(
|
||||||
Locale.ROOT,
|
Locale.ROOT,
|
||||||
"Query result-type error - expecting `%s`, but found `%s`",
|
"Incorrect query result type: query produces '%s' but type '%s' was given",
|
||||||
expectedResultType.getName(),
|
expectedResultType.getName(),
|
||||||
resultType.getName()
|
resultType.getName()
|
||||||
)
|
)
|
||||||
|
|
|
@ -66,6 +66,7 @@ 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;
|
||||||
|
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||||
import org.hibernate.query.sqm.tree.select.SqmSelection;
|
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;
|
||||||
|
@ -104,33 +105,36 @@ public abstract class AbstractSelectionQuery<R>
|
||||||
super( session );
|
super( session );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isTupleResultClass(Class<?> resultType) {
|
|
||||||
return Tuple.class.isAssignableFrom( resultType )
|
|
||||||
|| Map.class.isAssignableFrom( resultType );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TupleMetadata buildTupleMetadata(SqmStatement<?> statement, Class<R> resultType) {
|
protected TupleMetadata buildTupleMetadata(SqmStatement<?> statement, Class<R> resultType) {
|
||||||
if ( resultType == null ) {
|
if ( isInstantiableWithoutMetadata( resultType ) ) {
|
||||||
|
// no need to build metadata for instantiating tuples
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else if ( isTupleResultClass( resultType ) ) {
|
|
||||||
final List<SqmSelection<?>> selections =
|
|
||||||
( (SqmSelectStatement<?>) statement ).getQueryPart()
|
|
||||||
.getFirstQuerySpec()
|
|
||||||
.getSelectClause()
|
|
||||||
.getSelections();
|
|
||||||
if ( getQueryOptions().getTupleTransformer() == null ) {
|
|
||||||
return new TupleMetadata( buildTupleElementMap( selections ) );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer : " +
|
|
||||||
getQueryOptions().getTupleTransformer()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
return null;
|
final SqmSelectStatement<?> select = (SqmSelectStatement<?>) statement;
|
||||||
|
final List<SqmSelection<?>> selections =
|
||||||
|
select.getQueryPart().getFirstQuerySpec().getSelectClause()
|
||||||
|
.getSelections();
|
||||||
|
if ( Tuple.class.equals( resultType ) || selections.size() > 1 ) {
|
||||||
|
return getTupleMetadata( selections );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// only one element in select list,
|
||||||
|
// we don't support instantiation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TupleMetadata getTupleMetadata(List<SqmSelection<?>> selections) {
|
||||||
|
if ( getQueryOptions().getTupleTransformer() == null ) {
|
||||||
|
return new TupleMetadata( buildTupleElementMap( selections ) );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer : " +
|
||||||
|
getQueryOptions().getTupleTransformer()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,55 +267,52 @@ public abstract class AbstractSelectionQuery<R>
|
||||||
SqmQuerySpec<T> querySpec,
|
SqmQuerySpec<T> querySpec,
|
||||||
Class<T> expectedResultClass,
|
Class<T> expectedResultClass,
|
||||||
SessionFactoryImplementor sessionFactory) {
|
SessionFactoryImplementor sessionFactory) {
|
||||||
if ( expectedResultClass == null || expectedResultClass == Object.class ) {
|
if ( !isResultTypeAlwaysAllowed( expectedResultClass ) ) {
|
||||||
// nothing to check
|
final List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections();
|
||||||
return;
|
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 List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections();
|
// special case for parameters in the select list
|
||||||
|
final SqmSelectableNode<?> selection = sqmSelection.getSelectableNode();
|
||||||
if ( expectedResultClass.isArray() ) {
|
if ( selection instanceof SqmParameter ) {
|
||||||
// todo (6.0) : implement
|
final SqmParameter<?> sqmParameter = (SqmParameter<?>) selection;
|
||||||
}
|
final SqmExpressible<?> nodeType = sqmParameter.getNodeType();
|
||||||
else if ( Tuple.class.isAssignableFrom( expectedResultClass )
|
// we may not yet know a selection type
|
||||||
|| Map.class.isAssignableFrom( expectedResultClass )
|
if ( nodeType == null || nodeType.getExpressibleJavaType() == null ) {
|
||||||
|| List.class.isAssignableFrom( expectedResultClass ) ) {
|
// we can't verify the result type up front
|
||||||
// todo (6.0) : implement
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
final boolean jpaQueryComplianceEnabled = sessionFactory.getSessionFactoryOptions()
|
|
||||||
.getJpaCompliance()
|
|
||||||
.isJpaQueryComplianceEnabled();
|
|
||||||
if ( selections.size() != 1 ) {
|
|
||||||
final String errorMessage = "Query result-type error - multiple selections: use Tuple or array";
|
|
||||||
|
|
||||||
if ( jpaQueryComplianceEnabled ) {
|
|
||||||
throw new IllegalArgumentException( errorMessage );
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
throw new QueryTypeMismatchException( errorMessage );
|
final boolean jpaQueryComplianceEnabled =
|
||||||
|
sessionFactory.getSessionFactoryOptions()
|
||||||
|
.getJpaCompliance()
|
||||||
|
.isJpaQueryComplianceEnabled();
|
||||||
|
if ( !jpaQueryComplianceEnabled ) {
|
||||||
|
verifyResultType( expectedResultClass, sqmSelection.getNodeType() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// else, let's assume we can instantiate it!
|
||||||
final SqmSelection<?> sqmSelection = selections.get( 0 );
|
|
||||||
|
|
||||||
if ( sqmSelection.getSelectableNode() instanceof SqmParameter ) {
|
|
||||||
final SqmParameter<?> sqmParameter = (SqmParameter<?>) sqmSelection.getSelectableNode();
|
|
||||||
|
|
||||||
// we may not yet know a selection type
|
|
||||||
if ( sqmParameter.getNodeType() == null || sqmParameter.getNodeType().getExpressibleJavaType() == null ) {
|
|
||||||
// we can't verify the result type up front
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( jpaQueryComplianceEnabled ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
verifyResultType( expectedResultClass, sqmSelection.getNodeType() );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 == List.class
|
||||||
|
|| expectedResultClass == Tuple.class
|
||||||
|
|| expectedResultClass.isArray();
|
||||||
|
}
|
||||||
|
|
||||||
protected static <T> void verifyResultType(Class<T> resultClass, SqmExpressible<?> sqmExpressible) {
|
protected static <T> void verifyResultType(Class<T> resultClass, SqmExpressible<?> sqmExpressible) {
|
||||||
assert sqmExpressible != null;
|
assert sqmExpressible != null;
|
||||||
final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType();
|
final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType();
|
||||||
|
@ -319,48 +320,65 @@ public abstract class AbstractSelectionQuery<R>
|
||||||
final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass();
|
final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass();
|
||||||
if ( !resultClass.isAssignableFrom( javaTypeClass ) ) {
|
if ( !resultClass.isAssignableFrom( javaTypeClass ) ) {
|
||||||
if ( expressibleJavaType instanceof PrimitiveJavaType ) {
|
if ( expressibleJavaType instanceof PrimitiveJavaType ) {
|
||||||
if ( ( (PrimitiveJavaType<?>) expressibleJavaType ).getPrimitiveClass() == resultClass ) {
|
if ( ( (PrimitiveJavaType<?>) expressibleJavaType ).getPrimitiveClass() != resultClass ) {
|
||||||
return;
|
throwQueryTypeMismatchException( resultClass, sqmExpressible );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if ( isMatchingDateType( javaTypeClass, resultClass, sqmExpressible ) ) {
|
||||||
|
// special case, we are good
|
||||||
|
}
|
||||||
|
else {
|
||||||
throwQueryTypeMismatchException( resultClass, sqmExpressible );
|
throwQueryTypeMismatchException( resultClass, sqmExpressible );
|
||||||
}
|
}
|
||||||
// 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
|
|
||||||
if ( javaTypeClass == Date.class ) {
|
|
||||||
JdbcType jdbcType = null;
|
|
||||||
if ( sqmExpressible instanceof BasicDomainType<?> ) {
|
|
||||||
jdbcType = ( (BasicDomainType<?>) sqmExpressible).getJdbcType();
|
|
||||||
}
|
|
||||||
else if ( sqmExpressible instanceof SqmPathSource<?> ) {
|
|
||||||
final DomainType<?> domainType = ( (SqmPathSource<?>) sqmExpressible).getSqmPathType();
|
|
||||||
if ( domainType instanceof BasicDomainType<?> ) {
|
|
||||||
jdbcType = ( (BasicDomainType<?>) domainType ).getJdbcType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( jdbcType != null ) {
|
|
||||||
switch ( jdbcType.getDefaultSqlTypeCode() ) {
|
|
||||||
case Types.DATE:
|
|
||||||
if ( resultClass.isAssignableFrom( java.sql.Date.class ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Types.TIME:
|
|
||||||
if ( resultClass.isAssignableFrom( java.sql.Time.class ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Types.TIMESTAMP:
|
|
||||||
if ( resultClass.isAssignableFrom( java.sql.Timestamp.class ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throwQueryTypeMismatchException( resultClass, sqmExpressible );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 DomainType<?> domainType = ( (SqmPathSource<?>) sqmExpressible).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:
|
||||||
|
if ( resultClass.isAssignableFrom( java.sql.Date.class ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIME:
|
||||||
|
if ( resultClass.isAssignableFrom( java.sql.Time.class ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Types.TIMESTAMP:
|
||||||
|
if ( resultClass.isAssignableFrom( java.sql.Timestamp.class ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static <T> void throwQueryTypeMismatchException(Class<T> resultClass, SqmExpressible<?> sqmExpressible) {
|
private static <T> void throwQueryTypeMismatchException(Class<T> resultClass, SqmExpressible<?> sqmExpressible) {
|
||||||
final String errorMessage = String.format(
|
final String errorMessage = String.format(
|
||||||
"Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array",
|
"Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array",
|
||||||
|
|
|
@ -23,7 +23,6 @@ import org.hibernate.engine.spi.SubselectFetch;
|
||||||
import org.hibernate.internal.EmptyScrollableResults;
|
import org.hibernate.internal.EmptyScrollableResults;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||||
import org.hibernate.query.IllegalQueryOperationException;
|
|
||||||
import org.hibernate.query.Query;
|
import org.hibernate.query.Query;
|
||||||
import org.hibernate.query.TupleTransformer;
|
import org.hibernate.query.TupleTransformer;
|
||||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
|
@ -50,6 +49,7 @@ import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||||
import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
|
import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
|
||||||
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
|
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
|
||||||
import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
|
import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
|
||||||
|
import org.hibernate.sql.results.internal.RowTransformerConstructorImpl;
|
||||||
import org.hibernate.sql.results.internal.RowTransformerJpaTupleImpl;
|
import org.hibernate.sql.results.internal.RowTransformerJpaTupleImpl;
|
||||||
import org.hibernate.sql.results.internal.RowTransformerListImpl;
|
import org.hibernate.sql.results.internal.RowTransformerListImpl;
|
||||||
import org.hibernate.sql.results.internal.RowTransformerMapImpl;
|
import org.hibernate.sql.results.internal.RowTransformerMapImpl;
|
||||||
|
@ -178,44 +178,40 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
||||||
if ( queryOptions.getTupleTransformer() != null ) {
|
if ( queryOptions.getTupleTransformer() != null ) {
|
||||||
return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions );
|
return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions );
|
||||||
}
|
}
|
||||||
|
else if ( resultType == null ) {
|
||||||
if ( resultType == null ) {
|
|
||||||
return RowTransformerStandardImpl.instance();
|
return RowTransformerStandardImpl.instance();
|
||||||
}
|
}
|
||||||
|
else if ( resultType == Object[].class ) {
|
||||||
if ( resultType == Object[].class ) {
|
|
||||||
return (RowTransformer<T>) RowTransformerArrayImpl.instance();
|
return (RowTransformer<T>) RowTransformerArrayImpl.instance();
|
||||||
}
|
}
|
||||||
else if ( List.class.equals( resultType ) ) {
|
else if ( resultType == List.class ) {
|
||||||
return (RowTransformer<T>) RowTransformerListImpl.instance();
|
return (RowTransformer<T>) RowTransformerListImpl.instance();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// NOTE : if we get here :
|
||||||
|
// 1) there is no TupleTransformer specified
|
||||||
|
// 2) an explicit result-type, other than an array, was specified
|
||||||
|
|
||||||
// NOTE : if we get here :
|
if ( tupleMetadata == null ) {
|
||||||
// 1) there is no TupleTransformer specified
|
if ( sqm.getQueryPart().getFirstQuerySpec().getSelectClause().getSelections().size() == 1 ) {
|
||||||
// 2) an explicit result-type, other than an array, was specified
|
return RowTransformerSingularReturnImpl.instance();
|
||||||
|
}
|
||||||
final List<SqmSelection<?>> selections =
|
else {
|
||||||
sqm.getQueryPart().getFirstQuerySpec().getSelectClause().getSelections();
|
throw new AssertionFailure( "Query defined multiple selections, should have had TupleMetadata" );
|
||||||
if ( tupleMetadata != null ) {
|
}
|
||||||
if ( Tuple.class.equals( resultType ) ) {
|
|
||||||
return (RowTransformer<T>) new RowTransformerJpaTupleImpl( tupleMetadata );
|
|
||||||
}
|
|
||||||
else if ( Map.class.equals( resultType ) ) {
|
|
||||||
return (RowTransformer<T>) new RowTransformerMapImpl( tupleMetadata );
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new AssertionFailure( "Wrong result type for tuple handling: " + resultType );
|
if ( Tuple.class.equals( resultType ) ) {
|
||||||
|
return (RowTransformer<T>) new RowTransformerJpaTupleImpl( tupleMetadata );
|
||||||
|
}
|
||||||
|
else if ( Map.class.equals( resultType ) ) {
|
||||||
|
return (RowTransformer<T>) new RowTransformerMapImpl( tupleMetadata );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new RowTransformerConstructorImpl<>( resultType, tupleMetadata );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE : if we get here we have a resultType of some kind
|
|
||||||
|
|
||||||
if ( selections.size() > 1 ) {
|
|
||||||
throw new IllegalQueryOperationException( "Query defined multiple selections, return cannot be typed (other that Object[] or Tuple)" );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return RowTransformerSingularReturnImpl.instance();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> RowTransformer<T> makeRowTransformerTupleTransformerAdapter(
|
private static <T> RowTransformer<T> makeRowTransformerTupleTransformerAdapter(
|
||||||
|
|
|
@ -60,6 +60,7 @@ import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||||
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;
|
||||||
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE;
|
import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE;
|
||||||
import static org.hibernate.jpa.HibernateHints.HINT_CACHE_MODE;
|
import static org.hibernate.jpa.HibernateHints.HINT_CACHE_MODE;
|
||||||
|
@ -114,25 +115,35 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> determineResultType(SqmSelectStatement<?> sqm) {
|
private Class<?> determineResultType(SqmSelectStatement<?> sqm) {
|
||||||
if ( expectedResultType != null ) {
|
final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections();
|
||||||
if ( expectedResultType.isArray() ) {
|
if ( selections.size() == 1 ) {
|
||||||
|
if ( Object[].class.equals( expectedResultType ) ) {
|
||||||
|
// for JPA compatibility
|
||||||
return Object[].class;
|
return Object[].class;
|
||||||
}
|
}
|
||||||
else if ( List.class.isAssignableFrom( expectedResultType ) ) {
|
|
||||||
return expectedResultType;
|
|
||||||
}
|
|
||||||
else if ( isTupleResultClass( expectedResultType ) ) {
|
|
||||||
return expectedResultType;
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
return Object[].class;
|
final SqmSelection<?> selection = selections.get(0);
|
||||||
|
if ( selection!=null ) {
|
||||||
|
JavaType<?> javaType = selection.getNodeJavaType();
|
||||||
|
if ( javaType != null) {
|
||||||
|
return javaType.getJavaTypeClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// due to some error in the query,
|
||||||
|
// we don't have any information,
|
||||||
|
// so just let it through so the
|
||||||
|
// user sees the real error
|
||||||
|
return expectedResultType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ( expectedResultType != null ) {
|
||||||
|
// assume we can repackage the tuple as
|
||||||
|
// the given type (worry about how later)
|
||||||
|
return expectedResultType;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections();
|
// for JPA compatibility
|
||||||
return selections.size() == 1
|
return Object[].class;
|
||||||
? selections.get(0).getNodeJavaType().getJavaTypeClass()
|
|
||||||
: Object[].class;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.hibernate.sql.results.internal;
|
||||||
|
|
||||||
|
import jakarta.persistence.TupleElement;
|
||||||
|
import org.hibernate.InstantiationException;
|
||||||
|
import org.hibernate.sql.results.spi.RowTransformer;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link RowTransformer} instantiating an arbitrary class
|
||||||
|
*
|
||||||
|
* @author Gavin King
|
||||||
|
*/
|
||||||
|
public class RowTransformerConstructorImpl<T> implements RowTransformer<T> {
|
||||||
|
private final Class<T> type;
|
||||||
|
private final TupleMetadata tupleMetadata;
|
||||||
|
private final Constructor<T> constructor;
|
||||||
|
|
||||||
|
public RowTransformerConstructorImpl(Class<T> type, TupleMetadata tupleMetadata) {
|
||||||
|
this.type = type;
|
||||||
|
this.tupleMetadata = tupleMetadata;
|
||||||
|
final List<TupleElement<?>> elements = tupleMetadata.getList();
|
||||||
|
final Class<?>[] sig = new Class[elements.size()];
|
||||||
|
for (int i = 0; i < elements.size(); i++) {
|
||||||
|
sig[i] = elements.get(i).getJavaType();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
constructor = type.getDeclaredConstructor( sig );
|
||||||
|
constructor.setAccessible( true );
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new InstantiationException( "Cannot instantiate query result type ", type, e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T transformRow(Object[] row) {
|
||||||
|
try {
|
||||||
|
return constructor.newInstance( row );
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new InstantiationException( "Cannot instantiate query result type", type, e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int determineNumberOfResultElements(int rawElementCount) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,9 @@ import org.hibernate.sql.results.spi.RowTransformer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RowTransformer used when an array is explicitly specified as the return type
|
* {@link RowTransformer} instantiating a {@link List}
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
public class RowTransformerListImpl<T> implements RowTransformer<List<Object>> {
|
public class RowTransformerListImpl<T> implements RowTransformer<List<Object>> {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package org.hibernate.sql.results.internal;
|
package org.hibernate.sql.results.internal;
|
||||||
|
|
||||||
import jakarta.persistence.Tuple;
|
|
||||||
import jakarta.persistence.TupleElement;
|
import jakarta.persistence.TupleElement;
|
||||||
import org.hibernate.sql.results.spi.RowTransformer;
|
import org.hibernate.sql.results.spi.RowTransformer;
|
||||||
|
|
||||||
|
@ -16,9 +15,9 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RowTransformer generating a JPA {@link Tuple}
|
* {@link RowTransformer} instantiating a {@link Map}
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
public class RowTransformerMapImpl implements RowTransformer<Map<String,Object>> {
|
public class RowTransformerMapImpl implements RowTransformer<Map<String,Object>> {
|
||||||
private final TupleMetadata tupleMetadata;
|
private final TupleMetadata tupleMetadata;
|
||||||
|
|
|
@ -29,9 +29,4 @@ public class RowTransformerTupleTransformerAdapter<T> implements RowTransformer<
|
||||||
assert aliases == null || row.length == aliases.length;
|
assert aliases == null || row.length == aliases.length;
|
||||||
return tupleTransformer.transformTuple( row, aliases );
|
return tupleTransformer.transformTuple( row, aliases );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int determineNumberOfResultElements(int rawElementCount) {
|
|
||||||
return rawElementCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,34 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@SessionFactory
|
@SessionFactory
|
||||||
public class ImplicitInstantiationTest {
|
public class ImplicitInstantiationTest {
|
||||||
|
|
||||||
|
static class Record {
|
||||||
|
Long id;
|
||||||
|
String name;
|
||||||
|
public Record(Long id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
Long id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecordInstantiationWithoutAlias(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
session.persist(new Thing(1L, "thing"));
|
||||||
|
Record result = session.createSelectionQuery("select id, upper(name) from Thing", Record.class).getSingleResult();
|
||||||
|
assertEquals( result.id(), 1L );
|
||||||
|
assertEquals( result.name(), "THING" );
|
||||||
|
session.getTransaction().setRollbackOnly();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTupleInstantiationWithAlias(SessionFactoryScope scope) {
|
public void testTupleInstantiationWithAlias(SessionFactoryScope scope) {
|
||||||
scope.inTransaction(
|
scope.inTransaction(
|
||||||
|
|
Loading…
Reference in New Issue