1717import java .util .function .Consumer ;
1818import java .util .function .Supplier ;
1919
20+ import org .checkerframework .checker .nullness .qual .Nullable ;
2021import org .hibernate .CacheMode ;
2122import org .hibernate .FlushMode ;
23+ import org .hibernate .jpa .spi .NativeQueryConstructorTransformer ;
24+ import org .hibernate .jpa .spi .NativeQueryListTransformer ;
25+ import org .hibernate .jpa .spi .NativeQueryMapTransformer ;
2226import org .hibernate .metamodel .spi .MappingMetamodelImplementor ;
2327import org .hibernate .query .QueryFlushMode ;
2428import org .hibernate .HibernateException ;
105109import jakarta .persistence .TypedQuery ;
106110import jakarta .persistence .metamodel .SingularAttribute ;
107111import org .hibernate .type .BasicTypeRegistry ;
112+ import org .hibernate .type .descriptor .java .JavaType ;
113+ import org .hibernate .type .descriptor .java .spi .UnknownBasicJavaType ;
108114import org .hibernate .type .spi .TypeConfiguration ;
109115
110116import static java .lang .Character .isWhitespace ;
111117import static java .util .Collections .addAll ;
118+ import static org .hibernate .internal .util .ReflectHelper .isClass ;
112119import static org .hibernate .internal .util .StringHelper .unqualify ;
113120import static org .hibernate .internal .util .collections .CollectionHelper .isEmpty ;
114121import static org .hibernate .internal .util .collections .CollectionHelper .isNotEmpty ;
115122import static org .hibernate .internal .util .collections .CollectionHelper .makeCopy ;
116123import static org .hibernate .jpa .HibernateHints .HINT_NATIVE_LOCK_MODE ;
117124import static org .hibernate .query .results .internal .Builders .resultClassBuilder ;
118125import static org .hibernate .query .results .ResultSetMapping .resolveResultSetMapping ;
126+ import static org .hibernate .query .sqm .internal .SqmUtil .isResultTypeAlwaysAllowed ;
119127
120128/**
121129 * @author Steve Ebersole
@@ -129,6 +137,7 @@ public class NativeQueryImpl<R>
129137 private final List <ParameterOccurrence > parameterOccurrences ;
130138 private final QueryParameterBindings parameterBindings ;
131139
140+ private final Class <R > resultType ;
132141 private final ResultSetMapping resultSetMapping ;
133142 private final boolean resultMappingSuppliedToCtor ;
134143
@@ -166,6 +175,7 @@ public NativeQueryImpl(
166175 return false ;
167176 }
168177 },
178+ null ,
169179 session
170180 );
171181 }
@@ -218,26 +228,9 @@ public NativeQueryImpl(
218228 return false ;
219229 }
220230 },
231+ resultJavaType ,
221232 session
222233 );
223-
224- if ( resultJavaType == Tuple .class ) {
225- setTupleTransformer ( new NativeQueryTupleTransformer () );
226- }
227- else if ( resultJavaType != null && !resultJavaType .isArray () ) {
228- switch ( resultSetMapping .getNumberOfResultBuilders () ) {
229- case 0 :
230- throw new IllegalArgumentException ( "Named query exists, but did not specify a resultClass" );
231- case 1 :
232- final Class <?> actualResultJavaType = resultSetMapping .getResultBuilders ().get ( 0 ).getJavaType ();
233- if ( actualResultJavaType != null && !resultJavaType .isAssignableFrom ( actualResultJavaType ) ) {
234- throw buildIncompatibleException ( resultJavaType , actualResultJavaType );
235- }
236- break ;
237- default :
238- throw new IllegalArgumentException ( "Cannot create TypedQuery for query with more than one return" );
239- }
240- }
241234 }
242235
243236 /**
@@ -258,6 +251,7 @@ public NativeQueryImpl(
258251 mappingMemento .resolve ( resultSetMapping , querySpaceConsumer , context );
259252 return true ;
260253 },
254+ null ,
261255 session
262256 );
263257
@@ -268,6 +262,15 @@ public NativeQueryImpl(
268262 Supplier <ResultSetMapping > resultSetMappingCreator ,
269263 ResultSetMappingHandler resultSetMappingHandler ,
270264 SharedSessionContractImplementor session ) {
265+ this ( memento , resultSetMappingCreator , resultSetMappingHandler , null , session );
266+ }
267+
268+ public NativeQueryImpl (
269+ NamedNativeQueryMemento <?> memento ,
270+ Supplier <ResultSetMapping > resultSetMappingCreator ,
271+ ResultSetMappingHandler resultSetMappingHandler ,
272+ @ Nullable Class <R > resultType ,
273+ SharedSessionContractImplementor session ) {
271274 super ( session );
272275
273276 this .originalSqlString = memento .getOriginalSqlString ();
@@ -279,13 +282,35 @@ public NativeQueryImpl(
279282 this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
280283 this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
281284 this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
285+ this .resultType = resultType ;
282286 this .querySpaces = new HashSet <>();
283287
284288 this .resultSetMapping = resultSetMappingCreator .get ();
285289
286290 this .resultMappingSuppliedToCtor =
287291 resultSetMappingHandler .resolveResultSetMapping ( resultSetMapping , querySpaces ::add , this );
288292
293+ if ( resultType != null ) {
294+ if ( !isResultTypeAlwaysAllowed ( resultType ) ) {
295+ switch ( resultSetMapping .getNumberOfResultBuilders () ) {
296+ case 0 :
297+ throw new IllegalArgumentException ( "Named query exists, but did not specify a resultClass" );
298+ case 1 :
299+ final Class <?> actualResultJavaType = resultSetMapping .getResultBuilders ().get ( 0 )
300+ .getJavaType ();
301+ if ( actualResultJavaType != null && !resultType .isAssignableFrom ( actualResultJavaType ) ) {
302+ throw buildIncompatibleException ( resultType , actualResultJavaType );
303+ }
304+ break ;
305+ default :
306+ throw new IllegalArgumentException (
307+ "Cannot create TypedQuery for query with more than one return" );
308+ }
309+ }
310+ else {
311+ setTupleTransformerForResultType ( resultType );
312+ }
313+ }
289314 applyOptions ( memento );
290315 }
291316
@@ -301,6 +326,7 @@ public NativeQueryImpl(
301326 this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
302327 this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
303328 this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
329+ this .resultType = null ;
304330 this .querySpaces = new HashSet <>();
305331
306332 this .resultSetMapping = buildResultSetMapping ( resultSetMappingMemento .getName (), false , session );
@@ -310,6 +336,10 @@ public NativeQueryImpl(
310336 }
311337
312338 public NativeQueryImpl (String sqlString , SharedSessionContractImplementor session ) {
339+ this ( sqlString , null , session );
340+ }
341+
342+ public NativeQueryImpl (String sqlString , @ Nullable Class <R > resultType , SharedSessionContractImplementor session ) {
313343 super ( session );
314344
315345 this .querySpaces = new HashSet <>();
@@ -320,11 +350,46 @@ public NativeQueryImpl(String sqlString, SharedSessionContractImplementor sessio
320350 this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
321351 this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
322352 this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
353+ this .resultType = resultType ;
354+ if ( resultType != null ) {
355+ setTupleTransformerForResultType ( resultType );
356+ }
323357
324358 this .resultSetMapping = resolveResultSetMapping ( sqlString , true , session .getFactory () );
325359 this .resultMappingSuppliedToCtor = false ;
326360 }
327361
362+ protected <T > void setTupleTransformerForResultType (Class <T > resultClass ) {
363+ final TupleTransformer <?> tupleTransformer = determineTupleTransformerForResultType ( resultClass );
364+ if ( tupleTransformer != null ) {
365+ setTupleTransformer ( tupleTransformer );
366+ }
367+ }
368+
369+ protected @ Nullable TupleTransformer <?> determineTupleTransformerForResultType (Class <?> resultClass ) {
370+ if ( Tuple .class .equals ( resultClass ) ) {
371+ return NativeQueryTupleTransformer .INSTANCE ;
372+ }
373+ else if ( Map .class .equals ( resultClass ) ) {
374+ return NativeQueryMapTransformer .INSTANCE ;
375+ }
376+ else if ( List .class .equals ( resultClass ) ) {
377+ return NativeQueryListTransformer .INSTANCE ;
378+ }
379+ else if ( resultClass != Object .class && resultClass != Object [].class ) {
380+ if ( isClass ( resultClass ) && !hasJavaTypeDescriptor ( resultClass ) ) {
381+ // not a basic type
382+ return new NativeQueryConstructorTransformer <>( resultClass );
383+ }
384+ }
385+ return null ;
386+ }
387+
388+ private <T > boolean hasJavaTypeDescriptor (Class <T > resultClass ) {
389+ final JavaType <Object > descriptor = getTypeConfiguration ().getJavaTypeRegistry ().findDescriptor ( resultClass );
390+ return descriptor != null && descriptor .getClass () != UnknownBasicJavaType .class ;
391+ }
392+
328393 @ FunctionalInterface
329394 private interface ResultSetMappingHandler {
330395 boolean resolveResultSetMapping (
@@ -436,11 +501,16 @@ public QueryParameterBindings getParameterBindings() {
436501 return getQueryParameterBindings ();
437502 }
438503
504+ @ Override
505+ public Class <R > getResultType () {
506+ return resultType ;
507+ }
508+
439509 @ Override
440510 public NamedNativeQueryMemento <?> toMemento (String name ) {
441511 return new NamedNativeQueryMementoImpl <>(
442512 name ,
443- extractResultClass ( resultSetMapping ),
513+ resultType != null ? resultType : extractResultClass ( resultSetMapping ),
444514 sqlString ,
445515 originalSqlString ,
446516 resultSetMapping .getMappingIdentifier (),
@@ -459,14 +529,14 @@ public NamedNativeQueryMemento<?> toMemento(String name) {
459529 );
460530 }
461531
462- private Class <? > extractResultClass (ResultSetMapping resultSetMapping ) {
532+ private Class <R > extractResultClass (ResultSetMapping resultSetMapping ) {
463533 final List <ResultBuilder > resultBuilders = resultSetMapping .getResultBuilders ();
464534 if ( resultBuilders .size () == 1 ) {
465535 final ResultBuilder resultBuilder = resultBuilders .get ( 0 );
466536 if ( resultBuilder instanceof ImplicitResultClassBuilder
467537 || resultBuilder instanceof ImplicitModelPartResultBuilderEntity
468538 || resultBuilder instanceof DynamicResultBuilderEntityCalculated ) {
469- return resultBuilder .getJavaType ();
539+ return ( Class < R >) resultBuilder .getJavaType ();
470540 }
471541 }
472542 return null ;
@@ -618,13 +688,28 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
618688 }
619689
620690 protected SelectQueryPlan <R > resolveSelectQueryPlan () {
691+ final ResultSetMapping mapping ;
692+ if ( resultType != null && resultSetMapping .isDynamic () && resultSetMapping .getNumberOfResultBuilders () == 0 ) {
693+ mapping = ResultSetMapping .resolveResultSetMapping ( originalSqlString , true , getSessionFactory () );
694+
695+ if ( getSessionFactory ().getMappingMetamodel ().isEntityClass ( resultType ) ) {
696+ mapping .addResultBuilder (
697+ Builders .entityCalculated ( unqualify ( resultType .getName () ), resultType .getName (),
698+ LockMode .READ , getSessionFactory () ) );
699+ }
700+ else if ( !isResultTypeAlwaysAllowed ( resultType ) ) {
701+ mapping .addResultBuilder ( Builders .resultClassBuilder ( resultType , getSessionFactory () ) );
702+ }
703+ }
704+ else {
705+ mapping = resultSetMapping ;
706+ }
621707 return isCacheableQuery ()
622- ? getInterpretationCache ()
623- .resolveSelectQueryPlan ( selectInterpretationsKey (), this ::createQueryPlan )
624- : createQueryPlan ();
708+ ? getInterpretationCache ().resolveSelectQueryPlan ( selectInterpretationsKey ( mapping ), () -> createQueryPlan ( mapping ) )
709+ : createQueryPlan ( mapping );
625710 }
626711
627- private NativeSelectQueryPlan <R > createQueryPlan () {
712+ private NativeSelectQueryPlan <R > createQueryPlan (ResultSetMapping resultSetMapping ) {
628713 final NativeSelectQueryDefinition <R > queryDefinition = new NativeSelectQueryDefinition <>() {
629714 final String sqlString = expandParameterLists ();
630715
@@ -834,7 +919,7 @@ public static int determineBindValueMaxCount(boolean paddingEnabled, int inExprL
834919 return bindValueMaxCount ;
835920 }
836921
837- private SelectInterpretationsKey selectInterpretationsKey () {
922+ private SelectInterpretationsKey selectInterpretationsKey (ResultSetMapping resultSetMapping ) {
838923 return new SelectInterpretationsKey (
839924 getQueryString (),
840925 resultSetMapping ,
0 commit comments