1919import java .lang .reflect .InvocationTargetException ;
2020import java .lang .reflect .Member ;
2121import java .lang .reflect .Method ;
22+ import java .lang .reflect .RecordComponent ;
2223import java .util .ArrayList ;
2324import java .util .Arrays ;
2425import java .util .Iterator ;
@@ -398,6 +399,121 @@ else if (hasIdClass && ID.equals(sort.property()))
398399 return combined ;
399400 }
400401
402+ /**
403+ * Generates the SELECT clause of the JPQL.
404+ *
405+ * @return the SELECT clause.
406+ */
407+ StringBuilder generateSelectClause () {
408+ StringBuilder q = new StringBuilder (200 );
409+ String o = entityVar ;
410+ String o_ = entityVar_ ;
411+
412+ String [] cols , selections = entityInfo .builder .provider .compat .getSelections (method );
413+ if (selections == null || selections .length == 0 ) {
414+ cols = null ;
415+ } else if (type == QueryInfo .Type .FIND_AND_DELETE ) {
416+ // TODO NLS message for error path once selections are supported function
417+ throw new UnsupportedOperationException ();
418+ } else {
419+ cols = new String [selections .length ];
420+ for (int i = 0 ; i < cols .length ; i ++) {
421+ String name = entityInfo .getAttributeName (selections [i ], true );
422+ cols [i ] = name == null ? selections [i ] : name ;
423+ }
424+ }
425+
426+ Class <?> singleType = getSingleResultType ();
427+
428+ if (singleType .isPrimitive ())
429+ singleType = QueryInfo .wrapperClassIfPrimitive (singleType );
430+
431+ q .append ("SELECT " );
432+
433+ if (cols == null || cols .length == 0 ) {
434+ if (singleType .isAssignableFrom (entityInfo .entityClass )
435+ || entityInfo .inheritance && entityInfo .entityClass .isAssignableFrom (singleType )) {
436+ // Whole entity
437+ q .append (o );
438+ } else {
439+ // Look for single entity attribute with the desired type:
440+ String singleAttributeName = null ;
441+ for (Map .Entry <String , Class <?>> entry : entityInfo .attributeTypes .entrySet ()) {
442+ Class <?> collectionElementType = entityInfo .collectionElementTypes .get (entry .getKey ());
443+ Class <?> attributeType = collectionElementType == null ? entry .getValue () : collectionElementType ;
444+ if (attributeType .isPrimitive ())
445+ attributeType = QueryInfo .wrapperClassIfPrimitive (attributeType );
446+ if (singleType .isAssignableFrom (attributeType )) {
447+ singleAttributeName = entry .getKey ();
448+ q .append (o_ ).append (singleAttributeName );
449+ break ;
450+ }
451+ }
452+
453+ if (singleAttributeName == null ) {
454+ // Construct new instance from IdClass, embeddable, or entity attributes.
455+ // It would be preferable if the spec included the Select annotation to explicitly identify parameters, but if that doesn't happen
456+ // TODO we could compare attribute types with known constructor to improve on guessing a correct order of parameters
457+ q .append ("NEW " ).append (singleType .getName ()).append ('(' );
458+ List <String > relAttrNames ;
459+ boolean first = true ;
460+ if (entityInfo .idClassAttributeAccessors != null && singleType .equals (entityInfo .idType ))
461+ for (String idClassAttributeName : entityInfo .idClassAttributeAccessors .keySet ()) {
462+ String name = entityInfo .getAttributeName (idClassAttributeName , true );
463+ q .append (first ? "" : ", " ).append (o ).append ('.' ).append (name );
464+ first = false ;
465+ }
466+ else if ((relAttrNames = entityInfo .relationAttributeNames .get (singleType )) != null )
467+ for (String name : relAttrNames ) {
468+ q .append (first ? "" : ", " ).append (o ).append ('.' ).append (name );
469+ first = false ;
470+ }
471+ else if (entityInfo .recordClass == null )
472+ for (String name : entityInfo .attributeTypes .keySet ()) {
473+ q .append (first ? "" : ", " ).append (o ).append ('.' ).append (name );
474+ first = false ;
475+ }
476+ else {
477+ for (RecordComponent component : entityInfo .recordClass .getRecordComponents ()) {
478+ String name = component .getName ();
479+ q .append (first ? "" : ", " ).append (o ).append ('.' ).append (name );
480+ first = false ;
481+ }
482+ }
483+ q .append (')' );
484+ }
485+ }
486+ } else { // Individual columns are requested by @Select
487+ Class <?> entityType = entityInfo .getType ();
488+ boolean selectAsColumns = singleType .isAssignableFrom (entityType )
489+ || singleType .isInterface () // NEW instance doesn't apply to interfaces
490+ || singleType .isPrimitive () // NEW instance should not be used on primitives
491+ || singleType .getName ().startsWith ("java" ) // NEW instance constructor is unlikely for non-user-defined classes
492+ || entityInfo .inheritance && entityType .isAssignableFrom (singleType );
493+ if (!selectAsColumns && cols .length == 1 ) {
494+ String singleAttributeName = cols [0 ];
495+ Class <?> attributeType = entityInfo .collectionElementTypes .get (singleAttributeName );
496+ if (attributeType == null )
497+ attributeType = entityInfo .attributeTypes .get (singleAttributeName );
498+ selectAsColumns = attributeType != null && (Object .class .equals (attributeType ) // JPA metamodel does not preserve the type if not an EmbeddableCollection
499+ || singleType .isAssignableFrom (attributeType ));
500+ }
501+ if (selectAsColumns ) {
502+ // Specify columns without creating new instance
503+ for (int i = 0 ; i < cols .length ; i ++)
504+ q .append (i == 0 ? "" : ", " ).append (o ).append ('.' ).append (cols [i ]);
505+ } else {
506+ // Construct new instance from defined columns
507+ q .append ("NEW " ).append (singleType .getName ()).append ('(' );
508+ for (int i = 0 ; i < cols .length ; i ++)
509+ q .append (i == 0 ? "" : ", " ).append (o ).append ('.' ).append (cols [i ]);
510+ q .append (')' );
511+ }
512+ }
513+
514+ return q ;
515+ }
516+
401517 /**
402518 * Locate the entity information for the specified result class.
403519 *
@@ -417,7 +533,7 @@ EntityInfo getEntityInfo(Class<?> entityType, Map<String, CompletableFuture<Enti
417533 failedFuture = future ;
418534 } else {
419535 EntityInfo info = future .join ();
420- if (entityType .equals (info .entityClass ))
536+ if (entityType .equals (info .entityClass ) || entityType . equals ( info . recordClass ) )
421537 return info ;
422538 }
423539 if (failedFuture != null )
@@ -930,17 +1046,16 @@ else if (order0 >= 0 && orderLen == 0)
9301046 whereLen += 2 + (addSpace ? 1 : 0 );
9311047 }
9321048
933- StringBuilder q = new StringBuilder (ql .length () + (selectLen >= 0 ? 0 : 50 ) + (fromLen >= 0 ? 0 : 50 ) + 2 );
934- q .append ("SELECT" );
1049+ StringBuilder q ;
9351050 if (selectLen > 0 ) {
1051+ q = new StringBuilder (ql .length () + (selectLen >= 0 ? 0 : 50 ) + (fromLen >= 0 ? 0 : 50 ) + 2 );
1052+ q .append ("SELECT" );
9361053 appendWithIdentifierName (ql , select0 , select0 + selectLen , q );
937- if (fromLen == 0 && whereLen == 0 && orderLen == 0 )
938- q .append (' ' );
9391054 } else {
940- q . append ( ' ' ). append ( entityVar ). append ( ' ' );
1055+ q = generateSelectClause ( );
9411056 }
9421057
943- q .append ("FROM" );
1058+ q .append (" FROM" );
9441059 if (fromLen > 0 && !lacksEntityVar )
9451060 q .append (ql .substring (from0 , from0 + fromLen ));
9461061 else
0 commit comments